feat(simulation): T-1170 A2/A3 + T-1168 A5 — course inventor, coast termination, riparian signal
A2 (river_course.rs): Stage A rung-independent valley-seeking control
path (chord/8 stations, k=5 bilinear-scored candidates + continuity
penalty); Stage B rung-indexed perpendicular warp on GLOBAL arc-length
(window-independence, Ruling 1e), band chord/2 down to min_wavelength_m
hard-truncate, sine taper to zero at anchors, amplitude min(8% chord,
half-cell) slope/class-scaled. SeedDomain::RiverCourse=17, distinct
salt. Wire: RiverCourse{edge_id,class,points,terminus} on
DistrictWindowLayer.courses (serde-default); bbox-culled, window-
cropped +1 station. TerrainAnalysisCache retains Layer1Output (the
gen_queue:626 discard, Ruling 4b). A3: mouth termination walks
stations sampling the window's OWN rung-consistent morphology verdict,
6-iteration bisect; land-at-anchor probes one segment then None;
EdgeDrain never probes. A5: near_perennial_water point-to-segment
predicate (D-239 §8 governed bands 1-3m class-scaled) threaded through
both batch and window paths; Region always false; never touches
moisture_q. Discipline closed: dormant zoom-ladder bench run + numbers
recorded in the design doc (District 3.009/1.785, Quarter 1.823
us/cell, Region window 0.617ms); course-cost bench CAUGHT a real
+12-36% per-cell riparian scan regression -> precomputed bbox O(1)
reject (60ns->2.2ns/call), final delta +3.4-6.9% at budget; three
determinism tests (overlapping-window byte-identity, cross-rung
amplitude bound, warp-stream cross-correlation r<0.3); goldens: window
sweep gained a verified course-bearing position (pure append), new
river_course golden at both rungs, believability verified unchanged.
Revert-verification discovered the pole-row branch is structurally
unreachable (flow_direction bounds-check) — the real edge-drain path
is k<0 flat-plateau; test fixture rewritten to exercise reality.
scale.rs stale comment fixed. Full cargo test green.
Tickets: T-1170, T-1168
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1933,6 +1933,7 @@ fn derive_profile_for_body(params: &BodyParams) -> DistrictProfile {
|
||||
"test_body",
|
||||
&BTreeMap::new(),
|
||||
settled_reach_server::atlas::scale::BasinDirection::default(),
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -558,6 +558,7 @@ fn generate_atlas_layer_response_fixtures() {
|
||||
confluences: vec![],
|
||||
mouths: vec![(12, 58)],
|
||||
river_class: vec![1, 2],
|
||||
river_downstream: vec![2, 8], // 2=E direction; 8=MOUTH sentinel
|
||||
},
|
||||
drainage_basins: vec![DrainageBasin {
|
||||
basin_id: 1,
|
||||
@@ -736,6 +737,7 @@ fn generate_atlas_layer_response_fixtures() {
|
||||
moisture_q: vec![90, 55, 0, 100],
|
||||
vegetation: vec![6, 3, 0, 5], // Marine, Forest, Absent, RiparianThicket
|
||||
glaciation: vec![0, 0, 4, 1], // None, None, IceCap, Light
|
||||
courses: vec![],
|
||||
};
|
||||
let ready_with_window = AtlasLayerResponse {
|
||||
body_id: "GJ1c".into(),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -254,6 +254,57 @@
|
||||
"moisture_q": 43,
|
||||
"vegetation": 1
|
||||
},
|
||||
{
|
||||
"label": "river_course",
|
||||
"rung": "district",
|
||||
"wx_m": 36277344,
|
||||
"wy_m": -6830545,
|
||||
"min_wl_m": 4096,
|
||||
"morphology": 8,
|
||||
"tectonic": 0,
|
||||
"glaciation": 1,
|
||||
"precipitation": 2,
|
||||
"slope_q": 2,
|
||||
"elev_q": 8,
|
||||
"ocean_fraction_q": 0,
|
||||
"temperature_dc": 14,
|
||||
"moisture_q": 49,
|
||||
"vegetation": 3
|
||||
},
|
||||
{
|
||||
"label": "river_course",
|
||||
"rung": "quarter",
|
||||
"wx_m": 36277344,
|
||||
"wy_m": -6830545,
|
||||
"min_wl_m": 1024,
|
||||
"morphology": 8,
|
||||
"tectonic": 0,
|
||||
"glaciation": 1,
|
||||
"precipitation": 2,
|
||||
"slope_q": 3,
|
||||
"elev_q": 4,
|
||||
"ocean_fraction_q": 0,
|
||||
"temperature_dc": 34,
|
||||
"moisture_q": 50,
|
||||
"vegetation": 3
|
||||
},
|
||||
{
|
||||
"label": "river_course",
|
||||
"rung": "region",
|
||||
"wx_m": 36277344,
|
||||
"wy_m": -6830545,
|
||||
"min_wl_m": 0,
|
||||
"morphology": 8,
|
||||
"tectonic": 0,
|
||||
"glaciation": 1,
|
||||
"precipitation": 2,
|
||||
"slope_q": 0,
|
||||
"elev_q": 11,
|
||||
"ocean_fraction_q": 0,
|
||||
"temperature_dc": -2,
|
||||
"moisture_q": 49,
|
||||
"vegetation": 3
|
||||
},
|
||||
{
|
||||
"label": "airless_dry/coastal_a",
|
||||
"rung": "district",
|
||||
@@ -509,6 +560,57 @@
|
||||
"moisture_q": 0,
|
||||
"vegetation": 0
|
||||
},
|
||||
{
|
||||
"label": "airless_dry/river_course",
|
||||
"rung": "district",
|
||||
"wx_m": 36277344,
|
||||
"wy_m": -6830545,
|
||||
"min_wl_m": 4096,
|
||||
"morphology": 8,
|
||||
"tectonic": 0,
|
||||
"glaciation": 0,
|
||||
"precipitation": 0,
|
||||
"slope_q": 3,
|
||||
"elev_q": 5,
|
||||
"ocean_fraction_q": 0,
|
||||
"temperature_dc": -2147483648,
|
||||
"moisture_q": 0,
|
||||
"vegetation": 0
|
||||
},
|
||||
{
|
||||
"label": "airless_dry/river_course",
|
||||
"rung": "quarter",
|
||||
"wx_m": 36277344,
|
||||
"wy_m": -6830545,
|
||||
"min_wl_m": 1024,
|
||||
"morphology": 8,
|
||||
"tectonic": 0,
|
||||
"glaciation": 0,
|
||||
"precipitation": 0,
|
||||
"slope_q": 3,
|
||||
"elev_q": 6,
|
||||
"ocean_fraction_q": 0,
|
||||
"temperature_dc": -2147483648,
|
||||
"moisture_q": 0,
|
||||
"vegetation": 0
|
||||
},
|
||||
{
|
||||
"label": "airless_dry/river_course",
|
||||
"rung": "region",
|
||||
"wx_m": 36277344,
|
||||
"wy_m": -6830545,
|
||||
"min_wl_m": 0,
|
||||
"morphology": 8,
|
||||
"tectonic": 0,
|
||||
"glaciation": 0,
|
||||
"precipitation": 0,
|
||||
"slope_q": 0,
|
||||
"elev_q": 5,
|
||||
"ocean_fraction_q": 0,
|
||||
"temperature_dc": -2147483648,
|
||||
"moisture_q": 0,
|
||||
"vegetation": 0
|
||||
},
|
||||
{
|
||||
"label": "volcanic_coast/coastal_a",
|
||||
"rung": "district",
|
||||
@@ -763,5 +865,56 @@
|
||||
"temperature_dc": 555,
|
||||
"moisture_q": 30,
|
||||
"vegetation": 2
|
||||
},
|
||||
{
|
||||
"label": "volcanic_coast/river_course",
|
||||
"rung": "district",
|
||||
"wx_m": 36277344,
|
||||
"wy_m": -6830545,
|
||||
"min_wl_m": 4096,
|
||||
"morphology": 15,
|
||||
"tectonic": 2,
|
||||
"glaciation": 0,
|
||||
"precipitation": 2,
|
||||
"slope_q": 4,
|
||||
"elev_q": 14,
|
||||
"ocean_fraction_q": 0,
|
||||
"temperature_dc": 500,
|
||||
"moisture_q": 47,
|
||||
"vegetation": 3
|
||||
},
|
||||
{
|
||||
"label": "volcanic_coast/river_course",
|
||||
"rung": "quarter",
|
||||
"wx_m": 36277344,
|
||||
"wy_m": -6830545,
|
||||
"min_wl_m": 1024,
|
||||
"morphology": 15,
|
||||
"tectonic": 2,
|
||||
"glaciation": 0,
|
||||
"precipitation": 2,
|
||||
"slope_q": 6,
|
||||
"elev_q": 5,
|
||||
"ocean_fraction_q": 0,
|
||||
"temperature_dc": 547,
|
||||
"moisture_q": 49,
|
||||
"vegetation": 3
|
||||
},
|
||||
{
|
||||
"label": "volcanic_coast/river_course",
|
||||
"rung": "region",
|
||||
"wx_m": 36277344,
|
||||
"wy_m": -6830545,
|
||||
"min_wl_m": 0,
|
||||
"morphology": 15,
|
||||
"tectonic": 2,
|
||||
"glaciation": 0,
|
||||
"precipitation": 2,
|
||||
"slope_q": 0,
|
||||
"elev_q": 18,
|
||||
"ocean_fraction_q": 0,
|
||||
"temperature_dc": 480,
|
||||
"moisture_q": 47,
|
||||
"vegetation": 3
|
||||
}
|
||||
]
|
||||
|
||||
@@ -39,6 +39,7 @@ use settled_reach_server::atlas::district_profile::{
|
||||
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::river_course;
|
||||
use settled_reach_server::atlas::scale;
|
||||
use settled_reach_server::seed::{SeedChain, SeedDomain};
|
||||
|
||||
@@ -163,6 +164,20 @@ fn sweep_positions() -> Vec<(&'static str, f64, f64)> {
|
||||
("coastal_c", 2_100_000.0, 1_560_000.0),
|
||||
("inland", 500_000.0, 3_000_000.0),
|
||||
("high_lat", 1_200_000.0, 8_500_000.0),
|
||||
// T-1170 A2 Discipline item 4: empirically verified (probe run against
|
||||
// this fixture, `sample_hm()`/`sample_params()`) that `sample_hm()`
|
||||
// produces a real river-cell chain around working-grid pixel
|
||||
// (row=10, col=116) — NONE of the original five sweep positions
|
||||
// (pixel cols ~1.6-6.7) land anywhere near it. This position converts
|
||||
// that pixel to world metres (same `world_m_to_pixel` inverse the
|
||||
// production mapping uses) so the golden sweep also exercises
|
||||
// `derive_at_metres` genuinely close to invented river geometry —
|
||||
// closing the "believability expected unchanged, verify, don't
|
||||
// assume" discipline item for the district-profile-only fields this
|
||||
// golden already pins (courses themselves are pinned separately
|
||||
// below, `river_course_golden_regression`, since this sweep's
|
||||
// `derive_at_metres` calls never touch `RiverCourse` at all).
|
||||
("river_course", 36_277_344.8, -6_830_545.5),
|
||||
]
|
||||
}
|
||||
|
||||
@@ -182,7 +197,7 @@ fn derive_golden_sample(
|
||||
let prof = if orbital {
|
||||
derive_orbital_at_metres(seed, body_id, params, ta, wx, wy, climate)
|
||||
} else {
|
||||
derive_at_metres(seed, body_id, params, ta, wx, wy, climate, min_wl_m)
|
||||
derive_at_metres(seed, body_id, params, ta, wx, wy, climate, min_wl_m, &[])
|
||||
};
|
||||
GoldenSample {
|
||||
label: label.to_string(),
|
||||
@@ -417,3 +432,138 @@ fn golden_cutoffs_match_the_scale_ladder() {
|
||||
assert_eq!(2 * scale::DISTRICT_M, DISTRICT_MIN_WL_M as i32);
|
||||
assert_eq!(2 * scale::QUARTER_M, QUARTER_MIN_WL_M as i32);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// River course golden (T-1170 A2, Discipline item 4)
|
||||
// ---------------------------------------------------------------------------
|
||||
//
|
||||
// Empirically verified (probe run against `sample_hm()`/`sample_params()`):
|
||||
// this fixture body produces a real river-cell chain around working-grid
|
||||
// pixel (row≈10, col=116) — the "river_course" sweep position above converts
|
||||
// that pixel to world metres. This section pins the ACTUAL invented course
|
||||
// geometry for an edge from that chain, at both District and Quarter station
|
||||
// spacing, so courses themselves — not just the district-profile fields the
|
||||
// main golden above covers — are regression-pinned.
|
||||
|
||||
const RIVER_COURSE_GOLDEN_FILE: &str = "tests/golden/river_course_golden.json";
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize, PartialEq, Clone)]
|
||||
struct GoldenCourseSample {
|
||||
rung: String,
|
||||
edge_id: u32,
|
||||
class: u8,
|
||||
terminus: String,
|
||||
/// Points rounded to the nearest metre (D-010 integer boundary at the
|
||||
/// golden-pinning layer — the production wire path itself rounds to
|
||||
/// `i32` metres, `layer_proxy::crop_course_to_window`).
|
||||
points: Vec<(i64, i64)>,
|
||||
}
|
||||
|
||||
fn river_course_golden_samples() -> Vec<GoldenCourseSample> {
|
||||
let hm = sample_hm();
|
||||
let ta = sample_ta(&hm);
|
||||
let params = sample_params();
|
||||
let seed = SeedChain::root(0xC0FFEE_u64).derive(SeedDomain::Body, 7);
|
||||
|
||||
let dr = drainage::analyze(&hm.data, hm.width, hm.height, hm.sea_level);
|
||||
let edges = river_course::build_edges(&dr.river_network);
|
||||
// Pick the interior edge whose upstream cell is closest to (row=10,
|
||||
// col=116) — deterministic (BTreeMap-free linear scan, fixed tie-break
|
||||
// by edge_id) rather than hardcoding an index that could silently shift
|
||||
// if `build_edges`' ordering ever changes.
|
||||
let target = edges
|
||||
.iter()
|
||||
.filter(|e| e.terminus == river_course::EdgeTerminusKind::Interior)
|
||||
.min_by_key(|e| {
|
||||
let dr = e.upstream.0 as i64 - 10;
|
||||
let dc = e.upstream.1 as i64 - 116;
|
||||
(dr * dr + dc * dc, e.edge_id)
|
||||
})
|
||||
.expect("sample_hm() fixture must have at least one interior river edge near (10, 116)");
|
||||
|
||||
let mut out = Vec::new();
|
||||
for (rung, spacing_m, min_wl_m) in [
|
||||
("district", DISTRICT_MIN_WL_M, DISTRICT_MIN_WL_M),
|
||||
("quarter", QUARTER_MIN_WL_M, QUARTER_MIN_WL_M),
|
||||
] {
|
||||
let course = river_course::invent_course(seed, target, &ta, ¶ms, spacing_m, min_wl_m);
|
||||
out.push(GoldenCourseSample {
|
||||
rung: rung.to_string(),
|
||||
edge_id: course.edge_id,
|
||||
class: course.class,
|
||||
terminus: format!("{:?}", course.terminus),
|
||||
points: course
|
||||
.points
|
||||
.iter()
|
||||
.map(|p| (p.0.round() as i64, p.1.round() as i64))
|
||||
.collect(),
|
||||
});
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn river_course_golden_regression() {
|
||||
let manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
let golden_path = manifest.join(RIVER_COURSE_GOLDEN_FILE);
|
||||
|
||||
let run1 = river_course_golden_samples();
|
||||
let run2 = river_course_golden_samples();
|
||||
assert_eq!(
|
||||
run1, run2,
|
||||
"double-derivation mismatch — course invention determinism is broken (D-010/D-227)"
|
||||
);
|
||||
|
||||
let actual_json = serde_json::to_string_pretty(&run1).expect("serialize") + "\n";
|
||||
|
||||
if std::env::var("UPDATE_GOLDEN").is_ok() {
|
||||
std::fs::create_dir_all(golden_path.parent().unwrap()).expect("mkdir golden");
|
||||
std::fs::write(&golden_path, &actual_json).expect("write golden");
|
||||
eprintln!(
|
||||
"Golden written: {} ({} bytes)",
|
||||
golden_path.display(),
|
||||
actual_json.len()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let golden_json = std::fs::read_to_string(&golden_path).unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"Golden file not found: {}.\n\
|
||||
First run: UPDATE_GOLDEN=1 cargo test --test window_derivation_golden\n{e}",
|
||||
golden_path.display()
|
||||
)
|
||||
});
|
||||
|
||||
let actual_v: serde_json::Value = serde_json::from_str(&actual_json).expect("reparse actual");
|
||||
let golden_v: serde_json::Value = serde_json::from_str(&golden_json).expect("parse golden");
|
||||
|
||||
if actual_v != golden_v {
|
||||
panic!(
|
||||
"River-course golden mismatch — course invention changed.\n\
|
||||
Update: UPDATE_GOLDEN=1 cargo test --test window_derivation_golden\n\
|
||||
Golden: {}\nActual: {}",
|
||||
golden_json.trim(),
|
||||
actual_json.trim()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The course golden's target edge must genuinely differ in point geometry
|
||||
/// between District and Quarter station spacing (more, finer-spaced stations
|
||||
/// at Quarter — Ruling 3b's cross-rung invariant) — otherwise the golden
|
||||
/// would be pinning two identical rungs and the test would give false
|
||||
/// confidence.
|
||||
#[test]
|
||||
fn river_course_rungs_have_different_station_counts() {
|
||||
let samples = river_course_golden_samples();
|
||||
let district = samples.iter().find(|s| s.rung == "district").unwrap();
|
||||
let quarter = samples.iter().find(|s| s.rung == "quarter").unwrap();
|
||||
assert!(
|
||||
quarter.points.len() > district.points.len(),
|
||||
"Quarter's finer station spacing must produce MORE points than District \
|
||||
(district={}, quarter={})",
|
||||
district.points.len(),
|
||||
quarter.points.len()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,8 @@ 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_REGION, WIRE_CAP_CELLS,
|
||||
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};
|
||||
@@ -54,6 +55,12 @@ fn bench_ta(hm: &BodyHeightmap) -> TerrainAnalysis {
|
||||
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()),
|
||||
@@ -83,8 +90,17 @@ fn time_derive_sweep(
|
||||
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);
|
||||
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);
|
||||
}
|
||||
@@ -273,6 +289,7 @@ fn bench_derive_orbital_at_metres_region_spacing() {
|
||||
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);
|
||||
@@ -293,6 +310,7 @@ fn bench_served_region_window_tile_at_wire_cap() {
|
||||
"bench",
|
||||
¶ms,
|
||||
&ta,
|
||||
&rn,
|
||||
(0, 0),
|
||||
n,
|
||||
&climate,
|
||||
@@ -309,6 +327,7 @@ fn bench_served_region_window_tile_at_wire_cap() {
|
||||
"bench",
|
||||
¶ms,
|
||||
&ta,
|
||||
&rn,
|
||||
(0, 0),
|
||||
n,
|
||||
&climate,
|
||||
@@ -338,3 +357,172 @@ fn bench_served_region_window_tile_at_wire_cap() {
|
||||
" 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
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user