fix(simulation): make voxel relief sub-district + fix integration-test fallout (T-1081)
The push gate's integration tests (not in cargo test --lib) caught two regressions from the relief change: - derivation_harness::cross_district_elevation_blend_reduces_seam_step — the relief's large per-position term (span 300 → ±200 m, far above the compressed elev_q/2 base) swamped the T-1042 seam measurement and clamped heavily at sea level. Fixes: (1) shift the voxel-relief octave band to 0.13–1 km (all sub-district, dropping the 2 km octave that competed with elev_q's district role); (2) reduce VOXEL_RELIEF_SPAN_M 300 → 100 so relief stays mostly below the base (fewer sea-level clamp artifacts, proportional hills); (3) rewrite the seam test's avg_elev to average over an 8 km multi-wavelength y-transect so the zero-mean relief cancels, isolating the base seam. - derivation_harness::golden_seed_determinism_regression — legitimate golden refresh (determinism still holds; elevation values changed intentionally). Believability relief now ≈22 m mean (was 5 m flat; 59 m at the over-aggressive span 300) — navigable hills without clamp artifacts, 7/8 criteria. Both goldens regenerated; full cargo test (38 binaries) + clippy --all-targets -D warnings green. D-239 amendment + Q-123 item 4 updated to the final span/octave/numbers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -24,11 +24,13 @@ use crate::seed::splitmix64;
|
||||
/// coarsest (~33 km) the heightmap itself carries the shape.
|
||||
const OCTAVE_WAVELENGTHS_M: [f64; 4] = [32_768.0, 16_384.0, 8_192.0, 4_096.0];
|
||||
|
||||
/// Voxel-tier octave wavelengths in metres — the ≈0.25–2 km band that the
|
||||
/// district-tier [`terrain_detail`] (4–40 km) is too coarse to carry and the
|
||||
/// per-voxel scatter (<64 m) is too fine to reach. This is the [`voxel_relief`]
|
||||
/// band: the rolling/ridged hills a *walking character* navigates by (T-1081).
|
||||
const VOXEL_OCTAVE_WAVELENGTHS_M: [f64; 4] = [2_048.0, 1_024.0, 512.0, 256.0];
|
||||
/// Voxel-tier octave wavelengths in metres — the ≈0.13–1 km **sub-district** band
|
||||
/// (all finer than the 2 km district planning unit) that the district-tier
|
||||
/// [`terrain_detail`] (4–40 km) is too coarse to carry and the per-voxel scatter
|
||||
/// (<64 m) is too fine to reach. This is the [`voxel_relief`] band: the rolling/ridged
|
||||
/// hills a *walking character* navigates by (T-1081). The coarsest octave stays below
|
||||
/// the district size so the relief never competes with `elev_q`'s district-scale role.
|
||||
const VOXEL_OCTAVE_WAVELENGTHS_M: [f64; 4] = [1_024.0, 512.0, 256.0, 128.0];
|
||||
|
||||
/// Deterministic lattice value in `[-1, 1)` for an integer noise cell.
|
||||
#[inline]
|
||||
|
||||
@@ -334,13 +334,19 @@ fn zone_to_family(zone: &MorphologyZone) -> MorphologyFamily {
|
||||
///
|
||||
/// A fully derived `VoxelColumn` with all D-228 axes populated.
|
||||
/// T-1081: the metre span of the voxel-tier mid-scale relief field — the amplitude
|
||||
/// ceiling for the ≈0.25–2 km rolling/ridged hills added to the family base
|
||||
/// ceiling for the ≈0.13–1 km rolling/ridged hills added to the family base
|
||||
/// elevation. Actual relief at a voxel is `voxel_relief(...) * SPAN`, which the
|
||||
/// envelope (district ruggedness) scales down on gentle terrain, so this is the
|
||||
/// steep-terrain ceiling, not a uniform amplitude. Provisional — tuned against the
|
||||
/// believability probe (T-1079) like the moisture gradient; per-body *absolute*
|
||||
/// elevation span is a later refinement (the T-1080 climate-fields lineage).
|
||||
const VOXEL_RELIEF_SPAN_M: i32 = 300;
|
||||
///
|
||||
/// Kept modest so relief stays mostly *below* the family base elevation: the base is
|
||||
/// the compressed `elev_q/N` scale (max ~50 m) and `elevation_m` clamps at 0 (sea
|
||||
/// level), so an oversized span clamps away on low ground — drowning the relief and
|
||||
/// biasing it positive. A larger span belongs with the deferred per-body absolute
|
||||
/// elevation model that would give the base real headroom.
|
||||
const VOXEL_RELIEF_SPAN_M: i32 = 100;
|
||||
|
||||
pub fn derive_voxel_column(
|
||||
world_seed: u64,
|
||||
|
||||
@@ -1191,40 +1191,42 @@ fn cross_district_elevation_blend_reduces_seam_step() {
|
||||
"T-1042: boundary blend weight must be 128 (50-50)"
|
||||
);
|
||||
|
||||
// Derive ChunkContext for last-A with the secondary district B at 50-50 blend.
|
||||
let ctx_last_a = derive_chunk_context(
|
||||
seed,
|
||||
body,
|
||||
&district_a,
|
||||
last_a_chunk,
|
||||
Some((&district_b, blend_w)),
|
||||
);
|
||||
// First-B: no blend (interior to B).
|
||||
let ctx_first_b = derive_chunk_context(seed, body, &district_b, first_b_chunk, None);
|
||||
// Interior chunks: no blend.
|
||||
// Interior-A context retained for the determinism sub-check (Criterion 3).
|
||||
let ctx_interior_a = derive_chunk_context(seed, body, &district_a, interior_a_chunk, None);
|
||||
let ctx_interior_b = derive_chunk_context(seed, body, &district_b, interior_b_chunk, None);
|
||||
|
||||
// Average elevation across a full 64-voxel row through each chunk.
|
||||
// We scan y=0 (along the x cross-axis for this AlluvialPlain basin).
|
||||
let avg_elev = |chunk_pos: ChunkPos,
|
||||
ctx: &settled_reach_server::atlas::chunk_context::ChunkContext,
|
||||
dist: &DistrictProfile|
|
||||
-> i64 {
|
||||
let base_x = chunk_pos.0 * scale::CHUNK_M;
|
||||
let base_y = chunk_pos.1 * scale::CHUNK_M;
|
||||
let mut sum = 0i64;
|
||||
for dx in 0..scale::VOXELS_PER_CHUNK {
|
||||
let col = derive_voxel_column(seed, body, dist, ctx, base_x + dx, base_y);
|
||||
sum += col.elevation_m as i64;
|
||||
}
|
||||
sum / scale::VOXELS_PER_CHUNK as i64
|
||||
};
|
||||
// Average elevation over a tall multi-wavelength y-transect at a given x-column,
|
||||
// deriving a ChunkContext per y-chunk. T-1081 adds a zero-mean voxel-relief term to
|
||||
// elevation_m; averaged over ≥4× the coarsest relief wavelength (1024 m) it cancels,
|
||||
// leaving the elev_q-derived base — the quantity T-1042's blend actually smooths. A
|
||||
// single 64 m row would carry a per-chunk relief offset that swamps the seam signal.
|
||||
let avg_elev =
|
||||
|x_chunk: i32, blend: Option<(&DistrictProfile, u8)>, dist: &DistrictProfile| -> i64 {
|
||||
const Y_CHUNKS: i32 = 128; // 8192 m ≈ 8× the 1024 m coarsest relief octave
|
||||
let base_x = x_chunk * scale::CHUNK_M;
|
||||
let mut sum = 0i64;
|
||||
let mut n = 0i64;
|
||||
for yc in 0..Y_CHUNKS {
|
||||
let cp: ChunkPos = (x_chunk, yc);
|
||||
let ctx = derive_chunk_context(seed, body, dist, cp, blend);
|
||||
let base_y = yc * scale::CHUNK_M;
|
||||
for dx in (0..scale::VOXELS_PER_CHUNK).step_by(8) {
|
||||
for dy in (0..scale::VOXELS_PER_CHUNK).step_by(8) {
|
||||
let col =
|
||||
derive_voxel_column(seed, body, dist, &ctx, base_x + dx, base_y + dy);
|
||||
sum += col.elevation_m as i64;
|
||||
n += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
sum / n
|
||||
};
|
||||
|
||||
let elev_last_a = avg_elev(last_a_chunk, &ctx_last_a, &district_a);
|
||||
let elev_first_b = avg_elev(first_b_chunk, &ctx_first_b, &district_b);
|
||||
let elev_interior_a = avg_elev(interior_a_chunk, &ctx_interior_a, &district_a);
|
||||
let elev_interior_b = avg_elev(interior_b_chunk, &ctx_interior_b, &district_b);
|
||||
// Force the 50-50 blend with B across the whole last-A column (x=31); the interiors
|
||||
// and first-B column are unblended (per the boundary detection asserted above).
|
||||
let elev_last_a = avg_elev(last_a_chunk.0, Some((&district_b, blend_w)), &district_a);
|
||||
let elev_first_b = avg_elev(first_b_chunk.0, None, &district_b);
|
||||
let elev_interior_a = avg_elev(interior_a_chunk.0, None, &district_a);
|
||||
let elev_interior_b = avg_elev(interior_b_chunk.0, None, &district_b);
|
||||
|
||||
// Seam step = elevation gap between the blended last-A chunk and the clean first-B chunk.
|
||||
let seam_step = (elev_last_a - elev_first_b).unsigned_abs() as i64;
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"morphology_zones": 9,
|
||||
"vegetation_classes": 3,
|
||||
"terrain_materials": 4,
|
||||
"voxel_relief_m": 49
|
||||
"voxel_relief_m": 20
|
||||
},
|
||||
"coherence": {
|
||||
"water_districts": 49,
|
||||
@@ -68,7 +68,7 @@
|
||||
"morphology_zones": 9,
|
||||
"vegetation_classes": 1,
|
||||
"terrain_materials": 4,
|
||||
"voxel_relief_m": 45
|
||||
"voxel_relief_m": 19
|
||||
},
|
||||
"coherence": {
|
||||
"water_districts": 25,
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"terrain": 5,
|
||||
"vegetation": 0,
|
||||
"water": 0,
|
||||
"elevation_m": 21,
|
||||
"elevation_m": 36,
|
||||
"cover": 0
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user