test(simulation): upstream-cut Mouth-terminus regression pin (PR #197 acceptance follow-through)
The live acceptance question (Mouth flag apparently not surviving crop) resolved to hypothesis (b): the terminus genuinely lay outside both test windows — the Lendel mouth chord is ~108.5km, not the ~38km estimated, so a District n=32 window centered on the cell OR the midpoint misses the bisected waterline; a seaward-cell-centered window provably ships Mouth. Hypothesis (a) — upstream-only crops collapsing the flag — was FALSIFIED directly with a constructed window (upstream anchor cut, true terminus in range -> Mouth ships; crop reads only last_in). This test converts that probe into a permanent pin with construction-sanity asserts, closing the blind spot where the existing GJ1c acceptance test's bbox-derived window always contains the whole course. Revert-verified: reintroducing a first_in==0 requirement fails this test by name while the whole- course test blindly passes — exactly the gap. Probe file deleted. cargo test --lib 1877/1877. Tickets: T-1170 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -3105,6 +3105,137 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// **T-1170 PR #197 review round 2 (coordinator's live GJ380c/Lendel
|
||||
/// repro) — the upstream-cut coverage gap in the test above, closed.**
|
||||
///
|
||||
/// `all_real_gj1c_mouths_resolve_to_mouth_terminus_not_none` derives its
|
||||
/// window rect from the invented course's OWN min/max point bbox, so by
|
||||
/// construction that window always contains the WHOLE course (both
|
||||
/// `first_in == 0` and `last_in == n-1`) — it never exercises a window
|
||||
/// that cuts the UPSTREAM anchor while the true downstream terminus
|
||||
/// still falls inside. `crop_course_to_window`'s terminus branch is
|
||||
/// driven entirely by `last_in`/`hi` (the downstream side); this test is
|
||||
/// the direct proof that an upstream cut (`first_in > 0`, i.e. `lo > 0`)
|
||||
/// does NOT collapse the terminus flag to `ContinuesBeyondWindow` — the
|
||||
/// coordinator's hypothesis (a) from the live-server investigation,
|
||||
/// falsified here as a permanent regression case rather than only a
|
||||
/// throwaway probe (`mouth_repro_probe.rs`, deleted after this landed).
|
||||
///
|
||||
/// Window construction: take one real GJ1c mouth edge's full invented
|
||||
/// course, find the TRUE (uncropped) terminus point, then build a window
|
||||
/// rect deliberately offset upstream along the course's own tail
|
||||
/// direction so its near edge sits well past the upstream anchor (cutting
|
||||
/// it out of range) while its far edge still comfortably contains the
|
||||
/// true terminus.
|
||||
#[test]
|
||||
fn mouth_terminus_survives_an_upstream_only_crop() {
|
||||
use crate::atlas::drainage;
|
||||
use crate::atlas::heightmap::load_heightmap_png;
|
||||
use crate::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 committed GJ1c heightmap");
|
||||
let small = heightmap.downsample(256, 128);
|
||||
let dr = drainage::analyze(&small.data, small.width, small.height, small.sea_level);
|
||||
let ta = crate::atlas::features::TerrainAnalysis::analyze(&small, &dr);
|
||||
let rn = &dr.river_network;
|
||||
|
||||
let params = crate::atlas::district_profile::BodyParams {
|
||||
hydrosphere: Some("ocean".into()),
|
||||
atmosphere: Some("breathable".into()),
|
||||
planet_class: Some("temperate".into()),
|
||||
body_radius_km: Some(6371.0),
|
||||
..Default::default()
|
||||
};
|
||||
let climate = crate::atlas::district_profile::ClimateConstants::default();
|
||||
let seed = SeedChain::root(0xC0FFEE_u64).derive(SeedDomain::Body, 1);
|
||||
let station_spacing_m = DISTRICT_M as f64;
|
||||
|
||||
let edges = river_course::build_edges(rn);
|
||||
let edge = edges
|
||||
.iter()
|
||||
.find(|e| e.terminus == river_course::EdgeTerminusKind::Mouth)
|
||||
.expect("GJ1c fixture must have at least one Mouth edge");
|
||||
|
||||
let course =
|
||||
river_course::invent_course(seed, edge, &ta, ¶ms, station_spacing_m, 0.0);
|
||||
assert!(
|
||||
course.points.len() >= 2,
|
||||
"need a non-degenerate course to construct a meaningful upstream cut"
|
||||
);
|
||||
let upstream_anchor = course.points[0];
|
||||
let true_end = *course.points.last().unwrap();
|
||||
|
||||
// Window offset upstream along the course's own tail direction (the
|
||||
// last segment), far enough that the upstream anchor falls outside
|
||||
// the window but the true terminus stays comfortably inside.
|
||||
let second_last = course.points[course.points.len().saturating_sub(2)];
|
||||
let (dx, dy) = (true_end.0 - second_last.0, true_end.1 - second_last.1);
|
||||
let len = (dx * dx + dy * dy).sqrt().max(1e-9);
|
||||
let (ux, uy) = (dx / len, dy / len);
|
||||
// Half the upstream->downstream distance keeps the window's near
|
||||
// edge well clear of the upstream anchor for any real mouth chord
|
||||
// (mouth edges are short, ~one D8 step), while the far edge margin
|
||||
// below still comfortably covers the terminus.
|
||||
let chord_m = ((true_end.0 - upstream_anchor.0).powi(2)
|
||||
+ (true_end.1 - upstream_anchor.1).powi(2))
|
||||
.sqrt();
|
||||
let offset_m = (chord_m * 0.5).max(5_000.0);
|
||||
let center = (true_end.0 - ux * offset_m, true_end.1 - uy * offset_m);
|
||||
let half_extent_m = (chord_m * 0.5).max(5_000.0);
|
||||
let window_rect = (
|
||||
center.0 - half_extent_m,
|
||||
center.1 - half_extent_m,
|
||||
center.0 + half_extent_m,
|
||||
center.1 + half_extent_m,
|
||||
);
|
||||
|
||||
// Sanity on the window construction itself (not the code under
|
||||
// test): the upstream anchor must genuinely be cropped out, and the
|
||||
// true terminus must genuinely be inside — otherwise this test
|
||||
// isn't exercising the branch it claims to.
|
||||
let inside = |p: (f64, f64)| {
|
||||
p.0 >= window_rect.0
|
||||
&& p.0 <= window_rect.2
|
||||
&& p.1 >= window_rect.1
|
||||
&& p.1 <= window_rect.3
|
||||
};
|
||||
assert!(
|
||||
!inside(upstream_anchor),
|
||||
"test construction error: upstream anchor {upstream_anchor:?} must be OUTSIDE \
|
||||
the window {window_rect:?} for this to be a real upstream-cut case"
|
||||
);
|
||||
assert!(
|
||||
inside(true_end),
|
||||
"test construction error: true terminus {true_end:?} must be INSIDE the \
|
||||
window {window_rect:?} for this to test the terminus-survives claim"
|
||||
);
|
||||
|
||||
let wire = crop_course_to_window(
|
||||
&course,
|
||||
window_rect,
|
||||
seed,
|
||||
"GJ1c",
|
||||
¶ms,
|
||||
&ta,
|
||||
&climate,
|
||||
0.0,
|
||||
station_spacing_m,
|
||||
)
|
||||
.unwrap_or_else(|| panic!("course cropped to nothing despite containing the terminus"));
|
||||
|
||||
assert_eq!(
|
||||
wire.terminus,
|
||||
CourseTerminus::Mouth,
|
||||
"an upstream-only crop (anchor cut, true terminus still in-window) must NOT \
|
||||
collapse the terminus flag — got {:?} for edge {:?}",
|
||||
wire.terminus,
|
||||
edge.edge_id
|
||||
);
|
||||
}
|
||||
|
||||
/// Clamped-window edge: `n = 1` is the minimum valid window (a single
|
||||
/// district) — no panic, no empty output, exactly one cell per array.
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user