From 75e3b764459d012e296319ac56e981c9198b8353 Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 8 Jul 2026 08:42:36 +0200 Subject: [PATCH] =?UTF-8?q?docs(#185):=20implementation=20plan=20=E2=80=94?= =?UTF-8?q?=20red=20replay=20->=20pin=20->=20retention=20fix=20->=20regres?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../2026-07-08-185-outdoor-stairs-fix.md | 223 ++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100644 docs/superpowers/plans/2026-07-08-185-outdoor-stairs-fix.md diff --git a/docs/superpowers/plans/2026-07-08-185-outdoor-stairs-fix.md b/docs/superpowers/plans/2026-07-08-185-outdoor-stairs-fix.md new file mode 100644 index 00000000..c7f6f09b --- /dev/null +++ b/docs/superpowers/plans/2026-07-08-185-outdoor-stairs-fix.md @@ -0,0 +1,223 @@ +# #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.cs` and + `CellarUpTrajectoryReplayTests.cs` end-to-end to learn exactly how they (a) build a `PhysicsEngine`, + (b) register a building object's collision shape (`ShadowShapeBuilder`/`RegisterMultiPart` from a + Setup/GfxObj + world transform), and (c) drive `ResolveWithTransition`. Read + `GfxObjDumpRoundTripTests.cs` for how a gfxobj's `ResolvedPolygons` (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_lb` translation × 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 (`0xF68221` parts 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 + from `0xF68221` part 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 the + `ResolveWithTransition` args from the capture: `sphereRadius 0.48`, `sphereHeight 1.835`, + `stepUpHeight 0.6`, `stepDownHeight 1.5`, `isOnGround true`, `moverFlags IsPlayer|EdgeSlide`. + +```csharp +// 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 n` + Expected: **FAIL** — `pos.Y` pins at ~77.9 and/or `Sliding` flag 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 exact + `bodyBefore` from `185-recapture.jsonl` tick 4277: contact plane, walkable verts, and the prior-frame + `slidingNormal`) before proceeding; do not weaken the assertion. + +- [ ] **Step 4: Commit the red test.** +```bash +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 " +``` + +--- + +## 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` (env `ACDREAM_PROBE_CONTACT_PLANE`) + + `ProbeStepWalkEnabled` + `ProbeIndoorBspEnabled`, capturing `Console.Out` (exactly as + `Issue137CorridorSeamReplayTests.SeamShake_WestBoundary_SnapshotExact_Advances` does, lines 207–236). +- Read: `src/AcDream.Core/Physics/TransitionTypes.cs` (TransitionalInsert loop `~1030–1110` + Slid-clears at 1047/1073/1102; the neg-poly/step-down dispatch `~1339`; `ValidateTransition` LKCP + 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 at `PhysicsEngine.cs:1004` never 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_collisions` Contact branch + pc 323838+, or `set_contact_plane`/`get_object_info` pc 272547 for the retain path) and confirm what + retail does that keeps `contact_plane_valid` here. 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` (or + `PhysicsEngine.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.ContactPlane` from the coplanar tread the sphere rests on (retail's forward + landing keeps `contact_plane_valid`, pc 273244) — e.g. the walkable-landing/`find_walkable` on 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's `get_object_info` retain 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.md` + row **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 n` + Expected: **PASS** — the run climbs past Y=78.10 with no persisted sliding normal. +- [ ] **Step 3: Commit the fix + green test.** +```bash +git add -A +git commit -m "fix(#185): keep the mover grounded on a continuous walkable ramp seam + +. 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 " +``` + +--- + +## 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 n` + Expected: PASS (`Issue137CorridorSeamReplayTests`, `Issue137SlidingNormalLifecycleTests`, + `Issue137CorridorSeamInspectionTests`). Also run `CellarUpTrajectoryReplayTests`, `BSPStepUpTests`, + `SphereCollisionFamilyTests` — the step-up/step-down/slide neighbors. +- [ ] **Step 2: Full build + test.** + Run: `dotnet build src/AcDream.App/AcDream.App.csproj -c Debug` then + `dotnet 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.** +```bash +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 " +``` + +--- + +## 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 `+Acdream` up 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.