diff --git a/.project/objectives/README.md b/.project/objectives/README.md
index 451215d7..c63ac1d9 100644
--- a/.project/objectives/README.md
+++ b/.project/objectives/README.md
@@ -15,10 +15,10 @@
| Priority | β
| π΅ | π‘ | π΄ | β | β« | Total |
|---|---|---|---|---|---|---|---|
| **P0** | 43 | 0 | 0 | 0 | 0 | 0 | 43 |
-| **P1** | 33 | 1 | 8 | 0 | 14 | 1 | 57 |
-| **P2** | 32 | 0 | 2 | 1 | 7 | 0 | 42 |
+| **P1** | 33 | 1 | 9 | 0 | 17 | 1 | 61 |
+| **P2** | 33 | 0 | 2 | 1 | 7 | 2 | 45 |
| **P3 (oos)** | 3 | 0 | 0 | 0 | 1 | 19 | 23 |
-| **total** | **111** | **1** | **10** | **1** | **22** | **20** | **165** |
+| **total** | **112** | **1** | **11** | **1** | **25** | **22** | **172** |
@@ -29,6 +29,7 @@
| [warcouncil](../team-leads/warcouncil.md) | 7 |
| [asset-sprite](../team-leads/asset-sprite.md) | 6 |
| [shipwright](../team-leads/shipwright.md) | 5 |
+| [terraformer](../team-leads/terraformer.md) | 5 |
| [combat-dev](../team-leads/combat-dev.md) | 1 |
| [simulator-infra](../team-leads/simulator-infra.md) | 1 |
| [asset-audio](../team-leads/asset-audio.md) | 1 |
@@ -134,6 +135,10 @@
| [p1-43](p1-43-building-stacking-upgrade.md) | β missing | Building stacking β per-category upgrade chains (military / science / culture / production / etc.) | β | 2026-04-29 |
| [p1-44](p1-44-buildings-as-producers.md) | β missing | Buildings produce units, not the city center β per-building production queues | β | 2026-04-29 |
| [p1-45](p1-45-batch-binary-freshness.md) | β missing | "Batch binary freshness: rebuild GDExt before every autoplay batch" | [simulator-infra](../team-leads/simulator-infra.md) | 2026-04-30 |
+| [p1-46](p1-46-design-lab-terrain-dimensions.md) | π‘ partial | Terrain Dimensions Lab β fix ridginess, bind 149 flora species, add Whittaker plot | [terraformer](../team-leads/terraformer.md) | 2026-04-30 |
+| [p1-47](p1-47-river-hydrology-network.md) | β missing | River hydrology β D6 flow analysis, multi-hex lakes, cross-tile rivers | [terraformer](../team-leads/terraformer.md) | 2026-04-30 |
+| [p1-48](p1-48-flora-species-renderer.md) | β missing | Flora species renderer β bind 149 species to world-map tile rendering | [terraformer](../team-leads/terraformer.md) | 2026-04-30 |
+| [p1-49](p1-49-fauna-species-renderer.md) | β missing | Fauna species renderer β 61 Game-1 species visible on encounter and lair tiles | [terraformer](../team-leads/terraformer.md) | 2026-04-30 |
| [p2-06](p2-06-export-pipeline.md) | β
done | Export pipeline for Windows / macOS / Linux | [shipwright](../team-leads/shipwright.md) | 2026-04-25 |
| [p2-16](p2-16-audio-assets.md) | π΅ in_progress | Audio assets β in-theme OSS launch pack + source ledger | [asset-audio](../team-leads/asset-audio.md) | 2026-04-27 |
| [p2-22](p2-22-sprite-generation-pipeline.md) | π‘ partial | Sprite generation pipeline β runnable end-to-end | [asset-sprite](../team-leads/asset-sprite.md) | 2026-04-25 |
@@ -185,12 +190,13 @@
| [p2-37](p2-37-react-calculator-metadata-surface.md) | β
done | "React calculator UI β surface flavor, lore, clan_affinity, archetype filter" | [tourguide](../team-leads/tourguide.md) | 2026-04-27 |
| [p2-38](p2-38-unit-audio-cues-stubs.md) | β
done | "Unit audio_cues stub strings β selection/move/attack lines for the dwarven roster" | [asset-audio](../team-leads/asset-audio.md) | 2026-04-27 |
| [p2-39](p2-39-chronicle-hall-phantom-unlock.md) | β
done | Resolve `chronicle_hall` phantom unlock in `chronicle_keeping` culture tech | β | 2026-04-27 |
-| [p2-43](p2-43-culture-research-completion-event.md) | β missing | "Culture research completion event β wire `culture_researched` from Rust to GDScript" | β | 2026-04-30 |
+| [p2-43](p2-43-culture-research-completion-event.md) | β missing | "Culture research live-game pipeline β per-turn GDExt bridge + `culture_researched` emit" | β | 2026-04-30 |
| [p2-44](p2-44-ai-promotion-selection.md) | β missing | "AI promotion selection β auto-pick + emit unit_promoted for AI units" | β | 2026-04-30 |
-| [p2-45](p2-45-elimination-reconciliation.md) | β missing | "Player elimination reconciliation β emit `player_eliminated` on every transition" | β | 2026-04-30 |
+| [p2-45](p2-45-elimination-reconciliation.md) | β
done | "Player elimination reconciliation β emit `player_eliminated` on every transition" | β | 2026-04-30 |
| [p2-46](p2-46-past-games-archive-replay-viewer.md) | β missing | Past-games archive & replay viewer β `mc-replay` crate, on-disk archive, projection-based playback | [shipwright](../team-leads/shipwright.md) | 2026-04-30 |
| [p2-47](p2-47-in-game-statistics-screens.md) | β missing | In-game statistics screens β Civ-style 5-tab modal (Demographics / Graphs / Rankings / Replay / Histories) | [shipwright](../team-leads/shipwright.md) | 2026-04-30 |
| [p2-48](p2-48-end-of-game-summary-screen.md) | β missing | End-of-game summary screen β outcome banner, standings, score graph, awards, timeline, footer actions | [shipwright](../team-leads/shipwright.md) | 2026-04-30 |
+| [p2-49](p2-49-climate-axes-latitude-continentality.md) | β missing | Climate axes refactor β latitude + continentality as first-class per-hex inputs | [terraformer](../team-leads/terraformer.md) | 2026-04-30 |
## Out of Scope (Game 2 / Game 3)
@@ -199,6 +205,8 @@
| ID | Status | Title | Owner | Updated |
|---|---|---|---|---|
| [p1-14](p1-14-guide-magic-school-scope-drift.md) | β« oos | Gate Game 2/3/4 magic-school content behind EpisodeGate (future-game scope) | β | 2026-04-17 |
+| [g2-05](g2-05-tectonics-lithology-oos.md) | β« oos | Tectonics + lithology axes for procedural map generation (Game 2) | β | 2026-04-30 |
+| [g2-06](g2-06-soil-derivation-oos.md) | β« oos | Soil derivation layer β emergent soil order from rock + climate + slope (Game 2) | β | 2026-04-30 |
| [g2-01](g2-01-leylines-oos.md) | β« oos | Ley lines β Game 2 (Age of Kzzykt) | β | 2026-04-17 |
| [g2-02](g2-02-additional-races-oos.md) | β« oos | Kzzykt playable race β Game 2 (Age of Kzzykt) | β | 2026-04-17 |
| [g2-03](g2-03-green-school-oos.md) | β« oos | Kzzykt Green school of magic β Game 2 (Age of Kzzykt) | β | 2026-04-17 |
diff --git a/.project/objectives/g2-05-tectonics-lithology-oos.md b/.project/objectives/g2-05-tectonics-lithology-oos.md
new file mode 100644
index 00000000..4f2f6494
--- /dev/null
+++ b/.project/objectives/g2-05-tectonics-lithology-oos.md
@@ -0,0 +1,40 @@
+---
+id: g2-05
+title: Tectonics + lithology axes for procedural map generation (Game 2)
+priority: p2
+status: oos
+scope: game2
+updated_at: 2026-04-30
+---
+
+## Summary
+
+Game 1 ships with elevation as the single geological axis. Game 2's
+expanded scope (additional races, leylines, planet-scale events) calls
+for plate-tectonic simulation:
+
+- Per-hex `plate_id`, `plate_type`
+ (cratonic / passive_margin / active_margin / volcanic_arc / rift /
+ hotspot)
+- `lithology` (granite / basalt / limestone / sandstone / volcanic /
+ metamorphic)
+
+Drives mountain shape, mineral suite filtering, soil-order derivation
+(g2-06), and volcanic-event placement.
+
+## Acceptance
+
+- β» Plate boundary network generated from a Voronoi seed pattern.
+- β» Lithology assigned per hex from plate type + age + erosion.
+- β» Mineral deposits in `public/resources/deposits/*.json` filtered by
+ `terrains[]` β© `lithology` (currently terrain-only filter).
+- β» Mountain shape varies by plate type: arc volcanoes (Cascadia),
+ collision ranges (Himalaya), rift escarpments (East Africa).
+- β» Soil derivation (g2-06) consumes lithology as upstream input.
+- β» Lab Tier-0 sliders expose plate type + lithology selectors.
+
+## Non-goals
+
+- Real-time plate motion (geological time-scale).
+- Earthquake events as gameplay mechanics β separate objective.
+- Continent-scale plate reconstruction (paleo-climate).
diff --git a/.project/objectives/g2-06-soil-derivation-oos.md b/.project/objectives/g2-06-soil-derivation-oos.md
new file mode 100644
index 00000000..e8df3eb0
--- /dev/null
+++ b/.project/objectives/g2-06-soil-derivation-oos.md
@@ -0,0 +1,48 @@
+---
+id: g2-06
+title: Soil derivation layer β emergent soil order from rock + climate + slope (Game 2)
+priority: p2
+status: oos
+scope: game2
+updated_at: 2026-04-30
+---
+
+## Summary
+
+Game 1's biome classifier has no soil layer; tile yield depends on
+biome + deposits only. Real ecological behaviour requires soil β a
+Mollisol grassland is far more productive than an Aridisol grassland.
+
+Soil is emergent from
+`lithology + mean_T + mean_P + slope + flora_succession_stage + time`
+and outputs one of the 8 USDA soil orders (or simplified subset):
+
+- Mollisol (grassland, deep dark, fertile)
+- Spodosol (boreal, acidic, leached)
+- Oxisol (tropical, ancient, weathered, low nutrient)
+- Aridisol (desert, calcium-rich, shallow)
+- Histosol (peat, saturated)
+- Andisol (volcanic, light, exceptional)
+- Inceptisol (young, weakly developed)
+- Entisol (very young, alluvial)
+
+Game 2 adds the soil layer as a drop-in fold over yield, fertility, and
+flora `growth_rate`.
+
+## Acceptance
+
+- β» `mc-ecology::soil::derive(lithology, T, P, slope, succession)`
+ returns a `SoilOrder` enum.
+- β» Tile yield in `mc-city` multiplied by soil-fertility coefficient
+ per soil order.
+- β» Flora `growth_rate` per species multiplied by soil suitability
+ (each species lists tolerated soil orders; schema extension
+ required).
+- β» Lab "Soil" overlay toggle shows soil-order tint per hex.
+- β» Determinism golden vector.
+
+## Non-goals
+
+- Soil weathering / drift over turns β static derivation.
+- Player-modifiable soil (terraform building) β Game 3+ only.
+- Soil chemistry per element (N, P, K) β too granular.
diff --git a/.project/objectives/p1-46-design-lab-terrain-dimensions.md b/.project/objectives/p1-46-design-lab-terrain-dimensions.md
new file mode 100644
index 00000000..127975c6
--- /dev/null
+++ b/.project/objectives/p1-46-design-lab-terrain-dimensions.md
@@ -0,0 +1,68 @@
+---
+id: p1-46
+title: Terrain Dimensions Lab β fix ridginess, bind 149 flora species, add Whittaker plot
+priority: p1
+status: partial
+scope: game1
+owner: terraformer
+updated_at: 2026-04-30
+evidence:
+ - .project/designs/app/src/pages/WorldGen/ForestLab.tsx
+ - .project/designs/app/src/pages/WorldGen.tsx
+ - .project/designs/app/src/utils/worldGen/terrain.ts
+ - .project/designs/app/src/utils/worldGen/hexCanvas.ts
+---
+
+## Summary
+
+The Terrain Dimensions Lab at `/world-gen/forest-lab` (lab tab) currently
+classifies the 17 base biomes and renders cross-tile flora/minerals/fauna
+overlays driven by 4 biome sliders + 3 toggleable overlay layers. Three
+known gaps:
+
+1. **Ridginess slider has zero effect at default elevation 0.65.** The
+ classifier (`terrain.ts:118`) only consults ridginess when
+ `elevation > 0.85 β§ ridginess > 0.92` for the volcano case. Anywhere
+ else, the slider does nothing β the user verified this on the live
+ lab and flagged it.
+2. **The lab loads 0 of 149 flora species.** Trees / shrubs / ground
+ cover are generic per terrain. The 149 species in
+ `public/resources/ecology/flora/species/*.json` carry rich schema
+ (`biomes[]`, `lineage`, `tags` with layer info, `quality_tier`,
+ `canopy_contribution`) that the lab ignores entirely.
+3. **No Whittaker TΓP plot inset** β the user can move sliders but has
+ no visual map of where they are in biome space.
+
+## Acceptance
+
+- β» **Ridginess works at all elevations** β repurpose as a
+ cross-elevation modifier: at `elevation < 0.70 β§ ridginess > 0.5`
+ upgrade plains/grassland β hills; at any elevation, increase rocky-
+ stipple density and reduce flora density by `(1 - 0.6 * ridginess)`.
+ Visible side-by-side screenshot at low / mid / high ridginess.
+- β» **149 flora species bound** β load
+ `public/resources/ecology/flora/species/*.json` via `import.meta.glob`,
+ build `TERRAIN_FLORA: Map` filtered by
+ `biomes[]` β© alias-normalised current biome. Render canopy via lineage
+ tag (`conifers` β `drawConifer3Layer`, `broadleaf_trees` β
+ `drawTree3Layer(temperate)`, `tropical_broadleaf` β
+ `drawTree3Layer(tropical)`, `cacti` β cactus glyph, `palms` β palm,
+ `aquatic_plants` β water-lily). Layer selection by `tags`
+ (`layer_canopy`/`layer_understory`/`layer_ground`/`layer_fungal`).
+- β» **Info card shows top-3 species per layer** β canopy / understory /
+ ground each with up to 3 species names + `quality_tier`, sorted by
+ contribution weight.
+- β» **Whittaker plot inset** β small 200Γ160 SVG at right of the canvas
+ showing biome regions in T (x) Γ P (y) space with a focus dot at
+ current (T, P). Colours match terrain colour palette.
+- β» **Visual proof** β screenshot of the lab at 4 distinct slider
+ configurations (forest, jungle, boreal, desert) committed to
+ `.project/screenshots/p1-46-*.png`, each showing flora species names
+ in info card.
+
+## Non-goals
+
+- New flora species authoring β schema is owned elsewhere.
+- Hydrology rendering β that's p1-47.
+- Tectonics / lithology axes β that's g2-05.
+- Climate refactor (latitude, continentality) β that's p2-49.
diff --git a/.project/objectives/p1-47-river-hydrology-network.md b/.project/objectives/p1-47-river-hydrology-network.md
new file mode 100644
index 00000000..bbc7421d
--- /dev/null
+++ b/.project/objectives/p1-47-river-hydrology-network.md
@@ -0,0 +1,80 @@
+---
+id: p1-47
+title: River hydrology β D6 flow analysis, multi-hex lakes, cross-tile rivers
+priority: p1
+status: missing
+scope: game1
+owner: terraformer
+updated_at: 2026-04-30
+---
+
+## Summary
+
+Water bodies in the current map are per-hex terrain types
+(`ocean`, `coast`, `lake`, `inland_sea`) with no connectivity. Real
+geographic water is **topological**: rivers are DAGs from headwaters to
+ocean, lakes are multi-hex polygons, coastlines are polylines along
+land/water borders.
+
+The data already encodes this in:
+
+- `public/games/age-of-dwarves/data/terrain/terrain_blends.json` β
+ `riverside_forest`, `shore`, `cliff`, `bog_edge` ecotones (cross-tile)
+- 10+ fauna species with `river` / `lake` / `wetland` biomes
+ (bald_eagle, alligator, otter, kingfisher, beaver, etc.)
+- 4 riparian flora species: `lotus`, `papyrus`, `giant_water_lily`,
+ `pioneer_sedge`
+
+But there is **no rendering pass** that uses the topology. This
+objective adds D6 flow-direction analysis on the elevation grid,
+drainage-area accumulation, lake-basin filling, and a cross-tile
+renderer that draws rivers as bezier paths through hex edges and lakes
+as multi-hex continuous fills.
+
+## Acceptance
+
+- β» **D6 flow analysis in mc-mapgen** β new module
+ `src/simulator/crates/mc-mapgen/src/hydrology.rs`: per-hex
+ `flow_out: Option`, `drainage_area: u32`,
+ `stream_order: u8`, `lake_id: Option`. Topological sort
+ produces a deterministic order; drainage accumulates bottom-up;
+ closed basins fill to overflow.
+- β» **GDExtension surface** β `api-gdext/src/lib.rs` exposes
+ `GdGridState::tile_hydrology(col, row)` returning a struct with the
+ four hydrology fields. Read by Godot tile tooltip + design-lab via
+ cargo-built artifact.
+- β» **Design lab renders rivers** β new module
+ `.project/designs/app/src/utils/worldGen/hydrology.ts` reproduces the
+ D6 algorithm in TS for the synthetic 7Γ5 grid; new render pass
+ between minerals and flora draws bezier curves through hex edges
+ with width = `1 + log2(drainage_area + 1)` and stroke colour
+ interpolated from light to dark blue by stream order.
+- β» **Multi-hex lakes** β connected components of `lake`/`coast`/
+ `ocean` cells render as one continuous fill; internal hex borders
+ suppressed; surface wave glyphs respect the body's overall shape,
+ not the tile's.
+- β» **Coastline ecotone strokes** β for each land/water hex edge,
+ draw a styled stroke using `terrain_blends.json` lookup:
+ `coast+plains` β sandy stipple, `coast+mountains` β cliff dark
+ jagged, `coast+forest` β riverside green, `coast+desert` β cracked
+ tan.
+- β» **Riparian feedback into flora** β within 1 hex of a river edge or
+ lake border: flora density bumped by 1.4Γ; riparian flora species
+ (lotus / papyrus / black_alder / silver_birch / bald_cypress)
+ preferred in canopy/understory selection. Verified by toggling river
+ overlay off and counting trees within riparian band.
+- β» **Determinism test** β
+ `src/simulator/crates/mc-mapgen/tests/hydrology.rs`: same seed
+ produces same flow graph + drainage areas + lake_ids over 5
+ different map sizes. Frozen golden vector for one seed.
+- β» **Visual proof** β screenshot showing connected river system
+ across 5+ hexes with multi-hex lake at the bottom committed to
+ `.project/screenshots/p1-47-*.png`.
+
+## Non-goals
+
+- Naval movement / pathfinding on rivers β that's `g6-01-naval-combat`.
+- Aquifer / groundwater modelling β too granular for Game 1.
+- Glacier / sea-ice flow β only static frozen tiles for now.
+- River EROSION feedback to elevation β one-shot computation, not
+ multi-step simulation.
diff --git a/.project/objectives/p1-48-flora-species-renderer.md b/.project/objectives/p1-48-flora-species-renderer.md
new file mode 100644
index 00000000..2dfda867
--- /dev/null
+++ b/.project/objectives/p1-48-flora-species-renderer.md
@@ -0,0 +1,68 @@
+---
+id: p1-48
+title: Flora species renderer β bind 149 species to world-map tile rendering
+priority: p1
+status: missing
+scope: game1
+owner: terraformer
+updated_at: 2026-04-30
+coordinates_with:
+ - p1-46
+---
+
+## Summary
+
+`public/resources/ecology/flora/species/*.json` defines **149 species**
+with rich schema:
+
+- `biomes[]` β which terrain types support the species
+- `tags[]` including layer (`layer_canopy` / `layer_understory` /
+ `layer_ground` / `layer_fungal`) and structure (`structure_woody` /
+ `conifer` / `broadleaf` / `evergreen`)
+- `lineage` β taxonomic group (`conifers`, `broadleaf_trees`,
+ `tropical_broadleaf`, `cacti`, `palms`, `aquatic_plants`,
+ `mosses_lichens`, etc.)
+- `quality_tier` (0β10), `canopy_contribution`,
+ `undergrowth_contribution`, `fungi_contribution`
+- `drought_tolerance`, `fire_resistance`, `growth_rate`
+
+But the world-map renderer (`mc-render` or Godot tilemap layer) draws
+generic green circles for forest tiles. This objective wires the 149
+species data into the canvas / TileMap rendering so a "forest" tile in
+temperate Europe shows oak / beech / birch and a "forest" tile in
+tropical climate shows mahogany / teak / strangler_fig β each instance
+picked from the species pool that lists this terrain in its `biomes[]`.
+
+## Acceptance
+
+- β» **Species pool builder** β new module
+ `src/simulator/crates/mc-ecology/src/species.rs` builds
+ `TerrainFloraIndex: HashMap>` at startup
+ from species JSONs, indexed by biome alias.
+- β» **Per-tile species selection** β deterministic pick of N species
+ per tile (N driven by tile quality + `canopy_contribution` weights),
+ seeded by `(map_seed, col, row)`. Same tile yields same species set
+ across loads.
+- β» **Lineage β glyph mapping** β `conifers` β `drawConifer3Layer`,
+ `broadleaf_trees` β `drawTree3Layer(temperate)`,
+ `tropical_broadleaf` β `drawTree3Layer(tropical)`, `cacti` β cactus
+ glyph, `palms` β palm glyph, `aquatic_plants` β water-lily,
+ `mosses_lichens` β ground stipple.
+- β» **Layer stratification** β `layer_canopy` species rendered first,
+ `layer_understory` smaller / behind, `layer_ground` smallest; visible
+ vertical ordering side-by-side.
+- β» **Tooltip integration** β world-map tile tooltip shows top-3 flora
+ species names with `quality_tier` per stratum.
+- β» **Design-lab uses same module** β lab and shipped renderer share
+ the same `mc-ecology::species` selection logic; not two
+ implementations. (Couples to p1-46.)
+- β» **Determinism gate** β
+ `src/simulator/crates/mc-ecology/tests/species_selection.rs`: golden
+ vector for 10 seeds Γ 5 biomes; same input β same species set.
+
+## Non-goals
+
+- Per-species animation / sway β static icons only.
+- Successional drift over turns β one-shot pick at map gen, not
+ dynamic.
+- Custom sprite assets per species β procedural canvas glyphs only.
diff --git a/.project/objectives/p1-49-fauna-species-renderer.md b/.project/objectives/p1-49-fauna-species-renderer.md
new file mode 100644
index 00000000..74c2703a
--- /dev/null
+++ b/.project/objectives/p1-49-fauna-species-renderer.md
@@ -0,0 +1,62 @@
+---
+id: p1-49
+title: Fauna species renderer β 61 Game-1 species visible on encounter and lair tiles
+priority: p1
+status: missing
+scope: game1
+owner: terraformer
+updated_at: 2026-04-30
+coordinates_with:
+ - p0-17
+ - p1-47
+---
+
+## Summary
+
+`public/games/age-of-dwarves/data/manifests/fauna.json` whitelists **61
+Game-1 species** with rich JSON schema in
+`public/resources/ecology/fauna/species/*.json`:
+
+- `domain` (land / air / marine / freshwater)
+- `trophic_level` (apex_predator / predator / herbivore / omnivore)
+- `biomes[]`
+- `prey[]` β actual food-web edges
+- `ecology_tier` (1β10 rarity / strength)
+- `forms_lairs` + `lair_type`
+- `lineage` (canines, ursids, cervids, raptors, etc.)
+- `traits[]` including `size_*`
+
+But the world-map renderer draws abstract icons for lairs and no
+overlay at all for ambient fauna. This objective wires the species
+data into the renderer so a wolf lair shows a wolf silhouette, a bear
+cave shows a bear, a harpy nest shows a winged figure.
+
+## Acceptance
+
+- β» **Species glyph table** β per-species canvas glyph in
+ `src/simulator/crates/mc-ecology/src/fauna_glyphs.rs` (or TS twin in
+ design-lab) keyed by `lineage` + `domain` + `size_class`. 61 species
+ β ~12 distinct glyphs after lineage clustering.
+- β» **Lair tiles render species silhouette** β Godot side
+ `wild_creature_lair_*.gd` tile sprite swap to use species-specific
+ glyph instead of generic icon.
+- β» **Ambient fauna overlay** β design-lab fauna toggle picks species
+ from `TerrainFaunaIndex` (built from manifest + species JSONs)
+ weighted by `ecology_tier`. Up to 5 silhouettes per tile.
+- β» **Aquatic species only on water-adjacent tiles** β couples to
+ p1-47: fauna with `domain β {marine, freshwater}` only spawn on
+ water cells or hexes adjacent to lakes / rivers.
+- β» **Tooltip integration** β world-map tile tooltip lists matching
+ fauna species with trophic level + ecology_tier.
+- β» **Determinism gate** β same seed β same species per tile.
+- β» **Visual proof** β screenshot of a forest tile with 3+ species
+ silhouettes (e.g. red_deer + brown_bear + grey_wolf) committed to
+ `.project/screenshots/p1-49-*.png`.
+
+## Non-goals
+
+- Combat behaviour scripting (lair AI) β that's p0-17.
+- Per-species call sounds / audio β out of scope.
+- Migration paths over turns β static placement.
+- Schema additions (e.g. new `glyph_override` field) β bind to
+ existing fields only.
diff --git a/.project/objectives/p2-49-climate-axes-latitude-continentality.md b/.project/objectives/p2-49-climate-axes-latitude-continentality.md
new file mode 100644
index 00000000..87c50a7f
--- /dev/null
+++ b/.project/objectives/p2-49-climate-axes-latitude-continentality.md
@@ -0,0 +1,67 @@
+---
+id: p2-49
+title: Climate axes refactor β latitude + continentality as first-class per-hex inputs
+priority: p2
+status: missing
+scope: game1
+owner: terraformer
+updated_at: 2026-04-30
+coordinates_with:
+ - p0-31
+ - p0-32
+---
+
+## Summary
+
+`mc-mapgen::sampleCell` (and the design-lab twin in `terrain.ts:213`)
+derives `cold` from
+`abs(row/rows - 0.5) * 2 * (1 - climate)` β an implicit latitude proxy
+that doesn't expose:
+
+- **Continentality** β distance from ocean, drives seasonal swing and
+ base aridity (Siberia vs Ireland)
+- **Seasonality** β latitude amplitude (tropic vs arctic)
+- **Rain shadow** β windward / leeward of mountains (wet Pacific NW
+ vs dry Great Basin)
+- **Elevation lapse rate** β ~6.5Β°C/km cooling
+
+The classifier consumes the collapsed `cold` value, producing biomes
+that don't differentiate maritime temperate from continental temperate,
+or windward rainforest from leeward rain shadow.
+
+This objective decomposes the climate input into independent per-hex
+fields β `latitude`, `continentality`, `prevailing_wind_dir` β then
+derives mean T, mean P, seasonality, evapotranspiration deficit. The
+biome classifier consumes the derived values, exposing
+maritime / continental and rain-shadow distinctions at last.
+
+## Acceptance
+
+- β» **Per-hex climate fields** β `mc-mapgen::TileMeta` extended with
+ `latitude: f32 (0..1)`, `continentality: f32 (0..1)`,
+ `wind_dir: HexDir`. Computed deterministically from `map_seed`.
+- β» **Derived fields** β `mean_temp`, `mean_precip`, `seasonality`,
+ `aridity_index` computed in `mc-climate::derive` from
+ (latitude, continentality, elevation, wind_dir, mountain_proximity).
+- β» **Rain-shadow logic** β windward of a mountain ridge:
+ `mean_precip *= 1.5`; leeward: `*= 0.4`. Visible as desert / steppe
+ in lee of major ranges.
+- β» **Classifier rewrite** β `classifyTerrain` consumes derived (T, P)
+ instead of (moisture, cold). Whittaker-style boundaries.
+- β» **Lab exposes 5 climate sliders** β Latitude, Continentality,
+ Wind, Precipitation, Elevation Lapse Adjustment. Old "Humidity" +
+ "Temperature" sliders reframed as derived display.
+- β» **Backwards-compat** β existing `mc-climate` tile_sync invariants
+ (verified in p0-31's batch tests) still hold; canopy/undergrowth
+ evolves consistently with the refactored climate inputs.
+- β» **Determinism + golden** β
+ `src/simulator/crates/mc-mapgen/tests/climate_decomposition.rs`
+ freezes derived fields for one seed; subsequent runs match.
+
+## Non-goals
+
+- Multi-year climate cycles β static per-map.
+- Ocean current modelling β `wind_dir` is a hex-direction proxy; no
+ Gulf Stream simulation.
+- Dynamic climate change β static.
+- Latitude-driven day-length effects on flora growth β Game 2+.
diff --git a/.project/team-leads/README.md b/.project/team-leads/README.md
index b7fbed30..6d7d02bb 100644
--- a/.project/team-leads/README.md
+++ b/.project/team-leads/README.md
@@ -71,3 +71,4 @@ specialist does not own any objective.
| [testwright](testwright.md) | Testwright | Regression-test coverage across Rust + GDScript + data validators β seeds the evidence substrate for the Objective Status Integrity rule | p1-09, p2-10 |
| [tourguide](tourguide.md) | Tourguide | Developer experience of the guide web app β dev server boots on plum, route coverage e2e, dev-tier deploy at mc.next.black.local, sim-cache baked on apricot, welcomeβHomePageβtheme alignment, and the "no build output in src" rule stays enforced | p1-11, p1-12, p1-13, p1-15, p1-17, p2-20, p2-21, p2-29 |
| [envoy](envoy.md) | Envoy | Post-EA diplomacy depth β courier-gated information trade, open-borders agreements, courier unit family + building chain + severable route infrastructure. Parked until Shipwright ships EA. | p3-01 |
+| [terraformer](terraformer.md) | Terraformer | Procedural terrain generation, ecology rendering, and the Earth-systems layered model β biome classifier, hydrology network, flora/fauna species binding, and the design-app Terrain Dimensions Lab at /world-gen/forest-lab | p1-46, p1-47, p1-48, p1-49, p2-49 |
diff --git a/.project/team-leads/terraformer.md b/.project/team-leads/terraformer.md
new file mode 100644
index 00000000..053ada41
--- /dev/null
+++ b/.project/team-leads/terraformer.md
@@ -0,0 +1,90 @@
+---
+id: terraformer
+name: Terraformer
+specialization: Procedural terrain generation, ecology rendering, and the Earth-systems layered model β biome classifier, hydrology network, flora/fauna species binding, and the design-app Terrain Dimensions Lab at /world-gen/forest-lab.
+objectives:
+ - p1-46
+ - p1-47
+ - p1-48
+ - p1-49
+ - p2-49
+---
+
+## Mandate
+
+Own the world-generation stack from tectonics through ecology rendering.
+Game 1 ships with `mc-mapgen`'s `classifyTerrain` producing 17 base
+biomes from elevation + moisture + cold + ridginess. The Terraformer
+extends this in two directions:
+
+1. **Make the existing system EXPRESSIVE.** Bind the 149 flora species
+ and 61 Game-1 fauna species in `public/resources/ecology/` to the
+ renderer so the world looks like its data β not generic green
+ circles.
+2. **Add the missing TIER between topography and climate** β connected
+ hydrology (rivers as DAGs, lakes as multi-hex polygons, coastlines
+ as ecotone polylines) computed by D6 flow analysis on the elevation
+ field.
+
+The Terrain Dimensions Lab at `/world-gen/forest-lab` is the canonical
+playground where designers validate biome distinctions. The Terraformer
+keeps it honest: every slider must do something visible at default
+parameters, and every species in the data must be reachable through the
+lab's overlays.
+
+## Owned surface
+
+Files / crates this lead may modify:
+
+- `.project/designs/app/src/pages/WorldGen/` β design lab pages
+- `.project/designs/app/src/utils/worldGen/` β terrain classifier, hex
+ rendering, and new modules: `flora.ts`, `hydrology.ts`, `ridginess.ts`
+- `src/simulator/crates/mc-mapgen/src/` β procedural map generation,
+ hydrology (`hydrology.rs` to add), flow-direction analysis
+- `src/simulator/crates/mc-ecology/src/` β species binding modules
+ (`species.rs`, `fauna_glyphs.rs`)
+- `src/simulator/crates/mc-climate/src/` β climate inputs to the biome
+ classifier (latitude, continentality, rain shadow) β coordinate with
+ shipwright before mutating climate spec
+- `src/simulator/api-gdext/src/` β exposing hydrology + species fields
+ to Godot
+- `public/games/age-of-dwarves/data/terrain/*.json` β terrain
+ definitions including blends/ecotones
+- `src/game/engine/src/generation/` β Godot-side generation glue
+- `src/game/engine/src/map/` β Godot tile renderer integration
+
+## Boundaries β read but do NOT modify
+
+- `public/resources/ecology/flora/species/*.json` β flora schema. Bind
+ to renderer; never alter species records.
+- `public/resources/ecology/fauna/species/*.json` β fauna schema. Same.
+- `public/games/age-of-dwarves/data/manifests/fauna.json` β Game 1
+ fauna whitelist. Read only.
+- `public/games/age-of-dwarves/data/deposits/manifest.json` β deposit
+ whitelist. Read only.
+- `mc-city` / `mc-economy` β terrain yields. Terraformer ensures
+ classification is correct; how a city extracts food/prod/trade is
+ shipwright's call (p1-38).
+- `mc-combat` β `defense_bonus` and `movement_cost` consumers.
+- `wild_creature_lair_*.gd` AI β fauna behaviour is shipwright's
+ (p0-17). Renderer-only changes here.
+
+## Escalation
+
+- **Climate spec rewrites** (e.g. switching from `cold` to derived T
+ from latitude+altitude) β coordinate with shipwright before merging:
+ changes `mc-climate` invariants validated by p0-31's batch tests.
+- **Ecology schema additions** (new flora/fauna fields) β coordinate
+ with whoever owns ecology authoring; currently no claimed lead.
+- **Headless rendering breakage** β if hydrology/flora rendering
+ crashes the headless GUT pipeline β escalate to simulator-infra.
+- **Game 2 / Game 3 scope creep** β `g2-05` (tectonics-lithology) and
+ `g2-06` (soil derivation) STAY UNOWNED until Game 1 ships. The
+ Terraformer's mandate ends at the Game-1 ecology surface.
+
+## Cron loop
+
+Terraformer does NOT run a cron loop initially. The owned objectives
+get a single `/experts-team` parallel sweep to drive p1-46 + p1-47 +
+p1-48 + p1-49 to done. p2-49 follows after climate refactor lands.
+Subsequent work invoked on demand.
diff --git a/public/games/age-of-dwarves/data/objectives.json b/public/games/age-of-dwarves/data/objectives.json
index 4024c3a4..af2d88e2 100644
--- a/public/games/age-of-dwarves/data/objectives.json
+++ b/public/games/age-of-dwarves/data/objectives.json
@@ -1,13 +1,13 @@
{
- "generated_at": "2026-04-30T14:25:49Z",
+ "generated_at": "2026-04-30T22:05:58Z",
"totals": {
- "partial": 10,
- "oos": 20,
- "missing": 22,
- "stub": 1,
- "done": 111,
"in_progress": 1,
- "total": 165
+ "partial": 11,
+ "stub": 1,
+ "done": 112,
+ "missing": 25,
+ "oos": 22,
+ "total": 172
},
"objectives": [
{
@@ -910,6 +910,46 @@
"updated_at": "2026-04-30",
"summary": "Autoplay batches (`tools/autoplay-batch.sh`) run Godot against the installed GDExtension binary\n(`engine/addons/magic_civ_physics/libmagic_civ_physics.x86_64.so`). When multiple agents or\ndevelopers land Rust changes between batches, the `.so` silently goes stale β the GDScript wrappers\ncall methods that don't exist yet in the binary, producing cryptic errors like:\n\n```\nInvalid call. Nonexistent function 'process_culture_with_modifier' in base 'RefCounted (City)'.\n```\n\nThis was observed on 2026-04-30 during the p1-29 anti-snowball batch cycle when the culture-port\nteammate's `process_culture_with_modifier` and p1-30's `set_map`/`update_tile` GDExt methods were\nlanded after the last build, causing R12 test failures at T22."
},
+ {
+ "id": "p1-46",
+ "title": "Terrain Dimensions Lab β fix ridginess, bind 149 flora species, add Whittaker plot",
+ "priority": "p1",
+ "status": "partial",
+ "scope": "game1",
+ "owner": "terraformer",
+ "updated_at": "2026-04-30",
+ "summary": "The Terrain Dimensions Lab at `/world-gen/forest-lab` (lab tab) currently\nclassifies the 17 base biomes and renders cross-tile flora/minerals/fauna\noverlays driven by 4 biome sliders + 3 toggleable overlay layers. Three\nknown gaps:\n\n1. **Ridginess slider has zero effect at default elevation 0.65.** The\n classifier (`terrain.ts:118`) only consults ridginess when\n `elevation > 0.85 β§ ridginess > 0.92` for the volcano case. Anywhere\n else, the slider does nothing β the user verified this on the live\n lab and flagged it.\n2. **The lab loads 0 of 149 flora species.** Trees / shrubs / ground\n cover are generic per terrain. The 149 species in\n `public/resources/ecology/flora/species/*.json` carry rich schema\n (`biomes[]`, `lineage`, `tags` with layer info, `quality_tier`,\n `canopy_contribution`) that the lab ignores entirely.\n3. **No Whittaker TΓP plot inset** β the user can move sliders but has\n no visual map of where they are in biome space."
+ },
+ {
+ "id": "p1-47",
+ "title": "River hydrology β D6 flow analysis, multi-hex lakes, cross-tile rivers",
+ "priority": "p1",
+ "status": "missing",
+ "scope": "game1",
+ "owner": "terraformer",
+ "updated_at": "2026-04-30",
+ "summary": "Water bodies in the current map are per-hex terrain types\n(`ocean`, `coast`, `lake`, `inland_sea`) with no connectivity. Real\ngeographic water is **topological**: rivers are DAGs from headwaters to\nocean, lakes are multi-hex polygons, coastlines are polylines along\nland/water borders.\n\nThe data already encodes this in:\n\n- `public/games/age-of-dwarves/data/terrain/terrain_blends.json` β\n `riverside_forest`, `shore`, `cliff`, `bog_edge` ecotones (cross-tile)\n- 10+ fauna species with `river` / `lake` / `wetland` biomes\n (bald_eagle, alligator, otter, kingfisher, beaver, etc.)\n- 4 riparian flora species: `lotus`, `papyrus`, `giant_water_lily`,\n `pioneer_sedge`\n\nBut there is **no rendering pass** that uses the topology. This\nobjective adds D6 flow-direction analysis on the elevation grid,\ndrainage-area accumulation, lake-basin filling, and a cross-tile\nrenderer that draws rivers as bezier paths through hex edges and lakes\nas multi-hex continuous fills."
+ },
+ {
+ "id": "p1-48",
+ "title": "Flora species renderer β bind 149 species to world-map tile rendering",
+ "priority": "p1",
+ "status": "missing",
+ "scope": "game1",
+ "owner": "terraformer",
+ "updated_at": "2026-04-30",
+ "summary": "`public/resources/ecology/flora/species/*.json` defines **149 species**\nwith rich schema:\n\n- `biomes[]` β which terrain types support the species\n- `tags[]` including layer (`layer_canopy` / `layer_understory` /\n `layer_ground` / `layer_fungal`) and structure (`structure_woody` /\n `conifer` / `broadleaf` / `evergreen`)\n- `lineage` β taxonomic group (`conifers`, `broadleaf_trees`,\n `tropical_broadleaf`, `cacti`, `palms`, `aquatic_plants`,\n `mosses_lichens`, etc.)\n- `quality_tier` (0β10), `canopy_contribution`,\n `undergrowth_contribution`, `fungi_contribution`\n- `drought_tolerance`, `fire_resistance`, `growth_rate`\n\nBut the world-map renderer (`mc-render` or Godot tilemap layer) draws\ngeneric green circles for forest tiles. This objective wires the 149\nspecies data into the canvas / TileMap rendering so a \"forest\" tile in\ntemperate Europe shows oak / beech / birch and a \"forest\" tile in\ntropical climate shows mahogany / teak / strangler_fig β each instance\npicked from the species pool that lists this terrain in its `biomes[]`."
+ },
+ {
+ "id": "p1-49",
+ "title": "Fauna species renderer β 61 Game-1 species visible on encounter and lair tiles",
+ "priority": "p1",
+ "status": "missing",
+ "scope": "game1",
+ "owner": "terraformer",
+ "updated_at": "2026-04-30",
+ "summary": "`public/games/age-of-dwarves/data/manifests/fauna.json` whitelists **61\nGame-1 species** with rich JSON schema in\n`public/resources/ecology/fauna/species/*.json`:\n\n- `domain` (land / air / marine / freshwater)\n- `trophic_level` (apex_predator / predator / herbivore / omnivore)\n- `biomes[]`\n- `prey[]` β actual food-web edges\n- `ecology_tier` (1β10 rarity / strength)\n- `forms_lairs` + `lair_type`\n- `lineage` (canines, ursids, cervids, raptors, etc.)\n- `traits[]` including `size_*`\n\nBut the world-map renderer draws abstract icons for lairs and no\noverlay at all for ambient fauna. This objective wires the species\ndata into the renderer so a wolf lair shows a wolf silhouette, a bear\ncave shows a bear, a harpy nest shows a winged figure."
+ },
{
"id": "p2-06",
"title": "Export pipeline for Windows / macOS / Linux",
@@ -1010,6 +1050,26 @@
"updated_at": "2026-04-27",
"summary": "`AudioManager` (`p0-21`, done) ships 10 SFX events and 6 era-keyed music\ntracks. The current manifest is one stream per id, no variation, no\nfallback chain, and no story for the 91 units / 65 buildings / 600\nfauna species the game ships with β every entity that ever wants a\ndistinct sound has to add a hand-authored entry, which doesn't scale to\nlaunch.\n\nThis objective extends the manifest schema and `audio_manager.gd` so\nthe asset pack tracked by `p2-16` can land cleanly:\n\n* **Variant pools** β an entry can list `streams[]` with 2-3 paths;\n the player picks one uniformly to break repetition.\n* **Pitch jitter** β optional Β±X% pitch randomisation per play.\n* **Categorical fallback ladder** β `play_for_entity(entity_id,\n event_kind)` resolves `.` β `.` β\n ``, so a fresh unit with no bespoke sound automatically routes\n to its category bucket (`unit.melee.attack`, `building.production.complete`,\n `fauna.apex.roar`, etc.). The category is read from existing JSON\n fields (`unit_type` / `category` / `trophic_class`) β no new schema\n fields on units / buildings / wilds.\n* **EventBus expansion** β wire the additional signals that already\n exist on `event_bus.gd` but aren't routed to audio yet\n (`combat_started`, `unit_destroyed`, `unit_promoted`, `city_grew`,\n `city_starved`, `golden_age_started`, `golden_age_ended`,\n `border_expanded`, `culture_researched`, `wild_creature_spawned`,\n `weather_event`, `tech_research_started`).\n\nThis is a **schema-and-code** objective. No `.ogg` files land here β\nthose are `p2-16`'s responsibility, which is `blockedBy: [p2-33]` so\nthe dependency-aware ordering surfaces this work first."
},
+ {
+ "id": "g2-05",
+ "title": "Tectonics + lithology axes for procedural map generation (Game 2)",
+ "priority": "p2",
+ "status": "oos",
+ "scope": "game2",
+ "owner": null,
+ "updated_at": "2026-04-30",
+ "summary": "Game 1 ships with elevation as the single geological axis. Game 2's\nexpanded scope (additional races, leylines, planet-scale events) calls\nfor plate-tectonic simulation:\n\n- Per-hex `plate_id`, `plate_type`\n (cratonic / passive_margin / active_margin / volcanic_arc / rift /\n hotspot)\n- `lithology` (granite / basalt / limestone / sandstone / volcanic /\n metamorphic)\n\nDrives mountain shape, mineral suite filtering, soil-order derivation\n(g2-06), and volcanic-event placement."
+ },
+ {
+ "id": "g2-06",
+ "title": "Soil derivation layer β emergent soil order from rock + climate + slope (Game 2)",
+ "priority": "p2",
+ "status": "oos",
+ "scope": "game2",
+ "owner": null,
+ "updated_at": "2026-04-30",
+ "summary": "Game 1's biome classifier has no soil layer; tile yield depends on\nbiome + deposits only. Real ecological behaviour requires soil β a\nMollisol grassland is far more productive than an Aridisol grassland.\n\nSoil is emergent from\n`lithology + mean_T + mean_P + slope + flora_succession_stage + time`\nand outputs one of the 8 USDA soil orders (or simplified subset):\n\n- Mollisol (grassland, deep dark, fertile)\n- Spodosol (boreal, acidic, leached)\n- Oxisol (tropical, ancient, weathered, low nutrient)\n- Aridisol (desert, calcium-rich, shallow)\n- Histosol (peat, saturated)\n- Andisol (volcanic, light, exceptional)\n- Inceptisol (young, weakly developed)\n- Entisol (very young, alluvial)\n\nGame 2 adds the soil layer as a drop-in fold over yield, fertility, and\nflora `growth_rate`."
+ },
{
"id": "p2-01",
"title": "Minimap β fog reflection and unit markers",
@@ -1372,13 +1432,13 @@
},
{
"id": "p2-43",
- "title": "\"Culture research completion event β wire `culture_researched` from Rust to GDScript\"",
+ "title": "\"Culture research live-game pipeline β per-turn GDExt bridge + `culture_researched` emit\"",
"priority": "p2",
"status": "missing",
"scope": "game1",
"owner": null,
"updated_at": "2026-04-30",
- "summary": "`EventBus.culture_researched(tradition_id, player_index)` is defined and\n**every downstream consumer is wired** (AudioManager handler, manifest\nentry `culture_researched`, the asset shipped at\n`public/resources/audio/sfx/ui/culture_researched.ogg`). What's missing is\nthe upstream emit.\n\nThe Rust simulator (`src/simulator/crates/mc-turn/src/processor.rs:636-652`)\nalready detects tradition completion via\n`mc_culture::CultureResearchResult::Completed { tech_id, .. }` and writes\nto `player.researched_traditions`. But it does **not** surface a per-event\nrecord on the per-turn result the way tech does\n(`research_events: [{type: \"tech\", id: ...}]`).\n\nConsequence: when an AI player or the human completes a tradition, the\naudio handler `AudioManager._on_culture_researched` never fires. Verified\nby `grep -rn 'culture_researched\\.emit' src/` β zero hits in production\ncode."
+ "summary": "`EventBus.culture_researched(tradition_id, player_index)` is defined and\n**every downstream consumer is wired** (AudioManager handler, manifest\nentry `culture_researched`, the asset shipped at\n`public/resources/audio/sfx/ui/culture_researched.ogg`). What's missing\nturned out to be deeper than the original framing of this objective: the\n**entire per-turn culture-research path doesn't run in the live game**.\n\n### Trace\n\n- `turn_manager.gd:246` calls `_process_culture(player, game_map)`\n- `turn_processor.gd:360 _process_culture` only handles **border\n expansion** via `city.process_culture_with_modifier()` β no\n tradition-research accumulator\n- `processor.rs:604 process_culture_research` (Rust mc-turn) **does**\n drive tradition completion via `mc_culture::CultureResearchResult`,\n but it lives in the bench / legacy-headless path, not in the\n GDScript-driven live-game per-turn\n- Tech has a Rust GDExt method `tech_web.process_research(player_dict,\n yields, mult) β {new_progress, new_researching, completed_tech}` that\n GDScript calls in `turn_processor.gd::_process_research` β **no\n equivalent exists for culture**\n\nSo in the playable game today: `culture_research_progress` never\nincrements, `researched_traditions` never grows, no completion event\never fires. `p1-28` shipped the UI and the data graph but not the\nruntime accumulator."
},
{
"id": "p2-44",
@@ -1394,7 +1454,7 @@
"id": "p2-45",
"title": "\"Player elimination reconciliation β emit `player_eliminated` on every transition\"",
"priority": "p2",
- "status": "missing",
+ "status": "done",
"scope": "game1",
"owner": null,
"updated_at": "2026-04-30",
@@ -1430,6 +1490,16 @@
"updated_at": "2026-04-30",
"summary": "Full-screen summary triggered when the game ends β by victory condition, last-clan-standing, turn-limit, or player resignation. Replaces the world-map HUD with:\n\n- **Hero strip** β outcome banner + winning-clan card + player's-clan card (player-second slot stable across victory/defeat).\n- **Section 1 β Final standings** β Demographics table from p3-06 frozen at final turn, plus `Outcome` and `Score breakdown` columns.\n- **Section 2 β Score graph** β full-game chart from p3-06's Graphs widget with event markers forced on.\n- **Section 3 β Awards** β JSON-driven per-category superlatives.\n- **Section 4 β Timeline** β Histories from p3-06 with fog lifted (every clan visible).\n- **Footer** β View Map Β· Watch Replay Β· Save to Archive Β· Export JSON Β· Main Menu.\n\nDesign doc: [.project/designs/end-game-summary.md](../designs/end-game-summary.md)."
},
+ {
+ "id": "p2-49",
+ "title": "Climate axes refactor β latitude + continentality as first-class per-hex inputs",
+ "priority": "p2",
+ "status": "missing",
+ "scope": "game1",
+ "owner": "terraformer",
+ "updated_at": "2026-04-30",
+ "summary": "`mc-mapgen::sampleCell` (and the design-lab twin in `terrain.ts:213`)\nderives `cold` from\n`abs(row/rows - 0.5) * 2 * (1 - climate)` β an implicit latitude proxy\nthat doesn't expose:\n\n- **Continentality** β distance from ocean, drives seasonal swing and\n base aridity (Siberia vs Ireland)\n- **Seasonality** β latitude amplitude (tropic vs arctic)\n- **Rain shadow** β windward / leeward of mountains (wet Pacific NW\n vs dry Great Basin)\n- **Elevation lapse rate** β ~6.5Β°C/km cooling\n\nThe classifier consumes the collapsed `cold` value, producing biomes\nthat don't differentiate maritime temperate from continental temperate,\nor windward rainforest from leeward rain shadow.\n\nThis objective decomposes the climate input into independent per-hex\nfields β `latitude`, `continentality`, `prevailing_wind_dir` β then\nderives mean T, mean P, seasonality, evapotranspiration deficit. The\nbiome classifier consumes the derived values, exposing\nmaritime / continental and rain-shadow distinctions at last."
+ },
{
"id": "g2-01",
"title": "Ley lines β Game 2 (Age of Kzzykt)",
|