feat(simulation): WaterBody voxel family — oceans/lakes/tidal flats render wet (T-1082)
The water zones (OpenOcean/Lake/TidalFlat) fell through to the dry-land AlluvialPlain fallback, so the sea rendered as dry forested land (the D-245 believability bug). - voxel.rs: new MorphologyFamily::WaterBody (9th family) + generate_water_body, dispatched from zone_to_family. Deep open water with a Shallow shoal band on the district-anchored coast line (wider for shallower bodies); TidalFlat all-Shallow. Seabed Rock (steep) / Sand (gentle) / Wetland (tidal mud); Vegetation::Barren; water surface elevation_m = 0; seasonal Ice via derive_cover (frozen seas). Wetland stays AlluvialPlain (it is land — a marsh, not open water). Integer/D-010. + 4 tests. - believability.rs: fix a sampling bias in the T-1083 enforcer found while verifying this — the metrics sampled the first 64 districts in BTreeMap order (a spatial corner, e.g. all-ocean), which reported a forested world as "0 vegetated". Now a deterministic seeded spread across the body (matches D-245's "randomly-sampled locations"). - D-239 amended (§5 8→9 families; §6 refined — the 3 water zones now dispatch to WaterBody). Believability golden regenerated. Probe (Arbour @ yolo): water-renders-wet 4/64 FAIL → 42/44 PASS; terrain-material variety 1 → 4 PASS; 3/7 → 5/7 criteria. (Remaining fails — moisture gradient, vegetation-present — are T-1080.) clippy --all-targets -D warnings clean; 1579 lib tests + the harness pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -33,8 +33,9 @@
|
||||
//!
|
||||
//! [`analyze`] is a pure, deterministic function of `(world_seed, body_id, districts)`:
|
||||
//! scalar/categorical contrast is computed over **all** districts; the voxel-derived
|
||||
//! metrics sample the first [`VOXEL_SAMPLE_DISTRICTS`] in `BTreeMap` order (sorted →
|
||||
//! stable). Suitable for golden-snapshot regression.
|
||||
//! metrics sample a **seeded spread** of [`VOXEL_SAMPLE_DISTRICTS`] across the body
|
||||
//! (deterministic from the world seed — D-245's "randomly-sampled locations", and
|
||||
//! unbiased unlike a contiguous corner). Suitable for golden-snapshot regression.
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::path::PathBuf;
|
||||
@@ -60,8 +61,9 @@ const DEFAULT_SEA_LEVEL: f32 = 0.3;
|
||||
/// `ocean_fraction_q` at or above this marks a district water-present (D-239 §10 uses
|
||||
/// `ocean_fraction_q` as the chunk-scale water proxy).
|
||||
const WATER_PRESENCE_Q: i32 = 10;
|
||||
/// Districts voxel-sampled for the derived metrics — the first N in `BTreeMap` order
|
||||
/// (deterministic). Scalar/categorical contrast uses *all* districts (cheap).
|
||||
/// Districts voxel-sampled for the derived metrics — a deterministic seeded spread
|
||||
/// across the body (D-245 randomly-sampled-anywhere). Scalar/categorical contrast
|
||||
/// uses *all* districts (cheap).
|
||||
const VOXEL_SAMPLE_DISTRICTS: usize = 64;
|
||||
/// Stride over a district's representative 64 m chunk (8 → an 8×8 = 64-voxel sample).
|
||||
const VOXEL_SAMPLE_STRIDE: usize = 8;
|
||||
@@ -171,14 +173,21 @@ pub fn analyze(
|
||||
terrain_materials: 0, // filled from the voxel sample below
|
||||
};
|
||||
|
||||
// ── Voxel-derived metrics over a deterministic subset of districts ────────
|
||||
// ── Voxel-derived metrics over a deterministic seeded spread of districts ──
|
||||
// Sampling must span the whole body, not a contiguous prefix: BTreeMap order is
|
||||
// a spatial corner (e.g. an all-ocean edge), which would report a forested world
|
||||
// as "0 vegetated". A seeded spread is both unbiased and matches D-245's
|
||||
// "randomly-sampled locations across the body" wording.
|
||||
let mut terrain_set: BTreeSet<String> = BTreeSet::new();
|
||||
let mut coh = CoherenceMetrics::default();
|
||||
let mut sampled = 0usize;
|
||||
|
||||
for (dp, prof) in districts.iter().take(VOXEL_SAMPLE_DISTRICTS) {
|
||||
let keys: Vec<DistrictPos> = districts.keys().copied().collect();
|
||||
for idx in sample_indices(world_seed, keys.len(), VOXEL_SAMPLE_DISTRICTS) {
|
||||
let dp = keys[idx];
|
||||
let prof = &districts[&dp];
|
||||
sampled += 1;
|
||||
let chunk = district_centre_chunk(*dp);
|
||||
let chunk = district_centre_chunk(dp);
|
||||
let ctx = derive_chunk_context(world_seed, body_id, prof, chunk, None);
|
||||
|
||||
let mut any_wet = false;
|
||||
@@ -339,6 +348,29 @@ fn district_centre_chunk(dp: DistrictPos) -> ChunkPos {
|
||||
)
|
||||
}
|
||||
|
||||
/// Deterministic seeded spread of up to `take` distinct indices in `0..n` — a
|
||||
/// reproducible "random" sample across the whole district list (D-245's
|
||||
/// randomly-sampled-anywhere requirement), unbiased unlike a contiguous prefix.
|
||||
/// Returns all of `0..n` when `n <= take`. splitmix64 mixing keyed on the world seed.
|
||||
fn sample_indices(world_seed: u64, n: usize, take: usize) -> Vec<usize> {
|
||||
if n <= take {
|
||||
return (0..n).collect();
|
||||
}
|
||||
let mut picked: BTreeSet<usize> = BTreeSet::new();
|
||||
let mut i: u64 = 0;
|
||||
while picked.len() < take {
|
||||
let mut z = world_seed
|
||||
.wrapping_add(i)
|
||||
.wrapping_add(0x9E37_79B9_7F4A_7C15);
|
||||
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
|
||||
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
|
||||
z ^= z >> 31;
|
||||
picked.insert((z % n as u64) as usize);
|
||||
i += 1;
|
||||
}
|
||||
picked.into_iter().collect()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Loader — resolve committed data + run the real cascade (shared by the probe
|
||||
// binary and the regression harness, so both measure the same thing)
|
||||
|
||||
Reference in New Issue
Block a user