fix(simulation): address PR #172 review — C1 over-vegetation + H1 biosphere gate (T-1084/T-1085)
Hoshe + Tyre review of the micro-habitat-mosaic branch requested changes on two data-confirmed correctness bugs plus follow-through. All addressed. Blocking: - C1 (voxel.rs): the mosaic re-granted vegetation the climate withheld — a frozen ice world (Edict) read 61% vegetated tundra. The Grassland apply-gate now fires only where the climate already grants cover (!Barren); a climatically-barren district (frozen < -50C = surface ice/geology D-239 §2, or hyper-arid moisture<5, both resolved to VegetationClass::Barren upstream) is skipped. Genuine tundra (cold + moisture>=15) is Scrub upstream and still reaches the tundra palette via the same !Barren path. believability.json regenerated: Edict vegetated_districts 39->0, Arbour 20->8 (its real cold-pole/arid districts revert to barren); intra-class variety preserved (Arbour vegetation_classes 3, micro_habitat_distinct 2). - H1 (bodies.py): biosphere Gate 0 keyed on `inhabited`, conflating settlement with biosphere and force-Airless'ing uninhabited-but-alive worlds (D-247: worlds were alive before humans). Gate 0 is now settlement-independent — keyed on atmosphere/hydrosphere/planet_class. 24 uninhabited-alive worlds now classified (was 0); GJ0g-1 (dense atm + rivers) -> NativeMirror. Split 122 Compatible : 121 Mirror; all 9 canon exemplars hold. Follow-through: - C2 (bodies.py + D-247): Gate 0 now reads planet_class (barren -> Airless) as D-247's "+ planet_class" phrasing names. D-247 amended to record the actual two-gate signal set (atm/hydro/planet_class habitability; economic_base_primary chirality; settlement-independence). - C3 (believability.rs): micro_habitat_distinct criterion relaxed to `== 0 || >= 2` — the min-over-patches estimator makes K=3 unreachable against a dominant-entry palette; K=2 provisional, Q-123 calibrates. - C4 + H2 (D-246): amended to record the v1 scope cut — only the Soil-derived vegetated/wet classes take the mosaic; material-driven families are `_ => false` so Hardpan/Scree are authored but unreachable in v1; no elevation change in v1; the mosaic OWNS the §8 climate->vegetation law for the classes it touches. - H3 (voxel.rs): three dedicated mosaic-pass regression tests — C1 barren-respect, §8 material-untouched + forest-textured, T-1040 no-water-without-channel. - H4 (voxel.rs): relief_signal comment corrected to ~[-1,+1]. - H5 (bodies.py): frontmatter override now strips quotes + validates against the 4-value enum (warn-and-skip on typo). systems.db regenerated + stamped (H1). believability golden regenerated (C1). Full cargo test green; clippy -D warnings + ruff clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -480,3 +480,5 @@ Workstream 4 DONE (partial) + committed (5fb34675e): grassland biome-awareness (
|
||||
Workstream 3 DONE + committed (cc7c5ada0): biosphere_class plumbing via the stamped regen-db path. Nullable bodies.biosphere_class column (schema + migration); economy_import/bodies.py populate_biosphere_class (frontmatter override + D-247 two-gate default, recompute-all); wired as import step 17; frontmatter overrides for Solandar/GJ-524/Hạ Long (the 3 whose default deviated). systems.db regenerated + stamped (make regen-db). VERIFIED: all 9 canon exemplars correct; distribution 111 Compatible : 108 Mirror among inhabited alive (~50/50), Airless for uninhabited, TerraformedSterile for thin-atm. BodyParams Rust hook deferred (no consumer yet — mosaic is chirality-blind; Burnelli economy is the future consumer).
|
||||
|
||||
T-1085 STATUS: WS1 (chirality assignment) DONE, WS2 (wiki continuity) DONE, WS3 (biosphere plumbing) DONE, WS4 (vocab→palette) DONE-partial (grassland biome-awareness + vocab doc; Forest/register/§8-material continuation noted). REMAINING: (a) rename completion — propagate Linnaeus to bodies.proper_name in the DB (still Cadwal; proper_name is atlas-CLI-owned, wipe+reimport would drop non-proposal cols → needs a targeted approach); (b) WS4 continuation; (c) Burnelli food-economy consumer (separate stakeholder follow-on).', NULL, '2026-07-05 09:57:14', '2026-07-05 09:57:14.888', '2026-07-05 09:57:14.888', NULL, '869fe82bcfe88a3270abefd09e0e941d', 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 ('06FGSK19FGZAC3H25VRAV4JRFW', 'status', 'in_progress', 'review', NULL, '2026-07-05 11:04:20', '2026-07-05 11:04:20.117', '2026-07-05 11:04:20.117', NULL, 'a7164036d1b3711d22d912020fc4dde9', 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', 'review', NULL, '2026-07-05 11:04:20', '2026-07-05 11:04:20.119', '2026-07-05 11:04:20.119', NULL, '75373d6899b3793b3b8a33526ad01d48', 2) ON CONFLICT(hash) DO NOTHING;
|
||||
|
||||
@@ -364,3 +364,70 @@ Workstream 4 DONE (partial) + committed (5fb34675e): grassland biome-awareness (
|
||||
Workstream 3 DONE + committed (cc7c5ada0): biosphere_class plumbing via the stamped regen-db path. Nullable bodies.biosphere_class column (schema + migration); economy_import/bodies.py populate_biosphere_class (frontmatter override + D-247 two-gate default, recompute-all); wired as import step 17; frontmatter overrides for Solandar/GJ-524/Hạ Long (the 3 whose default deviated). systems.db regenerated + stamped (make regen-db). VERIFIED: all 9 canon exemplars correct; distribution 111 Compatible : 108 Mirror among inhabited alive (~50/50), Airless for uninhabited, TerraformedSterile for thin-atm. BodyParams Rust hook deferred (no consumer yet — mosaic is chirality-blind; Burnelli economy is the future consumer).
|
||||
|
||||
T-1085 STATUS: WS1 (chirality assignment) DONE, WS2 (wiki continuity) DONE, WS3 (biosphere plumbing) DONE, WS4 (vocab→palette) DONE-partial (grassland biome-awareness + vocab doc; Forest/register/§8-material continuation noted). REMAINING: (a) rename completion — propagate Linnaeus to bodies.proper_name in the DB (still Cadwal; proper_name is atlas-CLI-owned, wipe+reimport would drop non-proposal cols → needs a targeted approach); (b) WS4 continuation; (c) Burnelli food-economy consumer (separate stakeholder follow-on).', 'in_progress', 'medium', 'miri', NULL, 'D-247', '2026-07-02 20:30:57.222', '2026-07-05 09:57:14.888', NULL, '266b2ac2007b0bb0ac6ebb5d1a0522be', 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 ('06FGSK19FGZAC3H25VRAV4JRFW', 'story', '06FGSJNP8YM537DJCC82ZD6544', 'Intra-class micro-habitat mosaic — seed-scattered sub-features within every terrain type', 'A third believability lever, distinct from T-1080 (macro field gradient) and T-1081 (relief): within a SINGLE terrain class the sub-chunk seed should paint a spatially-coherent mosaic of micro-habitats, generalized to ALL terrain types. Motivating case: the probe''s ''Wetland 100%'' should resolve into clearings, copses, creeks, meadows, bogs, marshes — and likewise for every other class. NOT D-210/T-948: D-210 SubBiomeVariant is a MACRO biome tag per attractor (Tundra vs TemperateForest vs Wetland) — ''which biome''. THIS is finer: the intra-class MICRO-mosaic (''texture within a class''), at sub-chunk (<64 m) scale. MECHANIC: district/biome picks the PALETTE; the sub-chunk seed (SeedDomain::Voxel) paints the MOSAIC, reusing the derive-don''t-store tier (detail_scatter.rs, domain_warp, D-227). Three properties: (1) spatially COHERENT — patches (copse/clearing/bog/meadow) are blobs via low-frequency noise; creeks are lines following micro-drainage; never per-tile salt-and-pepper. (2) causally CONDITIONED on micro-relief/micro-drainage/moisture/slope/aspect (bogs in wettest lows, meadows on drier rises, copses where drainage improves, creeks along micro-thalwegs) — legible, not random. (3) MODULATES the existing D-228 axes, not a new axis. PER-CLASS VOCAB (one mechanism, per-class data): Wetland: clearing/copse/creek/meadow/bog/marsh; Forest: glade/dense stand/deadfall/brook/fern undergrowth; Grassland: tussock/scrub island/wildflower/dry wash; Sand: dune crest/slack/hardpan/oasis/scrub clump; Rock: outcrop/scree/ledge/lichen/talus; Gravel: bar/active channel/vegetated island; Lava: fresh sheet/weathered crust/tube collapse/pioneer scrub. Needs a Q/D-record before implementation. Relates to T-948 (D-210 macro), T-1081 (relief, a conditioning input), T-1083 (protocol must measure intra-class mosaic diversity).
|
||||
|
||||
---
|
||||
Refinement (2026-07-02, /whats-next Si pass + user resolution): design gate resolved. First work item: write the Q/D-record (claim via `pql decisions claim D architecture`), pinning the five decisions below; then implement.
|
||||
|
||||
DECISIONS (user-resolved):
|
||||
1. MECHANISM: new `voxel_mosaic()` octave band in detail_scatter.rs (~[64,32,16,8] m) + `SeedDomain::VoxelMosaic = 12` (append-only; update the pinned-discriminant test in server/src/seed.rs) → organic blob patches. Creeks + wet/dry conditioning follow the existing voxel_relief field''s micro-thalwegs (creeks along lowest-relief paths; bogs in lows, meadows on rises). ~4 extra value_noise calls/voxel — same cost class as voxel_relief.
|
||||
2. VOCAB→AXES: expand D-228 enum VARIANTS (values on existing axes — not new axes) to cover the FULL shipped vocabulary NOW, all 7 classes. User rationale: palette composition affects the deterministic realization, so adding variants later would shift generated worlds — front-load the vocabulary. The complete axis-combination table for all 7×5–6 vocab items is the load-bearing content of the D-record, with a D-228 amendment for every item needing a new variant (deadfall / hardpan / lichen / fern-undergrowth have no honest existing mapping).
|
||||
3. PALETTE RESIDENCY: `server/data/mosaic_constants.toml`, runtime-loaded like climate_constants.toml (T-1080 precedent). Note: the TOML is part of the deterministic input surface — weight changes change realizations; expected during Q-123 calibration, freeze after.
|
||||
4. DoD METRIC IN SCOPE: add a within-patch intra-class variety field to ContrastMetrics (server/src/atlas/believability.rs) + a provisional criterion K≥3 distinct micro-features per sampled patch; Q-123 calibrates the final K. Matches the T-1081 precedent (mechanic + metric land together).
|
||||
5. FAMILY GATING (grounded default, accepted): flat families (AlluvialPlain, LavaField, BraidedDelta, DuneStrand, MeanderReach) take the full mosaic; dramatic families (CliffCoast, FjordWall, IncisedGorge) get a per-family-gated non-structural modulation pass (vegetation/material only — must not warp carved geometry), mirroring voxel_relief gating. WaterBody: none. <5 ms chunk budget verification is part of implementation (T-1081 harness precedent).
|
||||
|
||||
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.', 'review', 'high', NULL, 'server', 'D-227', '2026-06-28 05:55:34.396', '2026-07-05 11:04:20.115', NULL, '6c6979d10d1da1a90d8bbd0629e58ce1', 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.
|
||||
|
||||
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.
|
||||
|
||||
Workstream 4 DONE (partial) + committed (5fb34675e): grassland biome-awareness (ClimateBiome from temperature_c+moisture_q → savanna/temperate/boreal/tundra palettes; savanna Forest-copses, tundra lichen/sparse) + Miri''s full biome×register vocab preserved as docs/design/alien-ecology-vocabulary.md. Cold-barren districts now render structured-sparse (D-245 climate-appropriateness): vegetated_districts 8→20, 0→39 on cold bodies. Full suite green, believability golden regenerated. CONTINUATION (noted): Forest biomes (tropical/boreal), Wetland/extreme-class biomes, the introduced-Earth register (needs biosphere_class + the managed layer), and the §8 material follow-up. REMAINING on T-1085: workstream 3 (biosphere_class schema+importer+default-rule+bake) + this WS4 continuation.
|
||||
|
||||
Workstream 3 DONE + committed (cc7c5ada0): biosphere_class plumbing via the stamped regen-db path. Nullable bodies.biosphere_class column (schema + migration); economy_import/bodies.py populate_biosphere_class (frontmatter override + D-247 two-gate default, recompute-all); wired as import step 17; frontmatter overrides for Solandar/GJ-524/Hạ Long (the 3 whose default deviated). systems.db regenerated + stamped (make regen-db). VERIFIED: all 9 canon exemplars correct; distribution 111 Compatible : 108 Mirror among inhabited alive (~50/50), Airless for uninhabited, TerraformedSterile for thin-atm. BodyParams Rust hook deferred (no consumer yet — mosaic is chirality-blind; Burnelli economy is the future consumer).
|
||||
|
||||
T-1085 STATUS: WS1 (chirality assignment) DONE, WS2 (wiki continuity) DONE, WS3 (biosphere plumbing) DONE, WS4 (vocab→palette) DONE-partial (grassland biome-awareness + vocab doc; Forest/register/§8-material continuation noted). REMAINING: (a) rename completion — propagate Linnaeus to bodies.proper_name in the DB (still Cadwal; proper_name is atlas-CLI-owned, wipe+reimport would drop non-proposal cols → needs a targeted approach); (b) WS4 continuation; (c) Burnelli food-economy consumer (separate stakeholder follow-on).', 'review', 'medium', 'miri', NULL, 'D-247', '2026-07-02 20:30:57.222', '2026-07-05 11:04:20.119', NULL, '8adc7b9bdd2f69889aae0237c1670291', 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);
|
||||
|
||||
@@ -1924,6 +1924,10 @@ Technical foundation decisions that constrain implementation: engine, client-ser
|
||||
- **New D-228 axis values** (append-only discriminants, [D-010](#d-010)): `Vegetation::Meadow = 7` (forb-rich flowering herbaceous — the wetland *meadow* and grassland *wildflower*), `Vegetation::Deadfall = 8` (dead woody debris — obstruction + fire fuel, no canopy concealment), `Vegetation::Lichen = 9` (crustose/pioneer biological crust — the rock *lichen* and lava *weathered crust*), `TerrainMaterial::Hardpan = 6` (compacted flat crust — hard fast footing, does not slump like sand), `TerrainMaterial::Scree = 7` (loose angular rock debris — unstable footing; *scree*/*talus* differ only by derived name). `Water` is unchanged (Dry/Shallow/Deep suffice). Adding these lights up exhaustive `match` arms across `voxel.rs`, `from_vegetation_class`, the renderer, and any serializer — an intentional compile-time checklist.
|
||||
- **Rationale:** T-1084's finding was that a class can pass every binding law ([D-239](#d-239) §8) and still render as one uniform material ("Wetland 100%"); [D-245](#d-245) makes *believability* the gate, and intra-class variety is one of its five necessary conditions. Reusing the T-1081 `detail_scatter` machinery (same enveloped-fBm core, one more octave band, one more `SeedDomain`) keeps the mechanism inside the derive-don't-store tier with no new architecture. Front-loading the whole vocabulary — rather than growing it as needs appear — is forced by determinism: the palette is a *weighted selection* the seed indexes, so every added value shifts the realization of already-generated worlds. Expressing micro-habitats as combinations of orthogonal axes (plus derived name/relief) rather than a flat `MicroHabitat` enum is the same anti-combinatorial-explosion logic as D-228 itself.
|
||||
- **Implementation:** [T-1084](../../.pql) (story under epic [T-1079](../../.pql)). Touchpoints: `detail_scatter.rs` (`voxel_mosaic()` + the `[64,32,16,8]` m band); `seed.rs` (`SeedDomain::VoxelMosaic = 12` + the pinned-discriminant guard); `voxel.rs` (new per-family-gated mosaic pass after the relief block ~`:495`; **replaces** `scatter_vegetation()` ~`:1907`; new axis-value `match` arms); `believability.rs` (`micro_habitat_distinct` + criterion); new `server/data/mosaic_constants.toml`. Budget verified in the T-1081 harness.
|
||||
- **Amendment (T-1084, 2026-07-05 — v1 implementation scope, PR #172 H2/C4):** the first implementation lands a **narrower pass than item 5 specifies**, recorded here so the record and code agree (the [D-239](#d-239) T-1080/81/82 postscript pattern):
|
||||
- **Only the Soil-derived vegetated/wet classes take the mosaic** in v1 — Wetland, Forest, Grassland (the `apply` gate in `voxel.rs`). The **material-driven** families (Sand, Rock, Gravel, Lava) are gated `_ => false`, so the material-variant rows of the table above — dune crest/**hardpan**, **scree**/talus, dry-wash Gravel, and every Lava row — are **authored but not yet constructed on any reachable path**, and the two new `TerrainMaterial::{Hardpan, Scree}` values are consequently unreachable in v1. A material change on those families must respect the family's own lithology and carved geometry (shape-aware §8 reconciliation), so they are the deferred follow-up. Item 5's promise that the flat families take the *full* (material + micro-relief) mosaic is **deferred, not delivered** at v1.
|
||||
- **No elevation change in v1.** The item-5 micro-relief nudge (dune crest/slack, bog lows) and the non-structural dramatic-family pass are both deferred — v1 modulates **vegetation + water only**, never `elevation_m`. The D-239 §8 **lithology** law therefore holds trivially: material and shape are left exactly as the family generator set them.
|
||||
- **For the three classes it does touch, the mosaic OWNS the §8 climate→vegetation law** (C4), not merely stays inside it. The **no-skip** half holds — no palette emits `Barren` in a Forest / Wetland / Grassland zone. The **climate-appropriateness** half is enforced by the Grassland apply-gate `!Barren`: a climatically-barren district (frozen `< -50 °C` = surface ice / geology per D-239 §2, or hyper-arid `moisture_q < 5` — both resolved to `VegetationClass::Barren` upstream by `derive_vegetation`) is **skipped**, so the mosaic never re-grants cover the climate withheld (the C1 fix: Edict, a frozen ice world, must not read as vegetated tundra — [D-245](#d-245) §5). The trade: the family generators' *guaranteed* riparian Thicket band is softened to a **probabilistic** wet-habitat clustered near `has_active_channel` chunks (T-1040-gated) — acceptable at v1.
|
||||
- **Cross-reference:** [D-228](#d-228) (the axes this modulates — amended 2026-07-02 to add the five values), [D-227](#d-227) (derive-don't-store — the tier the mosaic lives in), [D-245](#d-245) (believability gate — this is condition 3, intra-class variety), [D-239](#d-239) §8 (binding laws — the mosaic stays inside them) / §10 (sub-chunk budget), [D-243](#d-243) §2 (the detail-scatter tier T-1081 and this extend), [D-247](content.md#d-247) (native-mirror vs introduced-Earth **biosphere register** — palette-key input 3; the mosaic is chirality-blind, but its register labels each niche native or introduced), [D-210](#d-210) / [T-948](../../.pql) (SubBiomeVariant — now **palette-key input 2**: the macro biome realized as the finer intra-class distribution, not a single tag). Tickets: T-1084 (this), T-1079 (epic), T-1081 (relief — a conditioning input + the reused machinery), T-1083 (enforcer — measures the mosaic), [Q-123](../questions/architecture.md#q-123) item 3 (calibrates K).
|
||||
- **Dissent:** None
|
||||
|
||||
|
||||
@@ -634,6 +634,10 @@ How narrative, NPCs, and world content are created: content tiers, NPC generatio
|
||||
- **Colonization is ecological *war*, not terraforming.** Two centuries can't build an atmosphere, but they are ample to **burn back native ecology and hold an Earth-crop perimeter.** So the Earth-chirality footprint is a **spatial field that grows outward from settlements over wave-time**: `P(introduced Earth ecology) ≈ f(distance-to-settlement, settlement_age, economic_tier, arability)`. Wave-1 capitals → broad established farmland; Wave-5 frontier → a thin beachhead in native wilds. Three policy states per locale: **converted** (Earth-managed core), **contested** (naturalized front — escapees vs native reclaiming; abandoned land reverting), **preserved** (native biosphere protected — either *from exploitation* because it is edible/valuable (Keid, GJ-166A, the Cadwal archetype) or *for study* because it is inert (the Solandar xenobotany economy, GJ-827)). Mirror worlds show *sharp two-biosphere fronts*; Compatible worlds show *blended, competing* ecology.
|
||||
- **Code hooks (bounded, verified derivation-safe).** A new body attribute `biosphere_class ∈ {NativeCompatible, NativeMirror, TerraformedSterile, Airless}`, read *once per body*, consumed downstream (food/economy/perception), touching **nothing** in voxel geometry. It is an **authored input, not a derivation** — chirality follows a world's authored economic/cultural identity (Arbour's estate families *grow their own food*; a coin flip could wrongly declare that biology inedible), so it is authored data, never seeded entropy. Home: wiki body frontmatter (where `substrate:` already lives) → a nullable `bodies.biosphere_class` column → the `BodyParams` read set (D-240). Canon-silent bodies take a **principled default inferred from `economic_base_primary` + `planet_class`** (still authored-data logic, not entropy), with explicit per-world overrides where canon speaks (Solandar=Mirror, Keid=Compatible, Oshima=`TerraformedSterile`, Arbour=Compatible). That frontmatter + schema + importer bundle **lands with the Miri continuity pass** (asset-pipeline: schema ships with its source + consumer) — **no column is needed for D-246**, whose native mosaic is chirality-agnostic. Provenance at the tile is a new **orthogonal overlay field** `VoxelColumn.biosphere ∈ {Native, Introduced}` (defaulted `Native`, `#[serde(default)]`), sitting beside `cover` — set `Native` by the wild/natural derivation (all of Phase 4) and `Introduced` by the future managed override, i.e. it is exactly D-228's **wild → economic/managed** precedence read as a label. Every voxel still resolves to a complete D-228 tuple; the bit adds no unset state and no new RNG.
|
||||
- **Scope.** The *biochemistry* is phase-independent canon. The *tile register* is Phase 4 — but only its **native** half exists now (the managed Earth-conversion layer is drawn later by the settlement/economic pass, D-166 outside-in). Food-supply economics is a Phase-2 consumer (already latent — agriculture-as-leverage, frontier net food importers). Foraging futility, predators that gore-but-don't-eat, absent native medicine, and Earth-blight-not-native-plague are **parked gameplay** (T-1036) — flagged, not designed here.
|
||||
- **Amendment (T-1085, 2026-07-05 — implemented default signal set, PR #172 H1/C2):** the shipped `populate_biosphere_class` default (`tooling/economy-db/economy_import/bodies.py`) is a **two-gate** function, and its signal set is the "`economic_base_primary` + `planet_class`" phrasing above **honored and widened** so the record matches the code:
|
||||
- **Gate 0 — habitability, settlement-independent** — reads `atmosphere` + `hydrosphere` + `planet_class` (not `economic_base_primary`, not `inhabited`). No real atmosphere (`""/none/trace`) or a `barren` `planet_class` → `Airless`; a *thin* atmosphere → `TerraformedSterile` if inhabited else `Airless` (the Oshima case — a held atmosphere is settlement infrastructure); a real atmosphere but no liquid hydrosphere → `Airless`. This gate is deliberately **independent of settlement**: an uninhabited world with a real atmosphere + liquid water was alive before humans and stays alive (the H1 fix — the earlier `not inhabited → Airless` pre-gate conflated *settlement* with *biosphere* and mis-classified ~24 uninhabited-but-alive worlds, e.g. `GJ0g-1`, collapsing the Compatible/Mirror population).
|
||||
- **Gate 1 — chirality** — reached only for a habitable world, reads `economic_base_primary`: agriculture / service → `NativeCompatible`; penal / extractive / manufacturing → `NativeMirror`; the neutral remainder → a **stable per-body SHA-1 hash split** (~50/50, deterministic, no entropy). Authored frontmatter overrides still win over the whole function.
|
||||
- So `planet_class` **is** a default input (Gate 0, alongside atmosphere + hydrosphere), and `economic_base_primary` is the Gate-1 input — the two named signals now map to the two gates, with the habitability signals (atmosphere/hydrosphere) that the original phrasing left implicit made explicit.
|
||||
- **Rationale:** "Implemented" is a false summit for a setting the same way it is for the cascade (cf. [D-245](../decisions/architecture.md#d-245)). The Reach's habitability was scattered across a dozen wiki entries as unexplained flavor ("alien biochemistries, incompatible, inedible, non-infectious — *almost always*"); this record supplies the single mechanism that predicts all of it *and* its exceptions, and turns "can I eat here?" into a legible, generatable world property. The 50/50 chirality boolean (vs a fuzzy "usually incompatible") is chosen because it makes both world-classes common enough to matter and gives the storyteller a hard axis. Native-life-not-terraforming is forced by the timeline math the setting already committed to. Touchstones: Mass Effect (levo/dextro food chirality), Vance's *Cadwal Chronicles* (the Conservancy world → Keid), the Bobiverse (convergent-but-alien niches), Hamilton's Commonwealth (non-animal/mega clades, the Primes), Card's descolada/Lusitania (a biosphere as one linked genetic system).
|
||||
- **Affects:** **Wiki continuity pass** (Miri) over the entries that already gesture at this, each assigned a chirality bit consistent with its authored identity — GJ-338B (Arbour — Compatible; estate-food garden), GJ-827 (Solandar — Mirror-inert research preserve), GJ-166A (Keid — Compatible; preserved *because* its edible native biology is an exploitation target — conservation against capital), GJ-524 (Mirror), GJ-684A (native + introduced coexisting), GJ-197 (Earth introductions thrive), GJ-79 (Oshima — `TerraformedSterile`), GJ-124 (Hạ Long — resolve whether its "protein" is native-Compatible harvest or Earth aquaculture). **Schema/code:** authored `bodies.biosphere_class` column + wiki frontmatter (nullable; principled default from `economic_base_primary`+`planet_class`, explicit overrides) — an **input, not a seed derivation** — landing with the continuity pass; `VoxelColumn.biosphere` overlay field. **Economy** (Burnelli): food-supply demand primitive keyed on `biosphere_class`. **[D-246](../decisions/architecture.md#d-246):** its palette register is now grounded as native-mirror-wild vs introduced-Earth-managed.
|
||||
- **Cross-reference:** [D-246](../decisions/architecture.md#d-246) (the mosaic register that consumes this — native vs introduced), [D-228](../decisions/architecture.md#d-228) (the wild→managed override stack this labels), [D-227](../decisions/architecture.md#d-227) (derive-don't-store — `biosphere` is a derived overlay), [D-166](../decisions/scope.md#d-166) (outside-in — the settlement pass draws the Earth-conversion layer), [D-188](../decisions/architecture.md#d-188) (`planet_class` — the sibling body attribute), [D-240](../decisions/architecture.md#d-240) (BodyParams read set `biosphere_class` joins), [D-245](../decisions/architecture.md#d-245) (believability — the conversion gradient supplies coherence + non-stationarity for free). Tickets: T-1084 (D-246 mosaic register), T-1079 (epic); a Miri continuity/vocabulary ticket to be filed.
|
||||
|
||||
Binary file not shown.
@@ -113,10 +113,16 @@ pub struct ContrastMetrics {
|
||||
/// 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).
|
||||
/// in any single sampled VEGETATED-land district (T-1084, D-246); `0` = no vegetated
|
||||
/// patch was sampled (barren / ocean world). 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%"). Barren / water patches are excluded
|
||||
/// (legitimately uniform). NOTE (PR #172, C3): this is a *minimum over patches* against
|
||||
/// palettes with a dominant entry (e.g. dense stand 40/90), so a single 64 m chunk
|
||||
/// often sits in the dominant bucket → the provisional floor is K=2 ("no vegetated
|
||||
/// patch is 100% one micro-habitat"), the achievable, meaningful bar. The estimator
|
||||
/// (min vs mean/median), the final K, and same-class-only pooling are Q-123's to
|
||||
/// calibrate against the enforcer's baselines.
|
||||
pub micro_habitat_distinct: usize,
|
||||
}
|
||||
|
||||
@@ -375,9 +381,13 @@ pub fn evaluate_criteria(r: &BelievabilityReport) -> Vec<Criterion> {
|
||||
},
|
||||
Criterion {
|
||||
name: "intra-class variety",
|
||||
pass: c.micro_habitat_distinct >= 3,
|
||||
// 0 = no vegetated patch sampled (barren/ocean world — legitimately nothing to
|
||||
// vary). Otherwise the provisional floor is K=2 = "no vegetated patch is 100%
|
||||
// one micro-habitat" — the achievable bar for a min-over-patches estimator;
|
||||
// ≥3 + the estimator are Q-123's to calibrate (PR #172, C3).
|
||||
pass: c.micro_habitat_distinct == 0 || c.micro_habitat_distinct >= 2,
|
||||
detail: format!(
|
||||
"min {} distinct micro-habitats per vegetated patch",
|
||||
"min {} distinct micro-habitats per vegetated patch (K≥2 provisional; Q-123 calibrates)",
|
||||
c.micro_habitat_distinct
|
||||
),
|
||||
},
|
||||
|
||||
+111
-17
@@ -509,7 +509,7 @@ 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)
|
||||
relief_signal = relief; // ~[-1, +1] (envelope clamps to 1); negative = valley (wetter)
|
||||
}
|
||||
|
||||
// ── 3c. Intra-class micro-habitat mosaic (T-1084, D-246) ──────────────
|
||||
@@ -520,12 +520,15 @@ pub fn derive_voxel_column(
|
||||
// (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.
|
||||
// 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 (D-246 amendment). The
|
||||
// mosaic now OWNS the §8 *climate→vegetation* law for these classes: the no-skip half
|
||||
// holds (no palette emits Barren in a Forest zone), and a climatically-barren district
|
||||
// stays barren (D-245 §5 — the mosaic never re-grants cover the climate withheld). The
|
||||
// family's guaranteed riparian band is softened to a probabilistic wet-habitat near
|
||||
// channels — acceptable at v1. Within these classes this supersedes `scatter_vegetation`.
|
||||
if !matches!(family, MorphologyFamily::WaterBody) {
|
||||
use crate::atlas::mosaic::{self, SurfaceClass};
|
||||
let dominant_veg = Vegetation::from_vegetation_class(district_eff.vegetation_class);
|
||||
@@ -536,16 +539,14 @@ pub fn derive_voxel_column(
|
||||
mosaic::ClimateBiome::from_climate(district_eff.temperature_c, district_eff.moisture_q);
|
||||
let apply = match class {
|
||||
SurfaceClass::Wetland | SurfaceClass::Forest => true,
|
||||
// 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
|
||||
)
|
||||
}
|
||||
// Only where the climate already grants cover (C1 fix). A Barren-dominant
|
||||
// district is Barren for a reason `derive_vegetation` already resolved —
|
||||
// frozen (< -50 °C = surface ice/geology, D-239 §2), hyper-arid (moisture < 5),
|
||||
// or cold-dry marginal ground — so the mosaic must NOT re-grant it (else it
|
||||
// paints tundra scrub onto frozen ice worlds like Edict). Genuine tundra
|
||||
// (cold + moisture ≥ 15) is already Scrub upstream, so it reaches the biome's
|
||||
// tundra palette through this same !Barren path — no special cold clause needed.
|
||||
SurfaceClass::Grassland => !matches!(dominant_veg, Vegetation::Barren),
|
||||
// Rock / Sand / Gravel / Lava are material-driven — a §8-aware follow-up.
|
||||
_ => false,
|
||||
};
|
||||
@@ -2535,6 +2536,99 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Intra-class micro-habitat mosaic — dedicated integration surface (T-1084,
|
||||
// D-246, PR #172 H3). Locks the three load-bearing invariants of the §3c pass
|
||||
// that no other test targets directly: the C1 climate-authority gate, the §8
|
||||
// lithology hold, and the T-1040 hydrology guard.
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
/// C1 regression: the mosaic must NOT re-grant vegetation the climate withheld.
|
||||
/// A climatically-barren district — a frozen ice world (like Edict) or a
|
||||
/// hyper-arid margin, both resolved to `VegetationClass::Barren` upstream by
|
||||
/// `derive_vegetation` — reaches the Grassland surface-class (AlluvialPlain →
|
||||
/// Soil, not temperature-gated), but the apply-gate is `!Barren`, so the mosaic
|
||||
/// is skipped and every voxel stays Barren. Before the fix,
|
||||
/// `ClimateBiome::from_climate` read the sub-zero temp as Tundra and painted
|
||||
/// tundra scrub onto the ice — Edict read 61 % vegetated (D-245 §5 violation).
|
||||
#[test]
|
||||
fn mosaic_pass_does_not_revegetate_barren_climate() {
|
||||
let frozen_barren = DistrictProfile {
|
||||
vegetation_class: VegetationClass::Barren,
|
||||
temperature_c: Some(-60.0), // frozen: surface ice = geology (D-239 §2)
|
||||
moisture_q: 5, // also hyper-arid; keeps terrain Soil (< 60)
|
||||
..alluvial_district()
|
||||
};
|
||||
let chunk = derive_chunk_context(42, "GJ244Ad", &frozen_barren, (10, 20), None);
|
||||
for tx in [100, 137, 200, 263, 331, 404] {
|
||||
let col = derive_voxel_column(42, "GJ244Ad", &frozen_barren, &chunk, tx, 90);
|
||||
assert_eq!(
|
||||
col.vegetation,
|
||||
Vegetation::Barren,
|
||||
"frozen/hyper-arid barren district must stay Barren (C1), got {:?} at x={tx}",
|
||||
col.vegetation,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Positive path + §8 lithology hold: a vegetated Forest district IS textured
|
||||
/// by the mosaic (≥2 distinct vegetation micro-habitats, never Barren per the
|
||||
/// §8 no-skip law), while the terrain MATERIAL is left exactly as the family
|
||||
/// generator set it (Soil). The mosaic owns the climate→vegetation half of §8
|
||||
/// but never the lithology half (D-246 v1 scope).
|
||||
#[test]
|
||||
fn mosaic_pass_textures_forest_without_touching_material() {
|
||||
let district = alluvial_district(); // Forest, temperate, moisture 55 → Soil
|
||||
let chunk = alluvial_chunk(&district);
|
||||
let mut veg_seen = std::collections::BTreeSet::new();
|
||||
for tx in 100..160 {
|
||||
let col = derive_voxel_column(42, "GJ1c", &district, &chunk, tx, 100);
|
||||
assert_eq!(
|
||||
col.terrain,
|
||||
TerrainMaterial::Soil,
|
||||
"mosaic must not change terrain material (§8 lithology), got {:?} at x={tx}",
|
||||
col.terrain,
|
||||
);
|
||||
assert_ne!(
|
||||
col.vegetation,
|
||||
Vegetation::Barren,
|
||||
"Forest zone never renders Barren (§8 no-skip), at x={tx}",
|
||||
);
|
||||
veg_seen.insert(format!("{:?}", col.vegetation));
|
||||
}
|
||||
assert!(
|
||||
veg_seen.len() >= 2,
|
||||
"mosaic must texture a Forest district into ≥2 micro-habitats, saw {veg_seen:?}",
|
||||
);
|
||||
}
|
||||
|
||||
/// T-1040 hydrology guard: the mosaic adds standing water ONLY where the
|
||||
/// chunk's hydrology gate allows it. In a channel-free chunk
|
||||
/// (`ocean_fraction_q < 10` ⇒ `has_active_channel == false`) the wet-habitat
|
||||
/// water is suppressed — every voxel stays Dry — even though the Forest palette
|
||||
/// contains wet habitats.
|
||||
#[test]
|
||||
fn mosaic_pass_adds_no_water_without_active_channel() {
|
||||
let dry_forest = DistrictProfile {
|
||||
ocean_fraction_q: 0, // < 10 ⇒ has_active_channel == false
|
||||
..alluvial_district()
|
||||
};
|
||||
let chunk = derive_chunk_context(42, "GJ1c", &dry_forest, (10, 20), None);
|
||||
assert!(
|
||||
!chunk.has_active_channel,
|
||||
"test precondition: chunk must have no active channel",
|
||||
);
|
||||
for tx in 100..160 {
|
||||
let col = derive_voxel_column(42, "GJ1c", &dry_forest, &chunk, tx, 100);
|
||||
assert_eq!(
|
||||
col.water,
|
||||
Water::Dry,
|
||||
"no standing water may appear in a channel-free chunk (T-1040), got {:?} at x={tx}",
|
||||
col.water,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn elevation_is_non_negative() {
|
||||
let district = alluvial_district();
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
"drainage_samples": 0,
|
||||
"drainage_monotonic": 0,
|
||||
"vegetation_samples": 64,
|
||||
"vegetated_districts": 20
|
||||
"vegetated_districts": 8
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -70,7 +70,7 @@
|
||||
"vegetation_classes": 1,
|
||||
"terrain_materials": 4,
|
||||
"voxel_relief_m": 19,
|
||||
"micro_habitat_distinct": 1
|
||||
"micro_habitat_distinct": 0
|
||||
},
|
||||
"coherence": {
|
||||
"water_districts": 25,
|
||||
@@ -78,7 +78,7 @@
|
||||
"drainage_samples": 0,
|
||||
"drainage_monotonic": 0,
|
||||
"vegetation_samples": 64,
|
||||
"vegetated_districts": 39
|
||||
"vegetated_districts": 0
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -209,17 +209,18 @@ def populate_biosphere_class(conn: sqlite3.Connection, dry_run: bool) -> int:
|
||||
|
||||
Reads ``environment.biosphere_class`` from each body's index.md frontmatter (the
|
||||
AUTHORED override) and, where absent, applies the D-247 two-gate default from the
|
||||
body's atmosphere / hydrosphere / inhabited flag + the system's
|
||||
body's atmosphere / hydrosphere / planet_class + the system's
|
||||
``economic_base_primary``. Recomputes for ALL bodies each run (the value is fully
|
||||
determined by frontmatter + the default, with no other authoritative writer to
|
||||
preserve), so an edited override re-applies on the next regen.
|
||||
|
||||
Authored input, not seed entropy (D-247): the default is a function of other
|
||||
AUTHORED fields, and explicit frontmatter always wins.
|
||||
- Gate 0 (habitability): uninhabited or no atmosphere -> Airless; inhabited +
|
||||
thin atmosphere -> engineered -> TerraformedSterile; inhabited + real
|
||||
atmosphere + open liquid water -> Alive -> Gate 1; inhabited + real atmosphere
|
||||
but no liquid water (ice/subsurface) -> sealed/engineered -> Airless.
|
||||
- Gate 0 (habitability — independent of SETTLEMENT, since D-247 holds worlds were
|
||||
alive before humans): no atmosphere or an explicitly ``barren`` planet_class ->
|
||||
Airless; ``thin`` atmosphere -> TerraformedSterile if inhabited (engineered),
|
||||
else Airless (marginal natural); real atmosphere + open liquid water -> Alive ->
|
||||
Gate 1; real atmosphere but no liquid water -> Airless (sealed / marginal).
|
||||
- Gate 1 (Compatible vs Mirror): agriculture/service exporters -> Compatible;
|
||||
penal/extraction/manufacturing -> Mirror; the neutral remainder -> a stable
|
||||
body_id split toward the ~50/50 design target (canon worlds carry explicit
|
||||
@@ -276,7 +277,8 @@ def populate_biosphere_class(conn: sqlite3.Connection, dry_run: bool) -> int:
|
||||
bc = stripped.partition(":")[2].strip() or None
|
||||
return (body_id, bc)
|
||||
|
||||
# 1. Authored overrides from frontmatter.
|
||||
# 1. Authored overrides from frontmatter (quote-stripped + enum-validated — H5).
|
||||
_VALID = {"NativeCompatible", "NativeMirror", "TerraformedSterile", "Airless"}
|
||||
override: dict[str, str] = {}
|
||||
body_files = sorted(_glob.glob(str(WIKI_STAR_SYSTEMS / "*" / "bodies" / "*" / "index.md")))
|
||||
for fpath in body_files:
|
||||
@@ -286,6 +288,10 @@ def populate_biosphere_class(conn: sqlite3.Connection, dry_run: bool) -> int:
|
||||
continue
|
||||
body_id, bc = _biosphere_from_frontmatter(text)
|
||||
if body_id and bc:
|
||||
bc = bc.strip().strip("'\"")
|
||||
if bc not in _VALID:
|
||||
print(f" WARNING: {fpath}: biosphere_class {bc!r} not in {_VALID}; ignored")
|
||||
continue
|
||||
override[body_id] = bc
|
||||
|
||||
# 2. Bodies needing a value + the inputs for the two-gate default.
|
||||
@@ -295,7 +301,8 @@ def populate_biosphere_class(conn: sqlite3.Connection, dry_run: bool) -> int:
|
||||
# an edited override must re-apply on the next regen.
|
||||
rows = conn.execute(
|
||||
"""
|
||||
SELECT b.body_id, b.inhabited, b.atmosphere, b.hydrosphere, e.economic_base_primary
|
||||
SELECT b.body_id, b.inhabited, b.atmosphere, b.hydrosphere, b.planet_class,
|
||||
e.economic_base_primary
|
||||
FROM bodies b
|
||||
LEFT JOIN system_economy e ON e.system_id = b.system_id
|
||||
"""
|
||||
@@ -303,16 +310,24 @@ def populate_biosphere_class(conn: sqlite3.Connection, dry_run: bool) -> int:
|
||||
|
||||
_LIQUID = ("ocean", "liquid", "rivers", "moderate", "extensive", "coastal", "lakes")
|
||||
|
||||
def _default(inhabited, atmosphere, hydrosphere, econ, body_id) -> str:
|
||||
def _default(inhabited, atmosphere, hydrosphere, planet_class, econ, body_id) -> str:
|
||||
atm = (atmosphere or "").strip().lower()
|
||||
hyd = (hydrosphere or "").strip().lower()
|
||||
# Gate 0 — habitability.
|
||||
if not inhabited or atm in ("", "none", "trace"):
|
||||
return "Airless"
|
||||
pclass = (planet_class or "").strip().lower()
|
||||
# Gate 0 — habitability. Independent of SETTLEMENT (H1 fix): D-247's premise is
|
||||
# that habitable worlds were alive before humans, so an UNINHABITED world with a
|
||||
# real atmosphere + open liquid water is a living world nobody has settled — not
|
||||
# Airless. Keyed on atmosphere / hydrosphere / planet_class (C2: these are the
|
||||
# real habitability signals; D-247's "+ planet_class" phrasing amended to match).
|
||||
if atm in ("", "none", "trace") or pclass == "barren":
|
||||
return "Airless" # no atmosphere, or an explicitly lifeless world
|
||||
if atm == "thin":
|
||||
return "TerraformedSterile"
|
||||
# A thin *breathable* atmosphere is an engineering achievement only where
|
||||
# INHABITED (maintained infrastructure — D-247 Oshima); an uninhabited
|
||||
# thin-atmosphere world is a marginal natural one with no open-air biosphere.
|
||||
return "TerraformedSterile" if inhabited else "Airless"
|
||||
if not any(k in hyd for k in _LIQUID):
|
||||
return "Airless" # real atmosphere, no open liquid water -> sealed/engineered
|
||||
return "Airless" # real atmosphere but no open liquid water -> sealed / marginal
|
||||
# Gate 1 — Compatible vs Mirror (Alive).
|
||||
base = (econ or "").strip().lower()
|
||||
if "agricultur" in base or "service" in base:
|
||||
@@ -323,8 +338,10 @@ def populate_biosphere_class(conn: sqlite3.Connection, dry_run: bool) -> int:
|
||||
return "NativeCompatible" if h % 2 == 0 else "NativeMirror"
|
||||
|
||||
updates: list[tuple[str, str]] = []
|
||||
for body_id, inhabited, atmosphere, hydrosphere, econ in rows:
|
||||
val = override.get(body_id) or _default(inhabited, atmosphere, hydrosphere, econ, body_id)
|
||||
for body_id, inhabited, atmosphere, hydrosphere, planet_class, econ in rows:
|
||||
val = override.get(body_id) or _default(
|
||||
inhabited, atmosphere, hydrosphere, planet_class, econ, body_id
|
||||
)
|
||||
updates.append((val, body_id))
|
||||
|
||||
if not dry_run and updates:
|
||||
|
||||
Reference in New Issue
Block a user