fix(simulation): derive_moisture_q canonical hydrosphere vocab (T-1034)

derive_moisture_q matched hydrosphere on 'ocean'/'rivers'/'subsurface', but the
real bodies.hydrosphere vocab in systems.db is dominated by 'liquid_water' (175
bodies) — so most surface-water worlds fell to the '_ => 30' default, propagating
wrong precipitation_class / glaciation_grade / vegetation. Same root cause as
D-240 (code keyed on a vocab the data doesn't use).

Align the match to the canonical vocab, grouped by available surface moisture
(mirrors the [hydrosphere_maritime] table from T-1033): surface liquid
(liquid_water/ocean/ocean-coastal/extensive) -> 80; rivers/rivers-lakes/moderate
-> 55; ice -> 20; subsurface_liquid -> 15; subsurface/subsurface_ice -> 10;
minimal/trace -> 5; none -> 0. Add a liquid_water regression test and broaden the
clamp sweep to the full vocabulary.

Closes T-1034 — last open ticket under epic T-974 (Atlas-to-tile derivation),
which is now complete.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-08 23:01:43 +02:00
co-authored by Claude Opus 4.8
parent 839ac18460
commit 4984d59bf4
3 changed files with 108 additions and 6 deletions
@@ -258,3 +258,6 @@ WORKFLOW REMINDERS for the resumer: lead drives all git; agents run lean (don''t
fmt/clippy/test — the pre-push gate enforces them + cargo test now); review caught real
bugs on EVERY PR this session (hollow tests, a cache-coherence bug hidden by a tautological
test, the +356C climate bug) — keep reviewing + verifying agent output, don''t trust "all green".', NULL, '2026-06-08 17:22:10', '2026-06-08 17:22:10', '2026-06-08 17:22:10', NULL, 'e497455bce9ece94481dcc060d7db1c0', 1) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1031', 'status', 'in_progress', 'done', NULL, '2026-06-08 20:38:59', '2026-06-08 20:38:59', '2026-06-08 20:38:59', NULL, 'db4d162c8bddb6ead74d6d6458c1a41c', 1) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1034', 'status', 'backlog', 'done', NULL, '2026-06-08 21:01:34', '2026-06-08 21:01:34', '2026-06-08 21:01:34', NULL, '03c07199221bba3a0305d5e0e2d81ec5', 1) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-974', 'status', 'backlog', 'done', NULL, '2026-06-08 21:01:34', '2026-06-08 21:01:34', '2026-06-08 21:01:34', NULL, 'eb367d867a3258091fc50d8ad6743748', 1) ON CONFLICT(hash) DO NOTHING;
+51
View File
@@ -679,3 +679,54 @@ WORKFLOW REMINDERS for the resumer: lead drives all git; agents run lean (don''t
fmt/clippy/test — the pre-push gate enforces them + cargo test now); review caught real
bugs on EVERY PR this session (hollow tests, a cache-coherence bug hidden by a tautological
test, the +356C climate bug) — keep reviewing + verifying agent output, don''t trust "all green".', 'in_progress', 'high', NULL, 'server', 'D-239', '2026-06-07 10:34:11', '2026-06-08 17:22:10', NULL, '32ac92fca534508f3387c87bc54d9fae', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1031', 'task', 'T-974', 'Believability + determinism + <5ms budget verification harness', 'D-239 sections 1,8,9. Golden-seed regression for the binding laws (drainage monotonicity, lithology->landform, glaciation->form with no glacial-U on GlaciationGrade 0, climate->vegetation no-skip + riparian band), per-family <5ms/chunk budget assertion, and the validation-case bodies: Kallast=alluvial plain/Soil, Velen=tidal-flat/dune coast (VERIFY it has a moon param for the tidal term first), Cygni B=volcanic immature drainage, Ross 128=ocean island terrain, Gruenfeld=marginal Gravel/Scrub. If a body reads wrong, fix its params — never patch the derivation (D-239 section 1). Blocked by the family generators.
---
Note from T-1028 review (Tyre, 2026-06-08): VoxelCache LRU eviction is an O(capacity) linear scan for the minimum access_gen (default cap 8192). Latent — at the D-239 §10 ~2.2-4.2 ms/chunk budget (4096 voxels) an 8k scan is measurable noise, not a blocker. If it shows up in this ticket''s profiling, the fix is a secondary BTreeMap<(access_gen, CacheKey), ()> eviction index (stays D-010-compliant). Scope before the budget test runs.
---
RESUME NOTE (2026-06-08, context reset). Where T-974 stands and how to finish it.
STATUS: Epic T-974 (Atlas-to-tile derivation) is ~done. Merged this session:
T-1023/1024/1025/1026/1027/1028/1029/1030/1032 + T-1033 (class-envelope temperature,
D-240). Only T-1031 (this ticket) + T-1034 remain to close T-974.
D-240 (just landed): registry orbit/star data is non-canonical placeholder; temperature
derives from planet_class envelope + atmosphere + hydrosphere(maritime moderation) +
seed — NO orbit/star inputs. This FIXED the +356C "temperate" worlds. BodyParams was
stripped of orbital_period_days / spectral_class / star_type / axial_tilt_deg.
T-1031 IS PARKED in a worktree at .worktrees/verification-harness (branch
verification-harness), created BEFORE T-1033. The harness (server/tests/derivation_harness.rs)
is built: golden-seed determinism + §8 law sweeps + per-family budget + validation bodies
(Kallast pass; Velen/Gruenfeld skipped-not-in-wiki). It also made derive_morphology_zone pub.
TO FINISH T-1031 (3 steps):
1. Rebase/merge current main into the verification-harness worktree. It will then FAIL TO
COMPILE: the harness test fixtures construct BodyParams with the now-removed fields
(orbital_period_days/spectral_class/star_type/axial_tilt_deg) — strip those from the
fixtures. Also re-check any validation-body temperature assertions against the NEW
class-envelope temps (the harness previously noted Marevna -56C as a param issue — that
may now read differently).
2. Generate the golden against the CORRECTED temps: from the worktree,
`cd server && UPDATE_GOLDEN=1 cargo test --test derivation_harness -- golden_seed_determinism_regression`
then commit tests/golden/derivation_harness.json + the harness + the region_profile.rs
pub-visibility change together.
3. Lead verify (cargo test/clippy -D warnings/fmt — the push gate runs all), then /pr-review
(Hoshe+Tyre) -> merge -> mark T-1031 done -> T-974 CLOSES.
ALSO PENDING (separate, fast-follow):
- T-1034 (high, under T-974): derive_moisture_q hydrosphere vocab mismatch — expects ''ocean''
but DB uses ''liquid_water'' (175 bodies) -> wrong moisture/precip/glaciation/vegetation.
The T-1033 [hydrosphere_maritime] table in climate_constants.toml has the real vocab as a
reference. NOTE: this is now important (water worlds get right temp, wrong moisture).
- Flaky test (no ticket yet, offer to file): voice::lookup::cache_miss_returns_base_text
intermittently fails the full-suite push gate under parallelism (passes in isolation;
unrelated to climate work). CI-hygiene.
WORKFLOW REMINDERS for the resumer: lead drives all git; agents run lean (don''t pre-run
fmt/clippy/test — the pre-push gate enforces them + cargo test now); review caught real
bugs on EVERY PR this session (hollow tests, a cache-coherence bug hidden by a tautological
test, the +356C climate bug) — keep reviewing + verifying agent output, don''t trust "all green".', 'done', 'high', NULL, 'server', 'D-239', '2026-06-07 10:34:11', '2026-06-08 20:38:59', NULL, '1120adccd354f2864b061fa8dba28b1b', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1034', 'bug', 'T-974', 'derive_moisture_q hydrosphere vocab mismatch — liquid_water etc. fall to default', 'derive_moisture_q (region_profile.rs) matches hydrosphere on ''ocean''/''rivers''/''ice''/''subsurface''/''none'', but the actual bodies.hydrosphere vocab in systems.db is: liquid_water (175 bodies), ice (962), rivers (31), moderate (21), ocean (only 19), minimal (10), trace (8), subsurface_liquid (7), rivers-lakes (4), extensive (4), subsurface_ice (3), ocean-coastal (1), plus NULL (1649). So ''liquid_water'' and most surface-water worlds fall to the ''_ => 30'' default instead of high moisture — propagating wrong precipitation_class / glaciation_grade / vegetation_class for ~175+ water worlds. Same root cause as D-240 (code keying on a vocab the data doesn''t use). Fix: align derive_moisture_q (and audit any other hydrosphere consumer) to the real vocab — surface liquid (liquid_water/ocean/ocean-coastal/extensive) -> high; rivers/rivers-lakes/moderate -> mid; ice/subsurface* -> low; minimal/trace/none/NULL -> arid. T-1033''s maritime_factor table already maps the real vocab and can be the reference. Found during T-1033 verification (2026-06-08).', 'done', 'high', NULL, 'server', 'D-240', '2026-06-08 17:00:07', '2026-06-08 21:01:34', NULL, '99d8cef76a78b012c9838f1dd033fa62', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-974', 'epic', 'T-750', 'Atlas-to-tile derivation model', 'Foundational world-derivation model from the atlas-derivation workshop. D-227 (deterministic-rebuild world model: pure function of seed+atlas, derived on demand + cached, only tile mutators persisted, fully volumetric voxel/subvoxel, vertical extent physical not floor-count, variable-height floors) + D-228 (composite tile schema: orthogonal axes TerrainMaterial/FloorMaterial/Vegetation/Water/elevation, derived shape/tactical-form, region-level morphology zones, cohesion matrix, dynamic clock-bound region seasonal state). Most implementation lands as the cascade reaches the tile layers (Phase 4+). Open design tracked in Q-100 (biome authority), Q-101 (refinement contract + body-class river density + ocean mask), Q-102 (cohesion-matrix algorithm), Q-103 (mutator op schema), Q-104 (floor-to-voxel-z mapping), Q-105 (region seasonal/clock state: water/snow/weather/crops).', 'done', 'high', NULL, 'server', 'D-228', '2026-05-25 10:51:49', '2026-06-08 21:01:34', NULL, '1c48b836fb7b63e287ab13872c6f0aca', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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);