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:
2026-06-28 21:43:39 +02:00
co-authored by Claude Opus 4.8
parent dc1d905ce0
commit 677de42bd2
7 changed files with 53 additions and 43 deletions
+7 -5
View File
@@ -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.252 km band that the
/// district-tier [`terrain_detail`] (440 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.131 km **sub-district** band
/// (all finer than the 2 km district planning unit) that the district-tier
/// [`terrain_detail`] (440 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]
+8 -2
View File
@@ -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.252 km rolling/ridged hills added to the family base
/// ceiling for the ≈0.131 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,
+33 -31
View File
@@ -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;
+2 -2
View File
@@ -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,
+1 -1
View File
@@ -20,7 +20,7 @@
"terrain": 5,
"vegetation": 0,
"water": 0,
"elevation_m": 21,
"elevation_m": 36,
"cover": 0
},
{