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:
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -847,16 +847,30 @@ pub fn derive_temperature_c(
|
||||
///
|
||||
/// D-239 §2: moisture is derived from hydrosphere + atmosphere; 0 = arid, 100 = saturated.
|
||||
/// Integer output (D-010).
|
||||
///
|
||||
/// The `hydro` arms use the **actual `bodies.hydrosphere` vocabulary in systems.db**
|
||||
/// — same canonical set the `[hydrosphere_maritime]` table (D-240) keys on — grouped
|
||||
/// by available surface moisture. Keying on a vocab the data doesn't use (e.g. only
|
||||
/// `"ocean"`, which is 19 bodies, while 175 use `"liquid_water"`) silently dropped most
|
||||
/// water worlds to the default (T-1034 — same root cause as D-240).
|
||||
pub fn derive_moisture_q(params: &BodyParams) -> i32 {
|
||||
let hydro = params.hydrosphere.as_deref().unwrap_or("none");
|
||||
let atmo = params.atmosphere.as_deref().unwrap_or("none");
|
||||
|
||||
let base: i32 = match hydro {
|
||||
"none" => 0,
|
||||
"subsurface" => 10,
|
||||
// Large surface liquid — saturated.
|
||||
"liquid_water" | "ocean" | "ocean-coastal" | "extensive" => 80,
|
||||
// Partial surface water (rivers / lakes / seasonal) — moderate.
|
||||
"rivers" | "rivers-lakes" | "moderate" => 55,
|
||||
// Frozen surface water — low available moisture.
|
||||
"ice" => 20,
|
||||
"rivers" => 55,
|
||||
"ocean" => 80,
|
||||
// Buried water — minimal surface effect.
|
||||
"subsurface_liquid" => 15,
|
||||
"subsurface" | "subsurface_ice" => 10,
|
||||
// Effectively dry.
|
||||
"minimal" | "trace" => 5,
|
||||
"none" => 0,
|
||||
// Unknown vocab — conservative mid-low default.
|
||||
_ => 30,
|
||||
};
|
||||
let atmo_boost: i32 = match atmo {
|
||||
@@ -1465,6 +1479,23 @@ mod tests {
|
||||
assert!(q >= 70, "ocean + breathable moisture {q} should be >= 70");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn moisture_q_liquid_water_is_high_not_default() {
|
||||
// T-1034 regression: "liquid_water" is the dominant surface-water vocab in
|
||||
// systems.db (175 bodies). It must map to the high surface-liquid band, not
|
||||
// silently fall to the `_ => 30` default like it did before the fix.
|
||||
let params = BodyParams {
|
||||
hydrosphere: Some("liquid_water".into()),
|
||||
atmosphere: Some("standard".into()),
|
||||
..Default::default()
|
||||
};
|
||||
let q = derive_moisture_q(¶ms);
|
||||
assert!(
|
||||
q >= 70,
|
||||
"liquid_water moisture {q} should be >= 70 (surface-liquid band), not the default 30"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn moisture_q_airless_is_zero() {
|
||||
let params = BodyParams {
|
||||
@@ -1478,8 +1509,25 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn moisture_q_clamped_to_range() {
|
||||
// All combinations must stay in [0, 100].
|
||||
let hydros = ["none", "subsurface", "ice", "rivers", "ocean", "unknown"];
|
||||
// All combinations must stay in [0, 100]. Sweep the full canonical
|
||||
// `bodies.hydrosphere` vocabulary (T-1034) plus an unknown fallback.
|
||||
let hydros = [
|
||||
"liquid_water",
|
||||
"ocean",
|
||||
"ocean-coastal",
|
||||
"extensive",
|
||||
"rivers",
|
||||
"rivers-lakes",
|
||||
"moderate",
|
||||
"ice",
|
||||
"subsurface_liquid",
|
||||
"subsurface",
|
||||
"subsurface_ice",
|
||||
"minimal",
|
||||
"trace",
|
||||
"none",
|
||||
"unknown",
|
||||
];
|
||||
let atmos = [
|
||||
"none",
|
||||
"thin",
|
||||
|
||||
Reference in New Issue
Block a user