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>
This commit is contained in:
Erik 2026-05-19 16:12:24 +02:00
parent 1f11ba9b38
commit f0900ebe12
4 changed files with 373 additions and 17 deletions

View file

@ -776,7 +776,39 @@ acdream's plan lives in two files committed to the repo:
acceptance criteria. Do not drift from the spec without explicit user
approval.
**Currently in Phase L.2 (Movement & Collision Conformance).** L.2a slices
**Indoor walking Phase 1 — BSP cluster (Cluster A) partially shipped
2026-05-19.** Seven commits across five phases:
- `18a2e28` — implementation plan
- `27d7de1` — Phase A: `[indoor-bsp]` probe + `ProbeIndoorBspEnabled` toggle
- `3764867` — Phase B: `CellBspRayOccluder` in `WorldPicker.Pick` (**closes #86**)
- `4e308d5` — Phase B follow-up: screen-rect cell-occlusion tests
- `c19d6fb` — Phase D: AABB containment for indoor CellId promotion + L.2e bare-low-byte fix (partial #84 fix)
- `fda6af7` — Phase E first commit: `[cell-cache]` probe
- `1f11ba9` — Phase E second commit: extended `[cell-cache]` with AABB + bsphere + poly counts
**#86** (click selection penetrates walls) — **CLOSED.** `WorldPicker.Pick`
consults `CellBspRayOccluder.NearestWallT`; entities behind walls are filtered.
**#84** (blocked by air indoors) — **PARTIAL.** The "spawn-in-building stuck
above floor" variant is resolved (Phase D promotes CellId to the indoor cell
on spawn-in). The remaining "walls don't block from inside during normal
walking" symptom is the same root cause as #85 — AABB containment is too
tight for threshold/doorway cells (Z range ~0.2 m, player stands at ~0.46 m)
to keep CellId promoted. Both are tracked under new issue **#87**.
**#85** (pass through walls outside→in) — **OPEN.** Root cause confirmed as
same as #84 remaining symptom — CellId drifts back to outdoor cell, indoor
BSP never fires. See #87.
**#87** (indoor portal-based cell tracking) — **FILED.** Retail-faithful fix
via `CObjMaint::HandleObjectEnterCell` + `CEnvCell` portal connectivity.
Diagnostic infrastructure from Cluster A (`[indoor-bsp]` + `[cell-cache]`
probes, both runtime-toggleable) stays in place as scaffolding for the
follow-up phase. Handoff: [`docs/research/2026-05-19-cluster-a-shipped-handoff.md`](docs/research/2026-05-19-cluster-a-shipped-handoff.md).
**Next phase is Claude's choice** per work-order autonomy. Candidates:
indoor portal-based cell tracking (#87, completes the indoor walking story);
M2 critical path (F.2 / F.3 / F.5a / L.1c / L.1b — kill-a-drudge demo);
or the pre-existing "next phase candidates" list below.
**Previously in Phase L.2 (Movement & Collision Conformance).** L.2a slices
1+2+3 + L.2d slice 1+1.5 + L.2g slice 1 + L.2g slice 1b + L.2g slice 1c +
**Phase B.4b** + **Phase B.4c** all shipped and visual-verified 2026-05-13;
**Phase B.5** (ground-item pickup, F-key) shipped and visual-verified

View file

@ -239,7 +239,7 @@ to the second floor without getting stuck.
## #84 — Blocked by air indoors
**Status:** OPEN
**Status:** OPEN (partial fix 2026-05-19)
**Severity:** HIGH (blocks indoor navigation)
**Filed:** 2026-05-19
**Component:** physics, collision
@ -266,6 +266,20 @@ visible cell mesh. Possibilities:
**Acceptance:** Walking through interior cell space hits collisions
only where visible walls/furniture exist.
**Resolution (2026-05-19 partial · `c19d6fb`):** Phase D of Cluster A
extended `ResolveOutdoorCellId` in `PhysicsEngine.cs` with an indoor
cell-containment scan: when the player's world position falls inside any
cached EnvCell's AABB, `CellId` is promoted to that indoor cell, which
enables the `FindEnvCollisions` indoor-BSP branch. This resolved the
"spawn in building and be stuck above the floor" variant of #84
player's CellId now promotes to the interior cell on spawn-in, the floor
is walkable, and the player can move freely. The "invisible air obstacle"
symptom for rooms the player walks INTO from outside is now superseded by
the root cause in #87 (AABB containment is too tight for threshold/
doorway cells to keep CellId promoted during normal walking). That
remaining symptom will be resolved by the portal-based cell tracking
fix.
---
## #85 — Pass through walls from outside→in
@ -293,30 +307,65 @@ collision polys or per-poly back-face handling.
**Acceptance:** Walking into an inn wall from outside collides; player
must enter via the door portal.
**Status update (2026-05-19):** The root cause is now pinned as the
same failure as #84's remaining symptom — `CellId` isn't promoted to
the indoor cell during normal outdoor→indoor walking because AABB
containment is too tight for threshold/doorway cells. Without CellId
in the indoor cell, the indoor-BSP collision branch in
`FindEnvCollisions` never fires regardless of approach direction.
See new issue #87 (portal-based indoor cell tracking) for the
retail-faithful fix.
---
## #86 — Click selection penetrates walls
## #87 — Indoor cell tracking uses AABB containment instead of portal traversal
**Status:** OPEN
**Severity:** MEDIUM
**Severity:** HIGH
**Filed:** 2026-05-19
**Component:** input, interaction
**Component:** physics
**Description:** Clicking through a wall from the outside selects NPCs
and objects inside the building. The `WorldPicker` raycast doesn't
intersect cell BSP geometry.
**Description:** `PhysicsDataCache.TryFindContainingCell` promotes the
player's `CellId` to an indoor EnvCell when their world position falls
inside any cached cell's local AABB. This is too tight to keep `CellId`
promoted to an indoor cell during normal walking. Threshold/doorway cells
(the polys that sit at a room boundary) have AABB Z ranges of only ~0.2 m;
a standing player at local Z=0.46 m is OUTSIDE the AABB and containment
fails. Because `CellId` drifts back to the outdoor cell, the indoor-BSP
collision branch in `TransitionTypes.FindEnvCollisions` is gated out for
most movement, so walls don't block from inside the house and the floor
physics is unreliable. The retail fix is portal-based cell traversal —
when the player crosses a cell portal boundary, the cell ownership
propagates through portal connectivity data in `CEnvCell`.
**Root cause / status:** `WorldPicker.BuildRay + Pick` (introduced in
Phase B.4) tests against entity AABBs and scenery BSPs but probably
not cell BSP. Outdoor NPCs are pickable because their entity AABB is
the test target; indoor NPCs are pickable from outside because the
wall isn't in the ray's intersection set.
**Evidence:** `launch-cluster-a-cache-diag3.log` (Cluster A Phase E
capture). Cell `0xA9B40143` (real room) has
`physicsPolyCount=14 bspTotalLeafPolys=14 bspUnmatchedIds=0
aabbMin=(-11.60,-1.60,0.00) aabbMax=(-6.20,7.60,2.80)` — geometry is
complete and the AABB spans 2.8 m height, which works. Cell `0xA9B40146`
(threshold/doorway) has `physicsPolyCount=4
aabbMin=(-11.60,2.80,-0.20) aabbMax=(-10.00,7.60,0.00)` — Z range is
only 0.2 m; a standing player is always outside it. Only 6 `[indoor-bsp]`
lines fired across an entire indoor walking session (all during mid-jump
frames when the player was briefly inside the room AABB at jump height).
**Files:**
- `src/AcDream.App/Rendering/WorldPicker.cs` (or equivalent — check
Phase B.4b reference).
- `src/AcDream.Core/Physics/PhysicsDataCache.cs` (`TryFindContainingCell`,
approximately line 261)
- `src/AcDream.Core/Physics/PhysicsEngine.cs` (`ResolveOutdoorCellId`,
approximately line 238)
- `src/AcDream.Core/Physics/TransitionTypes.cs` (`FindEnvCollisions` cell
branch, approximately line 1188)
**Acceptance:** Clicking on a wall doesn't select NPCs behind it.
**Retail reference:** PDB symbols `CObjMaint::HandleObjectEnterCell` and
`CEnvCell` portal data. See `docs/research/named-retail/acclient.h` lines
31715-31726 for `CCellStructure` shape; `acclient_2013_pseudo_c.txt` for
the implementations.
**Acceptance:** Player walking from outside the Holtburg cottage into the
interior crosses portals and `CellId` updates accordingly; walls block
from both inside and outside; the `[indoor-bsp]` probe fires consistently
during indoor walking (not just during mid-jump frames).
---
@ -2847,6 +2896,23 @@ Unverified. The likely culprits, ranked by suspected probability:
# Recently closed
## #86 — [DONE 2026-05-19 · 3764867 + 4e308d5] Click selection penetrates walls
**Closed:** 2026-05-19
**Commits:** `3764867` — fix(picker): Cluster A #86 — cell-BSP ray occlusion in WorldPicker; `4e308d5` — test(picker): Cluster A #86 — screen-rect cell-occlusion tests
**Component:** input, interaction
**Resolution:** `WorldPicker.Pick` now accepts a `cellOccluder` callback
(`CellBspRayOccluder`). Before returning a hit, both `Pick` overloads
consult the occluder's `NearestWallT` value; any candidate entity whose
ray parameter exceeds the nearest-wall intersection is filtered out.
The occluder is wired from `GameWindow` using the loaded `PhysicsDataCache`
cell structs. Entities behind walls from the camera's perspective are no
longer selectable. Screen-rect occlusion tests verify the filter across
several hit/miss scenarios.
---
## #77 — [DONE 2026-05-18 · 3be7000] Auto-walk doesn't engage at walking range; pickup at walking range overshoots and snaps back
**Closed:** 2026-05-18

View file

@ -71,6 +71,7 @@
| Indoor lighting + rendering — Phase 1 (diagnostics) | Five `[indoor-*]` probes wired through new `AcDream.Core.Rendering.RenderingDiagnostics` static class + DebugVM mirrors + DebugPanel checkboxes. `WbMeshAdapter` emits `[indoor-upload] requested/completed`; `WbDrawDispatcher` emits `[indoor-walk]`, `[indoor-lookup]`, `[indoor-xform]`, `[indoor-cull]` per cell entity. All rate-limited via per-cellId frame counter; lookup probe uses high-bit-tagged key namespace to avoid cross-probe suppression. Holtburg `ACDREAM_PROBE_INDOOR_ALL=1` capture identified 26/123 cells silently failing — confirmed H1 (WB swallowed exception). Spec: [`docs/superpowers/specs/2026-05-19-indoor-cell-rendering-fix-design.md`](../superpowers/specs/2026-05-19-indoor-cell-rendering-fix-design.md). Plan: [`docs/superpowers/plans/2026-05-19-indoor-cell-rendering-phase1-diagnostics.md`](../superpowers/plans/2026-05-19-indoor-cell-rendering-phase1-diagnostics.md). Capture: [`docs/research/2026-05-19-indoor-cell-rendering-probe-capture.md`](../research/2026-05-19-indoor-cell-rendering-probe-capture.md). | Tests ✓ |
| Indoor lighting + rendering — Phase 2 (fix) | Three-component diagnostic-driven fix for missing-floor bug. Component 1: `WbMeshAdapter` captures the `Task<ObjectMeshData?>` from `PrepareMeshDataAsync` and attaches a `ContinueWith` for EnvCell ids — surfaces faulted-task exceptions + clean-null returns. Component 2: replaced `NullLogger<ObjectMeshManager>` with a Console-backed `ConsoleErrorLogger<T>` so WB's intentional `_logger.LogError(ex, ...)` at the swallow site at `ObjectMeshManager.cs:589` writes `[wb-error]` lines. **Root cause definitively identified in one capture: `ArgumentOutOfRangeException` from `DatReaderWriter.Setup.Unpack` at WB's `PrepareEnvCellMeshData` line 1223 — `TryGet<Setup>(stab.Id, ...)` was called blindly on every `envCell.StaticObjects` id without checking the Setup-prefix bit. GfxObj-typed stabs (0x01xxxxxx) caused mid-deserialization throws, bubbling up to PrepareMeshData's outer catch which silently returned null. Entire cell upload failed, room mesh never reached `_renderData`.** Component 3 fix: one-line type-check guard `(stab.Id & 0xFF000000u) == 0x02000000u && _dats.Portal.TryGet<Setup>(stab.Id, out var stabSetup)`. Committed to WB submodule on branch `acdream-fix-floor-rendering` at SHA `34460c4` — needs submodule pointer advance at merge time. **Verification: 0 [wb-error] (was 385), 0 NULL_RESULT (was 55), Holtburg 123/123 cells complete (was 97/123). User visually confirmed floors render in Holtburg Inn.** Surfaced 9 pre-existing indoor bugs (see-through floor, indoor collision, stairs, walls, click-thru, indoor lighting artifacts, atmospheric-lighting-on-stabs, slope terrain lighting) — all filed in `docs/ISSUES.md` for follow-up phases. Cause report: [`docs/research/2026-05-19-indoor-cell-rendering-cause.md`](../research/2026-05-19-indoor-cell-rendering-cause.md). Verification: [`docs/research/2026-05-19-indoor-cell-rendering-verification.md`](../research/2026-05-19-indoor-cell-rendering-verification.md). Plan: [`docs/superpowers/plans/2026-05-19-phase2-indoor-cell-rendering-fix.md`](../superpowers/plans/2026-05-19-phase2-indoor-cell-rendering-fix.md). | Live ✓ |
| C.1.5b | Per-part PES transforms + dat-hydrated entity DefaultScript dispatch. Closes issue #56. Shipped 2026-05-12 across 5 commits (`1e3c33b` docs+plan, `f3bc15e` SetupPartTransforms helper, `11521f4` ParticleHookSink applies `CreateParticleHook.PartIndex`, `5ca5827` activator refactor + GameWindow resolver lambda, `8735c39` GpuWorldState 4 new fire-sites). **Slice A** — new [`SetupPartTransforms.Compute(setup)`](../../src/AcDream.Core/Meshing/SetupPartTransforms.cs) walks `PlacementFrames[Resting]``[Default]` → first-available (mirrors `SetupMesh.Flatten` priority) and returns `Matrix4x4` per part; new `ParticleHookSink.SetEntityPartTransforms(entityId, partTransforms)` mirrors the existing `_rotationByEntity` pattern; `SpawnFromHook` now transforms hook offset through `partTransforms[partIndex]` before applying entity rotation. **Slice B** — activator's `ServerGuid==0` guard relaxed: keys by `entity.ServerGuid` when non-zero, else `entity.Id` (collision-free with server guids in the `0x40xxxxxx` interior / `0x80xxxxxx` scenery / `0xC0xxxxxx` ranges). Resolver delegate refactored to return `ScriptActivationInfo(ScriptId, PartTransforms)` so one dat lookup yields both pieces. `GpuWorldState` fires the activator from 4 new sites: `AddLandblock` + `AddEntitiesToExistingLandblock` (Far→Near promotion) for OnCreate, `RemoveLandblock` + `RemoveEntitiesFromLandblock` (Near→Far demotion) for OnRemove. ServerGuid==0 filter on AddLandblock avoids double-firing pending-bucket merges. **Reality discovery folded into spec §3**: EnvCell `StaticObjects` are already hydrated as `WorldEntity` instances by `GameWindow.BuildInteriorEntitiesForStreaming` (with stable `entity.Id` in `0x40xxxxxx`) — no synthetic-ID scheme or separate walker class needed (handoff §4 Q1/Q2 mooted). **Visual verification 2026-05-12**: Holtburg Town network portal swirl distributes across the arch (no ground-burial), Inn fireplace flames render over the firebox, cottage chimney smoke columns render, spell-cast animation-hook particles all match retail. 18 new + 4 updated tests, all Vfx/Meshing/Streaming/Activator green. Spec: [`docs/superpowers/specs/2026-05-13-phase-c1.5b-design.md`](../superpowers/specs/2026-05-13-phase-c1.5b-design.md). Plan: [`docs/superpowers/plans/2026-05-13-phase-c1.5b.md`](../superpowers/plans/2026-05-13-phase-c1.5b.md). | Live ✓ |
| Indoor walking Phase 1 — BSP cluster (partial) | 2026-05-19. Probe + WorldPicker cell-BSP occlusion (#86 closed) + CellId promotion via AABB containment (partial #84 fix). Seven commits across 5 phases: `18a2e28` plan, `27d7de1` Phase A `[indoor-bsp]` probe + toggle, `3764867` Phase B CellBspRayOccluder in WorldPicker, `4e308d5` Phase B screen-rect tests, `c19d6fb` Phase D AABB containment + L.2e bare-low-byte fix, `fda6af7` Phase E `[cell-cache]` diagnostic, `1f11ba9` Phase E extended AABB/bsphere/poly-count fields. **#86 closed** (picker occlusion). **#84 partially closed** (spawn-in-building stuck-above-floor resolved; threshold/doorway walls remain open under #87). **#85 open** (wall pass-through root cause confirmed as same as #84 remaining symptom — CellId doesn't stay promoted during outdoor→indoor walking). **#87 filed** (portal-based indoor cell tracking — retail-faithful follow-up). `[indoor-bsp]` + `[cell-cache]` probes stay in place as scaffolding for the follow-up phase. Handoff: [`docs/research/2026-05-19-cluster-a-shipped-handoff.md`](../research/2026-05-19-cluster-a-shipped-handoff.md). Plan: [`docs/superpowers/plans/2026-05-19-indoor-walking-phase1-bsp-cluster.md`](../superpowers/plans/2026-05-19-indoor-walking-phase1-bsp-cluster.md). | Tests ✓ |
Plus polish that doesn't get its own phase number:
- FlyCamera default speed lowered + Shift-to-boost
@ -224,7 +225,8 @@ Research: R9 + R12 + R13.
- **✓ SHIPPED — G.1 — Sky + weather + day-night.** Deterministic client-side from Portal Year time. Sky dome geometry + keyframe gradients + rain/snow particles. See `r12-weather-daynight.md`. Full data + visual stack shipped: Region dat loader, keyframe interp, WeatherSystem with 5-kind PDF + transitions + storm flashes, WorldSession→WorldTimeService sync via ConnectRequest+TimeSync, SkyRenderer with sky-object arcs + UV scroll, rain/snow billboard renderer, F7/F10 debug cycle keys.
- **✓ SHIPPED — G.2 — Dynamic lighting.** 8-light D3D-style fixed pipeline. Hard-cutoff at Range, no attenuation inside. Cell ambient. Shader UBO per frame. See `r13-dynamic-lighting.md`. SceneLightingUbo std140 at binding=1 feeds terrain + mesh + mesh_instanced + sky shaders. LightingHookSink auto-registers Setup.Lights at entity stream-in, flips IsLit on SetLightHook, unregisters on landblock unload.
- **G.3 — Dungeon streaming + portal space.** `EnvCellStreamer`, portal-visibility BFS, `PlayerTeleport (0xF751)` handling with `LoginComplete` re-send, "pink bubble" loading state. **Blocked on L.2e** for trustworthy `cell_bsp`, indoor/outdoor portal transit, adjacent-cell ownership, and building entry/exit collision boundaries. See `r09-dungeon-portal-space.md`.
- **Indoor portal-based cell tracking (follow-up to Indoor walking Phase 1 / 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 the `CEnvCell` portal connectivity graph. Prerequisite for wall collision from outside (#85) and the remaining #84 threshold symptom. PDB symbols and `acclient.h` `CCellStructure` refs are in place (see #87). **Unblocks G.3.**
- **G.3 — Dungeon streaming + portal space.** `EnvCellStreamer`, portal-visibility BFS, `PlayerTeleport (0xF751)` handling with `LoginComplete` re-send, "pink bubble" loading state. **Blocked on indoor portal-based cell tracking above** (and previously on L.2e) for trustworthy indoor/outdoor portal transit, adjacent-cell ownership, and building entry/exit collision boundaries. See `r09-dungeon-portal-space.md`.
**Acceptance:** walk outside at dusk, see the sky gradient + sun moving; enter a torch-lit dungeon via portal; leave back to daylight.

View file

@ -0,0 +1,256 @@
# 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`](../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 `ResolveOutdoorCellId``ResolveCellId`.**
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.