Batch-9 server track — closing the placement gap PR #215's review exposed. The synthetic-overflow path (D-211 Phase 4) derived positions by pure grid arithmetic with no terrain input at all, and the gap was far more widespread than assumed: 46 of 109 synthetic placements across the 267 real bodies sat in open water at seed 42 (e.g. GJ903c at a genuine polar ocean cell).
Fix:synthetic_attractor now takes the terrain analysis and land-corrects via a bounded nearest-land ring walk (T-1116's proven pattern: row-major tie-break, column wrap, row clamp; MAX_LAND_SEARCH_RING=128 sized empirically — real polar ocean bands push nearest land up to 125 cells). Land positions pass through untouched. Degradation defined and pinned: no land within the bound → the synthetic attractor is skipped and the existing not-placed warning reports it — never a panic, never a fabricated water position.
Compatibility (the load-bearing requirement): direct before/after scan of all 267 real bodies — all 63 land-arithmetic placements byte-identical; post-fix 0 in water, all 109 synthetic placements preserved, confirmed at a second seed. Every golden/determinism harness passes unchanged — no regeneration.
Records: road_graph.rs anchor comment + the D-210 amendment updated to mark the gap CLOSED (validated).
Test plan
9 new unit tests: land passthrough, water correction, tie-break determinism, wrap/clamp edges, degradation skip
Full cargo test 2221 passed / 0 failed; fmt + clippy -D warnings pre-verified clean
Data-layer only; settlement markers stop rendering in ocean once Wave-2 overlays land (T-1207)
## Summary
Batch-9 server track — closing the placement gap PR #215's review exposed. The synthetic-overflow path (D-211 Phase 4) derived positions by pure grid arithmetic with no terrain input at all, and the gap was far more widespread than assumed: **46 of 109 synthetic placements across the 267 real bodies sat in open water** at seed 42 (e.g. GJ903c at a genuine polar ocean cell).
**Fix:** `synthetic_attractor` now takes the terrain analysis and land-corrects via a bounded nearest-land ring walk (T-1116's proven pattern: row-major tie-break, column wrap, row clamp; MAX_LAND_SEARCH_RING=128 sized empirically — real polar ocean bands push nearest land up to 125 cells). Land positions pass through untouched. Degradation defined and pinned: no land within the bound → the synthetic attractor is skipped and the existing not-placed warning reports it — never a panic, never a fabricated water position.
**Compatibility (the load-bearing requirement):** direct before/after scan of all 267 real bodies — all 63 land-arithmetic placements byte-identical; post-fix 0 in water, all 109 synthetic placements preserved, confirmed at a second seed. Every golden/determinism harness passes unchanged — no regeneration.
Records: road_graph.rs anchor comment + the D-210 amendment updated to mark the gap CLOSED (validated).
## Test plan
- 9 new unit tests: land passthrough, water correction, tie-break determinism, wrap/clamp edges, degradation skip
- Full cargo test 2221 passed / 0 failed; fmt + clippy -D warnings pre-verified clean
- Data-layer only; settlement markers stop rendering in ocean once Wave-2 overlays land (T-1207)
synthetic_attractor now takes the terrain analysis and land-corrects
its pure-arithmetic position via a bounded nearest-land ring walk
(T-1116's pattern: row-major tie-break, column wrap, row clamp,
MAX_LAND_SEARCH_RING=128 sized empirically — real polar ocean bands
push nearest land up to 125 cells). Land positions pass through
UNTOUCHED — verified by direct before/after scan of all 267 real
bodies: 63 land-arithmetic placements byte-identical, and every
golden/determinism harness passes unchanged. The gap was real and
widespread: 46 of 109 synthetic-overflow placements sat in open water
at seed 42 (e.g. GJ903c at a genuine polar ocean cell); post-fix zero,
with all 109 preserved (confirmed at a second seed). Degradation is
defined and pinned: no land within the bound -> the synthetic
attractor is skipped and Phase 5's existing not-placed warning
reports it — never a panic, never a fabricated water position (the
bound never triggers on any scanned real body). 9 new unit tests;
road_graph's anchor comment and the D-210 amendment record the gap
CLOSED (validated).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review: ocean-guard-synthetic -> main (type: code)
Reviewers: Hoshe (QA) + Tyre (architecture/governance). Both REQUEST_CHANGES — three small, surgical findings on merge-quality engineering.
Hoshe (QA): REQUEST_CHANGES
#
File
Issue
1
attractor_matching.rs tests
The headline evidence (46/109 placements in water; GJ903c at a polar ocean cell) lives only in a DELETED scratch scan — no committed test pins the claim on real data. All 9 new tests use synthetic fixtures. T-1116's own precedent (cascade_for_body(42, "GJ251c") real-body tests) is directly reusable: add a real-body test asserting every synthetic placement on GJ903c (or whichever body exercises overflow) lands off ocean_mask. Without it, a guard regression is only caught by synthetic fixtures, not the production data that motivated the fix.
Hoshe's confirmations: land-passthrough byte-identical BY CONSTRUCTION (the is-land short-circuit precedes all correction math); Option<&TerrainAnalysis> provably always Some in production (Settlement > Topography in CascadeLayer's Ord, pinned by layers_are_ordered); wrap conventions consistent; tree carries zero scratch remnants.
Tyre (architecture/governance): REQUEST_CHANGES
#
File
Issue
1
governance D-211
The record whose behavior changed carries no note. Ruling: the fix needs no D-211 re-decision (position remains a pure function of seed + terrain — fulfilling the placement intent, not deviating) — but D-211 step 4's outcome set changed: "cities that cannot be matched receive a synthetic PlainCenter" can now resolve to a defined SKIP, and step 5's warning fires for a new legitimate reason. A reader of D-211 alone would believe synthetic overflow always places, and its cross-reference line is a dead end to T-1206/D-210's closure. One-line dated amendment on D-211, symmetric with how T-1116 landed on both records.
2
attractor_matching.rs:290
MAX_LAND_SEARCH_RING=128's doc is a model of empirical-constant governance EXCEPT: the whole justification is grid_h/2 while the value is a hardcoded literal (the 512×256 grid coupling is load-bearing and implicit — derive it from the in-scope grid height, or state the dependency explicitly), and no recalibration owner is written (add: re-derive via the T-1206 scan if the body corpus or grid resolution changes).
Tyre's rulings on record: duplicate-over-share is CORRECT for the ring walk (different resolutions, predicates, return shapes, index spaces — a generic helper would couple two files across a layer boundary to save ~25 commented lines; stated so it isn't re-litigated; note the two implementations wrap at different points, both correct, not line-mirrors); determinism clean (seed-invariant guard over seed-derived input — the cross-seed claim holds structurally); scope containment exact; the D-210 addendum + road_graph comment citation-faithful and validated.
Verdict: CHANGES REQUESTED
All three to the authoring agent: real-body regression test, D-211 amendment, constant derivation + recalibration note. Fix round → gate → merge (no eyeball owed — data-layer; markers leave the ocean visibly when T-1207 lands).
# Review: ocean-guard-synthetic -> main (type: code)
Reviewers: Hoshe (QA) + Tyre (architecture/governance). Both REQUEST_CHANGES — three small, surgical findings on merge-quality engineering.
## Hoshe (QA): REQUEST_CHANGES
| # | File | Issue |
|---|------|-------|
| 1 | attractor_matching.rs tests | The headline evidence (46/109 placements in water; GJ903c at a polar ocean cell) lives only in a DELETED scratch scan — no committed test pins the claim on real data. All 9 new tests use synthetic fixtures. T-1116's own precedent (`cascade_for_body(42, "GJ251c")` real-body tests) is directly reusable: add a real-body test asserting every synthetic placement on GJ903c (or whichever body exercises overflow) lands off ocean_mask. Without it, a guard regression is only caught by synthetic fixtures, not the production data that motivated the fix. |
Hoshe's confirmations: land-passthrough byte-identical BY CONSTRUCTION (the is-land short-circuit precedes all correction math); Option<&TerrainAnalysis> provably always Some in production (Settlement > Topography in CascadeLayer's Ord, pinned by layers_are_ordered); wrap conventions consistent; tree carries zero scratch remnants.
## Tyre (architecture/governance): REQUEST_CHANGES
| # | File | Issue |
|---|------|-------|
| 1 | governance D-211 | **The record whose behavior changed carries no note.** Ruling: the fix needs no D-211 re-decision (position remains a pure function of seed + terrain — fulfilling the placement intent, not deviating) — but D-211 step 4's outcome set changed: "cities that cannot be matched receive a synthetic PlainCenter" can now resolve to a defined SKIP, and step 5's warning fires for a new legitimate reason. A reader of D-211 alone would believe synthetic overflow always places, and its cross-reference line is a dead end to T-1206/D-210's closure. One-line dated amendment on D-211, symmetric with how T-1116 landed on both records. |
| 2 | attractor_matching.rs:290 | MAX_LAND_SEARCH_RING=128's doc is a model of empirical-constant governance EXCEPT: the whole justification is grid_h/2 while the value is a hardcoded literal (the 512×256 grid coupling is load-bearing and implicit — derive it from the in-scope grid height, or state the dependency explicitly), and no recalibration owner is written (add: re-derive via the T-1206 scan if the body corpus or grid resolution changes). |
Tyre's rulings on record: duplicate-over-share is CORRECT for the ring walk (different resolutions, predicates, return shapes, index spaces — a generic helper would couple two files across a layer boundary to save ~25 commented lines; stated so it isn't re-litigated; note the two implementations wrap at different points, both correct, not line-mirrors); determinism clean (seed-invariant guard over seed-derived input — the cross-seed claim holds structurally); scope containment exact; the D-210 addendum + road_graph comment citation-faithful and validated.
## Verdict: CHANGES REQUESTED
All three to the authoring agent: real-body regression test, D-211 amendment, constant derivation + recalibration note. Fix round → gate → merge (no eyeball owed — data-layer; markers leave the ocean visibly when T-1207 lands).
Three findings from Hoshe (QA) and Tyre (architecture), plus a bug the first
of them uncovered.
HOSHE — the headline evidence lived only in a deleted scratch scan. All nine
tests used synthetic fixtures, so nothing committed held the "46 of 109
synthetic placements in water" claim on real data. Two tests now do. The
T-1206 verification scan itself is committed as an #[ignore]d full-corpus
test (267 bodies, run with --ignored), which also makes the recalibration
instruction on the search bound executable rather than aspirational; a fast
test pins the bodies it identifies.
That scan promptly caught a bug in its own first draft, and it is the reason
this commit is worth reading. `CascadeSnapshot::terrain_analysis` is
transient — the cascade nulls it the moment DistrictProfile and RoadGraph are
done (D-203/T-1048, ~2 MB a body) — so it is ALWAYS None on a returned
snapshot, whatever cascade_snapshot_for_body's doc-comment implies. Reading
the ocean mask off the snapshot and skipping when absent therefore skipped
every body while reporting success: 267 bodies "scanned", 0 findings, a green
assert over an empty set, in 697 seconds. Terrain is now re-derived through
the same run_layer1_with_moisture call the cascade used, reproducing the grid
the placements were computed against.
Two habits caught it, both prompted by Hoshe's finding: a vacuity guard that
refuses to pass when no synthetic placement was seen, and counters that stop
"none found" and "never got that far" from looking identical. Corrected
figures at seed 42: 267 bodies, all reaching Layer 3, 344 placements, 109
synthetic, 0 in water — the synthetic count matching the original scan, so
the claim is reproducible now rather than anecdotal.
TYRE 1 — MAX_LAND_SEARCH_RING was justified as grid_h/2 but written as a
literal 128, leaving the 512x256 coupling implicit. It is now derived from
the grid in scope, so the value cannot drift from its own rationale. On the
current working grid it evaluates to exactly 128: no behaviour change, and
the byte-identical-placement guarantee is untouched. Recalibration owner
recorded.
That derivation does change one test. nearest_land_cell_clamps_rows_no_wrap
uses a 16x16 fixture, so its bound drops 128 -> 8, which now sits BETWEEN the
clamped distance to the far pole (15) and the wrapped one (1). The assertion
moves from position to absence and gets sharper for it: previously both
implementations returned Some((15,0)) and only the position could be pinned;
now any Some at all proves rows wrapped.
TYRE 2 — D-211 carried no note though its behaviour changed. Dated amendment
added: step 4's outcome set is no longer total (synthetic overflow may now
resolve to a defined SKIP), and step 5's warning fires for a new legitimate
reason. No re-decision needed — position remains a pure function of seed and
terrain — and the dead-end cross-reference to D-210's closure is now a live
anchor.
Full cargo test green (30 binaries).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review round addressed — all three findings, plus one they uncovered
Commit 4b75be597. Full cargo test green (30 binaries).
Hoshe: real-body regression test — fixed
Two tests now hold the claim on production data:
t1206_verification_scan — the T-1206 verification scan itself, committed as an #[ignore]d full-corpus test (267 bodies, cargo test --lib t1206_verification_scan -- --ignored --nocapture). Your complaint was that the scan was throwaway, so the headline number had nothing holding it; this makes it re-runnable. It doubles as the executable form of the recalibration instruction now written on the search bound.
real_body_synthetic_placements_never_land_in_ocean — the fast gate, pinning six bodies the scan identifies as actually reaching Phase-4 overflow, asserting no synthetic placement sits on an ocean cell.
Corrected corpus figures at seed 42: 267 bodies, all reaching Layer 3, 344 placements, 109 synthetic, 0 in water. The synthetic count matches what the original scan reported, so the PR's evidence is reproducible rather than anecdotal.
The finding behind the finding
Asking for this test surfaced a real bug, and it is worth recording because it is the same defect class you were objecting to.
CascadeSnapshot::terrain_analysis is transient — the cascade sets it to None as soon as DistrictProfile and RoadGraph have consumed it (D-203/T-1048, ~2 MB a body). So it is alwaysNone on a returned snapshot, regardless of what cascade_snapshot_for_body's doc-comment says about "still carrying" it. My first draft read the ocean mask off the snapshot and skipped bodies where it was absent — which skipped every body while reporting success:
T-1206 scan @ seed 42: 267 bodies loaded, 0 synthetic placements, 0 in water
test result: ok. finished in 697.23s
A green assert_eq!(in_water, 0) over an empty set. I was one step from reporting that the PR's evidence could not be reproduced on current data.
Two things caught it, both added because of your finding: a vacuity guard (synthetic_seen > 0) that refuses to pass over an empty set, and counters that separate "no synthetic placements" from "never got that far" — those two states were rendering identically. Terrain is now re-derived through the same run_layer1_with_moisture call the cascade used, so the grid matches the one the placements were computed against. The trap is documented on the helper so it isn't rediscovered.
Also worth noting for the record: the first body list I picked paired GJ903c with GJ251c/GJ380c/GJ820Bc/GJ338Bd — and those four have no synthetic placements at all, so the assertion would have rested on a single body. The pinned list is now taken from the scan's own output rather than guessed.
The whole justification was expressed in grid heights while the value was a literal, so the 512×256 coupling was load-bearing and implicit. On the current working grid (GRID_W × GRID_H = 512 × 256) this evaluates to exactly the 128 it replaces — no behaviour change, and the byte-identical-placement guarantee is untouched. Recalibration owner recorded: whoever changes the body corpus or grid resolution re-runs the scan and confirms the observed maximum still sits under h / 2.
One consequence, flagged rather than buried.nearest_land_cell_clamps_rows_no_wrap uses a 16×16 fixture, so its bound drops from 128 to 8 — which now sits between the clamped distance to the far pole (15) and the wrapped one (1). The assertion moves from position to absence, and gets stronger: previously both a clamping and a wrapping implementation returned Some((15,0)), so only the position could be pinned. Now any Some at all proves rows wrapped.
Tyre 2: D-211 amendment — fixed
Dated amendment (2026-08-06) on D-211. It records that step 4's outcome set is no longer total — synthetic overflow may now resolve to a defined SKIP where no land exists within the bound — and that step 5's name-fulfillment warning consequently fires for a new legitimate reason (a genuinely water-locked body), not only for a pipeline failure. It states explicitly that no re-decision is required, since position remains a pure function of seed + terrain: this fulfils the placement intent rather than deviating from it.
The cross-reference to D-210 is now a live anchor pointing at its 2026-07-26 amendment — the matched-attractor half of this same placement-in-water gap — instead of a dead end. pql decisions validate → {"ok":true}.
Ready for re-review.
# Review round addressed — all three findings, plus one they uncovered
Commit `4b75be597`. Full `cargo test` green (30 binaries).
## Hoshe: real-body regression test — **fixed**
Two tests now hold the claim on production data:
- **`t1206_verification_scan`** — the T-1206 verification scan itself, committed as an `#[ignore]`d full-corpus test (267 bodies, `cargo test --lib t1206_verification_scan -- --ignored --nocapture`). Your complaint was that the scan was throwaway, so the headline number had nothing holding it; this makes it re-runnable. It doubles as the executable form of the recalibration instruction now written on the search bound.
- **`real_body_synthetic_placements_never_land_in_ocean`** — the fast gate, pinning six bodies the scan identifies as actually reaching Phase-4 overflow, asserting no synthetic placement sits on an ocean cell.
**Corrected corpus figures at seed 42:** 267 bodies, all reaching Layer 3, 344 placements, **109 synthetic, 0 in water**. The synthetic count matches what the original scan reported, so the PR's evidence is reproducible rather than anecdotal.
### The finding behind the finding
Asking for this test surfaced a real bug, and it is worth recording because it is the same defect class you were objecting to.
`CascadeSnapshot::terrain_analysis` is **transient** — the cascade sets it to `None` as soon as DistrictProfile and RoadGraph have consumed it (D-203/T-1048, ~2 MB a body). So it is *always* `None` on a returned snapshot, regardless of what `cascade_snapshot_for_body`'s doc-comment says about "still carrying" it. My first draft read the ocean mask off the snapshot and skipped bodies where it was absent — which skipped **every** body while reporting success:
```
T-1206 scan @ seed 42: 267 bodies loaded, 0 synthetic placements, 0 in water
test result: ok. finished in 697.23s
```
A green `assert_eq!(in_water, 0)` over an empty set. I was one step from reporting that the PR's evidence could not be reproduced on current data.
Two things caught it, both added because of your finding: a **vacuity guard** (`synthetic_seen > 0`) that refuses to pass over an empty set, and **counters that separate "no synthetic placements" from "never got that far"** — those two states were rendering identically. Terrain is now re-derived through the same `run_layer1_with_moisture` call the cascade used, so the grid matches the one the placements were computed against. The trap is documented on the helper so it isn't rediscovered.
Also worth noting for the record: the first body list I picked paired GJ903c with GJ251c/GJ380c/GJ820Bc/GJ338Bd — and those four have **no synthetic placements at all**, so the assertion would have rested on a single body. The pinned list is now taken from the scan's own output rather than guessed.
## Tyre 1: `MAX_LAND_SEARCH_RING` derivation — **fixed**
`const MAX_LAND_SEARCH_RING: u16 = 128` → `fn max_land_search_ring(ta: &TerrainAnalysis) -> u16 { ta.h / 2 }`.
The whole justification was expressed in grid heights while the value was a literal, so the 512×256 coupling was load-bearing and implicit. On the current working grid (`GRID_W × GRID_H = 512 × 256`) this evaluates to **exactly the 128 it replaces** — no behaviour change, and the byte-identical-placement guarantee is untouched. Recalibration owner recorded: whoever changes the body corpus or grid resolution re-runs the scan and confirms the observed maximum still sits under `h / 2`.
**One consequence, flagged rather than buried.** `nearest_land_cell_clamps_rows_no_wrap` uses a 16×16 fixture, so its bound drops from 128 to 8 — which now sits *between* the clamped distance to the far pole (15) and the wrapped one (1). The assertion moves from position to absence, and gets stronger: previously both a clamping and a wrapping implementation returned `Some((15,0))`, so only the position could be pinned. Now any `Some` at all proves rows wrapped.
## Tyre 2: D-211 amendment — **fixed**
Dated amendment (2026-08-06) on D-211. It records that step 4's outcome set is no longer total — synthetic overflow may now resolve to a defined **SKIP** where no land exists within the bound — and that step 5's name-fulfillment warning consequently fires for a new legitimate reason (a genuinely water-locked body), not only for a pipeline failure. It states explicitly that no re-decision is required, since position remains a pure function of seed + terrain: this fulfils the placement intent rather than deviating from it.
The cross-reference to D-210 is now a live anchor pointing at its 2026-07-26 amendment — the matched-attractor half of this same placement-in-water gap — instead of a dead end. `pql decisions validate` → `{"ok":true}`.
---
Ready for re-review.
Hoshe and Tyre independently found the same defect, neither having seen the
other's review. They were right.
THE SPACING REGRESSION. synthetic_attractor's walk picks a candidate that
satisfies MIN_SPACING against every already-placed city, and the ocean
correction then moves that candidate up to h/2 = 128 cells with no
re-validation. D-211 step 4 promises the synthetic attractor is placed "at a
position that respects minimum city spacing" — the fix was buying the land
half of that promise with the spacing half. road_graph::collapse_colocated is
no safety net either: it dedupes by name, not position.
The spacing predicate now goes INTO the ring walk (nearest_cell_matching)
rather than running before it, so the search returns the nearest cell
satisfying land AND spacing, with the same tie-break order and the same
degrade-to-skip. A predicate on the existing walk, not a second walk.
Unmeasured, and stated rather than implied: whether this was ever a LIVE
violation or only a latent one. The old behaviour was replaced before it was
measured. What the corpus does say is that 12 of the 13 bodies showing any
sub-MIN_SPACING pair carry no synthetic placement at all.
AND THE FIRST VERSION OF THAT FIX OVER-ASSERTED. Checking MIN_SPACING across
ALL placement pairs found 13 violations corpus-wide, none of them the guard's
doing: the promise is step 4's alone, and matched placements (Tier A greedy,
Tier B/C Hungarian) sit on their terrain attractor, never subject to it. Two
real river mouths 12 cells apart is geography. Shipping that assertion would
have failed the gate and blamed this guard for pre-existing placements. Both
checks are now scoped to pairs involving a synthetic placement, with the 13
matched-pair proximities recorded in-code so they are not re-litigated.
Corpus, both invariants: 267 bodies, 267 reaching Layer 3, 344 placements,
109 synthetic, 0 in water, 0 spacing violations.
ALSO FIXED:
- t1206_verification_scan could still pass vacuously (Hoshe). The fast test
got bodies_loaded>0 / synthetic_seen>0 guards last round; the scan — the one
test whose entire purpose is being the re-runnable evidence — did not, and
!bodies.is_empty() only proves the directory listing worked. Both added.
- cascade_snapshot_for_body's doc-comment claimed the snapshot "still carries
the transient TerrainAnalysis" (Tyre). It is always None for a full-cascade
call. Corrected in place, with the re-derivation recipe and a note that this
sentence cost a false-clean 267-body scan.
- The passthrough test's comment described a land-island fixture and claimed
(0,0) is not returned; the fixture is ta_all_land and the test asserts (0,0)
IS returned (both reviewers). Rewritten to match reality.
- max_land_search_ring's cost note said ~(h/2)^2 = 16k candidates (Hoshe).
That is one quadrant's area, not cumulative ring cost: sum of 8r over
1..=128 is 66,048. Conclusion unchanged; the arithmetic is the executable
recalibration rationale, so it has to be right.
- The fast test ran 25.8s, close to the long pole of the whole --lib suite
(Hoshe). Trimmed 6 bodies to 3: 13.0s. Not to one — synthetic_seen>0 needs
only one body, but resting on one is how the first draft ended up depending
on GJ903c alone.
RETRACTED: the shared-ring-search-helper finding. Round 1 ruled duplicate-
over-share CORRECT for this walk and said so "stated so it isn't
re-litigated"; round 2 asks to factor it with no new evidence and no change to
either implementation. Fixing the spacing gap by predicate rather than by a
second walk moves that direction anyway.
Full cargo test green (40 binaries).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both reviewers returned REQUEST_CHANGES. Findings below in the order they matter, not the order received.
The headline: both of you found the same defect, independently
Tyre's #1 and Hoshe's #1 are the same gap, reached without seeing each other's output. That convergence is the strongest signal of this round, and it was correct.
The spacing walk in synthetic_attractor picks a candidate that satisfies MIN_SPACING against every already-placed city — and then the ocean correction moves that candidate by up to h/2 = 128 cells with no re-validation. D-211 step 4 promises the synthetic attractor is placed "at a position that respects minimum city spacing"; the fix was buying the land half of that promise with the spacing half. I confirmed the sequencing in the code before accepting it (attractor_matching.rs: spacing .all() at the walk, correction immediately after), and confirmed Tyre's point that road_graph::collapse_colocated is no safety net — it dedupes by name, not position.
Fix: the spacing predicate is now threaded into the ring walk (nearest_cell_matching), so the search returns the nearest cell satisfying land and spacing, with identical tie-break order and identical degradation-to-skip. Not a second walk — a predicate on the existing one.
Honest limit: whether this was ever a live violation or only a latent one is unmeasured, and I'd rather say so than imply I caught it in the act — I replaced the behaviour before testing the old path. The corpus scan can't settle it either, because by the time it ran the predicate was already in, which guarantees the result by construction.
My first fix over-asserted, and the corpus caught it
Worth recording because it nearly shipped. I initially asserted MIN_SPACING across all placement pairs. The 267-body scan returned 13 violations — none of them the guard's doing.
D-211's spacing promise is step 4's alone. Matched placements (Tier A greedy, Tier B/C Hungarian) sit on their terrain attractor and were never subject to it; two real river mouths 12 cells apart is legitimate geography. Had that assertion shipped it would have failed the push gate and blamed the ocean guard for pre-existing placements.
I checked rather than assumed: of the 12 distinct bodies carrying those 13 pairs, not one has a synthetic placement at all — every violation is matched-vs-matched. Both checks are now scoped to pairs involving a synthetic placement, with the 13 documented in-code so this isn't re-litigated.
Hoshe #2 — t1206_verification_scan lacks the vacuity guards its sibling has
Fixed. Correct and sharp: I added bodies_loaded > 0 / synthetic_seen > 0 to the fast test and left the scan — the one test whose entire purpose is being the re-runnable evidence — able to go green over an empty set, which is exactly how its first draft failed. Both guards added; !bodies.is_empty() only ever proved the directory listing worked.
Tyre #2 — cascade_snapshot_for_body doc-comment is false
Fixed. It claimed the snapshot "still carries the transient TerrainAnalysis"; it is always None for a full-cascade call. Corrected in place, with the re-derivation recipe and a note that this sentence cost a false-clean 267-body scan.
Tyre #3 / Hoshe #3 — test comment contradicts fixture and assertion
Fixed. The comment described a land-island-at-(128,128) fixture and claimed (0,0) is not returned; the fixture is ta_all_land and the test asserts (0,0)is returned. Rewritten to describe what actually happens and why (0,0) is the production-relevant case.
Fixed. Verified: Σ 8r over 1..=128 = 66,048, not (h/2)² = 16,384. The old figure was one quadrant's area, not cumulative ring cost. Conclusion ("negligible") unchanged, but this paragraph is the executable recalibration rationale, so the arithmetic has to be right.
Fixed, though not literally as suggested. Trimmed 6 bodies → 3: 25.8s → 13.0s. Not to one body: synthetic_seen > 0 needs only one, but resting on a single body is precisely how the first draft ended up depending on GJ903c alone. Three keeps independent terrain coverage at a third less cost; the 267-body sweep stays behind --ignored.
Retracting this one, per the skill's pushback process, on the grounds that the reviewer was wrong about it — specifically, wrong against their own prior ruling.
Round 1, Tyre, on this exact code:
duplicate-over-share is CORRECT for the ring walk (different resolutions, predicates, return shapes, index spaces — a generic helper would couple two files across a layer boundary to save ~25 commented lines; stated so it isn't re-litigated; note the two implementations wrap at different points, both correct, not line-mirrors)
Round 2 asks to factor it into a shared helper. Neither implementation has changed since that ruling, and no new evidence is offered — so this is the re-litigation the first round explicitly foreclosed. The cross-layer coupling objection still stands.
Worth noting the round-2 fix moves in the direction #4 wanted anyway: fixing the spacing gap by adding a predicate to the existing walk rather than writing a second one means this PR ends with fewer bespoke ring walks than a naive fix would have produced.
Full cargo test green. Ready for re-review.
# Round 2 addressed — 6 fixed, 1 retracted
Commit `fc55bd897`.
Both reviewers returned REQUEST_CHANGES. Findings below in the order they matter, not the order received.
## The headline: both of you found the same defect, independently
Tyre's #1 and Hoshe's #1 are the same gap, reached without seeing each other's output. That convergence is the strongest signal of this round, and it was correct.
The spacing walk in `synthetic_attractor` picks a candidate that satisfies `MIN_SPACING` against every already-placed city — and *then* the ocean correction moves that candidate by up to `h/2` = 128 cells with no re-validation. D-211 step 4 promises the synthetic attractor is placed "at a position that respects minimum city spacing"; the fix was buying the land half of that promise with the spacing half. I confirmed the sequencing in the code before accepting it (`attractor_matching.rs`: spacing `.all()` at the walk, correction immediately after), and confirmed Tyre's point that `road_graph::collapse_colocated` is no safety net — it dedupes by name, not position.
**Fix:** the spacing predicate is now threaded *into* the ring walk (`nearest_cell_matching`), so the search returns the nearest cell satisfying land **and** spacing, with identical tie-break order and identical degradation-to-skip. Not a second walk — a predicate on the existing one.
**Honest limit:** whether this was ever a *live* violation or only a *latent* one is **unmeasured**, and I'd rather say so than imply I caught it in the act — I replaced the behaviour before testing the old path. The corpus scan can't settle it either, because by the time it ran the predicate was already in, which guarantees the result by construction.
## My first fix over-asserted, and the corpus caught it
Worth recording because it nearly shipped. I initially asserted `MIN_SPACING` across **all** placement pairs. The 267-body scan returned **13 violations** — none of them the guard's doing.
D-211's spacing promise is **step 4's alone**. Matched placements (Tier A greedy, Tier B/C Hungarian) sit on their terrain attractor and were never subject to it; two real river mouths 12 cells apart is legitimate geography. Had that assertion shipped it would have failed the push gate and blamed the ocean guard for pre-existing placements.
I checked rather than assumed: of the 12 distinct bodies carrying those 13 pairs, **not one has a synthetic placement at all** — every violation is matched-vs-matched. Both checks are now scoped to pairs involving a synthetic placement, with the 13 documented in-code so this isn't re-litigated.
## Corpus evidence, re-run with both invariants
```
267 bodies loaded, 267 reached Layer 3, 344 placements total, 109 synthetic, 0 in water
spacing violations (< 15 cells Chebyshev): 0
```
## Remaining findings
| Finding | Resolution |
|---|---|
| **Hoshe #2** — `t1206_verification_scan` lacks the vacuity guards its sibling has | **Fixed.** Correct and sharp: I added `bodies_loaded > 0` / `synthetic_seen > 0` to the fast test and left the scan — the one test whose entire purpose is being the re-runnable evidence — able to go green over an empty set, which is exactly how its first draft failed. Both guards added; `!bodies.is_empty()` only ever proved the directory listing worked. |
| **Tyre #2** — `cascade_snapshot_for_body` doc-comment is false | **Fixed.** It claimed the snapshot "still carries the transient TerrainAnalysis"; it is always `None` for a full-cascade call. Corrected in place, with the re-derivation recipe and a note that this sentence cost a false-clean 267-body scan. |
| **Tyre #3 / Hoshe #3** — test comment contradicts fixture and assertion | **Fixed.** The comment described a land-island-at-(128,128) fixture and claimed `(0,0)` is not returned; the fixture is `ta_all_land` and the test asserts `(0,0)` *is* returned. Rewritten to describe what actually happens and why `(0,0)` is the production-relevant case. |
| **Hoshe #4** — worst-case candidate count off ~4× | **Fixed.** Verified: `Σ 8r` over `1..=128` = **66,048**, not `(h/2)² = 16,384`. The old figure was one quadrant's area, not cumulative ring cost. Conclusion ("negligible") unchanged, but this paragraph is the executable recalibration rationale, so the arithmetic has to be right. |
| **Hoshe #5** — fast test runtime disproportionate | **Fixed,** though not literally as suggested. Trimmed 6 bodies → 3: **25.8s → 13.0s**. Not to one body: `synthetic_seen > 0` needs only one, but resting on a single body is precisely how the first draft ended up depending on GJ903c alone. Three keeps independent terrain coverage at a third less cost; the 267-body sweep stays behind `--ignored`. |
## Retracted: Tyre #4 (shared ring-search helper)
Retracting this one, per the skill's pushback process, on the grounds that the reviewer was wrong about it — specifically, wrong against their own prior ruling.
Round 1, Tyre, on this exact code:
> duplicate-over-share is **CORRECT** for the ring walk (different resolutions, predicates, return shapes, index spaces — a generic helper would couple two files across a layer boundary to save ~25 commented lines; **stated so it isn't re-litigated**; note the two implementations wrap at different points, both correct, not line-mirrors)
Round 2 asks to factor it into a shared helper. Neither implementation has changed since that ruling, and no new evidence is offered — so this is the re-litigation the first round explicitly foreclosed. The cross-layer coupling objection still stands.
Worth noting the round-2 fix moves in the direction #4 wanted anyway: fixing the spacing gap by adding a *predicate to the existing walk* rather than writing a second one means this PR ends with fewer bespoke ring walks than a naive fix would have produced.
---
Full `cargo test` green. Ready for re-review.
Three findings, all doc-accuracy, and all the same root cause: folding the
spacing predicate into the ring walk changed what three comments describe, and
two of those comments were written by this same PR one round earlier.
TYRE 1 — road_graph.rs's T-1206 gap-closure comment cited `nearest_land_cell`,
which round 2 made `#[cfg(test)]`. A reader chasing that name lands on a
test-only function and reasonably wonders whether they are looking at dead
code. Repointed to `nearest_cell_matching`, and the paragraph's closing claim
that "T-1206 guarantees the placement pixel is land" is corrected: it has been
land-AND-spacing-or-skip since round 2.
TYRE 2 — `max_land_search_ring`'s doc named the same test-only wrapper as the
thing that walks the bound. It now names the production consumer and both
callers.
TYRE 3 — the D-211 amendment was written in round 1, before round 2 existed,
and still described a land-only correction. It now carries a dated refinement
recording what the code actually does: the walk satisfies BOTH of step 4's
promises in one search, and SKIP therefore also fires where land exists but
none of it clears spacing within the bound. The no-re-decision conclusion is
unaffected — position remains a deterministic, non-fabricated function of seed
and terrain — and the refinement notes the spacing promise is step 4's alone,
since Tier A/B/C placements sit on their matched attractor and were never
subject to it.
HOSHE's three findings were the same three hunks, observed uncommitted while
the review ran: accurate content, but not in the branch tip, so the PR would
have merged a governance record that misdescribes its own commit. That is this
commit.
Both reviewers independently confirmed what the round-2 fix claims. Tyre traced
the ring geometry and tie-break order by hand against the spacing predicate;
Hoshe re-ran the full 267-body corpus scan live (850s) and reproduced the
figures exactly — 267 bodies, 267 reaching Layer 3, 344 placements, 109
synthetic, 0 in water, 0 spacing violations.
The shared-ring-search-helper retraction is confirmed and settled, with NEW
grounds rather than a restatement: round 2 strengthened the case for keeping
them separate, since this walk is now parameterized by an arbitrary predicate
over native u16 terrain coordinates while road_graph's is a RouteGrid method
over downsampled routing cells with a fixed cost test and an unrelated bound.
22 module tests green; clippy and fmt clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Round 3 addressed — 6 findings, all doc-accuracy, one root cause
Commit d5e617eff.
Both reviewers returned REQUEST_CHANGES, and between them raised the same three issues twice — from opposite ends. Tyre found the comments were stale; Hoshe found the fixes for them weren't committed yet (I was editing while the review ran). Same three hunks either way.
The root cause is worth stating plainly
The round-2 fix outran its own documentation. Folding the spacing predicate into the ring walk changed what three comments describe — and two of those comments were written by this PR, one round earlier. That is the third consecutive round where this PR has caught its own documentation drifting behind its own code.
#
Finding
Fix
Tyre 1
road_graph.rs cites nearest_land_cell, which round 2 made #[cfg(test)]
Repointed to nearest_cell_matching; the "guarantees the placement pixel is land" claim corrected to land-and-spacing-or-skip
Tyre 2
max_land_search_ring doc names the test-only wrapper as the thing that walks the bound
Now names the production consumer and both callers
Tyre 3
D-211 amendment predates round 2 and still describes a land-only correction
Dated refinement added — the walk satisfies both step-4 promises in one search, and SKIP now also fires where land exists but none clears spacing
Hoshe 1–3
Those same three hunks uncommitted at the branch tip
This commit
Hoshe's framing is the one that matters: had this merged as it stood, the governance record checked in specifically to be the source of truth would have misdescribed the shipped code in the same commit.
Independent verification — the part I could not have supplied myself
Tyre traced the ring geometry by hand against the spacing predicate: candidate visit order matches the doc and matches road_graph.rs::nearest_passable_cell's independent implementation exactly; the predicate is consulted only on already-land candidates, so neither of D-211's promises is double-spent.
Hoshe re-ran the full 267-body corpus scan live (850s) and reproduced the claimed figures exactly: 267 bodies loaded, 267 reaching Layer 3, 344 placements, 109 synthetic, 0 in water, 0 spacing violations.
Hoshe proved the Option<&TerrainAnalysis>-always-Some claim structurally rather than by inspection: CascadeLayer derives Ord from declaration order with Topography before Settlement, so the up_to >= CascadeLayer::Settlement guard provably implies the Topography block that populates terrain_analysis already ran.
The retraction is settled
Tyre confirmed the shared-ring-search-helper retraction with new grounds rather than a restatement — round 2 actually strengthened the case for keeping them apart: this walk is now parameterised by an arbitrary predicate over native u16 terrain coordinates, while road_graph's is a RouteGrid method over downsampled usize routing cells with a fixed cost test and an unrelated bound (COASTAL_ANCHOR_MAX_RING = 3 vs derived h/2). Unifying them would mean generic-ising over index type and grid accessor for ~40 lines of different-domain logic.
That closes it properly — resolved on evidence, not on my assertion against theirs.
Also confirmed, no action needed
Synthetic-only spacing scoping is architecturally correct.MIN_SPACING appears exactly once in D-211 and is scoped to step 4's own placement; Tier A/B/C matched placements sit on their terrain attractor and were never subject to it. The scoping correctly includes synthetic-vs-matched and synthetic-vs-synthetic while excluding matched-vs-matched — consistent with what synthetic_attractor's own walk checks against.
nearest_land_cell as #[cfg(test)] — sound factoring; zero-risk delegation that cannot drift from the production path.
believability.rs's corrected doc-comment is accurate — verified against the exact conditional in cascade.rs.
D-010 determinism clean across the new predicate path: pure integer arithmetic, no floats, no RNG, fixed visit order.
22 module tests green, clippy and fmt clean. Ready for re-review.
# Round 3 addressed — 6 findings, all doc-accuracy, one root cause
Commit `d5e617eff`.
Both reviewers returned REQUEST_CHANGES, and between them raised the same three issues twice — from opposite ends. Tyre found the comments were stale; Hoshe found the fixes for them weren't committed yet (I was editing while the review ran). Same three hunks either way.
## The root cause is worth stating plainly
**The round-2 fix outran its own documentation.** Folding the spacing predicate into the ring walk changed what three comments describe — and two of those comments were written by *this PR, one round earlier*. That is the third consecutive round where this PR has caught its own documentation drifting behind its own code.
| # | Finding | Fix |
|---|---|---|
| Tyre 1 | `road_graph.rs` cites `nearest_land_cell`, which round 2 made `#[cfg(test)]` | Repointed to `nearest_cell_matching`; the "guarantees the placement pixel is land" claim corrected to land-**and-spacing**-or-skip |
| Tyre 2 | `max_land_search_ring` doc names the test-only wrapper as the thing that walks the bound | Now names the production consumer and both callers |
| Tyre 3 | D-211 amendment predates round 2 and still describes a land-only correction | Dated refinement added — the walk satisfies **both** step-4 promises in one search, and SKIP now also fires where land exists but none clears spacing |
| Hoshe 1–3 | Those same three hunks uncommitted at the branch tip | This commit |
Hoshe's framing is the one that matters: had this merged as it stood, the governance record checked in *specifically to be the source of truth* would have misdescribed the shipped code in the same commit.
## Independent verification — the part I could not have supplied myself
- **Tyre traced the ring geometry by hand** against the spacing predicate: candidate visit order matches the doc and matches `road_graph.rs::nearest_passable_cell`'s independent implementation exactly; the predicate is consulted only on already-land candidates, so neither of D-211's promises is double-spent.
- **Hoshe re-ran the full 267-body corpus scan live** (850s) and reproduced the claimed figures exactly: 267 bodies loaded, 267 reaching Layer 3, 344 placements, 109 synthetic, **0 in water, 0 spacing violations**.
- **Hoshe proved the `Option<&TerrainAnalysis>`-always-`Some` claim structurally** rather than by inspection: `CascadeLayer` derives `Ord` from declaration order with `Topography` before `Settlement`, so the `up_to >= CascadeLayer::Settlement` guard provably implies the Topography block that populates `terrain_analysis` already ran.
## The retraction is settled
Tyre confirmed the shared-ring-search-helper retraction **with new grounds rather than a restatement** — round 2 actually strengthened the case for keeping them apart: this walk is now parameterised by an arbitrary predicate over native `u16` terrain coordinates, while `road_graph`'s is a `RouteGrid` method over downsampled `usize` routing cells with a fixed cost test and an unrelated bound (`COASTAL_ANCHOR_MAX_RING = 3` vs derived `h/2`). Unifying them would mean generic-ising over index type and grid accessor for ~40 lines of different-domain logic.
That closes it properly — resolved on evidence, not on my assertion against theirs.
## Also confirmed, no action needed
- **Synthetic-only spacing scoping is architecturally correct.** `MIN_SPACING` appears exactly once in D-211 and is scoped to step 4's own placement; Tier A/B/C matched placements sit on their terrain attractor and were never subject to it. The scoping correctly includes synthetic-vs-matched and synthetic-vs-synthetic while excluding matched-vs-matched — consistent with what `synthetic_attractor`'s own walk checks against.
- **`nearest_land_cell` as `#[cfg(test)]`** — sound factoring; zero-risk delegation that cannot drift from the production path.
- **`believability.rs`'s corrected doc-comment is accurate** — verified against the exact conditional in `cascade.rs`.
- **D-010 determinism** clean across the new predicate path: pure integer arithmetic, no floats, no RNG, fixed visit order.
---
22 module tests green, clippy and fmt clean. Ready for re-review.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Batch-9 server track — closing the placement gap PR #215's review exposed. The synthetic-overflow path (D-211 Phase 4) derived positions by pure grid arithmetic with no terrain input at all, and the gap was far more widespread than assumed: 46 of 109 synthetic placements across the 267 real bodies sat in open water at seed 42 (e.g. GJ903c at a genuine polar ocean cell).
Fix:
synthetic_attractornow takes the terrain analysis and land-corrects via a bounded nearest-land ring walk (T-1116's proven pattern: row-major tie-break, column wrap, row clamp; MAX_LAND_SEARCH_RING=128 sized empirically — real polar ocean bands push nearest land up to 125 cells). Land positions pass through untouched. Degradation defined and pinned: no land within the bound → the synthetic attractor is skipped and the existing not-placed warning reports it — never a panic, never a fabricated water position.Compatibility (the load-bearing requirement): direct before/after scan of all 267 real bodies — all 63 land-arithmetic placements byte-identical; post-fix 0 in water, all 109 synthetic placements preserved, confirmed at a second seed. Every golden/determinism harness passes unchanged — no regeneration.
Records: road_graph.rs anchor comment + the D-210 amendment updated to mark the gap CLOSED (validated).
Test plan
Review: ocean-guard-synthetic -> main (type: code)
Reviewers: Hoshe (QA) + Tyre (architecture/governance). Both REQUEST_CHANGES — three small, surgical findings on merge-quality engineering.
Hoshe (QA): REQUEST_CHANGES
cascade_for_body(42, "GJ251c")real-body tests) is directly reusable: add a real-body test asserting every synthetic placement on GJ903c (or whichever body exercises overflow) lands off ocean_mask. Without it, a guard regression is only caught by synthetic fixtures, not the production data that motivated the fix.Hoshe's confirmations: land-passthrough byte-identical BY CONSTRUCTION (the is-land short-circuit precedes all correction math); Option<&TerrainAnalysis> provably always Some in production (Settlement > Topography in CascadeLayer's Ord, pinned by layers_are_ordered); wrap conventions consistent; tree carries zero scratch remnants.
Tyre (architecture/governance): REQUEST_CHANGES
Tyre's rulings on record: duplicate-over-share is CORRECT for the ring walk (different resolutions, predicates, return shapes, index spaces — a generic helper would couple two files across a layer boundary to save ~25 commented lines; stated so it isn't re-litigated; note the two implementations wrap at different points, both correct, not line-mirrors); determinism clean (seed-invariant guard over seed-derived input — the cross-seed claim holds structurally); scope containment exact; the D-210 addendum + road_graph comment citation-faithful and validated.
Verdict: CHANGES REQUESTED
All three to the authoring agent: real-body regression test, D-211 amendment, constant derivation + recalibration note. Fix round → gate → merge (no eyeball owed — data-layer; markers leave the ocean visibly when T-1207 lands).
Review round addressed — all three findings, plus one they uncovered
Commit
4b75be597. Fullcargo testgreen (30 binaries).Hoshe: real-body regression test — fixed
Two tests now hold the claim on production data:
t1206_verification_scan— the T-1206 verification scan itself, committed as an#[ignore]d full-corpus test (267 bodies,cargo test --lib t1206_verification_scan -- --ignored --nocapture). Your complaint was that the scan was throwaway, so the headline number had nothing holding it; this makes it re-runnable. It doubles as the executable form of the recalibration instruction now written on the search bound.real_body_synthetic_placements_never_land_in_ocean— the fast gate, pinning six bodies the scan identifies as actually reaching Phase-4 overflow, asserting no synthetic placement sits on an ocean cell.Corrected corpus figures at seed 42: 267 bodies, all reaching Layer 3, 344 placements, 109 synthetic, 0 in water. The synthetic count matches what the original scan reported, so the PR's evidence is reproducible rather than anecdotal.
The finding behind the finding
Asking for this test surfaced a real bug, and it is worth recording because it is the same defect class you were objecting to.
CascadeSnapshot::terrain_analysisis transient — the cascade sets it toNoneas soon as DistrictProfile and RoadGraph have consumed it (D-203/T-1048, ~2 MB a body). So it is alwaysNoneon a returned snapshot, regardless of whatcascade_snapshot_for_body's doc-comment says about "still carrying" it. My first draft read the ocean mask off the snapshot and skipped bodies where it was absent — which skipped every body while reporting success:A green
assert_eq!(in_water, 0)over an empty set. I was one step from reporting that the PR's evidence could not be reproduced on current data.Two things caught it, both added because of your finding: a vacuity guard (
synthetic_seen > 0) that refuses to pass over an empty set, and counters that separate "no synthetic placements" from "never got that far" — those two states were rendering identically. Terrain is now re-derived through the samerun_layer1_with_moisturecall the cascade used, so the grid matches the one the placements were computed against. The trap is documented on the helper so it isn't rediscovered.Also worth noting for the record: the first body list I picked paired GJ903c with GJ251c/GJ380c/GJ820Bc/GJ338Bd — and those four have no synthetic placements at all, so the assertion would have rested on a single body. The pinned list is now taken from the scan's own output rather than guessed.
Tyre 1:
MAX_LAND_SEARCH_RINGderivation — fixedconst MAX_LAND_SEARCH_RING: u16 = 128→fn max_land_search_ring(ta: &TerrainAnalysis) -> u16 { ta.h / 2 }.The whole justification was expressed in grid heights while the value was a literal, so the 512×256 coupling was load-bearing and implicit. On the current working grid (
GRID_W × GRID_H = 512 × 256) this evaluates to exactly the 128 it replaces — no behaviour change, and the byte-identical-placement guarantee is untouched. Recalibration owner recorded: whoever changes the body corpus or grid resolution re-runs the scan and confirms the observed maximum still sits underh / 2.One consequence, flagged rather than buried.
nearest_land_cell_clamps_rows_no_wrapuses a 16×16 fixture, so its bound drops from 128 to 8 — which now sits between the clamped distance to the far pole (15) and the wrapped one (1). The assertion moves from position to absence, and gets stronger: previously both a clamping and a wrapping implementation returnedSome((15,0)), so only the position could be pinned. Now anySomeat all proves rows wrapped.Tyre 2: D-211 amendment — fixed
Dated amendment (2026-08-06) on D-211. It records that step 4's outcome set is no longer total — synthetic overflow may now resolve to a defined SKIP where no land exists within the bound — and that step 5's name-fulfillment warning consequently fires for a new legitimate reason (a genuinely water-locked body), not only for a pipeline failure. It states explicitly that no re-decision is required, since position remains a pure function of seed + terrain: this fulfils the placement intent rather than deviating from it.
The cross-reference to D-210 is now a live anchor pointing at its 2026-07-26 amendment — the matched-attractor half of this same placement-in-water gap — instead of a dead end.
pql decisions validate→{"ok":true}.Ready for re-review.
Round 2 addressed — 6 fixed, 1 retracted
Commit
fc55bd897.Both reviewers returned REQUEST_CHANGES. Findings below in the order they matter, not the order received.
The headline: both of you found the same defect, independently
Tyre's #1 and Hoshe's #1 are the same gap, reached without seeing each other's output. That convergence is the strongest signal of this round, and it was correct.
The spacing walk in
synthetic_attractorpicks a candidate that satisfiesMIN_SPACINGagainst every already-placed city — and then the ocean correction moves that candidate by up toh/2= 128 cells with no re-validation. D-211 step 4 promises the synthetic attractor is placed "at a position that respects minimum city spacing"; the fix was buying the land half of that promise with the spacing half. I confirmed the sequencing in the code before accepting it (attractor_matching.rs: spacing.all()at the walk, correction immediately after), and confirmed Tyre's point thatroad_graph::collapse_colocatedis no safety net — it dedupes by name, not position.Fix: the spacing predicate is now threaded into the ring walk (
nearest_cell_matching), so the search returns the nearest cell satisfying land and spacing, with identical tie-break order and identical degradation-to-skip. Not a second walk — a predicate on the existing one.Honest limit: whether this was ever a live violation or only a latent one is unmeasured, and I'd rather say so than imply I caught it in the act — I replaced the behaviour before testing the old path. The corpus scan can't settle it either, because by the time it ran the predicate was already in, which guarantees the result by construction.
My first fix over-asserted, and the corpus caught it
Worth recording because it nearly shipped. I initially asserted
MIN_SPACINGacross all placement pairs. The 267-body scan returned 13 violations — none of them the guard's doing.D-211's spacing promise is step 4's alone. Matched placements (Tier A greedy, Tier B/C Hungarian) sit on their terrain attractor and were never subject to it; two real river mouths 12 cells apart is legitimate geography. Had that assertion shipped it would have failed the push gate and blamed the ocean guard for pre-existing placements.
I checked rather than assumed: of the 12 distinct bodies carrying those 13 pairs, not one has a synthetic placement at all — every violation is matched-vs-matched. Both checks are now scoped to pairs involving a synthetic placement, with the 13 documented in-code so this isn't re-litigated.
Corpus evidence, re-run with both invariants
Remaining findings
t1206_verification_scanlacks the vacuity guards its sibling hasbodies_loaded > 0/synthetic_seen > 0to the fast test and left the scan — the one test whose entire purpose is being the re-runnable evidence — able to go green over an empty set, which is exactly how its first draft failed. Both guards added;!bodies.is_empty()only ever proved the directory listing worked.cascade_snapshot_for_bodydoc-comment is falseNonefor a full-cascade call. Corrected in place, with the re-derivation recipe and a note that this sentence cost a false-clean 267-body scan.(0,0)is not returned; the fixture ista_all_landand the test asserts(0,0)is returned. Rewritten to describe what actually happens and why(0,0)is the production-relevant case.Σ 8rover1..=128= 66,048, not(h/2)² = 16,384. The old figure was one quadrant's area, not cumulative ring cost. Conclusion ("negligible") unchanged, but this paragraph is the executable recalibration rationale, so the arithmetic has to be right.synthetic_seen > 0needs only one, but resting on a single body is precisely how the first draft ended up depending on GJ903c alone. Three keeps independent terrain coverage at a third less cost; the 267-body sweep stays behind--ignored.Retracted: Tyre #4 (shared ring-search helper)
Retracting this one, per the skill's pushback process, on the grounds that the reviewer was wrong about it — specifically, wrong against their own prior ruling.
Round 1, Tyre, on this exact code:
Round 2 asks to factor it into a shared helper. Neither implementation has changed since that ruling, and no new evidence is offered — so this is the re-litigation the first round explicitly foreclosed. The cross-layer coupling objection still stands.
Worth noting the round-2 fix moves in the direction #4 wanted anyway: fixing the spacing gap by adding a predicate to the existing walk rather than writing a second one means this PR ends with fewer bespoke ring walks than a naive fix would have produced.
Full
cargo testgreen. Ready for re-review.Round 3 addressed — 6 findings, all doc-accuracy, one root cause
Commit
d5e617eff.Both reviewers returned REQUEST_CHANGES, and between them raised the same three issues twice — from opposite ends. Tyre found the comments were stale; Hoshe found the fixes for them weren't committed yet (I was editing while the review ran). Same three hunks either way.
The root cause is worth stating plainly
The round-2 fix outran its own documentation. Folding the spacing predicate into the ring walk changed what three comments describe — and two of those comments were written by this PR, one round earlier. That is the third consecutive round where this PR has caught its own documentation drifting behind its own code.
road_graph.rscitesnearest_land_cell, which round 2 made#[cfg(test)]nearest_cell_matching; the "guarantees the placement pixel is land" claim corrected to land-and-spacing-or-skipmax_land_search_ringdoc names the test-only wrapper as the thing that walks the boundHoshe's framing is the one that matters: had this merged as it stood, the governance record checked in specifically to be the source of truth would have misdescribed the shipped code in the same commit.
Independent verification — the part I could not have supplied myself
road_graph.rs::nearest_passable_cell's independent implementation exactly; the predicate is consulted only on already-land candidates, so neither of D-211's promises is double-spent.Option<&TerrainAnalysis>-always-Someclaim structurally rather than by inspection:CascadeLayerderivesOrdfrom declaration order withTopographybeforeSettlement, so theup_to >= CascadeLayer::Settlementguard provably implies the Topography block that populatesterrain_analysisalready ran.The retraction is settled
Tyre confirmed the shared-ring-search-helper retraction with new grounds rather than a restatement — round 2 actually strengthened the case for keeping them apart: this walk is now parameterised by an arbitrary predicate over native
u16terrain coordinates, whileroad_graph's is aRouteGridmethod over downsampledusizerouting cells with a fixed cost test and an unrelated bound (COASTAL_ANCHOR_MAX_RING = 3vs derivedh/2). Unifying them would mean generic-ising over index type and grid accessor for ~40 lines of different-domain logic.That closes it properly — resolved on evidence, not on my assertion against theirs.
Also confirmed, no action needed
MIN_SPACINGappears exactly once in D-211 and is scoped to step 4's own placement; Tier A/B/C matched placements sit on their terrain attractor and were never subject to it. The scoping correctly includes synthetic-vs-matched and synthetic-vs-synthetic while excluding matched-vs-matched — consistent with whatsynthetic_attractor's own walk checks against.nearest_land_cellas#[cfg(test)]— sound factoring; zero-risk delegation that cannot drift from the production path.believability.rs's corrected doc-comment is accurate — verified against the exact conditional incascade.rs.22 module tests green, clippy and fmt clean. Ready for re-review.
Pull request closed