test(simulation): address PR #165 review (T-1031)
Hoshe review findings — close silent-pass gaps in the §8 drainage tests: - assert_drainage_monotonicity now returns whether it actually checked (chunk had both wet and dry tiles); the three sweep tests (Alluvial/Meander/Braided Delta) assert at least one chunk exercised the law, so a regression that zeroes all channels fails loudly instead of passing vacuously. - The fjord-floor test scans the full 64x64 chunk instead of a single Y=32 row and asserts the deep-water trough exists, so the sea-level floor law can no longer be skipped by a probe that missed the trough. - Remove the unreferenced make_test_terrain_analysis helper (was behind #[allow(dead_code)] with a 'future use' comment — the maintenance trap the review flagged); make_dry_terrain_analysis covers the validation bodies. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -246,16 +246,22 @@ fn golden_seed_determinism_regression() {
|
||||
// level; BraidedDelta/CliffCoast/FjordWall floors at sea level."
|
||||
// Tested by deriving a full 64×64 chunk and checking min(wet_elev) ≤ max(dry_elev).
|
||||
|
||||
/// Returns `true` if the monotonicity assertion was actually exercised (the chunk
|
||||
/// produced both wet and dry tiles). A `false` return means the chunk had no wet
|
||||
/// tiles, so the law was trivially satisfied without checking anything — callers
|
||||
/// sweep several chunks and assert at least one returned `true`, so a derivation
|
||||
/// regression that silently zeroes all channels fails loudly instead of passing.
|
||||
#[must_use]
|
||||
fn assert_drainage_monotonicity(
|
||||
seed: u64,
|
||||
body_id: &str,
|
||||
label: &str,
|
||||
region: &RegionProfile,
|
||||
chunk_pos: (i32, i32),
|
||||
) {
|
||||
) -> bool {
|
||||
let chunk = derive_chunk_context(seed, body_id, region, chunk_pos);
|
||||
if !chunk.has_active_channel {
|
||||
return; // No channel → monotonicity trivially satisfied.
|
||||
return false; // No channel → monotonicity trivially satisfied.
|
||||
}
|
||||
|
||||
let mut max_dry_elev = i32::MIN;
|
||||
@@ -295,7 +301,9 @@ fn assert_drainage_monotonicity(
|
||||
chunk_pos.0,
|
||||
chunk_pos.1
|
||||
);
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -311,9 +319,15 @@ fn law_drainage_monotonicity_alluvial_sweep() {
|
||||
Some(15.0),
|
||||
VegetationClass::Forest,
|
||||
);
|
||||
let mut checked = false;
|
||||
for (cx, cy) in [(0, 0), (1, 0), (0, 1), (4, 4), (8, 3)] {
|
||||
assert_drainage_monotonicity(42, "GJ144d", "AlluvialPlain", ®ion, (cx, cy));
|
||||
checked |= assert_drainage_monotonicity(42, "GJ144d", "AlluvialPlain", ®ion, (cx, cy));
|
||||
}
|
||||
assert!(
|
||||
checked,
|
||||
"AlluvialPlain sweep exercised no wet tiles — the monotonicity law was never \
|
||||
actually checked; a derivation regression could silently pass this test."
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -329,9 +343,15 @@ fn law_drainage_monotonicity_meander_sweep() {
|
||||
Some(14.0),
|
||||
VegetationClass::Forest,
|
||||
);
|
||||
let mut checked = false;
|
||||
for (cx, cy) in [(0, 0), (2, 1), (5, 5)] {
|
||||
assert_drainage_monotonicity(99, "GJ447c", "MeanderReach", ®ion, (cx, cy));
|
||||
checked |= assert_drainage_monotonicity(99, "GJ447c", "MeanderReach", ®ion, (cx, cy));
|
||||
}
|
||||
assert!(
|
||||
checked,
|
||||
"MeanderReach sweep exercised no wet tiles — the monotonicity law was never \
|
||||
actually checked; a derivation regression could silently pass this test."
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -353,30 +373,45 @@ fn law_drainage_monotonicity_fjord_floor_at_sea_level() {
|
||||
let mut deep_elevs: Vec<i32> = vec![];
|
||||
let mut dry_elevs: Vec<i32> = vec![];
|
||||
|
||||
for dx in 0..64i32 {
|
||||
let col = derive_voxel_column(42, "fjord_body", ®ion, &chunk, dx, 32);
|
||||
match col.water {
|
||||
Water::Deep => deep_elevs.push(col.elevation_m),
|
||||
Water::Dry => dry_elevs.push(col.elevation_m),
|
||||
_ => {}
|
||||
// Scan the full 64×64 chunk, not a single row — the deep-water trough axis is
|
||||
// not guaranteed to intersect any fixed Y, so a single-row probe could miss it
|
||||
// entirely and silently pass without ever checking the sea-level claim.
|
||||
for dy in 0..64i32 {
|
||||
for dx in 0..64i32 {
|
||||
let col = derive_voxel_column(42, "fjord_body", ®ion, &chunk, dx, dy);
|
||||
match col.water {
|
||||
Water::Deep => deep_elevs.push(col.elevation_m),
|
||||
Water::Dry => dry_elevs.push(col.elevation_m),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !deep_elevs.is_empty() && !dry_elevs.is_empty() {
|
||||
let max_deep = *deep_elevs.iter().max().unwrap();
|
||||
let min_dry = *dry_elevs.iter().min().unwrap();
|
||||
// Fjord floor (deep water) must be below wall elevation.
|
||||
assert!(
|
||||
max_deep <= min_dry,
|
||||
"§8 FjordWall drainage: deep-water max elev {max_deep} m must be \
|
||||
≤ dry wall min elev {min_dry} m"
|
||||
);
|
||||
// Fjord floor must be near sea level (D-239 §8).
|
||||
assert!(
|
||||
max_deep <= 5,
|
||||
"§8 FjordWall: deep-water floor elev {max_deep} m must be near sea level (≤5 m)"
|
||||
);
|
||||
}
|
||||
// A glaciated fjord chunk MUST carve a deep-water trough — if it doesn't, the
|
||||
// derivation regressed and the sea-level law below would never run. Fail loudly.
|
||||
assert!(
|
||||
!deep_elevs.is_empty(),
|
||||
"§8 FjordWall: no deep-water tiles found in the fjord chunk — the trough \
|
||||
derivation regressed; the sea-level floor law was never exercised."
|
||||
);
|
||||
assert!(
|
||||
!dry_elevs.is_empty(),
|
||||
"§8 FjordWall: no dry wall tiles found in the fjord chunk."
|
||||
);
|
||||
|
||||
let max_deep = *deep_elevs.iter().max().unwrap();
|
||||
let min_dry = *dry_elevs.iter().min().unwrap();
|
||||
// Fjord floor (deep water) must be below wall elevation.
|
||||
assert!(
|
||||
max_deep <= min_dry,
|
||||
"§8 FjordWall drainage: deep-water max elev {max_deep} m must be \
|
||||
≤ dry wall min elev {min_dry} m"
|
||||
);
|
||||
// Fjord floor must be near sea level (D-239 §8).
|
||||
assert!(
|
||||
max_deep <= 5,
|
||||
"§8 FjordWall: deep-water floor elev {max_deep} m must be near sea level (≤5 m)"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -392,9 +427,16 @@ fn law_drainage_monotonicity_braided_delta() {
|
||||
Some(18.0),
|
||||
VegetationClass::Scrub,
|
||||
);
|
||||
let mut checked = false;
|
||||
for (cx, cy) in [(0, 0), (1, 1)] {
|
||||
assert_drainage_monotonicity(17, "delta_body", "BraidedDelta", ®ion, (cx, cy));
|
||||
checked |=
|
||||
assert_drainage_monotonicity(17, "delta_body", "BraidedDelta", ®ion, (cx, cy));
|
||||
}
|
||||
assert!(
|
||||
checked,
|
||||
"BraidedDelta sweep exercised no wet tiles — the monotonicity law was never \
|
||||
actually checked; a derivation regression could silently pass this test."
|
||||
);
|
||||
}
|
||||
|
||||
// ── §8 Law 2: Lithology → Landform ──────────────────────────────────────────
|
||||
@@ -1433,34 +1475,6 @@ fn make_region(
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a minimal flat `TerrainAnalysis` for use in body-level derive calls.
|
||||
/// Same geometry as the region_profile.rs unit tests (64×32 grid, sea_level=0.3).
|
||||
///
|
||||
/// Retained as the gentle-slope counterpart to `make_dry_terrain_analysis` (which
|
||||
/// the validation bodies currently use); kept for future morphology-gate tests that
|
||||
/// need a sloped, partially-oceanic grid rather than the flat all-dry one.
|
||||
#[allow(dead_code)]
|
||||
fn make_test_terrain_analysis() -> TerrainAnalysis {
|
||||
let (w, h) = (64u32, 32u32);
|
||||
let n = (w * h) as usize;
|
||||
let data: Vec<f32> = (0..n)
|
||||
.map(|i| {
|
||||
let r = (i / w as usize) as f32 / h as f32;
|
||||
let c = (i % w as usize) as f32 / w as f32;
|
||||
(r * 0.6 + c * 0.4).min(1.0)
|
||||
})
|
||||
.collect();
|
||||
let hm = BodyHeightmap {
|
||||
body_id: "test".into(),
|
||||
width: w,
|
||||
height: h,
|
||||
data,
|
||||
sea_level: 0.3,
|
||||
};
|
||||
let dr = drainage::analyze(&hm.data, hm.width, hm.height, hm.sea_level);
|
||||
TerrainAnalysis::analyze(&hm, &dr)
|
||||
}
|
||||
|
||||
/// Build a flat all-dry `TerrainAnalysis` for validation body derivation.
|
||||
///
|
||||
/// All cells have elevation = 0.5 with sea_level = 0.3 → ocean_fraction_q = 0 at
|
||||
|
||||
Reference in New Issue
Block a user