fix(simulation): PR #202 review round — spill cell always wired + golden truth
Hoshe finding 1 (live-firing on GJ1c: 2 spill collisions + 1 i==1 collision among 51 Overflow basins): adjacency adjudicated INSUFFICIENT for the cue — a course's visible anchor is its upstream cell, so nothing pre-existing belongs to the lake unless wired. The spill cell (outlet_path[0]) now always gets a real entry: appended when new, OVERWRITTEN IN PLACE when it collided with an existing river cell (append would duplicate edge_id; the hydrology solve is the more authoritative downstream answer for that cell than flat D8 extraction). Interior stop-on-collision stays, now provably safe. Internal lookup is a dense Vec<Option<usize>>, never iterated (D-010). Two non-vacuous regression tests prove the cue through build_edges output; end-to-end on GJ1c all 51 Overflow basins now build a readable edge (was: one silently missing). Hoshe finding 2: both doc sites now state the fallback-vs-production split explicitly (fallback moisture 55: 51/2; production GJ1c moisture 80: 53/53 all-Overflow) — the golden's Endorheic pair is a fallback-constant artifact, not a fact about GJ1c. Golden re-regenerated: river_cells 143->192, position-identity diff purely additive (zero removed, one legitimate in-place overwrite at the spill-collision cell); attractors/basins/mouths/confluences byte-identical. Suites: hydrology 26/26, full lib 1943, cascade_golden 1/1, window goldens + believability untouched green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -424,44 +424,75 @@ pub fn solve(
|
|||||||
/// byte-identical order. No hashing, no `HashMap`, nothing keyed on wall-clock
|
/// byte-identical order. No hashing, no `HashMap`, nothing keyed on wall-clock
|
||||||
/// or thread scheduling.
|
/// or thread scheduling.
|
||||||
///
|
///
|
||||||
/// **Cell selection.** `outlet_path` is "inclusive of both ends" (the basin's
|
/// **Cell selection — the spill cell ALWAYS gets a real outflow pointer
|
||||||
/// `spill_cell` through the downstream terminus) — the spill cell itself is
|
/// (PR #202 review, Hoshe finding 1).** `outlet_path` is "inclusive of both
|
||||||
/// already part of the basin's own lake footprint (a `Lake` gridunit, not a
|
/// ends": index 0 is the basin's `spill_cell` (a `Lake` gridunit at the
|
||||||
/// river cell), so it is skipped; every remaining path cell becomes a new
|
/// basin's own boundary — morphology sourcing and river-cell membership are
|
||||||
/// river cell whose downstream pointer is the real D8 direction toward the
|
/// independent, T-1184, so a cell can legitimately be both), index
|
||||||
/// NEXT path cell (mirroring `extract_river_network`'s own `fdir[i]`
|
/// `len - 1` is the downstream terminus. The spill cell is the ONE cell that
|
||||||
/// convention exactly — `outlet_path` is itself a chain of true D8 neighbors,
|
/// visually anchors the lake's exit: every course drawn on the map starts at
|
||||||
/// guaranteed by construction: [`cheapest_overflow_path`]'s Dijkstra only
|
/// a `RiverEdge::upstream` position ([`crate::atlas::river_course::
|
||||||
/// ever relaxes D8-adjacent cells). The final path cell's downstream pointer
|
/// invent_course`]'s `anchor_a`), so unless the spill cell itself carries an
|
||||||
/// is set from `downstream_target`: [`DownstreamTarget::Sea`] maps to
|
/// outflow-pointing entry, NOTHING in the served network visually touches
|
||||||
/// [`RIVER_DOWNSTREAM_MOUTH`] (a real river reaching the sea IS a mouth, the
|
/// this lake — a real `Overflow` basin would read exactly like `Endorheic`
|
||||||
/// same semantics `extract_river_network` already assigns), and
|
/// (no course leaving the lake), which is the CUE this ticket exists to
|
||||||
/// [`DownstreamTarget::Basin`]/[`DownstreamTarget::OpenSpillway`] both map to
|
/// deliver, not an incidental gap. This is why the spill cell is never
|
||||||
/// [`RIVER_DOWNSTREAM_EDGE_DRAIN`] (the course simply ends at the last
|
/// skipped, unconditionally: it always gets an entry pointing along
|
||||||
/// station — there is no meaningful "direction" once the outlet has reached
|
/// `outlet_path` (D8 direction toward `outlet_path[1]`, or straight to the
|
||||||
/// another lake's footprint or open ground, the same semantics `build_edges`
|
/// terminus sentinel on a length-1 residual path — see below), whether or
|
||||||
/// already gives a grid-artifact edge-drain: no further chord to invent).
|
/// not it was already a `river_cells` member. If it WAS already present (a
|
||||||
|
/// real, if rare, case — verified on GJ1c: 2/51 real `Overflow` basins have
|
||||||
|
/// a pre-existing-river-cell spill point), its EXISTING entry is
|
||||||
|
/// overwritten in place (same array index, same position, new downstream
|
||||||
|
/// direction/class/seaward) rather than appended — appending would create a
|
||||||
|
/// second `river_cells` entry at the same `(row, col)`, which
|
||||||
|
/// [`crate::atlas::river_course::build_edges`] would turn into two
|
||||||
|
/// `RiverEdge`s sharing the same `edge_id` (`pack_cell_id` is a pure
|
||||||
|
/// function of position, Ruling 2d), corrupting the
|
||||||
|
/// one-edge-per-upstream-cell invariant `edge_ids_are_unique` guards.
|
||||||
|
/// Overwriting is correct, not merely safe: the pre-existing pointer was
|
||||||
|
/// computed by `extract_river_network`'s D8 walk on the ORIGINAL
|
||||||
|
/// (unfilled) surface, which has no knowledge of the basin's spill
|
||||||
|
/// direction — the hydrology solve is the more authoritative answer for
|
||||||
|
/// what water actually does at this specific cell once the basin is full,
|
||||||
|
/// so it wins.
|
||||||
///
|
///
|
||||||
/// **Cells already present in `river_cells` are skipped** (an outlet path can
|
/// Every remaining interior path cell (index 1 through `len - 2`) becomes a
|
||||||
/// legitimately re-enter the D8-extracted network, e.g. `DownstreamTarget::
|
/// new river cell whose downstream pointer is the real D8 direction toward
|
||||||
/// Basin` chaining through a stretch of terrain the original extraction
|
/// the NEXT path cell (mirroring `extract_river_network`'s own `fdir[i]`
|
||||||
/// already classified as a river) — re-adding them would create duplicate
|
/// convention exactly — `outlet_path` is itself a chain of true D8
|
||||||
/// entries in the parallel arrays [`crate::atlas::river_course::build_edges`]
|
/// neighbors, guaranteed by construction: [`cheapest_overflow_path`]'s
|
||||||
/// enumerates by index, corrupting the one-edge-per-upstream-cell invariant
|
/// Dijkstra only ever relaxes D8-adjacent cells). The final path cell's
|
||||||
/// (Ruling 2d). The chain still connects visually: the LAST cell before the
|
/// downstream pointer is set from `downstream_target`: [`DownstreamTarget::
|
||||||
/// duplicate correctly points its D8 direction at the existing river cell,
|
/// Sea`] maps to [`RIVER_DOWNSTREAM_MOUTH`] (a real river reaching the sea
|
||||||
/// which already has its own onward pointer — the course simply continues
|
/// IS a mouth, the same semantics `extract_river_network` already assigns),
|
||||||
/// through machinery that was already there.
|
/// and [`DownstreamTarget::Basin`]/[`DownstreamTarget::OpenSpillway`] both
|
||||||
|
/// map to [`RIVER_DOWNSTREAM_EDGE_DRAIN`] (the course simply ends at the
|
||||||
|
/// last station — there is no meaningful "direction" once the outlet has
|
||||||
|
/// reached another lake's footprint or open ground, the same semantics
|
||||||
|
/// `build_edges` already gives a grid-artifact edge-drain: no further chord
|
||||||
|
/// to invent).
|
||||||
///
|
///
|
||||||
/// **`EdgeUnreachable` and empty/single-cell paths contribute nothing** — a
|
/// **Interior cells (index ≥ 1) already present in `river_cells` STOP the
|
||||||
/// path of length ≤ 1 (just the spill cell, already skipped) has no interior
|
/// walk** (an outlet path can legitimately re-enter the D8-extracted
|
||||||
/// cell to assign a direction to, so no new edge is created. This is the
|
/// network, e.g. `DownstreamTarget::Basin` chaining through a stretch of
|
||||||
/// correct behaviour, not a gap: a basin whose overflow search exhausted its
|
/// terrain the original extraction already classified as a river) — this is
|
||||||
/// budget without finding a real terminus has no honest direction to draw a
|
/// safe (unlike the spill-cell case above) precisely BECAUSE the spill cell
|
||||||
/// river in, and the module's own guarantee
|
/// always got its own real entry first: the chain from the lake is never
|
||||||
/// (`overflowing_basin_has_nonempty_outlet_path`) is about `Overflow`
|
/// silently dropped, only its LATER re-entry into pre-existing machinery is
|
||||||
/// basins having a *search path*, not that every search path resolves to a
|
/// deduplicated. The cell immediately before the collision (which may BE the
|
||||||
/// non-trivial extension.
|
/// spill cell itself, on a length-2 path) correctly points its D8 direction
|
||||||
|
/// at the existing river cell, which already has its own onward pointer —
|
||||||
|
/// the course continues through machinery that was already there.
|
||||||
|
///
|
||||||
|
/// **`EdgeUnreachable` paths and a length-1 residual (spill-only) path still
|
||||||
|
/// get the spill cell's real entry, just with no interior D8 hop.** A path
|
||||||
|
/// of length ≤ 1 (`outlet_path == [spill]` — the `EdgeUnreachable`
|
||||||
|
/// best-effort case, or any basin whose search terminates immediately) has
|
||||||
|
/// no `outlet_path[1]` to point toward, so the spill cell's downstream
|
||||||
|
/// sentinel is taken directly from `downstream_target` (the same terminus
|
||||||
|
/// mapping the multi-cell path's LAST cell uses) instead of a D8 direction —
|
||||||
|
/// still a real, readable outflow entry, never silently dropped.
|
||||||
///
|
///
|
||||||
/// **`river_class` scope note.** Every appended cell is classified `0`
|
/// **`river_class` scope note.** Every appended cell is classified `0`
|
||||||
/// (stream — [`crate::atlas::river_course`]'s narrowest, most conservative
|
/// (stream — [`crate::atlas::river_course`]'s narrowest, most conservative
|
||||||
@@ -479,17 +510,24 @@ pub fn extend_river_network_with_basin_outlets(
|
|||||||
height: u32,
|
height: u32,
|
||||||
mut network: crate::atlas::body_world_state::RiverNetwork,
|
mut network: crate::atlas::body_world_state::RiverNetwork,
|
||||||
) -> crate::atlas::body_world_state::RiverNetwork {
|
) -> crate::atlas::body_world_state::RiverNetwork {
|
||||||
use crate::atlas::body_world_state::{RIVER_DOWNSTREAM_EDGE_DRAIN, RIVER_DOWNSTREAM_MOUTH};
|
use crate::atlas::body_world_state::RIVER_DOWNSTREAM_MOUTH;
|
||||||
|
|
||||||
let w = width as usize;
|
let w = width as usize;
|
||||||
|
let n = (width as usize) * (height as usize);
|
||||||
|
|
||||||
// O(1) membership test against the network's pre-existing river cells —
|
// Position -> array-index for the network's pre-existing river cells — a
|
||||||
// built once, outside the basin loop (basins can number in the thousands
|
// dense `Vec` (same shape/determinism story as `is_lake`/`basin_of`
|
||||||
// on a real body, per the T-1177 population survey: 22,270 basins across
|
// above: O(1) point lookup, never iterated, so there is no D-010
|
||||||
// 267 bodies).
|
// HashMap-iteration-order concern to even raise). Built once, outside
|
||||||
let mut is_river_cell = vec![false; (width as usize) * (height as usize)];
|
// the basin loop (basins can number in the thousands on a real body, per
|
||||||
for &(r, c) in &network.river_cells {
|
// the T-1177 population survey: 22,270 basins across 267 bodies).
|
||||||
is_river_cell[r as usize * w + c as usize] = true;
|
// Doubles as the membership test the interior-cell dedup walk needs; the
|
||||||
|
// INDEX half is what the spill-cell overwrite case (Hoshe finding 1)
|
||||||
|
// needs to mutate the correct existing entry in place rather than
|
||||||
|
// appending a duplicate.
|
||||||
|
let mut river_cell_index: Vec<Option<usize>> = vec![None; n];
|
||||||
|
for (idx, &(r, c)) in network.river_cells.iter().enumerate() {
|
||||||
|
river_cell_index[r as usize * w + c as usize] = Some(idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Basins are already stored in ascending `basin_id` order (the `(0..
|
// Basins are already stored in ascending `basin_id` order (the `(0..
|
||||||
@@ -504,25 +542,62 @@ pub fn extend_river_network_with_basin_outlets(
|
|||||||
else {
|
else {
|
||||||
continue; // Endorheic — no continuation; absence IS the cue.
|
continue; // Endorheic — no continuation; absence IS the cue.
|
||||||
};
|
};
|
||||||
|
if outlet_path.is_empty() {
|
||||||
|
continue; // Degenerate — no spill cell at all to anchor from.
|
||||||
|
}
|
||||||
|
|
||||||
|
// The spill cell's downstream sentinel (fn doc's "Cell selection"
|
||||||
|
// section): real D8 direction toward `outlet_path[1]` when an
|
||||||
|
// interior cell exists, otherwise the terminus mapping directly (a
|
||||||
|
// length-1 residual/EdgeUnreachable path) — either way, ALWAYS a
|
||||||
|
// real entry, never skipped.
|
||||||
|
let spill = outlet_path[0];
|
||||||
|
let (sr, sc) = (spill / w, spill % w);
|
||||||
|
let spill_sentinel = if outlet_path.len() >= 2 {
|
||||||
|
let next = outlet_path[1];
|
||||||
|
let (nr, nc) = (next / w, next % w);
|
||||||
|
d8_direction_between((sr, sc), (nr, nc), w)
|
||||||
|
} else {
|
||||||
|
terminus_sentinel(downstream_target)
|
||||||
|
};
|
||||||
|
let spill_seaward = seaward_for(spill_sentinel, sr, sc, RIVER_DOWNSTREAM_MOUTH);
|
||||||
|
|
||||||
|
match river_cell_index[spill] {
|
||||||
|
Some(existing_idx) => {
|
||||||
|
// Overwrite in place (fn doc: the hydrology solve is the
|
||||||
|
// more authoritative answer for this cell's true downstream
|
||||||
|
// direction than the original flat D8 extraction) — never
|
||||||
|
// append, which would duplicate `edge_id` at this position.
|
||||||
|
network.river_class[existing_idx] = 0;
|
||||||
|
network.river_downstream[existing_idx] = spill_sentinel;
|
||||||
|
network.river_seaward[existing_idx] = spill_seaward;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let new_idx = network.river_cells.len();
|
||||||
|
network.river_cells.push((sr as u16, sc as u16));
|
||||||
|
network.river_class.push(0);
|
||||||
|
network.river_downstream.push(spill_sentinel);
|
||||||
|
network.river_seaward.push(spill_seaward);
|
||||||
|
river_cell_index[spill] = Some(new_idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// `outlet_path` is inclusive of the spill cell (index 0) — skip it,
|
|
||||||
// it's a lake cell, not a river cell. Fewer than 2 entries means no
|
|
||||||
// interior cell exists to extend from (the EdgeUnreachable
|
|
||||||
// best-effort `vec![spill]` case, or a degenerate empty path).
|
|
||||||
if outlet_path.len() < 2 {
|
if outlet_path.len() < 2 {
|
||||||
continue;
|
continue; // No interior cell beyond the spill entry above.
|
||||||
}
|
}
|
||||||
|
|
||||||
for i in 1..outlet_path.len() {
|
for i in 1..outlet_path.len() {
|
||||||
let cell = outlet_path[i];
|
let cell = outlet_path[i];
|
||||||
let (r, c) = (cell / w, cell % w);
|
let (r, c) = (cell / w, cell % w);
|
||||||
if is_river_cell[cell] {
|
if river_cell_index[cell].is_some() {
|
||||||
// Re-entered the pre-existing D8 network (e.g. chained into
|
// Re-entered the pre-existing D8 network (e.g. chained into
|
||||||
// another basin's already-extracted river reach) — the prior
|
// another basin's already-extracted river reach) — the prior
|
||||||
// cell's downstream pointer (set below, on the PREVIOUS loop
|
// cell's downstream pointer (the spill entry above, or a
|
||||||
// iteration or as the loop's own boundary case) already
|
// PREVIOUS loop iteration) already points here, so the chain
|
||||||
// points here, so the chain is visually continuous without
|
// is visually continuous without adding a duplicate entry.
|
||||||
// adding a duplicate entry.
|
// Safe here (unlike the spill cell) because the spill entry
|
||||||
|
// above already guarantees the lake's own outflow is never
|
||||||
|
// silently dropped — this only dedups a LATER re-entry.
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -533,50 +608,60 @@ pub fn extend_river_network_with_basin_outlets(
|
|||||||
let (nr, nc) = (next / w, next % w);
|
let (nr, nc) = (next / w, next % w);
|
||||||
d8_direction_between((r, c), (nr, nc), w)
|
d8_direction_between((r, c), (nr, nc), w)
|
||||||
} else {
|
} else {
|
||||||
match downstream_target {
|
terminus_sentinel(downstream_target)
|
||||||
DownstreamTarget::Sea => RIVER_DOWNSTREAM_MOUTH,
|
|
||||||
DownstreamTarget::Basin(_) | DownstreamTarget::OpenSpillway => {
|
|
||||||
RIVER_DOWNSTREAM_EDGE_DRAIN
|
|
||||||
}
|
|
||||||
// EdgeUnreachable never reaches here: it only ever
|
|
||||||
// carries a length-≤-1 path (see `cheapest_overflow_path`),
|
|
||||||
// already filtered above.
|
|
||||||
DownstreamTarget::EdgeUnreachable => RIVER_DOWNSTREAM_EDGE_DRAIN,
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let new_idx = network.river_cells.len();
|
||||||
network.river_cells.push((r as u16, c as u16));
|
network.river_cells.push((r as u16, c as u16));
|
||||||
network.river_class.push(0); // stream — fixed default, see fn doc's scope note
|
network.river_class.push(0); // stream — fixed default, see fn doc's scope note
|
||||||
network.river_downstream.push(sentinel);
|
network.river_downstream.push(sentinel);
|
||||||
network
|
network
|
||||||
.river_seaward
|
.river_seaward
|
||||||
.push(if sentinel == RIVER_DOWNSTREAM_MOUTH {
|
.push(seaward_for(sentinel, r, c, RIVER_DOWNSTREAM_MOUTH));
|
||||||
// A real mouth needs a real seaward neighbor for
|
river_cell_index[cell] = Some(new_idx);
|
||||||
// `build_edges`' Mouth-chord synthesis (Ruling 2b's own
|
|
||||||
// precedent — see `RiverNetwork::river_seaward`'s doc). The
|
|
||||||
// outlet's own next-cell-below-sea-level is exactly that:
|
|
||||||
// `cheapest_overflow_path`'s Sea termination condition is
|
|
||||||
// `original[idx] <= sea_scaled` AT THIS CELL, so `(r, c)`
|
|
||||||
// itself is already the sub-sea-level neighbor being
|
|
||||||
// recorded — but `river_seaward` wants the SEAWARD cell, one
|
|
||||||
// step further than the last land cell. Since this loop only
|
|
||||||
// ever visits path cells up to and including the terminus,
|
|
||||||
// and the terminus here has original elevation <= sea_scaled
|
|
||||||
// by construction, (r, c) IS a below-sea-level cell — use it
|
|
||||||
// directly (matches `extract_river_network`'s own `(nr, nc)`
|
|
||||||
// capture, which likewise stores the sub-sea-level neighbor
|
|
||||||
// itself, not a cell beyond it).
|
|
||||||
(r as u16, c as u16)
|
|
||||||
} else {
|
|
||||||
(0, 0)
|
|
||||||
});
|
|
||||||
is_river_cell[cell] = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
network
|
network
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Terminus sentinel for a [`BasinOutcome::Overflow`]'s downstream end, from
|
||||||
|
/// [`DownstreamTarget`] — shared by both the spill-cell entry (a length-1
|
||||||
|
/// residual path with no interior cell to point at) and the last interior
|
||||||
|
/// path cell's entry (the common multi-cell case).
|
||||||
|
/// [`DownstreamTarget::Sea`] maps to [`RIVER_DOWNSTREAM_MOUTH`] (a real
|
||||||
|
/// river reaching the sea IS a mouth, the same semantics
|
||||||
|
/// `extract_river_network` already assigns); every other variant
|
||||||
|
/// ([`DownstreamTarget::Basin`], [`DownstreamTarget::OpenSpillway`],
|
||||||
|
/// [`DownstreamTarget::EdgeUnreachable`]) maps to
|
||||||
|
/// [`RIVER_DOWNSTREAM_EDGE_DRAIN`] — no further chord to invent once the
|
||||||
|
/// outlet has reached another lake's footprint, open ground, or exhausted
|
||||||
|
/// its search budget.
|
||||||
|
fn terminus_sentinel(target: &DownstreamTarget) -> u8 {
|
||||||
|
use crate::atlas::body_world_state::{RIVER_DOWNSTREAM_EDGE_DRAIN, RIVER_DOWNSTREAM_MOUTH};
|
||||||
|
match target {
|
||||||
|
DownstreamTarget::Sea => RIVER_DOWNSTREAM_MOUTH,
|
||||||
|
DownstreamTarget::Basin(_)
|
||||||
|
| DownstreamTarget::OpenSpillway
|
||||||
|
| DownstreamTarget::EdgeUnreachable => RIVER_DOWNSTREAM_EDGE_DRAIN,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [`RiverNetwork::river_seaward`] value for a cell whose downstream
|
||||||
|
/// sentinel is `sentinel` — a real seaward neighbor when `sentinel ==
|
||||||
|
/// mouth_sentinel` (see [`extend_river_network_with_basin_outlets`]'s doc on
|
||||||
|
/// why `(r, c)` itself, not a cell beyond it, is the correct seaward value
|
||||||
|
/// here — mirrors `extract_river_network`'s own `(nr, nc)` capture), the
|
||||||
|
/// unreadable `(0, 0)` placeholder otherwise (matches every non-MOUTH
|
||||||
|
/// `river_seaward` entry across the codebase).
|
||||||
|
fn seaward_for(sentinel: u8, r: usize, c: usize, mouth_sentinel: u8) -> (u16, u16) {
|
||||||
|
if sentinel == mouth_sentinel {
|
||||||
|
(r as u16, c as u16)
|
||||||
|
} else {
|
||||||
|
(0, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The D8 direction index `k` (matching [`d8_offset`]'s table) such that
|
/// The D8 direction index `k` (matching [`d8_offset`]'s table) such that
|
||||||
/// stepping from `(r, c)` by `d8_offset(k)` (with horizontal wraparound,
|
/// stepping from `(r, c)` by `d8_offset(k)` (with horizontal wraparound,
|
||||||
/// [`crate::atlas::drainage`]'s convention) reaches `(nr, nc)`. Panics if the
|
/// [`crate::atlas::drainage`]'s convention) reaches `(nr, nc)`. Panics if the
|
||||||
@@ -1536,6 +1621,152 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// PR #202 review, Hoshe finding 1 — the i==1 collision regression,
|
||||||
|
/// asserted NON-VACUOUSLY on the actual D-227 amendment (4) cue: when
|
||||||
|
/// `outlet_path[1]` (the FIRST interior cell after the spill) is already
|
||||||
|
/// a pre-existing river cell, the basin's Overflow status must still be
|
||||||
|
/// READABLE via `build_edges` — a course must leave the lake. Before the
|
||||||
|
/// fix, this exact scenario made the loop `break` on its first
|
||||||
|
/// iteration having pushed zero cells for the basin, silently making a
|
||||||
|
/// real Overflow basin indistinguishable from Endorheic.
|
||||||
|
#[test]
|
||||||
|
fn overflow_basin_survives_an_i_equals_one_collision_non_vacuously() {
|
||||||
|
let result = overflow_bowl_result();
|
||||||
|
let overflow = result
|
||||||
|
.basins
|
||||||
|
.iter()
|
||||||
|
.find(|b| matches!(b.outcome, BasinOutcome::Overflow { .. }))
|
||||||
|
.expect("fixture sanity: at least one Overflow basin");
|
||||||
|
let BasinOutcome::Overflow { outlet_path, .. } = &overflow.outcome else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
assert!(
|
||||||
|
outlet_path.len() >= 2,
|
||||||
|
"fixture sanity: outlet_path must have an interior cell at index 1 \
|
||||||
|
to pre-seed as the i==1 collision"
|
||||||
|
);
|
||||||
|
let spill = outlet_path[0];
|
||||||
|
let collision_cell = outlet_path[1]; // the exact i==1 collision position
|
||||||
|
let (sr, sc) = (spill / 64, spill % 64);
|
||||||
|
let (cr, cc) = (collision_cell / 64, collision_cell % 64);
|
||||||
|
|
||||||
|
// Pre-seed the network with ONLY the i==1 cell as a native river
|
||||||
|
// cell (pointing at some unrelated existing direction) — the spill
|
||||||
|
// cell itself is NOT pre-existing, isolating this as the exact
|
||||||
|
// "first interior cell collides" scenario Hoshe's finding names.
|
||||||
|
let mut network = empty_network();
|
||||||
|
network.river_cells.push((cr as u16, cc as u16));
|
||||||
|
network.river_class.push(1);
|
||||||
|
network
|
||||||
|
.river_downstream
|
||||||
|
.push(crate::atlas::body_world_state::RIVER_DOWNSTREAM_EDGE_DRAIN);
|
||||||
|
network.river_seaward.push((0, 0));
|
||||||
|
|
||||||
|
let after = extend_river_network_with_basin_outlets(&result, 64, 32, network);
|
||||||
|
|
||||||
|
// The spill cell must have its OWN entry — this is the actual fix:
|
||||||
|
// before it, nothing was ever pushed for this basin at all.
|
||||||
|
let spill_idx = after
|
||||||
|
.river_cells
|
||||||
|
.iter()
|
||||||
|
.position(|&cell| cell == (sr as u16, sc as u16))
|
||||||
|
.expect(
|
||||||
|
"the spill cell must have a real river_cells entry even when \
|
||||||
|
outlet_path[1] collides with a pre-existing river cell — this \
|
||||||
|
is the exact bug PR #202 flagged: a silent break at i==1 must \
|
||||||
|
never erase the basin's own outflow anchor",
|
||||||
|
);
|
||||||
|
// Its downstream pointer must be a real D8 direction (not a sentinel)
|
||||||
|
// toward the collision cell, since outlet_path[1] IS the next hop.
|
||||||
|
assert!(
|
||||||
|
after.river_downstream[spill_idx] < 8,
|
||||||
|
"the spill cell's downstream pointer must be a real D8 direction \
|
||||||
|
toward outlet_path[1], not a terminus sentinel"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Non-vacuous on the cue itself: build_edges must produce a real
|
||||||
|
// edge whose upstream IS the spill cell — an overflow lake's exit
|
||||||
|
// river must be readable from the served network, not just present
|
||||||
|
// as raw unreachable array data.
|
||||||
|
let edges = crate::atlas::river_course::build_edges(&after);
|
||||||
|
assert!(
|
||||||
|
edges.iter().any(|e| e.upstream == (sr as u16, sc as u16)),
|
||||||
|
"an Overflow basin's spill cell must produce a real RiverEdge even \
|
||||||
|
when its first interior outlet cell collides with a pre-existing \
|
||||||
|
river cell — otherwise this basin is visually indistinguishable \
|
||||||
|
from Endorheic, which is exactly the cue this ticket must not break"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The sibling collision case: the SPILL CELL ITSELF (not an interior
|
||||||
|
/// cell) is already a pre-existing river cell — verified to occur on
|
||||||
|
/// real GJ1c data (2/51 real Overflow basins). The pre-existing entry's
|
||||||
|
/// downstream pointer must be overwritten to point along the outlet
|
||||||
|
/// path (the hydrology solve is the more authoritative answer for this
|
||||||
|
/// cell's true direction), not left pointing wherever the original flat
|
||||||
|
/// D8 extraction guessed — and it must not create a duplicate
|
||||||
|
/// `river_cells` entry at the same position.
|
||||||
|
#[test]
|
||||||
|
fn overflow_basin_overwrites_a_pre_existing_spill_cell_in_place() {
|
||||||
|
let result = overflow_bowl_result();
|
||||||
|
let overflow = result
|
||||||
|
.basins
|
||||||
|
.iter()
|
||||||
|
.find(|b| matches!(b.outcome, BasinOutcome::Overflow { .. }))
|
||||||
|
.expect("fixture sanity: at least one Overflow basin");
|
||||||
|
let BasinOutcome::Overflow { outlet_path, .. } = &overflow.outcome else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
assert!(
|
||||||
|
outlet_path.len() >= 2,
|
||||||
|
"fixture sanity: needs an interior cell"
|
||||||
|
);
|
||||||
|
let spill = outlet_path[0];
|
||||||
|
let (sr, sc) = (spill / 64, spill % 64);
|
||||||
|
|
||||||
|
// Pre-seed the network with the SPILL cell as a native river cell,
|
||||||
|
// pointing in an unrelated direction (simulating the original flat
|
||||||
|
// D8 extraction having already claimed this cell for its own,
|
||||||
|
// wrong-for-the-lake reasons).
|
||||||
|
let mut network = empty_network();
|
||||||
|
network.river_cells.push((sr as u16, sc as u16));
|
||||||
|
network.river_class.push(2); // deliberately a different class
|
||||||
|
network.river_downstream.push(0); // deliberately a real but likely-wrong direction
|
||||||
|
network.river_seaward.push((0, 0));
|
||||||
|
|
||||||
|
let after = extend_river_network_with_basin_outlets(&result, 64, 32, network);
|
||||||
|
|
||||||
|
// Exactly one entry at the spill position — overwritten, not duplicated.
|
||||||
|
let occurrences = after
|
||||||
|
.river_cells
|
||||||
|
.iter()
|
||||||
|
.filter(|&&cell| cell == (sr as u16, sc as u16))
|
||||||
|
.count();
|
||||||
|
assert_eq!(
|
||||||
|
occurrences, 1,
|
||||||
|
"the spill cell must be overwritten in place, never duplicated"
|
||||||
|
);
|
||||||
|
let spill_idx = after
|
||||||
|
.river_cells
|
||||||
|
.iter()
|
||||||
|
.position(|&cell| cell == (sr as u16, sc as u16))
|
||||||
|
.unwrap();
|
||||||
|
assert!(
|
||||||
|
after.river_downstream[spill_idx] < 8,
|
||||||
|
"the overwritten spill cell must point along the real outlet path, \
|
||||||
|
not retain whatever direction the pre-existing entry had"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Non-vacuous on the cue: the overwritten spill cell must still
|
||||||
|
// build into a real, readable RiverEdge.
|
||||||
|
let edges = crate::atlas::river_course::build_edges(&after);
|
||||||
|
assert!(
|
||||||
|
edges.iter().any(|e| e.upstream == (sr as u16, sc as u16)),
|
||||||
|
"an overwritten spill cell must still produce a real RiverEdge — \
|
||||||
|
the lake's outflow must remain readable"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn d8_direction_between_matches_the_offset_table() {
|
fn d8_direction_between_matches_the_offset_table() {
|
||||||
// Round-trip sanity: for every direction in the D8 table, stepping
|
// Round-trip sanity: for every direction in the D8 table, stepping
|
||||||
|
|||||||
@@ -83,6 +83,23 @@ pub struct Layer1Output {
|
|||||||
/// used ONLY by [`run_layer1`]'s two-arg form; [`run_layer1_with_moisture`]
|
/// used ONLY by [`run_layer1`]'s two-arg form; [`run_layer1_with_moisture`]
|
||||||
/// (called by every production site that has real `BodyParams` in scope) never
|
/// (called by every production site that has real `BodyParams` in scope) never
|
||||||
/// reaches this constant.
|
/// reaches this constant.
|
||||||
|
///
|
||||||
|
/// **This constant is NOT what a player sees on any real body (PR #202
|
||||||
|
/// review, Hoshe finding 2) — never cite a basin's Overflow/Endorheic split
|
||||||
|
/// at this moisture value as representative of production.** The
|
||||||
|
/// endorheic-vs-overflow decision (`hydrology_equilibrium::is_endorheic`)
|
||||||
|
/// gates on `moisture_q <= ENDORHEIC_MOISTURE_CEILING (60)`; this fallback
|
||||||
|
/// (55) sits just BELOW that ceiling, so a fallback-path solve can show
|
||||||
|
/// Endorheic basins that would never occur on the real body. Concretely, on
|
||||||
|
/// GJ1c (`hydrosphere: liquid_water`, `atmosphere: standard` →
|
||||||
|
/// `derive_moisture_ceiling_q` = 80, well ABOVE the ceiling): the fallback
|
||||||
|
/// path (55) splits its 53 real basins 51 Overflow / 2 Endorheic, while the
|
||||||
|
/// PRODUCTION path (80, via `run_layer1_with_moisture` at a real call site)
|
||||||
|
/// is 53/53 all-Overflow on the identical geometry — moisture is the only
|
||||||
|
/// variable that moves (`filled_scaled`/basin geometry itself is
|
||||||
|
/// moisture-independent). Any doc, golden comment, or report citing a
|
||||||
|
/// basin-outcome ratio must state which moisture path produced it — see
|
||||||
|
/// `tests/cascade_golden.rs`'s own re-pin note for the worked example.
|
||||||
const DEFAULT_HYDROLOGY_MOISTURE_Q: i32 = 55;
|
const DEFAULT_HYDROLOGY_MOISTURE_Q: i32 = 55;
|
||||||
|
|
||||||
/// Run the Layer-1 topography pipeline for a single body.
|
/// Run the Layer-1 topography pipeline for a single body.
|
||||||
|
|||||||
@@ -61,28 +61,57 @@
|
|||||||
//! D8 river-network wiring):** `run_layer1`/`run_layer1_with_moisture` now
|
//! D8 river-network wiring):** `run_layer1`/`run_layer1_with_moisture` now
|
||||||
//! extends the D8-extracted `RiverNetwork` with every `BasinOutcome::
|
//! extends the D8-extracted `RiverNetwork` with every `BasinOutcome::
|
||||||
//! Overflow` basin's `outlet_path` as new river cells (the endorheic cue:
|
//! Overflow` basin's `outlet_path` as new river cells (the endorheic cue:
|
||||||
//! outflow-course PRESENCE). On this fixture (GJ1c, 256×128 downsample,
|
//! outflow-course PRESENCE). `attractors` (256) and `drainage_basins` are
|
||||||
//! `moisture_q=55` — `run_cascade_from_heightmap`'s `body_params: None`
|
//! UNCHANGED — the extension runs strictly after
|
||||||
//! path), 51 of the working grid's 53 real basins classify `Overflow`;
|
//! `features::extract_attractors`, by design, so basin-outlet cells never
|
||||||
//! their outlets add exactly **50 new river cells** (`river_cells`
|
//! perturb geographic attractor placement. `mouths`/`confluences` also
|
||||||
//! 93→143 — one basin's single new cell coincides with a cell another
|
//! unchanged (T-1185 never rewrites those arrays).
|
||||||
//! basin's outlet already added, correctly deduplicated). `attractors`
|
//!
|
||||||
//! (256) and `drainage_basins` are UNCHANGED — the extension runs strictly
|
//! **This golden pins the FALLBACK moisture path, not production — stated
|
||||||
//! after `features::extract_attractors`, by design, so basin-outlet cells
|
//! explicitly so nobody cites its basin-outcome split as what a player
|
||||||
//! never perturb geographic attractor placement. `mouths`/`confluences`
|
//! actually sees (PR #202 review, Hoshe finding 2).**
|
||||||
//! also unchanged (T-1185 never rewrites those arrays, only appends to
|
//! `run_cascade_from_heightmap`'s `body_params: None` argument here means
|
||||||
//! `river_cells`/`river_class`/`river_downstream`/`river_seaward`). Every
|
//! `run_layer1`'s `DEFAULT_HYDROLOGY_MOISTURE_Q = 55` fallback is what
|
||||||
//! appended cell on this real body carries `river_downstream =
|
//! solves this fixture's hydrology, not GJ1c's REAL body params
|
||||||
//! RIVER_DOWNSTREAM_EDGE_DRAIN` (9) — verified separately (a scratch probe,
|
//! (`wiki/star-systems/GJ-1/bodies/GJ1c/index.md`: `hydrosphere:
|
||||||
//! not committed) that all 51 real `Overflow` basins here have a
|
//! liquid_water`, `atmosphere: standard` → `derive_moisture_ceiling_q`
|
||||||
//! length-2 `outlet_path` (`[spill_cell, terminus]`) resolving to
|
//! yields **moisture_q=80**, well above `ENDORHEIC_MOISTURE_CEILING=60`).
|
||||||
//! `DownstreamTarget::OpenSpillway`, the dominant real-world pattern on
|
//! At the fallback `moisture_q=55` this fixture's 53 real working-grid
|
||||||
//! this body (most basins spill onto immediately-adjacent open ground
|
//! basins split 51 Overflow / 2 Endorheic; verified directly (a scratch
|
||||||
//! rather than carving a long channel to the sea) — not a bug or an
|
//! probe, not committed) that at the PRODUCTION `moisture_q=80` the SAME
|
||||||
//! unexercised code path; the interior-D8-direction branch (0–7) and the
|
//! 53 basins are **53/53 all-Overflow — zero Endorheic** (moisture is the
|
||||||
//! `RIVER_DOWNSTREAM_MOUTH` terminus branch are both exercised by the
|
//! only thing that moves; `filled_scaled`/basin geometry itself is
|
||||||
//! dedicated `hydrology_equilibrium`/`layer1` unit tests instead, which use
|
//! moisture-independent, per `run_layer1_with_moisture_changes_
|
||||||
//! synthetic fixtures shaped to hit those specific branches.
|
//! endorheic_split_not_lake_extent`'s own invariant). A materially
|
||||||
|
//! different picture: on the real body, every one of these basins shows an
|
||||||
|
//! exit river on the map — this fixture happening to include 2 Endorheic
|
||||||
|
//! basins is an artifact of testing at the body-agnostic fallback
|
||||||
|
//! constant, not a fact about GJ1c itself.
|
||||||
|
//!
|
||||||
|
//! **Cell count (post PR #202 review fix — the i==1/spill-collision
|
||||||
|
//! regression, Hoshe finding 1):** `river_cells` 93→192 (+99). Every one of
|
||||||
|
//! the 51 real `Overflow` basins now has its spill cell wired as a real
|
||||||
|
//! river-network entry, unconditionally — the earlier landing (143, now
|
||||||
|
//! superseded) silently dropped a basin's ENTIRE outlet whenever
|
||||||
|
//! `outlet_path[1]` collided with a pre-existing river cell (the loop broke
|
||||||
|
//! on its first iteration having pushed nothing), which on THIS fixture
|
||||||
|
//! happened for 1 basin outright and would have made it visually
|
||||||
|
//! indistinguishable from Endorheic — exactly the cue this ticket must
|
||||||
|
//! never break. Verified directly against this fixture (scratch probe, not
|
||||||
|
//! committed): all 51 real `Overflow` basins now build into a real,
|
||||||
|
//! readable `RiverEdge` via `river_course::build_edges` — zero basins
|
||||||
|
//! missing an outlet edge. One pre-existing baseline cell (`(28, 14)`) is
|
||||||
|
//! overwritten in place (its `river_downstream` sentinel changes from
|
||||||
|
//! `RIVER_DOWNSTREAM_EDGE_DRAIN` to a real D8 direction) rather than
|
||||||
|
//! duplicated — the other real spill-cell collision on this fixture lands
|
||||||
|
//! among the newly-appended range, not the original 93-cell baseline.
|
||||||
|
//! Confirmed by direct position-identity diff (not raw array-index
|
||||||
|
//! comparison, which is misleading once the fix reshuffles positions
|
||||||
|
//! within each basin's block): zero positions are ever REMOVED between the
|
||||||
|
//! pre-fix and post-fix goldens, only added-or-overwritten — the fix is
|
||||||
|
//! additive at the position level, exactly as designed. No duplicate
|
||||||
|
//! `(row, col)` positions exist in the final array (verified — the
|
||||||
|
//! `edge_ids_are_unique` invariant `build_edges` depends on holds).
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
|||||||
@@ -8152,86 +8152,170 @@
|
|||||||
124,
|
124,
|
||||||
239
|
239
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
2,
|
||||||
|
99
|
||||||
|
],
|
||||||
[
|
[
|
||||||
1,
|
1,
|
||||||
100
|
100
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
2,
|
||||||
|
137
|
||||||
|
],
|
||||||
[
|
[
|
||||||
1,
|
1,
|
||||||
138
|
138
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
8,
|
||||||
|
194
|
||||||
|
],
|
||||||
[
|
[
|
||||||
7,
|
7,
|
||||||
195
|
195
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
8,
|
||||||
|
204
|
||||||
|
],
|
||||||
[
|
[
|
||||||
7,
|
7,
|
||||||
204
|
204
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
10,
|
||||||
|
68
|
||||||
|
],
|
||||||
[
|
[
|
||||||
10,
|
10,
|
||||||
69
|
69
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
14,
|
||||||
|
29
|
||||||
|
],
|
||||||
[
|
[
|
||||||
14,
|
14,
|
||||||
28
|
28
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
13,
|
||||||
|
47
|
||||||
|
],
|
||||||
[
|
[
|
||||||
14,
|
14,
|
||||||
48
|
48
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
13,
|
||||||
|
172
|
||||||
|
],
|
||||||
[
|
[
|
||||||
14,
|
14,
|
||||||
173
|
173
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
18,
|
||||||
|
179
|
||||||
|
],
|
||||||
[
|
[
|
||||||
19,
|
19,
|
||||||
180
|
180
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
24,
|
||||||
|
152
|
||||||
|
],
|
||||||
[
|
[
|
||||||
25,
|
25,
|
||||||
152
|
152
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
20,
|
||||||
|
165
|
||||||
|
],
|
||||||
[
|
[
|
||||||
21,
|
21,
|
||||||
166
|
166
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
16,
|
||||||
|
199
|
||||||
|
],
|
||||||
[
|
[
|
||||||
17,
|
17,
|
||||||
200
|
200
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
16,
|
||||||
|
6
|
||||||
|
],
|
||||||
[
|
[
|
||||||
17,
|
17,
|
||||||
6
|
6
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
19,
|
||||||
|
181
|
||||||
|
],
|
||||||
[
|
[
|
||||||
20,
|
20,
|
||||||
181
|
181
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
17,
|
||||||
|
45
|
||||||
|
],
|
||||||
[
|
[
|
||||||
16,
|
16,
|
||||||
46
|
46
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
17,
|
||||||
|
92
|
||||||
|
],
|
||||||
[
|
[
|
||||||
17,
|
17,
|
||||||
93
|
93
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
17,
|
||||||
|
126
|
||||||
|
],
|
||||||
[
|
[
|
||||||
17,
|
17,
|
||||||
127
|
127
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
19,
|
||||||
|
223
|
||||||
|
],
|
||||||
[
|
[
|
||||||
19,
|
19,
|
||||||
224
|
224
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
22,
|
||||||
|
231
|
||||||
|
],
|
||||||
[
|
[
|
||||||
21,
|
21,
|
||||||
232
|
232
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
32,
|
||||||
|
254
|
||||||
|
],
|
||||||
[
|
[
|
||||||
33,
|
33,
|
||||||
255
|
255
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
26,
|
||||||
|
103
|
||||||
|
],
|
||||||
[
|
[
|
||||||
27,
|
27,
|
||||||
104
|
104
|
||||||
@@ -8240,114 +8324,226 @@
|
|||||||
29,
|
29,
|
||||||
15
|
15
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
30,
|
||||||
|
61
|
||||||
|
],
|
||||||
[
|
[
|
||||||
31,
|
31,
|
||||||
61
|
61
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
27,
|
||||||
|
237
|
||||||
|
],
|
||||||
[
|
[
|
||||||
27,
|
27,
|
||||||
236
|
236
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
29,
|
||||||
|
138
|
||||||
|
],
|
||||||
[
|
[
|
||||||
30,
|
30,
|
||||||
138
|
138
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
31,
|
||||||
|
9
|
||||||
|
],
|
||||||
[
|
[
|
||||||
31,
|
31,
|
||||||
10
|
10
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
34,
|
||||||
|
137
|
||||||
|
],
|
||||||
[
|
[
|
||||||
34,
|
34,
|
||||||
136
|
136
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
34,
|
||||||
|
156
|
||||||
|
],
|
||||||
[
|
[
|
||||||
35,
|
35,
|
||||||
155
|
155
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
37,
|
||||||
|
73
|
||||||
|
],
|
||||||
[
|
[
|
||||||
37,
|
37,
|
||||||
72
|
72
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
39,
|
||||||
|
64
|
||||||
|
],
|
||||||
[
|
[
|
||||||
39,
|
39,
|
||||||
63
|
63
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
40,
|
||||||
|
235
|
||||||
|
],
|
||||||
[
|
[
|
||||||
41,
|
41,
|
||||||
235
|
235
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
45,
|
||||||
|
123
|
||||||
|
],
|
||||||
[
|
[
|
||||||
44,
|
44,
|
||||||
122
|
122
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
52,
|
||||||
|
241
|
||||||
|
],
|
||||||
[
|
[
|
||||||
51,
|
51,
|
||||||
242
|
242
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
51,
|
||||||
|
0
|
||||||
|
],
|
||||||
[
|
[
|
||||||
52,
|
52,
|
||||||
1
|
1
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
51,
|
||||||
|
109
|
||||||
|
],
|
||||||
[
|
[
|
||||||
50,
|
50,
|
||||||
108
|
108
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
56,
|
||||||
|
40
|
||||||
|
],
|
||||||
[
|
[
|
||||||
57,
|
57,
|
||||||
41
|
41
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
57,
|
||||||
|
63
|
||||||
|
],
|
||||||
[
|
[
|
||||||
56,
|
56,
|
||||||
64
|
64
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
58,
|
||||||
|
215
|
||||||
|
],
|
||||||
[
|
[
|
||||||
57,
|
57,
|
||||||
216
|
216
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
62,
|
||||||
|
43
|
||||||
|
],
|
||||||
[
|
[
|
||||||
62,
|
62,
|
||||||
42
|
42
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
70,
|
||||||
|
233
|
||||||
|
],
|
||||||
[
|
[
|
||||||
70,
|
70,
|
||||||
232
|
232
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
74,
|
||||||
|
228
|
||||||
|
],
|
||||||
[
|
[
|
||||||
74,
|
74,
|
||||||
229
|
229
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
74,
|
||||||
|
230
|
||||||
|
],
|
||||||
[
|
[
|
||||||
74,
|
74,
|
||||||
231
|
231
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
91,
|
||||||
|
214
|
||||||
|
],
|
||||||
[
|
[
|
||||||
92,
|
92,
|
||||||
213
|
213
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
88,
|
||||||
|
37
|
||||||
|
],
|
||||||
[
|
[
|
||||||
88,
|
88,
|
||||||
38
|
38
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
97,
|
||||||
|
89
|
||||||
|
],
|
||||||
[
|
[
|
||||||
96,
|
96,
|
||||||
88
|
88
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
101,
|
||||||
|
215
|
||||||
|
],
|
||||||
[
|
[
|
||||||
101,
|
101,
|
||||||
214
|
214
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
115,
|
||||||
|
40
|
||||||
|
],
|
||||||
[
|
[
|
||||||
114,
|
114,
|
||||||
41
|
41
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
115,
|
||||||
|
60
|
||||||
|
],
|
||||||
[
|
[
|
||||||
115,
|
115,
|
||||||
59
|
59
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
116,
|
||||||
|
220
|
||||||
|
],
|
||||||
[
|
[
|
||||||
117,
|
117,
|
||||||
221
|
221
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
124,
|
||||||
|
25
|
||||||
|
],
|
||||||
[
|
[
|
||||||
124,
|
124,
|
||||||
24
|
24
|
||||||
@@ -8408,7 +8604,7 @@
|
|||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
0,
|
0,
|
||||||
2,
|
0,
|
||||||
1,
|
1,
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
@@ -8496,6 +8692,55 @@
|
|||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
0
|
0
|
||||||
],
|
],
|
||||||
"river_downstream": [
|
"river_downstream": [
|
||||||
@@ -8553,7 +8798,7 @@
|
|||||||
4,
|
4,
|
||||||
9,
|
9,
|
||||||
7,
|
7,
|
||||||
9,
|
6,
|
||||||
3,
|
3,
|
||||||
3,
|
3,
|
||||||
9,
|
9,
|
||||||
@@ -8592,55 +8837,104 @@
|
|||||||
9,
|
9,
|
||||||
2,
|
2,
|
||||||
8,
|
8,
|
||||||
|
4,
|
||||||
|
9,
|
||||||
|
4,
|
||||||
|
9,
|
||||||
|
4,
|
||||||
|
9,
|
||||||
|
0,
|
||||||
|
9,
|
||||||
|
2,
|
||||||
|
9,
|
||||||
|
3,
|
||||||
|
9,
|
||||||
|
6,
|
||||||
|
9,
|
||||||
|
6,
|
||||||
|
9,
|
||||||
|
6,
|
||||||
|
9,
|
||||||
|
1,
|
||||||
|
9,
|
||||||
|
6,
|
||||||
|
9,
|
||||||
|
6,
|
||||||
|
9,
|
||||||
|
1,
|
||||||
|
9,
|
||||||
|
1,
|
||||||
|
9,
|
||||||
|
4,
|
||||||
|
9,
|
||||||
|
2,
|
||||||
|
9,
|
||||||
|
2,
|
||||||
|
9,
|
||||||
|
2,
|
||||||
|
9,
|
||||||
|
4,
|
||||||
|
9,
|
||||||
|
6,
|
||||||
|
9,
|
||||||
|
6,
|
||||||
9,
|
9,
|
||||||
9,
|
9,
|
||||||
|
1,
|
||||||
9,
|
9,
|
||||||
|
3,
|
||||||
9,
|
9,
|
||||||
|
1,
|
||||||
9,
|
9,
|
||||||
|
2,
|
||||||
9,
|
9,
|
||||||
|
3,
|
||||||
9,
|
9,
|
||||||
|
7,
|
||||||
9,
|
9,
|
||||||
|
3,
|
||||||
9,
|
9,
|
||||||
|
3,
|
||||||
9,
|
9,
|
||||||
|
1,
|
||||||
9,
|
9,
|
||||||
|
5,
|
||||||
9,
|
9,
|
||||||
|
4,
|
||||||
9,
|
9,
|
||||||
|
6,
|
||||||
9,
|
9,
|
||||||
|
5,
|
||||||
9,
|
9,
|
||||||
|
6,
|
||||||
9,
|
9,
|
||||||
|
4,
|
||||||
9,
|
9,
|
||||||
|
4,
|
||||||
9,
|
9,
|
||||||
|
3,
|
||||||
9,
|
9,
|
||||||
|
3,
|
||||||
9,
|
9,
|
||||||
|
2,
|
||||||
9,
|
9,
|
||||||
|
2,
|
||||||
9,
|
9,
|
||||||
|
7,
|
||||||
9,
|
9,
|
||||||
|
2,
|
||||||
9,
|
9,
|
||||||
|
5,
|
||||||
9,
|
9,
|
||||||
|
3,
|
||||||
9,
|
9,
|
||||||
|
4,
|
||||||
9,
|
9,
|
||||||
|
3,
|
||||||
9,
|
9,
|
||||||
|
6,
|
||||||
9,
|
9,
|
||||||
9,
|
3,
|
||||||
9,
|
|
||||||
9,
|
|
||||||
9,
|
|
||||||
9,
|
|
||||||
9,
|
|
||||||
9,
|
|
||||||
9,
|
|
||||||
9,
|
|
||||||
9,
|
|
||||||
9,
|
|
||||||
9,
|
|
||||||
9,
|
|
||||||
9,
|
|
||||||
9,
|
|
||||||
9,
|
|
||||||
9,
|
|
||||||
9,
|
|
||||||
9,
|
|
||||||
9,
|
|
||||||
9
|
9
|
||||||
],
|
],
|
||||||
"river_seaward": [
|
"river_seaward": [
|
||||||
@@ -9212,6 +9506,202 @@
|
|||||||
0,
|
0,
|
||||||
0
|
0
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
[
|
[
|
||||||
0,
|
0,
|
||||||
0
|
0
|
||||||
|
|||||||
Reference in New Issue
Block a user