Files
settled-reach/server/tests/window_derivation_golden.rs
T
jpmschweitzerandClaude Fable 5 d256233faf fix(simulation): PR #197 review round — real seaward chord for Mouth edges (Hoshe #1/#2, Tyre 1/2)
The batch's real bug: extract_river_network computed the seaward
neighbor (nr,nc) to decide the MOUTH sentinel then discarded it, and
build_edges placeholder-pointed mouth edges at themselves — zero
chord, invent_course's degenerate 1-point return, resolve_mouth_
terminus dead code on real data, ALL real mouths resolving None, and
(because Ruling 3g retired the D/Q clip on the promise of real
termini) mouths vanishing at District/Quarter. The second instance of
the threw-away-the-answer anti-pattern Ruling 2b fixed for interior
pointers. Fix: river_seaward: Vec<(u16,u16)> on RiverNetwork
(additive, serde-default, parallel array; meaningful only at MOUTH
entries), captured in the same extraction pass; build_edges gives
Mouth edges the real one-D8-step chord. Permanent acceptance:
all_real_gj1c_mouths_resolve_to_mouth_terminus_not_none — 3/3, revert-
verified failing at the golden's first mouth (38,47). Mouth golden
coverage added (river_course_golden gains district_mouth/quarter_mouth
samples + non-degeneracy test; the previously Interior-only filter
gap closed). Tyre 1: the land-probe's dead dx/len*len arithmetic
replaced — station_spacing_m threaded through the crop path, probe
steps one real cell spacing, comment reconciled. Tyre 2: boxing
comment reattributed to variant-size balancing (Layer1Output retention
lives on TerrainAnalysisCache, not BodyWorldState). Hoshe #4:
cascade_golden's doc now states the attractor cascade accurately
(count-parity, not byte-identity — water_dist seeds from mouths).
Full cargo test green; goldens re-pinned deliberately; bench +3.0%.

Tickets: T-1170

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 15:02:16 +02:00

633 lines
26 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Window-derivation golden regression (T-1162).
//!
//! Pins `derive_at_metres` (District/Quarter rungs) and
//! `derive_orbital_at_metres` (Region rung) output at a fixed
//! (seed, body, coords) sweep, across all three rung cutoffs — the window
//! path `layer_proxy::derive_window_cell` actually calls in production.
//! Mirrors `tests/derivation_harness.rs`'s golden pattern exactly (same
//! regen convention, same double-derive determinism check, same JSON-value
//! comparison so formatting drift doesn't false-positive).
//!
//! **T-1162 regen rationale:** this golden is generated AFTER the T-1162
//! octave-extension changes (extended coast-warp band, sub-district relief at
//! Quarter, vegetation patchiness) — it deliberately pins the NEW output.
//! There is no "pre-T-1162" golden to preserve: this file did not exist
//! before this ticket, so there is nothing to regress against except itself
//! from this point forward. Any future change to the coast warp, the voxel
//! relief band, the vegetation-patchiness field, or the MIN_WL_BANDS_M
//! quantization will change this golden's values — regenerate deliberately
//! (per the asset-pipeline discipline: source changes, not hand-edits).
//!
//! **Body coverage (Tyre, PR #194 review I4):** the sweep runs against THREE
//! bodies, not one — the original temperate/ocean/breathable body (unchanged
//! from the initial T-1162 landing), plus an airless/dry body (exercises
//! `vegetation_invention::VegetationEnvelope`'s `ceiling_q == 0` short
//! circuit at the full derivation-stack level) and a volcanic/high-tectonic
//! coastal body (exercises the ridged-warp/wide-`scatter_floor` branch of
//! `coast_invention`). The two new bodies' rows are APPENDED after the
//! original body's rows (never interleaved), so the original rows stay
//! byte-identical across the I4 regen — see `body_sweep_samples`'s doc.
//!
//! Run: `cargo test --test window_derivation_golden`
//! Regenerate: `UPDATE_GOLDEN=1 cargo test --test window_derivation_golden`
use std::path::PathBuf;
use settled_reach_server::atlas::district_profile::{
derive_at_metres, derive_orbital_at_metres, BodyParams, ClimateConstants,
};
use settled_reach_server::atlas::drainage;
use settled_reach_server::atlas::features::TerrainAnalysis;
use settled_reach_server::atlas::heightmap::BodyHeightmap;
use settled_reach_server::atlas::river_course;
use settled_reach_server::atlas::scale;
use settled_reach_server::seed::{SeedChain, SeedDomain};
const GOLDEN_FILE: &str = "tests/golden/window_derivation_golden.json";
/// Compact representation of a `DistrictProfile` sample for golden pinning.
/// Integer-discriminant fields only (D-010) — no float equality flakiness.
///
/// **No `body` field (Tyre, PR #194 I4 constraint):** the two new body rows
/// (I4) distinguish themselves via the `label` field's prefix instead of a
/// new struct field — adding a field here would change the JSON shape of
/// EVERY existing row (not just the new ones), which fails I4's explicit
/// "existing rows must stay byte-identical" requirement. `label` was always
/// a free-form string, so `"golden_body/coastal_a"` vs `"airless_dry/coastal_a"`
/// costs nothing structurally and keeps the diff a pure append.
#[derive(Debug, serde::Serialize, serde::Deserialize, PartialEq, Eq, Clone)]
struct GoldenSample {
label: String,
rung: String,
wx_m: i64,
wy_m: i64,
min_wl_m: i64,
morphology: u8,
tectonic: u8,
glaciation: u8,
precipitation: u8,
slope_q: i32,
elev_q: i32,
ocean_fraction_q: i32,
temperature_dc: i32, // deci-°C, i32::MIN sentinel for None (airless)
moisture_q: i32,
vegetation: u8,
}
fn sample_hm() -> BodyHeightmap {
// Deterministic gradient with enough variance for coast/relief/vegetation
// content to actually differ across the sweep positions — same shape
// convention as district_profile.rs's own test_hm / zoom_ladder_bench's
// bench_hm, sized a bit larger so the sweep coordinates land on distinct
// heightmap cells rather than a single interpolated patch.
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;
// A gentle sine ripple on top of the linear gradient gives the
// coastline invention real slope/ocean-mask variance to warp.
let ripple = (c * std::f32::consts::TAU * 3.0).sin() * 0.08;
(r * 0.55 + c * 0.35 + ripple + 0.05).clamp(0.0, 1.0)
})
.collect();
BodyHeightmap {
body_id: "golden_body".into(),
width: w,
height: h,
data,
sea_level: 0.32,
}
}
fn sample_ta(hm: &BodyHeightmap) -> TerrainAnalysis {
let dr = drainage::analyze(&hm.data, hm.width, hm.height, hm.sea_level);
TerrainAnalysis::analyze(hm, &dr)
}
fn sample_params() -> BodyParams {
BodyParams {
hydrosphere: Some("ocean".into()),
atmosphere: Some("breathable".into()),
planet_class: Some("temperate".into()),
body_radius_km: Some(6371.0),
..Default::default()
}
}
/// I4 (Tyre, PR #194 review): airless/dry body params — exercises
/// `vegetation_invention::VegetationEnvelope`'s `ceiling_q == 0` short
/// circuit (no water, no atmosphere → zero patchiness swing, per
/// `envelope_airless_or_dry_body_has_zero_ceiling`'s unit-level proof) at
/// the FULL derivation-stack level, which the unit test alone doesn't pin.
/// Also airless (`atmosphere: "none"`) so `temperature_c`/`vegetation_class`
/// take the `None`/`Absent` branches — a body-envelope regression that
/// invented forest on a bone-dry world would show up here as a NEW
/// non-Barren/non-Absent vegetation discriminant in the golden diff.
fn airless_dry_params() -> BodyParams {
BodyParams {
hydrosphere: Some("none".into()),
atmosphere: Some("none".into()),
planet_class: Some("arid".into()),
body_radius_km: Some(3_390.0), // Mars-scale, deliberately distinct from the wet body
..Default::default()
}
}
/// I4 (Tyre, PR #194 review): volcanic/high-tectonic coastal body params —
/// exercises `coast_invention`'s ridged-warp + wide-`scatter_floor` branch
/// (`TectonicClass::Volcanic` → `tectonic_energy` near its ceiling in
/// `body_coast_envelope`, driving up `roughness`/`warp_amplitude_px`/
/// `scatter_floor` per that function's doc) — the coast-crinkle branch most
/// likely to visibly differ from the temperate body's gentler warp, and thus
/// the branch most likely to silently regress without dedicated coverage.
fn volcanic_coast_params() -> BodyParams {
BodyParams {
hydrosphere: Some("ocean".into()),
atmosphere: Some("breathable".into()),
planet_class: Some("volcanic".into()),
tectonic_activity: Some("volcanic".into()),
body_radius_km: Some(6_000.0),
..Default::default()
}
}
/// Fixed sweep positions (world metres from origin) — a handful of points
/// spanning a coastal stretch (per the heightmap's ripple) plus a couple of
/// clearly inland/high-latitude points, so the golden exercises coast warp,
/// sub-district relief, and vegetation patchiness all at once.
fn sweep_positions() -> Vec<(&'static str, f64, f64)> {
vec![
("coastal_a", 2_000_000.0, 1_500_000.0),
("coastal_b", 2_050_000.0, 1_500_000.0),
("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),
]
}
fn derive_golden_sample(
label: &str,
rung: &str,
seed: SeedChain,
body_id: &str,
params: &BodyParams,
ta: &TerrainAnalysis,
climate: &ClimateConstants,
wx: f64,
wy: f64,
min_wl_m: f64,
orbital: bool,
) -> GoldenSample {
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, &[])
};
GoldenSample {
label: label.to_string(),
rung: rung.to_string(),
wx_m: wx as i64,
wy_m: wy as i64,
min_wl_m: min_wl_m as i64,
morphology: prof.morphology_zone as u8,
tectonic: prof.tectonic_class as u8,
glaciation: prof.glaciation_grade as u8,
precipitation: prof.precipitation_class as u8,
slope_q: prof.slope_q,
elev_q: prof.elev_q,
ocean_fraction_q: prof.ocean_fraction_q,
temperature_dc: prof
.temperature_c
.map(|t| (t * 10.0).round() as i32)
.unwrap_or(i32::MIN),
moisture_q: prof.moisture_q,
vegetation: prof.vegetation_class as u8,
}
}
/// District's real quantized `min_wl_m` band (`layer_proxy::MIN_WL_BANDS_M`'s
/// District entry — District's own Nyquist floor, `2 * DISTRICT_M` per the
/// PR #194 I1 dependency-direction fix: the RUNG authors the floor, and
/// `detail_scatter::OCTAVE_WAVELENGTHS_M`'s finest octave coinciding with it
/// is pinned by the `const` drift-guard next to `MIN_WL_BANDS_M`, not the
/// source of the value). A District-rung request in production quantizes to
/// exactly this value, so the golden pins the SAME cutoff a real window
/// request would actually carry; `golden_cutoffs_match_the_scale_ladder`
/// asserts the `2 × spacing` coupling for both this and `QUARTER_MIN_WL_M`.
const DISTRICT_MIN_WL_M: f64 = 4_096.0;
/// Quarter's real quantized `min_wl_m` band (`layer_proxy::MIN_WL_BANDS_M`'s
/// new T-1162 entry — Quarter's own Nyquist floor, `2 * QUARTER_M`).
const QUARTER_MIN_WL_M: f64 = 1_024.0;
/// Run the fixed sweep (every position × the three rung cutoffs — District /
/// Quarter use their REAL production `MIN_WL_BANDS_M` values / Region via
/// `derive_orbital_at_metres`, which takes no cutoff parameter — see its own
/// doc on why) for ONE body. Extracted (Tyre, PR #194 I4) so multiple bodies
/// can share the same sweep logic; `label_prefix` (empty for the original
/// body, non-empty for the I4 additions) is prepended to each row's `label`
/// so multi-body output stays distinguishable without a new struct field
/// (see [`GoldenSample`]'s doc on why no `body` field was added).
#[allow(clippy::too_many_arguments)]
fn body_sweep_samples(
label_prefix: &str,
seed: SeedChain,
body_id: &str,
params: &BodyParams,
ta: &TerrainAnalysis,
climate: &ClimateConstants,
) -> Vec<GoldenSample> {
let mut out = Vec::new();
for (label, wx, wy) in sweep_positions() {
let label = format!("{label_prefix}{label}");
out.push(derive_golden_sample(
&label,
"district",
seed,
body_id,
params,
ta,
climate,
wx,
wy,
DISTRICT_MIN_WL_M,
false,
));
out.push(derive_golden_sample(
&label,
"quarter",
seed,
body_id,
params,
ta,
climate,
wx,
wy,
QUARTER_MIN_WL_M,
false,
));
out.push(derive_golden_sample(
&label, "region", seed, body_id, params, ta, climate, wx, wy, 0.0, true,
));
}
out
}
/// Build the full golden sample set: the ORIGINAL temperate/ocean/breathable
/// body's sweep first (byte-identical inputs to the pre-I4 `golden_samples`
/// — same seed, same `body_id`, same unprefixed labels, so its rows are
/// byte-identical in the regenerated fixture), THEN the two I4 body rows
/// appended after (never interleaved) so the diff against the pre-I4 golden
/// is a pure append, not a reshuffle.
fn golden_samples() -> Vec<GoldenSample> {
let hm = sample_hm();
let ta = sample_ta(&hm);
let climate = ClimateConstants::default();
let mut out = Vec::new();
// Original body — UNCHANGED inputs from pre-I4 (T-1162 initial landing).
out.extend(body_sweep_samples(
"",
SeedChain::root(0xC0FFEE_u64).derive(SeedDomain::Body, 7),
"golden_body",
&sample_params(),
&ta,
&climate,
));
// I4 addition 1: airless/dry — ceiling_q == 0 vegetation short-circuit.
out.extend(body_sweep_samples(
"airless_dry/",
SeedChain::root(0xC0FFEE_u64).derive(SeedDomain::Body, 8),
"golden_body_airless_dry",
&airless_dry_params(),
&ta,
&climate,
));
// I4 addition 2: volcanic/high-tectonic coast — ridged warp, wide scatter_floor.
out.extend(body_sweep_samples(
"volcanic_coast/",
SeedChain::root(0xC0FFEE_u64).derive(SeedDomain::Body, 9),
"golden_body_volcanic_coast",
&volcanic_coast_params(),
&ta,
&climate,
));
out
}
#[test]
fn window_derivation_golden_regression() {
let manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let golden_path = manifest.join(GOLDEN_FILE);
// Double-derive determinism check (D-010) before ever touching the golden.
let run1 = golden_samples();
let run2 = golden_samples();
assert_eq!(
run1, run2,
"double-derivation mismatch — determinism is broken (D-010)"
);
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!(
"Window-derivation golden mismatch — derivation chain changed.\n\
Update: UPDATE_GOLDEN=1 cargo test --test window_derivation_golden\n\
Golden: {}\nActual: {}",
golden_json.trim(),
actual_json.trim()
);
}
}
/// Cross-rung coherence sanity check on the golden's own fixed sweep: for
/// each coastal label, the Quarter-rung sample must differ from the
/// District-rung sample at the SAME position (the whole point of the
/// extension — Quarter sees finer content District's coarser cutoff
/// truncates). This is a structural companion to the golden file itself,
/// not a replacement for it — it fails loudly if the golden ever gets
/// regenerated with `min_wl_m` accidentally identical across rungs.
#[test]
fn quarter_and_district_rungs_diverge_at_the_same_position() {
let samples = golden_samples();
let mut any_diverged = false;
for (label, _, _) in sweep_positions() {
let district = samples
.iter()
.find(|s| s.label == label && s.rung == "district")
.unwrap();
let quarter = samples
.iter()
.find(|s| s.label == label && s.rung == "quarter")
.unwrap();
if district.elev_q != quarter.elev_q
|| district.slope_q != quarter.slope_q
|| district.moisture_q != quarter.moisture_q
{
any_diverged = true;
}
}
assert!(
any_diverged,
"no sweep position showed ANY difference between District and Quarter \
rungs — the T-1162 octave extension would be structurally inert"
);
}
/// `scale::DISTRICT_M` / `scale::QUARTER_M` sanity — documents WHY
/// `DISTRICT_MIN_WL_M`/`QUARTER_MIN_WL_M` are the cutoffs used above (each
/// rung's own Nyquist floor, `2 × <rung's spacing>`), so a future
/// scale-ladder change surfaces here. Pins BOTH rungs' coupling (Tyre, PR
/// #194 I1 — District's coupling was previously unpinned; only Quarter's
/// `2 × QUARTER_M` was checked) — this mirrors
/// `layer_proxy::MIN_WL_BANDS_M`'s own direct `2 × DISTRICT_M` / `2 ×
/// QUARTER_M` derivation, not `detail_scatter::OCTAVE_WAVELENGTHS_M`.
#[test]
fn golden_cutoffs_match_the_scale_ladder() {
assert_eq!(scale::DISTRICT_M, 2_048);
assert_eq!(scale::QUARTER_M, 512);
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.
//
// **Mouth coverage (T-1170 PR #197 review, Hoshe #2):** the sample set below
// also includes a real Mouth-terminus edge from `sample_hm()` (empirically
// probed: 4 mouth edges exist in this fixture) — `invent_course`'s raw output
// for that edge is pinned here (edge_id/class/terminus/points), closing the
// "zero Mouth coverage in any golden" gap at the invention layer. The FULL
// end-to-end path (invent → crop → `resolve_mouth_terminus` →
// `CourseTerminus::Mouth`) is covered separately and permanently by
// `layer_proxy::tests::all_real_gj1c_mouths_resolve_to_mouth_terminus_not_none`
// (in-crate, since `crop_course_to_window`/`resolve_mouth_terminus` are
// private to `layer_proxy.rs` and unreachable from this integration test) —
// that test is the actual Hoshe #1 acceptance criterion (3/3 real mouths);
// this golden's job is regression-pinning the raw invented geometry, not
// re-proving the crop/resolve path.
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)");
// Hoshe #2: a real Mouth-terminus edge, deterministically selected as the
// lowest edge_id among the fixture's mouth edges (fixed tie-break, no
// hardcoded index).
let mouth_target = edges
.iter()
.filter(|e| e.terminus == river_course::EdgeTerminusKind::Mouth)
.min_by_key(|e| e.edge_id)
.expect("sample_hm() fixture must have at least one Mouth edge (empirically verified: 4)");
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(),
});
let mouth_course =
river_course::invent_course(seed, mouth_target, &ta, &params, spacing_m, min_wl_m);
out.push(GoldenCourseSample {
rung: format!("{rung}_mouth"),
edge_id: mouth_course.edge_id,
class: mouth_course.class,
terminus: format!("{:?}", mouth_course.terminus),
points: mouth_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()
);
}
/// **T-1170 PR #197 review, Hoshe #2 (golden coverage):** the pinned Mouth
/// edge samples must be genuine, non-degenerate courses (`terminus == "Mouth"`,
/// `points.len() >= 2`) — the direct golden-level check that the Hoshe #1 fix
/// (real seaward chord via `RiverNetwork::river_seaward`) reaches this fixture
/// too, not just the dedicated GJ1c acceptance test.
#[test]
fn river_course_mouth_samples_are_non_degenerate() {
let samples = river_course_golden_samples();
for rung in ["district_mouth", "quarter_mouth"] {
let sample = samples
.iter()
.find(|s| s.rung == rung)
.unwrap_or_else(|| panic!("missing golden sample for rung {rung}"));
assert_eq!(
sample.terminus, "Mouth",
"{rung}: build_edges must produce a Mouth-terminus RiverEdge for the pinned target"
);
assert!(
sample.points.len() >= 2,
"{rung}: Mouth edge invented a degenerate {}-point course — the chord-length fix \
(Hoshe #1) regressed for this fixture",
sample.points.len()
);
}
}