fix(simulation): unify C1 climate gate across Wetland+Forest+Grassland (PR #172 N1)
Tyre's re-review found the C1 !Barren guard only covered the Grassland arm — the Wetland arm was an unconditional => true. Since derive_morphology_zone has no temperature gate, a frozen + wet + flat district still yields Wetland material, so the wetland palette would paint copse/bog onto frozen marsh: the exact C1 symptom via the material path. Not a regression (the arm was unchanged) and unreachable by the 2 harness bodies, but the D-246 amendment + the voxel.rs comment already claim the guarantee universally, so the gate must match the claim.
Gate is now: apply = !Barren && class in {Wetland, Forest, Grassland}. Behavior change is limited to frozen/hyper-arid Wetland districts (a temperate marsh is never Barren-dominant; Forest is Barren-free by construction). believability.json unchanged (neither harness body reaches the frozen-wet case). New regression test mosaic_pass_does_not_revegetate_frozen_wetland; D-246 amendment reworded to the unified gate. Full cargo test green; clippy -D warnings clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+47
-13
@@ -537,19 +537,24 @@ pub fn derive_voxel_column(
|
||||
// boreal / tundra read distinctly (D-246 biome-awareness).
|
||||
let biome =
|
||||
mosaic::ClimateBiome::from_climate(district_eff.temperature_c, district_eff.moisture_q);
|
||||
let apply = match class {
|
||||
SurfaceClass::Wetland | SurfaceClass::Forest => true,
|
||||
// Only where the climate already grants cover (C1 fix). A Barren-dominant
|
||||
// district is Barren for a reason `derive_vegetation` already resolved —
|
||||
// frozen (< -50 °C = surface ice/geology, D-239 §2), hyper-arid (moisture < 5),
|
||||
// or cold-dry marginal ground — so the mosaic must NOT re-grant it (else it
|
||||
// paints tundra scrub onto frozen ice worlds like Edict). Genuine tundra
|
||||
// (cold + moisture ≥ 15) is already Scrub upstream, so it reaches the biome's
|
||||
// tundra palette through this same !Barren path — no special cold clause needed.
|
||||
SurfaceClass::Grassland => !matches!(dominant_veg, Vegetation::Barren),
|
||||
// Rock / Sand / Gravel / Lava are material-driven — a §8-aware follow-up.
|
||||
_ => false,
|
||||
};
|
||||
// Apply within the vegetated / wet land classes — but ONLY where the climate
|
||||
// already grants cover (C1 + N1). A Barren-dominant district is Barren for a
|
||||
// reason `derive_vegetation` already resolved — frozen (< -50 °C = surface
|
||||
// ice/geology, D-239 §2), hyper-arid (moisture < 5), or cold-dry marginal ground
|
||||
// — so the mosaic must NOT re-grant it, else it paints tundra scrub onto a frozen
|
||||
// ice world (Edict, via the Grassland arm) or wetland copse onto a frozen marsh
|
||||
// (via the Wetland arm — `derive_morphology_zone` has no temperature gate, so
|
||||
// frozen + wet + flat still yields Wetland material). The `!Barren` guard therefore
|
||||
// spans all three classes, not just Grassland. Genuine tundra (cold + moisture ≥ 15)
|
||||
// is Scrub upstream (not Barren), so it still reaches the biome's tundra palette.
|
||||
// Forest is Barren-free by construction (SurfaceClass::Forest requires
|
||||
// dominant_veg == Forest). Rock / Sand / Gravel / Lava are material-driven — a
|
||||
// §8-aware follow-up (D-246 amendment).
|
||||
let apply = !matches!(dominant_veg, Vegetation::Barren)
|
||||
&& matches!(
|
||||
class,
|
||||
SurfaceClass::Wetland | SurfaceClass::Forest | SurfaceClass::Grassland
|
||||
);
|
||||
if apply {
|
||||
let mosaic_seed = SeedChain::for_body(world_seed, body_id)
|
||||
.derive(SeedDomain::VoxelMosaic, 0)
|
||||
@@ -2629,6 +2634,35 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// N1 regression (PR #172 re-review): the C1 climate gate must hold on the Wetland
|
||||
/// arm too, not only Grassland. `derive_morphology_zone` takes no temperature, so a
|
||||
/// FROZEN + wet + flat district maps to Wetland material (`SurfaceClass::Wetland`)
|
||||
/// while `derive_vegetation` returns Barren — the exact C1 symptom via the material
|
||||
/// path. The unified `!Barren` gate skips it, so the frozen marsh stays Barren rather
|
||||
/// than being painted with the wetland palette (copse / meadow / bog / marsh). Before
|
||||
/// the N1 fix the Wetland arm was an unconditional `=> true`.
|
||||
#[test]
|
||||
fn mosaic_pass_does_not_revegetate_frozen_wetland() {
|
||||
let frozen_wet = DistrictProfile {
|
||||
vegetation_class: VegetationClass::Barren,
|
||||
temperature_c: Some(-60.0), // frozen: surface ice = geology (D-239 §2)
|
||||
moisture_q: 70, // ≥ 60 + flat ⇒ Wetland material (SurfaceClass::Wetland)
|
||||
morphology_zone: MorphologyZone::Wetland,
|
||||
slope_q: 3,
|
||||
..alluvial_district()
|
||||
};
|
||||
let chunk = derive_chunk_context(42, "frozenmarsh", &frozen_wet, (10, 20), None);
|
||||
for tx in [100, 137, 200, 263, 331] {
|
||||
let col = derive_voxel_column(42, "frozenmarsh", &frozen_wet, &chunk, tx, 90);
|
||||
assert_eq!(
|
||||
col.vegetation,
|
||||
Vegetation::Barren,
|
||||
"frozen Wetland district must stay Barren (N1), got {:?} at x={tx}",
|
||||
col.vegetation,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn elevation_is_non_negative() {
|
||||
let district = alluvial_district();
|
||||
|
||||
Reference in New Issue
Block a user