acdream/docs/research/2026-06-03-p2-door-stepup-handoff.md
Erik f984e92e37 docs(p2): correct the handoff — B1 was the Path 5 near-miss gate, not the climb
The prior localization (step-up CLIMB) was disproven by an ITestOutputHelper
capture. Records the real root cause (A6.P4 near-miss missing retail's
num_sphere>1 gate, fixed in abbd761), that the door blocks faithfully with a
real floor, and that the remaining red tests are separate (apparatus
synthetic-floor artifact, LiveCompare buggy-captures, D4 airborne) — not
simple "flip to green" targets. Next is the user visual gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 09:35:14 +02:00

203 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# P2 pickup handoff — door / building-shell collision = BSP Path 5 grounded step-up
> **🔴 CORRECTED 2026-06-04 — the localization below (the step-up CLIMB) was WRONG; B1 is FIXED.**
> An `ITestOutputHelper` capture of B1 (xunit swallows `Console.WriteLine`) proved the climb code
> (`find_walkable`/`AdjustSphereToPlane`/`step_sphere_down`/`DoStepUp`/`DoStepDown`) is **correct** and
> matches ACE exactly. The real B1 bug was the **A6.P4 near-miss dispatch in `BSPQuery.FindCollisions`
> Path 5 (Contact branch)**, which diverged from retail three ways: (1) recorded a near-miss
> `NegPolyHit` **unconditionally** — retail gates both `set_neg_poly_hit` calls behind
> `if (num_sphere > 1)` (`acclient_2013_pseudo_c.txt:323852`); (2) checked the foot near-miss before
> the head's (retail checks the head/sphere1 first); (3) reversed the `neg_step_up` mapping (retail:
> head index0→FALSE/slide, foot index1→TRUE/step-up, per `SPHEREPATH::set_neg_poly_hit` :323279).
> For B1's single foot sphere the spurious near-miss → outer `!NegStepUp → Collided` → revert → the
> mover wedged at x=0.1, never reached the wall to step up. **Verbatim fix committed (`abbd761`):**
> the gate+order+mapping now match retail; B1 climbs (foot→(0.6,0,0.25)); the Holtburg door blocks
> faithfully (slab `(0,-1,0)` normal) when the scenario has a real floor.
>
> **Remaining red (NOT simple flips — all separate from B1):**
> - `Apparatus_Grounded_50cmOffCenter` — its tick-0 `(0,0,1)` "block" is a **synthetic-test artifact**:
> the apparatus sets `terrain=-1000` so the only BSP is the door slab; the contact-maintenance
> step-down finds no floor underfoot → false Collided/revert, then the mover walks through. With a
> real floor (`terrain=0`) the door blocks faithfully at Y≈11.5 with `(0,-1,0)`. Fix = give the
> grounded test a real floor + assert the block normal is the door's ±Y (NOT the tick-0 `(0,0,1)`
> contact-maintenance hiccup, which is a separate cold-seed first-frame artifact). Do **NOT** just
> flip to `Assert.True(blocked)` — that blesses the artifact.
> - `LiveCompare_DoorOffCenterWalkthrough_Tick13558` / `_DoorBlocksFromOutside_Tick22760` — compare
> against captured **buggy-live** positions; a correct fix makes the harness diverge (blocks earlier).
> Re-baseline to the corrected behavior or retire as documents-the-bug.
> - `D4_AirborneMover_TallWall_PersistsSlidingNormalAcrossFrames` — **airborne (Path 6)**, a separate
> sliding-normal-persistence issue, unrelated to the Path 5 grounded near-miss. Pre-existing.
>
> See `memory/project_p2_door_stepup_findings.md`. **Next: USER VISUAL GATE** (walk through a cottage
> door cleanly; step up a stair) — the authoritative P2 acceptance. The original (wrong) analysis is
> retained below for the record.
> **Canonical pickup for the next session.** Branch `claude/thirsty-goldberg-51bb9b`
> (do NOT branch/worktree; do NOT push without asking; NEVER `git stash`/`gc`). PowerShell on
> Windows; launch logs are UTF-16. Read this FIRST.
## State both altitudes
- **Milestone:** M1.5 — Indoor world feels right.
- **Effort:** the VERBATIM spatial-pipeline port (master plan:
`docs/superpowers/specs/2026-06-03-verbatim-spatial-pipeline-port-master-plan.md`).
- **P1 (membership) = DONE.** Proven to already match retail; the "0/11 lag" was a cdb capture
artifact. Merged to `main` + pushed to both remotes (HEAD `f0d37d8`). See
`docs/research/2026-06-03-p1-membership-swept-advance-handoff.md` (RESOLVED banner) +
`memory/project_retail_membership_criterion.md`.
- **P2 (door / building-shell collision) = IN PROGRESS, root cause LOCALIZED.** The fix is the next step.
- **Next concrete step:** read+compare acdream `find_walkable`/`step_sphere_down` vs retail
`BSPTREE::step_sphere_down` (pc:323665) + `find_walkable`, pin the step-up CLIMB divergence, and
drive `B1_GroundedMover_LowStep_StepsUp` + the door apparatus RED→GREEN.
## TL;DR (the P2 finding)
All **5 failing Core tests** localize to **BSP Path 5 (the grounded `Contact + StepSphereUp` branch)**:
| Test | Symptom |
|---|---|
| `BSPStepUpTests.B1_GroundedMover_LowStep_StepsUp` | grounded mover wall-slides a **walkable 0.25 m step** instead of stepping up (`CurPos.Z` stays 0). The cleanest isolation of the bug. |
| `BSPStepUpTests.D4_AirborneMover_TallWall_PersistsSlidingNormalAcrossFrames` | airborne tall-wall sliding-normal count (`Expected: 2`) — Path 4/5 sliding-normal family. |
| `DoorCollisionApparatusTests.Apparatus_Grounded_50cmOffCenter_FrontApproach_DocumentsBug` | synthetic door + grounded off-center: now blocks at tick 0 with an `(0,0,1)` up-normal + goes airborne (Path 5 step-up artifact, not a faithful door block). |
| `DoorBugTrajectoryReplayTests.LiveCompare_DoorOffCenterWalkthrough_Tick13558` | replay of captured live tick diverges from the engine (documents-the-bug). |
| `DoorBugTrajectoryReplayTests.LiveCompare_DoorBlocksFromOutside_Tick22760` | same, outdoor-block tick. |
These have been the documented baseline RED set for a while (they are P2's target). They did NOT
regress this session — P1's work only touched conformance tests + docs.
## Root cause — PRECISELY localized (the whole upstream chain is verified faithful)
For B1 (the cleanest case), the path is reached + dispatched correctly; the divergence is deep in the
climb. Verified faithful and ruled out this session (DO NOT re-investigate these — see DO-NOT-RETRY):
1. **Path 5 dispatch is reached.** `BSPStepUpFixtures.MakeGroundedTransition` sets `State = Contact |
OnWalkable` + `StepDown=true` — but that `StepDown` is `ObjectInfo`'s flag; the Path 3 dispatch
gates on the `SpherePath.StepDown` flag (only set during a step-down probe), so the **main sweep
correctly lands in Path 5's `Contact` branch** (`BSPQuery.cs:1814`).
2. **Recursion guard passes.** `if (engine is not null && !path.StepUp && !path.StepDown) return
StepSphereUp(...)` (`BSPQuery.cs:1848`) — on the main sweep both `SpherePath` flags are false →
`StepSphereUp` → `DoStepUp` IS called on the wall hit.
3. **`DoStepUp` (`TransitionTypes.cs:3254`) = faithful port of retail `CTransition::step_up`
(pc:273099).** Same structure: clear contact-plane, `stepDownHeight = OnWalkable ? StepUpHeight :
0.04`, `walkableAllowance = OnWalkable ? GetWalkableZ() : LandingZ`, call `DoStepDown(...)`, return
its result. (acdream adds a restore-contact-plane-on-failure block — benign.)
4. **`DoStepDown` (`TransitionTypes.cs:3074`) = faithful port of retail `CTransition::step_down`
(pc:272946).** Skips the down-offset when `StepUp` is set, runs `TransitionalInsert(5)`, accepts
iff `OK && ContactPlaneValid && ContactPlane.N.z >= walkableZ`, then placement-validates.
**So the divergence is INSIDE the step-up climb:** `DoStepDown` → `TransitionalInsert(5)` → Path 3
`step_sphere_down` → **`find_walkable`'s upper-floor find + sphere-up-adjust when `sp.StepUp=true`**.
It fails to locate/lift onto the 0.25 floor within the 0.30 budget → `DoStepDown` returns false →
`StepUpSlide` → wall-slide → `Z=0`. The retail oracle for the climb is `BSPTREE::step_sphere_down`
(`@ 0x53a210` pc:323665) + `BSPNODE/BSPLEAF::find_walkable` + `adjust_sphere_to_plane`.
acdream code map: `BSPQuery.StepSphereUp` (`:1372`), `BSPQuery.step_sphere_down` (`:1206`),
`BSPQuery.find_walkable` (`:693`), `BSPQuery.AdjustSphereToPlane` (search it), `Transition.DoStepUp`
(`:3254`), `Transition.DoStepDown` (`:3074`).
## ⚠ TOOLING NOTE (cost me a probe cycle — don't repeat)
**xunit swallows `Console.WriteLine`.** The built-in `ACDREAM_DUMP_STEPUP=1` trace (in `DoStepUp`)
and the `[step-walk]` probe (`PhysicsDiagnostics.ProbeStepWalkEnabled`) both print via `Console` →
they do NOT surface in `dotnet test ... -l "console;verbosity=detailed"`. The tests that DID show
output used `ITestOutputHelper` (`_out.WriteLine`). So to trace which climb condition fails
(`TransitionalInsert(5)` result / contact-plane / `N.z` / placement), **add an `ITestOutputHelper`-based
trace to B1 (or a focused harness) — don't rely on the `Console` probes inside the engine.**
## DO-NOT-RETRY (verified faithful this session)
1. Path 5 dispatch / the Contact-branch reachability — confirmed B1 reaches Path 5.
2. The recursion guard / `StepSphereUp` call — confirmed `DoStepUp` is called.
3. `DoStepUp` vs retail `step_up` — faithful, ruled out.
4. `DoStepDown` vs retail `step_down` — faithful, ruled out.
The bug is downstream in `find_walkable`/`step_sphere_down`'s **step-up adjust**. Start there.
## Next steps (evidence-first — the door saga burned many SPECULATIVE fixes; do NOT repeat)
1. **Read+compare** acdream `BSPQuery.find_walkable` (`:693`) + `step_sphere_down` (`:1206`) +
`AdjustSphereToPlane` against retail `BSPTREE::step_sphere_down` (pc:323665) + `BSPNODE/BSPLEAF::
find_walkable` + `adjust_sphere_to_plane`. Focus on the `step_up==1` path: how retail lifts the
sphere onto a step within `step_down_amt`, and where acdream fails to.
2. **Instrument B1 with `ITestOutputHelper`** (Console is swallowed — see tooling note) to pin which
condition fails: does `TransitionalInsert(5)` return OK? is `ContactPlaneValid` set? is the landing
`N.z >= walkableZ`? does placement (`TransitionalInsert(1)`) reject? `B1` is sub-second, headless.
3. **Fix the climb verbatim** (cite the decomp anchor), drive `B1` GREEN, then `B2` (must still
block the 5 m wall), then the door apparatus (`Apparatus_Grounded_50cmOffCenter…` flips to
block-not-walkthrough → rewrite its assertion to `Assert.True(blocked) && pos.Y < 12.0`), then the
2 `LiveCompare` ticks, then `D4`.
4. **Definitive cross-check if the decomp is ambiguous:** cdb-attach to retail at a Holtburg cottage
doorway, break on `CTransition::step_up`/`step_down`/`BSPTREE::step_sphere_down`, walk a low step +
the door, capture what retail does. PDB MATCHES; tooling in `tools/cdb/` (CLAUDE.md "Retail debugger
toolchain"). Needs the user's retail client up + walking.
5. **User visual gate:** at a doorway, walk through cleanly (foot Y stable, no oscillation), walls
block; step up a low step (cottage stair) climbs.
## Test baseline (going into the P2 fix)
Core **1309 pass / 5 fail / 1 skip** — the 5 are exactly this P2 target (`Apparatus_Grounded`,
`LiveCompare_DoorOffCenterWalkthrough_Tick13558`, `LiveCompare_DoorBlocksFromOutside_Tick22760`,
`BSPStepUpTests.D4`, `BSPStepUpTests.B1`). Conformance 60 pass / 1 skip / 0 fail. App 177 green.
## Parked (do NOT touch without explicit user approval)
- **(a)(d) membership cleanups** — approval-gated refactors of WORKING code (CLAUDE.md "don't replace
working retail-faithful logic without approval"): (a) remove redundant `ResolveCellId` (already out
of the prod per-frame path; survives only in the `DataCache==null` test fallback); (b) unify the
forked `find_env_collisions`; (c) replace the `CheckBuildingTransit` bridge with intrinsic building
stabs in `find_transit_cells`; (d) make the per-cell ObjCell graph the collision authority (collision
still uses the landblock-wide `ShadowObjectRegistry`). The one soft spot: outdoor→indoor `0031↔0170`
building-entry is live-clean but NOT conformance-locked (rides on `CheckBuildingTransit`).
- **Render residuals (P3/P4)** — the VISIBLE doorway seam is now in the render path: the flap =
camera-collision residual (chase eye drifts out of the cell → viewer-cell flips; master-plan P3,
`SmartBox::update_viewer`); the void = unported PView seal (P4). Membership (physics) is correct.
See `docs/research/2026-06-03-p1-visual-gate-render-residuals.md`. Master-plan order: P2 → P3 → P4.
---
## FRESH-SESSION PROMPT (copy-paste)
```
Continue the VERBATIM retail spatial-pipeline port for acdream. Branch claude/thirsty-goldberg-51bb9b
(do NOT branch/worktree; do NOT push without asking; NEVER git stash/gc). PowerShell on Windows;
launch logs are UTF-16.
STATE: M1.5 (Indoor world feels right). P1 membership = DONE (proven to already match retail; the
"0/11 lag" was a cdb capture artifact; merged + pushed, HEAD f0d37d8). P2 (door/building-shell
collision) = IN PROGRESS, root cause LOCALIZED to BSP Path 5 grounded step-up. The fix is the job.
READ FIRST (canonical, in order):
1. docs/research/2026-06-03-p2-door-stepup-handoff.md (THIS handoff — the localization, the
DO-NOT-RETRY list, the tooling note, the next steps).
2. docs/superpowers/specs/2026-06-03-verbatim-spatial-pipeline-port-master-plan.md (§3 P2; §1/§2 B3/B4).
3. docs/research/2026-05-25-door-bug-partial-fix-shipped.md (the door saga state + its do-not list).
THE FINDING: all 5 failing Core tests localize to BSP Path 5 (grounded Contact + StepSphereUp). For
B1 (cleanest: a grounded mover wall-slides a walkable 0.25 m step with a 0.30 m budget, Z stays 0),
the whole upstream chain is VERIFIED FAITHFUL + correctly reached — Path 5 dispatch, the recursion
guard, DoStepUp (= retail CTransition::step_up pc:273099), DoStepDown (= retail step_down pc:272946).
The divergence is INSIDE the step-up CLIMB: DoStepDown → TransitionalInsert(5) → Path 3
step_sphere_down → find_walkable's upper-floor find + sphere-up-adjust when sp.StepUp=true. It fails
to lift onto the 0.25 floor → StepUpSlide → wall-slide.
DO NOT RE-INVESTIGATE (verified faithful): Path 5 dispatch, the recursion guard, DoStepUp, DoStepDown.
DO NOT speculate on the BSP fix without apparatus (the door saga burned many speculative fixes).
TOOLING: xunit swallows Console.WriteLine — the ACDREAM_DUMP_STEPUP / [step-walk] probes don't surface
in the runner; instrument B1 with ITestOutputHelper to trace the climb conditions.
THE JOB (P2 fix, evidence-first):
1. Read+compare acdream BSPQuery.find_walkable (:693) / step_sphere_down (:1206) / AdjustSphereToPlane
vs retail BSPTREE::step_sphere_down (pc:323665) + BSPNODE/BSPLEAF::find_walkable + adjust_sphere_to_plane,
focused on the step_up==1 climb. Pin the divergence (instrument B1 with ITestOutputHelper if needed).
2. Port the climb verbatim (cite the anchor). Drive RED→GREEN: B1 (steps up), then B2 (still blocks the
5 m wall), then DoorCollisionApparatusTests.Apparatus_Grounded_50cmOffCenter (flips to block — then
rewrite its documents-the-bug assertion to Assert.True(blocked) && pos.Y < 12.0), then the 2
LiveCompare ticks, then D4.
3. If the decomp is ambiguous: cdb-attach to retail at a cottage doorway (break on step_up/step_down/
step_sphere_down) — needs the user's retail client. PDB matches; tools/cdb/.
4. USER VISUAL GATE: walk through a doorway cleanly (foot Y stable, walls block); step up a cottage
stair (climbs).
TEST BASELINE: Core 1309 pass / 5 fail (the P2 target above) / 1 skip; Conformance 60 pass / 1 skip;
App 177 green. PARKED (need explicit approval): the (a)-(d) membership cleanups + the render residuals
(P3/P4 — the visible doorway flap/void). Do NOT speculate a Path-5 fix before the climb divergence is
pinned with evidence.
```