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:
2026-07-25 09:04:54 +02:00
co-authored by Claude Fable 5
parent adceb60865
commit 1d2cac9e65
4 changed files with 896 additions and 129 deletions
+51 -22
View File
@@ -61,28 +61,57 @@
//! D8 river-network wiring):** `run_layer1`/`run_layer1_with_moisture` now
//! extends the D8-extracted `RiverNetwork` with every `BasinOutcome::
//! Overflow` basin's `outlet_path` as new river cells (the endorheic cue:
//! outflow-course PRESENCE). On this fixture (GJ1c, 256×128 downsample,
//! `moisture_q=55` — `run_cascade_from_heightmap`'s `body_params: None`
//! path), 51 of the working grid's 53 real basins classify `Overflow`;
//! their outlets add exactly **50 new river cells** (`river_cells`
//! 93→143 — one basin's single new cell coincides with a cell another
//! basin's outlet already added, correctly deduplicated). `attractors`
//! (256) and `drainage_basins` are UNCHANGED — the extension runs strictly
//! after `features::extract_attractors`, by design, so basin-outlet cells
//! never perturb geographic attractor placement. `mouths`/`confluences`
//! also unchanged (T-1185 never rewrites those arrays, only appends to
//! `river_cells`/`river_class`/`river_downstream`/`river_seaward`). Every
//! appended cell on this real body carries `river_downstream =
//! RIVER_DOWNSTREAM_EDGE_DRAIN` (9) — verified separately (a scratch probe,
//! not committed) that all 51 real `Overflow` basins here have a
//! length-2 `outlet_path` (`[spill_cell, terminus]`) resolving to
//! `DownstreamTarget::OpenSpillway`, the dominant real-world pattern on
//! this body (most basins spill onto immediately-adjacent open ground
//! rather than carving a long channel to the sea) — not a bug or an
//! unexercised code path; the interior-D8-direction branch (0–7) and the
//! `RIVER_DOWNSTREAM_MOUTH` terminus branch are both exercised by the
//! dedicated `hydrology_equilibrium`/`layer1` unit tests instead, which use
//! synthetic fixtures shaped to hit those specific branches.
//! outflow-course PRESENCE). `attractors` (256) and `drainage_basins` are
//! UNCHANGED — the extension runs strictly after
//! `features::extract_attractors`, by design, so basin-outlet cells never
//! perturb geographic attractor placement. `mouths`/`confluences` also
//! unchanged (T-1185 never rewrites those arrays).
//!
//! **This golden pins the FALLBACK moisture path, not production — stated
//! explicitly so nobody cites its basin-outcome split as what a player
//! actually sees (PR #202 review, Hoshe finding 2).**
//! `run_cascade_from_heightmap`'s `body_params: None` argument here means
//! `run_layer1`'s `DEFAULT_HYDROLOGY_MOISTURE_Q = 55` fallback is what
//! solves this fixture's hydrology, not GJ1c's REAL body params
//! (`wiki/star-systems/GJ-1/bodies/GJ1c/index.md`: `hydrosphere:
//! liquid_water`, `atmosphere: standard` → `derive_moisture_ceiling_q`
//! yields **moisture_q=80**, well above `ENDORHEIC_MOISTURE_CEILING=60`).
//! At the fallback `moisture_q=55` this fixture's 53 real working-grid
//! basins split 51 Overflow / 2 Endorheic; verified directly (a scratch
//! probe, not committed) that at the PRODUCTION `moisture_q=80` the SAME
//! 53 basins are **53/53 all-Overflow — zero Endorheic** (moisture is the
//! only thing that moves; `filled_scaled`/basin geometry itself is
//! moisture-independent, per `run_layer1_with_moisture_changes_
//! 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;
+512 -22
View File
@@ -8152,86 +8152,170 @@
124,
239
],
[
2,
99
],
[
1,
100
],
[
2,
137
],
[
1,
138
],
[
8,
194
],
[
7,
195
],
[
8,
204
],
[
7,
204
],
[
10,
68
],
[
10,
69
],
[
14,
29
],
[
14,
28
],
[
13,
47
],
[
14,
48
],
[
13,
172
],
[
14,
173
],
[
18,
179
],
[
19,
180
],
[
24,
152
],
[
25,
152
],
[
20,
165
],
[
21,
166
],
[
16,
199
],
[
17,
200
],
[
16,
6
],
[
17,
6
],
[
19,
181
],
[
20,
181
],
[
17,
45
],
[
16,
46
],
[
17,
92
],
[
17,
93
],
[
17,
126
],
[
17,
127
],
[
19,
223
],
[
19,
224
],
[
22,
231
],
[
21,
232
],
[
32,
254
],
[
33,
255
],
[
26,
103
],
[
27,
104
@@ -8240,114 +8324,226 @@
29,
15
],
[
30,
61
],
[
31,
61
],
[
27,
237
],
[
27,
236
],
[
29,
138
],
[
30,
138
],
[
31,
9
],
[
31,
10
],
[
34,
137
],
[
34,
136
],
[
34,
156
],
[
35,
155
],
[
37,
73
],
[
37,
72
],
[
39,
64
],
[
39,
63
],
[
40,
235
],
[
41,
235
],
[
45,
123
],
[
44,
122
],
[
52,
241
],
[
51,
242
],
[
51,
0
],
[
52,
1
],
[
51,
109
],
[
50,
108
],
[
56,
40
],
[
57,
41
],
[
57,
63
],
[
56,
64
],
[
58,
215
],
[
57,
216
],
[
62,
43
],
[
62,
42
],
[
70,
233
],
[
70,
232
],
[
74,
228
],
[
74,
229
],
[
74,
230
],
[
74,
231
],
[
91,
214
],
[
92,
213
],
[
88,
37
],
[
88,
38
],
[
97,
89
],
[
96,
88
],
[
101,
215
],
[
101,
214
],
[
115,
40
],
[
114,
41
],
[
115,
60
],
[
115,
59
],
[
116,
220
],
[
117,
221
],
[
124,
25
],
[
124,
24
@@ -8408,7 +8604,7 @@
0,
1,
0,
2,
0,
1,
0,
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
],
"river_downstream": [
@@ -8553,7 +8798,7 @@
4,
9,
7,
9,
6,
3,
3,
9,
@@ -8592,55 +8837,104 @@
9,
2,
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,
1,
9,
3,
9,
1,
9,
2,
9,
3,
9,
7,
9,
3,
9,
3,
9,
1,
9,
5,
9,
4,
9,
6,
9,
5,
9,
6,
9,
4,
9,
4,
9,
3,
9,
3,
9,
2,
9,
2,
9,
7,
9,
2,
9,
5,
9,
3,
9,
4,
9,
3,
9,
6,
9,
9,
9,
9,
9,
9,
9,
9,
9,
9,
9,
9,
9,
9,
9,
9,
9,
9,
9,
9,
9,
3,
9
],
"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