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:
2026-07-23 13:36:16 +02:00
co-authored by Claude Fable 5
parent c31cc6220e
commit 4320df9b60
16 changed files with 4353 additions and 78 deletions
+1
View File
@@ -1933,6 +1933,7 @@ fn derive_profile_for_body(params: &BodyParams) -> DistrictProfile {
"test_body",
&BTreeMap::new(),
settled_reach_server::atlas::scale::BasinDirection::default(),
None,
)
}
+2
View File
@@ -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
}
]
+151 -1
View File
@@ -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, &params, 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()
);
}
+191 -3
View File
@@ -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",
&params,
&ta,
&rn,
(0, 0),
n,
&climate,
@@ -309,6 +327,7 @@ fn bench_served_region_window_tile_at_wire_cap() {
"bench",
&params,
&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", &params, &ta, &rn_off, center, n, &climate, WindowGranularity::District, 0,
);
let _ = build_district_window_layer(
seed, "GJ1c", &params, &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", &params, &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", &params, &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, &params, 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, &params, 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
);
}