From a16fa074c951c370f54258530671e956031e80a4 Mon Sep 17 00:00:00 2001 From: Natalie Date: Fri, 1 May 2026 01:11:47 -0400 Subject: [PATCH] =?UTF-8?q?feat(@projects/@magic-civilization):=20?= =?UTF-8?q?=E2=9C=A8=20add=20substrate-flora=20ontology=20split=20task?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Lilith Autocommit --- .project/objectives/README.md | 7 +- ...52-substrate-flora-cover-ontology-split.md | 121 ++++++++++++++++++ .project/team-leads/terraformer.md | 1 + .../games/age-of-dwarves/data/objectives.json | 20 ++- 4 files changed, 141 insertions(+), 8 deletions(-) create mode 100644 .project/objectives/p2-52-substrate-flora-cover-ontology-split.md diff --git a/.project/objectives/README.md b/.project/objectives/README.md index a6022975..ff2f1993 100644 --- a/.project/objectives/README.md +++ b/.project/objectives/README.md @@ -16,9 +16,9 @@ |---|---|---|---|---|---|---|---| | **P0** | 43 | 0 | 0 | 0 | 0 | 0 | 43 | | **P1** | 36 | 1 | 14 | 0 | 14 | 1 | 66 | -| **P2** | 33 | 0 | 5 | 1 | 6 | 6 | 51 | +| **P2** | 33 | 0 | 5 | 1 | 7 | 6 | 52 | | **P3 (oos)** | 3 | 0 | 0 | 0 | 1 | 19 | 23 | -| **total** | **115** | **1** | **19** | **1** | **21** | **26** | **183** | +| **total** | **115** | **1** | **19** | **1** | **22** | **26** | **184** | @@ -26,7 +26,7 @@ | Team Lead | Remaining | |---|---| -| [terraformer](../team-leads/terraformer.md) | 9 | +| [terraformer](../team-leads/terraformer.md) | 10 | | [warcouncil](../team-leads/warcouncil.md) | 7 | | [asset-sprite](../team-leads/asset-sprite.md) | 6 | | [shipwright](../team-leads/shipwright.md) | 5 | @@ -204,6 +204,7 @@ | [p2-49](p2-49-climate-axes-latitude-continentality.md) | 🟡 partial | Climate axes refactor — latitude + continentality + zonal winds as first-class per-hex inputs | [terraformer](../team-leads/terraformer.md) | 2026-04-30 | | [p2-50](p2-50-rng-determinism-pin.md) | 🟡 partial | Deterministic RNG + seed-derivation pin across mc-mapgen / mc-climate / mc-ecology | [terraformer](../team-leads/terraformer.md) | 2026-04-30 | | [p2-51](p2-51-world-shape-knobs.md) | 🟡 partial | Player-facing world-shape parameters on new-game screen | [terraformer](../team-leads/terraformer.md) | 2026-04-30 | +| [p2-52](p2-52-substrate-flora-cover-ontology-split.md) | ❌ missing | Split terrain enum into substrate × flora-cover layers (resolve biome ontology) | [terraformer](../team-leads/terraformer.md) | 2026-05-01 | ## Out of Scope (Game 2 / Game 3) diff --git a/.project/objectives/p2-52-substrate-flora-cover-ontology-split.md b/.project/objectives/p2-52-substrate-flora-cover-ontology-split.md new file mode 100644 index 00000000..82aabe4b --- /dev/null +++ b/.project/objectives/p2-52-substrate-flora-cover-ontology-split.md @@ -0,0 +1,121 @@ +--- +id: p2-52 +title: Split terrain enum into substrate × flora-cover layers (resolve biome ontology) +priority: p2 +status: missing +scope: game1 +owner: terraformer +updated_at: 2026-05-01 +canonical_doc: public/games/age-of-dwarves/docs/terrain/CLIMATE.md +coordinates_with: + - p1-46 + - p1-48 + - p1-50 + - p2-49 + - p2-51 +--- + +## Summary + +The current `terrain.json` enum (16 IDs) conflates three orthogonal +ecological layers into a single field: + +| Layer | Examples currently in terrain enum | Should live where | +|---|---|---| +| **Substrate** (geological / hydrological) | `mountains`, `hills`, `ocean`, `coast`, `lake`, `inland_sea`, `volcano`, `ice`, `snow` | terrain (correct) | +| **Flora-cover** (emergent from species) | `forest`, `jungle`, `boreal_forest`, `grassland`, `swamp` | derived from flora selector | +| **Substrate × climate composite** | `desert`, `tundra`, `plains` | derived label, not authored | + +The `feature_type: "foliage"` field on `forest`/`jungle`/`boreal_forest` +in `public/games/age-of-dwarves/data/terrain/land_forest.json` is the +data layer admitting these are flora cover masquerading as terrain. + +This conflation propagates everywhere: +- Flora species `biomes[]` arrays list flora-cover types as locking + conditions (a beech tree's `biomes = [temperate_forest, forest]` — + both are flora-cover labels, not substrates) +- Climate classifier (`mc-climate::derive::classify_terrain_whittaker`) + emits flora-cover names from T/P bands directly, skipping the + substrate axis +- Terrain blends (`terrain_blends.json`) mix substrate edges + (`coast+plains → shore`) with flora-cover edges + (`forest+plains → grass_fringe`) on equal footing + +This objective restructures the data model into three independent layers +that the renderer composes into a final visual: + +``` +Substrate ← Tectonics + Hydrology output + × Climate ← (t_band, p_band, riparian_distance) + × Flora-cover ← Ecology selector output (canopy/understory/ground/bare) + = Biome label ← Display name only, derived not stored +``` + +## Why p2 (not p1) and why Game 1 not Game 2 + +- Visually the lab works fine today because the conflation produces + plausible-looking output. Refactoring is **architectural cleanup**, + not a user-visible bug. +- The Wave A–E pipeline already implicitly does substrate (tectonics) + → climate → flora (ecology selector) — the data model lags the + pipeline. This objective reconciles them. +- Doing it pre-EA prevents lock-in of the wrong contract — every + subsequent Game-2 mechanic (leylines, soil g2-06, lifecycle g2-07, + population dynamics g2-08) inherits whatever shape ships with EA. + +## Acceptance + +- ◻ **New `substrate.json`** at `public/games/age-of-dwarves/data/terrain/` + authoring 8–10 substrate types: `bedrock`, `soil`, `sand`, + `permafrost`, `peat`, `water`, `seawater`, `ice`, `lava`. Each with + fields: `albedo`, `evapotranspiration_max`, `drainage`, `fertility_base`. +- ◻ **TileState refactor** — `TileState::terrain_id` deprecated; + replaced by `TileState::{substrate_id, flora_cover_id, biome_label_id}` + where `biome_label_id` is *derived* (read-only, computed from the + three independent fields). +- ◻ **Flora `biomes[]` rewrite** — every species's `biomes[]` array + becomes a list of `(substrate, climate_band)` pairs. Migration + script + visual diff to confirm equivalence. +- ◻ **Whittaker classifier consumes substrate** — + `mc-climate::derive::classify_terrain_whittaker(t_band, p_band, + substrate)` returns a `(biome_label, flora_cover_id)` pair. The + current single-output signature is replaced. +- ◻ **terrain_blends.json restructured** into two tables: + `substrate_blends.json` (substrate-edge ecotones like `coast+soil`) + and `flora_cover_blends.json` (e.g. `closed_canopy + open_grass → + forest_edge`). The renderer composes them. +- ◻ **Renderer composition** — Lab.tsx + Godot tile renderer fill + substrate base → flora cover overlay → biome decorations, each + driven by the matching field. +- ◻ **Backward-compat removed** — no aliasing of old `forest`/`jungle` + IDs to new substrate+cover combos. Per Zero Tech Debt, the old IDs + are gone, not shimmed. +- ◻ **Migration test** — for one frozen seed, before/after + comparison: every tile's *visual* output (substrate + flora cover + rendered) matches the pre-refactor render to within tolerance. + Determinism preserved. +- ◻ **Doc updates** — `CLIMATE.md` Whittaker section rewritten to + show 3-axis lookup; `ECOLOGY_BINDING.md` species index keyed on + `(substrate, t_band, p_band)`; new `SUBSTRATE.md` canonical doc + authored. + +## Dependencies / risks + +- p1-46 (Wave-E lab integration) should land FIRST, locking the + current visual baseline before the refactor. +- Touches `mc-climate`, `mc-mapgen`, `mc-ecology`, `api-gdext`, + `api-wasm`, all 6 lab pages, all flora/fauna species JSON, and + Godot tile renderer. High blast radius — coordinate carefully. +- Risk: post-EA save format must migrate (or refuse to load) old + worlds. Coordinate with `p2-50`'s save-format pin. + +## Non-goals + +- Adding new substrate types beyond the ~10 above (g2-05 / g2-06 + expand into lithology + soil orders for Game 2). +- Restructuring fauna `biomes[]` similarly — fauna mostly cluster + around flora cover anyway, lower priority. Possibly follow-up + objective. +- A "Substrate" page in the design lab — covered by the + per-substrate inspector on the Tectonics page (already authored + in p1-53). diff --git a/.project/team-leads/terraformer.md b/.project/team-leads/terraformer.md index d7573f6f..99659f2d 100644 --- a/.project/team-leads/terraformer.md +++ b/.project/team-leads/terraformer.md @@ -15,6 +15,7 @@ objectives: - p2-49 - p2-50 - p2-51 + - p2-52 --- ## Mandate diff --git a/public/games/age-of-dwarves/data/objectives.json b/public/games/age-of-dwarves/data/objectives.json index 9fe63775..378a0d25 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-05-01T05:05:59Z", + "generated_at": "2026-05-01T05:07:45Z", "totals": { - "stub": 1, - "done": 115, "partial": 19, + "missing": 22, "oos": 26, "in_progress": 1, - "missing": 21, - "total": 183 + "stub": 1, + "done": 115, + "total": 184 }, "objectives": [ { @@ -1610,6 +1610,16 @@ "updated_at": "2026-04-30", "summary": "The terraformer pipeline now exposes ~15 internal parameters\n(plate count, tectonic strength, fbm octaves, sea level, latitude\ngradient, continentality decay, rain-shadow factor, erosion\niterations, drainage threshold, etc.). Designers tune these in the\nforest lab; **players see none of them**. The new-game screen ships\n\"Map Size\" and not much else.\n\nIndustry baseline (Civ 6, Old World, Songs of Conquest) exposes 4–6\nhigh-level shape knobs. Each knob is a *preset* that derives several\ninternal parameters at once. This objective wires that surface from\nJSON presets through `mc-mapgen` parameters into the Godot game-setup\nscene." }, + { + "id": "p2-52", + "title": "Split terrain enum into substrate × flora-cover layers (resolve biome ontology)", + "priority": "p2", + "status": "missing", + "scope": "game1", + "owner": "terraformer", + "updated_at": "2026-05-01", + "summary": "The current `terrain.json` enum (16 IDs) conflates three orthogonal\necological layers into a single field:\n\n| Layer | Examples currently in terrain enum | Should live where |\n|---|---|---|\n| **Substrate** (geological / hydrological) | `mountains`, `hills`, `ocean`, `coast`, `lake`, `inland_sea`, `volcano`, `ice`, `snow` | terrain (correct) |\n| **Flora-cover** (emergent from species) | `forest`, `jungle`, `boreal_forest`, `grassland`, `swamp` | derived from flora selector |\n| **Substrate × climate composite** | `desert`, `tundra`, `plains` | derived label, not authored |\n\nThe `feature_type: \"foliage\"` field on `forest`/`jungle`/`boreal_forest`\nin `public/games/age-of-dwarves/data/terrain/land_forest.json` is the\ndata layer admitting these are flora cover masquerading as terrain.\n\nThis conflation propagates everywhere:\n- Flora species `biomes[]` arrays list flora-cover types as locking\n conditions (a beech tree's `biomes = [temperate_forest, forest]` —\n both are flora-cover labels, not substrates)\n- Climate classifier (`mc-climate::derive::classify_terrain_whittaker`)\n emits flora-cover names from T/P bands directly, skipping the\n substrate axis\n- Terrain blends (`terrain_blends.json`) mix substrate edges\n (`coast+plains → shore`) with flora-cover edges\n (`forest+plains → grass_fringe`) on equal footing\n\nThis objective restructures the data model into three independent layers\nthat the renderer composes into a final visual:\n\n```\nSubstrate ← Tectonics + Hydrology output\n × Climate ← (t_band, p_band, riparian_distance)\n × Flora-cover ← Ecology selector output (canopy/understory/ground/bare)\n = Biome label ← Display name only, derived not stored\n```" + }, { "id": "g2-01", "title": "Ley lines — Game 2 (Age of Kzzykt)",