diff --git a/.pql/changelog/ticket_history/2026-07.sql b/.pql/changelog/ticket_history/2026-07.sql index 34ccb23d4..5895714ef 100644 --- a/.pql/changelog/ticket_history/2026-07.sql +++ b/.pql/changelog/ticket_history/2026-07.sql @@ -205,3 +205,86 @@ Chirality decisions resolved (2026-07-02, with Jeroen): NAMING CONFIRMED: Keid''s habitable body Cadwal → Linnaeus (the great classifier; ties to Solander/Solandar taxonomy + the ''five kingdoms''). Capital stays Vance.', NULL, '2026-07-02 21:24:12', '2026-07-02 21:24:12.734', '2026-07-02 21:24:12.734', NULL, 'b13c9f1ce1d47437fdb599e6efce7f0e', 2) ON CONFLICT(hash) DO NOTHING; INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FJ91R8RV38BJXGB2V55FP3B8', 'status', 'backlog', 'in_progress', NULL, '2026-07-03 20:18:52', '2026-07-03 20:18:52.241', '2026-07-03 20:18:52.241', NULL, '89aa6692b3a1ab821cb7b02c3970f5a5', 2) ON CONFLICT(hash) DO NOTHING; INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FJ91R8RV38BJXGB2V55FP3B8', 'status', 'in_progress', 'in_progress', NULL, '2026-07-03 20:20:01', '2026-07-03 20:20:01.466', '2026-07-03 20:20:01.466', NULL, '6d2a3d77d702940bbc9207e4b9ce2b26', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FJ91R8RV38BJXGB2V55FP3B8', 'description', 'Operationalize the D-247 native-biosphere premise. Full brief appended. + +--- +BRIEF (2026-07-02) — governing record: D-247 (governance/decisions/content.md); consumer: D-246 / T-1084 mosaic register. + +WHAT THIS IS +D-247 established that habitable Reach worlds were alive before humans (the settlement timeline forbids terraforming from dead rock), and that an AUTHORED per-world chirality bit gates whether native life is edible. This ticket turns that canon into data + reconciled wiki + the vocabulary the mosaic needs. Owner: Miri (worldbuilding/continuity). Stakeholder: Burnelli (food-import economics). Co-maintainer for the vocab: Araminta (visual grammar, per the T-995 ObjectTag precedent). + +THE MODEL (locked in D-247 — do not re-litigate, apply it): +- `biosphere_class ∈ {NativeCompatible, NativeMirror, TerraformedSterile, Airless}`. It is an AUTHORED INPUT, never seed-derived — it follows a world''s economic/cultural identity. +- Compatible ("levo") = edible; coupling *permits* an authored hazard but does not force it (most are benign gardens). +- Mirror ("dextro") = inedible but *guaranteed* inert (can''t infect/poison — safe to study). +- "Safe" ≠ "edible" — decoupled. Bio-safe is guaranteed on Mirror; food-safe is Compatible-only. +- Achiral seam (always works either way): breathe air, drink water, mine ore, burn/build with native biomass. Only eat/metabolize/medicate/infect/chirally-poison are gated. +- ~50/50 Compatible:Mirror is a DESIGN TARGET across canon-silent worlds, not a per-world draw. + +WORKSTREAMS +1. CHIRALITY ASSIGNMENT (Miri). Give every inhabited body a biosphere_class by authored identity: principled default from `economic_base_primary` + `planet_class` (food/drink exporters → Compatible; dead+maintained-atmosphere → TerraformedSterile), explicit overrides where canon speaks. Locked exemplars: Arbour (GJ-338B)=Compatible; Solandar (GJ-827)=Mirror; Keid (GJ-166A)=Compatible (preserved *from exploitation* of edible alien life — the stronger conservation motive; Solandar is the Mirror-inert preserve); Oshima (GJ-79)=TerraformedSterile. Hit the ~50/50 target across the silent remainder. +2. WIKI CONTINUITY (Miri). Reconcile the entries that already gesture at native biochemistry so each is consistent with its assigned bit + the mechanism: GJ-827, GJ-524, GJ-166A, GJ-684A, GJ-197, GJ-79, GJ-124 (resolve whether Hạ Long''s "protein" is native-Compatible harvest or Earth aquaculture). Additive/explanatory, not contradictory — preserve deliberately-vague lines as intentional. +3. biosphere_class AUTHORING PATH (asset-pipeline discipline — schema + source + values together, per .claude/rules/asset-pipeline.md). Add `biosphere_class` to wiki body frontmatter (where `substrate:` already lives), a nullable `bodies.biosphere_class` column in systems-schema.sql + MIGRATION_SQL, and importer read. NOT needed for D-246; lands with this pass. +4. ALIEN-ECOLOGY VOCABULARY (Miri + Araminta) — feeds T-1084 directly. The mosaic palette is keyed on surface-class × climate-biome × biosphere-register (D-246 Palette-key). Provide the micro-habitat NAMES per biome × register: native-mirror niches get *alien* clade names (Solandar''s "five kingdoms" flavor); introduced-Earth niches get Earth names (wheatfield, orchard, pasture). Fill the fixed niche spine (canopy / understory / wet-hollow / pioneer / bare) per climate-biome (savanna gallery-copse, tundra thermokarst / lichen-heath, boreal muskeg / deadfall, …). DETERMINISM: the vocab must be fixed before T-1084 generates its first world (a later addition re-rolls affected voxels). Table size is not a perf/storage concern — author generously but to taste (~100k is headroom, not a target). + +BURNELLI (stakeholder, follow-on): a food-supply demand primitive keyed on biosphere_class (Mirror + frontier → import dependency; Compatible → local-food capable). Phase-2 economy consumer; does not block this ticket. + +CONSTRAINTS +- Authored input, not derivation (Arbour''s estate food must never be coin-flipped inedible). +- Semantic axes stay clade-agnostic (Forest = canopy whether oak or mega-fungus); the register skins niches, it does not change gameplay semantics. +- The mosaic geometry is chirality-blind — this work does NOT touch voxel geometry, only edibility/economy/naming/vocab. + +Chirality decisions resolved (2026-07-02, with Jeroen): +- Locked exemplars: Arbour=NativeCompatible, Solandar=NativeMirror, Keid=NativeCompatible, Oshima=TerraformedSterile. +- Miri assignments accepted: GJ-524=Mirror, Freyburg(GJ-684A)=Compatible, Hạ Long(GJ-124)=Compatible (native-Compatible harvest, per her rationale). +- USER OVERRIDES: Puerto Último(GJ-197)=NativeCompatible (breadbasket identity over Miri''s balance-motivated Mirror). Edict/Sirius(GJ-244A)=Airless — keep the world harsh/frozen (deliberately-inconvenient below-grade transit capital is load-bearing canon); no climate change; below-grade sealed habitat = no open-air biosphere. +- Two-gate default rule ACCEPTED for canon-silent worlds: Gate 0 (habitability from atmosphere/hydrosphere frontmatter → Airless / TerraformedSterile / Alive), Gate 1 (Compatible vs Mirror from economic_base_primary; agriculture→Compatible near-lock, penal→Mirror, research→50/50 lever). +- NAMING: Keid''s habitable body renamed Cadwal→(pending final pick; rec Linnaeus — pairs with Solander, its student naturalist; or Beagle). Capital stays ''Vance'' (already the author tribute). Miri''s IP flag (Cadwal = Vance''s exact book title) is the driver. +- Still to APPLY: the 7 additive wiki continuity edits (Miri specified exact text), the biosphere_class frontmatter+schema+importer plumbing (workstream 3), and the Cadwal→ rename across wiki + the GJ166A atlas proposal. + +NAMING CONFIRMED: Keid''s habitable body Cadwal → Linnaeus (the great classifier; ties to Solander/Solandar taxonomy + the ''five kingdoms''). Capital stays Vance.', 'Operationalize the D-247 native-biosphere premise. Full brief appended. + +--- +BRIEF (2026-07-02) — governing record: D-247 (governance/decisions/content.md); consumer: D-246 / T-1084 mosaic register. + +WHAT THIS IS +D-247 established that habitable Reach worlds were alive before humans (the settlement timeline forbids terraforming from dead rock), and that an AUTHORED per-world chirality bit gates whether native life is edible. This ticket turns that canon into data + reconciled wiki + the vocabulary the mosaic needs. Owner: Miri (worldbuilding/continuity). Stakeholder: Burnelli (food-import economics). Co-maintainer for the vocab: Araminta (visual grammar, per the T-995 ObjectTag precedent). + +THE MODEL (locked in D-247 — do not re-litigate, apply it): +- `biosphere_class ∈ {NativeCompatible, NativeMirror, TerraformedSterile, Airless}`. It is an AUTHORED INPUT, never seed-derived — it follows a world''s economic/cultural identity. +- Compatible ("levo") = edible; coupling *permits* an authored hazard but does not force it (most are benign gardens). +- Mirror ("dextro") = inedible but *guaranteed* inert (can''t infect/poison — safe to study). +- "Safe" ≠ "edible" — decoupled. Bio-safe is guaranteed on Mirror; food-safe is Compatible-only. +- Achiral seam (always works either way): breathe air, drink water, mine ore, burn/build with native biomass. Only eat/metabolize/medicate/infect/chirally-poison are gated. +- ~50/50 Compatible:Mirror is a DESIGN TARGET across canon-silent worlds, not a per-world draw. + +WORKSTREAMS +1. CHIRALITY ASSIGNMENT (Miri). Give every inhabited body a biosphere_class by authored identity: principled default from `economic_base_primary` + `planet_class` (food/drink exporters → Compatible; dead+maintained-atmosphere → TerraformedSterile), explicit overrides where canon speaks. Locked exemplars: Arbour (GJ-338B)=Compatible; Solandar (GJ-827)=Mirror; Keid (GJ-166A)=Compatible (preserved *from exploitation* of edible alien life — the stronger conservation motive; Solandar is the Mirror-inert preserve); Oshima (GJ-79)=TerraformedSterile. Hit the ~50/50 target across the silent remainder. +2. WIKI CONTINUITY (Miri). Reconcile the entries that already gesture at native biochemistry so each is consistent with its assigned bit + the mechanism: GJ-827, GJ-524, GJ-166A, GJ-684A, GJ-197, GJ-79, GJ-124 (resolve whether Hạ Long''s "protein" is native-Compatible harvest or Earth aquaculture). Additive/explanatory, not contradictory — preserve deliberately-vague lines as intentional. +3. biosphere_class AUTHORING PATH (asset-pipeline discipline — schema + source + values together, per .claude/rules/asset-pipeline.md). Add `biosphere_class` to wiki body frontmatter (where `substrate:` already lives), a nullable `bodies.biosphere_class` column in systems-schema.sql + MIGRATION_SQL, and importer read. NOT needed for D-246; lands with this pass. +4. ALIEN-ECOLOGY VOCABULARY (Miri + Araminta) — feeds T-1084 directly. The mosaic palette is keyed on surface-class × climate-biome × biosphere-register (D-246 Palette-key). Provide the micro-habitat NAMES per biome × register: native-mirror niches get *alien* clade names (Solandar''s "five kingdoms" flavor); introduced-Earth niches get Earth names (wheatfield, orchard, pasture). Fill the fixed niche spine (canopy / understory / wet-hollow / pioneer / bare) per climate-biome (savanna gallery-copse, tundra thermokarst / lichen-heath, boreal muskeg / deadfall, …). DETERMINISM: the vocab must be fixed before T-1084 generates its first world (a later addition re-rolls affected voxels). Table size is not a perf/storage concern — author generously but to taste (~100k is headroom, not a target). + +BURNELLI (stakeholder, follow-on): a food-supply demand primitive keyed on biosphere_class (Mirror + frontier → import dependency; Compatible → local-food capable). Phase-2 economy consumer; does not block this ticket. + +CONSTRAINTS +- Authored input, not derivation (Arbour''s estate food must never be coin-flipped inedible). +- Semantic axes stay clade-agnostic (Forest = canopy whether oak or mega-fungus); the register skins niches, it does not change gameplay semantics. +- The mosaic geometry is chirality-blind — this work does NOT touch voxel geometry, only edibility/economy/naming/vocab. + +Chirality decisions resolved (2026-07-02, with Jeroen): +- Locked exemplars: Arbour=NativeCompatible, Solandar=NativeMirror, Keid=NativeCompatible, Oshima=TerraformedSterile. +- Miri assignments accepted: GJ-524=Mirror, Freyburg(GJ-684A)=Compatible, Hạ Long(GJ-124)=Compatible (native-Compatible harvest, per her rationale). +- USER OVERRIDES: Puerto Último(GJ-197)=NativeCompatible (breadbasket identity over Miri''s balance-motivated Mirror). Edict/Sirius(GJ-244A)=Airless — keep the world harsh/frozen (deliberately-inconvenient below-grade transit capital is load-bearing canon); no climate change; below-grade sealed habitat = no open-air biosphere. +- Two-gate default rule ACCEPTED for canon-silent worlds: Gate 0 (habitability from atmosphere/hydrosphere frontmatter → Airless / TerraformedSterile / Alive), Gate 1 (Compatible vs Mirror from economic_base_primary; agriculture→Compatible near-lock, penal→Mirror, research→50/50 lever). +- NAMING: Keid''s habitable body renamed Cadwal→(pending final pick; rec Linnaeus — pairs with Solander, its student naturalist; or Beagle). Capital stays ''Vance'' (already the author tribute). Miri''s IP flag (Cadwal = Vance''s exact book title) is the driver. +- Still to APPLY: the 7 additive wiki continuity edits (Miri specified exact text), the biosphere_class frontmatter+schema+importer plumbing (workstream 3), and the Cadwal→ rename across wiki + the GJ166A atlas proposal. + +NAMING CONFIRMED: Keid''s habitable body Cadwal → Linnaeus (the great classifier; ties to Solander/Solandar taxonomy + the ''five kingdoms''). Capital stays Vance. + +PROGRESS 2026-07-03: Workstreams 1-2 DONE + committed (388168eef, branch micro-habitat-mosaic). +- Cadwal→Linnaeus rename applied (body frontmatter name + biosphere_class:NativeCompatible, GJ-166A infobox, atlas proposal, D-247 clarified). Capital stays Vance. +- 5 wiki continuity edits applied (GJ-524 Mirror, GJ-166A/Keid Compatible, GJ-684A Freyburg Compatible, GJ-124 Hạ Long Compatible, GJ-197 Puerto Último Compatible). Solandar/Oshima needed none. +REMAINING: +- Workstream 3 (biosphere_class plumbing): add nullable bodies.biosphere_class column to systems-schema.sql; teach tooling/db/wiki_sync.py import_from_wiki to read biosphere_class frontmatter + apply the two-gate DEFAULT for canon-silent bodies (Gate0 atmosphere/hydrosphere, Gate1 economic_role/economic_base); add explicit frontmatter for the 3 override bodies still needing it (Solandar=NativeMirror, GJ-524=NativeMirror [uninhabited-but-alive → default would mis-Airless it], Edict/GJ244Ad=Airless [standard-atm+ice edge case]); ensure generate_wiki round-trips biosphere_class; then the BAKE (run import_from_wiki + generate_wiki → systems.db + regenerated infoboxes, incl. the Linnaeus name propagation) + commit systems.db. Add biosphere_class to BodyParams read set (D-240) as the D-247 code hook. NOTE: the bake is a systems.db binary-artifact commit — consequential, do deliberately. +- Workstream 4 (alien-ecology vocab → palette): integrate Miri''s biome × register vocabulary (cane/vane/wrack/husk/drum-root native-mirror lexicon + Earth-register names, per her Deliverable 3) into atlas/mosaic.rs + mosaic_constants.toml — extends the temperate/native baseline to the full surface-class × climate-biome × register palette. This also completes T-1084''s biome-awareness. Determinism: lock before generation. +Miri''s full Deliverable 3 vocab (the source for workstream 4) is in her T-1085 agent report.', NULL, '2026-07-03 20:40:15', '2026-07-03 20:40:15.305', '2026-07-03 20:40:15.305', NULL, 'fa34e073db0be60d59ec01ebd7a5e5f4', 2) ON CONFLICT(hash) DO NOTHING; diff --git a/.pql/changelog/tickets/2026-07.sql b/.pql/changelog/tickets/2026-07.sql index 806ece109..4b6af1892 100644 --- a/.pql/changelog/tickets/2026-07.sql +++ b/.pql/changelog/tickets/2026-07.sql @@ -218,3 +218,49 @@ Chirality decisions resolved (2026-07-02, with Jeroen): - Still to APPLY: the 7 additive wiki continuity edits (Miri specified exact text), the biosphere_class frontmatter+schema+importer plumbing (workstream 3), and the Cadwal→ rename across wiki + the GJ166A atlas proposal. NAMING CONFIRMED: Keid''s habitable body Cadwal → Linnaeus (the great classifier; ties to Solander/Solandar taxonomy + the ''five kingdoms''). Capital stays Vance.', 'in_progress', 'medium', 'miri', NULL, 'D-247', '2026-07-02 20:30:57.222', '2026-07-03 20:20:01.466', NULL, 'f7bd0e6fd21f924f65afcec9a7ed9624', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FJ91R8RV38BJXGB2V55FP3B8', 'story', '06FGSJNP8YM537DJCC82ZD6544', 'Biosphere register (D-247) — chirality bits, wiki continuity, biosphere_class authoring + alien-ecology vocab', 'Operationalize the D-247 native-biosphere premise. Full brief appended. + +--- +BRIEF (2026-07-02) — governing record: D-247 (governance/decisions/content.md); consumer: D-246 / T-1084 mosaic register. + +WHAT THIS IS +D-247 established that habitable Reach worlds were alive before humans (the settlement timeline forbids terraforming from dead rock), and that an AUTHORED per-world chirality bit gates whether native life is edible. This ticket turns that canon into data + reconciled wiki + the vocabulary the mosaic needs. Owner: Miri (worldbuilding/continuity). Stakeholder: Burnelli (food-import economics). Co-maintainer for the vocab: Araminta (visual grammar, per the T-995 ObjectTag precedent). + +THE MODEL (locked in D-247 — do not re-litigate, apply it): +- `biosphere_class ∈ {NativeCompatible, NativeMirror, TerraformedSterile, Airless}`. It is an AUTHORED INPUT, never seed-derived — it follows a world''s economic/cultural identity. +- Compatible ("levo") = edible; coupling *permits* an authored hazard but does not force it (most are benign gardens). +- Mirror ("dextro") = inedible but *guaranteed* inert (can''t infect/poison — safe to study). +- "Safe" ≠ "edible" — decoupled. Bio-safe is guaranteed on Mirror; food-safe is Compatible-only. +- Achiral seam (always works either way): breathe air, drink water, mine ore, burn/build with native biomass. Only eat/metabolize/medicate/infect/chirally-poison are gated. +- ~50/50 Compatible:Mirror is a DESIGN TARGET across canon-silent worlds, not a per-world draw. + +WORKSTREAMS +1. CHIRALITY ASSIGNMENT (Miri). Give every inhabited body a biosphere_class by authored identity: principled default from `economic_base_primary` + `planet_class` (food/drink exporters → Compatible; dead+maintained-atmosphere → TerraformedSterile), explicit overrides where canon speaks. Locked exemplars: Arbour (GJ-338B)=Compatible; Solandar (GJ-827)=Mirror; Keid (GJ-166A)=Compatible (preserved *from exploitation* of edible alien life — the stronger conservation motive; Solandar is the Mirror-inert preserve); Oshima (GJ-79)=TerraformedSterile. Hit the ~50/50 target across the silent remainder. +2. WIKI CONTINUITY (Miri). Reconcile the entries that already gesture at native biochemistry so each is consistent with its assigned bit + the mechanism: GJ-827, GJ-524, GJ-166A, GJ-684A, GJ-197, GJ-79, GJ-124 (resolve whether Hạ Long''s "protein" is native-Compatible harvest or Earth aquaculture). Additive/explanatory, not contradictory — preserve deliberately-vague lines as intentional. +3. biosphere_class AUTHORING PATH (asset-pipeline discipline — schema + source + values together, per .claude/rules/asset-pipeline.md). Add `biosphere_class` to wiki body frontmatter (where `substrate:` already lives), a nullable `bodies.biosphere_class` column in systems-schema.sql + MIGRATION_SQL, and importer read. NOT needed for D-246; lands with this pass. +4. ALIEN-ECOLOGY VOCABULARY (Miri + Araminta) — feeds T-1084 directly. The mosaic palette is keyed on surface-class × climate-biome × biosphere-register (D-246 Palette-key). Provide the micro-habitat NAMES per biome × register: native-mirror niches get *alien* clade names (Solandar''s "five kingdoms" flavor); introduced-Earth niches get Earth names (wheatfield, orchard, pasture). Fill the fixed niche spine (canopy / understory / wet-hollow / pioneer / bare) per climate-biome (savanna gallery-copse, tundra thermokarst / lichen-heath, boreal muskeg / deadfall, …). DETERMINISM: the vocab must be fixed before T-1084 generates its first world (a later addition re-rolls affected voxels). Table size is not a perf/storage concern — author generously but to taste (~100k is headroom, not a target). + +BURNELLI (stakeholder, follow-on): a food-supply demand primitive keyed on biosphere_class (Mirror + frontier → import dependency; Compatible → local-food capable). Phase-2 economy consumer; does not block this ticket. + +CONSTRAINTS +- Authored input, not derivation (Arbour''s estate food must never be coin-flipped inedible). +- Semantic axes stay clade-agnostic (Forest = canopy whether oak or mega-fungus); the register skins niches, it does not change gameplay semantics. +- The mosaic geometry is chirality-blind — this work does NOT touch voxel geometry, only edibility/economy/naming/vocab. + +Chirality decisions resolved (2026-07-02, with Jeroen): +- Locked exemplars: Arbour=NativeCompatible, Solandar=NativeMirror, Keid=NativeCompatible, Oshima=TerraformedSterile. +- Miri assignments accepted: GJ-524=Mirror, Freyburg(GJ-684A)=Compatible, Hạ Long(GJ-124)=Compatible (native-Compatible harvest, per her rationale). +- USER OVERRIDES: Puerto Último(GJ-197)=NativeCompatible (breadbasket identity over Miri''s balance-motivated Mirror). Edict/Sirius(GJ-244A)=Airless — keep the world harsh/frozen (deliberately-inconvenient below-grade transit capital is load-bearing canon); no climate change; below-grade sealed habitat = no open-air biosphere. +- Two-gate default rule ACCEPTED for canon-silent worlds: Gate 0 (habitability from atmosphere/hydrosphere frontmatter → Airless / TerraformedSterile / Alive), Gate 1 (Compatible vs Mirror from economic_base_primary; agriculture→Compatible near-lock, penal→Mirror, research→50/50 lever). +- NAMING: Keid''s habitable body renamed Cadwal→(pending final pick; rec Linnaeus — pairs with Solander, its student naturalist; or Beagle). Capital stays ''Vance'' (already the author tribute). Miri''s IP flag (Cadwal = Vance''s exact book title) is the driver. +- Still to APPLY: the 7 additive wiki continuity edits (Miri specified exact text), the biosphere_class frontmatter+schema+importer plumbing (workstream 3), and the Cadwal→ rename across wiki + the GJ166A atlas proposal. + +NAMING CONFIRMED: Keid''s habitable body Cadwal → Linnaeus (the great classifier; ties to Solander/Solandar taxonomy + the ''five kingdoms''). Capital stays Vance. + +PROGRESS 2026-07-03: Workstreams 1-2 DONE + committed (388168eef, branch micro-habitat-mosaic). +- Cadwal→Linnaeus rename applied (body frontmatter name + biosphere_class:NativeCompatible, GJ-166A infobox, atlas proposal, D-247 clarified). Capital stays Vance. +- 5 wiki continuity edits applied (GJ-524 Mirror, GJ-166A/Keid Compatible, GJ-684A Freyburg Compatible, GJ-124 Hạ Long Compatible, GJ-197 Puerto Último Compatible). Solandar/Oshima needed none. +REMAINING: +- Workstream 3 (biosphere_class plumbing): add nullable bodies.biosphere_class column to systems-schema.sql; teach tooling/db/wiki_sync.py import_from_wiki to read biosphere_class frontmatter + apply the two-gate DEFAULT for canon-silent bodies (Gate0 atmosphere/hydrosphere, Gate1 economic_role/economic_base); add explicit frontmatter for the 3 override bodies still needing it (Solandar=NativeMirror, GJ-524=NativeMirror [uninhabited-but-alive → default would mis-Airless it], Edict/GJ244Ad=Airless [standard-atm+ice edge case]); ensure generate_wiki round-trips biosphere_class; then the BAKE (run import_from_wiki + generate_wiki → systems.db + regenerated infoboxes, incl. the Linnaeus name propagation) + commit systems.db. Add biosphere_class to BodyParams read set (D-240) as the D-247 code hook. NOTE: the bake is a systems.db binary-artifact commit — consequential, do deliberately. +- Workstream 4 (alien-ecology vocab → palette): integrate Miri''s biome × register vocabulary (cane/vane/wrack/husk/drum-root native-mirror lexicon + Earth-register names, per her Deliverable 3) into atlas/mosaic.rs + mosaic_constants.toml — extends the temperate/native baseline to the full surface-class × climate-biome × register palette. This also completes T-1084''s biome-awareness. Determinism: lock before generation. +Miri''s full Deliverable 3 vocab (the source for workstream 4) is in her T-1085 agent report.', 'in_progress', 'medium', 'miri', NULL, 'D-247', '2026-07-02 20:30:57.222', '2026-07-03 20:40:15.305', NULL, '83246ebc2aa0550f44afd68721efb9d4', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); diff --git a/docs/design/alien-ecology-vocabulary.md b/docs/design/alien-ecology-vocabulary.md new file mode 100644 index 000000000..dffcc10f4 --- /dev/null +++ b/docs/design/alien-ecology-vocabulary.md @@ -0,0 +1,202 @@ +# Alien-Ecology Vocabulary — micro-habitat naming layer (D-247 / D-246 / T-1085) + +Authored by **Miri** (T-1085 workstream 4), 2026-07-03. This is the **naming / +rendering** layer that skins the mosaic's semantic micro-habitats (T-1084, D-246). +The mosaic selects **D-228 axis-triples** (terrain × vegetation × water); this table +supplies the **display names** per `climate-biome × register × niche`, so a "canopy" +niche in a temperate forest reads as *vane-cane stand* (native-mirror) or *dense +stand* (introduced-Earth). Names are **derived labels** (D-228) — never stored on the +voxel. Co-maintenance with **Araminta** for visual grammar (per the T-995 ObjectTag +precedent). + +Governing canon: [D-247](../../governance/decisions/content.md) (native biospheres + +chirality), [D-246](../../governance/decisions/architecture.md) (the mosaic that +consumes this). Determinism: this vocabulary is part of the deterministic input +surface once wired — fix it before the mosaic generates its first world. + +--- + +## Methodology + +### Niche spine (the cross-biome authoring scaffold — reinterpreted per biome) + +The five niches are **roles**, not literal "trees exist here" claims — each biome +reinterprets them (a grassland's "canopy" is its tallest grass stratum): + +- **canopy** — the biome's tallest/densest cover stratum. Full overhead concealment + where the biome has one; the thickest topmost growth otherwise. Blocks sightlines + + weather; sets the movement-sound baseline. +- **understory** — the layer beneath/within canopy, or a secondary partial-cover + stratum where there's no true canopy. Partial concealment, filtered light. +- **wet-hollow** — moisture-conditioned low point, tied to the `voxel_relief` + micro-thalweg (D-246). Slows movement; sound and smell carry differently. +- **pioneer** — colonizing / disturbed / regrowth ground; first-succession or edge + margin. The "in-between" transition zone. +- **bare-ground** — minimal-to-no cover, exposed substrate. Best sightlines, least + concealment, fastest movement; the biome's "clearing"/"pan" default. + +### Register naming + +- **Introduced-Earth** — ordinary recognizable agricultural/ecological English + (orchard, pasture, hedgerow): warm, human-scaled, legible at a glance. +- **Native (alien)** — built from a small recurring invented growth-habit lexicon (the + alien equivalent of "tree / grass / moss / reed" as informal field terms, not formal + taxonomy — Solandar's "five kingdoms" stays deliberately unnamed at the formal level). + +### The native growth-habit lexicon + +| Root | Fills the role of | Typical niche | +|---|---|---| +| **cane** | tall, upright, single-or-clustered stalks | canopy / understory in wooded biomes | +| **vane** | broad, blade-like, often iridescent structures | canopy (the richest biomes) | +| **wrack** | low, spreading, mat-forming growth | bare-ground / pioneer / understory | +| **husk** | bulb/pod-bearing, clustered near water | wet-hollow | +| **drum-root** | colonial, ring-spreading root-network | pioneer (recurs across Grassland/Tundra/Savanna) | + +A few names deliberately **recur across biomes** (husk-fall tangle, lichen-scald flat, +scorch-bloom flush, the drum-root/ring motif) — intentional coherence: it reads as one +designed ecology, not eleven unrelated ones. This is the Bobiverse "convergent-but-alien +niches" touchstone made concrete: the same growth-habit fills the same niche across +biomes, as oak/pine/baobab all fill "canopy" on Earth without being the same species. + +### Sensory discipline — the achiral seam (D-247) stays intact + +Native water is always safe to drink, ground always safe to mine/walk, biomass always +safe to burn/build. Every note flavors **strangeness** (smell, sound, texture, footing), +never *danger*, for those actions. Where hazard flavor appears it is **mechanical/ +textural** (brittle husks giving way, thorns, uneven footing) — never biochemical +(Mirror life can't infect or poison; Compatible hazard is an *authored* option, flagged +as a hook, not exercised on any specific world here). + +### Scope + +This table fills **climate-biome × register**, richest on the six vegetated biomes, +lighter on the five extreme ones (niches merged where they don't naturally apply). A +full `climate-biome × surface-class × register` cross-product is a future pass; the +D-246 Sand/Rock/Gravel/Lava surface-class rows already carry register-neutral flavor. + +--- + +## Rich biomes + +### TropicalWet +*The Solandar register — hot, wet, maximum biomass, fast decomposition. "Twelve thousand species, five kingdoms."* + +| Niche | Native-Mirror | Introduced-Earth | +|---|---|---| +| Canopy | **Vane-crown stand** — towering oil-sheened vane-blades interlock into a solid ceiling; understory twilight-dim at midday. A thick sweetish rot-and-sap smell that never clears. | **Cacao shade-block** — planted tree-crop under managed shade canopy; rows just regular enough to give away human hands. | +| Understory | **Wrack-drape thicket** — wet curtains of wrack draping from the canopy; clings and slows movement, leaves a faint chemical tang. | **Shade-coffee row** — broad-leafed cultivars beneath the plantation canopy. | +| Wet-hollow | **Husk-seep hollow** — bellied husk-growths at a warm standing seep; water runs faintly opalescent with alien tannins — safe to drink, unmistakably not Earth's water by the smell. | **Rice-paddy terrace** — flooded cultivated wet-ground rows. | +| Pioneer | **Scorch-bloom flush** — fast alien color-succession flooding a canopy-fall light-gap within weeks, before slower vane-forms retake it. | **Cassava clearing** — a slashed-and-fallow plot gone to quick Earth pioneer weeds and root crops. | +| Bare-ground | **Salt-crust flat** — mineral-crusted open patch where nothing native roots, by chemistry not disturbance; wrong-colored crust crunches underfoot. | **Threshing yard** — bare packed earth cut into the plantation. | + +### TemperateForest + +| Niche | Native-Mirror | Introduced-Earth (existing D-246 baseline) | +|---|---|---| +| Canopy | **Vane-cane stand** — cane trunks crowned with stiff, faintly metallic-blue vane-fronds; a dry clicking rustle, not a hiss — it doesn't *sound* like a forest. | **Dense stand** | +| Understory | **Wrack-frond undergrowth** — knee-high tangle, cool and slightly rubbery; brushing it releases a sharp resinous smell with no Earth analog. | **Fern undergrowth** | +| Wet-hollow | **Husk-reed run** — husk-reeds trace the brook's micro-thalweg; husks pop and refill audibly as current passes — heard before seen. | **Brook** | +| Pioneer | **Husk-fall tangle** — collapsed cane-husks, brittle and hollow rather than woody; lighter to move than deadfall but sharper underfoot (a footing hazard, not a fire-fuel one). | **Deadfall** | +| Bare-ground | **Bloom-scald clearing** — kept open by an allelopathic wrack that poisons competing roots (achiral-safe to walk, just barren); faint bitter-almond air. | **Glade** | + +### TemperateGrassland + +| Niche | Native-Mirror | Introduced-Earth | +|---|---|---| +| Canopy | **Cane-tassel sweep** — waist-to-shoulder cane tipped with heavy seed-tassels; moves in slow grass-like waves, but the tassels chime softly against each other. | **Tussock** | +| Understory | **Wrack-bloom understory** — a low flowering layer threading between the cane-tassels; a sweet "almost right" smell that unsettles Earth-register visitors. | **Wildflower** | +| Wet-hollow | **Husk-line seep** — the ephemeral channel lined with squat husk-forms that only surface after rain — a visible tell of where the water runs even in dry season. | **Dry wash** | +| Pioneer | **Drum-root knot** — a knotted ring of colonial root-growth pushing up through the grass, spreading centimeters a year; the ring's center is always slightly bare. | **Scrub island** | +| Bare-ground | **Hardpan scald** — a mineral-crusted bare patch; the ground rings slightly hollow underfoot. | **Grazing pan** — bare-trodden earth from stock. | + +### BorealForest + +| Niche | Native-Mirror | Introduced-Earth | +|---|---|---| +| Canopy | **Rime-vane stand** — slow, frost-hardy vane-cane holding a permanent rime-like bloom even in summer; dim, cold, nearly silent — a "wrong" quiet. | **Spruce-shadow stand** — conifer-analog, deep cold shade, needle-duff footing. | +| Understory | **Wrack-moss carpet** — thick spongy wrack holding meltwater like a sponge; boots sink and release a cold mineral smell. | **Bilberry scrub** | +| Wet-hollow | **Husk-bog muskeg** — waterlogged cold ground where the tussocks are husk-forms; each step risks a hollow husk giving way. | **Muskeg fen** | +| Pioneer | **Husk-fall tangle** (shared with TemperateForest) | **Deadfall** — cold-climate blowdown. | +| Bare-ground | **Lichen-scald flat** — thin pale native crust over permafrost-like ground, ancient and slow; disturbing it outlasts a human lifetime. | **Burn scar** | + +### Tundra +*Treeless, thermokarst. Thin biomass; niches all present but small.* + +| Niche | Native-Mirror | Introduced-Earth | +|---|---|---| +| Canopy (dwarf) | **Husk-heath brake** — the tallest native growth is ankle-high, a dense husk-mat; reads as heath at distance, clearly not-leaves up close. | **Dwarf-willow brake** | +| Understory | **Wrack-lichen mat** — a thin crust; grows so slowly that a season's footpath shows a decade later. | **Moss-sedge mat** | +| Wet-hollow | **Thermokarst husk-pool** — a collapse-and-thaw pool ringed with husk-forms that emerge only in the brief thaw window — seasonal in a way no other biome's wet-hollow is. | **Thaw-pond sedge** | +| Pioneer | **Drum-root ring** (shared motif, at tundra scale — centimeters across after decades) | **Gravel scar** | +| Bare-ground | **Lichen-scald flat** (shared with Boreal — here over patterned-ground polygons) | **Frost-heave flat** | + +### Savanna +*Warm, seasonal, grass-dominant with scattered tree-copses.* + +| Niche | Native-Mirror | Introduced-Earth | +|---|---|---| +| Canopy | **Vane-crown copse** — an isolated stand, the only real shade for kilometers; mistakable for a gallery copse at distance, but the bark-analog is smooth and faintly warm. | **Gallery copse** | +| Understory | **Husk-thorn scrub** — thorny native scrub, identical obstruction/concealment to acacia; the thorns are hollow and whistle faintly in wind. | **Acacia scrub** | +| Wet-hollow | **Husk-pan waterhole** — seasonal flood-and-dry; dormant husk-forms visibly "wake" within days of the first rain. | **Seasonal waterhole** | +| Pioneer | **Scorch-bloom flush** (shared with TropicalWet — succession by seasonal fire) | **Burnt-grass flush** | +| Bare-ground | **Drum-mound flat** — a hardpan clearing built by colonial drum-root activity; bare, sun-baked, faintly warm underfoot even at night. | **Termite-pan clearing** | + +--- + +## Lighter biomes (extreme classes — niches merged/noted where they don't apply) + +### Desert +| Niche | Native-Mirror | Introduced-Earth | +|---|---|---| +| Canopy ("tallest thing here") | **Cane-spire stand** — sparse deep-taprooted spires, each commanding the horizon with a thin moving shadow. | **Date-palm draw** | +| Understory + pioneer (merged) | **Wrack-thorn scatter** — sparse widely-spaced domed clumps hoarding moisture. | **Scrub flat** | +| Wet-hollow | **Husk-spring hollow** — a rare seep ringed with water-swollen husk-forms; the local landmark. Water fine to drink; nothing in the ring looks edible. | **Wadi oasis** | +| Bare-ground | *served by the D-246 Sand/Rock rows* (dune crest, hardpan, outcrop, scree) — bare rock/sand carries no register. | (same) | + +### Alpine +| Niche | Native-Mirror | Introduced-Earth | +|---|---|---| +| Canopy (treeline fringe) | **Wind-cane fringe** — wind-flagged, permanently bent one way; the flag-shape wayfinds like krummholz, the material reads wrong up close. | **Krummholz fringe** | +| Understory + pioneer (merged) | **Wrack-cushion field** — tight ground-hugging growth, faintly warm to bare skin (a metabolic-heat tell Earth cushion plants lack). | **Cushion-plant field** | +| Wet-hollow | **Husk-melt seep** — seasonal meltwater seep, husk-forms present only weeks a year. | **Snowmelt sedge** | +| Bare-ground | *served by the Rock rows* (outcrop/scree/talus/ledge/**lichen** — lichen doubles as this biome's dominant cover). | (same) | + +### Volcanic +*D-210 flags this the least data-grounded biome; the Lava surface-class already has the richest table. Kept light.* + +| Niche | Native-Mirror | Introduced-Earth | +|---|---|---| +| Recovering slope (canopy/understory collapsed) | **Husk-root slope** — pioneer husk-root colonizing cooled ash, first native life back after a flow. | **Ash-orchard slope** — Earth fruit trees on fertile volcanic ash. | +| Wet-hollow | **Husk-vent seep** — a geothermal seep, the hardiest husk-forms in the register; warm, safe to bathe in. | **Hot-spring terrace** | +| Pioneer + bare-ground | *served by the Lava rows* (fresh sheet, weathered crust w/ Lichen, tube collapse, pioneer scrub). | (same) | + +### Wetland (climate-biome) +*Heavily overlaps the surface-class Wetland table (richest baseline). Native mirror given for symmetry; kept light.* + +| Niche | Native-Mirror | Introduced-Earth (existing D-246 names) | +|---|---|---| +| Canopy | **Husk-crown copse** — water-rooted husk-trees hung with drip-tendrils. | **Copse** | +| Understory / bare-ground | **Wrack-flat clearing** — open wrack-carpeted shallows. | **Meadow / Clearing** | +| Wet-hollow (×3) | **Husk-bog / Husk-marsh / Husk-run** — the marsh variant's reed-analog hisses faintly when disturbed, a natural detection tell. | **Bog / Marsh / Creek** | +| Pioneer | thin/underdeveloped (same gap as the baseline) | (same gap) | + +### CoastalLowland +| Niche | Native-Mirror | Introduced-Earth | +|---|---|---| +| Canopy | **Vane-palm fringe** — salt-tolerant, leaning permanently seaward; same visual tell as a coastal treeline, different material. | **Coconut fringe** | +| Understory + pioneer (merged) | **Wrack-tangle scrub** — salt-hardy tangle; smells of ozone and brine-that's-not-quite-brine. | **Dune scrub** | +| Wet-hollow (tidal) | **Husk-tide flat** — husk-forms seal shut at low tide and visibly "breathe" open as water returns — a legible tide-state tell. | **Salt-marsh flat** | +| Bare-ground | **Husk-shard beach** — the tideline wrack is broken husk-fragments instead of shells; the wrong shapes up close. | **Shell-sand beach** | + +--- + +## Open hooks (Miri, T-1085) + +- **Five kingdoms** — Solandar's "twelve thousand species across five kingdoms" is left + formally unnamed (deliberately-vague canon). Naming the kingdoms is a future hook. +- **Compatible-world hazard** — a Compatible (levo) biome (TropicalWet, Savanna) is the + right *register* for a future authored native pathogen/toxin (D-247 §"coupling permits + but doesn't force"); a hook, not yet exercised. +- **Full cross-product** — climate-biome × **surface-class** × register (this table is + climate-biome × register only) is a future pass. diff --git a/server/data/mosaic_constants.toml b/server/data/mosaic_constants.toml index 0bd689dc5..7228d2feb 100644 --- a/server/data/mosaic_constants.toml +++ b/server/data/mosaic_constants.toml @@ -107,38 +107,160 @@ water = "Dry" weight = 15 affinity = "any" -[[grassland]] -name = "tussock" +# Grassland is BIOME-KEYED (T-1085, D-246): the ClimateBiome derived from the district's +# temperature_c + moisture_q selects savanna / temperate / boreal / tundra, so a +# grassland reads distinctly per climate rather than as one biome-blind palette. §8-safe +# (vegetation + water; terrain stays Soil in v1). Names per +# docs/design/alien-ecology-vocabulary.md. + +[[grassland_temperate]] +name = "cane-tassel sweep" terrain = "Soil" vegetation = "Grass" water = "Dry" weight = 40 affinity = "any" -[[grassland]] -name = "scrub island" +[[grassland_temperate]] +name = "drum-root knot" terrain = "Soil" vegetation = "Scrub" water = "Dry" weight = 18 affinity = "dry" -[[grassland]] -name = "wildflower" +[[grassland_temperate]] +name = "wrack-bloom" terrain = "Soil" vegetation = "Meadow" water = "Dry" weight = 17 affinity = "dry" -[[grassland]] -name = "dry wash" -terrain = "Gravel" +[[grassland_temperate]] +name = "husk-line seep" +terrain = "Soil" vegetation = "Barren" water = "Dry" weight = 10 affinity = "wet" +# Savanna: scattered Forest copses in a grass matrix — the vane-crown copse is the +# clearest distinction from temperate grassland. +[[grassland_savanna]] +name = "cane-tassel sweep" +terrain = "Soil" +vegetation = "Grass" +water = "Dry" +weight = 38 +affinity = "any" + +[[grassland_savanna]] +name = "vane-crown copse" +terrain = "Soil" +vegetation = "Forest" +water = "Dry" +weight = 12 +affinity = "dry" + +[[grassland_savanna]] +name = "husk-thorn scrub" +terrain = "Soil" +vegetation = "Scrub" +water = "Dry" +weight = 18 +affinity = "dry" + +[[grassland_savanna]] +name = "husk-pan waterhole" +terrain = "Soil" +vegetation = "Grass" +water = "Shallow" +weight = 10 +affinity = "wet" + +[[grassland_savanna]] +name = "drum-mound flat" +terrain = "Soil" +vegetation = "Barren" +water = "Dry" +weight = 12 +affinity = "dry" + +# Boreal grassland: cooler, sparser — more scrub, a lichen fraction, no meadow. +[[grassland_boreal]] +name = "cane-tassel sweep" +terrain = "Soil" +vegetation = "Grass" +water = "Dry" +weight = 30 +affinity = "any" + +[[grassland_boreal]] +name = "wrack-scrub" +terrain = "Soil" +vegetation = "Scrub" +water = "Dry" +weight = 25 +affinity = "dry" + +[[grassland_boreal]] +name = "husk-line seep" +terrain = "Soil" +vegetation = "Grass" +water = "Shallow" +weight = 10 +affinity = "wet" + +[[grassland_boreal]] +name = "wrack-lichen mat" +terrain = "Soil" +vegetation = "Lichen" +water = "Dry" +weight = 12 +affinity = "any" + +[[grassland_boreal]] +name = "cold scald" +terrain = "Soil" +vegetation = "Barren" +water = "Dry" +weight = 15 +affinity = "dry" + +# Tundra: sparse and cold — scrub, lichen, bare patterned ground, NO forest. +[[grassland_tundra]] +name = "husk-heath brake" +terrain = "Soil" +vegetation = "Scrub" +water = "Dry" +weight = 26 +affinity = "any" + +[[grassland_tundra]] +name = "wrack-lichen mat" +terrain = "Soil" +vegetation = "Lichen" +water = "Dry" +weight = 24 +affinity = "any" + +[[grassland_tundra]] +name = "thermokarst husk-pool" +terrain = "Soil" +vegetation = "Grass" +water = "Shallow" +weight = 10 +affinity = "wet" + +[[grassland_tundra]] +name = "lichen-scald flat" +terrain = "Soil" +vegetation = "Barren" +water = "Dry" +weight = 30 +affinity = "dry" + # ── Follow-up (§8-aware material pass): Sand / Rock / Gravel / Lava ────────────── [[sand]] diff --git a/server/src/atlas/mosaic.rs b/server/src/atlas/mosaic.rs index da8a90fbc..e2414f1b4 100644 --- a/server/src/atlas/mosaic.rs +++ b/server/src/atlas/mosaic.rs @@ -85,6 +85,40 @@ impl SurfaceClass { } } +/// The district's climate-biome (T-1085, D-246) — derived from `temperature_c` + +/// `moisture_q`. It refines the *vegetated* surface-class palettes so a Grassland reads +/// as savanna / temperate / boreal / tundra rather than one biome-blind palette (the +/// D-246 biome-awareness fix). The alien display names per biome × register live in +/// `docs/design/alien-ecology-vocabulary.md`; this layer carries only the D-228 axes. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ClimateBiome { + Tropical, + Temperate, + Boreal, + Tundra, + Savanna, +} + +impl ClimateBiome { + /// Coarse biome from mean-annual temperature (°C) + district moisture (0–100). + /// Airless districts never reach the mosaic; the `10.0` default is a safe + /// temperate fallback for a missing temperature. + pub fn from_climate(temperature_c: Option, moisture_q: i32) -> Self { + let t = temperature_c.unwrap_or(10.0); + if t < 0.0 { + ClimateBiome::Tundra + } else if t < 7.0 { + ClimateBiome::Boreal + } else if t <= 20.0 { + ClimateBiome::Temperate + } else if moisture_q >= 45 { + ClimateBiome::Tropical + } else { + ClimateBiome::Savanna + } + } +} + // Shorthand for the const tables below. use MoistureAffinity::{Any, Dry, Wet}; use TerrainMaterial as T; @@ -128,11 +162,49 @@ const FOREST: &[MicroHabitat] = &[ h("fern undergrowth", T::Soil, V::Thicket, W::Dry, 15, Any), ]; -const GRASSLAND: &[MicroHabitat] = &[ - h("tussock", T::Soil, V::Grass, W::Dry, 40, Any), - h("scrub island", T::Soil, V::Scrub, W::Dry, 18, Dry), - h("wildflower", T::Soil, V::Meadow, W::Dry, 17, Dry), - h("dry wash", T::Gravel, V::Barren, W::Dry, 10, Wet), +// Grassland is biome-keyed (T-1085): savanna / temperate / boreal / tundra read +// distinctly rather than as one biome-blind palette (D-246). §8-safe — vegetation + +// water only; terrain stays Soil in v1 (the Gravel dry-wash material variant is the +// §8-aware follow-up). Names per docs/design/alien-ecology-vocabulary.md. +const GRASSLAND_TEMPERATE: &[MicroHabitat] = &[ + h("cane-tassel sweep", T::Soil, V::Grass, W::Dry, 40, Any), + h("drum-root knot", T::Soil, V::Scrub, W::Dry, 18, Dry), + h("wrack-bloom", T::Soil, V::Meadow, W::Dry, 17, Dry), + h("husk-line seep", T::Soil, V::Barren, W::Dry, 10, Wet), +]; + +// Savanna: the signature is scattered Forest copses in a grass matrix (the +// vane-crown copse) — the clearest biome distinction from temperate grassland. +const GRASSLAND_SAVANNA: &[MicroHabitat] = &[ + h("cane-tassel sweep", T::Soil, V::Grass, W::Dry, 38, Any), + h("vane-crown copse", T::Soil, V::Forest, W::Dry, 12, Dry), + h("husk-thorn scrub", T::Soil, V::Scrub, W::Dry, 18, Dry), + h("husk-pan waterhole", T::Soil, V::Grass, W::Shallow, 10, Wet), + h("drum-mound flat", T::Soil, V::Barren, W::Dry, 12, Dry), +]; + +// Boreal grassland: cooler and sparser — more scrub, a lichen fraction, no meadow. +const GRASSLAND_BOREAL: &[MicroHabitat] = &[ + h("cane-tassel sweep", T::Soil, V::Grass, W::Dry, 30, Any), + h("wrack-scrub", T::Soil, V::Scrub, W::Dry, 25, Dry), + h("husk-line seep", T::Soil, V::Grass, W::Shallow, 10, Wet), + h("wrack-lichen mat", T::Soil, V::Lichen, W::Dry, 12, Any), + h("cold scald", T::Soil, V::Barren, W::Dry, 15, Dry), +]; + +// Tundra: sparse and cold — scrub, lichen, bare patterned ground, NO forest. +const GRASSLAND_TUNDRA: &[MicroHabitat] = &[ + h("husk-heath brake", T::Soil, V::Scrub, W::Dry, 26, Any), + h("wrack-lichen mat", T::Soil, V::Lichen, W::Dry, 24, Any), + h( + "thermokarst husk-pool", + T::Soil, + V::Grass, + W::Shallow, + 10, + Wet, + ), + h("lichen-scald flat", T::Soil, V::Barren, W::Dry, 30, Dry), ]; const SAND: &[MicroHabitat] = &[ @@ -161,12 +233,19 @@ const LAVA: &[MicroHabitat] = &[ h("pioneer scrub", T::Lava, V::Scrub, W::Dry, 20, Dry), ]; -/// The embedded palette for a surface-class (temperate / native baseline). -pub fn palette(class: SurfaceClass) -> &'static [MicroHabitat] { +/// The embedded native palette for a `(surface-class, climate-biome)` (D-246 Palette +/// key). Grassland is biome-keyed (T-1085); the other classes are biome-invariant in +/// v1 — Forest biomes (tropical/boreal) and the extreme classes are the follow-up. +pub fn palette(class: SurfaceClass, biome: ClimateBiome) -> &'static [MicroHabitat] { match class { SurfaceClass::Wetland => WETLAND, SurfaceClass::Forest => FOREST, - SurfaceClass::Grassland => GRASSLAND, + SurfaceClass::Grassland => match biome { + ClimateBiome::Savanna | ClimateBiome::Tropical => GRASSLAND_SAVANNA, + ClimateBiome::Boreal => GRASSLAND_BOREAL, + ClimateBiome::Tundra => GRASSLAND_TUNDRA, + ClimateBiome::Temperate => GRASSLAND_TEMPERATE, + }, SurfaceClass::Sand => SAND, SurfaceClass::Rock => ROCK, SurfaceClass::Gravel => GRAVEL, @@ -240,7 +319,7 @@ mod tests { #[test] fn every_palette_nonempty_and_positively_weighted() { - for class in [ + let classes = [ SurfaceClass::Wetland, SurfaceClass::Forest, SurfaceClass::Grassland, @@ -248,19 +327,58 @@ mod tests { SurfaceClass::Rock, SurfaceClass::Gravel, SurfaceClass::Lava, - ] { - let p = palette(class); - assert!(!p.is_empty(), "{class:?} palette empty"); - assert!( - p.iter().all(|hab| hab.weight > 0), - "{class:?} has a non-positive weight" - ); + ]; + let biomes = [ + ClimateBiome::Tropical, + ClimateBiome::Temperate, + ClimateBiome::Boreal, + ClimateBiome::Tundra, + ClimateBiome::Savanna, + ]; + for class in classes { + for biome in biomes { + let p = palette(class, biome); + assert!(!p.is_empty(), "{class:?}/{biome:?} palette empty"); + assert!( + p.iter().all(|hab| hab.weight > 0), + "{class:?}/{biome:?} has a non-positive weight" + ); + } } } + #[test] + fn from_climate_bands() { + use ClimateBiome::*; + assert_eq!(ClimateBiome::from_climate(Some(-10.0), 40), Tundra); + assert_eq!(ClimateBiome::from_climate(Some(3.0), 40), Boreal); + assert_eq!(ClimateBiome::from_climate(Some(12.0), 40), Temperate); + assert_eq!(ClimateBiome::from_climate(Some(28.0), 70), Tropical); + assert_eq!(ClimateBiome::from_climate(Some(28.0), 20), Savanna); + } + + #[test] + fn grassland_biomes_are_distinct() { + // Savanna carries scattered Forest copses; tundra carries none but has Lichen. + let savanna = palette(SurfaceClass::Grassland, ClimateBiome::Savanna); + let tundra = palette(SurfaceClass::Grassland, ClimateBiome::Tundra); + assert!( + savanna.iter().any(|h| h.vegetation == V::Forest), + "savanna should have Forest copses" + ); + assert!( + !tundra.iter().any(|h| h.vegetation == V::Forest), + "tundra must not carry Forest" + ); + assert!( + tundra.iter().any(|h| h.vegetation == V::Lichen), + "tundra should have Lichen" + ); + } + #[test] fn select_is_deterministic_and_spans_the_palette() { - let p = palette(SurfaceClass::Wetland); + let p = palette(SurfaceClass::Wetland, ClimateBiome::Temperate); assert_eq!(select(p, 0.37, 0.5).name, select(p, 0.37, 0.5).name); let names: std::collections::BTreeSet<_> = (0..100) .map(|i| select(p, i as f64 / 100.0, 0.5).name) @@ -274,7 +392,7 @@ mod tests { #[test] fn wetness_favours_wet_habitats_over_dry_signal() { - let p = palette(SurfaceClass::Wetland); + let p = palette(SurfaceClass::Wetland, ClimateBiome::Temperate); let wet_count = |wetness: f64| { (0..100) .filter(|i| { diff --git a/server/src/atlas/voxel.rs b/server/src/atlas/voxel.rs index 46311db1d..b561095a5 100644 --- a/server/src/atlas/voxel.rs +++ b/server/src/atlas/voxel.rs @@ -530,10 +530,22 @@ pub fn derive_voxel_column( use crate::atlas::mosaic::{self, SurfaceClass}; let dominant_veg = Vegetation::from_vegetation_class(district_eff.vegetation_class); let class = SurfaceClass::from_base(column.terrain, dominant_veg); + // Climate-biome (T-1085) refines the Grassland palette: savanna / temperate / + // boreal / tundra read distinctly (D-246 biome-awareness). + let biome = + mosaic::ClimateBiome::from_climate(district_eff.temperature_c, district_eff.moisture_q); let apply = match class { SurfaceClass::Wetland | SurfaceClass::Forest => true, - // Barren grassland (cold / arid) stays barren — no imposed grass. - SurfaceClass::Grassland => !matches!(dominant_veg, Vegetation::Barren), + // A vegetated grassland always; a barren-dominant one only when the biome is + // cold (tundra / boreal), where the sparse lichen/scrub mosaic is climate- + // appropriate — a hot barren district (desert on Soil) stays bare. + SurfaceClass::Grassland => { + !matches!(dominant_veg, Vegetation::Barren) + || matches!( + biome, + mosaic::ClimateBiome::Tundra | mosaic::ClimateBiome::Boreal + ) + } // Rock / Sand / Gravel / Lava are material-driven — a §8-aware follow-up. _ => false, }; @@ -550,7 +562,7 @@ pub fn derive_voxel_column( // lowered on the rises (relief_signal negative in valleys). let moisture = (district_eff.moisture_q as f64 / 100.0).clamp(0.0, 1.0); let wetness = (moisture - relief_signal).clamp(0.0, 1.0); - let hab = mosaic::select(mosaic::palette(class), field, wetness); + let hab = mosaic::select(mosaic::palette(class, biome), field, wetness); column.vegetation = hab.vegetation; // Add standing water only where the family left it dry AND the chunk's // hydrology gate allows it (T-1040: water placement must agree with diff --git a/server/tests/golden/believability.json b/server/tests/golden/believability.json index da2796ca3..380899855 100644 --- a/server/tests/golden/believability.json +++ b/server/tests/golden/believability.json @@ -37,7 +37,7 @@ "drainage_samples": 0, "drainage_monotonic": 0, "vegetation_samples": 64, - "vegetated_districts": 8 + "vegetated_districts": 20 } }, { @@ -70,7 +70,7 @@ "vegetation_classes": 1, "terrain_materials": 4, "voxel_relief_m": 19, - "micro_habitat_distinct": 0 + "micro_habitat_distinct": 1 }, "coherence": { "water_districts": 25, @@ -78,7 +78,7 @@ "drainage_samples": 0, "drainage_monotonic": 0, "vegetation_samples": 64, - "vegetated_districts": 0 + "vegetated_districts": 39 } } ]