test(simulation): post-adversarial workshop benches — population survey, chunk rung, S2 density, global tier
hydrology_equilibrium_bench: bench_population_survey_all_committed_bodies (all 267 real heightmaps solved independently — zero carved cells population- wide, ~0.86s total, byte-exact determinism; closes Troblum B1) + bench_per_basin_size_distribution_real_population (22,270 basins: dense wins 100% for lake carriers). bmv_gridunit_bench: chunk-64m per-cell + deep-step canvas benches (1,838 ns/cell, ~1.7s full 4K; Option D's missing row) + S2 courses-density benches (+38-87% at chunk/block, mechanism traced to station spacing scaling with rung cutoff). bmv_global_tier_bench (new): real per-body radii from systems.db via BodyParamsReader — global tier ~8.85MB PNG across the population (supersedes the ~174MB mis-priced figure), rung-0 derive ~16-21ms/body measured. All #[ignore]d release tests, stability re-run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,351 @@
|
||||
//! Body Map Viewer workshop — global-tier byte-cost recheck (Jeroen's
|
||||
//! post-ratification correction to the ladder top).
|
||||
//!
|
||||
//! Jeroen's correction: GLOBAL is rung 0, the body-surface opener, with a
|
||||
//! **VARIABLE canvas = the body's own region grid** (one gridunit PER
|
||||
//! REGION, `regions_per_equator × regions_per_equator/2`, D-243's elastic
|
||||
//! seam) — NOT a fixed 3840×2160 District-spacing canvas the way the round-2
|
||||
//! cache-tier spec's ~174 MB figure assumed. REGION is rung 1, the largest
|
||||
//! FIXED-size rung (viewport-sized, evictable like every other sub-global
|
||||
//! rung). This file recomputes the always-keep global-tier byte budget
|
||||
//! against the CORRECT rung-0 shape, using REAL per-body radii read from
|
||||
//! `systems.db` via [`settled_reach_server::atlas::body_params_reader::BodyParamsReader`]
|
||||
//! (never raw `sqlite3` — the project's asset-pipeline rule) — not an
|
||||
//! assumed Earth-class uniform radius, since region-grid size is the ONE
|
||||
//! per-body-floating quantity in the whole D-243 ladder (the elastic seam)
|
||||
//! and the whole point of this recheck is that assuming Earth-class for
|
||||
//! every body was the error being corrected.
|
||||
//!
|
||||
//! Run: `cargo test --release --test bmv_global_tier_bench -- --ignored --nocapture`
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::time::Instant;
|
||||
|
||||
use settled_reach_server::atlas::body_params_reader::BodyParamsReader;
|
||||
use settled_reach_server::atlas::district_profile::{
|
||||
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::scale;
|
||||
use settled_reach_server::seed::{SeedChain, SeedDomain};
|
||||
|
||||
/// Same body-discovery walk the T-1177 population survey bench
|
||||
/// (`hydrology_equilibrium_bench.rs::bench_population_survey_all_committed_bodies`)
|
||||
/// already established — every committed `heightmap.png`'s parent directory
|
||||
/// name is a real body_id. Reused here (not re-invented) so this bench's
|
||||
/// population is exactly the same 267-body set the hydrology survey already
|
||||
/// covers, for direct comparability.
|
||||
fn discover_body_ids() -> Vec<String> {
|
||||
let wiki_root =
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../wiki/star-systems");
|
||||
let mut ids = Vec::new();
|
||||
fn walk(dir: &std::path::Path, out: &mut Vec<String>) {
|
||||
let Ok(entries) = std::fs::read_dir(dir) else {
|
||||
return;
|
||||
};
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
walk(&path, out);
|
||||
} else if path.file_name().and_then(|n| n.to_str()) == Some("heightmap.png") {
|
||||
if let Some(body_id) = path
|
||||
.parent()
|
||||
.and_then(|p| p.file_name())
|
||||
.and_then(|n| n.to_str())
|
||||
{
|
||||
out.push(body_id.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
walk(&wiki_root, &mut ids);
|
||||
ids.sort();
|
||||
ids
|
||||
}
|
||||
|
||||
/// The global/rung-0 canvas cell count for a body of the given radius:
|
||||
/// `regions_per_equator(R) × (regions_per_equator(R) / 2)` — one gridunit
|
||||
/// PER REGION (Jeroen's ruling), pole-to-pole being half the equatorial
|
||||
/// count per [`scale::regions_per_equator`]'s own doc.
|
||||
fn rung0_cells(body_radius_km: f64) -> u64 {
|
||||
let cols = scale::regions_per_equator(body_radius_km) as u64;
|
||||
let rows = (cols / 2).max(1);
|
||||
cols * rows
|
||||
}
|
||||
|
||||
/// T-1179's measured PNG-per-field rate: 638,382 bytes / 331,776 cells at
|
||||
/// district spacing (the only body+field-set this workshop measured a real
|
||||
/// encode on) = 1.924 bytes/cell PNG-encoded, six dense fields
|
||||
/// (morphology/elev_q/temp_dc/moisture_q/vegetation/glaciation). Applied
|
||||
/// here as the SAME field-set/encoding assumption the round-2 cache-tier
|
||||
/// spec used for the (wrong-shape) 174 MB estimate — this bench corrects the
|
||||
/// CANVAS SHAPE, not the per-cell wire-cost model, so the two numbers are
|
||||
/// comparable apples-to-apples on the encoding axis and differ only on the
|
||||
/// canvas-shape axis Jeroen actually corrected.
|
||||
const PNG_BYTES_PER_CELL: f64 = 638_382.0 / 331_776.0;
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn bench_global_tier_bytes_real_population() {
|
||||
let systems_db =
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("data/systems.db");
|
||||
let reader = BodyParamsReader::open(&systems_db)
|
||||
.expect("open read-only systems.db (asset-pipeline golden rule: read-only snapshot)");
|
||||
|
||||
let body_ids = discover_body_ids();
|
||||
assert!(
|
||||
body_ids.len() > 200,
|
||||
"expected the real committed body population (~267), found {}",
|
||||
body_ids.len()
|
||||
);
|
||||
|
||||
let mut found = 0usize;
|
||||
let mut missing_radius: Vec<String> = Vec::new();
|
||||
let mut total_cells: u64 = 0;
|
||||
let mut per_body: Vec<(String, f64, u64)> = Vec::new(); // (body_id, radius_km, cells)
|
||||
|
||||
for body_id in &body_ids {
|
||||
match reader.read_body_params(body_id) {
|
||||
Ok(params) => {
|
||||
if let Some(r_km) = params.body_radius_km {
|
||||
let cells = rung0_cells(r_km);
|
||||
total_cells += cells;
|
||||
per_body.push((body_id.clone(), r_km, cells));
|
||||
found += 1;
|
||||
} else {
|
||||
missing_radius.push(body_id.clone());
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
missing_radius.push(format!("{body_id} ({e})"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
per_body.sort_by(|a, b| b.2.cmp(&a.2)); // largest region-grid first
|
||||
|
||||
let total_bytes_raw6 = total_cells as f64 * 6.0; // 6 raw bytes/cell, DistrictWindowLayer's own documented figure
|
||||
let total_bytes_png = total_cells as f64 * PNG_BYTES_PER_CELL;
|
||||
|
||||
println!("\n=== Global-tier (rung 0) byte-cost recheck: REAL population, REAL per-body radii ===");
|
||||
println!(
|
||||
" bodies discovered: {}, radius found: {found}, missing/unreadable: {}",
|
||||
body_ids.len(),
|
||||
missing_radius.len()
|
||||
);
|
||||
if !missing_radius.is_empty() {
|
||||
println!(
|
||||
" missing radius (excluded from total, listed for audit): {:?}",
|
||||
&missing_radius[..missing_radius.len().min(10)]
|
||||
);
|
||||
if missing_radius.len() > 10 {
|
||||
println!(" ... and {} more", missing_radius.len() - 10);
|
||||
}
|
||||
}
|
||||
println!("\n total rung-0 cells across population: {total_cells}");
|
||||
println!(
|
||||
" average cells/body: {:.0}",
|
||||
total_cells as f64 / found.max(1) as f64
|
||||
);
|
||||
println!(
|
||||
"\n total bytes, raw 6 B/cell (DistrictWindowLayer's documented rate): {:.2} MB",
|
||||
total_bytes_raw6 / 1_048_576.0
|
||||
);
|
||||
println!(
|
||||
" total bytes, PNG-per-field ({:.3} B/cell, T-1179's measured rate): {:.2} MB",
|
||||
PNG_BYTES_PER_CELL,
|
||||
total_bytes_png / 1_048_576.0
|
||||
);
|
||||
|
||||
println!("\n Top 10 bodies by rung-0 cell count (largest global canvases):");
|
||||
for (body_id, r_km, cells) in per_body.iter().take(10) {
|
||||
let cols = scale::regions_per_equator(*r_km);
|
||||
println!(
|
||||
" {body_id:>12} radius={r_km:>8.1}km regions_per_equator={cols:>4} cells={cells:>7} ({:.1} KB PNG)",
|
||||
*cells as f64 * PNG_BYTES_PER_CELL / 1024.0
|
||||
);
|
||||
}
|
||||
println!("\n Bottom 5 bodies by rung-0 cell count (smallest global canvases):");
|
||||
for (body_id, r_km, cells) in per_body.iter().rev().take(5) {
|
||||
let cols = scale::regions_per_equator(*r_km);
|
||||
println!(
|
||||
" {body_id:>12} radius={r_km:>8.1}km regions_per_equator={cols:>4} cells={cells:>7} ({:.1} KB PNG)",
|
||||
*cells as f64 * PNG_BYTES_PER_CELL / 1024.0
|
||||
);
|
||||
}
|
||||
|
||||
// Earth-class reference point (R=6371km), for direct comparison against
|
||||
// the brief appendix's own "~195x98 Earth-sized" framing.
|
||||
let earth_cols = scale::regions_per_equator(6371.0);
|
||||
let earth_rows = (earth_cols / 2).max(1);
|
||||
println!(
|
||||
"\n Reference: Earth-class (R=6371km) regions_per_equator={earth_cols}, rows={earth_rows}, cells={}",
|
||||
earth_cols as u64 * earth_rows as u64
|
||||
);
|
||||
|
||||
println!();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Rung-0 (GLOBAL opener) derive cost — MEASURED, not extrapolated (Tyre's
|
||||
// bracket request). The existing `bench_derive_orbital_at_metres_region_spacing`
|
||||
// (zoom_ladder_bench.rs) measures `derive_orbital_at_metres`'s per-cell rate
|
||||
// at a fixed 4,096-cell (64x64) sweep — that number is real, but scaling it
|
||||
// up to a whole rung-0 canvas by multiplication is exactly the kind of
|
||||
// "EXTRAPOLATED from the measured per-cell rate" label that bench's own
|
||||
// full-canvas rows already carry (explicitly disclosed there, not measured).
|
||||
// This bench instead runs the REAL per-body canvas shape (the actual
|
||||
// regions_per_equator(R) x regions_per_equator(R)/2 grid, real per-body
|
||||
// radius) end-to-end for a representative sample of real bodies, so the
|
||||
// rung-0 derive-cost figure is measured at the shape it will actually be
|
||||
// served at, not inferred from a differently-shaped sweep.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
fn bench_hm() -> BodyHeightmap {
|
||||
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)
|
||||
}
|
||||
|
||||
/// Real rung-0 canvas derive cost at three representative real body sizes
|
||||
/// from the population (smallest, Earth-class-nearest, largest), single
|
||||
/// call per cell through the actual `derive_orbital_at_metres` function —
|
||||
/// no square-sweep proxy shape, the real `cols x rows` extent each body's
|
||||
/// canvas would actually be.
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn bench_rung0_derive_cost_real_canvas_shapes() {
|
||||
let hm = bench_hm();
|
||||
let ta = bench_ta(&hm);
|
||||
let params = BodyParams {
|
||||
hydrosphere: Some("ocean".into()),
|
||||
atmosphere: Some("breathable".into()),
|
||||
planet_class: Some("temperate".into()),
|
||||
body_radius_km: Some(6371.0), // overridden per-case below
|
||||
..Default::default()
|
||||
};
|
||||
let climate = ClimateConstants::default();
|
||||
let seed = SeedChain::root(99).derive(SeedDomain::Body, 1);
|
||||
let region_m = scale::REGION_M as f64;
|
||||
|
||||
println!("\n=== Rung-0 (GLOBAL opener) derive cost — REAL canvas shapes, MEASURED not extrapolated ===\n");
|
||||
|
||||
// Representative cases from the real population survey
|
||||
// (bmv_global_tier_bench.rs::bench_global_tier_bytes_real_population):
|
||||
// smallest (GJ784c-m1, a moon), Earth-class reference, largest (GJ325Ac).
|
||||
let cases: [(&str, f64); 3] = [
|
||||
("GJ784c-m1 (smallest, moon)", 733.9),
|
||||
("Earth-class reference", 6371.0),
|
||||
("GJ325Ac (largest)", 7317.6),
|
||||
];
|
||||
|
||||
// Per-body average cell count across the real population (from the
|
||||
// sibling bench in this file): 18,073 cells/body, 267 bodies.
|
||||
const AVG_CELLS_PER_BODY: u64 = 18_073;
|
||||
const BODY_COUNT: u64 = 267;
|
||||
|
||||
for (label, r_km) in cases {
|
||||
let cols = scale::regions_per_equator(r_km);
|
||||
let rows = (cols / 2).max(1);
|
||||
let cells = (cols as u64) * (rows as u64);
|
||||
let mut body_params = params.clone();
|
||||
body_params.body_radius_km = Some(r_km);
|
||||
|
||||
let t0 = Instant::now();
|
||||
for row in 0..rows {
|
||||
for col in 0..cols {
|
||||
let wx = col as f64 * region_m;
|
||||
let wy = row as f64 * region_m;
|
||||
let prof =
|
||||
derive_orbital_at_metres(seed, "bench", &body_params, &ta, wx, wy, &climate);
|
||||
std::hint::black_box(prof.elev_q);
|
||||
}
|
||||
}
|
||||
let elapsed = t0.elapsed();
|
||||
let ms = elapsed.as_secs_f64() * 1000.0;
|
||||
let ns_per_cell = elapsed.as_secs_f64() * 1e9 / cells as f64;
|
||||
println!(
|
||||
" {label:<28}: radius={r_km:>8.1}km cols={cols:>4} rows={rows:>4} cells={cells:>6}: \
|
||||
{ms:>8.3} ms single-thread, {ns_per_cell:>7.1} ns/cell"
|
||||
);
|
||||
}
|
||||
|
||||
// All-267-bodies single-thread total, using the MEASURED per-cell rate
|
||||
// from the Earth-class case above (representative — T-1178/T-1154 already
|
||||
// established this per-cell rate is flat across canvas size) applied to
|
||||
// the REAL total cell count across the population (4,825,615, from the
|
||||
// sibling bench), not the average-cells-per-body figure multiplied out
|
||||
// blindly.
|
||||
let (_, earth_r_km) = cases[1];
|
||||
let earth_cols = scale::regions_per_equator(earth_r_km);
|
||||
let earth_rows = (earth_cols / 2).max(1);
|
||||
let earth_cells = (earth_cols as u64) * (earth_rows as u64);
|
||||
let mut earth_body_params = params.clone();
|
||||
earth_body_params.body_radius_km = Some(earth_r_km);
|
||||
let t0 = Instant::now();
|
||||
for row in 0..earth_rows {
|
||||
for col in 0..earth_cols {
|
||||
let wx = col as f64 * region_m;
|
||||
let wy = row as f64 * region_m;
|
||||
let prof = derive_orbital_at_metres(
|
||||
seed,
|
||||
"bench",
|
||||
&earth_body_params,
|
||||
&ta,
|
||||
wx,
|
||||
wy,
|
||||
&climate,
|
||||
);
|
||||
std::hint::black_box(prof.elev_q);
|
||||
}
|
||||
}
|
||||
let earth_elapsed = t0.elapsed();
|
||||
let earth_ns_per_cell = earth_elapsed.as_secs_f64() * 1e9 / earth_cells as f64;
|
||||
|
||||
let total_cells_all_267 = 4_825_615u64; // measured directly in bench_global_tier_bytes_real_population
|
||||
let total_ms_all_267_singlethread = total_cells_all_267 as f64 * earth_ns_per_cell / 1e6;
|
||||
|
||||
println!(
|
||||
"\n Reference per-cell rate used for population total (Earth-class case, MEASURED above): {earth_ns_per_cell:.1} ns/cell"
|
||||
);
|
||||
println!(
|
||||
" Real per-body average (this file's population bench): {AVG_CELLS_PER_BODY} cells/body x {BODY_COUNT} bodies = {} total cells",
|
||||
AVG_CELLS_PER_BODY * BODY_COUNT
|
||||
);
|
||||
println!(
|
||||
" ALL 267 real bodies' rung-0 canvases, single-thread total (measured rate x real total cell count 4,825,615): {total_ms_all_267_singlethread:.1} ms = {:.3} s",
|
||||
total_ms_all_267_singlethread / 1000.0
|
||||
);
|
||||
println!(
|
||||
" Note: this is a ONE-TIME cost PER BODY (rung-0 solved once per body, held in the \
|
||||
keep-always cache per Tier 1) — never a per-request or per-frame cost, and never all \
|
||||
267 bodies solved in one synchronous batch in production (each body's rung-0 canvas \
|
||||
is populated lazily on that body's first Atlas-open, same as the existing D-206 \
|
||||
background-queue population path). The all-267-summed total above prices the \
|
||||
worst-case ceiling (every body opened once, back to back, single-threaded); the \
|
||||
REAL per-body cost that matters for a single Atlas-open is the individual-body rows \
|
||||
above (~16-21 ms single-thread for one body's rung-0 canvas — trivially interactive, \
|
||||
parallelizes further if ever needed the same way T-1177's per-body hydrology solves do)."
|
||||
);
|
||||
println!();
|
||||
}
|
||||
@@ -121,6 +121,7 @@ use settled_reach_server::atlas::drainage;
|
||||
use settled_reach_server::atlas::features::TerrainAnalysis;
|
||||
use settled_reach_server::atlas::heightmap::{load_heightmap_png, BodyHeightmap};
|
||||
use settled_reach_server::atlas::layer_proxy::{build_district_window_layer, WindowGranularity};
|
||||
use settled_reach_server::atlas::river_course::{self, InventedCourse};
|
||||
use settled_reach_server::atlas::scale;
|
||||
use settled_reach_server::seed::{SeedChain, SeedDomain};
|
||||
|
||||
@@ -851,3 +852,393 @@ fn bench_block_cutoff_confirms_savings() {
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Interview-2 redirect: chunk (64 m) — the never-benched rung, now the
|
||||
// proposed deepest Atlas ladder rung (Jeroen, interview 2: "the actual tile
|
||||
// level rung seems unusable. maybe replace with 64?" — tile/voxel dropped
|
||||
// from the Atlas ladder; chunk becomes the bottom). Same discipline as the
|
||||
// original T-1154 benches above: call `derive_at_metres` directly (no
|
||||
// wire-facing cutoff band exists below Quarter's 1,024 m `MIN_WL_BANDS_M`
|
||||
// floor either), matched cutoff = spacing (Nyquist), 4,096-cell sweep for
|
||||
// direct comparability with the existing Block/Tile row above, plus a
|
||||
// realistic deep-step VIEWPORT canvas at chunk spacing (replacing the old
|
||||
// 216x384m/1m-spacing deep-step bench, which measured the now-dropped tile
|
||||
// rung).
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Chunk (64 m) per-cell derive cost, matched to the existing Block/Tile
|
||||
/// 4,096-cell sweep shape (`bench_block_and_tile_spacing_4096_cells` above)
|
||||
/// so this row slots directly into the same comparison table. This is the
|
||||
/// ONE rung on the D-243 ladder nobody had measured before interview 2 (T-1154
|
||||
/// tested Block 128m and Tile-adjacent 1m/4m; chunk's 64m spacing sits
|
||||
/// between them and was never run). `VOXEL_OCTAVE_WAVELENGTHS_M`'s finest
|
||||
/// entry is 128m (`detail_scatter.rs:46`), so per the SAME cutoff-mechanism
|
||||
/// finding `bench_block_cutoff_confirms_savings` already established for
|
||||
/// Block, a cutoff at 64m (finer than every entry in that array) should
|
||||
/// truncate nothing either — this bench CONFIRMS that expectation for chunk
|
||||
/// specifically rather than assuming it transfers from Block's own result.
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn bench_chunk_spacing_4096_cells() {
|
||||
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 — matches the Block/Tile sweep above
|
||||
|
||||
println!("\n=== Interview-2: chunk (64m) spacing derive_at_metres benchmark (4,096-cell sweep) ===");
|
||||
println!(
|
||||
"grid: {grid_side}x{grid_side} = {} cells/sweep\n",
|
||||
grid_side * grid_side
|
||||
);
|
||||
|
||||
let chunk_m = scale::CHUNK_M as f64; // 64 m
|
||||
let block_m = scale::BLOCK_M as f64; // 128 m, for direct side-by-side
|
||||
|
||||
for (label, step_m, cutoff_m) in [
|
||||
("chunk (64m), cutoff=64m", chunk_m, chunk_m),
|
||||
("chunk (64m), UNCUT (cutoff=0)", chunk_m, 0.0),
|
||||
("block (128m), cutoff=128m [reference row]", block_m, block_m),
|
||||
] {
|
||||
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, "bench", ¶ms, &ta, wx, wy, &climate, cutoff_m, &[]);
|
||||
std::hint::black_box(prof.elev_q);
|
||||
}
|
||||
}
|
||||
let elapsed = t0.elapsed();
|
||||
let per_cell_ns = elapsed.as_secs_f64() * 1e9 / n_cells as f64;
|
||||
println!(
|
||||
" {label:<44}: {:>8.2} ms total, {:>7.1} ns/cell ({:.3} us/cell)",
|
||||
elapsed.as_secs_f64() * 1000.0,
|
||||
per_cell_ns,
|
||||
per_cell_ns / 1000.0
|
||||
);
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
/// The NEW realistic deepest-step canvas — chunk (64 m) spacing replacing
|
||||
/// the dropped tile (1 m) rung. Jeroen's interview-2 ruling: the old
|
||||
/// 10px/tile bottom-out ("full 1920 screen at 10px/tile shows ~192x108m") is
|
||||
/// in-world viewport territory (Phase 5), not Atlas map content — chunk (64
|
||||
/// m, D-243's "stream/derive unit") is the new floor.
|
||||
///
|
||||
/// Display-band derivation for the new bottom-out, stated precisely (this is
|
||||
/// the number Tyre's amendment text needs): at 1x1 px-per-gridunit (the
|
||||
/// workshop's own ideal ratio, premise 5), a 3840x2160 canvas at 64 m
|
||||
/// spacing covers `2160 * 64 = 138,240 m` (~138.2 km) on the smaller axis and
|
||||
/// `3840 * 64 = 245,760 m` (~245.8 km) on the larger axis — i.e. the
|
||||
/// bottom-out is "1 screen px per 64m gridunit", NOT "10 px per chunk" (10
|
||||
/// px/gridunit would need a canvas 10x larger in EXTENT for the same pixel
|
||||
/// budget, which no longer makes sense once chunk stands in for what tile's
|
||||
/// 10x margin was there to buy: legibility of a 1m ground feature at low
|
||||
/// pixel density; chunk itself IS the smallest legible Atlas content unit
|
||||
/// now, so it wants 1x1, not a magnification margin on top of 1x1). Full
|
||||
/// working: `canvas_px / gridunit_spacing_m = world_extent_m` per axis (the
|
||||
/// same arithmetic the old tile bottom-out used, just at 64m instead of 1m
|
||||
/// and without the 10x margin factor tile's own screen-legibility problem
|
||||
/// needed). This bench uses the FULL 3840x2160 canvas at 1x1 (matching every
|
||||
/// other rung's fixed-px-budget convention, per round 2 §(c) — chunk is the
|
||||
/// first deepest rung that does NOT need the display-ratio-sized-canvas
|
||||
/// exception the old tile rung required, precisely because it's not
|
||||
/// undersized relative to a legibility margin the way 1m/10px was).
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn bench_chunk_deep_step_realistic_canvas() {
|
||||
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);
|
||||
|
||||
// Full 3840x2160 canvas, 1 gridunit per screen px, 64 m spacing.
|
||||
let cols = 3840u32;
|
||||
let rows = 2160u32;
|
||||
let cells = (rows as u64) * (cols as u64);
|
||||
let step_m = scale::CHUNK_M as f64; // 64 m
|
||||
let cutoff_m = step_m; // Nyquist-matched
|
||||
|
||||
let world_w_km = cols as f64 * step_m / 1000.0;
|
||||
let world_h_km = rows as f64 * step_m / 1000.0;
|
||||
|
||||
println!("\n=== Interview-2: chunk (64m) deep-step realistic-canvas bench (3840x2160 @ 1x1 px/gridunit) ===");
|
||||
println!(
|
||||
" geometry: 3840x2160 canvas @ 64m/gridunit, 1x1 px-per-gridunit -> {world_w_km:.1} km x {world_h_km:.1} km world extent"
|
||||
);
|
||||
println!(" cells = 3840 * 2160 = {cells}");
|
||||
println!(" {}\n", rayon_threads_report());
|
||||
|
||||
let (elapsed_par, ns_per_cell_par) = rect_window_replica(
|
||||
seed, "bench", ¶ms, &ta, &climate, cols, rows, step_m, cutoff_m,
|
||||
);
|
||||
println!(
|
||||
" PARALLEL (row-chunked): {:.2} ms, {:.1} ns/cell ({:.3} us/cell)",
|
||||
elapsed_par.as_secs_f64() * 1000.0,
|
||||
ns_per_cell_par,
|
||||
ns_per_cell_par / 1000.0
|
||||
);
|
||||
|
||||
let pool = rayon::ThreadPoolBuilder::new()
|
||||
.num_threads(1)
|
||||
.build()
|
||||
.expect("build single-thread rayon pool");
|
||||
let (elapsed_seq, ns_per_cell_seq) = pool.install(|| {
|
||||
rect_window_replica(
|
||||
seed, "bench", ¶ms, &ta, &climate, cols, rows, step_m, cutoff_m,
|
||||
)
|
||||
});
|
||||
println!(
|
||||
" SINGLE-THREAD: {:.2} ms, {:.1} ns/cell ({:.3} us/cell)",
|
||||
elapsed_seq.as_secs_f64() * 1000.0,
|
||||
ns_per_cell_seq,
|
||||
ns_per_cell_seq / 1000.0
|
||||
);
|
||||
println!(
|
||||
" speedup: {:.2}x\n",
|
||||
elapsed_seq.as_secs_f64() / elapsed_par.as_secs_f64()
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// S2 (ruled: before filing) — deep-step x high-river-density COURSES-INCLUSIVE
|
||||
// cost. The last zero-data-point cell: every courses-inclusive number
|
||||
// measured so far (Cross-check 1 in the results doc, 18 courses/331,776
|
||||
// cells) is at District spacing. Nothing has measured courses-on cost at the
|
||||
// NEW deepest rung (chunk, 64m, per interview 2) or at Block (128m) — both
|
||||
// below District, where a real river window would have MORE edges in view
|
||||
// per unit area (finer spacing = smaller world extent per canvas, but a real
|
||||
// river network's edge density near a river is roughly constant per unit
|
||||
// ground area, so a narrower window can still contain a densely-braided
|
||||
// stretch). Builds real InventedCourse fixtures via the actual PUBLIC
|
||||
// invention pipeline (`river_course::build_edges` + `river_course::invent_course`
|
||||
// — both `pub`, unlike `layer_proxy::invent_courses_near_window` itself,
|
||||
// which is private to that module; this bench replicates its per-edge
|
||||
// invention loop using the same public primitives, same discipline as
|
||||
// `rect_window_replica` already replicates `build_district_window_layer`'s
|
||||
// internals elsewhere in this file).
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Build a high-density `InventedCourse` set from the REAL GJ1c river
|
||||
/// network — every edge whose invented course falls within
|
||||
/// `inflate_m` of the given world-metre window, at the given station
|
||||
/// spacing/cutoff. This is the densest REAL course set available in this
|
||||
/// repo (GJ1c is the only body with a river network already wired into a
|
||||
/// bench fixture) rather than a synthetic worst case — real geometry is
|
||||
/// preferred per this workshop's own measurement discipline (T-1178's cross-
|
||||
/// check pattern: synthetic first, then confirm on real geometry). Returns
|
||||
/// the course list plus the count found, so callers can report density
|
||||
/// alongside cost.
|
||||
fn build_gj1c_courses_near_window(
|
||||
seed: SeedChain,
|
||||
ta: &TerrainAnalysis,
|
||||
params: &BodyParams,
|
||||
river_network: &settled_reach_server::atlas::body_world_state::RiverNetwork,
|
||||
win_x0: f64,
|
||||
win_y0: f64,
|
||||
win_x1: f64,
|
||||
win_y1: f64,
|
||||
station_spacing_m: f64,
|
||||
min_wavelength_m: f64,
|
||||
) -> Vec<InventedCourse> {
|
||||
let edges = river_course::build_edges(river_network);
|
||||
let r_km = params.body_radius_km.expect("body_radius_km required");
|
||||
|
||||
// Inline the same pixel->world-metres formula the existing GJ1c
|
||||
// cross-check bench above already uses (district_profile::pixel_to_world_m
|
||||
// is `pub(crate)`, not reachable from an integration test — this is the
|
||||
// SAME formula, inlined, not a different one; consistent with how
|
||||
// `bench_square_window_production_fn_gj1c_real_body_crosscheck` and
|
||||
// zoom_ladder_bench.rs's `bench_course_cost_on_vs_off` already do this).
|
||||
let to_world = |row: u16, col: u16| -> (f64, f64) {
|
||||
(
|
||||
col as f64 / ta.w as f64 * (std::f64::consts::TAU * r_km * 1000.0),
|
||||
(row as f64 / (ta.h - 1) as f64 - 0.5) * (std::f64::consts::PI * r_km * 1000.0),
|
||||
)
|
||||
};
|
||||
|
||||
let mut courses = Vec::new();
|
||||
for edge in &edges {
|
||||
let anchor_a = to_world(edge.upstream.0, edge.upstream.1);
|
||||
let anchor_b = to_world(edge.downstream.0, edge.downstream.1);
|
||||
let chord_m = ((anchor_a.0 - anchor_b.0).powi(2) + (anchor_a.1 - anchor_b.1).powi(2)).sqrt();
|
||||
// Same 0.08 inflation fraction layer_proxy.rs's COURSE_BBOX_INFLATION_FRACTION
|
||||
// uses (that constant itself is private; the value is stated in its
|
||||
// own doc and reproduced here for the same bbox-cull purpose — a
|
||||
// bench-local approximation of the real cull, not a claim of exact
|
||||
// production parity for the cull step itself, which doesn't affect
|
||||
// measured PER-CELL cost once a course is in the list).
|
||||
let inflate_m = chord_m * 0.08;
|
||||
let (bx0, bx1) = (
|
||||
anchor_a.0.min(anchor_b.0) - inflate_m,
|
||||
anchor_a.0.max(anchor_b.0) + inflate_m,
|
||||
);
|
||||
let (by0, by1) = (
|
||||
anchor_a.1.min(anchor_b.1) - inflate_m,
|
||||
anchor_a.1.max(anchor_b.1) + inflate_m,
|
||||
);
|
||||
if bx1 < win_x0 || bx0 > win_x1 || by1 < win_y0 || by0 > win_y1 {
|
||||
continue;
|
||||
}
|
||||
courses.push(river_course::invent_course(
|
||||
seed,
|
||||
edge,
|
||||
ta,
|
||||
params,
|
||||
station_spacing_m,
|
||||
min_wavelength_m,
|
||||
));
|
||||
}
|
||||
courses
|
||||
}
|
||||
|
||||
/// S2: courses-on vs courses-off, at BOTH the new deepest rung (chunk, 64m)
|
||||
/// and Block (128m), over a window picked to maximize real river-edge
|
||||
/// density (the densest real region in the only river-network fixture this
|
||||
/// repo's benches have — GJ1c). Reports the delta as both absolute ms and
|
||||
/// percentage, matching `bench_course_cost_on_vs_off`'s own reporting shape
|
||||
/// (District's own courses-on-vs-off number: +0.09-0.21ms against a ~5ms
|
||||
/// baseline, under 5%) so this fills in the two remaining zero-data-point
|
||||
/// cells on the same comparison axis.
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn bench_s2_courses_density_at_chunk_and_block() {
|
||||
let (hm, ta) = gj1c_fixture();
|
||||
let dr = drainage::analyze(&hm.data, hm.width, hm.height, hm.sea_level);
|
||||
let rn = dr.river_network.clone();
|
||||
let params = bench_params();
|
||||
let climate = ClimateConstants::default();
|
||||
let seed = SeedChain::root(0xC0FFEE_u64).derive(SeedDomain::Body, 7);
|
||||
|
||||
assert!(
|
||||
!rn.river_cells.is_empty(),
|
||||
"GJ1c at production working resolution must have river cells for this bench to be meaningful"
|
||||
);
|
||||
|
||||
// Find the highest-density river region: scan river cells and pick the
|
||||
// one with the most OTHER river cells within a fixed pixel radius — a
|
||||
// proxy for confluence/braided density, maximizing edges-per-window
|
||||
// rather than picking an arbitrary river cell as the existing single
|
||||
// cross-check bench does.
|
||||
let radius_px = 8i64; // small radius = local confluence density, not just "near any river"
|
||||
let mut best_cell = rn.river_cells[0];
|
||||
let mut best_count = -1i64;
|
||||
for &cell in &rn.river_cells {
|
||||
let mut count = 0i64;
|
||||
for &other in &rn.river_cells {
|
||||
let dr_ = cell.0 as i64 - other.0 as i64;
|
||||
let dc_ = cell.1 as i64 - other.1 as i64;
|
||||
if dr_ * dr_ + dc_ * dc_ <= radius_px * radius_px {
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
if count > best_count {
|
||||
best_count = count;
|
||||
best_cell = cell;
|
||||
}
|
||||
}
|
||||
|
||||
let r_km = params.body_radius_km.expect("body_radius_km required");
|
||||
let to_world = |row: u16, col: u16| -> (f64, f64) {
|
||||
(
|
||||
col as f64 / ta.w as f64 * (std::f64::consts::TAU * r_km * 1000.0),
|
||||
(row as f64 / (ta.h - 1) as f64 - 0.5) * (std::f64::consts::PI * r_km * 1000.0),
|
||||
)
|
||||
};
|
||||
let center_world = to_world(best_cell.0, best_cell.1);
|
||||
|
||||
println!("\n=== S2: courses-on vs courses-off density bench (chunk 64m + block 128m, densest GJ1c river region) ===");
|
||||
println!(
|
||||
" densest river cell: {best_cell:?} ({best_count} river cells within {radius_px}px radius), world center {center_world:?}"
|
||||
);
|
||||
|
||||
for (label, step_m) in [("chunk (64m)", scale::CHUNK_M as f64), ("block (128m)", scale::BLOCK_M as f64)] {
|
||||
// Realistic-shape window at this spacing: 64x64 cells (4,096, matching
|
||||
// this file's other 4,096-cell sweeps for direct comparability).
|
||||
let grid_side = 64u32;
|
||||
let half_extent_m = (grid_side as f64 / 2.0) * step_m;
|
||||
let win_x0 = center_world.0 - half_extent_m;
|
||||
let win_x1 = center_world.0 + half_extent_m;
|
||||
let win_y0 = center_world.1 - half_extent_m;
|
||||
let win_y1 = center_world.1 + half_extent_m;
|
||||
|
||||
let courses = build_gj1c_courses_near_window(
|
||||
seed, &ta, ¶ms, &rn, win_x0, win_y0, win_x1, win_y1, step_m, step_m,
|
||||
);
|
||||
let total_points: usize = courses.iter().map(|c| c.points.len()).sum();
|
||||
let avg_points = if courses.is_empty() {
|
||||
0.0
|
||||
} else {
|
||||
total_points as f64 / courses.len() as f64
|
||||
};
|
||||
|
||||
println!(
|
||||
"\n --- {label}: {grid_side}x{grid_side} window ({:.0}m x {:.0}m), courses_in_window={}, avg_points_per_course={:.1} (station_spacing_m={step_m}) ---",
|
||||
half_extent_m * 2.0,
|
||||
half_extent_m * 2.0,
|
||||
courses.len(),
|
||||
avg_points
|
||||
);
|
||||
|
||||
let n_cells = (grid_side * grid_side) as u64;
|
||||
let iterations = 200; // higher rep count — a single 4,096-cell sweep is sub-10ms, noisy at n=1
|
||||
|
||||
// Courses OFF.
|
||||
let t_off = Instant::now();
|
||||
for _ in 0..iterations {
|
||||
for row in 0..grid_side {
|
||||
for col in 0..grid_side {
|
||||
let wx = win_x0 + col as f64 * step_m;
|
||||
let wy = win_y0 + row as f64 * step_m;
|
||||
let prof = derive_at_metres(
|
||||
seed, "GJ1c", ¶ms, &ta, wx, wy, &climate, step_m, &[],
|
||||
);
|
||||
std::hint::black_box(prof.elev_q);
|
||||
}
|
||||
}
|
||||
}
|
||||
let elapsed_off = t_off.elapsed();
|
||||
|
||||
// Courses ON.
|
||||
let t_on = Instant::now();
|
||||
for _ in 0..iterations {
|
||||
for row in 0..grid_side {
|
||||
for col in 0..grid_side {
|
||||
let wx = win_x0 + col as f64 * step_m;
|
||||
let wy = win_y0 + row as f64 * step_m;
|
||||
let prof = derive_at_metres(
|
||||
seed, "GJ1c", ¶ms, &ta, wx, wy, &climate, step_m, &courses,
|
||||
);
|
||||
std::hint::black_box(prof.elev_q);
|
||||
}
|
||||
}
|
||||
}
|
||||
let elapsed_on = t_on.elapsed();
|
||||
|
||||
let ms_off_per_sweep = elapsed_off.as_secs_f64() * 1000.0 / iterations as f64;
|
||||
let ms_on_per_sweep = elapsed_on.as_secs_f64() * 1000.0 / iterations as f64;
|
||||
let delta_ms = ms_on_per_sweep - ms_off_per_sweep;
|
||||
let delta_pct = 100.0 * delta_ms / ms_off_per_sweep;
|
||||
let ns_per_cell_off = elapsed_off.as_secs_f64() * 1e9 / (n_cells * iterations as u64) as f64;
|
||||
let ns_per_cell_on = elapsed_on.as_secs_f64() * 1e9 / (n_cells * iterations as u64) as f64;
|
||||
|
||||
println!(
|
||||
" courses OFF: {ms_off_per_sweep:.4} ms/sweep ({ns_per_cell_off:.1} ns/cell)"
|
||||
);
|
||||
println!(
|
||||
" courses ON: {ms_on_per_sweep:.4} ms/sweep ({ns_per_cell_on:.1} ns/cell)"
|
||||
);
|
||||
println!(
|
||||
" delta: {delta_ms:+.4} ms/sweep ({delta_pct:+.2}%), {} courses in window",
|
||||
courses.len()
|
||||
);
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
@@ -228,6 +228,17 @@ fn determinism_at_330k_cells() {
|
||||
/// solved across the Rayon pool, at the 512×256 production grid size —
|
||||
/// directly answering the workshop's red-flag-2-adjacent question of
|
||||
/// whether per-body-open hydrology is affordable at scale.
|
||||
///
|
||||
/// **Scope note (population-survey gap, closed by
|
||||
/// [`bench_population_survey_all_committed_bodies`] below):** this bench
|
||||
/// solves ONE real body (GJ1c) 273 times — it proves the *wall-clock*
|
||||
/// affordability of running 273 independent solves in parallel (the
|
||||
/// question it was built to answer), but the 273 solves are not 273
|
||||
/// *distinct* bodies, so it says nothing about how basin/endorheic/carved-
|
||||
/// cell distributions vary across the real body population. That is a
|
||||
/// different question, answered by the population survey, not this bench —
|
||||
/// left as-is (not rewritten) since it still correctly answers the
|
||||
/// question it was designed for.
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn bench_parallel_273_bodies_at_512x256() {
|
||||
@@ -258,3 +269,429 @@ fn bench_parallel_273_bodies_at_512x256() {
|
||||
elapsed.as_secs_f64() * 1000.0 / body_count as f64
|
||||
);
|
||||
}
|
||||
|
||||
/// **POPULATION SURVEY (post-adversarial, Troblum finding).** Loads and
|
||||
/// solves EVERY real committed heightmap PNG in the repo (267 distinct
|
||||
/// bodies as of this bench, `wiki/star-systems/*/bodies/*/heightmap.png`),
|
||||
/// each downsampled to the real 512×256 production working grid with its
|
||||
/// own PNG-embedded `sea_level`, run independently in parallel across the
|
||||
/// Rayon pool — the actual population-scale question
|
||||
/// [`bench_parallel_273_bodies_at_512x256`] does not answer (that bench
|
||||
/// solves ONE body 273 times, not 273 distinct bodies; see its doc comment
|
||||
/// above). This closes the gap: total wall time for the REAL population,
|
||||
/// and — the load-bearing part for the cliff Phase-4 ruling — the
|
||||
/// distribution of basins/endorheic-basins/carved-cliff-edge-cells ACROSS
|
||||
/// the population, not just on GJ1c.
|
||||
///
|
||||
/// **Known scope limit, stated explicitly (not hidden):** every body uses
|
||||
/// the SAME `ClimateInputs { moisture_q: 55 }` (`default_climate()`) — the
|
||||
/// module's own doc already states real per-body moisture requires wiring
|
||||
/// in `BodyWorldState.districts`/`regions` climate context, which is out of
|
||||
/// scope for this survey (same limitation the original T-1177 prototype
|
||||
/// documented, "No per-basin moisture/climate lookup"). This means the
|
||||
/// endorheic classification specifically is measured under a uniform
|
||||
/// climate assumption, not each body's real hydrosphere/atmosphere-derived
|
||||
/// moisture — a real per-body climate wiring pass could shift the
|
||||
/// endorheic counts. It does NOT limit the carved-cliff-edge finding below:
|
||||
/// carving depends on ELEVATION geometry (the narrow two-basin-saddle
|
||||
/// condition documented in the module), not on `moisture_q` at all — the
|
||||
/// climate input only gates the endorheic/overflow classification of an
|
||||
/// already-identified basin, never whether carving occurs.
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn bench_population_survey_all_committed_bodies() {
|
||||
use rayon::prelude::*;
|
||||
use settled_reach_server::atlas::hydrology_equilibrium::{BasinOutcome, DownstreamTarget};
|
||||
|
||||
let wiki_root = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../wiki/star-systems");
|
||||
|
||||
// Discover every committed heightmap PNG (deterministic ordering: sort
|
||||
// by path so the survey's own reporting order is stable run-to-run —
|
||||
// not load-bearing for solve() itself, which is pure per-body, but
|
||||
// keeps the printed output diffable).
|
||||
let mut heightmap_paths: Vec<std::path::PathBuf> = Vec::new();
|
||||
fn walk(dir: &std::path::Path, out: &mut Vec<std::path::PathBuf>) {
|
||||
let Ok(entries) = std::fs::read_dir(dir) else {
|
||||
return;
|
||||
};
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
walk(&path, out);
|
||||
} else if path.file_name().and_then(|n| n.to_str()) == Some("heightmap.png") {
|
||||
out.push(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
walk(&wiki_root, &mut heightmap_paths);
|
||||
heightmap_paths.sort();
|
||||
|
||||
assert!(
|
||||
heightmap_paths.len() > 200,
|
||||
"expected the real committed body population (~267 as of this bench), found {} — \
|
||||
did the wiki_root path resolve correctly? ({wiki_root:?})",
|
||||
heightmap_paths.len()
|
||||
);
|
||||
|
||||
struct BodySurvey {
|
||||
body_id: String,
|
||||
basins: usize,
|
||||
overflow: usize,
|
||||
endorheic: usize,
|
||||
lake_cells: usize,
|
||||
carved_cells: usize,
|
||||
cliff_edge_cells: usize,
|
||||
solve_ms: f64,
|
||||
}
|
||||
|
||||
let t0 = Instant::now();
|
||||
let surveys: Vec<BodySurvey> = heightmap_paths
|
||||
.par_iter()
|
||||
.map(|path| {
|
||||
// body_id = the directory name one level up from heightmap.png
|
||||
// (wiki/star-systems/<system>/bodies/<body_id>/heightmap.png).
|
||||
let body_id = path
|
||||
.parent()
|
||||
.and_then(|p| p.file_name())
|
||||
.and_then(|n| n.to_str())
|
||||
.unwrap_or("UNKNOWN")
|
||||
.to_string();
|
||||
|
||||
let heightmap = settled_reach_server::atlas::heightmap::load_heightmap_png(
|
||||
path, &body_id, 0.3,
|
||||
)
|
||||
.unwrap_or_else(|e| panic!("decode committed heightmap for {body_id} ({path:?}): {e}"));
|
||||
let small = heightmap.downsample(512, 256);
|
||||
|
||||
let t_solve = Instant::now();
|
||||
let result = solve(&small.data, 512, 256, small.sea_level, default_climate());
|
||||
let solve_ms = t_solve.elapsed().as_secs_f64() * 1000.0;
|
||||
|
||||
let lake_cells: usize = result.basins.iter().map(|b| b.cells.len()).sum();
|
||||
let carved_cells: usize = result
|
||||
.basins
|
||||
.iter()
|
||||
.filter(|b| {
|
||||
matches!(
|
||||
b.outcome,
|
||||
BasinOutcome::Overflow {
|
||||
downstream_target: DownstreamTarget::Sea
|
||||
| DownstreamTarget::Basin(_)
|
||||
| DownstreamTarget::OpenSpillway,
|
||||
..
|
||||
}
|
||||
) && result.channel_depth_scaled[b.spill_cell] > 0
|
||||
})
|
||||
.count();
|
||||
let cliff_edge_cells = result.cliff_edge.iter().filter(|&&c| c).count();
|
||||
let endorheic = result
|
||||
.basins
|
||||
.iter()
|
||||
.filter(|b| matches!(b.outcome, BasinOutcome::Endorheic { .. }))
|
||||
.count();
|
||||
let overflow = result
|
||||
.basins
|
||||
.iter()
|
||||
.filter(|b| matches!(b.outcome, BasinOutcome::Overflow { .. }))
|
||||
.count();
|
||||
|
||||
BodySurvey {
|
||||
body_id,
|
||||
basins: result.basins.len(),
|
||||
overflow,
|
||||
endorheic,
|
||||
lake_cells,
|
||||
carved_cells,
|
||||
cliff_edge_cells,
|
||||
solve_ms,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let elapsed = t0.elapsed();
|
||||
|
||||
let body_count = surveys.len();
|
||||
let total_basins: usize = surveys.iter().map(|s| s.basins).sum();
|
||||
let total_overflow: usize = surveys.iter().map(|s| s.overflow).sum();
|
||||
let total_endorheic: usize = surveys.iter().map(|s| s.endorheic).sum();
|
||||
let total_lake_cells: usize = surveys.iter().map(|s| s.lake_cells).sum();
|
||||
let total_carved_cells: usize = surveys.iter().map(|s| s.carved_cells).sum();
|
||||
let total_cliff_edge_cells: usize = surveys.iter().map(|s| s.cliff_edge_cells).sum();
|
||||
let bodies_with_any_carving = surveys.iter().filter(|s| s.cliff_edge_cells > 0).count();
|
||||
|
||||
let mut by_cliff_edge: Vec<&BodySurvey> = surveys.iter().collect();
|
||||
by_cliff_edge.sort_by(|a, b| b.cliff_edge_cells.cmp(&a.cliff_edge_cells));
|
||||
|
||||
println!(
|
||||
"\n=== POPULATION SURVEY: {body_count} real committed bodies x 512x256, Rayon par_iter ({} threads available) ===",
|
||||
std::thread::available_parallelism()
|
||||
.map(|n| n.get())
|
||||
.unwrap_or(0)
|
||||
);
|
||||
println!(
|
||||
" {:>9.2} ms total wall time, {:>7.3} ms/body average",
|
||||
elapsed.as_secs_f64() * 1000.0,
|
||||
elapsed.as_secs_f64() * 1000.0 / body_count as f64
|
||||
);
|
||||
println!(
|
||||
" basins: {total_basins} total across population ({total_overflow} overflow, {total_endorheic} endorheic)"
|
||||
);
|
||||
println!(" lake cells (summed across population): {total_lake_cells}");
|
||||
println!(
|
||||
" carved-outlet basins (channel_depth_scaled > 0 at spill cell, summed): {total_carved_cells}"
|
||||
);
|
||||
println!(
|
||||
" cliff_edge=true cells (summed across population): {total_cliff_edge_cells}"
|
||||
);
|
||||
println!(
|
||||
" bodies with ANY cliff_edge cell: {bodies_with_any_carving} / {body_count} ({:.2}%)",
|
||||
100.0 * bodies_with_any_carving as f64 / body_count as f64
|
||||
);
|
||||
println!("\n Top 15 bodies by cliff_edge cell count (the max-carve outliers):");
|
||||
for s in by_cliff_edge.iter().take(15) {
|
||||
println!(
|
||||
" {:>10} cliff_edge_cells={:>4} carved_basins={:>2} basins={:>3} (overflow={:>3}, endorheic={:>2}) lake_cells={:>6} solve={:.2}ms",
|
||||
s.body_id, s.cliff_edge_cells, s.carved_cells, s.basins, s.overflow, s.endorheic, s.lake_cells, s.solve_ms
|
||||
);
|
||||
}
|
||||
let zero_carve_bodies = surveys.iter().filter(|s| s.cliff_edge_cells == 0).count();
|
||||
println!(
|
||||
"\n Bodies with ZERO cliff_edge cells: {zero_carve_bodies} / {body_count} ({:.2}%)",
|
||||
100.0 * zero_carve_bodies as f64 / body_count as f64
|
||||
);
|
||||
|
||||
// Determinism spot-check on the max-carve outlier (if any carving was
|
||||
// found at all) — re-solve it once more and confirm byte-identical
|
||||
// cliff_edge/channel_depth output, so the survey's headline outlier
|
||||
// isn't itself an artifact of non-determinism.
|
||||
if let Some(top) = by_cliff_edge.first() {
|
||||
if top.cliff_edge_cells > 0 {
|
||||
let path = heightmap_paths
|
||||
.iter()
|
||||
.find(|p| {
|
||||
p.parent()
|
||||
.and_then(|d| d.file_name())
|
||||
.and_then(|n| n.to_str())
|
||||
== Some(top.body_id.as_str())
|
||||
})
|
||||
.expect("outlier body path must exist (found via the same walk above)");
|
||||
let heightmap = settled_reach_server::atlas::heightmap::load_heightmap_png(
|
||||
path,
|
||||
&top.body_id,
|
||||
0.3,
|
||||
)
|
||||
.expect("re-decode outlier heightmap for determinism spot-check");
|
||||
let small = heightmap.downsample(512, 256);
|
||||
let r1 = solve(&small.data, 512, 256, small.sea_level, default_climate());
|
||||
let r2 = solve(&small.data, 512, 256, small.sea_level, default_climate());
|
||||
assert_eq!(
|
||||
r1.cliff_edge, r2.cliff_edge,
|
||||
"max-carve outlier {} must be deterministic (cliff_edge)",
|
||||
top.body_id
|
||||
);
|
||||
assert_eq!(
|
||||
r1.channel_depth_scaled, r2.channel_depth_scaled,
|
||||
"max-carve outlier {} must be deterministic (channel_depth_scaled)",
|
||||
top.body_id
|
||||
);
|
||||
println!(
|
||||
"\n Determinism spot-check on max-carve outlier ({}): PASS (byte-identical across two solves)",
|
||||
top.body_id
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// **PER-BASIN SIZE DISTRIBUTION (body-map-viewer workshop, Araminta's
|
||||
/// endorheic-carrier crossover question, routed via the coordinator).**
|
||||
/// [`bench_population_survey_all_committed_bodies`] above reports only
|
||||
/// AGGREGATE lake-cell totals (2,694,012 summed across 22,270 basins) — it
|
||||
/// does not report individual basin SIZES, which is exactly what a
|
||||
/// sparse-list-vs-dense-field crossover analysis needs (a sparse per-cell
|
||||
/// list is cheap for small basins and expensive for large ones; the
|
||||
/// crossover point depends on the actual size DISTRIBUTION, not the
|
||||
/// aggregate). This bench solves every real committed body at the real
|
||||
/// 512×256 production working grid (same population, same grid size as the
|
||||
/// population survey above) and records every individual basin's cell
|
||||
/// count, then reports the full distribution: histogram buckets, percentiles,
|
||||
/// and the largest basins found, so the crossover call is made from a real
|
||||
/// distribution rather than the single synthetic 8.3M-cell grid's one
|
||||
/// data point (412,700 cells — T-1177's original measured appendix, a
|
||||
/// SYNTHETIC ridged-terrain grid at a canvas size no production path
|
||||
/// actually derives hydrology at synchronously, not a real-population
|
||||
/// statistic).
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn bench_per_basin_size_distribution_real_population() {
|
||||
use rayon::prelude::*;
|
||||
|
||||
let wiki_root =
|
||||
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../wiki/star-systems");
|
||||
let mut heightmap_paths: Vec<std::path::PathBuf> = Vec::new();
|
||||
fn walk(dir: &std::path::Path, out: &mut Vec<std::path::PathBuf>) {
|
||||
let Ok(entries) = std::fs::read_dir(dir) else {
|
||||
return;
|
||||
};
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
walk(&path, out);
|
||||
} else if path.file_name().and_then(|n| n.to_str()) == Some("heightmap.png") {
|
||||
out.push(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
walk(&wiki_root, &mut heightmap_paths);
|
||||
heightmap_paths.sort();
|
||||
assert!(
|
||||
heightmap_paths.len() > 200,
|
||||
"expected the real committed body population (~267), found {}",
|
||||
heightmap_paths.len()
|
||||
);
|
||||
|
||||
// (body_id, basin_cell_count, outcome_label) for EVERY basin across the
|
||||
// whole population — the raw material for the distribution below.
|
||||
let all_basins: Vec<(String, usize, &'static str)> = heightmap_paths
|
||||
.par_iter()
|
||||
.flat_map(|path| {
|
||||
let body_id = path
|
||||
.parent()
|
||||
.and_then(|p| p.file_name())
|
||||
.and_then(|n| n.to_str())
|
||||
.unwrap_or("UNKNOWN")
|
||||
.to_string();
|
||||
let heightmap = settled_reach_server::atlas::heightmap::load_heightmap_png(
|
||||
path, &body_id, 0.3,
|
||||
)
|
||||
.unwrap_or_else(|e| panic!("decode committed heightmap for {body_id} ({path:?}): {e}"));
|
||||
let small = heightmap.downsample(512, 256);
|
||||
let result = solve(&small.data, 512, 256, small.sea_level, default_climate());
|
||||
result
|
||||
.basins
|
||||
.iter()
|
||||
.map(|b| {
|
||||
let outcome_label = match b.outcome {
|
||||
settled_reach_server::atlas::hydrology_equilibrium::BasinOutcome::Overflow {
|
||||
..
|
||||
} => "overflow",
|
||||
settled_reach_server::atlas::hydrology_equilibrium::BasinOutcome::Endorheic {
|
||||
..
|
||||
} => "endorheic",
|
||||
};
|
||||
(body_id.clone(), b.cells.len(), outcome_label)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.collect();
|
||||
|
||||
assert_eq!(
|
||||
all_basins.len(),
|
||||
22_270,
|
||||
"basin count must match the population survey's own total exactly \
|
||||
(same population, same grid size, same solver — a mismatch here \
|
||||
means the two benches drifted, not that either is wrong)"
|
||||
);
|
||||
|
||||
let mut sizes: Vec<usize> = all_basins.iter().map(|(_, n, _)| *n).collect();
|
||||
sizes.sort_unstable();
|
||||
|
||||
let total: usize = sizes.iter().sum();
|
||||
let n = sizes.len();
|
||||
let percentile = |p: f64| -> usize {
|
||||
let idx = ((p / 100.0) * (n - 1) as f64).round() as usize;
|
||||
sizes[idx.min(n - 1)]
|
||||
};
|
||||
|
||||
// Histogram buckets chosen to bracket plausible sparse-vs-dense
|
||||
// crossover points: a Vec<(u16,u16)> cell entry is 4 raw bytes/cell
|
||||
// (MessagePack-framed, somewhat less) vs. the dense `water` field's
|
||||
// PNG-per-field rate of ~1.9 bytes/cell (T-1179) applied ONLY to that
|
||||
// basin's own bounding extent — the buckets below span from "clearly
|
||||
// sparse wins" (tiny basins) through "clearly dense wins" (the largest
|
||||
// basins in this population).
|
||||
let buckets: [(usize, usize, &str); 7] = [
|
||||
(0, 10, "1-10 cells"),
|
||||
(11, 50, "11-50 cells"),
|
||||
(51, 200, "51-200 cells"),
|
||||
(201, 1_000, "201-1,000 cells"),
|
||||
(1_001, 5_000, "1,001-5,000 cells"),
|
||||
(5_001, 20_000, "5,001-20,000 cells"),
|
||||
(20_001, usize::MAX, "20,001+ cells"),
|
||||
];
|
||||
let mut bucket_counts = [0usize; 7];
|
||||
let mut bucket_cell_totals = [0usize; 7];
|
||||
for &size in &sizes {
|
||||
for (i, (lo, hi, _)) in buckets.iter().enumerate() {
|
||||
if size >= *lo && size <= *hi {
|
||||
bucket_counts[i] += 1;
|
||||
bucket_cell_totals[i] += size;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut by_size: Vec<&(String, usize, &str)> = all_basins.iter().collect();
|
||||
by_size.sort_by(|a, b| b.1.cmp(&a.1));
|
||||
|
||||
println!("\n=== PER-BASIN SIZE DISTRIBUTION: {n} basins, real 267-body population, 512x256 working grid ===");
|
||||
println!(" total lake cells (cross-check vs population survey's 2,694,012): {total}");
|
||||
println!(" min={}, max={}, mean={:.1}, median(p50)={}",
|
||||
sizes[0], sizes[n - 1], total as f64 / n as f64, percentile(50.0));
|
||||
println!(" percentiles: p10={} p25={} p50={} p75={} p90={} p95={} p99={} p99.9={}",
|
||||
percentile(10.0), percentile(25.0), percentile(50.0), percentile(75.0),
|
||||
percentile(90.0), percentile(95.0), percentile(99.0), percentile(99.9));
|
||||
|
||||
println!("\n Histogram (basin cell-count buckets):");
|
||||
for (i, (_, _, label)) in buckets.iter().enumerate() {
|
||||
println!(
|
||||
" {label:<18}: {:>6} basins ({:>5.2}% of basins), {:>9} cells total ({:>5.2}% of lake cells)",
|
||||
bucket_counts[i],
|
||||
100.0 * bucket_counts[i] as f64 / n as f64,
|
||||
bucket_cell_totals[i],
|
||||
100.0 * bucket_cell_totals[i] as f64 / total as f64
|
||||
);
|
||||
}
|
||||
|
||||
println!("\n Top 20 largest basins (real population, real bodies):");
|
||||
for (body_id, size, outcome) in by_size.iter().take(20) {
|
||||
println!(" {body_id:>12} cells={size:>7} outcome={outcome}");
|
||||
}
|
||||
|
||||
// Bytes-if-sparse vs bytes-if-dense-per-basin-extent crossover, using
|
||||
// T-1179's measured PNG-per-field rate (638,382 B / 331,776 cells) as
|
||||
// the dense-encoding baseline, and 4 raw bytes/cell (u16,u16 pair,
|
||||
// MessagePack framing not applied — a conservative/pessimistic sparse
|
||||
// estimate, since MessagePack's compact array framing would cost less,
|
||||
// per T-1179's own "6.00 not 7 bytes/cell" finding for a different
|
||||
// field shape) for the sparse Vec<(u16,u16)> carrier.
|
||||
const PNG_BYTES_PER_CELL: f64 = 638_382.0 / 331_776.0;
|
||||
const SPARSE_BYTES_PER_CELL: f64 = 4.0;
|
||||
let mut crossover_basin_count = 0usize;
|
||||
let mut sparse_wins_cells = 0usize;
|
||||
let mut dense_wins_cells = 0usize;
|
||||
for &size in &sizes {
|
||||
let sparse_cost = size as f64 * SPARSE_BYTES_PER_CELL;
|
||||
let dense_cost = size as f64 * PNG_BYTES_PER_CELL;
|
||||
if sparse_cost <= dense_cost {
|
||||
crossover_basin_count += 1;
|
||||
sparse_wins_cells += size;
|
||||
} else {
|
||||
dense_wins_cells += size;
|
||||
}
|
||||
}
|
||||
println!(
|
||||
"\n Sparse-vs-dense crossover (sparse={SPARSE_BYTES_PER_CELL} B/cell raw vs dense={:.3} B/cell PNG-per-field):",
|
||||
PNG_BYTES_PER_CELL
|
||||
);
|
||||
println!(
|
||||
" sparse cheaper for {crossover_basin_count} / {n} basins ({:.2}%), covering {sparse_wins_cells} cells ({:.2}% of lake cells)",
|
||||
100.0 * crossover_basin_count as f64 / n as f64,
|
||||
100.0 * sparse_wins_cells as f64 / total as f64
|
||||
);
|
||||
println!(
|
||||
" dense cheaper for {} / {n} basins ({:.2}%), covering {dense_wins_cells} cells ({:.2}% of lake cells)",
|
||||
n - crossover_basin_count,
|
||||
100.0 * (n - crossover_basin_count) as f64 / n as f64,
|
||||
100.0 * dense_wins_cells as f64 / total as f64
|
||||
);
|
||||
println!();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user