acdream/docs/research/2026-06-04-p2-cellar-lip-flatfloor-cp-handoff.md
Erik 41db027f34 docs(p2): record cellar-lip wedge visual-gate PASS (cellar smooth, door blocks, step-up climbs)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 09:24:54 +02:00

502 lines
38 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 cellar-lip wedge — CANONICAL handoff (flat-floor contact-plane coin-flip)
## ✅ FIXED + VISUAL-GATE PASSED 2026-06-05 (START HERE — supersedes UPDATE 2 below)
**VISUAL GATE PASSED (user, 2026-06-05): "Yes all works!"** — cellar ascent smooth (no last-step wedge),
inn door still BLOCKS, generic step-up still climbs. The residual 9/29 sliding-normal records did NOT
manifest in live play, confirming they were buggy-trajectory artifacts (not a live issue). Commits:
`cc4590f` (fix + validation tests + handoff/memory) and `9fdf6a5` (strip the dispatch-trace probes).
**The P2 cellar-lip wedge is DONE.** (Commits are local on the branch — not pushed.) The rest of this
banner is the root-cause writeup.
**The pinned "find_walkable is NEVER called during the step-down" (UPDATE 2) was a PROBE ARTIFACT.**
A clean `[fc-dispatch]`/`[step-sphere-down]` trace (TEMP probes, gated on `ACDREAM_PROBE_INDOOR_BSP`,
in `BSPQuery.FindCollisions` + `StepSphereDown`) proved `find_walkable` (Path 3 / `StepSphereDown`)
**IS** reached for both 0175 (primary) and 0171 (other-cell) during the step-down — UPDATE 2 mis-read
it (the `[fc-dispatch]` cell logs `path.CheckCellId` = the carried cell 0175 even while iterating
0171's BSP, because `CheckCellId` is the carried cell, not the iterated one).
**THE REAL ROOT CAUSE (ramp-climb family, 20/29 records):** `Transition.CheckOtherCells` collided the
OTHER cells against a **stale `footCenter`** snapshotted at `FindEnvCollisions` entry (TransitionTypes.cs
~L1959) — i.e. BEFORE the primary `insert_into_cell` ran. The primary collide can MOVE the sphere: a
Path-5 full-hit dispatches `step_sphere_up`, and a successful step-up **climbs the foot onto the cottage
floor yet still returns OK**. Retail's `check_other_cells` (`acclient_2013_pseudo_c.txt:272735`
`(*cell+0x88)(this)`) reads the **LIVE `sphere_path.global_sphere`** (post-insert). acdream used the
pre-climb snapshot, which is sunk ~0.25 m below the floor → the foot spuriously **near-misses the very
floor it just climbed onto** → `neg_step_up` → a doomed SECOND step_up against the floor normal (0,0,1)
whose `step_up_slide` unwinds the climb (it slides relative to `GlobalCurrCenter` = the step start, low Z)
`validate_transition` reverts the whole step → **0 % advance**.
**FIX (shipped):** in `Transition.RunCheckOtherCellsAndAdvance` re-read `footCenter =
sp.GlobalSphere[0].Origin` before iterating other cells. One line + comment. Pre-fix 0/29 records
advanced; post-fix **20/29 climb onto the cottage floor (Z≈94)**. **Zero regression** — full Core suite
1321 pass / 4 fail (the documented baseline 4: `Apparatus_Grounded_50cmOffCenter`,
2× `DoorBugTrajectoryReplay LiveCompare_*`, `BSPStepUpTests.D4`) / 1 skip. The 2 door `LiveCompare`
divergences are **byte-identical** with/without the fix (the door's step_up FAILS → sphere restored →
position unchanged → `footCenter` == live). Tests: `CellarLipWedgeTests.Fix_StaleFootCenter_*` (2 new,
GREEN).
**REMAINING RESIDUAL (9/29, OUT OF SCOPE this pass):** the `(0,-1,0)` sliding-normal **+Y-kill**
(`AdjustOffset` slide-crease projects the into-cottage +Y onto the floor×wall crease = world X and zeroes
it → only X survives → hits the slab X wall → step_up fails on the flat floor → revert). 7/29 records;
record #6 is the canonical one (`DocumentsResidualWedge_LiveFloorCp_SlidingNormalKillsPlusY`). This is
**slide-recovery territory** the kickoff said NOT to re-investigate, and is **suspected to be a
buggy-trajectory artifact** (the stale slide accumulated only because the player was already oscillating;
once the ramp-climb advances cleanly the player should not enter the south-wall-slide-into-doorway state).
**Let the VISUAL GATE decide** whether it needs a follow-up before touching the slide. (Record #21 moves
Y away from the cottage and is likely a legitimate non-advance.)
**VISUAL GATE (next):** run the client, walk up the Holtburg cottage cellar stairs — expect the last-step
wedge GONE (smooth ascent onto the floor). Also re-confirm the inn door BLOCKS and a generic step-up
climbs (the fix only changes `check_other_cells`'s position reference). If the ascent still intermittently
wedges, the `(0,-1,0)` +Y-kill is live → investigate `AdjustOffset` slide-crease / the sliding-normal seed
(with the visual evidence then justifying touching the slide). Apparatus: `[fc-dispatch]`/`[step-sphere-down]`
probes + `CellarLipWedgeTests.Diagnostic_TraceRecordByIndex` reproduce any record in <200 ms.
---
## ▶ NEXT-SESSION KICKOFF (historical — its "find_walkable never called" framing was disproven above)
**State:** M1.5 / P2 cellar-lip "blocked at the last step" wedge. A FAITHFUL deterministic reproduction now
exists. The cause has been peeled through SIX evidence-disproven framings to one bounded question. No fix
landed (intentionally the last layers were each disproven; do NOT guess, the collision code is load-
bearing). Branch `claude/thirsty-goldberg-51bb9b` (do NOT branch/worktree; do NOT push w/o asking; NEVER
`git stash`/`gc`). PowerShell on Windows; launch logs UTF-16. Use `superpowers:systematic-debugging`.
**READ (in order):** (1) THIS file's `## SESSION-END` + `UPDATE 1` + `UPDATE 2` (the live captures + the
pinned root cause) and `CORRECTION 1`/`CORRECTION 2` (CP + cull are RETAIL-FAITHFUL, proven). (2)
`memory/project_p2_door_stepup_findings.md`.
**DONE — faithful apparatus (uncommitted in worktree; RECOMMEND committing first):**
`tests/AcDream.Core.Tests/Physics/CellarLipWedgeTests.cs` + `Fixtures/cellar-lip/0xA9B4017{1,4,5}.json` +
`Fixtures/cellar-lip/wedge-records.jsonl` (29 real `ACDREAM_CAPTURE_RESOLVE` wedge calls). Replays the EXACT
captured calls (seed body-before, real climb dir X,+Y) through the lip-cell engine **all 29 reproduce
the wedge at 0% advance in <200 ms.** Tests (all GREEN as documents-the-bug/diagnostics):
`DocumentsWedge_LiveFloorCp_PlayerStuckAtCottageFloorEdge`, `Diagnostic_ReplayLiveWedgeRecords_Advance`,
`Diagnostic_ReplayFloorCpRecord_StepUpProbes` (→ `%TEMP%/lip-wedge-stepup.log`), + 2 synthetic.
**DISPROVEN — do NOT re-investigate:** flat-floor CP (retail also no-CP, smooth Correction 1); the
`PosHitsSphere` cull sign (retail-faithful, `cdb -z`-verified Correction 2); sphere radius (0.48=player
correct, 0.30=camera probe); the A6.P4 neg-poly `Collided``slide_sphere` shortcut (fix attempted + reverted,
didn't clear it the slide returns offset=0 then degenerates to Collided on re-check).
**PINNED ROOT-CAUSE LAYER (UPDATE 2):** during the step-up's step-down (`DoStepUp``DoStepDown`
`TransitionalInsert(5)`), **`BSPQuery.FindWalkableInternal` is NEVER called for cell 0171** (confirmed after
a CLEAN rebuild `[fw-enter]` TEMP probe fires 0×). So the cottage floor (0171 poly 0x0023, n=(0,0,1),
Z=94) is **never tested as walkable** no contact plane step-down rejects (`cpValid=False`) step-up
fails `StepUpSlide=Collided` wedge. `[other-cells] iter=0171 result=OK` is returned WITHOUT reaching
`StepSphereDown``find_walkable`.
**THE JOB (bounded, evidence-first — NO speculative edits):**
1. Trace `Transition.FindEnvCollisions` (TransitionTypes.cs) `BSPQuery.FindCollisions` PATH DISPATCH for
cell 0171 when `StepDown=true`. Find WHY `StepSphereDown`/`FindWalkableInternal` is skipped candidates:
entry `NodeIntersects` early-OK; Path 1 (Placement) taken (DoStepDown's placement insert); the primary
0175 collision returning Collided (the X-wall Path-5 `StepSphereUp`, `stepUp=stepDown=False` = the OUTER
non-step pass) short-circuiting before `CheckOtherCells(0171)`; or StepDown not actually set on that call.
Use the `[fw-enter]`/`[find-walkable]` TEMP probes **FORCE A CLEAN REBUILD (`Remove-Item obj,bin`) for
any Core probe edit; `dotnet test`/`dotnet build` incremental did NOT pick up new BSPQuery.cs probes
(cost two probe rounds).**
2. Port retail's behavior (oracle: `CEnvCell::find_collisions` pc:309560 `BSPTREE::find_collisions`
pc:323725 Path-3 `BSPTREE::step_sphere_down` pc:323665 `BSPLEAF::find_walkable` pc:326793). Verify
how retail's step-down reaches `find_walkable` on the cottage floor where acdream's does not.
3. Fix VALIDATE: flip `CellarLipWedgeTests.DocumentsWedge_LiveFloorCp_*` to `advance>0.25·requested`
GREEN; `Diagnostic_ReplayLiveWedgeRecords` advance% jumps off 0%. 4. REGRESSION: `DoorBugTrajectoryReplayTests`
+ full Core suite. **VISUAL GATE: cellar ascent clean (no last-step wedge) + inn door BLOCKS + generic
step-up climbs.**
**ENABLER:** `cdb -z "C:\Turbine\Asheron's Call\acclient.exe"` = offline static disasm + `uf` w/ PDB symbols
(no live attach); use it to verify any retail branch/offset (the cull-sign error was a BN parity-jump
mis-read never trust BN `if(p)` for `test ah,N; jp`).
**Apparatus (uncommitted, worktree):** the test + fixtures above; TEMP probes in `BSPQuery.cs`
(`[path5-wall]`,`[fw-enter]`,`[find-walkable]`, STRIP) + `TransitionTypes.cs` (`[neg-poly]`,`[stepsphereup]`,
`[stepdown-decide]`, CheckOtherCells cn/sn/negHit, STRIP) all gated on `ACDREAM_PROBE_INDOOR_BSP`; captures
`lip-wedge-resolve.jsonl`/`lip-cells/`/`launch-*.log`; cdb scripts `tools/cdb/retail-connector-collide-trace.cdb`
(+ flatfloor/lip); analyzers `analyze_wedge_jsonl.py`/`extract_wedge_records.py`/`analyze_v1_corr.py`;
`cdbz-disasm.txt`/`cdbz-poshits.txt`. **Test baseline:** Core prior 1310p/4f/1s + 5 GREEN lip tests; App 177.
---
> **Canonical pickup, 2026-06-04 PM.** 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. This SUPERSEDES the membership re-diagnosis in
> `docs/research/2026-06-04-p2-cellar-corner-stepup-handoff.md` (now history) and folds in
> the full chain from `memory/project_p2_door_stepup_findings.md`.
---
## 🧪 SESSION-END 2026-06-04 PM — deterministic repro BUILT + first fix attempt REVERTED
**Apparatus shipped (uncommitted in worktree):** `tests/AcDream.Core.Tests/Physics/CellarLipWedgeTests.cs`
+ fixtures `tests/AcDream.Core.Tests/Fixtures/cellar-lip/0xA9B4017{1,4,5}.json` (copied from `lip-cells/`).
Loads the 3 lip cells (synthetic single-leaf BSP, same as `CellarUpTrajectoryReplayTests`), seeds the
player (r=0.48, foot bottom Z=93.456 foot-sphere center Z=93.936 = the live wedge) carried in slab
0175, drives forward. **Reproduces the wedge deterministically in <90 ms:** the player FREEZES, blocked
by the threshold slab's X side wall (poly normal world (1,0,0)). Two tests, both GREEN as documents-the-
bug: `Diagnostic_DriveOffThreshold_DumpTrajectory` (dumps trajectory+probes to `%TEMP%/lip-wedge-diag.log`
via Console redirect) + `DocumentsWedge_PlayerFrozenAtThreshold_BlockedByMinusXWall`.
**FIX ATTEMPT #1 — REVERTED.** Hypothesis: the A6.P4 neg-poly `NegStepUp==false` branch
(`TransitionTypes.cs` ~line 1083) returns `Collided` (a deliberate "simpler response" shortcut; the
comment says the slide was deferred), where retail dispatches `neg_step_up==0 → slide_sphere`. Replaced
it with `SlideSphereInternal(NegCollisionNormal, GlobalCurrCenter[0].Origin)` (mirroring the NegStepUp=
true branch). **Did NOT fix the wedge** reverted. WHY: the slide returns `Slid` with **offset=0** (the
Y displacement is already along the crease `dir=cross((1,0,0),(0,0,1))=(0,1,0)`), so the sphere
doesn't move; the loop re-checks with `gDelta≈0` `SlideSphere`'s `offset.LengthSquared<ε → Collided`
branch (`TransitionTypes.cs:2877`) revert. So the bug is NOT the shortcut alone it's the
**slide/loop-commit**: the parallel-graze slide produces no advance, and the re-check degenerates to
Collided.
**TWO GAPS for the next pass:**
1. **Faithful repro:** the synthetic drive direction (world Y) is a GUESS and is PARALLEL to the X
wall (keeps grazing). The real climb direction is unknown without the exact `targetPos`. **Get a
short `ACDREAM_CAPTURE_RESOLVE=<path>` JSONL of the live wedge** (one acdream run, wedge ~5 s) wire
a `LiveCompare`-style test (the proven `CellarUpTrajectoryReplayTests` pattern) with the exact
currentPos/targetPos/body-before. That makes the RED test faithful + the fix validatable.
2. **The real fix is in the slide/loop:** why does retail's `slide_sphere` advance the sphere PAST the
parallel graze where acdream's returns offset=0 then degenerates to Collided on re-check? Trace
retail `CSphere::slide_sphere` (pc:321660) vs acdream `SlideSphere` (`TransitionTypes.cs:2826`) for
the parallel-wall + grounded case, AND why the loop re-check sees `gDelta≈0`. NOTE the live wedge
used the **NegStepUp=TRUE** path (StepSphereUpStepUpSlide=Collided) while the synthetic repro used
**NegStepUp=FALSE** (neg-poly-dispatchCollided) BOTH end in `SlideSphere`/`SlideSphereInternal`
returning Collided, so the common fix point is `SlideSphere`'s degenerate-offset handling, not the
dispatch branch. **DOOR REGRESSION RISK:** any `SlideSphere`/neg-poly change touches the A6.P4 door
block regression-test `DoorBugTrajectoryReplayTests` + visual-gate the inn door BLOCKS.
**Test baseline unchanged:** the 2 new lip tests are GREEN (documents-the-bug). Build green. The reverted
fix leaves `TransitionTypes.cs` functionally identical (only an explanatory comment added at the shortcut).
### UPDATE (same session, later) — FAITHFUL repro BUILT + ROOT CAUSE PINNED
Got the JSONL (`ACDREAM_CAPTURE_RESOLVE` `lip-wedge-resolve.jsonl`, 17K player records). The real climb
direction is **X,+Y** (my synthetic Y guess was backwards). Extracted 29 representative wedge records to
`tests/AcDream.Core.Tests/Fixtures/cellar-lip/wedge-records.jsonl` (`extract_wedge_records.py`).
`CellarLipWedgeTests` now replays the EXACT captured calls (seed body-before, replay `ResolveWithTransition`
through the lip-cell engine): **all 29 reproduce the wedge bit-faithfully (0% advance).** New tests (all
GREEN as documents-the-bug / diagnostics): `Diagnostic_ReplayLiveWedgeRecords_Advance`,
`Diagnostic_ReplayFloorCpRecord_StepUpProbes`, `DocumentsWedge_LiveFloorCp_PlayerStuckAtCottageFloorEdge`.
**ROOT CAUSE PINNED** (via `Diagnostic_ReplayFloorCpRecord_StepUpProbes` `%TEMP%/lip-wedge-stepup.log`):
the player is at the doorway EDGE of the cottage floor. The step-up (triggered by the X wall, normal
(1,0,0), STEEP) step-down multi-cell check reaches **0171 poly 0x0023 = the cottage floor** (n=(0,0,1),
world Z=94). The 0.48 sphere **OVERLAPS** it (`overlapsSphere=True`, `dist=0.085`) BUT it's **REJECTED
because the sphere center projects outside the floor poly's edge** (`insideEdges=False`, `gap=0.395`). So
`[other-cells] iter=0171 result=OK` (NOT Adjusted), no contact plane is set `[stepdown-decide] cpValid=
False accept=False` step-up FAILS `StepUpSlide=Collided` wedge. Retail accepts the floor at its edge
and crosses (0175 never blocks). **This is a WALKABLE-EDGE acceptance divergence**, not a CP/cull/slide bug.
**THE FIX (next, narrow):** compare acdream's walkable-edge math vs retail for the sphere-overlaps-floor-
but-center-outside-edge case. Actual walkable test = `BSPQuery.WalkableHitsSphere` (254)
`PolygonHitsSpherePrecise` (overlap) + `AdjustSphereToPlane` (351); `[other-cells] result=OK` means one of
them returned false for poly 0x0023. The `[walkable-nearest]` diagnostic uses `CheckWalkable` (287, the
edge/`insideEdges` test). Retail oracle: `CPolygon::walkable_hits_sphere` (pc:323006) +
`CPolygon::check_walkable` (pc:322811) + `CPolygon::adjust_sphere_to_plane` (pc:322032). Read which one
rejects the edge-overlap and why retail accepts it. **Validate with `CellarLipWedgeTests` (flip
`DocumentsWedge_LiveFloorCp_*` to assert advance>0.25·requested). DOOR REGRESSION RISK: walkable changes
are global — run `DoorBugTrajectoryReplayTests` + visual-gate the inn door BLOCKS + generic step-up climbs.**
Apparatus: `lip-wedge-resolve.jsonl`, `Fixtures/cellar-lip/*`, `analyze_wedge_jsonl.py`,
`extract_wedge_records.py`, `%TEMP%/lip-wedge-stepup.log`.
### UPDATE 2 — deeper: `find_walkable` is NEVER called during the step-down (cottage floor never tested)
Drilled one layer further with TEMP probes `[fw-enter]`/`[find-walkable]` in `BSPQuery.FindWalkableInternal`
(gated on `ACDREAM_PROBE_INDOOR_BSP`, marked STRIP, uncommitted). **Confirmed after a CLEAN rebuild (deleted
obj/bin) — `[fw-enter]` fires ZERO times** while the prior probes fire. So during the step-up's step-down
(`DoStepUp``DoStepDown``TransitionalInsert(5)`), `FindWalkableInternal` is **never reached** for 0171 (nor
0175): the cottage floor poly 0x0023 is never tested by the walkable finder. `[other-cells] iter=0171
result=OK` is returned WITHOUT `StepSphereDown``FindWalkableInternal`. So the "walkable-edge acceptance"
framing in UPDATE 1 is one level too shallow — the floor isn't *rejected* by the edge test, it's never
*tested* at all. Root: `FindEnvCollisions`/`BSPQuery.FindCollisions` for 0171 during the step-down returns OK
on a path BEFORE Path 3 (StepDown→StepSphereDown). **NEXT: trace `FindEnvCollisions` (TransitionTypes) → which
`FindCollisions` path 0171 takes during `StepDown=true` (entry NodeIntersects early-out? Path 1 Placement?
the primary-0175 result short-circuiting CheckOtherCells?) and why StepSphereDown/find_walkable is skipped.**
The `[stepsphereup] stepUpFlag=False stepDownFlag=False` means the X-wall StepSphereUp is the OUTER
(non-step) collision; the step-DOWN that should find the floor is a separate inner insert that never runs
find_walkable. NOTE: a clean `dotnet build`/`dotnet test` did NOT pick up new `BSPQuery.cs` probes until
`Remove-Item obj,bin`**force a clean rebuild when adding Core probes** (cost two probe rounds this session).
**HONEST STATUS: NO FIX. The collision/step path is deeper than a single-line fix** — 6+ framings this
session (CP→cull→slide→neg-poly→walkable-edge→find_walkable-not-called), each disproven by evidence and the
next layer exposed. This is the systematic-debugging "question the architecture" signal. The FAITHFUL repro
(`CellarLipWedgeTests`, 29 records @0% advance) makes the next attempt iterable; the next move is the
`FindEnvCollisions`/`FindCollisions`-path trace above, NOT another speculative edit. The collision code is
load-bearing (every floor/wall/step) — do not guess.
---
## ⚠️ CORRECTION 2026-06-04 (next session) — THE CP IS RETAIL-FAITHFUL; v2 IS MOOT
**The "decisive question" below is ANSWERED from the EXISTING v1 log — no new retail trace
needed.** The v1 `retail-flatfloor-trace.log` was wrongly dismissed as a `gu` artifact. It is
**real data.** Proof (full-file correlation over all 5,349 records, `analyze_v1_corr.py`):
- sphere **z ≤ 90.0 → pure ret=3** (CP set); **z = 94.01 (the flat cottage floor) → pure ret=1
(NO-CP), 877 records, zero ret=3**; ret mixes only in the **ramp transition zone (9093.7)**,
which is physical. A corrupted-`eax` artifact CANNOT produce two large pure populations at
opposite Z extremes with a physical transition between — the ret tracks the input Z exactly.
- **`walk_interp = 1.0 → ret=1 (no-CP) 770×`** — i.e. retail, with `walk_interp=1.0` on the flat
floor, gets NO contact plane and is **smooth**. That is the *exact* acdream condition
(`walkInterp=1.000 cpValid=False`).
- `cdb -z acclient.exe` (offline static disasm, symbols) confirms `BSPTREE::step_sphere_down`
`+0x218`=`mov eax,3;ret` (reached only after `[eax+18h]=1` contact_plane_valid + `set_walkable`)
and `+0x227`=`mov eax,1;ret` (early `je` when `find_walkable` found nothing). So ret3↔CP-set,
ret1↔no-CP is certain.
**ANSWER:** retail's `step_sphere_down` returns **NO-CP (ret 1)** on the flat cottage floor —
exactly like acdream — and retail crosses smoothly. **The contact plane is NOT the divergence.**
Both "if NO-CP → trace set_contact_plane callers" and "if SET-CP → divergence in StepSphereDown"
branches below rest on a FALSE premise (that retail establishes a flat-floor CP somewhere). It
does not. **Do NOT run the v2 trace; do NOT hunt a retail flat-floor CP path.**
**REDIRECTED diagnosis (back to the connector recovery — the `RE-DIAGNOSIS 2` / `SLIDE LOCALIZED`
line in memory, which the flat-floor-CP finding had wrongly sidelined):** the wedge is the
per-cell collide on **connector 0175** returning **Slid** during the recovery, which reverts the
good floor landing. Static comparison this session confirms the recovery structures ALL MATCH
retail: `find_collisions` Contact full-hit → `step_sphere_up`; `step_up` fail → `step_up_slide`
`slide_sphere` (retail `CSphere::step_sphere_up` pc:321611321638, `step_up_slide` pc:273930);
`check_other_cells` halts on Slid (4) clearing CP (pc:272717, `cdb -z` jump-table on `(result-1)`);
acdream `TransitionalInsert` *continues* (no revert) on Slid (TransitionTypes.cs:881). The SOLE
open question: **does retail's per-cell `CEnvCell::find_collisions` return Slid (recover & slide)
or OK (never hits) for connector 0175 at the lip?**
- OK in retail → acdream's connector Slid is SPURIOUS (over-detect / over-step-up 0175) → fix there.
- Slid in retail → retail slides+continues; acdream's wedge is the substep **REVERT** upstream
(`FindTransitionalPosition` / `ResolveWithTransition`), not the collide.
**NEW decisive trace (READY, robust, no `gu`):** `tools/cdb/retail-connector-collide-trace.cdb`
breaks `CEnvCell::find_collisions+0x1e` (the SINGLE exit; `esi`=`this`, `eax`=result; cell id
`poi(esi+0x28)`), logs ret per lip cell 0xA9B4017X. Built + offset-verified entirely offline via
`cdb -z` (no live attach). **ENABLER:** `cdb -z "C:\Turbine\Asheron's Call\acclient.exe"` does
offline static disassembly with full PDB symbols — verify any trace offset without a running client.
Everything below this banner is RETAINED FOR HISTORY (the flat-floor-CP hypothesis, now disproven).
---
## ⚠️ CORRECTION 2 — 2026-06-04 PM (live retail + acdream captures; the REAL mechanism)
Two live captures this session settled it. **Retail-connector trace** (`tools/cdb/retail-connector-collide-trace.cdb`
`retail-connector-collide-trace.log`, breaking `CEnvCell::find_collisions+0x1e`, single nesting-safe exit):
over ~85K samples the **connector cell 0175 returns 2692 OK + 94 Adjusted + 0 Collided + 0 Slid** — it
**never blocks**. (Floor 0171 and 0174 DO block — real cottage-room walls — so the trace is working.) So
the connector is a pure pass-through / successful-step-up in retail; acdream spuriously blocks it.
**acdream live capture at the wedge** (`launch-lip-capture.log`, ACDREAM_PROBE_INDOOR_BSP=1 + the 4 TEMP
probes + cell dumps `lip-cells/0xA9B4017{1,4,5}.json`) — the stuck state is:
```
[indoor-bsp] cell=0xA9B40175 lpos=(8.523,-2.251,-0.064) lprev=(8.520,-2.251,-0.064) r=0.480 result=OK
[stepdown-decide] cell=0xA9B40175 insert=OK cpValid=False cpNz=1.000 walkableZ=0.664 accept=False pos=(...,93.456)
[stepsphereup] cell=0xA9B40175 stepUpFlag=False stepDownFlag=False n=(1.00,0,0) stepped=False
[stepsphereup] cell=0xA9B40175 StepUpSlide=Collided
[indoor-bsp] cell=0xA9B40175 r=0.480 result=Collided
```
**Decoded mechanism (this is NOT the memory's "connector Slid" — that was a pre-B1-fix state):**
- **0175 is a 0.364 m-tall threshold SLAB** (dump: 4 solid side walls at local X=7/9, Y=2.85/1.15;
open floor/ceiling portals poly4→0171, poly5→0174; WorldTransform 180°-rot at (161.929,7.503,94)).
- The wedge uses the **r=0.48 body sphere** (Ø0.96 — *bigger than the slab is tall*), centered at world
Z=93.936 (local Z=0.064, i.e. SUNK into the threshold, ~0.5 m below resting-on-floor Z≈94.48).
- That oversized sphere genuinely **full-hits** the X wall (poly 3, X=9; sphere at X=8.523 reaches
X=9.003 — a **3 mm graze**; `moveDot<0` so retail would keep it too) → `BSPQuery.StepSphereUp`
(Path 5, BSPQuery.cs:1849/1380) → **`DoStepUp` fails** (its internal step-down finds no CP on the flat
floor — `[stepdown-decide] cpValid=False`, retail-faithful per Correction 1) → **`StepUpSlide`
`SlideSphereInternal` returns `Collided`** → `FindEnvCollisions` returns Collided → wedge. The 3 mm
graze is hair-trigger → explains the intermittency.
**CULL SIGN = RED HERRING (verified faithful).** Mid-session I believed acdream's `PosHitsSphere` cull
(`if moveDot>=0 return false`) was OPPOSITE retail. **WRONG**`cdb -z uf acclient!CPolygon::pos_hits_sphere`
shows `test ah,5; jp +0x46`. `jp` is a **parity** jump; the cull branch is taken on EVEN parity = `{dot>=0}`.
So retail **keeps the hit when `dot<0`, culls when `dot>=0`** — IDENTICAL to acdream + ACE (`if dist>=0
return false`). Movement convention also matches (both `checkcurr`: acdream BSPQuery.cs:1663, retail
find_collisions). **Do NOT touch the cull.** The Binary Ninja pseudo-C renders `test ah,5; jp` as
`if (p) return 0` which READS like "cull when dot<0" it is not; the parity decode is inverted. LESSON:
verify any cull/branch sign against `cdb -z`, never the BN `if(p)` rendering of a parity jump.
**ENABLER:** `cdb -z "C:\Turbine\Asheron's Call\acclient.exe"` does offline static disasm + `uf` with full
PDB symbols used to build/verify the connector trace AND to catch the cull-sign error. Ghidra `patchmem`
addresses do NOT match the PDB/BN addresses (0x005394f0 `CPolygon::UnPack` in Ghidra); use `cdb -z`.
**THE SHARP REMAINING QUESTION:** at the thin slab, **why does retail's step-up SUCCEED (climb onto the
cottage floor, find_collisions returns OK) where acdream's `DoStepUp` FAILS (no CP StepUpSlide=Collided)?**
Sub-leads: (a) **RULED OUT — r=0.48 vs r=0.30 is two DIFFERENT movers, not a bug.** r=0.48 = the PLAYER
(`PlayerMovementController.cs:1116`, "human player radius from Setup"); r=0.30 = the CAMERA collision
probe (`PhysicsCameraCollisionProbe.cs:18` `ViewerSphereRadius=0.3`, single sphere). The smooth r=0.30
crossings are the camera spring-arm; the wedge is the player. Player radius 0.48 is correct. So the
question is purely the player step-up, NOT the sphere. Open: why is the player SUNK to Z=93.936 (0.5 m
below resting-on-floor Z94.48) at the threshold is that retail-faithful (it's mid-climb from the
cellar) or a position error? (b) does retail even full-hit 0175's wall, or does its sphere clear it
(position)? (c) the
flat-floor step-up success path (Correction 1's open question retail's step_up establishes the floor
CP via some path acdream lacks). NEXT: either build a deterministic harness test from `lip-cells/*.json`
(place the r=0.48 sphere at the captured wedge pos, assert FindCollisions returns OK not Collided RED
GREEN), or one targeted retail trace of `CSphere::step_sphere_up`/`CTransition::step_up` at 0175 (does it
return 1/OK or fall to step_up_slide?). Apparatus committed-in-worktree: `tools/cdb/retail-connector-collide-trace.cdb`,
`lip-cells/0xA9B4017{1,4,5}.json`, `launch-lip-capture.log`, `cdbz-disasm.txt`, `cdbz-poshits.txt`,
`analyze_v1_corr.py`. TEMP `[path5-wall]` probe added to BSPQuery.cs Path 5 (STRIP; was NOT in the stale
--no-build binary, so it didn't fire rebuild to use it).
---
## State both altitudes
- **Milestone:** M1.5 Indoor world feels right.
- **Effort:** P2 of the verbatim spatial-pipeline port
(`docs/superpowers/specs/2026-06-03-verbatim-spatial-pipeline-port-master-plan.md`).
- **Symptom (user words):** "run up the cellar stairs, get blocked at the last step;
sometimes through, sometimes not." Retail is always smooth there.
- **This session's outcome:** NO fix landed. The diagnosis was corrected three times with
evidence; the wedge is now precisely localized; the FINAL decisive question is still open
because the retail trace tooling (`gu` in a bp action) produced an artifact. One clean v2
retail trace pins it. Everything is saved; resume cold.
## The corrected diagnosis (evidence chain — all three prior theories DISPROVEN)
1. **NOT membership / cell-resolver ping-pong** (the prior handoff's claim). A live **retail
cdb trace** (`tools/cdb/retail-lip-trace.cdb` `retail-lip-trace.log`, breaking
`CTransition::step_up`, logging `sphere_path.check_pos.objcell_id`) proved retail's carried
cell ALSO alternates `0xA9B40174/0175/0171` at the lip (181/40/17 over 238 step_ups), yet
retail crosses smoothly. So the carried-cell flip is retail-faithful a "keep the cell
stable" / `ResolveCellId`-stickiness fix would DIVERGE from retail. Also: the production
`[cell-transit] reason=resolver` is the SWEPT `find_cell_list` pick (`RunCheckOtherCellsAndAdvance`
`CellTransit.FindCellSet`), NOT `PhysicsEngine.ResolveCellId` (which is only the cache-null
test fallback). `result.cellId` is STABLE in runs of 100s of ticks in
`acdream-corner-capture.jsonl` (154K recs).
2. **NOT a reverted landing (the M1 guess).** In `acdream-corner-capture.jsonl` the player
reaches the floor (Z=94.0, cpz 0.781.00) and WALKS ON into the cottage (idx 72157244);
the landing commits fine.
3. **IT IS: a step-up coin-flip on the FLAT cottage floor.** The `[stepdown-decide]` probe
(in `Transition.DoStepDown`) shows the trigger unambiguously:
- `accept=True (1845×): insert=OK cpValid=True` on the RAMP (cpNz=0.781, Z93.3, walkInterp0)
- `accept=False ( 849×): insert=OK cpValid=False cpNz=1.000 walkInterp=1.000` on the FLAT floor (Z=94.0)
The step-up's acceptance check (`insert==OK && ContactPlaneValid && cpNz>=walkableZ`,
`TransitionTypes.cs:3147`) rejects ONLY when `cpValid=False`, which happens ONLY on the FLAT
floor: the sphere is already settled `adjust_sphere_to_plane` is a no-op `FindWalkableInternal`
records no poly (its gate is `walkable && adjusted`, `BSPQuery.cs:736`) `StepSphereDown`
(`BSPQuery.cs:1216`) sets no contact plane reject. On the RAMP the sphere is always sliding
(adjusted=true) so the CP is set accept. **Ramp = adjusting = works; flat floor = settled =
no CP = fails.** The `[stepsphereup]` probe corroborates: lip-riser step-up (cell 0171,
n=(0,-1,0)) = 443 success / 445 fail; connector +X corner wall (cell 0175, n=(1,0,0)) = 74 fail
recursive `StepSphereUp` `StepUpSlide` = 401 Slid / 203 Collided overall.
## Why the OBVIOUS fix is WRONG (do not ship it)
"Just set the contact plane whenever a walkable poly is found, even without adjustment" this
DIVERGES from retail. Verified against the decomp:
- `BSPLEAF::find_walkable` (`acclient_2013_pseudo_c.txt:326793`) gates BOTH the poly AND the
changed-flag on `walkable_hits_sphere && adjust_sphere_to_plane` IDENTICAL to acdream's
`FindWalkableInternal`.
- `CPolygon::adjust_sphere_to_plane` (`:322032`) updates `walk_interp` and returns 1 only when
`new_interp = (1-t)*walk_interp < walk_interp`, i.e. `t>0` (the sphere must move toward the
plane). For a SETTLED sphere (`t≈0`) retail ALSO returns 0 records no poly sets no
step-down CP.
So acdream's `StepSphereDown` + `AdjustSphereToPlane` are FAITHFUL. Retail must establish the
flat-floor contact plane through a DIFFERENT path during the climb that path is what's still
unknown.
## THE DECISIVE OPEN QUESTION + the v2 trace protocol
**Does retail's `BSPTREE::step_sphere_down` SET the contact plane (ret 3) or NOT (ret 1) on the
flat floor cell (0xA9B40171)?**
- v1 trace FAILED: `gu` inside a cdb bp action ("commands skipped target execution inside an
event handler") corrupted eax perfect `1,3,1,3` alternation artifact (run-length=1; the
4216/1133 histogram is meaningless). **NEVER use `gu` in a cdb bp action.**
- v2 trace READY: `tools/cdb/retail-flatfloor-trace.cdb` stashes the cell in `$t3` at entry
via `@@c++`, counts at the two RETURN addresses (no `gu`). **STEP 0: verify the +0x218 (ret 3)
/ +0x227 (ret 1) offsets against the `u acclient!BSPTREE::step_sphere_down` disassembly the
script logs, fix if needed, re-attach, THEN have the user wedge ~10s.**
- Interpretation: if floor cell 0171 is mostly **NO-CP** retail establishes the floor CP via a
DIFFERENT path next trace breaks `COLLISIONINFO::set_contact_plane` and logs the CALLER
(`poi(@esp)`) for normal≈(0,0,1) to find that path, then port it. If mostly **SET-CP** the
divergence is inside acdream's `StepSphereDown`/`AdjustSphereToPlane` after all (re-read the
`walk_interp`/`t` math vs ACE).
## Leading hypothesis (UNCONFIRMED, pending v2)
Retail's step-up ALSO "fails" on the settled flat floor (step_sphere_down no-CP) but **recovers
via `step_up_slide` smoothly**, where acdream wedges so the divergence may be in the SLIDE
RECOVERY (`SpherePath.StepUpSlide` `Transition.SlideSphereInternal`, the B1/`slide_sphere`
area, commits `abbd761`/`0935a31`) and/or the connector-cell-0175 `StepSphereUp` interference,
NOT the contact plane itself. The B1/slide fixes are correct FOR THE DOOR; re-investigation is
warranted FOR THE CELLAR recovery only.
## Retail decomp anchors (verified this session)
`CTransition::step_up` pc:273099 (clears CP @273103, calls step_down) · `CTransition::step_down`
pc:272946 (the `if (step_up==0)` lower-gate @272954; `transitional_insert(5)`; accept iff
`!cond:0 && contact_plane_valid` @272968) · `BSPTREE::step_sphere_down` pc:323665 (sets
`contact_plane_valid=1` UNCONDITIONALLY when a poly is found @323711; return 3) ·
`BSPLEAF::find_walkable` pc:326793 · `CPolygon::adjust_sphere_to_plane` pc:322032 ·
`CTransition::transitional_insert` pc:273137 (neg_poly_hit slide_sphere @273350) ·
`CTransition::validate_transition` pc:272547 · `CTransition::check_other_cells` pc:272717.
## acdream code map (where the fix will likely go)
`BSPQuery.StepSphereDown` (:1216) · `FindWalkableInternal` gate (:736) · `AdjustSphereToPlane`
(:351) · `FindCollisions` StepDown dispatch (:1753) · `StepSphereUp` (:1372) · `StepUpSlide`
(TransitionTypes.cs:472) / `SlideSphereInternal` · `DoStepUp` (:3269) / `DoStepDown` (:3089) ·
step-up acceptance (:3147) · neg_poly dispatch gated `!StepDown && !StepUp` (:1040) ·
`CheckOtherCells` (:1632) · `RunCheckOtherCellsAndAdvance` (:2158).
## Apparatus inventory
**TEMP probes (UNCOMMITTED in worktree, gated on `ACDREAM_PROBE_INDOOR_BSP`, marked STRIP):**
`BSPQuery.NegPolyHitDispatch` `[neg-poly]`; `BSPQuery.StepSphereUp` `[stepsphereup]`;
`Transition.CheckOtherCells` `cn`/`sn`/`negHit` added to `[other-cells]`; `Transition.DoStepDown`
`[stepdown-decide]`.
**Existing env probes:** `ACDREAM_PROBE_INDOOR_BSP=1` (→ `[indoor-bsp]`+`[other-cells]`+the 4 TEMP),
`ACDREAM_DUMP_STEPUP=1` (→ `stepup:`), `ACDREAM_PROBE_CELL=1` (→ `[cell-transit]`),
`ACDREAM_PROBE_STEP_WALK=1` (→ `[step-walk]`, very high volume), `ACDREAM_CAPTURE_RESOLVE=<path>`.
**cdb scripts:** `tools/cdb/retail-lip-trace.cdb` (carried cell DONE), `tools/cdb/retail-flatfloor-trace.cdb`
(v2, READY). Binary `C:\Turbine\Asheron's Call\acclient.exe` MATCHES `refs/acclient.pdb`.
**Logs (worktree root, UTF-16/big — do NOT commit):** `acdream-corner-capture.jsonl` (321MB),
`launch-corner-{innerflow,slidepoly,negpoly,ssu,decide}.log`, `retail-lip-trace.log`,
`retail-flatfloor-trace.log` (artifact), `corner-cells-audit.txt`. **Analyzers:** `analyze_corner.py`.
## DO NOT
- Re-diagnose as membership / add `ResolveCellId` stickiness (RULED OUT by retail cdb).
- Ship "set the step-down CP without adjustment" (DIVERGES from retail verified vs decomp).
- Use `gu` inside a cdb bp action (corrupts eax v1 trace artifact).
- Re-investigate B1/`slide_sphere` AS THE DOOR FIX (correct); but the cellar SLIDE RECOVERY is a
legitimate new suspect.
- Flip `Apparatus_Grounded_50cmOffCenter` to `Assert.True` (synthetic-floor artifact).
- Guess the fix the divergence is genuinely subtle (`walk_interp`/slide-recovery), pin it first.
## Test baseline
Core 1310 pass / 4 fail / 1 skip (the 4: `Apparatus_Grounded_50cmOffCenter` [synthetic-floor],
2× `DoorBugTrajectoryReplay LiveCompare_*` [captured-buggy-live], `BSPStepUpTests.D4` [airborne
Path 6, separate]); App 177 green. Branch HEAD `664101f` + this session's UNCOMMITTED probes/docs.
## FRESH-SESSION KICKOFF PROMPT (copy-paste)
```
Continue the P2 cellar-lip wedge fix 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. Use superpowers:systematic-debugging.
READ FIRST (in order):
1. docs/research/2026-06-04-p2-cellar-lip-flatfloor-cp-handoff.md (THIS handoff — canonical).
2. memory/project_p2_door_stepup_findings.md (full chain: RE-DIAGNOSIS 2 + SLIDE LOCALIZED +
FAILING CONDITION PINNED + RETAIL trace ATTEMPT #1 entries).
STATE: M1.5. The cellar "blocked at the last step, sometimes through" wedge is RE-DIAGNOSED with
live retail cdb evidence: NOT membership (retail's carried cell flips the same way + is smooth),
NOT a reverted landing. It IS a step-up coin-flip on the FLAT cottage floor — the step-up's
internal step-down sets NO contact plane on the settled flat floor (cpValid=False, walkInterp=1.0)
so the acceptance check rejects, while it works on the ramp slope. acdream's StepSphereDown +
AdjustSphereToPlane are FAITHFUL to retail (verified vs find_walkable pc:326793 + adjust_sphere_to_plane
pc:322032), so the obvious "set the CP anyway" fix is WRONG — retail establishes the flat-floor CP
via a DIFFERENT path that is still unknown.
THE JOB (evidence-first; do NOT guess):
1. Run the READY v2 retail trace tools/cdb/retail-flatfloor-trace.cdb (user relaunches the retail
client + walks to the cellar lip; STEP 0 = verify the +0x218/+0x227 return offsets against the
`u` disassembly the script logs BEFORE driving; NO `gu` in bp actions). Answer: does retail's
step_sphere_down set the CP (ret 3) or not (ret 1) at floor cell 0xA9B40171?
2. If mostly NO-CP → trace COLLISIONINFO::set_contact_plane callers (poi(@esp)) for normal≈(0,0,1)
to find retail's flat-floor CP path; port it. If mostly SET-CP → the divergence is in acdream's
StepSphereDown/AdjustSphereToPlane walk_interp/t math vs ACE. Leading hypothesis: retail's
step-up also "fails" on the flat floor but RECOVERS via step_up_slide smoothly where acdream
wedges → the divergence may be the SLIDE RECOVERY (StepUpSlide/SlideSphereInternal) +
connector-0175 StepSphereUp interference, NOT the CP.
3. RED→GREEN deterministic test + STRIP the 4 TEMP probes once the fix lands. USER VISUAL GATE:
cellar ascent clean (no last-step wedge); inn door still BLOCKS; generic step-up climbs.
DO NOT: re-diagnose as membership / add ResolveCellId stickiness; ship "set the step-down CP
without adjust" (diverges from retail); use `gu` in a cdb bp action; guess.
TEST BASELINE: Core 1310 pass / 4 fail / 1 skip (documented); App 177 green. Branch HEAD 664101f +
UNCOMMITTED TEMP probes (BSPQuery.NegPolyHitDispatch [neg-poly], BSPQuery.StepSphereUp
[stepsphereup], CheckOtherCells cn/sn/negHit, DoStepDown [stepdown-decide]) gated on
ACDREAM_PROBE_INDOOR_BSP.
```