feat(simulation): intra-class micro-habitat mosaic v1 — vegetation/water texture (T-1084, D-246)

Sub-chunk mosaic that modulates the D-228 axes into a spatially-coherent,
relief/moisture-conditioned mosaic of micro-habitats within a terrain class —
the fix for the aliveness probe's uniform "Wetland 100%".

- seed.rs: SeedDomain::VoxelMosaic = 12 (append-only; pinned-discriminant guard)
- voxel.rs: five new D-228 axis values (Vegetation::{Meadow,Deadfall,Lichen},
  TerrainMaterial::{Hardpan,Scree}); the per-family-gated mosaic pass
- detail_scatter.rs: voxel_mosaic() selection field (~8-64 m band)
- atlas/mosaic.rs: surface-class palette + integer-basis-point weighted selection
  with moisture-affinity conditioning; temperate/native baseline palettes
- data/mosaic_constants.toml: source-canonical palette mirror
- believability.rs: micro_habitat_distinct metric + D-245 intra-class criterion

v1 scope (D-246-faithful): modulates vegetation + water only, in the vegetated
classes (Wetland/Forest/Grassland), so the D-239 §8 lithology law is untouched;
water is gated on the T-1040 has_active_channel hydrology rule. Material variants
(hardpan/scree) and the extreme classes are a §8-aware follow-up; the biome-aware
palette variants (T-1085 vocab) extend the baseline before generation.

Goldens regenerated (believability + derivation). Full server suite green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-03 10:42:20 +02:00
co-authored by Claude Opus 4.8
parent cf80c1e1b5
commit 31b553a3b3
11 changed files with 1022 additions and 9 deletions
+164
View File
@@ -39,3 +39,167 @@ DECISIONS (user-resolved):
CODE TOUCHPOINTS (Si-verified 2026-07-02): detail_scatter.rs:87-96 (voxel_relief pattern to extend), seed.rs:84-118 (SeedDomain; pinned-discriminant test ~:267), voxel.rs:1907-1943 (scatter_vegetation — the per-voxel salt-and-pepper this replaces), voxel.rs:445-495 (post-dispatch modulation seam), believability.rs (ContrastMetrics; evaluate_criteria ~:319). Overlaps to reconcile in the axis table: tube_depression (voxel.rs:710-717, Lava vocab) and braided-delta gravel-bar scrub (voxel.rs:1069-1092, Gravel vocab).
D-record written: D-246 (governance/decisions/architecture.md) pins all five resolved decisions + the full 32-row vocab→D-228 axis table + palette-key rule. D-228 amended to add the five new axis values (Vegetation::{Meadow,Deadfall,Lichen}, TerrainMaterial::{Hardpan,Scree}). D-246 is now the governing record for implementation; decision_ref D-227 kept as the derive-don''t-store parent.', NULL, '2026-07-02 18:26:37', '2026-07-02 18:26:37.203', '2026-07-02 18:26:37.203', NULL, 'c38329ce7eea51cd8aa53b0c5769da76', 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.', '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.', NULL, '2026-07-02 20:33:09', '2026-07-02 20:33:09.514', '2026-07-02 20:33:09.514', NULL, '02a8635cdb5f42ec0a512d12804b3f35', 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.', '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→<name> rename across wiki + the GJ166A atlas proposal.', NULL, '2026-07-02 21:23:34', '2026-07-02 21:23:34.735', '2026-07-02 21:23:34.735', NULL, '80cb1aabee1e9defb9795e677e7ee58d', 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→<name> rename across wiki + the GJ166A atlas proposal.', '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→<name> 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.', 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;
+103
View File
@@ -39,3 +39,106 @@ DECISIONS (user-resolved):
CODE TOUCHPOINTS (Si-verified 2026-07-02): detail_scatter.rs:87-96 (voxel_relief pattern to extend), seed.rs:84-118 (SeedDomain; pinned-discriminant test ~:267), voxel.rs:1907-1943 (scatter_vegetation — the per-voxel salt-and-pepper this replaces), voxel.rs:445-495 (post-dispatch modulation seam), believability.rs (ContrastMetrics; evaluate_criteria ~:319). Overlaps to reconcile in the axis table: tube_depression (voxel.rs:710-717, Lava vocab) and braided-delta gravel-bar scrub (voxel.rs:1069-1092, Gravel vocab).
D-record written: D-246 (governance/decisions/architecture.md) pins all five resolved decisions + the full 32-row vocab→D-228 axis table + palette-key rule. D-228 amended to add the five new axis values (Vegetation::{Meadow,Deadfall,Lichen}, TerrainMaterial::{Hardpan,Scree}). D-246 is now the governing record for implementation; decision_ref D-227 kept as the derive-don''t-store parent.', 'in_progress', 'high', NULL, 'server', 'D-227', '2026-06-28 05:55:34.396', '2026-07-02 18:26:37.202', NULL, '7edbe9dc6461eea90bf94ebcfe0b0687', 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.', 'backlog', 'medium', 'miri', NULL, 'D-247', '2026-07-02 20:30:57.222', '2026-07-02 20:30:57.222', NULL, 'e7f137504714f54f2c0cb1a1481609d3', 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.', 'backlog', 'medium', 'miri', NULL, 'D-247', '2026-07-02 20:30:57.222', '2026-07-02 20:33:09.514', NULL, '648fccf9411f626aaf04a2c5f0a0c9fa', 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→<name> rename across wiki + the GJ166A atlas proposal.', 'backlog', 'medium', 'miri', NULL, 'D-247', '2026-07-02 20:30:57.222', '2026-07-02 21:23:34.730', NULL, 'd575ad0ccc78d862800236759d1af46a', 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→<name> 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.', 'backlog', 'medium', 'miri', NULL, 'D-247', '2026-07-02 20:30:57.222', '2026-07-02 21:24:12.734', NULL, '4465986381752d527f9ac2cd9e58311f', 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);
+254
View File
@@ -0,0 +1,254 @@
# Intra-class micro-habitat mosaic palettes — D-246, T-1084.
#
# SOURCE-CANONICAL MIRROR. The authoritative runtime source today is the embedded
# `atlas::mosaic` const tables; this file mirrors them and is the file to edit when
# tuning weights (the same embedded-default + TOML-mirror pattern as
# `climate_constants.toml`; runtime loading lands with production tuning). Keep the
# two in sync by hand until then.
#
# Each entry is a micro-habitat = a distinct D-228 axis-triple the mosaic can paint,
# plus its relative `weight` and moisture `affinity` (any | wet | dry). Display names
# are derived labels (D-228) — scree/talus and outcrop/ledge collapse to one triple.
#
# This is the TEMPERATE / NATIVE baseline register. Biome variants (savanna, tundra,
# boreal) and the introduced-Earth register extend the table via T-1085, before
# generation (a later addition re-rolls affected voxels — D-246 determinism rule).
#
# v1 (T-1084) applies only the Wetland / Forest / Grassland palettes, and only their
# vegetation + water — terrain material is left to the family generator so the D-239
# §8 lithology law is untouched. The Sand / Rock / Gravel / Lava palettes below are
# authored for the §8-aware material follow-up.
[[wetland]]
name = "clearing"
terrain = "Wetland"
vegetation = "Barren"
water = "Dry"
weight = 15
affinity = "dry"
[[wetland]]
name = "copse"
terrain = "Wetland"
vegetation = "Forest"
water = "Dry"
weight = 10
affinity = "dry"
[[wetland]]
name = "creek"
terrain = "Wetland"
vegetation = "Barren"
water = "Shallow"
weight = 10
affinity = "wet"
[[wetland]]
name = "meadow"
terrain = "Wetland"
vegetation = "Meadow"
water = "Dry"
weight = 15
affinity = "dry"
[[wetland]]
name = "bog"
terrain = "Wetland"
vegetation = "Scrub"
water = "Shallow"
weight = 12
affinity = "wet"
[[wetland]]
name = "marsh"
terrain = "Wetland"
vegetation = "Grass"
water = "Shallow"
weight = 12
affinity = "wet"
[[forest]]
name = "dense stand"
terrain = "Soil"
vegetation = "Forest"
water = "Dry"
weight = 40
affinity = "any"
[[forest]]
name = "glade"
terrain = "Soil"
vegetation = "Grass"
water = "Dry"
weight = 15
affinity = "dry"
[[forest]]
name = "deadfall"
terrain = "Soil"
vegetation = "Deadfall"
water = "Dry"
weight = 10
affinity = "any"
[[forest]]
name = "brook"
terrain = "Soil"
vegetation = "Thicket"
water = "Shallow"
weight = 10
affinity = "wet"
[[forest]]
name = "fern undergrowth"
terrain = "Soil"
vegetation = "Thicket"
water = "Dry"
weight = 15
affinity = "any"
[[grassland]]
name = "tussock"
terrain = "Soil"
vegetation = "Grass"
water = "Dry"
weight = 40
affinity = "any"
[[grassland]]
name = "scrub island"
terrain = "Soil"
vegetation = "Scrub"
water = "Dry"
weight = 18
affinity = "dry"
[[grassland]]
name = "wildflower"
terrain = "Soil"
vegetation = "Meadow"
water = "Dry"
weight = 17
affinity = "dry"
[[grassland]]
name = "dry wash"
terrain = "Gravel"
vegetation = "Barren"
water = "Dry"
weight = 10
affinity = "wet"
# ── Follow-up (§8-aware material pass): Sand / Rock / Gravel / Lava ──────────────
[[sand]]
name = "dune crest"
terrain = "Sand"
vegetation = "Barren"
water = "Dry"
weight = 25
affinity = "dry"
[[sand]]
name = "slack"
terrain = "Sand"
vegetation = "Grass"
water = "Dry"
weight = 15
affinity = "wet"
[[sand]]
name = "hardpan"
terrain = "Hardpan"
vegetation = "Barren"
water = "Dry"
weight = 20
affinity = "dry"
[[sand]]
name = "oasis"
terrain = "Sand"
vegetation = "Forest"
water = "Shallow"
weight = 5
affinity = "wet"
[[sand]]
name = "scrub clump"
terrain = "Sand"
vegetation = "Scrub"
water = "Dry"
weight = 15
affinity = "any"
[[rock]]
name = "outcrop"
terrain = "Rock"
vegetation = "Barren"
water = "Dry"
weight = 45
affinity = "any"
[[rock]]
name = "scree"
terrain = "Scree"
vegetation = "Barren"
water = "Dry"
weight = 30
affinity = "any"
[[rock]]
name = "lichen"
terrain = "Rock"
vegetation = "Lichen"
water = "Dry"
weight = 25
affinity = "any"
[[gravel]]
name = "bar"
terrain = "Gravel"
vegetation = "Barren"
water = "Dry"
weight = 45
affinity = "any"
[[gravel]]
name = "active channel"
terrain = "Gravel"
vegetation = "Barren"
water = "Shallow"
weight = 25
affinity = "wet"
[[gravel]]
name = "vegetated island"
terrain = "Gravel"
vegetation = "Scrub"
water = "Dry"
weight = 30
affinity = "dry"
[[lava]]
name = "fresh sheet"
terrain = "Lava"
vegetation = "Barren"
water = "Dry"
weight = 50
affinity = "any"
[[lava]]
name = "weathered crust"
terrain = "Lava"
vegetation = "Lichen"
water = "Dry"
weight = 30
affinity = "any"
[[lava]]
name = "pioneer scrub"
terrain = "Lava"
vegetation = "Scrub"
water = "Dry"
weight = 20
affinity = "dry"
+37 -2
View File
@@ -112,6 +112,12 @@ pub struct ContrastMetrics {
/// voxel tier carries mid-scale relief. Measured over the 0.252 km band a character
/// traverses — not the single 64 m sample chunk, which is too narrow to register it.
pub voxel_relief_m: i32,
/// Minimum distinct micro-habitat axis-triples (terrain × vegetation × water) found
/// in any single sampled VEGETATED-land district (T-1084, D-246). The D-245 intra-class
/// variety screen: a vegetated patch must show ≥K distinct micro-features, never 100%
/// one — near 1 on the pre-mosaic uniform world ("Wetland 100%"), ≥3 once the mosaic
/// textures the class. Barren / water patches are excluded (legitimately uniform).
pub micro_habitat_distinct: usize,
}
/// Coherence checks — does the world render as a *caused* place? Pass/fail counts
@@ -181,8 +187,9 @@ pub fn analyze(
.values()
.map(|d| format!("{:?}", d.vegetation_class)),
),
terrain_materials: 0, // filled from the voxel sample below
voxel_relief_m: 0, // filled from the relief transect below
terrain_materials: 0, // filled from the voxel sample below
voxel_relief_m: 0, // filled from the relief transect below
micro_habitat_distinct: 0, // filled from the per-district triple sets below
};
// ── Voxel-derived metrics over a deterministic seeded spread of districts ──
@@ -196,6 +203,9 @@ pub fn analyze(
// Mid-scale relief accumulators (T-1081): sum of per-district transect ranges over
// land districts, and the count contributing, → mean within-district relief.
let (mut relief_range_sum, mut relief_district_count) = (0i64, 0i64);
// Intra-class variety (T-1084): the minimum distinct micro-habitat triples over the
// sampled vegetated-land districts — the worst patch, per the D-245 per-patch floor.
let mut min_habitat_distinct = usize::MAX;
let keys: Vec<DistrictPos> = districts.keys().copied().collect();
for idx in sample_indices(world_seed, keys.len(), VOXEL_SAMPLE_DISTRICTS) {
@@ -210,6 +220,7 @@ pub fn analyze(
// Elevation accumulators for the drainage-monotonicity proxy.
let (mut wet_elev_sum, mut wet_n) = (0i64, 0i64);
let (mut dry_elev_sum, mut dry_n) = (0i64, 0i64);
let mut habitat_triples: BTreeSet<String> = BTreeSet::new();
for sx in (0..CHUNK_M as usize).step_by(VOXEL_SAMPLE_STRIDE) {
for sy in (0..CHUNK_M as usize).step_by(VOXEL_SAMPLE_STRIDE) {
@@ -217,6 +228,10 @@ pub fn analyze(
let ty = chunk.1 * CHUNK_M + sy as i32;
let col = derive_voxel_column(world_seed, body_id, prof, &ctx, tx, ty);
terrain_set.insert(format!("{:?}", col.terrain));
habitat_triples.insert(format!(
"{:?}/{:?}/{:?}",
col.terrain, col.vegetation, col.water
));
if col.vegetation != Vegetation::Barren {
any_veg = true;
}
@@ -231,6 +246,12 @@ pub fn analyze(
}
}
// Intra-class micro-habitat variety (T-1084): distinct habitat triples in this
// patch; the report tracks the worst (minimum) vegetated patch.
if any_veg {
min_habitat_distinct = min_habitat_distinct.min(habitat_triples.len());
}
// Water coherence (T-1082): water-present districts must render wet voxels.
if prof.ocean_fraction_q >= WATER_PRESENCE_Q || ctx.has_active_channel {
coh.water_districts += 1;
@@ -294,6 +315,11 @@ pub fn analyze(
} else {
0
};
let micro_habitat_distinct = if min_habitat_distinct == usize::MAX {
0
} else {
min_habitat_distinct
};
BelievabilityReport {
body_id: body_id.to_string(),
@@ -303,6 +329,7 @@ pub fn analyze(
contrast: ContrastMetrics {
terrain_materials: terrain_set.len(),
voxel_relief_m,
micro_habitat_distinct,
..contrast_scalar
},
coherence: coh,
@@ -346,6 +373,14 @@ pub fn evaluate_criteria(r: &BelievabilityReport) -> Vec<Criterion> {
pass: c.terrain_materials >= 2,
detail: format!("{} distinct materials", c.terrain_materials),
},
Criterion {
name: "intra-class variety",
pass: c.micro_habitat_distinct >= 3,
detail: format!(
"min {} distinct micro-habitats per vegetated patch",
c.micro_habitat_distinct
),
},
Criterion {
name: "water renders wet",
pass: pct(h.water_districts_wet, h.water_districts) >= 50,
+78
View File
@@ -32,6 +32,11 @@ const OCTAVE_WAVELENGTHS_M: [f64; 4] = [32_768.0, 16_384.0, 8_192.0, 4_096.0];
/// the district size so the relief never competes with `elev_q`'s district-scale role.
const VOXEL_OCTAVE_WAVELENGTHS_M: [f64; 4] = [1_024.0, 512.0, 256.0, 128.0];
/// Sub-chunk mosaic octave wavelengths in metres — the ≈864 m band, finer than the
/// [`VOXEL_OCTAVE_WAVELENGTHS_M`] sub-district band, so a micro-habitat patch reads as
/// a distinct blob *inside* a single 64 m chunk (T-1084, D-246). Coarsest first.
const MOSAIC_OCTAVE_WAVELENGTHS_M: [f64; 4] = [64.0, 32.0, 16.0, 8.0];
/// Deterministic lattice value in `[-1, 1)` for an integer noise cell.
#[inline]
fn lattice(seed: u64, ix: i64, iy: i64) -> f64 {
@@ -95,6 +100,35 @@ pub fn voxel_relief(seed: u64, wx: f64, wy: f64, envelope: f64, ruggedness: f64)
)
}
/// The sub-chunk micro-habitat **selection field** in `[0, 1]` — a smooth,
/// body-global, position-keyed multi-octave value-noise scalar the mosaic pass
/// thresholds against a per-class palette's cumulative weights (T-1084, D-246).
///
/// Unlike [`voxel_relief`] this is not an elevation perturbation: it carries no
/// envelope and no ridge folding — it is a plain fBm remapped to `[0, 1]` whose only
/// job is to pick *which* micro-habitat a voxel lands in, as spatially-coherent blobs
/// (low-frequency → patches, never per-tile salt-and-pepper). The seed must be
/// **body-global** (constant across the body); the position `(wx, wy)` carries the
/// variation, exactly as for [`voxel_relief`].
pub fn voxel_mosaic(seed: u64, wx: f64, wy: f64) -> f64 {
let mut sum = 0.0;
let mut amp = 1.0;
let mut norm = 0.0;
for (i, &wl) in MOSAIC_OCTAVE_WAVELENGTHS_M.iter().enumerate() {
let n = value_noise(
seed.wrapping_add((i as u64).wrapping_mul(0x1000)),
wx,
wy,
wl,
);
sum += n * amp;
norm += amp;
amp *= 0.5;
}
let fbm = sum / norm; // ≈ [-1, 1]
((fbm + 1.0) * 0.5).clamp(0.0, 1.0)
}
/// Shared adaptive-fBm core for [`terrain_detail`] and [`voxel_relief`] — the only
/// difference between the two tiers is the octave wavelength band. Returns the
/// enveloped, ruggedness-modulated perturbation (roughly `[-envelope, +envelope]`).
@@ -235,4 +269,48 @@ mod tests {
max - min
);
}
// ── voxel_mosaic (T-1084): sub-chunk selection field ─────────────────────
#[test]
fn voxel_mosaic_deterministic_and_in_range() {
let a = voxel_mosaic(42, 12_345.0, -6_789.0);
let b = voxel_mosaic(42, 12_345.0, -6_789.0);
assert_eq!(a, b);
for i in 0..400 {
let v = voxel_mosaic(99, i as f64 * 137.0, i as f64 * -91.0);
assert!((0.0..=1.0).contains(&v), "v={v} out of [0,1] at {i}");
}
}
#[test]
fn voxel_mosaic_varies_at_sub_chunk_scale() {
// The mosaic must produce DISTINCT values within a single 64 m chunk — the
// blob scale that reads as patches, not the district-scale voxel_relief band.
// Steps stay well under 64 m and non-aligned to the octave wavelengths.
let seed = 7;
let vals: Vec<f64> = (0..16)
.map(|i| voxel_mosaic(seed, i as f64 * 3.7, i as f64 * 2.9))
.collect();
let min = vals.iter().cloned().fold(f64::INFINITY, f64::min);
let max = vals.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
assert!(
max - min > 0.1,
"mosaic field is near-flat inside a chunk: range {}",
max - min
);
}
#[test]
fn voxel_mosaic_is_smooth_not_salt_and_pepper() {
// C¹ value noise → a small position step is a small field change, so patches
// are coherent blobs, never the per-tile scatter the mosaic replaces.
let base = voxel_mosaic(11, 5_000.0, 5_000.0);
let near = voxel_mosaic(11, 5_000.5, 5_000.0);
assert!(
(base - near).abs() < 0.05,
"0.5 m step jumped by {}",
(base - near).abs()
);
}
}
+1
View File
@@ -21,6 +21,7 @@ pub mod gen_queue;
pub mod heightmap;
pub mod layer1;
pub mod layer_proxy;
pub mod mosaic;
pub mod plugin;
pub mod region_profile;
pub mod road_graph;
+295
View File
@@ -0,0 +1,295 @@
//! Intra-class micro-habitat mosaic (D-246, T-1084) — the sub-chunk palette that
//! modulates the D-228 axes into a spatially-coherent, relief/moisture-conditioned
//! mosaic of micro-habitats within a single terrain class.
//!
//! Mechanism (D-246): [`detail_scatter::voxel_mosaic`](crate::atlas::detail_scatter::voxel_mosaic)
//! supplies a smooth, body-global selection field in `[0, 1]`; this module maps a
//! voxel's base surface-class to a weighted palette of micro-habitats and picks one
//! by thresholding that field against cumulative weights — conditioned on a local
//! wetness signal (relief micro-thalweg + district moisture) so wet habitats
//! (creek / bog / marsh) land in the lows and dry ones (meadow / glade / clearing) on
//! the rises. Selection is **integer basis-points** (D-010): the f64 field and
//! wetness are the sanctioned positional inputs, truncated before the integer decision.
//!
//! Micro-habitats are distinct D-228 **axis-triples**, not names — several display
//! names (scree/talus, outcrop/ledge) collapse to one triple, the name being a
//! downstream derived label (D-228).
//!
//! Data residency (D-246): the palette is source-canonical in
//! `server/data/mosaic_constants.toml`; the embedded consts here mirror it and are
//! the authoritative runtime source today — the same embedded-default + TOML-mirror
//! pattern as `climate_constants.toml` (runtime loading lands with production tuning).
//! This is the **temperate / native baseline** register; biome variants
//! (savanna / tundra / boreal) and the introduced-Earth register extend the table via
//! T-1085 before generation.
use crate::atlas::voxel::{TerrainMaterial, Vegetation, Water};
/// How the local wetness signal reweights a micro-habitat.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MoistureAffinity {
/// Unconditioned — flat weight across wet and dry.
Any,
/// Favoured in the wet lows (creek / bog / marsh / oasis / active channel).
Wet,
/// Favoured on the dry rises (meadow / glade / clearing / dune crest / hardpan).
Dry,
}
/// One micro-habitat: a distinct D-228 axis-triple the mosaic can paint, plus its
/// relative weight and moisture affinity. `name` is documentation / a derived-label
/// seed only — it is never stored on the voxel.
#[derive(Debug, Clone, Copy)]
pub struct MicroHabitat {
pub name: &'static str,
pub terrain: TerrainMaterial,
pub vegetation: Vegetation,
pub water: Water,
pub weight: i32,
pub affinity: MoistureAffinity,
}
/// The surface-class palette key (D-246) — derived from the base voxel's terrain +
/// dominant vegetation, never stored.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SurfaceClass {
Wetland,
Forest,
Grassland,
Sand,
Rock,
Gravel,
Lava,
}
impl SurfaceClass {
/// Palette key from the family generator's base output (D-246 Palette key):
/// terrain material selects directly, except `Soil` splits Forest vs Grassland
/// by the district's dominant vegetation. `Hardpan`/`Scree` never arrive as a
/// *base* material (they are mosaic outputs) but map defensively.
pub fn from_base(terrain: TerrainMaterial, dominant_vegetation: Vegetation) -> Self {
match terrain {
TerrainMaterial::Wetland => SurfaceClass::Wetland,
TerrainMaterial::Sand | TerrainMaterial::Hardpan => SurfaceClass::Sand,
TerrainMaterial::Rock | TerrainMaterial::Scree => SurfaceClass::Rock,
TerrainMaterial::Gravel => SurfaceClass::Gravel,
TerrainMaterial::Lava => SurfaceClass::Lava,
TerrainMaterial::Soil => {
if matches!(dominant_vegetation, Vegetation::Forest) {
SurfaceClass::Forest
} else {
SurfaceClass::Grassland
}
}
}
}
}
// Shorthand for the const tables below.
use MoistureAffinity::{Any, Dry, Wet};
use TerrainMaterial as T;
use Vegetation as V;
use Water as W;
const fn h(
name: &'static str,
terrain: TerrainMaterial,
vegetation: Vegetation,
water: Water,
weight: i32,
affinity: MoistureAffinity,
) -> MicroHabitat {
MicroHabitat {
name,
terrain,
vegetation,
water,
weight,
affinity,
}
}
// ── Temperate / native baseline palettes (mirror of mosaic_constants.toml) ──────
const WETLAND: &[MicroHabitat] = &[
h("clearing", T::Wetland, V::Barren, W::Dry, 15, Dry),
h("copse", T::Wetland, V::Forest, W::Dry, 10, Dry),
h("creek", T::Wetland, V::Barren, W::Shallow, 10, Wet),
h("meadow", T::Wetland, V::Meadow, W::Dry, 15, Dry),
h("bog", T::Wetland, V::Scrub, W::Shallow, 12, Wet),
h("marsh", T::Wetland, V::Grass, W::Shallow, 12, Wet),
];
const FOREST: &[MicroHabitat] = &[
h("dense stand", T::Soil, V::Forest, W::Dry, 40, Any),
h("glade", T::Soil, V::Grass, W::Dry, 15, Dry),
h("deadfall", T::Soil, V::Deadfall, W::Dry, 10, Any),
h("brook", T::Soil, V::Thicket, W::Shallow, 10, Wet),
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),
];
const SAND: &[MicroHabitat] = &[
h("dune crest", T::Sand, V::Barren, W::Dry, 25, Dry),
h("slack", T::Sand, V::Grass, W::Dry, 15, Wet),
h("hardpan", T::Hardpan, V::Barren, W::Dry, 20, Dry),
h("oasis", T::Sand, V::Forest, W::Shallow, 5, Wet),
h("scrub clump", T::Sand, V::Scrub, W::Dry, 15, Any),
];
const ROCK: &[MicroHabitat] = &[
h("outcrop", T::Rock, V::Barren, W::Dry, 45, Any),
h("scree", T::Scree, V::Barren, W::Dry, 30, Any),
h("lichen", T::Rock, V::Lichen, W::Dry, 25, Any),
];
const GRAVEL: &[MicroHabitat] = &[
h("bar", T::Gravel, V::Barren, W::Dry, 45, Any),
h("active channel", T::Gravel, V::Barren, W::Shallow, 25, Wet),
h("vegetated island", T::Gravel, V::Scrub, W::Dry, 30, Dry),
];
const LAVA: &[MicroHabitat] = &[
h("fresh sheet", T::Lava, V::Barren, W::Dry, 50, Any),
h("weathered crust", T::Lava, V::Lichen, W::Dry, 30, Any),
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] {
match class {
SurfaceClass::Wetland => WETLAND,
SurfaceClass::Forest => FOREST,
SurfaceClass::Grassland => GRASSLAND,
SurfaceClass::Sand => SAND,
SurfaceClass::Rock => ROCK,
SurfaceClass::Gravel => GRAVEL,
SurfaceClass::Lava => LAVA,
}
}
/// Moisture-conditioned effective weight of one entry, in the same integer units as
/// `weight`. `wet_bp` is the local wetness in basis-points (0..10000). Wet/Dry
/// affinities scale within `[0.2×, 1.0×]` (a 0.2 floor so a habitat can still appear
/// off-affinity); `Any` is flat.
fn effective_weight(hab: &MicroHabitat, wet_bp: i32) -> i64 {
let factor_bp: i32 = match hab.affinity {
MoistureAffinity::Any => 10_000,
MoistureAffinity::Wet => 2_000 + 8 * wet_bp / 10, // 2000..10000
MoistureAffinity::Dry => 2_000 + 8 * (10_000 - wet_bp) / 10,
};
(hab.weight as i64 * factor_bp as i64) / 10_000
}
/// Pick a micro-habitat from `palette` using the mosaic selection field and a local
/// wetness signal. Integer basis-points (D-010): `field` and `wetness` are the
/// sanctioned f64 positional inputs, truncated to bp before the integer decision.
///
/// - `field`: the [`voxel_mosaic`](crate::atlas::detail_scatter::voxel_mosaic) scalar in `[0, 1]`.
/// - `wetness`: local wet/dry signal in `[0, 1]` (relief micro-thalweg + moisture).
pub fn select(palette: &[MicroHabitat], field: f64, wetness: f64) -> &MicroHabitat {
debug_assert!(!palette.is_empty(), "mosaic palette must be non-empty");
let wet_bp = (wetness.clamp(0.0, 1.0) * 10_000.0) as i32; // truncate (D-010)
let total: i64 = palette
.iter()
.map(|hab| effective_weight(hab, wet_bp))
.sum::<i64>()
.max(1);
let field_bp = (field.clamp(0.0, 1.0) * 10_000.0) as i64; // truncate (D-010)
let mut threshold = field_bp * total / 10_000;
for hab in palette {
let w = effective_weight(hab, wet_bp);
if threshold < w {
return hab;
}
threshold -= w;
}
// field == 1.0 edge → last bucket.
palette.last().expect("palette non-empty")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_base_maps_soil_by_vegetation() {
assert_eq!(
SurfaceClass::from_base(T::Soil, V::Forest),
SurfaceClass::Forest
);
assert_eq!(
SurfaceClass::from_base(T::Soil, V::Grass),
SurfaceClass::Grassland
);
assert_eq!(
SurfaceClass::from_base(T::Wetland, V::Barren),
SurfaceClass::Wetland
);
assert_eq!(
SurfaceClass::from_base(T::Lava, V::Barren),
SurfaceClass::Lava
);
}
#[test]
fn every_palette_nonempty_and_positively_weighted() {
for class in [
SurfaceClass::Wetland,
SurfaceClass::Forest,
SurfaceClass::Grassland,
SurfaceClass::Sand,
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"
);
}
}
#[test]
fn select_is_deterministic_and_spans_the_palette() {
let p = palette(SurfaceClass::Wetland);
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)
.collect();
assert!(
names.len() >= 3,
"selection collapsed to {} habitats",
names.len()
);
}
#[test]
fn wetness_favours_wet_habitats_over_dry_signal() {
let p = palette(SurfaceClass::Wetland);
let wet_count = |wetness: f64| {
(0..100)
.filter(|i| {
matches!(
select(p, *i as f64 / 100.0, wetness).affinity,
MoistureAffinity::Wet
)
})
.count()
};
assert!(
wet_count(0.95) > wet_count(0.05),
"wet conditioning did not favour wet habitats: {} vs {}",
wet_count(0.95),
wet_count(0.05)
);
}
}
+79 -4
View File
@@ -87,6 +87,12 @@ pub enum TerrainMaterial {
Wetland = 4,
/// Lava — sheets/shield slopes + tubes, immature drainage (D-239 §8).
Lava = 5,
/// Hardpan — compacted flat crust (D-246 mosaic). Hard, fast footing; does not
/// slump to an angle of repose like sand.
Hardpan = 6,
/// Scree — loose angular rock debris (D-246 mosaic). Unstable footing; the
/// derived name is "talus" at a cliff foot, "scree" on an open slope.
Scree = 7,
}
/// Built surface over natural ground (D-228).
@@ -128,6 +134,15 @@ pub enum Vegetation {
Crop = 5,
/// Cleared — formerly vegetated, stripped (player/sim action).
Cleared = 6,
/// Meadow — forb-rich flowering herbaceous (D-246 mosaic). Higher cover than
/// Grass; the wetland *meadow* and grassland *wildflower*.
Meadow = 7,
/// Deadfall — dead woody debris (D-246 mosaic). Obstruction + fire fuel, no
/// canopy concealment.
Deadfall = 8,
/// Lichen — crustose/pioneer biological crust on rock/lava (D-246 mosaic).
/// Distinct from Barren; the rock *lichen* and lava *weathered crust*.
Lichen = 9,
}
impl Vegetation {
@@ -464,6 +479,8 @@ pub fn derive_voxel_column(
// WaterBody stays at sea level. The flat families are the ones that read flat and
// need invented relief; their only feature is a 315 m channel, narrow enough that
// the smooth relief shifts it bodily with its banks.
// Captured for the mosaic wetness signal below (stays 0 where no relief is added).
let mut relief_signal = 0.0f64;
let takes_mid_scale_relief = matches!(
family,
MorphologyFamily::AlluvialPlain
@@ -492,6 +509,58 @@ pub fn derive_voxel_column(
);
let relief_m = (relief * VOXEL_RELIEF_SPAN_M as f64) as i32; // truncate (D-010)
column.elevation_m = (column.elevation_m + relief_m).max(0);
relief_signal = relief; // ~[-0.5, +0.5]; negative = mid-scale valley (wetter)
}
// ── 3c. Intra-class micro-habitat mosaic (T-1084, D-246) ──────────────
// Within the vegetated / wet land classes (Wetland, Forest, Grassland), modulate
// VEGETATION + WATER into a spatially-coherent mosaic of micro-habitats: a
// body-global selection field (SeedDomain::VoxelMosaic, ≈864 m band) picks a
// habitat from the surface-class palette, conditioned on a local wetness signal
// (mid-scale relief thalweg + district moisture) so wet habitats settle in the
// lows. This is the motivating fix for the probe's uniform "Wetland 100%".
//
// v1 scope: terrain MATERIAL is left exactly as the family generator set it, so
// the D-239 §8 lithology law holds untouched. The material-variant micro-habitats
// (hardpan / scree / dry-wash gravel) and the extreme classes (Rock / Sand /
// Gravel / Lava) need shape-aware §8 reconciliation and are a follow-up. A
// climatically-barren district stays barren (D-245 §5 climate-appropriateness).
// Within these classes this supersedes the per-voxel `scatter_vegetation` scatter.
if !matches!(family, MorphologyFamily::WaterBody) {
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);
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),
// Rock / Sand / Gravel / Lava are material-driven — a §8-aware follow-up.
_ => false,
};
if apply {
let mosaic_seed = SeedChain::for_body(world_seed, body_id)
.derive(SeedDomain::VoxelMosaic, 0)
.seed();
let field = crate::atlas::detail_scatter::voxel_mosaic(
mosaic_seed,
voxel_x as f64,
voxel_y as f64,
);
// Wetness in [0,1]: district moisture, raised in the mid-scale lows and
// 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);
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
// has_active_channel — water never appears in a channel-free chunk). Wet
// habitats (bog / marsh / creek / brook) thus cluster near channels; away
// from them they render in their dry-ground form.
if column.water == Water::Dry && chunk.has_active_channel {
column.water = hab.water;
}
}
}
// ── 4. Seasonal cover overlay (D-239 §3, T-1030) ──────────────────────
@@ -2433,16 +2502,22 @@ mod tests {
fn alluvial_plain_vegetation_from_district_class() {
let district = alluvial_district(); // vegetation_class = Forest
let chunk = alluvial_chunk(&district);
// Multiple samples to account for sub-chunk scatter; all should be
// in the Forest/Scrub/Grass range (D-239 §8: no-skip law — no Barren in Forest zone).
// Multiple samples to account for the intra-class mosaic (T-1084, D-246),
// which textures a Forest district into dense stand / glade / deadfall / brook /
// fern. The binding invariant is the D-239 §8 no-skip law: NEVER Barren in a
// Forest zone — the Forest palette upholds it (no Barren entry).
for tx in [100, 101, 102, 103, 104] {
let col = derive_voxel_column(42, "GJ1c", &district, &chunk, tx, 100);
assert!(
matches!(
col.vegetation,
Vegetation::Forest | Vegetation::Scrub | Vegetation::Grass
Vegetation::Forest
| Vegetation::Scrub
| Vegetation::Grass
| Vegetation::Thicket
| Vegetation::Deadfall
),
"Forest district must produce Forest/Scrub/Grass vegetation, got {:?}",
"Forest district must stay non-Barren (§8 no-skip), got {:?}",
col.vegetation
);
}
+6
View File
@@ -115,6 +115,11 @@ pub enum SeedDomain {
/// keyed by a single constant id (one field per body). Distinct domain so the
/// relief lattice can never correlate with the per-voxel terrain or cover streams.
VoxelRelief = 11,
/// Sub-chunk micro-habitat mosaic selection field (T-1084, D-246). A body-global,
/// position-keyed value-noise field at ≈864 m wavelengths (one field per body,
/// keyed by a single constant id). Distinct domain so the mosaic-selection lattice
/// can never correlate with the relief, cover, or per-voxel terrain streams.
VoxelMosaic = 12,
}
/// A position in the deterministic seed tree (D-224).
@@ -275,6 +280,7 @@ mod tests {
assert_eq!(SeedDomain::Voxel as u64, 9);
assert_eq!(SeedDomain::Cover as u64, 10);
assert_eq!(SeedDomain::VoxelRelief as u64, 11);
assert_eq!(SeedDomain::VoxelMosaic as u64, 12);
}
#[test]
+4 -2
View File
@@ -28,7 +28,8 @@
"morphology_zones": 9,
"vegetation_classes": 3,
"terrain_materials": 4,
"voxel_relief_m": 20
"voxel_relief_m": 20,
"micro_habitat_distinct": 2
},
"coherence": {
"water_districts": 49,
@@ -68,7 +69,8 @@
"morphology_zones": 9,
"vegetation_classes": 1,
"terrain_materials": 4,
"voxel_relief_m": 19
"voxel_relief_m": 19,
"micro_habitat_distinct": 0
},
"coherence": {
"water_districts": 25,
+1 -1
View File
@@ -6,7 +6,7 @@
"tile_x": 1375,
"tile_y": 800,
"terrain": 0,
"vegetation": 4,
"vegetation": 8,
"water": 0,
"elevation_m": 14,
"cover": 0