feat(@projects/@magic-civilization): add terraforming objectives and team leads

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Natalie 2026-04-30 18:08:24 -04:00
parent 1e431d389f
commit d00a3a61be
11 changed files with 617 additions and 15 deletions

View file

@ -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** |
</td><td valign='top' style='padding-left:2em'>
@ -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 |

View file

@ -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).

View file

@ -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.

View file

@ -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<terrainId, FloraEntry[]>` 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.

View file

@ -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<HexDir>`, `drainage_area: u32`,
`stream_order: u8`, `lake_id: Option<u32>`. 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.

View file

@ -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` (010), `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<TerrainId, Vec<FloraId>>` 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.

View file

@ -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` (110 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.

View file

@ -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+.

View file

@ -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 |

View file

@ -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.

View file

@ -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` (010), `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` (110 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 `<entity>.<event>` → `<category>.<event>` →\n `<event>`, 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)",