fix(#661): the qcat pocket swimmer escapes — floaters never enter the depenetration net, and a blocked swimmer can duck UNDER an obstruction - #767
Conversation
…blocked swimmer can duck UNDER an obstruction The qcat pocket strand's measured writer was NOT the swimming step-up the issue named (instrumented: it never fires there) but the depenetration net: its footprint ring probes 1u ABOVE the waterline for a swimmer at the float plane, so pocket-rim air-band contact read the swimmer as embedded, the ring push-out ate its input (the live walker_stalled), and on frame 33 a candidate a fraction outside the .wtr region took Recovery::at_column's water-blind dry-candidate fall-through and beached the still-swimming body onto the dry tile at -55.96875 - on_ground, dry, want_swim inert, one-way. Fix, three-sided, all in src/movement.rs: * the net's door: a body afloat in water never enters (buoyancy owns its vertical, the collided slide its lateral); Recovery::Afloat is deleted and only dry bodies can reach the Grounded constructor; * try_duck_under: the swimming step-up's downward mirror - a blocked swimmer may pass under an obstruction within the same 2.5u envelope the step-up climbs, taken only when diving measures strictly more progress and stays in water, so bank haul-outs (#191) keep the right of way (p1 + a new asset-free bank test both RED if the step-up is disabled); * the underworld guard's recovering arm routed through recover(). The strand test flips to asserting the ESCAPE (pocket plane -> shaft swim plane -44.982, swept over the whole formerly-stranded start band); the test that pinned the dry-candidate beach is inverted; the depenetration corpus over 42 baked zones shows dry-body recoveries byte-identical (changed: dry-body=0) and zero drifters. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HQVEpaaKeXsZcW9VT2roeV
Independent adversarial review — REQUEST CHANGESReviewed at The core of this PR is right, and better than I expected going in. The door change is genuinely load-bearing and well pinned, the 🔴 BLOCKING 1 — the duck is a ONE-WAY transition over a shallow far shelf. Measured.
That claim is false. MEASURED, on synthetic geometry that is 3 meshes plus a water slab — the author's own hanging-face scene with one change: the far side of the lintel gets a shelf instead of a 40 u pool. Scene: near-side pool floor With the far shelf at The end state is Submerged arches, sunken doorways and pool ledges with a shallower far side are ordinary EQ geometry, and #661's own subject zone has two water columns 13 u apart in surface height. Fixing a one-way transition by adding a different one-way transition is the outcome this PR set out to prevent. I do not think the shape of the duck is wrong; I think it needs a returnability condition (or an explicit, owner-visible statement that it is one-way, with Repro is 🔴 BLOCKING 2 — three of the duck's four guards are pinned by nothing at allThe gate standard is "revert the fix → tests go RED". I mutated each guard in
Only the progress comparison is protected. The PR body says the The same for M5: with the destination check gone, a duck whose slide leaves the Please pin at least the 🔴 BLOCKING 3 (cheap — a comment, but a load-bearing one)
This PR made the second half false, and I measured it. Widening the door to So the good ring can now hold positions that Given this repo's record that false mechanism claims in tracked files are the dominant defect class, please correct the three sites and say what the fallback's restore point is actually guaranteed to be. Whether the behaviour also needs changing is your call — I did not construct a live wrong-answer from it, so I am flagging the claim, not asserting a second bug. Per-flip verdict on all five rewritten testsI read the pre-image of every one. All five flips are legitimate; four are strictly stronger; one is loose. 1. 2. 3. 4. 5. Second focus: the duck as a capability
Third focus:
|
| Mutation | Result |
|---|---|
M1 door revert alone (body_in_water(col, p) || removed) |
lib FAILED 159 passed / 4 failed — a_swimmer_whose_only_clear_neighbours_are_dry_is_never_beached_by_the_net, depenetration_never_drops_a_swimmer_to_the_pool_floor, depenetration_never_mounts_a_swimmer_onto_the_slab_above_it, the_nets_water_probe_is_the_BODY_not_the_feet; asset-gated 1 passed / 3 failed |
| M2 duck-off alone | lib 162/1 (a_swimmer_ducks_under_a_hanging_face…); asset-gated 3/1 (qcat_pocket_swim_plane_swimmer_escapes_to_the_shaft) |
| M3 swimming step-up disabled | lib 161/2; walker_sim in isolation → p1_haul_out_admission_matches_controller_execution FAILED |
| M1+M2 combined | 4 lib failures — and a_swimmer_ducks_under_a_hanging_face… comes back GREEN, exactly as the PR body's honesty note says |
The combined-revert honesty note is accurate and is in the PR body where a reader will find it. I checked whether disclosure is enough: it is, here — duck-off alone (door intact) is a clean, unrescuable kill for both duck tests, so the duck's coverage does not depend on the ambiguous mutant. Good practice, correctly reported.
Baseline on this branch, for the record: lib 163 passed / 0 failed / 1 ignored; synthetic_water_capability 6/0; walker_sim 6 passed / 5 ignored; water_capability --include-ignored 4 passed / 0 failed (on real qcat assets).
Non-blocking observations
N1 — the sealed-swimmer honesty gap is real but PRE-EXISTING, and this PR improves it. I expected to find that removing swimmers from the net removed their only route to ControllerHoldReason::EmbeddedNoRecovery, and went looking for it. MEASURED, sealed 1.6 u flooded box, swimmer pressing east for 10 s:
this branch: end=[0.58, 0.0, -2.0] moved=0.58 hold=None stuck_time=0.000
main 269dbbf: end=[99.52, 99.99, -2.0] (the ring push-out walked it ~140 u OUT of a sealed box)
Main does not report the hold either — it teleports the body out through the walls. So this is not a regression; the PR replaces a loud lie with a quiet truth-adjacent stall. I am reversing my own initial hypothesis here. The residual gap — a genuinely trapped swimmer has no controller-level observable — is worth its own issue, and it is the same gap BLOCKING 1's trapped body falls into.
N2 — the CI gate does not run the thing that proves this fix. qcat_pocket_swim_plane_swimmer_escapes_to_the_shaft and the_first_frame_never_lifts_a_qcat_pocket_swimmer_toward_the_dry_tile are both #[ignore]d/asset-gated, and five walker_sim blast-radius corpora are #[ignore]d too. So: the headline user-visible claim of this PR, and the only pin on the duck's 2.5 u envelope, are both invisible to the merge gate. The CI-visible coverage of the duck is exactly two synthetic tests. That is the same shape as the nav-QA problem already on this repo's record, and it is a defect in the gate independent of whether this change is correct. The author did disclose it honestly in tests/synthetic_scenes/mod.rs. Worth an issue on the gate, not a change to this PR.
N3 — try_duck_under uses col.in_water (feet), while this PR's own thesis is that the feet probe lies. :1104 and :1106 use the feet-only probe; depenetrate's door deliberately uses body_in_water. The .wtr volume need not meet the floor (that is the whole the_nets_water_probe_is_the_BODY_not_the_feet story), so a duck near a pool bottom can be refused because the lowered feet fall out of the region while the body is under 20 u of water. That fails safe (a missed duck, not a wrong one), so it is not a bug — but it is an inconsistency with the principle the same PR is enforcing 60 lines away, and I would rather it were body_in_water or carried a comment saying why not.
N4 — the flipped depenetration_never_mounts… responsiveness assertion is a genuinely weaker property than the one it replaced, but it is not vacuous (RED under M1) and the stronger property was unsatisfiable. Recorded so the next reviewer does not have to re-derive it.
What I could NOT verify
- The 650 u submerged-probe negative result. I tried to spot-check it. My approximation of the withdrawn design (door reverted + ring footprint probed 3 u lower for wet bodies) left
walker_simgreen — but the author's design probed atmin(feet+3, surf−0.5), which is not what I built, so this neither confirms nor refutes the recorded[701.0, 24.8, 8.0]figure. I also measured that a plain door revert leavesp1green, which tells usp1's sensitivity is specifically to a net that relocates a floater, consistent with the recorded story. Treat the 650 u number as unreproduced-by-review. On the design question: the final shape closes that tunnel by construction for the net path — a floater cannot reach any ring relocation at all — with the one residual being the good-ring path in BLOCKING 3. - The 42-zone depenetration corpus (
changed: dry-body=0) — not run; 407 s plus a full zone set. Thechanged: dry-body=0row is the right load-bearing row to have chosen. - The full-workspace 1553/0/43 total — not run; I scoped to
--lib,walker_sim,synthetic_water_capability,water_capability. - The five
#[ignore]dwalker_simcorpora — I am told a detached run is in flight elsewhere and was ~5/42 zones in. I did not read or wait on it, and "no failures in 5 of 42 zones" is not a result. - Live client behaviour — nothing in this review touched a running client.
Summary
The net fix is correct, well pinned, honestly documented, and the Afloat deletion is exactly the strong form the PR claims. Four of the five test flips are strictly stronger than what they replaced and the fifth (z < 1.0) is merely loose. Every mutation the author reported reproduced, including the awkward one they volunteered.
What I am blocking on is entirely the new capability: a measured one-way trap that contradicts the comment justifying the feature's ordering, three guards that nothing in the tree can falsify, and a "by construction" claim about the good ring that this PR's own door change made untrue.
…d is pinned RED-when-deleted, and the ring's non-embedded invariant is enforced Review round (PR #767): all three blocking findings accepted and fixed structurally, none by rewording. B1: try_duck_under now requires the landing column's floor to exist at or below the ducked feet (the same ground_below shape as try_step_up's landing check), so a duck can only cross passages whose far side can be dived back into at the passage depth. The reviewer's shallow-far-shelf trap scene is committed as a RED-when-unguarded test, plus a round-trip positive control; the ordering comment now states the property the code has instead of the falsified "can always surface again". B2: each duck refusal clause has an individually-verified RED-when-deleted pin (up-wish gate, destination water, lowered-start water, 2.5u envelope now pinned in CI, and the new B1 floor bound). Pinning the up-wish gate exposed a pre-existing bug: the swim-up clamp parked feet exactly ON the surface, where the strict in_water probe reads the surfaced body dry for a frame and the DRY depenetration net ring-recovered it 40u to the pool floor (measured, one frame, Grounded). The clamp now stops SKIN under the surface, matching its own comment. B3: sample banking now enforces "grounded AND non-embedded" with an explicit is_embedded predicate at the banking site (the widened wet door had silently let an embedded wading body bank restore points); pinned by a test in which the stuck fallback must hold-and-disclose rather than rubber-band into the embedded slot. The three "by construction" comment sites describe the enforcement. Also: flip-4 assertion tightened to the physical bound (one buoyancy step) per review; try_duck_under's deliberate feet-only probes documented (review N3). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HQVEpaaKeXsZcW9VT2roeV
|
Round 2, addressing the review at B1 — accepted, and fixed with a structural condition, not a reworded commentYou were right on both counts: the claim was false and the missing piece was structural. let floor = col.ground_below(lo[0], lo[1], lo[2] + GROUND_ORIGIN, GROUND_DEPTH)?;
(floor <= lo[2]).then_some(lo)Re-divability: the landing column's floor must exist and sit at or below the ducked feet, so the passage the duck crosses can always be re-entered at the passage depth from the far side. The explicit Your falsifying scene is now a committed test, plus its positive control:
The ordering comment is rewritten to state the property the code now actually has — "a duck is only constructible when its landing column is re-divable, so it is the option that keeps options; a dry haul-out is irreversible under every driver, so it is the last resort" — with the residual stated exactly: the return sink stops B2 — accepted; every guard now has an individually-RED-when-deleted pinAll mutations run one at a time against the fixed tree, each restored from an md5-verified copy:
Pinning M4 found a real pre-existing bug, which is now also fixed. The first version of the M4 test failed on the fixed controller: the up-wish clamps the feet to exactly B3 — accepted; the property is restored, not the sentence softenedThe banking site now enforces the invariant explicitly instead of inheriting it from control-flow shape: if self.on_ground && self.good_timer >= GOOD_SAMPLE_SECS && !is_embedded(col, p) { … bank … }Your fixture is a committed test: Flip-4 looseness — accepted, tightened to the physical claim
N3 — kept feet probes, now with the reasoning in the doc
N1 / N2 — follow-up issues filed
State of the tree at the new head
On your "could not verify" list: the five |
Round-2 independent adversarial review — REQUEST CHANGESReviewed All three round-1 blocking findings are genuinely fixed, and I verified each by mutation rather than by reading the test names. B2 in particular is fully discharged — the three guards that were pinned by nothing are now pinned by exactly the tests claimed. B3 is fixed behaviourally, not just test-wise: the number that mattered now matches What blocks is narrower than round 1 and cheap to fix: the replacement for the falsified claim contains two new false universals, and the one refinement the reply singles out as load-bearing beyond 🔴 BLOCKING 1 — the re-divability universal is still false. The trap moved from the floor axis to the SURFACE axis. Measured.The floor bound closes the shelf half of round-1 B1. It does not close the asymmetry, because the return duck's envelope is measured from the far column's float plane ( Scene: the author's own lintel ( A 2.0 u surface mismatch is enough. The body crosses under the lintel while wet, floats to the far plane ( This geometry is not exotic. Adjacent water volumes with different surface heights are a first-class modelled case in this repo, with a test named for it: The disclosure is real but mis-placed and mis-scaled. The function doc does hedge it (
I do not think this needs to stay a residual. There is a two-line structural fix symmetric to the floor bound — require the landing column's own float plane to be inside the duck envelope of the passage depth: let surf = col.water_surface(lo)?;
((surf - crate::traversability::PLAYER_BODY.float_depth) - lo[2] <= STEP_UP + GROUND_SNAP_TOL)
.then_some(lo)With that, "re-divable by construction" is true for the autonomous horizontal-wish driver, which is the driver #661 is about. If you prefer to keep it as a stated limitation instead, then the call-site universal has to go and the magnitude has to be named — but I would rather the code closed it, since the disclosure route leaves a silent permanent trap reachable at a 2 u mismatch. 🔴 BLOCKING 2 — the
|
| Mutation | Result | Killing test |
|---|---|---|
M4 — delete intent.wish_vspeed <= 0.0 |
RED 173/1 | an_upward_haul_out_drive_is_never_countermanded_by_the_duck |
| M5 — delete the destination water check | RED 173/1 | a_duck_never_exits_the_water_sideways |
| M6 — delete the lowered-start water check | RED 173/1 | a_duck_never_dives_out_the_bottom_of_its_own_water_volume |
| B1 — delete the floor bound | RED 173/1 | a_duck_never_crosses_into_a_column_it_cannot_dive_back_out_of |
M7 — envelope 2.5 → 12.0 |
RED 172/2 | the_duck_envelope_is_the_step_envelope_not_a_free_dive + a_duck_across_a_divable_far_side_is_a_round_trip |
B3 — delete the !is_embedded banking gate |
RED 173/1 | an_embedded_wading_sample_never_enters_the_recovery_ring |
CLAMP — revert surf - SKIN → surf |
RED 173/1 | an_upward_haul_out_drive_is_never_countermanded_by_the_duck |
Round-1 B2 is discharged. And M7 is now genuinely CI-visible — it goes red in a plain --lib run with no assets, which is the specific N2 mitigation claimed.
B3 is fixed behaviourally, not just test-wise. I re-ran the round-1 measurement itself rather than trusting the new test — same wet/grounded/embedded 1.6 u wading fixture:
b7c33b0 : on_ground=true banked=0 samples=[] any_embedded=false
617b7d3 : on_ground=true banked=4 samples=[[0,0,0]×4] any_embedded=TRUE (round 1)
269dbbf : on_ground=false banked=0 (round 1, main)
The branch now matches main's behaviour. That is the number that mattered.
Flip-4's tightening is the physical bound, exactly. BUOY_RATE = 30.0 (movement.rs:100), dt = 1/60 → 0.5 u, and the buoyancy arms take want = (BUOY_RATE * dt).min(target - pos[2]) then swim_rise(want) ≤ want, so 0.5 u/frame really is the ceiling. SKIN = 0.05 (movement.rs:33) — the doc's stated constant is correct.
The clamp comment is now true, not merely closer. Measured on the branch: a surfaced up-wishing swimmer ends at z = −0.049993 with in_water = true. True in the probe's own terms, which is exactly how the comment scopes it.
#776 and #777 read faithfully. Neither overstates nor understates the findings they were split from; #776 correctly records that main was louder (the ~140 u sealed-box walk-out), which was the reversal I reported against my own hypothesis. I spot-checked #777's factual claim about the gate: .github/workflows/test.yml runs cargo test --workspace --locked on ubuntu-latest, no assets. Confirmed.
Non-blocking
N1 — I could not construct the SKIN residual, and I think it may not be reachable. The doc says a passage whose outbound clearance was under 0.05 u can shut behind the body. Far floor exactly at the duck depth (−4.5), lintel underside swept through the band:
lintel -0.20 / -0.40 / -0.46 / -0.49 : crossed, and returned
lintel -0.52 : duck REFUSED outbound (no trap)
REASONED mechanism for why: with matched surfaces the return sink's ray reaches exactly the outbound duck depth, and the bound guarantees the floor is at or below that, so the ray usually finds no hit and the return lands at the same depth it went out at. The doc errs conservatively, which is the right direction — but "stated exactly" is a reasoned figure, not a measured one, and it is the other residual (surface mismatch, unbounded) that needed the magnitude.
N2 — the bonus clamp fix: mechanism CONFIRMED on main, the 40 u figure NOT reproduced. The coordinator asked me to check whether this was a fabricated justification. It is not. On origin/main (44003f7), with a surfaced up-wishing swimmer in a 1.6 u flooded slot, I measured: the feet reach z = 3.7e-7 (i.e. the clamp puts them exactly on the surface), the body is then read dry, and the dry depenetration net takes it — it shoved the body 2.0 u laterally (one PUSHOUT_RADII step) and dropped it 0.033 u. So the chain you describe — strict in_water at the clamp → body probe says dry → dry net owns a swimming body — is real and pre-existing on main, exactly as claimed.
What I did not reproduce is the specific outcome: dz = -40.0, dxy = 1.0, Grounded. My scene gave the net a clear ring candidate at radius 2, so it escaped laterally instead of taking the floor-snap path. My scene is a guess at yours, so this neither confirms nor refutes the 40 u figure. Please publish the fixture, or soften the comment at movement.rs:773-775 from a specific measurement to the mechanism it actually needs to justify — the mechanism is sufficient on its own and I confirmed it.
N3 — keep the clamp fix in this PR. It is one token of code, discovered by the work, and it is required for the M4 pin to be constructible at all (reverting it turns the M4 test red). Splitting it would leave the PR unable to pin its own guard. On its interaction with the duck: the duck now starts 0.05 u lower, which is inside every margin I measured — the #191 haul-out has 2.4 u of margin against 0.05.
N4 — the clamp change has no test of its own name. Its only pin is a test named for a different guard. It works today; the coupling is a trip-wire for a future rename. One line in the M4 test asserting the clamped feet still read wet would fix it.
N5 — the corpora remain un-run against this head, and I think that is acceptable for this PR. Killing round-1's run was correct (it was measuring the wrong tree). The round-2 change is confined to movement.rs and the guards are now pinned in CI, including the envelope pin that was the specific hole. The general gap is #777's problem, not this PR's — and I would rather it be fixed by the enumerable-gap job #777 proposes than by holding this PR hostage to a multi-hour run.
The log figures — independently re-derived, and the 1560 accounted for
My own cargo test --workspace --locked run on b7c33b0 with my probes removed (tree md5-verified identical to the commit, git status --porcelain empty):
| Figure | Value |
|---|---|
Finished \test` profile` |
present, 1 line, in 15m 12s |
test result: lines |
49 |
| non-canonical (spliced / truncated / missing a count) | 0 |
| well-formed all-zero lines (empty targets — benign, reported separately) | 16 |
| totals | 1560 passed / 0 failed / 43 ignored / 0 measured |
FAILED summaries |
none |
Cross-check against the running N headers, not the binary-invocation lines: 49 running N headers, sum(N) = 1603; passed + failed + ignored = 1560 + 0 + 43 = 1603. Exact. (running N counts ignored tests; passed + failed alone does not match it, and reconciling those two would need a mechanism that does not exist.)
Completeness, not just arithmetic. The equality alone cannot detect a wholly lost binary, so I checked target identity as well: 49 binary-invocation lines = 49 running N headers = 49 summaries, and the set of integration-test target names in the run is an exact match for the 17 tests/*.rs files declared in the tree at b7c33b0 (plus 19 unit-test targets and 13 doc-test targets = 49). No target dropped its pair.
The 49-vs-50 and the ~73-test delta are explained by the base, and I verified it rather than assuming it. origin/main (44003f7) carries exactly one integration-test target that this branch's base does not: crates/eqoxide-renderer/tests/floating_placement.rs (+328 lines, added after 269dbbf). That is the 50th summary line. The passing-count gap is the rest of the same drift — steering.rs +1661, walker.rs +517, collision.rs +362, action_loop.rs +327 between this branch's base and current main. So the delta is the older base, not lost binaries.
One figure does not exist for a test run, and should stop being asked for. rbuild emits its fetched line only when it rsyncs a built eqoxide binary back (rbuild:85, guarded on the fetch path); cargo test produces no such binary, so its absence is expected and is not a truncation signal. The integrity evidence for a test run is the Finished line plus the 49/49/49 correspondence above.
Your reported figures reproduce exactly: 49 summary lines, all 0 failed, 1560 / 0 / 43.
What I could NOT verify
- the 42-zone depenetration corpus (
changed: dry-body=0) — asset-gated, not run; - the five
#[ignore]dwalker_simcorpora — not run against this head by anyone; - the specific 40 u /
dxy=1.0/Groundedtrace (see N2 — mechanism confirmed, magnitude not); - the asset-gated
water_capability --include-ignored4/0 on this head — I verified it at617b7d3last round and did not re-run it here; - live client behaviour — nothing in this review touched a running client.
Summary
Round 2 does what round 2 should: the fixes are structural, the pins are real, and I checked them by mutation rather than by name. B2 and B3 are closed, and B3 is closed on the measurement, not the test.
What remains is the same failure mode in a new place. The floor bound closed one axis of round-1 B1; the surface axis is still open, still silent, and reachable at a 2 u mismatch in geometry this repo already models on purpose. The comment written to replace the falsified universal contains two more universals, both measurably false. And the one refinement the reply calls load-bearing is the one thing in the new code that nothing tests.
… bound is pinned, and the ordering rationale drops both falsified universals Review round 2 (PR #767): all three findings accepted. R2-B1: try_duck_under adds the reviewer's surface bound - the landing column's own float plane must be inside the duck envelope of the passage depth - so an admitted duck's crossing is autonomously round-trip-capable on BOTH axes. Cost measured before adopting: the bound also refuses the qcat pocket-mouth duck (a ~13u surface mismatch, genuinely one-way for the autonomous driver), and measurement showed that cost is nearly nil against the real system: a driven dive crosses with no duck involved, and the walker's own rise-y steering at that pocket sends an up-wish the vspeed gate already excludes from ducking. The escape test therefore became qcat_pocket_swimmer_escapes_to_the_shaft_under_a_driven_dive (green, swept over the stranded band), with the horizontal driver's honest outcome pinned as a WET STALL (never dry, never one-way) in qcat_pocket_horizontal_wish_alone_stalls_wet_at_the_mouth. The same experiment reproduced the LIVE walker wedge offline for the first time (planner emits a rise-at-destination swim_surface hop, swim_vspeed turns the high carrot into an up-wish, body pins under the lid) - that residue is steering/walker territory, filed separately with the repro. R2-B2: the floor test now sweeps far_floor in {-3.0, -4.0}; the -4.0 arm is inside ground_below's probe band and only `floor <= lo[2]` refuses it (measured RED with the refinement deleted, is_some kept). R2-B3: the ordering comment's two falsified universals are deleted; the replacement states the narrow measured basis (banks self-refuse the duck; an admitted duck is round-trip by the bounds; a haul-out changes medium) and says outright that the ordering is not a reversibility argument. Review N4: the surface clamp has an explicit assertion under the M4 test; reverting the clamp fails it by name. The clamp comment now names the reproducible fixture for the 40u figure (the M4 scene, clamp reverted) instead of an unanchored magnitude, and records the reviewer's independent confirmation of the mechanism on main. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HQVEpaaKeXsZcW9VT2roeV
|
Round 3, addressing the round-2 review at R2-B1 — accepted; your two-line bound adopted verbatim — after measuring what it costs, which is the interesting partBefore adopting the surface bound I measured it against this PR's own deliverable, because the qcat pocket→shaft crossing is a ~13 u surface mismatch — your Three consequences, all now in the tree:
R2-B2 — accepted; the
|
| Mutation | Result | Killing test |
|---|---|---|
| surface bound deleted | RED (1 passed; 1 failed in the pair) |
a_duck_never_crosses_into_a_column_whose_float_plane_is_beyond_the_return_envelope |
<= → is_some (? kept) |
RED (−4.0 arm) | a_duck_never_crosses_into_a_column_it_cannot_dive_back_out_of |
clamp → exact surf |
RED | an_upward_haul_out_drive_is_never_countermanded_by_the_duck (crossing + named clamp assertion) |
State at 8514aa4
- movement lib:
66 passed; 0 failed; 1 ignored(Finishedverified) - walker_sim:
6 passed; 0 failed; 5 ignored— P1 green - synthetic_water_capability:
6 passed; 0 failed - asset-gated
water_capability --include-ignored:5 passed; 0 failedon this head — driven-dive escape, horizontal wet-stall, first-frame, deep-start capability (which survives the bound: the deep crossings never needed the duck), canals - Full workspace: two sources.
- CI at the merge ref (this branch merged onto current
main, run 30352477297):Finished \test` profilepresent · **50**test result:lines (the 50th ismain'sfloating_placementtarget, present by target identity) · 0 non-canonical · totals **1673 / 0 / 46** ·sum(running N) = 1719 = 1673 + 0 + 46`, exact. - Branch tree on the builder:
Finished \test` profilepresent (50m 40s — the box was at load ~130) · **49**test result:lines · 0 non-canonical · totals **1561 / 0 / 44** ·sum(running N) = 1605 = 1561 + 0 + 44`, exact. The +1/+1 vs round 2's 1560/43 is the new surface pin (passed) and the asset-gated escape/stall split (ignored), accounted by name.
- CI at the merge ref (this branch merged onto current
- Depenetration corpus: re-run on this head —
zones=42 embedded=35244,changed: dry-body=0, zero drifters,test result: ok. 1 passed; 0 failed ... finished in 248.80s— identical to rounds 1-2, as expected: the round-3 changes are unreachable under the corpus's idle intent (the duck needs a nonzero wish, the clamp an up-wish), and the run confirms rather than assumes that.
Process note applied: no missing-fetched claims on test runs; totals published in the five-figure shape with the running N cross-check.
Fixes #661 — the qcat spawn-pocket swimmer that mounted the tile floor at −55.9687 (0.009 u above the waterline) and could never get back off. Offline, deterministic; no live client involved.
The mechanism I actually found — the issue's attribution was wrong
#661 names the swimming step-up (#191's haul-out branch) as the writer. I instrumented both the step-up branch and the depenetration net on
main@ 269dbbf and drove the exact repro (pocket swim plane −57.978 → shaft,want_swim, horizontal wish only). The step-up never fires — nolow_hiteven occurs before the mount. The measured chain is:is_embedded's footprint ring probes atfeet + 3; a swimmer floats atsurface − 2, so the probe tests the AIR band 1 u above the waterline, where the pocket-rim geometry lives. On alternate frames the swimmer read as "embedded" while its route was physically open — on every frame the wish was allowed to run, the slide made full 0.733 u progress (never blocked).walker_stalled..wtrregion's XY extent.Recovery::at_column's afloat arm falls through to the water-blindnearest_floorhunt for a dry candidate column — the trace showsrec=Grounded(-55.96875) cand_wet=false, dxy exactly 1.000 (=PUSHOUT_RADII[0]), dz +2.0092. The body — still swimming, in water — is beached dry,on_ground,want_swiminert. One-way. (This behaviour was even pinned by a test: see "flipped tests" below.)The mount surface being "a real 14×14 floor indistinguishable from a bank" turned out not to be the crux at all: nothing needs to classify the surface, because the body that got put on it was never in the step-up's hands.
The fix (all in
src/movement.rs)body_in_waterprobestepuses. Buoyancy owns a floater's vertical, the collided slide its lateral; every question the net asks about a floater is mis-posed (details in the door comment).Recovery::Afloatis deleted;at_columnloses itsafloatparameter. The only constructible recovery isGrounded, and only dry bodies can reach the constructor. The qcat spawn pocket: a swimmer at the pocket swim plane is mounted onto the ceiling lid and stranded dry (the actual cause of the #329 live wedge) #649 invariant in its strongest form — not a guard, a state that cannot be expressed.try_duck_under: the swimming step-up's downward mirror. A blocked swimmer first tries sinking its feet (collided, ≤STEP_UP + GROUND_SNAP_TOL= 2.5 u, the same envelope the step-up can climb) and re-sliding; the duck is taken only when it measures strictly more lateral progress and both the lowered start and destination keep the feet in water. At a bank whose face is solid to the bottom the measurement fails and the haul-out keeps the right of way (Navigation + fall-guard don't handle swimmable water — characters can't cross a surface-level pool (stuck leaving Halas) #191). Gated onwish_vspeed <= 0so an explicit upward (haul-out) drive is never countermanded. This is what turns "stops being stranded" into "actually escapes": measured end state(-45.796, 1029.938, -44.98247)— the shaft XY on the shaft's swim plane, wet, in ~0.7 s.recover()(the issue's "second un-Recovery'd writer" note). Behaviour-identical: the only extra effect (stuck_time = 0) is a no-op on the frames that arm can run.The one owner-approved boundary I did not touch:
haul_out_upstays 2.0 and the planner side is untouched — no file outsidesrc/movement.rs+ the three test files changed.What did NOT work (recorded so nobody re-walks it)
min(feet+3, surf−0.5)). It stopped the strand — and then fired during ordinary bank approaches and tunnelled the body through the bank face to the first clear ring candidate on the far side:walker_sim::p1failed with the swimmer at[701.0, 24.8, 8.0], ~650 u out to sea. There is no probe height at which "near geometry" is an emergency for a body that floats; that measurement is what pushed the design to "floaters never enter" rather than "floaters are probed differently".Afloat-or-skip) does not fix the wedge: measured, the swimmer stays wet at its own depth but livelocks in the push-out ping-pong for the full 12 s — the strand becomes a stall.footprint_clearlanding guard on the step-up; any rule classifying the mount surface) were moot once the mechanism was measured: the step-up is not the writer here.Flipped / rewritten tests — called out explicitly
tests/water_capability.rs::qcat_pocket_swim_plane_strands_the_swimmer_on_the_tile_floor→qcat_pocket_swim_plane_swimmer_escapes_to_the_shaft. Old assertions:end z ≈ −55.9687,!in_water(end),>5 u short of the shaft. New: end on the shaft swim plane −44.982 ± 0.05, within 1.5 u of the shaft XY, still in water — swept over start depths {plane, −59.0, −60.5}, covering the band qcat spawn pocket: the swimming step-up hauls a swimmer onto a tile floor 0.009 u above the waterline that it cannot get back off (the remaining #329 wedge) #661 measured as STRANDED (−58.00…−60.75).movement::tests::depenetration_grounds_a_swimmer_pushed_out_of_the_water_entirely→a_swimmer_whose_only_clear_neighbours_are_dry_is_never_beached_by_the_net. The old test pinned the defect (asserted the afloat body ends at z=2.0, grounded, dry — exactly the qcat spawn pocket: the swimming step-up hauls a swimmer onto a tile floor 0.009 u above the waterline that it cannot get back off (the remaining #329 wedge) #661 beach at 1/10 scale). The new one asserts never-beached, still-wet, no hold, and still responsive to swim input. RED on main.movement::tests::depenetration_never_mounts_a_swimmer_onto_the_slab_above_it— first three assertions (never lifted / never grounded / still wet) unchanged; the fourth ("the push-out must have resolved the horizontal overlap") required the net to act on a swimmer and is replaced by a responsiveness assertion (a swimmer between close parallel walls is in a canal, not a trap — swim input moves it).movement::tests::the_nets_water_probe_is_the_BODY_not_the_feet—z == 0exactly was the net freezing the frame; now buoyancy's ordinary ≤0.5 u first-frame rise begins, so the assertion isz < 1.0 && !on_ground, which still fails the pinned mutation (feet-only probe → beached at z=2.0, grounded).tests/water_capability.rs::the_depenetration_push_out_holds_a_qcat_swimmer_at_its_own_depth→the_first_frame_never_lifts_a_qcat_pocket_swimmer_toward_the_dry_tile: frame 1 from that wedged start now legitimately ducks (bounded, collided, −2.0 u in the wish direction), sodz ≈ 0becamedz < 1e-3 && dz > −2.6+ not grounded + still wet — the qcat spawn pocket: a swimmer at the pocket swim plane is mounted onto the ceiling lid and stranded dry (the actual cause of the #329 live wedge) #649 teleports (+2.0092 mount, −12 drop) are still rejected.New tests:
movement::tests::a_swimmer_ducks_under_a_hanging_face_instead_of_stranding_at_it(CI, asset-free; RED on main twice over — no duck, and the net eats the approach) andmovement::tests::a_swimmer_at_a_solid_bank_still_hauls_out_the_duck_does_not_override_191(CI companion to walker_sim P1; RED if the swimming step-up is disabled).Proofs
1. Escape test (was the strand pin), on this branch:
2. Lid tests stay coherent:
3. #191 blast radius, measured (step-up disabled → RED): with the fix in place and only the swimming half of the step-up removed (
(self.on_ground || swimming)→self.on_ground):proving the branch this issue pointed at is still the load-bearing carrier of #191's haul-out guarantee, and this fix did not reroute around it. (With the fix intact, P1 is green:
test result: ok. 1 passed; 0 failed— and note P1 also failed RED under the withdrawn submerged-probe design, which is how that design died.)4. Mutation checks (fix reverted → new tests RED):
Full workspace suite:
Depenetration corpus over baked zones (asset-gated blast-radius harness):
scripts/check-no-local-detail.sh: OK — no forbidden patterns in tracked files.🤖 Generated with Claude Code
https://claude.ai/code/session_01HQVEpaaKeXsZcW9VT2roeV