feat(@projects/@magic-civilization): add harvest policy system

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Natalie 2026-05-03 20:30:43 -04:00
parent 24ed631a60
commit d702b1ebae
6 changed files with 533 additions and 1 deletions

View file

@ -9,6 +9,7 @@ import { SpecialistsPage } from "./pages/Specialists";
import { FreepeopleLifecyclePage } from "./pages/FreepeopleLifecycle";
import { TerrainEcologyPage } from "./pages/TerrainEcology";
import { FoodWebPage } from "./pages/FoodWeb";
import { HarvestPoliciesPage } from "./pages/HarvestPolicies";
import { CombatPreviewPage } from "./pages/CombatPreview";
import { CombatCalculatorPage } from "./pages/CombatCalculator";
import { PermutationsPage } from "./pages/Permutations";
@ -70,6 +71,7 @@ export function App(): React.ReactElement {
<Route path="/freepeople-lifecycle" element={<FreepeopleLifecyclePage />} />
<Route path="/terrain-ecology" element={<TerrainEcologyPage />} />
<Route path="/food-web" element={<FoodWebPage />} />
<Route path="/harvest-policies" element={<HarvestPoliciesPage />} />
<Route path="/trees" element={<BuildingTreesPage />} />
<Route path="/promotion" element={<PromotionPickerPage />} />
<Route path="/stats" element={<StatisticsPage />} />

View file

@ -30,6 +30,22 @@ function summariseYields(ys: SpecialistYield[]): string {
}).join(" ");
}
// ── Real harvest-policy data load ────────────────────────────────────────────
interface HarvestPolicy {
id: string;
name: string;
icon: string;
color: string;
yield_multiplier: { food: number; production: number; trade: number; culture: number };
biome_quality_delta_per_turn: number;
}
const policyModules = import.meta.glob<{ default: HarvestPolicy[] }>(
"../../../../../public/resources/harvest_policies/*.json",
{ eager: true },
);
const ALL_POLICIES: HarvestPolicy[] = Object.values(policyModules).flatMap(m => m.default);
const POLICY_BY_ID = new Map(ALL_POLICIES.map(p => [p.id, p]));
const MapBleed = styled.div`
position: fixed;
inset: 0;
@ -465,6 +481,35 @@ export function CityScreenPage(): React.ReactElement {
</CitizenRow>
</CitizensSection>
<SectionHead>Tile Harvest Policies</SectionHead>
<CitizensSection>
{/* Real per-tile policies — driven by harvest_policies.json */}
{[
{ tile: "🌲 Forest (0,3)", policyId: "extract_sustainable" },
{ tile: "🌲 Forest (4,1)", policyId: "replenish" },
{ tile: "🌲 Boreal (5,2)", policyId: "extract_high" },
{ tile: "🌳 Wooded Foothills (1,2)", policyId: "remove_chop" },
].map((row, i) => {
const p = POLICY_BY_ID.get(row.policyId);
if (!p) return null;
return (
<CitizenRow key={i}>
<CitizenIcon>{p.icon}</CitizenIcon>
<span style={{ minWidth: 100, color: p.color, fontWeight: 600 }}>{p.name}</span>
<CitizenTile>{row.tile}</CitizenTile>
<CitizenYields>
🌾×{p.yield_multiplier.food} ×{p.yield_multiplier.production}
{p.biome_quality_delta_per_turn !== 0 && (
<span style={{ marginLeft: 8, color: p.biome_quality_delta_per_turn > 0 ? "#7cd9a0" : "#d95940" }}>
biome {p.biome_quality_delta_per_turn > 0 ? "+" : ""}{p.biome_quality_delta_per_turn}
</span>
)}
</CitizenYields>
</CitizenRow>
);
})}
</CitizensSection>
<SectionHead>City Stats</SectionHead>
<StatsGrid>
<StatCard><StatCardLabel>City HP</StatCardLabel><StatCardVal $color={t.sem.positive}>200</StatCardVal><StatCardSub>/ 200 max · Walled</StatCardSub></StatCard>

View file

@ -0,0 +1,301 @@
import { type ReactElement } from "react";
import { Link } from "react-router-dom";
import styled from "styled-components";
import { t } from "../theme";
interface YieldMul { food: number; production: number; trade: number; culture: number }
interface OneTimeYield { production_base: number; gold_base: number; quality_tier_multiplier: number }
interface TerrainTransform { default: string; from_hills?: string; from_jungle?: string; from_swamp?: string }
interface Policy {
id: string;
name: string;
description: string;
icon: string;
color: string;
yield_multiplier: YieldMul;
biome_quality_delta_per_turn: number;
one_time_yield: OneTimeYield | null;
terrain_transform: TerrainTransform | null;
fauna_population_delta: number;
flora_density_delta: number;
applicable_terrains: string[];
flavor: string;
}
import policiesJson from "@resources/harvest_policies/policies.json";
const POLICIES = policiesJson as readonly Policy[];
const PageWrap = styled.div`
background: ${t.bg.menu};
min-height: 100vh;
color: ${t.text.primary};
font-family: ${t.font.body};
padding-bottom: 60px;
`;
const Header = styled.div`
background: ${t.bg.panel};
border-bottom: 1px solid ${t.border.panel};
padding: 14px 28px;
display: flex; align-items: center; gap: 24px;
`;
const HeaderTitle = styled.div`
font-family: ${t.font.heading};
font-size: 24px; color: ${t.text.title};
letter-spacing: 0.04em;
`;
const BackLink = styled(Link)`
color: ${t.text.muted}; text-decoration: none;
font-size: 13px; font-family: ${t.font.mono};
&:hover { color: ${t.accent.gold}; }
`;
const SourceTag = styled.div`
margin-left: auto; font-family: ${t.font.mono};
font-size: 11px; color: ${t.text.muted};
`;
const Content = styled.div`
max-width: 1200px; margin: 0 auto;
padding: 28px 24px;
display: flex; flex-direction: column; gap: 24px;
`;
const Intro = styled.div`
background: ${t.bg.panel};
border: 1px solid ${t.border.panel};
border-radius: ${t.radius.panel};
padding: 14px 18px;
font-size: 13px; color: ${t.text.secondary};
line-height: 1.6;
& strong { color: ${t.accent.gold}; }
& code { color: ${t.accent.science}; font-family: ${t.font.mono}; font-size: 12px; }
`;
const Grid = styled.div`
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 14px;
`;
const Card = styled.div<{ $color: string }>`
background: ${t.bg.panel};
border: 1px solid ${p => p.$color + "55"};
border-top: 4px solid ${p => p.$color};
border-radius: ${t.radius.panel};
padding: 14px 16px;
display: flex; flex-direction: column; gap: 8px;
`;
const CardHead = styled.div`
display: flex; align-items: baseline; gap: 10px;
border-bottom: 1px solid ${t.border.divider};
padding-bottom: 8px;
`;
const Icon = styled.div<{ $color: string }>`
font-size: 28px; color: ${p => p.$color};
`;
const Name = styled.div`
flex: 1;
font-family: ${t.font.heading};
font-size: 16px;
color: ${t.text.primary};
`;
const Desc = styled.div`
font-size: 12px; color: ${t.text.secondary};
line-height: 1.55;
`;
const Flavor = styled.div`
font-size: 11px; color: ${t.text.muted};
font-style: italic; line-height: 1.5;
border-left: 2px solid ${t.border.divider};
padding-left: 8px;
`;
const Stats = styled.div`
display: flex; flex-wrap: wrap; gap: 6px;
`;
const Stat = styled.span<{ $color: string }>`
display: inline-flex; align-items: baseline; gap: 4px;
padding: 2px 8px; border-radius: 8px;
font-size: 10px; font-family: ${t.font.mono};
background: ${p => p.$color + "22"};
color: ${p => p.$color};
border: 1px solid ${p => p.$color + "55"};
`;
const Section = styled.div`
display: flex; flex-direction: column; gap: 8px;
`;
const Sub = styled.div`
font-size: 10px; font-family: ${t.font.mono};
text-transform: uppercase; letter-spacing: 0.06em;
color: ${t.text.muted};
`;
const Header2 = styled.h2`
font-family: ${t.font.heading};
font-size: 16px; color: ${t.accent.gold};
margin: 12px 0 4px;
letter-spacing: 0.04em;
`;
const Table = styled.table`
width: 100%; border-collapse: collapse;
font-size: 12px; font-family: ${t.font.mono};
th, td {
padding: 6px 12px; text-align: center;
border-bottom: 1px solid ${t.border.divider};
}
th {
color: ${t.text.muted};
font-size: 10px; text-transform: uppercase;
letter-spacing: 0.06em; font-weight: 600;
}
td { color: ${t.text.primary}; }
th:first-child, td:first-child { text-align: left; }
`;
const fmtMul = (x: number) => x === 1 ? "1×" : x === 0 ? "—" : x.toFixed(2).replace(/\.?0+$/, "") + "×";
const fmtDelta = (x: number) => x === 0 ? "0" : (x > 0 ? "+" : "") + x.toFixed(2);
export function HarvestPoliciesPage(): ReactElement {
return (
<PageWrap>
<Header>
<BackLink to="/"> back</BackLink>
<HeaderTitle>Harvest Policies</HeaderTitle>
<SourceTag>
driven by @resources/harvest_policies/policies.json · {POLICIES.length} policies
</SourceTag>
</Header>
<Content>
<Intro>
Each tile worked by a city carries a <strong>harvest policy</strong> a per-tile setting
that controls how aggressively citizens extract from the land. Civ5 had only one option
(work the tile, or chop it once). We have <strong>four</strong>, and the choice ripples
forward through ecology dynamics: a tile under <code>extract_high</code> for too many
turns degrades; a tile under <code>replenish</code> recovers; a tile chopped via
<code> remove_chop</code> gives one burst of production and is gone.
<br /><br />
Policies are assigned in the City Screen and persist until changed. The simulator
(<code>mc-ecology::dynamics</code>) reads <code>biome_quality_delta_per_turn</code> +
<code> fauna_population_delta</code> + <code>flora_density_delta</code> per active policy
and applies them per turn to the tile state.
</Intro>
<Header2>The Four Policies</Header2>
<Grid>
{POLICIES.map(p => (
<Card key={p.id} $color={p.color}>
<CardHead>
<Icon $color={p.color}>{p.icon}</Icon>
<Name>{p.name}</Name>
</CardHead>
<Desc>{p.description}</Desc>
<Section>
<Sub>yield multiplier</Sub>
<Stats>
<Stat $color="#7cd9a0">🌾 food {fmtMul(p.yield_multiplier.food)}</Stat>
<Stat $color="#e69933"> prod {fmtMul(p.yield_multiplier.production)}</Stat>
<Stat $color="#ccbf73">🪙 trade {fmtMul(p.yield_multiplier.trade)}</Stat>
<Stat $color="#a07cc9"> culture {fmtMul(p.yield_multiplier.culture)}</Stat>
</Stats>
</Section>
<Section>
<Sub>per-turn ecology delta</Sub>
<Stats>
<Stat $color={p.biome_quality_delta_per_turn >= 0 ? "#7cd9a0" : "#d95940"}>
biome quality {fmtDelta(p.biome_quality_delta_per_turn)}
</Stat>
<Stat $color={p.flora_density_delta >= 0 ? "#7cd9a0" : "#d95940"}>
flora {fmtDelta(p.flora_density_delta)}
</Stat>
<Stat $color={p.fauna_population_delta >= 0 ? "#7cd9a0" : "#d95940"}>
fauna {fmtDelta(p.fauna_population_delta)}
</Stat>
</Stats>
</Section>
{p.one_time_yield && (
<Section>
<Sub>one-time yield (consumes tile)</Sub>
<Stats>
<Stat $color="#e69933"> +{p.one_time_yield.production_base} prod (× tier)</Stat>
<Stat $color="#ccbf73">🪙 +{p.one_time_yield.gold_base} gold (× tier)</Stat>
</Stats>
</Section>
)}
{p.terrain_transform && (
<Section>
<Sub>terrain transform on apply</Sub>
<Stats>
<Stat $color="#888"> {p.terrain_transform.default}</Stat>
{p.terrain_transform.from_hills && <Stat $color="#888">hills {p.terrain_transform.from_hills}</Stat>}
{p.terrain_transform.from_jungle && <Stat $color="#888">jungle {p.terrain_transform.from_jungle}</Stat>}
{p.terrain_transform.from_swamp && <Stat $color="#888">swamp {p.terrain_transform.from_swamp}</Stat>}
</Stats>
</Section>
)}
<Section>
<Sub>applies to {p.applicable_terrains.length} terrain types</Sub>
<Stats>
{p.applicable_terrains.slice(0, 6).map(tr => <Stat key={tr} $color="#888">{tr}</Stat>)}
{p.applicable_terrains.length > 6 && <Stat $color="#888">+{p.applicable_terrains.length - 6} more</Stat>}
</Stats>
</Section>
<Flavor>"{p.flavor}"</Flavor>
</Card>
))}
</Grid>
<Header2>Comparison Matrix</Header2>
<Table>
<thead>
<tr>
<th>Policy</th>
<th>food ×</th>
<th>prod ×</th>
<th>biome Δ/turn</th>
<th>flora Δ</th>
<th>fauna Δ</th>
<th>one-time</th>
<th>transforms?</th>
</tr>
</thead>
<tbody>
{POLICIES.map(p => (
<tr key={p.id}>
<td style={{ color: p.color, fontWeight: 600 }}>{p.icon} {p.name}</td>
<td>{fmtMul(p.yield_multiplier.food)}</td>
<td>{fmtMul(p.yield_multiplier.production)}</td>
<td style={{ color: p.biome_quality_delta_per_turn === 0 ? "#888" : (p.biome_quality_delta_per_turn > 0 ? "#7cd9a0" : "#d95940") }}>
{fmtDelta(p.biome_quality_delta_per_turn)}
</td>
<td>{fmtDelta(p.flora_density_delta)}</td>
<td>{fmtDelta(p.fauna_population_delta)}</td>
<td>{p.one_time_yield ? `+${p.one_time_yield.production_base}p +${p.one_time_yield.gold_base}g` : "—"}</td>
<td>{p.terrain_transform ? `${p.terrain_transform.default}` : "—"}</td>
</tr>
))}
</tbody>
</Table>
<Header2>Strategic Use</Header2>
<Intro>
<strong>Replenishment</strong> is the long game a tile under replenish for ~10 turns
recovers fully and becomes higher-tier than baseline. Pair with the <em>Outsider's Parley</em>
policy to keep neighboring freepeople from raiding (over-extraction shifts them toward
raiding per the ecology coupling rules).
<br /><br />
<strong>Sustainable</strong> is the dwarven default. No ecology consequences. Most tiles run
sustainable by default unless deliberately changed.
<br /><br />
<strong>High Extraction</strong> is the war economy lever. Pair with culture policy
<em> war_economy</em> (statecraft e8) for mobilization; expect biome degradation and possible
freepeople hostility within 510 turns.
<br /><br />
<strong>Removal</strong> is the Civ5 chop a one-time burst that converts the tile to
grassland (or plains, if hilly). Use to start a high-value city in a forest, or to free up
adjacency for a wonder. Cannot be reversed without the <code>reforestation</code> improvement.
</Intro>
</Content>
</PageWrap>
);
}

View file

@ -120,6 +120,7 @@ const routeCategories: RouteCategory[] = [
{ path: "/freepeople-lifecycle", label: "❀ Freepeople Lifecycle — wanderers → camps → city-states; tribute diplomacy" },
{ path: "/terrain-ecology", label: "🌳 Terrain & Ecology — terrain ↔ flora ↔ fauna ↔ wild creatures, grudge gate" },
{ path: "/food-web", label: "🍃 Food Web — trophic pyramid, prey chains, carrying capacity, ecology coupling" },
{ path: "/harvest-policies", label: "🌾 Harvest Policies — per-tile extraction settings, replenish/sustain/high/chop" },
],
},
{

View file

@ -1 +1 @@
{"root":["./src/app.tsx","./src/audiosynth.ts","./src/main.tsx","./src/theme.ts","./src/components/combat/combatantcard.tsx","./src/components/combat/damagematrix.tsx","./src/components/combat/hpafterbar.tsx","./src/components/combat/hpslider.tsx","./src/components/combat/kwbanner.tsx","./src/components/combat/modifierlist.tsx","./src/components/combat/probabilitybar.tsx","./src/components/combat/unitbrowser.tsx","./src/components/combat/unitrow.tsx","./src/components/tree/treeview.tsx","./src/components/tree/types.ts","./src/components/ui/button.tsx","./src/components/ui/panel.tsx","./src/components/ui/tabs.tsx","./src/components/ui/tag.tsx","./src/data/allbuildings.ts","./src/data/allunits.ts","./src/data/audiopacks.ts","./src/data/scenarios.ts","./src/data/units.ts","./src/pages/audiopackdetail.tsx","./src/pages/audiopacks.tsx","./src/pages/audiosystem.tsx","./src/pages/borders.tsx","./src/pages/buildingtrees.tsx","./src/pages/cityscreen.tsx","./src/pages/civilopedia.tsx","./src/pages/clanpersonality.tsx","./src/pages/combatcalculator.tsx","./src/pages/combatpreview.tsx","./src/pages/credits.tsx","./src/pages/culturetree.tsx","./src/pages/designgallery.tsx","./src/pages/diplomacy.tsx","./src/pages/empiredashboard.tsx","./src/pages/endgamesummary.tsx","./src/pages/eraprogression.tsx","./src/pages/foodweb.tsx","./src/pages/freepeoplelifecycle.tsx","./src/pages/gamesetup.tsx","./src/pages/gdrustbridge.tsx","./src/pages/gdrustmap.tsx","./src/pages/greatpeople.tsx","./src/pages/greatworks.tsx","./src/pages/hexformation.tsx","./src/pages/hud.tsx","./src/pages/index.tsx","./src/pages/mainmenu.tsx","./src/pages/notifications.tsx","./src/pages/pastgames.tsx","./src/pages/permutations.tsx","./src/pages/promotionpicker.tsx","./src/pages/replay.tsx","./src/pages/settings.tsx","./src/pages/specialists.tsx","./src/pages/statistics.tsx","./src/pages/techtree.tsx","./src/pages/terrainecology.tsx","./src/pages/throneroom.tsx","./src/pages/turnsummary.tsx","./src/pages/unitactions.tsx","./src/pages/worldgen.tsx","./src/pages/hud/minimap.tsx","./src/pages/hud/chrome.ts","./src/pages/hud/positions.ts","./src/pages/worldgen/biometransitions.tsx","./src/pages/worldgen/climate.tsx","./src/pages/worldgen/ecology.tsx","./src/pages/worldgen/hydrology.tsx","./src/pages/worldgen/lab.tsx","./src/pages/worldgen/mappanel.tsx","./src/pages/worldgen/noiseanatomy.tsx","./src/pages/worldgen/pipelinepanel.tsx","./src/pages/worldgen/presets.tsx","./src/pages/worldgen/rng.tsx","./src/pages/worldgen/substrate.tsx","./src/pages/worldgen/tectonics.tsx","./src/pages/worldgen/terraincatalog.tsx","./src/pages/worldgen/whittakerplot.tsx","./src/pages/worldgen/_layerpage.tsx","./src/pages/worldgen/lab/mapcanvas.tsx","./src/pages/worldgen/lab/overlaytoggles.tsx","./src/pages/worldgen/lab/presetbar.tsx","./src/pages/worldgen/lab/tileinspector.tsx","./src/pages/worldgen/lab/constants.ts","./src/pages/worldgen/lab/mapinteractions.ts","./src/pages/worldgen/lab/observations.ts","./src/pages/worldgen/lab/render.ts","./src/pages/worldgen/lab/types.ts","./src/utils/combatcalc.ts","./src/utils/wasm/smoke.ts","./src/utils/wasm/usewasmgrid.ts","./src/utils/worldgen/hexcanvas.test.ts","./src/utils/worldgen/hexcanvas.ts","./src/utils/worldgen/indicatordecorations.ts","./src/utils/worldgen/noise.ts","./src/utils/worldgen/poisson.ts","./src/utils/worldgen/terrain.ts","../../reports/gd-rust-relationships.json","../../../public/resources/audio/library.json","../../../public/games/age-of-dwarves/data/audio/manifest.json","../../../public/games/age-of-dwarves/data/audio/pools.json"],"version":"5.9.3"}
{"root":["./src/app.tsx","./src/audiosynth.ts","./src/main.tsx","./src/theme.ts","./src/components/combat/combatantcard.tsx","./src/components/combat/damagematrix.tsx","./src/components/combat/hpafterbar.tsx","./src/components/combat/hpslider.tsx","./src/components/combat/kwbanner.tsx","./src/components/combat/modifierlist.tsx","./src/components/combat/probabilitybar.tsx","./src/components/combat/unitbrowser.tsx","./src/components/combat/unitrow.tsx","./src/components/tree/treeview.tsx","./src/components/tree/types.ts","./src/components/ui/button.tsx","./src/components/ui/panel.tsx","./src/components/ui/tabs.tsx","./src/components/ui/tag.tsx","./src/data/allbuildings.ts","./src/data/allunits.ts","./src/data/audiopacks.ts","./src/data/scenarios.ts","./src/data/units.ts","./src/pages/audiopackdetail.tsx","./src/pages/audiopacks.tsx","./src/pages/audiosystem.tsx","./src/pages/borders.tsx","./src/pages/buildingtrees.tsx","./src/pages/cityscreen.tsx","./src/pages/civilopedia.tsx","./src/pages/clanpersonality.tsx","./src/pages/combatcalculator.tsx","./src/pages/combatpreview.tsx","./src/pages/credits.tsx","./src/pages/culturetree.tsx","./src/pages/designgallery.tsx","./src/pages/diplomacy.tsx","./src/pages/empiredashboard.tsx","./src/pages/endgamesummary.tsx","./src/pages/eraprogression.tsx","./src/pages/foodweb.tsx","./src/pages/freepeoplelifecycle.tsx","./src/pages/gamesetup.tsx","./src/pages/gdrustbridge.tsx","./src/pages/gdrustmap.tsx","./src/pages/greatpeople.tsx","./src/pages/greatworks.tsx","./src/pages/harvestpolicies.tsx","./src/pages/hexformation.tsx","./src/pages/hud.tsx","./src/pages/index.tsx","./src/pages/mainmenu.tsx","./src/pages/notifications.tsx","./src/pages/pastgames.tsx","./src/pages/permutations.tsx","./src/pages/promotionpicker.tsx","./src/pages/replay.tsx","./src/pages/settings.tsx","./src/pages/specialists.tsx","./src/pages/statistics.tsx","./src/pages/techtree.tsx","./src/pages/terrainecology.tsx","./src/pages/throneroom.tsx","./src/pages/turnsummary.tsx","./src/pages/unitactions.tsx","./src/pages/worldgen.tsx","./src/pages/hud/minimap.tsx","./src/pages/hud/chrome.ts","./src/pages/hud/positions.ts","./src/pages/worldgen/biometransitions.tsx","./src/pages/worldgen/climate.tsx","./src/pages/worldgen/ecology.tsx","./src/pages/worldgen/hydrology.tsx","./src/pages/worldgen/lab.tsx","./src/pages/worldgen/mappanel.tsx","./src/pages/worldgen/noiseanatomy.tsx","./src/pages/worldgen/pipelinepanel.tsx","./src/pages/worldgen/presets.tsx","./src/pages/worldgen/rng.tsx","./src/pages/worldgen/substrate.tsx","./src/pages/worldgen/tectonics.tsx","./src/pages/worldgen/terraincatalog.tsx","./src/pages/worldgen/whittakerplot.tsx","./src/pages/worldgen/_layerpage.tsx","./src/pages/worldgen/lab/mapcanvas.tsx","./src/pages/worldgen/lab/overlaytoggles.tsx","./src/pages/worldgen/lab/presetbar.tsx","./src/pages/worldgen/lab/tileinspector.tsx","./src/pages/worldgen/lab/constants.ts","./src/pages/worldgen/lab/mapinteractions.ts","./src/pages/worldgen/lab/observations.ts","./src/pages/worldgen/lab/render.ts","./src/pages/worldgen/lab/types.ts","./src/utils/combatcalc.ts","./src/utils/wasm/smoke.ts","./src/utils/wasm/usewasmgrid.ts","./src/utils/worldgen/hexcanvas.test.ts","./src/utils/worldgen/hexcanvas.ts","./src/utils/worldgen/indicatordecorations.ts","./src/utils/worldgen/noise.ts","./src/utils/worldgen/poisson.ts","./src/utils/worldgen/terrain.ts","../../reports/gd-rust-relationships.json","../../../public/resources/audio/library.json","../../../public/games/age-of-dwarves/data/audio/manifest.json","../../../public/games/age-of-dwarves/data/audio/pools.json"],"version":"5.9.3"}

View file

@ -0,0 +1,183 @@
[
{
"id": "replenish",
"name": "Replenishment",
"description": "Tile is set aside for regrowth. Citizens work it lightly — gathering only what falls or what the land freely gives. Yields are halved, but the biome heals over time and quality_tier slowly rises toward its terrain ceiling.",
"icon": "🌱",
"color": "#7cd9a0",
"yield_multiplier": {
"food": 0.5,
"production": 0.5,
"trade": 0.5,
"culture": 1
},
"biome_quality_delta_per_turn": 0.1,
"one_time_yield": null,
"terrain_transform": null,
"fauna_population_delta": 0.05,
"flora_density_delta": 0.05,
"applicable_terrains": [
"forest",
"jungle",
"boreal_forest",
"temperate_rainforest",
"tropical_rainforest",
"cloud_forest",
"grassland",
"plains",
"savanna",
"wooded_foothills",
"riverside_forest",
"swamp",
"bog"
],
"flavor": "The dwarves who tend the replenishment groves are elders. They do not rush the trees. The trees do not rush them.",
"encyclopedia": {
"category": "civilization",
"entry_type": "harvest_policy",
"detail_route": "/harvest-policies",
"tags": [
"harvest",
"sustainability"
]
}
},
{
"id": "extract_sustainable",
"name": "Sustainable Extraction",
"description": "Standard yield — citizens work the tile at the rate the land can sustain indefinitely. Biome holds steady; no degradation, no improvement.",
"icon": "🪓",
"color": "#ccbf73",
"yield_multiplier": {
"food": 1,
"production": 1,
"trade": 1,
"culture": 1
},
"biome_quality_delta_per_turn": 0,
"one_time_yield": null,
"terrain_transform": null,
"fauna_population_delta": 0,
"flora_density_delta": 0,
"applicable_terrains": [
"forest",
"jungle",
"boreal_forest",
"temperate_rainforest",
"tropical_rainforest",
"cloud_forest",
"grassland",
"plains",
"savanna",
"hills",
"wooded_foothills",
"riverside_forest",
"swamp",
"bog",
"mountains",
"tundra",
"alpine_meadow",
"alpine_tundra"
],
"flavor": "Take what the land offers. Leave what it needs. This is the dwarven default — older than coinage, older than runes.",
"encyclopedia": {
"category": "civilization",
"entry_type": "harvest_policy",
"detail_route": "/harvest-policies",
"tags": [
"harvest",
"default"
]
}
},
{
"id": "extract_high",
"name": "High Extraction",
"description": "Aggressive harvesting — +50% yield while the biome lasts, but quality_tier drops over time. Eventually the tile degrades into a lower-yield variant unless policy is changed.",
"icon": "⚒",
"color": "#e69933",
"yield_multiplier": {
"food": 1.5,
"production": 1.5,
"trade": 1.25,
"culture": 0.75
},
"biome_quality_delta_per_turn": -0.05,
"one_time_yield": null,
"terrain_transform": null,
"fauna_population_delta": -0.1,
"flora_density_delta": -0.08,
"applicable_terrains": [
"forest",
"jungle",
"boreal_forest",
"temperate_rainforest",
"tropical_rainforest",
"grassland",
"plains",
"savanna",
"hills",
"wooded_foothills",
"mountains",
"swamp"
],
"flavor": "When the front needs a thousand axe-shafts by winter, the deep groves are felled by spring.",
"encyclopedia": {
"category": "civilization",
"entry_type": "harvest_policy",
"detail_route": "/harvest-policies",
"tags": [
"harvest",
"war_economy"
]
}
},
{
"id": "remove_chop",
"name": "Removal (Chop)",
"description": "One-time conversion — clear-cut the tile for a burst of production and gold scaled to its quality_tier, then transform the terrain into grassland (or plains, if hilly). The Civ5 chop. Cannot be reversed without the Reforestation improvement.",
"icon": "🪵",
"color": "#d95940",
"yield_multiplier": {
"food": 0,
"production": 0,
"trade": 0,
"culture": 0
},
"biome_quality_delta_per_turn": 0,
"one_time_yield": {
"production_base": 60,
"gold_base": 25,
"quality_tier_multiplier": 1
},
"terrain_transform": {
"default": "grassland",
"from_hills": "plains",
"from_jungle": "grassland",
"from_swamp": "plains"
},
"fauna_population_delta": -1,
"flora_density_delta": -1,
"applicable_terrains": [
"forest",
"jungle",
"boreal_forest",
"temperate_rainforest",
"tropical_rainforest",
"wooded_foothills",
"riverside_forest",
"swamp",
"bog"
],
"flavor": "The forest gives one final gift, all at once. Then it is gone.",
"encyclopedia": {
"category": "civilization",
"entry_type": "harvest_policy",
"detail_route": "/harvest-policies",
"tags": [
"harvest",
"permanent"
]
}
}
]