test(simulation): align T-1031 harness with D-240 class-envelope temps

Adapt the believability/budget verification harness to the T-1033 BodyParams
strip: validation-body fixtures (Kallast/Glødberg/Marevna) no longer set the
removed orbit/star fields (orbital_period_days, axial_tilt_deg, spectral_class,
star_type). Temperature is now derived from the planet_class envelope (D-240),
so the Marevna validation note/assertion is rewritten — oceanic band [-12,28]°C
+ maritime 0.6 + standard-atmosphere greenhouse yields ~+26°C at the equator
(was a stale ~-56°C orbit-equilibrium note). Add the seed→tile golden fixture
(tests/golden/derivation_harness.json) that the determinism regression locks.

Also fmt the harness and fix one clippy lint (iter().copied().collect() ->
to_vec()) — the file predated the pre-push gate's cargo-test/clippy coverage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-08 19:35:58 +02:00
co-authored by Claude Opus 4.8
parent b8f8a5fb23
commit de5f0f1e58
2 changed files with 302 additions and 83 deletions
+264 -83
View File
@@ -101,7 +101,15 @@ fn derive_golden(
}
/// The three fixed golden inputs. Varied families and climate states.
fn golden_cases() -> Vec<(&'static str, u64, &'static str, RegionProfile, (i32, i32), i32, i32)> {
fn golden_cases() -> Vec<(
&'static str,
u64,
&'static str,
RegionProfile,
(i32, i32),
i32,
i32,
)> {
vec![
// Case A: AlluvialPlain — temperate forest, active channel.
(
@@ -112,7 +120,10 @@ fn golden_cases() -> Vec<(&'static str, u64, &'static str, RegionProfile, (i32,
MorphologyZone::AlluvialPlain,
TectonicClass::Active,
GlaciationGrade::None,
6, 22, 18, 68,
6,
22,
18,
68,
Some(12.0),
VegetationClass::Forest,
),
@@ -129,7 +140,10 @@ fn golden_cases() -> Vec<(&'static str, u64, &'static str, RegionProfile, (i32,
MorphologyZone::Volcanic,
TectonicClass::Volcanic,
GlaciationGrade::None,
12, 38, 0, 22,
12,
38,
0,
22,
Some(45.0),
VegetationClass::Barren,
),
@@ -146,7 +160,10 @@ fn golden_cases() -> Vec<(&'static str, u64, &'static str, RegionProfile, (i32,
MorphologyZone::Fjord,
TectonicClass::Active,
GlaciationGrade::Moderate,
55, 60, 28, 55,
55,
60,
28,
55,
Some(-8.0),
VegetationClass::Barren,
),
@@ -167,11 +184,15 @@ fn golden_seed_determinism_regression() {
// compare against the golden. This is the core D-010 determinism contract.
let run1: Vec<GoldenEntry> = golden_cases()
.into_iter()
.map(|(lbl, seed, body, region, cp, tx, ty)| derive_golden(lbl, seed, body, &region, cp, tx, ty))
.map(|(lbl, seed, body, region, cp, tx, ty)| {
derive_golden(lbl, seed, body, &region, cp, tx, ty)
})
.collect();
let run2: Vec<GoldenEntry> = golden_cases()
.into_iter()
.map(|(lbl, seed, body, region, cp, tx, ty)| derive_golden(lbl, seed, body, &region, cp, tx, ty))
.map(|(lbl, seed, body, region, cp, tx, ty)| {
derive_golden(lbl, seed, body, &region, cp, tx, ty)
})
.collect();
assert_eq!(
@@ -246,8 +267,7 @@ fn assert_drainage_monotonicity(
let base_y = chunk_pos.1 * 64;
for dy in 0..64i32 {
for dx in 0..64i32 {
let col =
derive_voxel_column(seed, body_id, region, &chunk, base_x + dx, base_y + dy);
let col = derive_voxel_column(seed, body_id, region, &chunk, base_x + dx, base_y + dy);
match col.water {
Water::Dry => {
max_dry_elev = max_dry_elev.max(col.elevation_m);
@@ -284,7 +304,10 @@ fn law_drainage_monotonicity_alluvial_sweep() {
MorphologyZone::AlluvialPlain,
TectonicClass::Stable,
GlaciationGrade::None,
5, 20, 18, 60,
5,
20,
18,
60,
Some(15.0),
VegetationClass::Forest,
);
@@ -299,7 +322,10 @@ fn law_drainage_monotonicity_meander_sweep() {
MorphologyZone::MeanderReach,
TectonicClass::Stable,
GlaciationGrade::None,
8, 18, 20, 65,
8,
18,
20,
65,
Some(14.0),
VegetationClass::Forest,
);
@@ -316,7 +342,10 @@ fn law_drainage_monotonicity_fjord_floor_at_sea_level() {
MorphologyZone::Fjord,
TectonicClass::Active,
GlaciationGrade::Moderate,
55, 60, 28, 55,
55,
60,
28,
55,
Some(-8.0),
VegetationClass::Barren,
);
@@ -356,7 +385,10 @@ fn law_drainage_monotonicity_braided_delta() {
MorphologyZone::Delta,
TectonicClass::Active,
GlaciationGrade::None,
3, 8, 25, 50,
3,
8,
25,
50,
Some(18.0),
VegetationClass::Scrub,
);
@@ -385,10 +417,10 @@ fn assert_all_terrain_is(
let base_y = chunk_pos.1 * 64;
for dy in (0..64i32).step_by(8) {
for dx in (0..64i32).step_by(8) {
let col =
derive_voxel_column(seed, body_id, region, &chunk, base_x + dx, base_y + dy);
let col = derive_voxel_column(seed, body_id, region, &chunk, base_x + dx, base_y + dy);
assert_eq!(
col.terrain, expected,
col.terrain,
expected,
"§8 lithology VIOLATED for '{label}': voxel ({},{}) returned {:?}, expected {:?}",
base_x + dx,
base_y + dy,
@@ -406,11 +438,21 @@ fn law_lithology_lava_emits_lava() {
MorphologyZone::Volcanic,
TectonicClass::Volcanic,
GlaciationGrade::None,
15, 35, 0, 20,
15,
35,
0,
20,
Some(40.0),
VegetationClass::Barren,
);
assert_all_terrain_is(42, "GJ581c", &region, (3, 3), TerrainMaterial::Lava, "LavaField");
assert_all_terrain_is(
42,
"GJ581c",
&region,
(3, 3),
TerrainMaterial::Lava,
"LavaField",
);
}
#[test]
@@ -419,11 +461,21 @@ fn law_lithology_fjord_emits_rock() {
MorphologyZone::Fjord,
TectonicClass::Active,
GlaciationGrade::Moderate,
55, 60, 28, 55,
55,
60,
28,
55,
Some(-8.0),
VegetationClass::Barren,
);
assert_all_terrain_is(42, "fjord_body", &region, (0, 0), TerrainMaterial::Rock, "FjordWall");
assert_all_terrain_is(
42,
"fjord_body",
&region,
(0, 0),
TerrainMaterial::Rock,
"FjordWall",
);
}
#[test]
@@ -432,11 +484,21 @@ fn law_lithology_cliff_coast_emits_rock() {
MorphologyZone::CliffCoast,
TectonicClass::Active,
GlaciationGrade::None,
60, 40, 20, 30,
60,
40,
20,
30,
Some(10.0),
VegetationClass::Scrub,
);
assert_all_terrain_is(42, "cliff_body", &region, (0, 0), TerrainMaterial::Rock, "CliffCoast");
assert_all_terrain_is(
42,
"cliff_body",
&region,
(0, 0),
TerrainMaterial::Rock,
"CliffCoast",
);
}
#[test]
@@ -446,12 +508,20 @@ fn law_lithology_incised_gorge_emits_rock() {
MorphologyZone::MountainPass,
TectonicClass::Active,
GlaciationGrade::None,
50, 65, 5, 40,
50,
65,
5,
40,
Some(5.0),
VegetationClass::Scrub,
);
assert_all_terrain_is(
42, "gorge_body", &region, (0, 0), TerrainMaterial::Rock, "IncisedGorge",
42,
"gorge_body",
&region,
(0, 0),
TerrainMaterial::Rock,
"IncisedGorge",
);
}
@@ -462,11 +532,21 @@ fn law_lithology_dune_strand_emits_sand() {
MorphologyZone::DuneStrand,
TectonicClass::Stable,
GlaciationGrade::None,
12, 15, 20, 25,
12,
15,
20,
25,
Some(22.0),
VegetationClass::Barren,
);
assert_all_terrain_is(42, "dune_body", &region, (0, 0), TerrainMaterial::Sand, "DuneStrand");
assert_all_terrain_is(
42,
"dune_body",
&region,
(0, 0),
TerrainMaterial::Sand,
"DuneStrand",
);
}
#[test]
@@ -476,12 +556,20 @@ fn law_lithology_braided_delta_emits_gravel() {
MorphologyZone::Delta,
TectonicClass::Active,
GlaciationGrade::None,
3, 8, 25, 50,
3,
8,
25,
50,
Some(18.0),
VegetationClass::Scrub,
);
assert_all_terrain_is(
42, "delta_body", &region, (0, 0), TerrainMaterial::Gravel, "BraidedDelta",
42,
"delta_body",
&region,
(0, 0),
TerrainMaterial::Gravel,
"BraidedDelta",
);
}
@@ -492,12 +580,20 @@ fn law_lithology_alluvial_plain_emits_soil() {
MorphologyZone::AlluvialPlain,
TectonicClass::Stable,
GlaciationGrade::None,
10, 25, 12, 45,
10,
25,
12,
45,
Some(16.0),
VegetationClass::Forest,
);
assert_all_terrain_is(
42, "alluvial_body", &region, (0, 0), TerrainMaterial::Soil, "AlluvialPlain",
42,
"alluvial_body",
&region,
(0, 0),
TerrainMaterial::Soil,
"AlluvialPlain",
);
}
@@ -508,12 +604,20 @@ fn law_lithology_meander_reach_emits_soil() {
MorphologyZone::MeanderReach,
TectonicClass::Stable,
GlaciationGrade::None,
8, 18, 20, 55,
8,
18,
20,
55,
Some(14.0),
VegetationClass::Forest,
);
assert_all_terrain_is(
42, "meander_body", &region, (0, 0), TerrainMaterial::Soil, "MeanderReach",
42,
"meander_body",
&region,
(0, 0),
TerrainMaterial::Soil,
"MeanderReach",
);
}
@@ -578,7 +682,7 @@ fn law_glaciation_grade_2_enables_fjord_with_correct_params() {
GlaciationGrade::Moderate, // grade 2 — gate opens
55, // steep enough
60,
25, // coastal
25, // coastal
55,
);
assert_eq!(
@@ -674,9 +778,7 @@ fn law_climate_vegetation_full_grid_no_skip() {
Some(-55.0),
None,
]
.iter()
.copied()
.collect();
.to_vec();
for moisture_q in (0i32..=100).step_by(10) {
for elev_q in (0i32..=100).step_by(10) {
@@ -784,35 +886,115 @@ fn budget_families() -> Vec<(&'static str, RegionProfile)> {
vec![
(
"AlluvialPlain",
make_region(MorphologyZone::AlluvialPlain, TectonicClass::Stable, GlaciationGrade::None, 5, 20, 18, 60, Some(15.0), VegetationClass::Forest),
make_region(
MorphologyZone::AlluvialPlain,
TectonicClass::Stable,
GlaciationGrade::None,
5,
20,
18,
60,
Some(15.0),
VegetationClass::Forest,
),
),
(
"LavaField",
make_region(MorphologyZone::Volcanic, TectonicClass::Volcanic, GlaciationGrade::None, 12, 35, 0, 20, Some(40.0), VegetationClass::Barren),
make_region(
MorphologyZone::Volcanic,
TectonicClass::Volcanic,
GlaciationGrade::None,
12,
35,
0,
20,
Some(40.0),
VegetationClass::Barren,
),
),
(
"FjordWall",
make_region(MorphologyZone::Fjord, TectonicClass::Active, GlaciationGrade::Moderate, 55, 60, 28, 55, Some(-8.0), VegetationClass::Barren),
make_region(
MorphologyZone::Fjord,
TectonicClass::Active,
GlaciationGrade::Moderate,
55,
60,
28,
55,
Some(-8.0),
VegetationClass::Barren,
),
),
(
"CliffCoast",
make_region(MorphologyZone::CliffCoast, TectonicClass::Active, GlaciationGrade::None, 60, 40, 20, 30, Some(10.0), VegetationClass::Scrub),
make_region(
MorphologyZone::CliffCoast,
TectonicClass::Active,
GlaciationGrade::None,
60,
40,
20,
30,
Some(10.0),
VegetationClass::Scrub,
),
),
(
"BraidedDelta",
make_region(MorphologyZone::Delta, TectonicClass::Active, GlaciationGrade::None, 3, 8, 25, 50, Some(18.0), VegetationClass::Scrub),
make_region(
MorphologyZone::Delta,
TectonicClass::Active,
GlaciationGrade::None,
3,
8,
25,
50,
Some(18.0),
VegetationClass::Scrub,
),
),
(
"DuneStrand",
make_region(MorphologyZone::DuneStrand, TectonicClass::Stable, GlaciationGrade::None, 12, 15, 20, 25, Some(22.0), VegetationClass::Barren),
make_region(
MorphologyZone::DuneStrand,
TectonicClass::Stable,
GlaciationGrade::None,
12,
15,
20,
25,
Some(22.0),
VegetationClass::Barren,
),
),
(
"IncisedGorge",
make_region(MorphologyZone::MountainPass, TectonicClass::Active, GlaciationGrade::None, 50, 65, 5, 40, Some(5.0), VegetationClass::Scrub),
make_region(
MorphologyZone::MountainPass,
TectonicClass::Active,
GlaciationGrade::None,
50,
65,
5,
40,
Some(5.0),
VegetationClass::Scrub,
),
),
(
"MeanderReach",
make_region(MorphologyZone::MeanderReach, TectonicClass::Stable, GlaciationGrade::None, 8, 18, 20, 55, Some(14.0), VegetationClass::Forest),
make_region(
MorphologyZone::MeanderReach,
TectonicClass::Stable,
GlaciationGrade::None,
8,
18,
20,
55,
Some(14.0),
VegetationClass::Forest,
),
),
]
}
@@ -826,7 +1008,10 @@ fn budget_per_family_chunk_derivation() {
// Warm up with the fallback family first (also establishes baseline).
let (_, _) = derive_chunk_timed(seed, body_id, &budget_families()[0].1, (0, 0));
let (_, baseline_us) = derive_chunk_timed(seed, body_id, &budget_families()[0].1, (0, 0));
eprintln!("[budget] AlluvialPlain baseline: {} µs / 4096 voxels", baseline_us);
eprintln!(
"[budget] AlluvialPlain baseline: {} µs / 4096 voxels",
baseline_us
);
let mut results: Vec<(&str, u128)> = vec![];
@@ -888,7 +1073,10 @@ fn budget_voxel_cache_lru_overhead() {
MorphologyZone::AlluvialPlain,
TectonicClass::Stable,
GlaciationGrade::None,
5, 20, 18, 60,
5,
20,
18,
60,
Some(15.0),
VegetationClass::Forest,
);
@@ -905,8 +1093,7 @@ fn budget_voxel_cache_lru_overhead() {
let t0 = Instant::now();
for dy in 0..64i32 {
for dx in 0..64i32 {
let _ =
cache.get_or_derive(seed, body_id, &region, &chunk, base_x + dx, base_y + dy);
let _ = cache.get_or_derive(seed, body_id, &region, &chunk, base_x + dx, base_y + dy);
}
}
let cache_us = t0.elapsed().as_micros();
@@ -947,17 +1134,12 @@ fn budget_voxel_cache_lru_overhead() {
/// Kallast (GJ144d) params — wiki: star-systems/GJ-144/bodies/GJ144d/index.md
fn kallast_body_params() -> BodyParams {
// planet_class: temperate | atmosphere: standard | hydrosphere: ocean
// orbit.period_days: 280.0 | orbit.axial_tilt_deg: 30.2 | tectonics: active
// star.type: K (luminosity_solar: 0.4)
// planet_class: temperate | atmosphere: standard | hydrosphere: ocean | tectonics: active
// (orbit/star data is non-canonical per D-240 and no longer feeds temperature)
BodyParams {
hydrosphere: Some("ocean".into()),
atmosphere: Some("standard".into()),
planet_class: Some("temperate".into()),
orbital_period_days: Some(280.0),
axial_tilt_deg: Some(30.2),
spectral_class: Some("K".into()),
star_type: None,
tectonic_activity: Some("active".into()),
region_latitude_deg: 0.0,
elevation_km: 0.0,
@@ -966,17 +1148,12 @@ fn kallast_body_params() -> BodyParams {
/// Glødberg (GJ581c) params — wiki: star-systems/GJ-581/bodies/GJ581c/index.md
fn gloedberg_body_params() -> BodyParams {
// planet_class: volcanic | atmosphere: standard | hydrosphere: subsurface_liquid
// orbit.period_days: 12.0 | orbit.axial_tilt_deg: 0.6 | tectonics: extreme
// star.type: M (luminosity_solar: 0.04)
// planet_class: volcanic | atmosphere: standard | hydrosphere: subsurface_liquid | tectonics: extreme
// (orbit/star data is non-canonical per D-240 and no longer feeds temperature)
BodyParams {
hydrosphere: Some("subsurface".into()),
atmosphere: Some("standard".into()),
planet_class: Some("volcanic".into()),
orbital_period_days: Some(12.0),
axial_tilt_deg: Some(0.6),
spectral_class: Some("M".into()),
star_type: None,
tectonic_activity: Some("volcanic".into()),
region_latitude_deg: 0.0,
elevation_km: 0.5,
@@ -986,17 +1163,12 @@ fn gloedberg_body_params() -> BodyParams {
/// Marevna (GJ447c) params — wiki: star-systems/GJ-447/bodies/GJ447c/index.md
/// GJ-447 is the real-world identifier for Ross 128.
fn marevna_body_params() -> BodyParams {
// planet_class: oceanic | atmosphere: standard | hydrosphere: ocean
// orbit.period_days: 170.0 | orbit.axial_tilt_deg: 7.0 | tectonics: active
// star.type: M (luminosity_solar: 0.04)
// planet_class: oceanic | atmosphere: standard | hydrosphere: ocean | tectonics: active
// (orbit/star data is non-canonical per D-240 and no longer feeds temperature)
BodyParams {
hydrosphere: Some("ocean".into()),
atmosphere: Some("standard".into()),
planet_class: Some("oceanic".into()),
orbital_period_days: Some(170.0),
axial_tilt_deg: Some(7.0),
spectral_class: Some("M".into()),
star_type: None,
tectonic_activity: Some("active".into()),
region_latitude_deg: 0.0,
elevation_km: 0.0,
@@ -1071,8 +1243,10 @@ fn validation_kallast_alluvial_plain() {
eprintln!(
"[validation] Kallast: zone={:?} temp={:?}°C glaciation={:?} tectonic={:?} — PASS",
profile.morphology_zone, profile.temperature_c,
profile.glaciation_grade, profile.tectonic_class
profile.morphology_zone,
profile.temperature_c,
profile.glaciation_grade,
profile.tectonic_class
);
}
@@ -1082,7 +1256,9 @@ fn validation_gloedberg_volcanic_immature_drainage() {
// No body named "Cygni B" exists in the current wiki. GJ581c (Glødberg) is
// the closest volcanic match. NOTE: update this test if a "Cygni B" body is
// added to the wiki in the future.
eprintln!("[validation] Glødberg (GJ581c): volcanic proxy — checking LavaField + immature drainage");
eprintln!(
"[validation] Glødberg (GJ581c): volcanic proxy — checking LavaField + immature drainage"
);
let profile = derive_profile_for_body(&gloedberg_body_params());
@@ -1131,7 +1307,9 @@ fn validation_gloedberg_volcanic_immature_drainage() {
#[test]
fn validation_marevna_ocean_island() {
// D-239 §1: "Ross 128 = ocean island." GJ-447 is Ross 128; GJ447c = Marevna.
eprintln!("[validation] Marevna (GJ447c / Ross 128): ocean island — checking coastal/ocean zone");
eprintln!(
"[validation] Marevna (GJ447c / Ross 128): ocean island — checking coastal/ocean zone"
);
let profile = derive_profile_for_body(&marevna_body_params());
@@ -1169,24 +1347,22 @@ fn validation_marevna_ocean_island() {
If wrong, fix body params — not the derivation (D-239 §1).",
profile.morphology_zone
);
// Temperature is deterministically derived: log it for audit.
// NOTE: At period_days=170 + M-star luminosity=0.04, the equilibrium derivation
// yields approximately -56°C — colder than expected for a "habitable" world.
// Per D-239 §1: if the body reads wrong, fix the body params (e.g. period_days),
// not the derivation. This assertion is intentionally loose — we verify the
// derivation pipeline ran without panic and produced a consistent result.
// A future wiki update to GJ447c orbital params will bring it into the
// habitable range without touching this harness.
// Temperature is deterministically derived from the planet_class envelope (D-240),
// not orbit/star data: oceanic band [-12, 28]°C, maritime factor 0.6 for an "ocean"
// hydrosphere. At the equator (latitude 0) the latitude lerp sits at the warm sub-band
// edge (~+20°C) and the standard-atmosphere greenhouse fraction lifts it to ~+26°C —
// a habitable result, as expected for an oceanic world. The assertion stays loose: we
// verify the pipeline ran without panic and produced a physically plausible value.
if let Some(t) = profile.temperature_c {
assert!(
t > -250.0 && t < 200.0,
"Marevna: temperature {t}°C is outside the physical range [-250, 200]"
t > -90.0 && t < 90.0,
"Marevna: temperature {t}°C is outside the oceanic-class plausible range [-90, 90]"
);
}
eprintln!(
"[validation] Marevna: zone={:?} temp={:?}°C moisture_q={} — PASS \
(NOTE: temp ~-56°C at period_days=170 M-star; wiki params may need tuning per D-239 §1)",
(oceanic class-envelope temp at equator per D-240)",
profile.morphology_zone, profile.temperature_c, profile.moisture_q
);
}
@@ -1259,6 +1435,11 @@ 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;
@@ -0,0 +1,38 @@
[
{
"label": "alluvial_forest_active_channel",
"seed": 16045690984503098046,
"body_id": "GJ144d",
"tile_x": 640,
"tile_y": 1280,
"terrain": 0,
"vegetation": 1,
"water": 0,
"elevation_m": 11,
"cover": 0
},
{
"label": "lava_field_barren",
"seed": 1311768467294899695,
"body_id": "GJ581c",
"tile_x": 320,
"tile_y": 448,
"terrain": 5,
"vegetation": 0,
"water": 0,
"elevation_m": 21,
"cover": 0
},
{
"label": "fjord_wall_glaciated",
"seed": 18369614217980264670,
"body_id": "GJ447c",
"tile_x": 928,
"tile_y": 544,
"terrain": 3,
"vegetation": 0,
"water": 2,
"elevation_m": 1,
"cover": 1
}
]