Inline-execution plan (frozen collision internals): dat-backed seam replay as the red pin, empirical pinning of the forward-move contact-plane loss, the localized retail-faithful grounding-retention fix (candidate tree keyed to the pin), #137 regression net, register/digest/ISSUES bookkeeping, live gate. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
14 KiB
#185 Outdoor-Stairs Phantom Fix — Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: this plan is intended for inline execution by the lead engineer with full context (superpowers:executing-plans), NOT subagent dispatch — it edits frozen collision internals and pins the fix empirically (CLAUDE.md: "Do not integrate via subagent unless the subagent has the full context"). Steps use checkbox (
- [ ]) syntax for tracking.
Goal: the local player walks up the full house-on-stilts outdoor staircase with no jam and no jump.
Architecture: restore retail's invariant that a grounded mover on a continuous walkable surface
keeps contact_plane_valid on the forward move (transitional_insert short-circuit, pc 273244), so
it never falls into the step-down recovery that cannot reach the coplanar (at-level) stair
continuation and fabricates a horizontal (0,1,0) sliding normal (PrecipiceSlide, the #137/TS-4
family). The exact retention-loss line is pinned by a dat-backed replay test, then fixed faithfully.
Tech stack: C# .NET 10, xUnit, DatCollection dat reader, AcDream.Core.Physics
(PhysicsEngine.ResolveWithTransition, Transition, SpherePath, ShadowObjectRegistry).
Design spec: docs/superpowers/specs/2026-07-08-185-outdoor-stairs-fix-design.md (read first).
Fixtures already captured (this session, in the scratchpad):
185-recapture.jsonl (jam pinned at (131.71, 77.914, 61.485), slidingNormal (0,1,0),
collisionNormal (0,0.78,0.62)); gfxdump/0x01000AC5.gfxobj.json (step-box tread = poly id 4, local
plane (-0.625,0,0.781,-0.312)); [resolve-bldg] per-object world origins, e.g. 0xF6822103 → entOrigin_lb (132.0,77.2,60.4), tread world verts (132.75,77.495,61.015)…. Copy the two gfxobj
dumps into tests/AcDream.Core.Tests/Physics/fixtures/ in Task 1 so the test is self-contained.
Reference tests to mirror (read before writing Task 1):
tests/AcDream.Core.Tests/Physics/Issue137CorridorSeamReplayTests.cs (replay + probe-capture shape),
DoorBugTrajectoryReplayTests.cs / CellarUpTrajectoryReplayTests.cs (registering a building/shadow
object and replaying a trajectory — the outdoor-static harness #185 needs), GfxObjDumpRoundTripTests.cs
(loading a gfxobj + its physics polys), ShadowObjectRegistryMultiPartTests.cs (RegisterMultiPart API).
Task 1: Red replay test — reproduce the wedge over the real stair objects
Files:
-
Create:
tests/AcDream.Core.Tests/Physics/Issue185OutdoorStairsSeamReplayTests.cs -
Create (fixtures): copy
…/scratchpad/gfxdump/0x01000AC5.gfxobj.json(+0x01000ACA) →tests/AcDream.Core.Tests/Physics/fixtures/ -
Read (harness API):
src/AcDream.Core/Physics/ShadowObjectRegistry.cs:92,152(Register/RegisterMultiPart),PhysicsEngine.ResolveWithTransition, and the two building-replay reference tests above. -
Step 1: Read the reference harness. Read
DoorBugTrajectoryReplayTests.csandCellarUpTrajectoryReplayTests.csend-to-end to learn exactly how they (a) build aPhysicsEngine, (b) register a building object's collision shape (ShadowShapeBuilder/RegisterMultiPartfrom a Setup/GfxObj + world transform), and (c) driveResolveWithTransition. ReadGfxObjDumpRoundTripTests.csfor how a gfxobj'sResolvedPolygons(the physics polys, incl. tread poly id 4) are obtained from the dat. Confirm whether registration takes a Setup id or a GfxObj id; the stairs are landblock statics whose collision is the GfxObj's own BSP/polys (no Setup cylsphere). -
Step 2: Write the harness + the FAILING wedge test. Build a
PhysicsEngine, register the stair step objects at their captured world transforms (each =entOrigin_lbtranslation × the +90°-about-Z rotation that maps local tread normal(-0.625,0,0.781)→ world(0,-0.625,0.781)), covering at least the objects spanning the jam seam (0xF68221parts 1–3 at entOrigin Y 76.2/76.7/77.2 and the next object up whose tread covers Y∈[77.5,78.0]+ — derive its origin as the +0.5 world-Y / +0.4 world-Z step from0xF68221part 3). Seed a grounded body on tread N (contact plane + walkable poly = the tread quad, world verts(132.75,77.495,61.015),(131.25,77.495,61.015),(131.25,76.995,60.615),(132.75,76.995,60.615)). Replay the forward run from(131.72,77.49,61.15)toward(131.71,78.18,61.15)for ~6 held-forward frames (flat target, +Y ≈ 0.2/frame — the movement controller sends flat, physics climbs). Use theResolveWithTransitionargs from the capture:sphereRadius 0.48,sphereHeight 1.835,stepUpHeight 0.6,stepDownHeight 1.5,isOnGround true,moverFlags IsPlayer|EdgeSlide.
// The wedge assertion (RED before the fix): the run must climb PAST the seam, not pin at Y≈77.9.
Assert.True(pos.Y > 78.10f,
$"Player must climb past the tread seam (reached Y={pos.Y:F3}); pinned at ~77.9 with " +
$"slidingNormal=(0,1,0) = the #185 fabricated-precipice wedge.");
Assert.False(body.TransientState.HasFlag(TransientStateFlags.Sliding),
"A continuous walkable ramp seam must not persist a horizontal sliding normal (#137 family).");
-
Step 3: Run it — verify it FAILS (reproduces the wedge). Run:
dotnet test tests/AcDream.Core.Tests --filter "FullyQualifiedName~Issue185OutdoorStairsSeamReplay" -v nExpected: FAIL —pos.Ypins at ~77.9 and/orSlidingflag set (the wedge reproduced offline). If it does NOT reproduce (climbs clean offline), the live trigger involves state beyond the replay's reach (as the #137 corridor replay initially did) — STOP and add the missing seed (the exactbodyBeforefrom185-recapture.jsonltick 4277: contact plane, walkable verts, and the prior-frameslidingNormal) before proceeding; do not weaken the assertion. -
Step 4: Commit the red test.
git add tests/AcDream.Core.Tests/Physics/Issue185OutdoorStairsSeamReplayTests.cs \
tests/AcDream.Core.Tests/Physics/fixtures/0x01000AC5.gfxobj.json \
tests/AcDream.Core.Tests/Physics/fixtures/0x01000ACA.gfxobj.json
git commit -m "test(#185): red — dat-backed outdoor-stairs seam replay reproduces the wedge
Registers the real stair step-boxes and replays the captured forward run; pins the
jam (no advance past Y~77.9, persisted (0,1,0) sliding normal). RED before the fix.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>"
Task 2: Pin the exact retention-loss site
Files:
-
Modify (temporary probe only, reverted at end): the replay test — wrap the seam-crossing frame with
PhysicsDiagnostics.ProbeContactPlaneEnabled = true(envACDREAM_PROBE_CONTACT_PLANE) +ProbeStepWalkEnabled+ProbeIndoorBspEnabled, capturingConsole.Out(exactly asIssue137CorridorSeamReplayTests.SeamShake_WestBoundary_SnapshotExact_Advancesdoes, lines 207–236). -
Read:
src/AcDream.Core/Physics/TransitionTypes.cs(TransitionalInsert loop~1030–1110Slid-clears at 1047/1073/1102; the neg-poly/step-down dispatch~1339;ValidateTransitionLKCP retention~4645–4735),src/AcDream.Core/Physics/PhysicsEngine.cs(seed~1004, writeback~1131). -
Step 1: Capture the seam-crossing frame's contact-plane transitions. Run the red test with the probes on; in the emitted trace, find the frame that reaches the step-down dispatch with
ci.ContactPlaneValid == false, and identify which upstream event set it false: (a) an env/building/object channel returned Slid (clears cp at 1047/1073/1102), or (b) the seed atPhysicsEngine.cs:1004never re-validated and the forward main-sweep set no fresh contact plane, or (c)ValidateTransition's LKCP proximity guard (~4698,radius+ε > |angle|) DROPPED the plane because the floated sphere is >radius from tread N's plane. Record the concrete site (file:line + the branch) in a scratch note; this selects the Task-3 candidate. -
Step 2: Confirm the retail-faithful expectation for that site. Grep the named-retail decomp for the corresponding retail function (the Slid channel →
BSPTREE::find_collisionsContact branch pc 323838+, orset_contact_plane/get_object_infopc 272547 for the retain path) and confirm what retail does that keepscontact_plane_validhere. Do NOT invent behavior — cite the pc line. This is the gate: the fix must make acdream match that retail behavior, not add a guard.
Task 3: Apply the retail-faithful grounding-retention fix
Files:
-
Modify: the pinned site from Task 2 in
src/AcDream.Core/Physics/TransitionTypes.cs(orPhysicsEngine.cs). Exactly ONE of the candidates below, chosen by Task 2's pin + Task 2's retail cite. -
Step 1: Apply the fix matching the pinned site. Candidate edits (pick the one Task 2 selected):
-
Candidate A (pin = spurious Slid on the coplanar-graze): the grounded foot grazing the coplanar continuation tread is being routed to a Slid (clearing cp) where retail records a walkable contact/step (keeps cp). Fix the specific collision-response branch so a grounded mover grazing a walkable (normal.Z ≥ FloorZ) coplanar poly it is resting on retains/sets the contact plane instead of clearing it — mirroring the retail Contact-branch path Task 2 cited. Do not blanket-skip Slid; scope to the walkable-coplanar-continuation case.
-
Candidate B (pin = forward move sets no fresh contact plane): ensure the grounded forward move re-establishes
ci.ContactPlanefrom the coplanar tread the sphere rests on (retail's forward landing keepscontact_plane_valid, pc 273244) — e.g. the walkable-landing/find_walkableon the forward insert must find the at-level continuation, not only a below-foot surface. -
Candidate C (pin = LKCP proximity guard drops the plane): the floated sphere separates from tread N's plane by >radius so the guard (
~4698) drops it — but the sphere is genuinely resting on the coplanar continuation. Fix so the proximity check is evaluated against the surface actually under the sphere (the coplanar continuation), matching retail'sget_object_inforetain logic (pc 272547), rather than dropping grounding at the seam.
Each candidate is a localized change. If the pinned faithful fix is larger than a localized edit, STOP and return to the user (scope guard, per the spec §3).
-
-
Step 2: Register bookkeeping in the same commit. Amend
docs/architecture/retail-divergence-register.mdrow TS-4 (or add a new row) to record the grounding-retention change and its retail oracle (pc 273244 / the Task-2 cite); retire/narrow as the port dictates.
Task 4: Green the replay + remove the temporary probes
- Step 1: Remove the temporary probe wrapping added in Task 2 (leave the test asserting behavior, no probe side effects).
- Step 2: Run the red test — verify it now PASSES.
Run:
dotnet test tests/AcDream.Core.Tests --filter "FullyQualifiedName~Issue185OutdoorStairsSeamReplay" -v nExpected: PASS — the run climbs past Y=78.10 with no persisted sliding normal. - Step 3: Commit the fix + green test.
git add -A
git commit -m "fix(#185): keep the mover grounded on a continuous walkable ramp seam
<one line naming the pinned site + the retail oracle from Task 2>. The grounded
forward move now retains contact_plane_valid across the coplanar stair seam
(retail transitional_insert pc 273244) instead of dropping into the step-down
recovery that can't reach the at-level continuation and fabricates a horizontal
(0,1,0) sliding normal (PrecipiceSlide, #137/TS-4 family). Register TS-4 amended.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>"
Task 5: Regression — the shared grounded-move path
- Step 1: Run the #137 suites (must stay green / un-skipped).
Run:
dotnet test tests/AcDream.Core.Tests --filter "FullyQualifiedName~Issue137" -v nExpected: PASS (Issue137CorridorSeamReplayTests,Issue137SlidingNormalLifecycleTests,Issue137CorridorSeamInspectionTests). Also runCellarUpTrajectoryReplayTests,BSPStepUpTests,SphereCollisionFamilyTests— the step-up/step-down/slide neighbors. - Step 2: Full build + test.
Run:
dotnet build src/AcDream.App/AcDream.App.csproj -c Debugthendotnet test(Core / App / UI / Net). Expected: all green, 0 warnings. - Step 3: If any regressed, the fix over-reached (not scoped to the walkable-coplanar-continuation case) — narrow it and re-run; do NOT loosen the regression assertions.
Task 6: Bookkeeping (docs + memory)
- Step 1:
docs/ISSUES.md— move #185 to Recently closed with the fix SHA. - Step 2:
claude-memory/project_physics_collision_digest.md— add the #185 banner (root cause + the fix + DO-NOT-RETRY: don't un-zero Z, don't touch step-up budget, fix the grounding provenance). - Step 3: roadmap/milestones — note the M1.5 #137-family stair fix if a phase line applies.
- Step 4: Commit the bookkeeping.
git add docs/ISSUES.md claude-memory/project_physics_collision_digest.md
git commit -m "docs(#185): close — grounding-retention fix for the outdoor-stairs phantom
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>"
Task 7: Live visual gate (USER — acceptance)
- Step 1: Build green, then launch (canonical live command; no pre-close of running clients).
- Step 2: USER walks
+Acdreamup the FULL outdoor staircase — must climb with no jam and no jump. Then regression spot-checks: indoor cellar/corridor stairs, another outdoor building, a ramp, flat ground — no new phantom blocks AND no sliding through walkable ramps/edges. - Step 3: on pass, #185 is closed (bookkeeping already landed in Task 6). On fail, capture with the apparatus (§6 of the spec) and re-pin — do not guess a follow-up.