From d702b1ebae172af1daa382a284dcae4c86a9e885 Mon Sep 17 00:00:00 2001 From: Natalie Date: Sun, 3 May 2026 20:30:43 -0400 Subject: [PATCH] =?UTF-8?q?feat(@projects/@magic-civilization):=20?= =?UTF-8?q?=E2=9C=A8=20add=20harvest=20policy=20system?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Lilith Autocommit --- .project/designs/app/src/App.tsx | 2 + .project/designs/app/src/pages/CityScreen.tsx | 45 +++ .../designs/app/src/pages/HarvestPolicies.tsx | 301 ++++++++++++++++++ .project/designs/app/src/pages/Index.tsx | 1 + .project/designs/app/tsconfig.tsbuildinfo | 2 +- .../resources/harvest_policies/policies.json | 183 +++++++++++ 6 files changed, 533 insertions(+), 1 deletion(-) create mode 100644 .project/designs/app/src/pages/HarvestPolicies.tsx create mode 100644 public/resources/harvest_policies/policies.json diff --git a/.project/designs/app/src/App.tsx b/.project/designs/app/src/App.tsx index 8d43518f..a18c5433 100644 --- a/.project/designs/app/src/App.tsx +++ b/.project/designs/app/src/App.tsx @@ -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 { } /> } /> } /> + } /> } /> } /> } /> diff --git a/.project/designs/app/src/pages/CityScreen.tsx b/.project/designs/app/src/pages/CityScreen.tsx index 5dbc1aba..8d979124 100644 --- a/.project/designs/app/src/pages/CityScreen.tsx +++ b/.project/designs/app/src/pages/CityScreen.tsx @@ -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 { + Tile Harvest Policies + + {/* 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 ( + + {p.icon} + {p.name} + {row.tile} + + 🌾×{p.yield_multiplier.food} ⚒×{p.yield_multiplier.production} + {p.biome_quality_delta_per_turn !== 0 && ( + 0 ? "#7cd9a0" : "#d95940" }}> + biome {p.biome_quality_delta_per_turn > 0 ? "+" : ""}{p.biome_quality_delta_per_turn} + + )} + + + ); + })} + + City Stats City HP200/ 200 max · Walled diff --git a/.project/designs/app/src/pages/HarvestPolicies.tsx b/.project/designs/app/src/pages/HarvestPolicies.tsx new file mode 100644 index 00000000..e3fbe1fc --- /dev/null +++ b/.project/designs/app/src/pages/HarvestPolicies.tsx @@ -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 ( + +
+ ← back + Harvest Policies + + driven by @resources/harvest_policies/policies.json · {POLICIES.length} policies + +
+ + + + Each tile worked by a city carries a harvest policy — 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 four, and the choice ripples + forward through ecology dynamics: a tile under extract_high for too many + turns degrades; a tile under replenish recovers; a tile chopped via + remove_chop gives one burst of production and is gone. +

+ Policies are assigned in the City Screen and persist until changed. The simulator + (mc-ecology::dynamics) reads biome_quality_delta_per_turn + + fauna_population_delta + flora_density_delta per active policy + and applies them per turn to the tile state. +
+ + The Four Policies + + {POLICIES.map(p => ( + + + {p.icon} + {p.name} + + {p.description} + +
+ yield multiplier + + 🌾 food {fmtMul(p.yield_multiplier.food)} + ⚒ prod {fmtMul(p.yield_multiplier.production)} + 🪙 trade {fmtMul(p.yield_multiplier.trade)} + ✦ culture {fmtMul(p.yield_multiplier.culture)} + +
+ +
+ per-turn ecology delta + + = 0 ? "#7cd9a0" : "#d95940"}> + biome quality {fmtDelta(p.biome_quality_delta_per_turn)} + + = 0 ? "#7cd9a0" : "#d95940"}> + flora {fmtDelta(p.flora_density_delta)} + + = 0 ? "#7cd9a0" : "#d95940"}> + fauna {fmtDelta(p.fauna_population_delta)} + + +
+ + {p.one_time_yield && ( +
+ one-time yield (consumes tile) + + ⚒ +{p.one_time_yield.production_base} prod (× tier) + 🪙 +{p.one_time_yield.gold_base} gold (× tier) + +
+ )} + + {p.terrain_transform && ( +
+ terrain transform on apply + + → {p.terrain_transform.default} + {p.terrain_transform.from_hills && hills → {p.terrain_transform.from_hills}} + {p.terrain_transform.from_jungle && jungle → {p.terrain_transform.from_jungle}} + {p.terrain_transform.from_swamp && swamp → {p.terrain_transform.from_swamp}} + +
+ )} + +
+ applies to {p.applicable_terrains.length} terrain types + + {p.applicable_terrains.slice(0, 6).map(tr => {tr})} + {p.applicable_terrains.length > 6 && +{p.applicable_terrains.length - 6} more} + +
+ + "{p.flavor}" +
+ ))} +
+ + Comparison Matrix + + + + + + + + + + + + + + + {POLICIES.map(p => ( + + + + + + + + + + + ))} + +
Policyfood ×prod ×biome Δ/turnflora Δfauna Δone-timetransforms?
{p.icon} {p.name}{fmtMul(p.yield_multiplier.food)}{fmtMul(p.yield_multiplier.production)} 0 ? "#7cd9a0" : "#d95940") }}> + {fmtDelta(p.biome_quality_delta_per_turn)} + {fmtDelta(p.flora_density_delta)}{fmtDelta(p.fauna_population_delta)}{p.one_time_yield ? `+${p.one_time_yield.production_base}p +${p.one_time_yield.gold_base}g` : "—"}{p.terrain_transform ? `→ ${p.terrain_transform.default}` : "—"}
+ + Strategic Use + + Replenishment is the long game — a tile under replenish for ~10 turns + recovers fully and becomes higher-tier than baseline. Pair with the Outsider's Parley + policy to keep neighboring freepeople from raiding (over-extraction shifts them toward + raiding per the ecology coupling rules). +

+ Sustainable is the dwarven default. No ecology consequences. Most tiles run + sustainable by default unless deliberately changed. +

+ High Extraction is the war economy lever. Pair with culture policy + war_economy (statecraft e8) for mobilization; expect biome degradation and possible + freepeople hostility within 5–10 turns. +

+ Removal 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 reforestation improvement. +
+
+
+ ); +} diff --git a/.project/designs/app/src/pages/Index.tsx b/.project/designs/app/src/pages/Index.tsx index 4f4ed166..2c9160bf 100644 --- a/.project/designs/app/src/pages/Index.tsx +++ b/.project/designs/app/src/pages/Index.tsx @@ -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" }, ], }, { diff --git a/.project/designs/app/tsconfig.tsbuildinfo b/.project/designs/app/tsconfig.tsbuildinfo index 4b86e821..a37ca97b 100644 --- a/.project/designs/app/tsconfig.tsbuildinfo +++ b/.project/designs/app/tsconfig.tsbuildinfo @@ -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"} \ No newline at end of file +{"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"} \ No newline at end of file diff --git a/public/resources/harvest_policies/policies.json b/public/resources/harvest_policies/policies.json new file mode 100644 index 00000000..349b514a --- /dev/null +++ b/public/resources/harvest_policies/policies.json @@ -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" + ] + } + } +]