feat(ui): rivers as cartographic strokes (D-261, T-1237)
Rivers now appear on the whole-body map for the first time. Five on Ferrath's
Global canvas, drawn as 5 px strokes that stop at the coastline.
Four rules, all client-side over existing server data, computed once on canvas
adoption rather than per draw:
- fixed 5 px screen-space stroke at every rung
- contiguous geometry through the river's own cells
- never drawn over water — ocean and lake end a run
- culled below 15 px of on-screen length (3x the stroke: below that a line
is a square, not a river)
TWO THINGS THE MEASUREMENT FOUND THAT THE RECORD DID NOT ANTICIPATE.
First, the cull unit was wrong. A server "course" is an EDGE of the river
network — the stretch between two confluences — not a river. Culling per
course culls per segment, so a long river assembled from many short edges
vanishes entirely. Measured on Ferrath Global: 375 courses, 180 surviving the
water clip, and ZERO surviving a per-course cull. Edges are now chained
end-to-end into rivers before the cull is applied, which also delivers the
other half of D-261's "contiguous": per-course contiguity only makes each edge
unbroken; joining is what makes a river read as one line rather than dashes.
After chaining, 5 rivers survive at Global — the "major systems only from
orbit" behaviour the record predicted, arrived at by a different route.
Second, and worse: uses_orbital_derive() still read `Global | Region` while
the client's mirror had said Global-only since 2026-07-26. The D-255 amendment
claims "Region left the orbital derive set... it now takes the full
courses-aware derive". That was implemented against the MIRROR and never
against the authority, so Region kept running envelope-only and carrying no
courses — the exact thing the amendment said it had stopped doing. Both test
suites stayed green for two days because neither compares itself to the other.
Fixed here, with a note on each side pointing at the other, since the two
cannot be cross-checked automatically.
Also removes the two gates that withheld courses from the orbital rung — the
reason the whole-body map had no rivers at all. Whether a course is worth
drawing is measured in screen pixels, which only the client knows, so the
server now supplies geometry at every rung and the client decides.
The capture harness reports "drawn" alongside "courses", because "375 courses
arrived" and "375 rivers are drawn" are different claims and conflating them
is what made an empty map look like a data problem.
Client suite 1836 / 1810 passed / 26 skipped. Server suite green.
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -154,15 +154,26 @@ impl StepCanvasRung {
|
||||
}
|
||||
|
||||
/// The derivation mode this rung uses (D-255(a)/(f), Dudley round-2 §(b)):
|
||||
/// [`Self::Global`] and [`Self::Region`] both ride `derive_orbital_at_metres`
|
||||
/// (the coarse envelope-only derivation — no invented terrain, D-243
|
||||
/// region/orbital spacing sits below `detail_scatter`'s own octave floor);
|
||||
/// every fixed sub-region rung rides `derive_at_metres` (full
|
||||
/// classification, courses-aware). This mirrors
|
||||
/// `layer_proxy::derive_window_cell`'s existing `WindowGranularity::Region`
|
||||
/// vs `District | Quarter` split, extended down through Block/Chunk.
|
||||
/// [`Self::Global`] rides `derive_orbital_at_metres` (the coarse
|
||||
/// envelope-only derivation — no invented terrain, since orbital spacing
|
||||
/// sits below `detail_scatter`'s own octave floor); every fixed rung rides
|
||||
/// `derive_at_metres` (full classification, courses-aware).
|
||||
///
|
||||
/// **Region left this set** (D-255 amendment, 2026-07-26). It was
|
||||
/// envelope-only because a Region canvas spanned ~251,658 km, where
|
||||
/// nothing finer than an envelope was meaningful. Post-inversion it spans
|
||||
/// 262x466 km — a genuine provincial map — so it derives like the rungs
|
||||
/// below it.
|
||||
///
|
||||
/// **Corrected 2026-07-28.** That amendment was implemented against the
|
||||
/// CLIENT's mirror (`step_canvas_transport.gd::is_orbital_rung`) only.
|
||||
/// THIS function is the authority, and still read `Global | Region`, so
|
||||
/// Region went on running envelope-only and carrying no courses — the
|
||||
/// precise thing the amendment claimed it had stopped doing. A mirror
|
||||
/// updated without its source is worse than no mirror at all: both sides
|
||||
/// read plausibly, and they disagree only in production.
|
||||
pub fn uses_orbital_derive(self) -> bool {
|
||||
matches!(self, StepCanvasRung::Global | StepCanvasRung::Region)
|
||||
matches!(self, StepCanvasRung::Global)
|
||||
}
|
||||
|
||||
/// Global's variable canvas extent for a body of the given radius:
|
||||
@@ -647,9 +658,13 @@ fn invent_courses_for_canvas(
|
||||
step_m: f64,
|
||||
min_wavelength_m: f64,
|
||||
) -> Vec<InventedCourse> {
|
||||
if rung.uses_orbital_derive() {
|
||||
return Vec::new();
|
||||
}
|
||||
// D-261: courses are invented at EVERY rung, Global included. They used to
|
||||
// be withheld from the orbital rung, which is why the whole-body map had no
|
||||
// rivers at all. Whether a given course is worth DRAWING is a presentation
|
||||
// question — measured in screen pixels, which only the client knows — so
|
||||
// the client culls anything shorter than three stroke-widths on screen
|
||||
// (~264 km at Global) and this side simply supplies the geometry.
|
||||
let _ = rung;
|
||||
// One station per gridunit — the finest density this canvas can draw.
|
||||
//
|
||||
// The former absolute floor ([`COURSE_STATION_SPACING_FLOOR_M`], 2,048 m)
|
||||
@@ -981,20 +996,21 @@ pub fn build_step_canvas(
|
||||
height as i32,
|
||||
step_m,
|
||||
);
|
||||
let invented_courses = if rung.is_global() {
|
||||
Vec::new()
|
||||
} else {
|
||||
invent_courses_for_canvas(
|
||||
seed,
|
||||
params,
|
||||
ta,
|
||||
river_network,
|
||||
canvas_rect,
|
||||
rung,
|
||||
step_m,
|
||||
min_wavelength_m,
|
||||
)
|
||||
};
|
||||
// D-261: courses at EVERY rung, Global included. This used to be gated on
|
||||
// `rung.is_global()` — a second gate on top of the one inside
|
||||
// invent_courses_for_canvas, which is why removing only the inner one
|
||||
// changed nothing. Whether a course is worth DRAWING is decided
|
||||
// client-side in screen pixels; this side just supplies the geometry.
|
||||
let invented_courses = invent_courses_for_canvas(
|
||||
seed,
|
||||
params,
|
||||
ta,
|
||||
river_network,
|
||||
canvas_rect,
|
||||
rung,
|
||||
step_m,
|
||||
min_wavelength_m,
|
||||
);
|
||||
|
||||
let rows: Vec<Vec<StepCanvasCell>> = (0..height as i32)
|
||||
.into_par_iter()
|
||||
@@ -1804,11 +1820,26 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// Only Global rides the envelope-only derive (D-255 amendment
|
||||
/// 2026-07-26; corrected here 2026-07-28).
|
||||
///
|
||||
/// This test previously asserted `Global | Region`, and it passed happily
|
||||
/// for two days after the client's mirror
|
||||
/// (`step_canvas_transport.gd::is_orbital_rung`) had already been changed
|
||||
/// to Global-only. Neither this test nor its GDScript twin compares itself
|
||||
/// to the other, so the two sides drifted apart while both suites stayed
|
||||
/// green — the mirror said Region took the full derive, the authority said
|
||||
/// otherwise, and Region shipped with no courses.
|
||||
///
|
||||
/// **If you change this, change `is_orbital_rung` in the same commit.**
|
||||
/// The two cannot be cross-checked automatically (different languages, no
|
||||
/// shared fixture for this predicate), so the coupling is held by this
|
||||
/// note and its counterpart on the client.
|
||||
#[test]
|
||||
fn only_global_and_region_use_orbital_derive() {
|
||||
fn only_global_uses_orbital_derive() {
|
||||
assert!(StepCanvasRung::Global.uses_orbital_derive());
|
||||
assert!(StepCanvasRung::Region.uses_orbital_derive());
|
||||
for rung in [
|
||||
StepCanvasRung::Region,
|
||||
StepCanvasRung::District,
|
||||
StepCanvasRung::Quarter,
|
||||
StepCanvasRung::Block,
|
||||
@@ -1821,6 +1852,21 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// Courses are invented at EVERY rung now (D-261) — the gate that withheld
|
||||
/// them from the orbital rung is why the whole-body map had no rivers.
|
||||
/// Whether a course is worth DRAWING is a client-side decision measured in
|
||||
/// screen pixels, which the server cannot make.
|
||||
#[test]
|
||||
fn no_rung_suppresses_course_invention() {
|
||||
// The predicate the old gate keyed on must no longer imply "no
|
||||
// courses" for anything: Global rides the orbital derive AND carries
|
||||
// courses, a combination that was previously impossible.
|
||||
assert!(
|
||||
StepCanvasRung::Global.uses_orbital_derive(),
|
||||
"Global still derives terrain envelope-only"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn global_cell_counts_only_for_global_rung() {
|
||||
assert!(StepCanvasRung::Region.global_cell_counts(6371.0).is_none());
|
||||
|
||||
Reference in New Issue
Block a user