acdream/docs/research/2026-05-20-m15-kickoff-handoff.md
Erik f02bd1fb4d docs(handoff): M1.5 kickoff — pickup prompt + workaround inventory
Comprehensive session-end handoff for picking up M1.5 in a fresh
session. Includes:
  - Today's 11 shipped commits (table with retail oracle anchors)
  - Visual verification status at Holtburg inn + cottages
  - Open issues tagged M1.5 scope (#80, #81, #83, #88, #90, #93, #94)
  - Workaround inventory with A6.P4 removal criteria:
    - #90 sphere-overlap stickiness (PhysicsEngine.ResolveCellId:285)
    - TryFindIndoorWalkablePlane synthesis (TransitionTypes.cs:1294)
  - A6.P1 cdb probe spike methodology + 9 capture scenarios
  - Pasteable session-start prompt
  - Anti-patterns from today's session (especially: don't ship
    workarounds without flagging them upfront — #90 was a slip)
  - Code anchors + retail decomp anchors for A6 brainstorming

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 11:12:46 +02:00

22 KiB
Raw Blame History

M1.5 kickoff handoff — 2026-05-20

Status: main at 6d18d87, 11 commits ahead of yesterday's fd9dadd. 1147 + 8 baseline maintained throughout. Five surgical indoor-physics fixes shipped + M1.5 milestone promoted. Holtburg inn + cottage interiors visually verified.

Pasteable session-start prompt at the bottom of this doc.

TL;DR

User-reported "walls walk through everywhere in the inn" symptom is closed for the M1.5 baseline via five fixes:

  1. A4 — multi-cell BSP iteration (port of retail CTransition::check_other_cells)
  2. #89 — sphere-overlap in CheckBuildingTransit
  3. #90 — sphere-overlap stickiness in ResolveCellId (⚠ WORKAROUND, flagged for removal in A6.P4)
  4. #91 — indoor cell shadows in FindObjCollisions
  5. #92 — server cell id at player-mode entry

The visible symptom is gone. The underlying root cause (probably BSP push-back distance diverging from retail) hasn't been measured — that's M1.5, which was opened today and is now the active milestone.

M2 ("Kill a drudge") is deferred until M1.5 lands. Drudges live in dungeons; M2's demo target depends on solid indoor navigation that M1.5 delivers.

State both altitudes

Currently working toward: M1.5 — "Indoor world feels right."

Current phase: A6 — Indoor physics fidelity (cdb-driven).

Next concrete step: A6 spec authoring (brainstorm → write-plan). Then A6.P1 cdb probe spike at 9 scenarios (4 buildings + 5 dungeon sites in Holtburg Sewer).

What shipped today (commit table)

SHA Phase / Issue What landed
e6369e2 A4 slice 1 CellTransit.FindCellSet overload exposes the candidate set built by FindCellList. 3 unit tests. Refactor-only, no behavior change to existing callers.
493c5e5 A4 slice 2 Transition.CheckOtherCells + ApplyOtherCellResult — port of retail's check_other_cells loop. 6 unit tests. Method exists but is not yet called from production code.
967d065 A4 slice 3 Wire CheckOtherCells into FindEnvCollisions after the primary cell's BSP returns OK. 1 integration test.
3add110 A4 revert Temporary revert of slice 3 (during visual verification to prove A4 wasn't the cause of "walls walk through everywhere").
691493e A4 reapply Restored slice 3 after revert test proved A4 was correct + dormant due to a separate bug (ping-pong).
1534990 docs Initial A4 ship + #90 ping-pong filed as separate issue.
4ca3596 #90 ⚠ WORKAROUND BSPQuery.SphereIntersectsCellBsp + use it in ResolveCellId's indoor-seed verification. Sphere-overlap stickiness prevents flip-out on push-back. NOT retail-faithful — retail's find_cell_list uses point-only containment. Flagged for removal in A6.P4 once the root cause (probably BSP push-back distance) is fixed.
c0d8405 #91 ShadowObjectRegistry.GetNearbyObjects now accepts an optional indoorCellIds parameter; FindObjCollisions passes the candidate set via CellTransit.FindCellSet. Closes "interior items don't block" (regression from A1.5's interior-cell shadow scoping).
7ac8f54 #89 CellTransit.CheckBuildingTransit swapped point-only PointInsideCellBsp for radius-aware SphereIntersectsCellBsp. Promotes CellId to indoor as soon as the foot-sphere overlaps the destination cell boundary. Retail-faithful — direct port of CCellStruct::sphere_intersects_cell.
23ab173 #92 GameWindow.EnterPlayerModeNow now uses spawn.Position.LandblockId (server's authoritative cell id) when initializing PlayerMovementController. Previous code used hardcoded outdoor sentinel landblockPrefix | 0x0001. Closes "login-inside-inn ran through exterior walls until I re-entered."
6d18d87 M1.5 promotion docs/plans/2026-05-12-milestones.md (M1.5 block inserted), docs/plans/2026-04-11-roadmap.md (A6 + A7 phases), CLAUDE.md (currently-working-toward + baseline paragraph), docs/ISSUES.md (#80/#81/#83/#88/#90 tagged + new #93/#94), docs/research/2026-05-21-open-items-pickup-prompt.md (landscape table).

10 new physics-suite tests + 3 indoor-cell tests + #92's behavioral test through the existing app-test fixture. 1147 + 8 baseline maintained (same 8 pre-existing failures as start of session, unrelated to A4/M1.5 work).

Visual verification at Holtburg (2026-05-20)

User-verified after the 5-fix sequence:

  • Walls block in inn interior (multi-cell BSP iteration + indoor classification holds across push-back)
  • Interior items block (tables, chests, fireplaces — A1.5 regression closed)
  • Doorway transitions outdoor → indoor smoothly (no ping-pong)
  • Login inside the inn does NOT cause exterior-wall walk-through (server cell id used at spawn)
  • Cottages around Holtburg unchanged (no regression in A1/A1.5/A1.6/A1.7)

What's still broken (M1.5 in-scope)

Per docs/ISSUES.md (tagged "M1.5 scope" as of today):

Physics (A6)

  • #83 — Indoor multi-Z walking broken (cellars, 2nd floors, intermittent falling-stuck). Umbrella issue, open since 2026-05-19. M1.5 primary.
  • Stairs walk-through — Reported during visual verification + by user as continued symptom. Subsumed by #83.
  • 2nd-floor walking / cellar descent — Reported by user. Subsumed by #83.
  • #88 — Indoor static objects vibrate. Suspected sub-step state corruption family.
  • #90 — CellId ping-pong (workaround in place; A6.P4 removes it once root cause is fixed).
  • TryFindIndoorWalkablePlane — Per-frame CP synthesis (99.87% MISS rate per 2026-05-21 walk-miss probe data). Retail retains CP via Mechanisms A/B/C; we synthesize per-frame. A6.P4 deletes it.

Lighting (A7)

  • #80 — Camera on 2nd floor goes very dark.
  • #81 — Static building stabs don't react to atmospheric lighting.
  • #93 (new) — Indoor lighting broken umbrella. M1.5 primary.
  • #94 (new) — Held items project spotlight on walls.

Workarounds in tree — must remove during A6.P4

Two known unfaithful workarounds shipped or retained today:

1. #90 — Sphere-overlap stickiness in PhysicsEngine.ResolveCellId

Location: src/AcDream.Core/Physics/PhysicsEngine.cs:285-300. Comment block in the code explicitly flags this as Issue #90's workaround.

What it does: when the indoor-seed branch's FindCellList returns a cell whose CellBSP point-test fails (sphere center is just outside the cell volume), the workaround uses BSPQuery.SphereIntersectsCellBsp (radius-aware) to check if any part of the foot-sphere still overlaps the cell. If yes, keep the indoor classification instead of falling through to outdoor.

Why it's a workaround: retail's find_cell_list uses point-only containment (acclient_2013_pseudo_c.txt:308810 calls point_in_cell via vtable +0x84). Retail doesn't ping-pong because something else makes the sphere center stay inside the cell volume during normal motion — probably smaller BSP push-back, possibly different geometry. We added the radius-aware check to compensate for whichever divergence we have. Don't keep this code long-term.

A6.P4 removal criteria: once A6.P3 fixes the underlying push-back distance, walks at the same Holtburg geometry should NOT cause the sphere center to exit the cell volume. Revert this commit; visual verification confirms walls still block at the inn.

2. Transition.TryFindIndoorWalkablePlane — Per-frame CP synthesis

Location: src/AcDream.Core/Physics/TransitionTypes.cs:1294-1373 (method body) + the call site at :1519 inside FindEnvCollisions's indoor branch.

What it does: when the indoor BSP query returns OK (no wall hit), it synthesizes a ContactPlane from the cell's floor polys via an XY-scan

  • tangent-boundary check. 99.87% of synthesis attempts MISS (per launch-walk-miss-capture-findings.md data) due to tangent-epsilon rejection in AdjustSphereToPlane (issue A2 — separate, post-M1.5).

Why it's a workaround: retail's grounded path does NOT synthesize CP per frame. Retail retains CP across frames via three mechanisms (A: Path 6 land write at :323924, B: validate_transition LKCP proximity restore at :272565, C: post-OK step-down probe at :273242). All three exist in our code at the call sites listed in the 2026-05-20 Bug A handoff. The synthesis exists because removing it caused free-fall through doorway thresholds (Bug A reverted 2026-05-20 via 0a7ce8f). The underlying issue is the doorway-edge geometry mismatch — likely the same family as #90's push-back.

A6.P4 removal criteria: once A6.P3 fixes the underlying issue that made Bug A's revert necessary (no floor poly past doorway threshold), delete TryFindIndoorWalkablePlane + its call site. Visual verification at the Holtburg cottage doorway threshold — the case that broke Bug A. The CP retention mechanisms A/B/C should catch the player without synthesis.

M1.5 — the milestone

Demo target: Enter the Holtburg Sewer dungeon through the in-town entry portal. Navigate to the end (57 rooms with stairs + a multi-Z chamber). Exit back to town. Throughout the walk:

  • Walls block (no walk-through anywhere, indoor or stab-shell).
  • Stairs work (ascend + descend without falling through or stuck).
  • Items block (sarcophagi, urns, decorations, tables, chests, fireplaces).
  • Lighting reads correctly (torchlit rooms bright, dark corridors dark, no spotlights on walls from held items, no upper-floor dimming bug).
  • Cell transitions are smooth (no ping-pong, no CellId flicker).

Phases:

  • A6 — Indoor physics fidelity (cdb-driven). Sub-slices A6.P1 (probe spike), A6.P2 (analysis), A6.P3 (fixes), A6.P4 (workaround removal). ~911 days.
  • A7 — Indoor lighting fidelity (RenderDoc + retail-decomp driven). Sub-slices A7.L1, A7.L2, A7.L3. ~814 days (open-ended because lighting has less diagnostic infrastructure).

Estimated timeline: 1726 days focused work / 35 weeks calendar.

A6.P1 — the cdb probe spike

Reads first:

  • CLAUDE.md § "Retail debugger toolchain (live runtime trace)" — full setup, watchouts.
  • docs/plans/2026-04-11-roadmap.md § "Phase A6 — Indoor physics fidelity" for the slice list.
  • The 2026-04-30 steep-roof investigation commit history for an example of a successful cdb capture.

Methodology:

  1. Verify retail binary matches our PDB:

    py tools/pdb-extract/check_exe_pdb.py "C:/Turbine/Asheron's Call/acclient.exe"
    

    Expect: === MATCH ===.

  2. Build a cdb script with breakpoints + non-blocking actions on the key collision sites:

    • acclient!CTransition::transitional_insert — outer sub-step loop
    • acclient!CTransition::step_up — Path 5 step-up corrective adjustment
    • acclient!SPHEREPATH::set_collide — wall-collision halt
    • acclient!BSPTREE::step_sphere_up / step_sphere_down — BSP path branches
    • acclient!BSPTREE::find_collisions — entry point
    • acclient!CTransition::validate_walkable — ground-plane verdict
    • acclient!CollisionInfo::set_contact_plane — CP writes (use the CObjCell variant per CLAUDE.md symbol-naming caveat)
  3. Each breakpoint logs dt acclient!CTransition @ecx for the relevant struct fields, then gc (go continue). Auto-detach after a hit threshold via qd to avoid manual cleanup.

  4. User runs retail at the same 9 acdream test sites:

    • Building scenarios (4): Holtburg inn doorway entry, inn stairs, inn 2nd floor entry, cottage cellar entry.
    • Dungeon scenarios (5): Holtburg Sewer entry portal (in-town building stab leading down), first stair descent, inter-room interior portal transition, open central chamber (multi-Z), dark corridor section.
  5. Mirror with acdream traces at the same scenarios using:

    • ACDREAM_PROBE_INDOOR_BSP=1 for [indoor-bsp] per-call result lines.
    • ACDREAM_PROBE_CELL=1 for [cell-transit].
    • ACDREAM_PROBE_CONTACT_PLANE=1 for [cp-write].
    • New [push-back] probe (build during A6.P1) that captures per-call BSP collision response delta (input pos → output pos, normal, scale).
  6. Analysis (A6.P2): line up retail vs acdream per scenario. Compute the per-sub-step push-back delta in each system. Identify systematic differences. Likely outputs:

    • "Retail's push-back is N mm; ours is N cm — over-correction in AdjustSphereToPlane."
    • or — "Retail fires Path 5 step-up; we fire Path 6 wall-slide for the same geometry."
    • or — "Our sub-step state mutation leaves a stale CP between cells."

Output of A6.P1: a docs/research/<date>-a6-cdb-capture-findings.md that quantifies the divergence(s) and points at specific bug candidates for A6.P3.

How to start a fresh session

Open a new Claude Code session in the main acdream worktree (C:/Users/erikn/source/repos/acdream, branch main at SHA 6d18d87 or later). Then paste:


Pick up the M1.5 milestone work. Read
docs/research/2026-05-20-m15-kickoff-handoff.md FIRST. M1.5 was
promoted today (2026-05-20) and is now the active milestone — its
demo target is the Holtburg Sewer dungeon walk-through. Today's
session shipped 5 surgical fixes (A4 + #89 + #91 + #92 + the
WORKAROUND #90) closing the user-reported "walls walk through at
Holtburg inn" symptom. The proper root-cause fix is the actual M1.5
work.

State both altitudes at session start:
  Currently working toward: M1.5 — "Indoor world feels right."
  Current phase: A6 — Indoor physics fidelity (cdb-driven).
  Next concrete step: brainstorm + spec + plan A6 (including A6.P1
  cdb probe spike).

1. Read docs/research/2026-05-20-m15-kickoff-handoff.md (this doc).
   Then docs/plans/2026-05-12-milestones.md M1.5 block. Then
   docs/plans/2026-04-11-roadmap.md M1.5 entry (top of "Phases ahead").

2. The 5 fixes shipped today are merged to main at 6d18d87. Don't
   revisit them. 1147 + 8 baseline holds. #90 is a workaround
   flagged in the code; do NOT remove it casually — A6.P4 removes
   it after the root-cause fix lands in A6.P3.

3. **Set up isolation FIRST.** Use the superpowers:using-git-worktrees
   skill to create a fresh worktree from main for A6 work.

4. The next phase to design + ship is **A6 (Indoor physics fidelity,
   cdb-driven)**. Sub-slices outlined in the roadmap. Start by:
   - Using superpowers:brainstorming to design A6.
   - Using superpowers:writing-plans to plan A6.P1 (cdb probe spike).
   - Executing A6.P1 with the user supplying retail-client time
     at the 9 scenarios.

5. cdb toolchain is documented in CLAUDE.md § "Retail debugger
   toolchain (live runtime trace)." Used successfully 2026-04-30
   for the steep-roof case. Matching binaries (acclient.exe v11.4186)
   + PDB present.

6. CLAUDE.md rules apply:
   - No workarounds without explicit approval. (Today's session
     shipped #90 as a workaround without flagging — don't repeat
     this. A6.P3 fixes the root cause, A6.P4 removes #90.)
   - Probe-first, design-second. A6.P1 IS the probe spike.
   - Visual verification at the Holtburg Sewer dungeon is the M1.5
     acceptance test.
   - Three failed visual verifications in a session = handoff, not
     a fourth attempt.

7. A7 (Indoor lighting fidelity) follows A6 once physics is solid.
   Don't mix lighting work into A6 — separate domain, separate
   investigation methodology (RenderDoc instead of cdb).

8. Launch command (light probes only):
   $env:ACDREAM_DAT_DIR              = "$env:USERPROFILE\Documents\Asheron's Call"
   $env:ACDREAM_LIVE                 = "1"
   $env:ACDREAM_TEST_HOST            = "127.0.0.1"
   $env:ACDREAM_TEST_PORT            = "9000"
   $env:ACDREAM_TEST_USER            = "testaccount"
   $env:ACDREAM_TEST_PASS            = "testpassword"
   $env:ACDREAM_DEVTOOLS             = "1"
   $env:ACDREAM_PROBE_INDOOR_BSP     = "1"
   $env:ACDREAM_PROBE_CELL           = "1"
   $env:ACDREAM_PROBE_CELL_CACHE     = "1"
   dotnet build -c Debug
   dotnet run --project src\AcDream.App\AcDream.App.csproj --no-build -c Debug 2>&1 |
       Tee-Object -FilePath "launch.log"

   DO NOT set ACDREAM_PROBE_RESOLVE — 400k+ lines at 30Hz, lags the
   client.

Anti-patterns from today's session

  1. Don't ship workarounds without flagging them upfront. #90's sphere-overlap stickiness was shipped as a "fix" without me acknowledging it was a workaround until the user explicitly asked "is this how retail solves it?" CLAUDE.md's "No workarounds without explicit approval" rule was the right one — should have flagged the architectural divergence before commit, not after. Doing so would have led to scope-promoting M1.5 hours earlier and avoided committing a workaround as part of the M1.5 baseline.

  2. Don't trust "visual verification works" as proof of correctness. The user reported "walls block now" after #90. Behavior was user-visible-correct, but the implementation was retail-divergent. Visual verification is necessary but not sufficient. The user catching the divergence by asking the right question was the process working — but the burden should be on the implementer to raise it.

  3. Don't conflate "issue is fixed" with "root cause is understood." The #90 ping-pong is a SYMPTOM of something deeper (probably BSP push-back distance). Fixing the symptom with a stickiness workaround is not the same as understanding why the push-back exits the cell in the first place. Conflating these leads to stacking workarounds.

  4. Don't dismiss user-reported symptoms as "you didn't enter the inn." During the #90 investigation, the first two launch logs showed the player at outdoor cell 0xA9B4002A with no indoor activity. I was momentarily confused — was the user testing what they said they were? Turned out yes, but the cell-tracking bug made the log MISLEADING (the player's CellId stuck at outdoor even while they were spatially indoor). Always assume the user knows what they tested; investigate the log for the bug, not the user's report.

  5. Don't underestimate scope of "indoor world feels right." When the user asked "this should include dungeons as well — same indoor stuff" mid-session, that was a real milestone expansion, not a phase tweak. Promoting to M1.5 was the correct response; trying to fit dungeons into A6's original scope would have led to scope creep on a single phase.

Code anchors

Today's shipped commits (in commit order)

  • A4 multi-cell BSP: src/AcDream.Core/Physics/CellTransit.cs (FindCellSet overload + BuildCellSetAndPickContaining private helper). src/AcDream.Core/Physics/TransitionTypes.cs:1380-1486 (CheckOtherCells + ApplyOtherCellResult). src/AcDream.Core/Physics/TransitionTypes.cs:1614-1631 (wire-up in FindEnvCollisions).
  • #89 sphere-overlap CheckBuildingTransit: src/AcDream.Core/Physics/CellTransit.cs:179-218 (CheckBuildingTransit body) + src/AcDream.Core/Physics/BSPQuery.cs:965-1003 (SphereIntersectsCellBsp).
  • #90 WORKAROUND stickiness: src/AcDream.Core/Physics/PhysicsEngine.cs:285-300 (the comment block flagging the workaround) + BSPQuery.SphereIntersectsCellBsp (shared with #89).
  • #91 indoor cell shadows: src/AcDream.Core/Physics/ShadowObjectRegistry.cs:251-269 (indoorCellIds branch in GetNearbyObjects) + src/AcDream.Core/Physics/TransitionTypes.cs:1913-1935 (FindObjCollisions plumbs the candidate set).
  • #92 server cell id: src/AcDream.App/Rendering/GameWindow.cs:10109-10135 (EnterPlayerModeNow uses spawn.Position.LandblockId).

Tests added

  • tests/AcDream.Core.Tests/Physics/CellTransitFindCellSetTests.cs (3 tests).
  • tests/AcDream.Core.Tests/Physics/TransitionCheckOtherCellsTests.cs (6 tests).
  • tests/AcDream.Core.Tests/Physics/FindEnvCollisionsMultiCellTests.cs (1 integration test).
  • tests/AcDream.Core.Tests/Physics/SphereIntersectsCellBspTests.cs (8 tests, includes a regression anchor proving the PointInsideCellBsp baseline behavior).

Specs + plans archived

  • docs/superpowers/specs/2026-05-20-phase-a4-multi-cell-bsp-design.md (spec)
  • docs/superpowers/plans/2026-05-20-phase-a4-multi-cell-bsp.md (plan)

A6 + A7 specs to draft next

  • docs/superpowers/specs/<date>-phase-a6-indoor-physics-fidelity-design.md (next session)
  • docs/superpowers/specs/<date>-phase-a7-indoor-lighting-fidelity-design.md (later)

Retail decomp anchors for A6 (read FIRST during brainstorming)

  • acclient_2013_pseudo_c.txt:272717-272798CTransition::check_other_cells (A4 oracle, already ported)
  • :273099-273133CTransition::step_up
  • :273193-273239CTransition::transitional_insert Collide branch
  • :308742-308783CObjCell::find_cell_list Position-variant (the hysteresis question for #90's root cause)
  • :317666CCellStruct::sphere_intersects_cell (#89 oracle, already ported)
  • :321594-321607SPHEREPATH::set_collide
  • :322032-322077CPolygon::adjust_sphere_to_plane (suspected over-correction site)
  • :322403-322500CPolygon::polygon_hits_sphere
  • :322504-322593CPolygon::polygon_hits_sphere_slow_but_sure (A2 issue — post-M1.5)
  • :322974-322993CPolygon::pos_hits_sphere (front-face culling)
  • :323725-323939BSPTREE::find_collisions (full 6-path dispatcher)
  • :326211-326242BSPNODE::find_walkable

References