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

38 KiB
Raw Blame History

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 ontoneg_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 Collidedslide_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 (DoStepUpDoStepDownTransitionalInsert(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 StepSphereDownfind_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≈0SlideSphere'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 (StepSphereUp→StepUpSlide=Collided) while the synthetic repro used NegStepUp=FALSE (neg-poly-dispatch→Collided) — 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_RESOLVElip-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 (DoStepUpDoStepDownTransitionalInsert(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 StepSphereDownFindWalkableInternal. 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,binforce 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_slideslide_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.cdbretail-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) → StepUpSlideSlideSphereInternal returns CollidedFindEnvCollisions 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. WRONGcdb -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 Z≈94.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.cdbretail-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 (RunCheckOtherCellsAndAdvanceCellTransit.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.78→1.00) and WALKS ON into the cottage (idx 7215→7244); 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, Z≈93.3, walkInterp≈0)
    • 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 StepSphereUpStepUpSlide = 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.StepUpSlideTransition.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.CheckOtherCellscn/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.