acdream/docs/research/2026-05-19-cluster-a-shipped-handoff.md
Erik f0900ebe12 docs(phase): Cluster A — partial ship + handoff
Cluster A's investigation pinned #86 (picker) as structural and closed
it (Phase B). #84 and #85 both pinned on missing indoor cell tracking;
Phase D promoted CellId via AABB containment which un-stuck the
spawn-in-building case (closes #84 partially) but proved too tight for
threshold/doorway cells to keep CellId indoor during normal walking.
The proper fix is retail's portal-based cell traversal; filed as a
new ISSUES.md issue (see body) for the follow-up phase. Phase E
diagnostic infrastructure ([cell-cache] + extended [indoor-bsp]) stays
in place as scaffolding for that work.

ISSUES.md: #86 → Recently closed. #84 status updated to PARTIAL with
resolution paragraph. #85 status update note added. New issue #87 filed
for portal-based indoor cell tracking.

Roadmap: Cluster A added to Recently shipped with partial-ship note.
Forward entry added for the portal-traversal follow-up under Phase G.

CLAUDE.md: current-phase paragraph updated to reflect Cluster A partial
ship. Next phase deferred to Claude's choice in a future session.

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

12 KiB

Indoor walking Phase 1 — BSP cluster (Cluster A) — handoff (2026-05-19)

Date: 2026-05-19. Branch: claude/competent-robinson-dec1f4 (commits land here; merge to main handled by controller). Predecessor: Indoor lighting + rendering Phase 2 (fix) — floors now render in Holtburg Inn. Nine pre-existing indoor bugs surfaced the moment floors were visible; this cluster addresses the collision/interaction subset (#84, #85, #86) and adds diagnostic infrastructure for the follow-up portal-traversal phase. Plan: docs/superpowers/plans/2026-05-19-indoor-walking-phase1-bsp-cluster.md.


TL;DR

Cluster A shipped partially. Three of the five planned phases (A, B, D) produced real behavior changes; two (C — obstacle audit — and E — cell-cache diagnostics) are diagnostic/research phases. The cluster's investigation confirmed that the wall-collision failures (#84, #85) all root in one cause: the player's CellId is never promoted to an indoor cell during normal walking, so the indoor-BSP collision branch in TransitionTypes.FindEnvCollisions never fires. Phase D implemented an AABB-containment shortcut that resolves the specific "spawn inside a building and be stuck above the floor" case but proved too tight to keep CellId promoted through threshold/doorway cells during normal outdoor→indoor entry.

#86 (click selection penetrates walls) is fully closed — a clean, self-contained fix in WorldPicker.

#84 is partially closed — the spawn-in-building symptom is gone; the remaining wall-collision symptom during normal walking is tracked under the new #87.

#85 remains open; its root cause is confirmed identical to #84's remaining symptom and is also tracked under #87.

#87 (indoor portal-based cell tracking) is filed and ready for the follow-up phase.


Commits

# SHA Subject Phase
1 18a2e28 docs(plan): implementation plan written Plan doc
2 27d7de1 feat(physics): Cluster A — indoor BSP collision probe Phase A
3 3764867 fix(picker): Cluster A #86 — cell-BSP ray occlusion in WorldPicker Phase B
4 4e308d5 test(picker): Cluster A #86 — screen-rect cell-occlusion tests Phase B follow-up
5 c19d6fb fix(physics): Cluster A #84 + #85 — indoor cell tracking Phase D
6 fda6af7 feat(physics): Cluster A — cell-cache diagnostic Phase E (1st)
7 1f11ba9 feat(diag): Cluster A — extend [cell-cache] with AABB + bsphere + recursive poly count Phase E (2nd)

Build: clean on all commits. Tests: dotnet test shows the same 8 pre-existing failures in AcDream.Core.Tests (MotionInterpreter / BSPStepUp / etc., unchanged across the entire cluster). All targeted test projects green. Phase B follow-up adds screen-rect occlusion tests; Phase D adds RegisterCellStructForTest helper used by caller-side tests.


What shipped

Phase A — [indoor-bsp] probe

New PhysicsDiagnostics.ProbeIndoorBspEnabled toggle (env var ACDREAM_PROBE_INDOOR_BSP + DebugPanel checkbox under ACDREAM_DEVTOOLS=1). When enabled, logs one [indoor-bsp] line each time TransitionTypes.FindEnvCollisions takes the indoor-cell branch — i.e., when CellId is an EnvCell id and the BSP contains physics polys. The probe serves as a presence detector: if [indoor-bsp] never fires during indoor walking, the BSP is not being consulted at all.

Phase B — WorldPicker cell-BSP ray occlusion (closes #86)

New CellBspRayOccluder class (in src/AcDream.App/Rendering/) computes NearestWallT: the smallest ray parameter at which the pick ray intersects any cached EnvCell BSP polygon. Both WorldPicker.Pick overloads now accept an optional cellOccluder callback and filter out any hit candidate whose ray T exceeds NearestWallT. The occluder is wired from GameWindow using the PhysicsDataCache cell structs that Phase D also extends.

Before Phase B: clicking through a wall from the outside selected NPCs/items inside the building — WorldPicker.BuildRay + Pick (Phase B.4b) tested only entity AABBs and scenery BSPs, not EnvCell BSP geometry.

After Phase B: entities behind the nearest wall from the camera's perspective are filtered out of the candidate set. Screen-rect unit tests verify the filter across hit/miss/occlusion scenarios.

Phase D — AABB containment for indoor CellId (partial #84 fix)

PhysicsEngine.ResolveOutdoorCellId is extended with an indoor cell-containment scan. After resolving the outdoor cell, the method checks whether the player's world position falls inside any cached CellPhysics AABB; if so, CellId is promoted to that EnvCell. This enables the FindEnvCollisions indoor-BSP branch.

New PhysicsDataCache.TryFindContainingCell(worldPos) does the AABB scan. New CellPhysics.WorldAabb caches the cell-local AABB in world space on first call (transforms the BSP bounding sphere's local AABB by the cell origin). New RegisterCellStructForTest helper allows unit test callers to populate the cache directly.

Also fixes the L.2e bare-low-byte preservation bug: ResolveOutdoorCellId was silently truncating the player CellId to the low 16 bits; the fix preserves the full 32-bit value.

What this solved: player spawning inside a building (e.g., logging in from a position inside Holtburg cottage) no longer sees walkable=False for hundreds of resolves with world Z=94.000. Phase D promotes CellId to the indoor cell, the floor's BSP polys are found, the player can move.

What this did NOT solve: the [indoor-bsp] probe fires only 6 times during an entire indoor walking session (all mid-jump, when the body happens to be at a height that falls inside a room AABB). During normal walking on the floor, the player's world Z is at the AABB floor level or lower — outside the AABB for threshold/doorway cells that have only a 0.2 m Z range. See Phase E evidence below.

Phase E — Cell-cache diagnostic infrastructure

Two commits add [cell-cache] log output (env var ACDREAM_PROBE_CELL_CACHE, also DebugPanel). For each EnvCell in the physics cache, the probe logs:

[cell-cache] id=0xA9B40143 physicsPolyCount=14 bspTotalLeafPolys=14
  bspUnmatchedIds=0 aabbMin=(-11.60,-1.60,0.00) aabbMax=(-6.20,7.60,2.80)
  bspOrigin=(0.00,0.00,0.00) bspRadius=9.97

The extended second commit adds bspTotalLeafPolys, bspUnmatchedIds, bspOrigin, and bspRadius fields to give a complete picture of cell geometry from the physics cache perspective. This infrastructure stays in place as scaffolding for the portal-traversal phase.


Issue status after Cluster A

Issue Status Notes
#84 Blocked by air indoors OPEN (partial) Spawn-in-building variant resolved by Phase D. Threshold/doorway wall-blocking remains open under #87.
#85 Pass through walls outside→in OPEN Root cause confirmed as same as #84 remaining symptom. See #87.
#86 Click selection penetrates walls CLOSED Phase B. WorldPicker.Pick + CellBspRayOccluder.
#87 Indoor portal-based cell tracking OPEN (new) Filed 2026-05-19. Retail-faithful fix via CObjMaint::HandleObjectEnterCell.

Probe evidence — log file findings

launch-cluster-a-capture.log

Initial probe run with ACDREAM_PROBE_INDOOR_BSP=1. Result: zero [indoor-bsp] lines during outdoor walking and during approach to the Holtburg cottage doorway. This was the first confirmation that the indoor-BSP branch was entirely gated out. The player's CellId remained an outdoor cell for all movement.

launch-cluster-a-verify.log

Post-Phase-D run. Observed [indoor-bsp] lines only during jump frames (6 total). When the player jumped inside the cottage, the body briefly rose to a height inside the room AABB, CellId promoted to 0xA9B40143, and the indoor-BSP branch fired. On landing, the body returned to floor level, fell outside the AABB, and CellId reverted to the outdoor cell. Confirmed that AABB containment works for the room cell when the player is mid-air, but fails at floor level.

launch-cluster-a-cache-diag2.log

First [cell-cache] probe run (Phase E first commit). Showed all cached cells with their physics poly counts and local AABBs. Confirmed 14 physics polys in cell 0xA9B40143 (the room), indicating BSP geometry is present and complete. Identified cell 0xA9B40146 as a 4-poly threshold cell.

launch-cluster-a-cache-diag3.log

Extended [cell-cache] probe run (Phase E second commit). Full data:

[cell-cache] id=0xA9B40143 physicsPolyCount=14 bspTotalLeafPolys=14
  bspUnmatchedIds=0 aabbMin=(-11.60,-1.60,0.00) aabbMax=(-6.20,7.60,2.80)
  bspOrigin=(0.00,0.00,0.00) bspRadius=9.97

Room cell: 2.80 m AABB height — works for mid-air player.

[cell-cache] id=0xA9B40146 physicsPolyCount=4
  aabbMin=(-11.60,2.80,-0.20) aabbMax=(-10.00,7.60,0.00)
  bspRadius=2.3

Threshold/doorway cell: 0.20 m AABB Z range (from -0.20 to 0.00). A standing player at local Z=0.46 m is outside this AABB. This is why AABB containment fails for normal walking through doorways.

Key conclusion: the geometry is correct and complete (14/14 polys match between physics cache and BSP leaf count). The problem is purely in the cell-ownership tracking mechanism, not the collision data itself.


Diagnostic infrastructure remaining in place

Both probes stay committed and wired. They serve as scaffolding for the portal-traversal follow-up phase:

  • ACDREAM_PROBE_INDOOR_BSP=1 / DebugPanel "Indoor BSP probe": logs one [indoor-bsp] line each time FindEnvCollisions takes the indoor-cell branch. After portal traversal is implemented, this probe should fire consistently whenever the player is indoors.

  • ACDREAM_PROBE_CELL_CACHE=1 / DebugPanel "Cell cache probe": dumps all cached EnvCell physics data (poly counts, BSP bounding sphere, AABB, unmatched ID count). Useful for verifying that cell structs load correctly and that portal connectivity data is present.

Both are gated behind PhysicsDiagnostics static class (existing pattern from L.2a).


Follow-up items for the portal-traversal phase

1. Implement portal-based indoor cell tracking (issue #87). Replace PhysicsDataCache.TryFindContainingCell AABB containment with retail's CObjMaint::HandleObjectEnterCell portal traversal. When the player crosses a cell portal boundary, CellId propagates through CEnvCell portal connectivity data. PDB symbols in docs/research/named-retail/acclient_2013_pseudo_c.txt and struct definitions in docs/research/named-retail/acclient.h lines 31715-31726 (CCellStructure shape). The retail reference implementation is the right oracle — do not guess at the traversal algorithm.

2. Audit-trail note: add retail PDB symbol citations to TryFindContainingCell. The current implementation in src/AcDream.Core/Physics/PhysicsDataCache.cs ~line 261 is documented as a shortcut. The follow-up phase should add the PDB symbol citation (e.g., // retail: CObjMaint::HandleObjectEnterCell // docs/research/named-retail/acclient_2013_pseudo_c.txt:XXXXX) per the Phase D code-review I1 note, so future readers know this is intentionally replacing an interim implementation.

3. Consider renaming ResolveOutdoorCellIdResolveCellId. The method now handles both outdoor and indoor cell resolution. The rename is low-risk (one call site in PhysicsEngine.cs) and would reduce the cognitive overhead for the next phase's author. Noted as a Phase D code-review M2 suggestion — do it in the same commit as the portal-traversal implementation to keep the rename and the semantic change together.


State at handoff

  • Branch: claude/competent-robinson-dec1f4, 7 commits of implementation/test/diagnostic work.
  • Build state: dotnet build -c Debug clean.
  • Tests: 8 pre-existing failures unchanged (MotionInterpreter / BSPStepUp baseline). All new tests green.
  • Issues: #86 CLOSED; #84 PARTIAL; #85 OPEN; #87 OPEN (new).
  • Diagnostic probes: [indoor-bsp] + [cell-cache] active and wired.
  • Next: portal-based indoor cell tracking (#87) or M2 critical path — Claude's choice per work-order autonomy.