Tyre's two governance sentences on the D-226 course note: (a) the Godot rasterizer floor makes per-class course WIDTH differentiation inert at every shipping fit zoom — classes distinguish by opacity alone until T-1175's per-vertex tapering; the 'trunk widest' promise is design intent, not current pixels. (b) The pole-row edge-drain branch is structurally unreachable; the real mechanism is interior k<0 (revert-verification discovery). Plus the dangling cargo-fmt reflow in the course-cost bench from the gate-bounce round. Tickets: T-1170 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
565 lines
21 KiB
Rust
565 lines
21 KiB
Rust
//! 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, derive_orbital_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::layer_proxy::{
|
||
build_district_window_layer, WindowGranularity, DISTRICT_WINDOW_MAX_N,
|
||
DISTRICT_WINDOW_MAX_N_REGION, WIRE_CAP_CELLS,
|
||
};
|
||
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_river_network(
|
||
hm: &BodyHeightmap,
|
||
) -> settled_reach_server::atlas::body_world_state::RiverNetwork {
|
||
drainage::analyze(&hm.data, hm.width, hm.height, hm.sea_level).river_network
|
||
}
|
||
|
||
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!();
|
||
}
|
||
|
||
/// Time `n_cells` sequential `derive_orbital_at_metres` calls — the
|
||
/// region-baseline-blend-only path (T-1152, design doc §2/§4/§9 R1), no
|
||
/// `invent_primitives` call at any point. Mirrors `time_derive_sweep`'s shape
|
||
/// exactly so the two numbers are directly comparable.
|
||
fn time_orbital_sweep(
|
||
seed: SeedChain,
|
||
body_id: &str,
|
||
params: &BodyParams,
|
||
ta: &TerrainAnalysis,
|
||
climate: &ClimateConstants,
|
||
grid_side: u32,
|
||
step_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_orbital_at_metres(seed, body_id, params, ta, wx, wy, climate);
|
||
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)
|
||
}
|
||
|
||
/// T-1152 / design doc §9 R1: "MEASURE FIRST" — per-cell cost of the
|
||
/// orbital-mode region-baseline-blend-only path (no `invent_primitives`) at
|
||
/// coarse (region-scale, ≥205 km) spacings, plus a realistic full-orbital-frame
|
||
/// extrapolation (1600×900 canvas). This is the number the design doc's §4/§7
|
||
/// planetary-rung cost story rested on as an UNMEASURED extrapolation —
|
||
/// this test replaces "extrapolated from the uncut per-cell rate" with an
|
||
/// actually-measured orbital-path rate.
|
||
#[test]
|
||
#[ignore]
|
||
fn bench_derive_orbital_at_metres_region_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/sweep, same shape as the district/quarter sweeps above
|
||
|
||
println!("\n=== T-1152 orbital-rung derive_orbital_at_metres benchmark ===");
|
||
println!(
|
||
"grid: {grid_side}x{grid_side} = {} cells/sweep\n",
|
||
grid_side * grid_side
|
||
);
|
||
|
||
let region_m = scale::REGION_M as f64;
|
||
|
||
// Region spacing (204,800 m) — the coarsest named rung short of the
|
||
// planet-wide elastic seam (D-243).
|
||
let (elapsed, per_cell_ns) =
|
||
time_orbital_sweep(seed, "bench", ¶ms, &ta, &climate, grid_side, region_m);
|
||
println!(
|
||
"orbital, region spacing (204.8km): {:>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
|
||
);
|
||
|
||
// Same spacing, for direct comparison: the FULL derive_at_metres path
|
||
// (invent_primitives included) at the SAME region spacing — quantifies
|
||
// exactly what skipping invention buys, at the spacing where it matters.
|
||
let (elapsed_full, per_cell_ns_full) = time_derive_sweep(
|
||
seed, "bench", ¶ms, &ta, &climate, grid_side, region_m, 0.0,
|
||
);
|
||
println!(
|
||
"district-mode (full derive_at_metres) at region spacing: {:>8.2} ms total, {:>7.1} ns/cell ({:.3} µs/cell)",
|
||
elapsed_full.as_secs_f64() * 1000.0,
|
||
per_cell_ns_full,
|
||
per_cell_ns_full / 1000.0
|
||
);
|
||
println!(
|
||
"orbital speedup vs. full derive at the same spacing: {:.2}x\n",
|
||
per_cell_ns_full / per_cell_ns
|
||
);
|
||
|
||
// Realistic full-orbital-frame estimate: a 1600x900 canvas at
|
||
// ~1-2 px/cell equivalents (design doc §4's worked example resolution
|
||
// class). Single-thread extrapolation from the MEASURED per-cell rate —
|
||
// labelled as an extrapolation, not claimed as independently measured at
|
||
// full canvas size (the parallel/chunked throughput is a SEPARATE
|
||
// measurement, T-1151's row-chunked par_iter, already landed and reused
|
||
// unchanged by the orbital rung's serving path — see the ticket report).
|
||
for (label, px_per_cell) in [("1 px/cell", 1u32), ("2 px/cell", 2u32)] {
|
||
let cols = 1600 / px_per_cell;
|
||
let rows = 900 / px_per_cell;
|
||
let cells = (cols as u64) * (rows as u64);
|
||
let est_ms = cells as f64 * per_cell_ns / 1e6;
|
||
println!(
|
||
"full-canvas 1600x900 @ {label} ({cols}x{rows} = {cells} cells): \
|
||
{est_ms:.1} ms single-thread (EXTRAPOLATED from the measured per-cell rate above)"
|
||
);
|
||
}
|
||
|
||
println!();
|
||
}
|
||
|
||
/// **T-1152 R1 — the number that actually governs interactive latency**, as
|
||
/// opposed to the full-canvas single-shot extrapolation above (which the
|
||
/// design doc's own carrier ruling makes moot — Jeroen's ruling is
|
||
/// progressive capped-density TILING, never a whole-canvas one-shot derive).
|
||
/// This measures a single served Region-granularity window tile through the
|
||
/// REAL production path (`build_district_window_layer`, including its
|
||
/// row-chunked `par_iter`, T-1151) at the wire-size cap — the same function
|
||
/// `serve_district_window`/`run_work_item`'s `DeriveWindow` arm calls, not a
|
||
/// hand-rolled sweep. This is the measured (not extrapolated) parallel
|
||
/// number the design doc's §7 flagged as missing ("no chunked-par_iter
|
||
/// benchmark has been run").
|
||
#[test]
|
||
#[ignore]
|
||
fn bench_served_region_window_tile_at_wire_cap() {
|
||
let hm = bench_hm();
|
||
let ta = bench_ta(&hm);
|
||
let rn = bench_river_network(&hm);
|
||
let params = bench_params();
|
||
let climate = ClimateConstants::default();
|
||
let seed = SeedChain::root(99).derive(SeedDomain::Body, 1);
|
||
|
||
println!("\n=== T-1152 served Region-window-tile benchmark (real production path) ===");
|
||
|
||
// The largest n the server will ever actually derive at Region
|
||
// granularity is DISTRICT_WINDOW_MAX_N_REGION, clamped further by
|
||
// clamp_window_n_v2 to the WIRE_CAP_CELLS ceiling — use the SAME
|
||
// capped n a real client's oversized request would resolve to.
|
||
let n = DISTRICT_WINDOW_MAX_N_REGION;
|
||
|
||
// Warm-up call (first call on a body pays no extra cost here since ta is
|
||
// already built — this just avoids counting one-time allocator warm-up
|
||
// noise in the timed sample).
|
||
let _ = build_district_window_layer(
|
||
seed,
|
||
"bench",
|
||
¶ms,
|
||
&ta,
|
||
&rn,
|
||
(0, 0),
|
||
n,
|
||
&climate,
|
||
WindowGranularity::Region,
|
||
0,
|
||
);
|
||
|
||
let iterations = 20;
|
||
let t0 = Instant::now();
|
||
let mut last_side = 0usize;
|
||
for _ in 0..iterations {
|
||
let layer = build_district_window_layer(
|
||
seed,
|
||
"bench",
|
||
¶ms,
|
||
&ta,
|
||
&rn,
|
||
(0, 0),
|
||
n,
|
||
&climate,
|
||
WindowGranularity::Region,
|
||
0,
|
||
);
|
||
last_side = (layer.morphology.len() as f64).sqrt().round() as usize;
|
||
std::hint::black_box(layer.elev_q.len());
|
||
}
|
||
let elapsed = t0.elapsed();
|
||
let per_call_ms = elapsed.as_secs_f64() * 1000.0 / iterations as f64;
|
||
|
||
println!(
|
||
"n={n} (DISTRICT_WINDOW_MAX_N_REGION), derived {last_side}x{last_side} region cells \
|
||
({} cells, WIRE_CAP_CELLS={WIRE_CAP_CELLS}):",
|
||
last_side * last_side
|
||
);
|
||
println!(
|
||
" {iterations} calls, {:.2} ms total, {per_call_ms:.3} ms/call \
|
||
(row-chunked par_iter, {} Rayon threads available)",
|
||
elapsed.as_secs_f64() * 1000.0,
|
||
std::thread::available_parallelism()
|
||
.map(|n| n.get())
|
||
.unwrap_or(0)
|
||
);
|
||
println!(
|
||
" compare: shipped district n=64 cap measures ~5 ms/call (design doc §7, MEASURED)\n"
|
||
);
|
||
}
|
||
|
||
/// **T-1170 Discipline item 2 (mandatory): course-cost bench.** Window
|
||
/// derive with courses on vs. off, at District granularity, real cap `n=64`
|
||
/// — the shape Tyre's cost probe measured (+0.09-0.21 ms against a ~5 ms
|
||
/// baseline, under 5%). "Off" uses an empty `RiverNetwork` (zero edges to
|
||
/// invent, exactly the pre-T-1170 cost shape); "on" uses a real body with
|
||
/// genuine river geometry (GJ1c) so the course inventor's Stage A/B pipeline
|
||
/// actually runs for the edges that cull into the window, not a synthetic
|
||
/// gradient body that might have zero river cells at all.
|
||
#[test]
|
||
#[ignore]
|
||
fn bench_course_cost_on_vs_off() {
|
||
use settled_reach_server::atlas::body_world_state::RiverNetwork;
|
||
use settled_reach_server::atlas::drainage;
|
||
use settled_reach_server::atlas::heightmap::load_heightmap_png;
|
||
|
||
let src = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||
.join("../wiki/star-systems/GJ-1/bodies/GJ1c/heightmap.png");
|
||
let heightmap = load_heightmap_png(&src, "GJ1c", 0.3).expect("decode committed GJ1c heightmap");
|
||
let small = heightmap.downsample(512, 256); // GRID_W x GRID_H, the real production working grid
|
||
let dr = drainage::analyze(&small.data, small.width, small.height, small.sea_level);
|
||
let ta = TerrainAnalysis::analyze(&small, &dr);
|
||
let rn_on = &dr.river_network;
|
||
let rn_off = RiverNetwork::default();
|
||
assert!(
|
||
!rn_on.river_cells.is_empty(),
|
||
"GJ1c at production working resolution must have river cells for this bench to be meaningful"
|
||
);
|
||
|
||
let params = BodyParams {
|
||
hydrosphere: Some("ocean".into()),
|
||
atmosphere: Some("breathable".into()),
|
||
planet_class: Some("temperate".into()),
|
||
body_radius_km: Some(6371.0),
|
||
..Default::default()
|
||
};
|
||
let climate = ClimateConstants::default();
|
||
let seed = SeedChain::root(0xC0FFEE_u64).derive(SeedDomain::Body, 7);
|
||
let n = DISTRICT_WINDOW_MAX_N; // the real n=64 shipped cap
|
||
|
||
// Centre the window on a real river cell — a window at the world origin
|
||
// (unrelated to where GJ1c's rivers actually are) would cull EVERY edge
|
||
// out and measure nothing but baseline noise. Convert a real river cell
|
||
// to world metres (the SAME pixel_to_world_m formula
|
||
// `district_profile.rs` uses internally — inlined here since that
|
||
// function is `pub(crate)`, not reachable from an integration test),
|
||
// then to the DistrictPos the window centres on.
|
||
let river_cell = dr.river_network.river_cells[dr.river_network.river_cells.len() / 2];
|
||
let r_km = params.body_radius_km.unwrap();
|
||
let world_pos = (
|
||
river_cell.1 as f64 / ta.w as f64 * (std::f64::consts::TAU * r_km * 1000.0),
|
||
(river_cell.0 as f64 / (ta.h - 1) as f64 - 0.5) * (std::f64::consts::PI * r_km * 1000.0),
|
||
);
|
||
let center: (i32, i32) = (
|
||
(world_pos.0 / scale::DISTRICT_M as f64).floor() as i32,
|
||
(world_pos.1 / scale::DISTRICT_M as f64).floor() as i32,
|
||
);
|
||
|
||
println!("\n=== T-1170 course-cost bench (District, n={n}, real GJ1c river geometry) ===");
|
||
println!(" window centred at district {center:?} (river cell {river_cell:?})");
|
||
|
||
// Warm-up (allocator/cache warm, not counted).
|
||
let _ = build_district_window_layer(
|
||
seed,
|
||
"GJ1c",
|
||
¶ms,
|
||
&ta,
|
||
&rn_off,
|
||
center,
|
||
n,
|
||
&climate,
|
||
WindowGranularity::District,
|
||
0,
|
||
);
|
||
let _ = build_district_window_layer(
|
||
seed,
|
||
"GJ1c",
|
||
¶ms,
|
||
&ta,
|
||
rn_on,
|
||
center,
|
||
n,
|
||
&climate,
|
||
WindowGranularity::District,
|
||
0,
|
||
);
|
||
|
||
let iterations = 1000; // higher count than the other benches — window cost here is ~1 ms, noisy at low n
|
||
|
||
let t_off = Instant::now();
|
||
for _ in 0..iterations {
|
||
let layer = build_district_window_layer(
|
||
seed,
|
||
"GJ1c",
|
||
¶ms,
|
||
&ta,
|
||
&rn_off,
|
||
center,
|
||
n,
|
||
&climate,
|
||
WindowGranularity::District,
|
||
0,
|
||
);
|
||
std::hint::black_box(layer.morphology.len());
|
||
}
|
||
let elapsed_off = t_off.elapsed();
|
||
let ms_off = elapsed_off.as_secs_f64() * 1000.0 / iterations as f64;
|
||
|
||
let t_on = Instant::now();
|
||
let mut courses_seen = 0usize;
|
||
for _ in 0..iterations {
|
||
let layer = build_district_window_layer(
|
||
seed,
|
||
"GJ1c",
|
||
¶ms,
|
||
&ta,
|
||
rn_on,
|
||
center,
|
||
n,
|
||
&climate,
|
||
WindowGranularity::District,
|
||
0,
|
||
);
|
||
courses_seen = layer.courses.len();
|
||
std::hint::black_box(layer.morphology.len());
|
||
}
|
||
let elapsed_on = t_on.elapsed();
|
||
let ms_on = elapsed_on.as_secs_f64() * 1000.0 / iterations as f64;
|
||
|
||
assert!(
|
||
courses_seen > 0,
|
||
"bench measured nothing meaningful — the window at {center:?} culled every edge out; \
|
||
re-pick a district position genuinely near GJ1c's river geometry"
|
||
);
|
||
|
||
let delta_pct = ((ms_on - ms_off) / ms_off) * 100.0;
|
||
|
||
println!(
|
||
" courses OFF (empty RiverNetwork): {:.3} ms/call ({iterations} calls, {:.2} ms total)",
|
||
ms_off,
|
||
elapsed_off.as_secs_f64() * 1000.0
|
||
);
|
||
println!(
|
||
" courses ON (real GJ1c network): {:.3} ms/call ({iterations} calls, {:.2} ms total, \
|
||
{courses_seen} courses in the n={n} window at {center:?})",
|
||
ms_on,
|
||
elapsed_on.as_secs_f64() * 1000.0
|
||
);
|
||
println!(" delta: {delta_pct:+.1}% (Discipline item 2 budget: < ~5%)\n");
|
||
}
|
||
|
||
#[test]
|
||
#[ignore]
|
||
fn bench_near_perennial_water_percell_isolated() {
|
||
use settled_reach_server::atlas::drainage;
|
||
use settled_reach_server::atlas::heightmap::load_heightmap_png;
|
||
use settled_reach_server::atlas::river_course;
|
||
|
||
let src = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||
.join("../wiki/star-systems/GJ-1/bodies/GJ1c/heightmap.png");
|
||
let heightmap = load_heightmap_png(&src, "GJ1c", 0.3).expect("decode heightmap");
|
||
let small = heightmap.downsample(512, 256);
|
||
let dr = drainage::analyze(&small.data, small.width, small.height, small.sea_level);
|
||
let ta = TerrainAnalysis::analyze(&small, &dr);
|
||
let params = BodyParams {
|
||
hydrosphere: Some("ocean".into()),
|
||
atmosphere: Some("breathable".into()),
|
||
planet_class: Some("temperate".into()),
|
||
body_radius_km: Some(6371.0),
|
||
..Default::default()
|
||
};
|
||
let seed = SeedChain::root(0xC0FFEE_u64).derive(SeedDomain::Body, 7);
|
||
|
||
let edges = river_course::build_edges(&dr.river_network);
|
||
let edge = &edges[edges.len() / 2];
|
||
let course = river_course::invent_course(seed, edge, &ta, ¶ms, 2048.0, 0.0);
|
||
let courses = vec![course];
|
||
|
||
let n = 4096u32;
|
||
let t0 = Instant::now();
|
||
let mut count = 0;
|
||
for i in 0..n {
|
||
let pos = (i as f64 * 100.0, i as f64 * 37.0);
|
||
if river_course::near_perennial_water(pos, &courses) {
|
||
count += 1;
|
||
}
|
||
}
|
||
let elapsed = t0.elapsed();
|
||
eprintln!(
|
||
"near_perennial_water: {:.3} ns/call ({n} calls, {} hits)",
|
||
elapsed.as_secs_f64() * 1e9 / n as f64,
|
||
count
|
||
);
|
||
|
||
// invent_course cost, isolated.
|
||
let t1 = Instant::now();
|
||
for _ in 0..100 {
|
||
let c = river_course::invent_course(seed, edge, &ta, ¶ms, 2048.0, 0.0);
|
||
std::hint::black_box(c.points.len());
|
||
}
|
||
eprintln!(
|
||
"invent_course: {:.3} us/call",
|
||
t1.elapsed().as_secs_f64() * 1e6 / 100.0
|
||
);
|
||
}
|