feat(simulation): T-1149 derive_at_metres extraction + min_wavelength octave cutoff
Extract derive_district's fractional-metres interior into the metres- addressable derive_at_metres(seed, body, params, ta, wx, wy, climate, min_wavelength_m) — the zoom ladder's keystone (design doc §8 step 1). derive_district is now a thin DistrictPos-quantizing wrapper calling it with cutoff 0.0; all four golden/believability/derivation harnesses pass byte-identical. enveloped_fbm/terrain_detail/voxel_relief gain min_wavelength_m: octaves below the cutoff are hard-truncated (amp still advances so surviving octaves keep relative weight; norm==0 guarded). Amplitude-fade-near- cutoff is documented as a seam, not built — the pop-risk A/B needs the client ladder (T-1153). Cutoff 0.0 is bit-identical to pre-change output, asserted by test. Measured (release, 4096-cell sweeps, zoom_ladder_bench.rs): district spacing 1.200µs/cell (cutoff 0) / 1.201µs (cutoff 2048m — all octaves survive, plumbing check); quarter spacing 1.165µs/cell (cutoff 512m). Matches the design doc's ~1.2-1.4µs/cell release estimate.
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
//! Zoom-ladder derivation benchmarks (T-1149, design doc §2/§7/§8 step 1).
|
||||
//!
|
||||
//! Measures `derive_at_metres` per-cell cost at district spacing (2,048 m) and
|
||||
//! quarter spacing (512 m), with and without a `min_wavelength_m` octave
|
||||
//! cutoff — the exact numbers the design doc flags as UNBUILT/UNMEASURED
|
||||
//! (§7: "Octave-cutoff derive (min_wavelength_m-bearing) ... not measured").
|
||||
//!
|
||||
//! Manual `Instant`-based timing, matching every other bench in this repo
|
||||
//! (`shadowcast_bench.rs`, `perf_bench.rs`) and the same technique
|
||||
//! `aliveness_probe --render` used to produce the ~1.2–1.4 µs/district
|
||||
//! release figure the design doc cites — no criterion dependency exists here.
|
||||
//!
|
||||
//! Run: `cargo test --release --test zoom_ladder_bench -- --ignored --nocapture`
|
||||
//! (debug numbers are ~5x slower and not representative of the design doc's
|
||||
//! release-build figures; run `--release` for numbers worth recording).
|
||||
|
||||
use std::time::Instant;
|
||||
|
||||
use settled_reach_server::atlas::district_profile::{
|
||||
derive_at_metres, BodyParams, ClimateConstants,
|
||||
};
|
||||
use settled_reach_server::atlas::drainage;
|
||||
use settled_reach_server::atlas::features::TerrainAnalysis;
|
||||
use settled_reach_server::atlas::heightmap::BodyHeightmap;
|
||||
use settled_reach_server::atlas::scale;
|
||||
use settled_reach_server::seed::{SeedChain, SeedDomain};
|
||||
|
||||
fn bench_hm() -> BodyHeightmap {
|
||||
// Same shape as district_profile.rs's own test_hm/window_test_hm fixtures
|
||||
// — a smooth gradient, deterministic, no PNG I/O.
|
||||
let (w, h) = (128u32, 64u32);
|
||||
let n = (w * h) as usize;
|
||||
let data = (0..n)
|
||||
.map(|i| {
|
||||
let r = (i / w as usize) as f32 / h as f32;
|
||||
let c = (i % w as usize) as f32 / w as f32;
|
||||
(r * 0.6 + c * 0.4).min(1.0)
|
||||
})
|
||||
.collect();
|
||||
BodyHeightmap {
|
||||
body_id: "bench".into(),
|
||||
width: w,
|
||||
height: h,
|
||||
data,
|
||||
sea_level: 0.3,
|
||||
}
|
||||
}
|
||||
|
||||
fn bench_ta(hm: &BodyHeightmap) -> TerrainAnalysis {
|
||||
let dr = drainage::analyze(&hm.data, hm.width, hm.height, hm.sea_level);
|
||||
TerrainAnalysis::analyze(hm, &dr)
|
||||
}
|
||||
|
||||
fn bench_params() -> BodyParams {
|
||||
BodyParams {
|
||||
hydrosphere: Some("ocean".into()),
|
||||
atmosphere: Some("breathable".into()),
|
||||
planet_class: Some("temperate".into()),
|
||||
body_radius_km: Some(6371.0),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Time `n_cells` sequential `derive_at_metres` calls on a spacing-`step_m`
|
||||
/// grid starting at world origin, with the given octave cutoff. Returns
|
||||
/// (total_elapsed, per_cell_ns).
|
||||
fn time_derive_sweep(
|
||||
seed: SeedChain,
|
||||
body_id: &str,
|
||||
params: &BodyParams,
|
||||
ta: &TerrainAnalysis,
|
||||
climate: &ClimateConstants,
|
||||
grid_side: u32,
|
||||
step_m: f64,
|
||||
min_wavelength_m: f64,
|
||||
) -> (std::time::Duration, f64) {
|
||||
let n_cells = (grid_side * grid_side) as u64;
|
||||
let t0 = Instant::now();
|
||||
for row in 0..grid_side {
|
||||
for col in 0..grid_side {
|
||||
let wx = col as f64 * step_m;
|
||||
let wy = row as f64 * step_m;
|
||||
let prof = derive_at_metres(
|
||||
seed,
|
||||
body_id,
|
||||
params,
|
||||
ta,
|
||||
wx,
|
||||
wy,
|
||||
climate,
|
||||
min_wavelength_m,
|
||||
);
|
||||
// Prevent the optimizer from hoisting the call out of the loop.
|
||||
std::hint::black_box(prof.elev_q);
|
||||
}
|
||||
}
|
||||
let elapsed = t0.elapsed();
|
||||
let per_cell_ns = elapsed.as_secs_f64() * 1e9 / n_cells as f64;
|
||||
(elapsed, per_cell_ns)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn bench_derive_at_metres_district_and_quarter_spacing() {
|
||||
let hm = bench_hm();
|
||||
let ta = bench_ta(&hm);
|
||||
let params = bench_params();
|
||||
let climate = ClimateConstants::default();
|
||||
let seed = SeedChain::root(99).derive(SeedDomain::Body, 1);
|
||||
let grid_side = 64u32; // 4,096 cells per sweep — matches the D-226 window cap
|
||||
|
||||
println!("\n=== T-1149 zoom-ladder derive_at_metres benchmark ===");
|
||||
println!(
|
||||
"grid: {grid_side}x{grid_side} = {} cells/sweep\n",
|
||||
grid_side * grid_side
|
||||
);
|
||||
|
||||
let district_m = scale::DISTRICT_M as f64;
|
||||
let quarter_m = scale::QUARTER_M as f64;
|
||||
|
||||
// District spacing (2,048 m), cutoff 0 — today's uncut behavior.
|
||||
let (elapsed, per_cell_ns) = time_derive_sweep(
|
||||
seed, "bench", ¶ms, &ta, &climate, grid_side, district_m, 0.0,
|
||||
);
|
||||
println!(
|
||||
"district spacing, cutoff=0: {:>8.2} ms total, {:>7.1} ns/cell ({:.3} µs/cell)",
|
||||
elapsed.as_secs_f64() * 1000.0,
|
||||
per_cell_ns,
|
||||
per_cell_ns / 1000.0
|
||||
);
|
||||
|
||||
// District spacing, cutoff 2,048 m — truncates every OCTAVE_WAVELENGTHS_M
|
||||
// entry below the district's own spacing (finest is 4,096 m, so this
|
||||
// cutoff is BELOW that — confirms the cutoff plumbing at district scale
|
||||
// without changing which octaves survive, since 2,048 < 4,096 admits all
|
||||
// of them; recorded for the design doc's requested (district, cutoff
|
||||
// 2048) combination regardless).
|
||||
let (elapsed, per_cell_ns) = time_derive_sweep(
|
||||
seed,
|
||||
"bench",
|
||||
¶ms,
|
||||
&ta,
|
||||
&climate,
|
||||
grid_side,
|
||||
district_m,
|
||||
2_048.0,
|
||||
);
|
||||
println!(
|
||||
"district spacing, cutoff=2048m: {:>8.2} ms total, {:>7.1} ns/cell ({:.3} µs/cell)",
|
||||
elapsed.as_secs_f64() * 1000.0,
|
||||
per_cell_ns,
|
||||
per_cell_ns / 1000.0
|
||||
);
|
||||
|
||||
// Quarter spacing (512 m), cutoff 512 m — the T-1150 Option B rung: full
|
||||
// reclassification at quarter spacing with the matching octave cutoff.
|
||||
let (elapsed, per_cell_ns) = time_derive_sweep(
|
||||
seed,
|
||||
"bench",
|
||||
¶ms,
|
||||
&ta,
|
||||
&climate,
|
||||
grid_side,
|
||||
quarter_m,
|
||||
512.0,
|
||||
);
|
||||
println!(
|
||||
"quarter spacing, cutoff=512m: {:>8.2} ms total, {:>7.1} ns/cell ({:.3} µs/cell)",
|
||||
elapsed.as_secs_f64() * 1000.0,
|
||||
per_cell_ns,
|
||||
per_cell_ns / 1000.0
|
||||
);
|
||||
|
||||
println!();
|
||||
}
|
||||
Reference in New Issue
Block a user