From de5f0f1e58777a0bc02814f42fd92d9d2199f75d Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Mon, 8 Jun 2026 19:33:00 +0200 Subject: [PATCH] test(simulation): align T-1031 harness with D-240 class-envelope temps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- server/tests/derivation_harness.rs | 347 +++++++++++++++----- server/tests/golden/derivation_harness.json | 38 +++ 2 files changed, 302 insertions(+), 83 deletions(-) create mode 100644 server/tests/golden/derivation_harness.json diff --git a/server/tests/derivation_harness.rs b/server/tests/derivation_harness.rs index f2a8a28e6..2d75597e6 100644 --- a/server/tests/derivation_harness.rs +++ b/server/tests/derivation_harness.rs @@ -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 = golden_cases() .into_iter() - .map(|(lbl, seed, body, region, cp, tx, ty)| derive_golden(lbl, seed, body, ®ion, cp, tx, ty)) + .map(|(lbl, seed, body, region, cp, tx, ty)| { + derive_golden(lbl, seed, body, ®ion, cp, tx, ty) + }) .collect(); let run2: Vec = golden_cases() .into_iter() - .map(|(lbl, seed, body, region, cp, tx, ty)| derive_golden(lbl, seed, body, ®ion, cp, tx, ty)) + .map(|(lbl, seed, body, region, cp, tx, ty)| { + derive_golden(lbl, seed, body, ®ion, 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", ®ion, (3, 3), TerrainMaterial::Lava, "LavaField"); + assert_all_terrain_is( + 42, + "GJ581c", + ®ion, + (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", ®ion, (0, 0), TerrainMaterial::Rock, "FjordWall"); + assert_all_terrain_is( + 42, + "fjord_body", + ®ion, + (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", ®ion, (0, 0), TerrainMaterial::Rock, "CliffCoast"); + assert_all_terrain_is( + 42, + "cliff_body", + ®ion, + (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", ®ion, (0, 0), TerrainMaterial::Rock, "IncisedGorge", + 42, + "gorge_body", + ®ion, + (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", ®ion, (0, 0), TerrainMaterial::Sand, "DuneStrand"); + assert_all_terrain_is( + 42, + "dune_body", + ®ion, + (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", ®ion, (0, 0), TerrainMaterial::Gravel, "BraidedDelta", + 42, + "delta_body", + ®ion, + (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", ®ion, (0, 0), TerrainMaterial::Soil, "AlluvialPlain", + 42, + "alluvial_body", + ®ion, + (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", ®ion, (0, 0), TerrainMaterial::Soil, "MeanderReach", + 42, + "meander_body", + ®ion, + (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, ®ion, &chunk, base_x + dx, base_y + dy); + let _ = cache.get_or_derive(seed, body_id, ®ion, &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; diff --git a/server/tests/golden/derivation_harness.json b/server/tests/golden/derivation_harness.json new file mode 100644 index 000000000..97685400c --- /dev/null +++ b/server/tests/golden/derivation_harness.json @@ -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 + } +]