Merge branch 'claude/competent-robinson-dec1f4' — Indoor walking Phase 1 + Phase 2

Cluster A (Indoor walking Phase 1 — BSP cluster):
- WorldPicker cell-BSP occlusion → #86 closed
- CellId promotion via AABB containment (partial Phase D fix)
- Diagnostic infrastructure: [indoor-bsp], [cell-cache] probes

Indoor walking Phase 2 (Portal-based cell tracking):
- CellBSP + Portals wired into CellPhysics
- CellTransit static class: FindTransitCellsSphere + AddAllOutsideCells + FindCellList
- ResolveCellId rename + sphereRadius plumbing
- BuildingPhysics + CheckBuildingTransit (outdoor→indoor entry)
- Foot-sphere center fix (made portal tracking actually work in production)
- Indoor walkable-plane synthesis (closes the falling-stuck bug)

Closes ISSUES.md #84, #85, #86, #87.
Files new issues #88 (indoor object vibration) + #89 (port SphereIntersectsCellBsp).

Spec: docs/superpowers/specs/2026-05-19-indoor-portal-cell-tracking-design.md
Handoff: docs/research/2026-05-19-indoor-walking-phase2-shipped-handoff.md
This commit is contained in:
Erik 2026-05-19 19:34:13 +02:00
commit 1af49b710e
34 changed files with 6618 additions and 79 deletions

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.

View file

@ -0,0 +1,284 @@
# Indoor walking Phase 2 — Portal-based cell tracking — handoff (2026-05-19)
**Date:** 2026-05-19.
**Branch:** `claude/competent-robinson-dec1f4` (commits land here; merge to main handled by controller).
**Predecessor:** Indoor walking Phase 1 — BSP cluster (Cluster A). Partially shipped 2026-05-19; closed #86 cleanly, filed #87 for the portal-traversal root cause. Diagnostic infrastructure (`[indoor-bsp]` + `[cell-cache]` probes) remained as scaffolding. Handoff: [`docs/research/2026-05-19-cluster-a-shipped-handoff.md`](2026-05-19-cluster-a-shipped-handoff.md).
---
## TL;DR
Phase 2 fully closes the indoor-walking story. Six commits replace Phase D's
AABB-containment shortcut with retail-faithful portal-graph cell traversal.
`CellId` now promotes to indoor cells via portals and remains promoted through
doorways, thresholds, and multi-room navigation. Indoor cell-BSP collision fires
consistently. A critical fix in commit 5 passes the foot-sphere center (not the
entity reference point) to `ResolveCellId`, which was the production failure that
made PointInsideCellBsp return false at floor level. Commit 6 adds
`TryFindIndoorWalkablePlane` so the walkability resolver doesn't fall through to
outdoor terrain when the player is inside.
**Visual verification at Holtburg cottage (2026-05-19, user testing live ACE):**
- Walls block from inside — player cannot walk through cottage walls.
- Multi-room navigation via doorways works — `[cell-transit]` log shows `0xA9B40145 → 0x143 → 0x144 → 0x13F` chains.
- Walking back outdoors through a door works (post-walkable fix in commit 6).
- Cell tracking is robust through multiple indoor sessions.
---
## Commits
| # | SHA | Subject |
|---|---|---|
| 1 | `1969c55` | `feat(physics): Phase 2 — wire CellBSP + Portals into CellPhysics` |
| 2 | `aad6976` | `feat(physics): Phase 2 — port CellTransit + wire into ResolveCellId` |
| 3 | `069534a` | `feat(physics): Phase 2 — BuildingPhysics + CheckBuildingTransit` |
| 4 | `702b30a` | `refactor(physics): Phase 2 — code-review polish on BuildingPhysics commit` |
| 5 | `3ffe1e4` | `fix(physics): Phase 2 — pass foot-sphere center to ResolveCellId` |
| 6 | `eb0f772` | `fix(physics): Phase 2 — synthesize indoor walkable plane from cell floor` |
**Build:** clean on all commits.
**Tests:** `dotnet test` shows the same 8 pre-existing failures in
`AcDream.Core.Tests` (MotionInterpreter / BSPStepUp / etc., unchanged). All
new Phase 2 tests and the walkable-plane tests green.
---
## What shipped
### Commit 1 — CellBSP + Portals wired into CellPhysics
New `PortalInfo` struct holds `PortalId`, `PortalPolygonIndex`, `PortalFlags`,
and `OtherCellId`. `CellPhysics` extended with:
- `CellBSP` — a third BSP tree (alongside `PhysicsBSP` and the render BSP) used
for point-in-cell tests. Retail: `CCellStructure::cell_bsp`.
- `Portals``IReadOnlyList<PortalInfo>` built from `envCell.CellPortals`.
- `PortalPolygons` — the visible polygons that portals reference (`cellStruct.Polygons`,
not `PhysicsPolygons`; portals reference the visible-geometry polygon list).
- `VisibleCellIds` — cells visible from this cell (used by `AddAllOutsideCells`).
Phase D's `LocalAabbMin/Max` + `TryFindContainingCell` are deleted — they are now
superseded by the portal traversal in `CellTransit`.
### Commit 2 — CellTransit + ResolveCellId
New `CellTransit` static class implements the retail portal-neighbour walk.
Three public entry points:
- **`FindTransitCellsSphere(sphereCenter, sphereRadius, startCell, cache)`** —
walks portal connectivity from `startCell` outward. For each portal, tests
whether the sphere overlaps the portal polygon (using `PointInsideCellBsp` on
the sphere center as an approximation — see issue #89 for the retail-faithful
sphere variant). Recurses into neighbour cells up to a depth limit.
- **`AddAllOutsideCells(sphereCenter, blockId, cache, results)`** — for the
outdoor path: populates a 24m grid of outdoor cell ids around the sphere center
using `TerrainSurface.ComputeOutdoorCellId`. Mirrors retail's `add_all_outside_cells`.
- **`FindCellList(sp, startCell, cache)`** — top-level driver. Determines whether
`startCell` is an indoor (EnvCell) or outdoor cell and dispatches accordingly.
Returns a list of candidate cell ids.
`PhysicsEngine.ResolveOutdoorCellId` renamed to `ResolveCellId` (accepts
`sphereRadius` parameter). Body splits on indoor vs outdoor:
- **Indoor:** delegates to `FindCellList` and picks the candidate cell where
`PointInsideCellBsp` returns true for the sphere center.
- **Outdoor:** existing terrain-grid loop (`AddAllOutsideCells`).
`BSPQuery.PointInsideCellBsp` retyped from `PhysicsBSPNode?` to `CellBSPNode?`
(dead code retype — no behavior change). Phase D's test file deleted.
### Commit 3 — BuildingPhysics + CheckBuildingTransit
Outdoor→indoor entry path via building-shell portal graph. New `BuildingPhysics`
class caches per-building portal data (`BldPortalInfo` structs with `PortalId`,
`OtherCellId`, `CellBSP`). `PhysicsDataCache` gains `_buildings` cache keyed by
building entity id. `GameWindow` iterates `lbInfo.Buildings` at landblock load and
populates the cache.
`CellTransit.CheckBuildingTransit(sphereCenter, sphereRadius, blockId, physicsCache)`
ports retail's outdoor→indoor portal-graph entry:
1. For each building in the landblock's physics cache, test whether the sphere
center is inside the building's shell cell BSP (`PointInsideCellBsp`).
2. If inside, walk the building's portal graph to find the indoor EnvCell that
contains the sphere center.
3. Returns the EnvCell id (or 0 if no match).
`PhysicsEngine.ResolveCellId`'s outdoor branch hooks `CheckBuildingTransit` after
the terrain-grid loop, so outdoor→indoor transition is detected during normal walking.
### Commit 4 — Code-review polish
Five items addressed from reviewer:
1. DRY cell-id derivation via existing `TerrainSurface.ComputeOutdoorCellId`
(removed inline duplicate in `CheckBuildingTransit`).
2. Named `PortalFlags.ExactMatch` enum instead of raw `0x01` literal.
3. Comment clarity on `ExactMatch` reserved field.
4. Doc comment on `CheckBuildingTransit` calling out the sphere-vs-point
divergence from retail's `sphere_intersects_cell` (see issue #89).
5. Rename misleading test method name.
### Commit 5 — Critical fix: foot-sphere center to ResolveCellId
**This was the production bug that prevented Phase 2 from working until the last run.**
`ResolveCellId` was being called with `sp.CheckPos` (the entity's reference point
at feet level, world Z = terrain Z after the +0.02f bump) instead of
`sp.GlobalSphere[0].Origin` (the foot sphere CENTER, approximately +0.48m above terrain).
Combined with the +0.02f Z-bump applied to cell origins in `PhysicsDataCache`, the
test point landed at cell-local Z = -0.02 m — just below the cell's floor — and
`PointInsideCellBsp` returned false for every cell. CellId never promoted to indoor
cells during normal walking despite `FindCellList` correctly finding the right
candidate cells.
Passing the foot-sphere center (which sits 0.48m above the floor, well inside any
room cell) made portal-based cell tracking actually work in production.
Also adds the `[check-bldg]` diagnostic line (logged when `CheckBuildingTransit`
returns a non-zero indoor cell id).
### Commit 6 — TryFindIndoorWalkablePlane
**Root cause of the post-Phase-2 falling-stuck bug.**
When indoor cell-BSP returned OK (no wall collision), the code fell through to
outdoor `SampleTerrainWalkable` + `ValidateWalkable`. Outdoor terrain Z is below
the indoor floor (due to the +0.02f Z-bump), so `ValidateWalkable` computed the
player as floating well above terrain → not walkable → player stuck in the falling
animation when blocked by an indoor wall.
New `TryFindIndoorWalkablePlane(worldPos, cellPhysics)`: finds the floor polygon
directly under the player's world position by testing `worldPos` against each
physics polygon's plane normal (upward-facing = floor) and building a `ContactPlane`
from it. Called from the indoor branch of `ResolveWithTransition` before the outdoor
terrain fallback. Returns true when a floor poly is found; the resolver uses the
synthesized plane for walkability.
---
## Issue status after Phase 2
| Issue | Status | Notes |
|---|---|---|
| #84 Blocked by air indoors | **FULLY CLOSED** | Spawn-in-building variant: Phase D (Cluster A). Wall-block-from-inside + falling-stuck variants: Phase 2 commits 2, 5, 6. |
| #85 Pass through walls outside→in | **CLOSED** | `CheckBuildingTransit` + portal traversal. CellId promotes to indoor on outdoor→indoor entry. |
| #86 Click selection penetrates walls | CLOSED (Phase 1) | `WorldPicker.Pick` + `CellBspRayOccluder`. |
| #87 Indoor portal-based cell tracking | **CLOSED** | `CellTransit.FindCellList` + `FindTransitCellsSphere` + `AddAllOutsideCells`. Portal-graph traversal replaces AABB containment. |
| #88 Indoor static objects vibrate | OPEN (new) | Pre-existing visual jitter on bookshelves/furnaces. Filed 2026-05-19. Medium severity. |
| #89 Port BSPQuery.SphereIntersectsCellBsp | OPEN (new) | `CheckBuildingTransit` uses `PointInsideCellBsp` (radius-less approximation) instead of retail's `sphere_intersects_cell`. Filed 2026-05-19. Low severity. |
---
## Probe evidence — log file findings
### `launch-phase2-verify3.log`
First run that showed indoor cell-transits firing. `[cell-transit]` output
confirmed the portal traversal was finding indoor cells. `[indoor-bsp]` probe
fired consistently during indoor walking (not just during mid-jump frames as in
Cluster A). This log is the first evidence that `CellTransit.FindCellList` was
working correctly for room interiors, though outdoor→indoor entry was not yet
exercised.
### `launch-phase2-verify4.log`
Multi-room navigation run. `[cell-transit]` log shows
`0xA9B40145 → 0x143 → 0x144 → 0x13F` chains as the player walked between
rooms in the Holtburg cottage via doorways. Confirmed the `FindTransitCellsSphere`
recursive portal walk was promoting CellId correctly through threshold cells.
Walls blocked from inside in all rooms tested.
### `launch-phase2-verify5.log`
Walkable bug evidence run. After the outdoor→indoor transition was wired
(`CheckBuildingTransit`), the player could walk into the cottage from outside,
but colliding with an indoor wall produced a falling-stuck state (the `[indoor-bsp]`
probe fired for the wall collision, but `ValidateWalkable` returned false because
it was sampling outdoor terrain Z). This log captured the falling-stuck symptom
and the `SampleTerrainWalkable` fallthrough trace, motivating commit 6.
### `launch-phase2-verify6.log`
Post-walkable-fix verification run. After `TryFindIndoorWalkablePlane` was added:
- Outdoor→indoor entry works (player walks through doorway, CellId promotes).
- Indoor wall collision works (walls block, player doesn't pass through).
- Walking back outdoors through the door works (CellId demotes to outdoor cell).
- No falling-stuck state observed. User confirmed all three behaviors.
---
## Diagnostic infrastructure remaining in place
All four probes stay committed and wired. They serve as production diagnostics
and as debugging aids for follow-up issues:
- **`ACDREAM_PROBE_INDOOR_BSP=1`** / DebugPanel "Indoor BSP probe": logs one
`[indoor-bsp]` line each time `FindEnvCollisions` takes the indoor-cell branch.
After Phase 2, this fires consistently whenever the player is indoors. Useful
for confirming the indoor-BSP path is active.
- **`ACDREAM_PROBE_CELL_CACHE=1`** / DebugPanel "Cell cache probe": dumps all
cached EnvCell physics data (poly counts, BSP bounding sphere, AABB, unmatched
ID count, portal count). Useful for verifying cell struct loads and portal
connectivity.
- **`ACDREAM_PROBE_CELL=1`** (existing L.2a slice 1): one `[cell-transit]` line
per `PlayerMovementController.CellId` change (old → new cell, world position,
reason tag). Essential for tracing indoor promotion/demotion sequences.
- **`[check-bldg]`** (commit 5): logged by `ResolveCellId` when
`CheckBuildingTransit` returns a non-zero indoor cell id. Fires once per
outdoor→indoor transition detection.
All gated behind `PhysicsDiagnostics` static class (existing pattern from L.2a).
---
## Visual verification outcomes
**2026-05-19, user testing live against local ACE at Holtburg.**
| Scenario | Result |
|---|---|
| Walk into cottage wall from inside | Blocked ✓ |
| Walk between rooms via doorway | CellId transitions logged, multi-room navigation works ✓ |
| Walk from outside into cottage through door | Outdoor→indoor entry promoted CellId; indoor BSP collision active ✓ |
| Walk back outside through door | CellId demoted to outdoor cell; outdoor physics resumed ✓ |
| No falling-stuck after post-walkable fix | Confirmed ✓ |
| Robust across multiple indoor sessions | Confirmed ✓ |
---
## Known follow-ups
**#88 — Indoor static objects vibrate (bookshelves, open furnaces).** Pre-existing
visual jitter spotted before Phase 2 shipped. Medium severity. Candidates: repeated
`EntityScriptActivator.OnCreate/OnRemove` near cell boundaries, per-part transform
drift, or particle-emitter offset accumulation. Investigate in a follow-up session.
**#89 — Port `BSPQuery.SphereIntersectsCellBsp`.** `CellTransit.CheckBuildingTransit`
currently uses `PointInsideCellBsp` (tests sphere CENTER only). Retail's
`CEnvCell::check_building_transit` uses `CCellStruct::sphere_intersects_cell`
(radius-aware, returns Inside/Crossing/Outside). Practical effect: entry fires
~0.48m deeper into the doorway than retail. Low severity — visually acceptable.
The `sphereRadius` parameter is already plumbed through for when this is ported.
**#80 — Indoor darkness (camera on 2nd floor goes very dark).** Still open.
Not in Phase 2's scope. Lighting / ambient-occlusion issue that predates indoor
rendering Phase 2.
---
## State at handoff
- **Branch:** `claude/competent-robinson-dec1f4`, 6 commits of Phase 2 work
(plus 7 from Phase 1 / Cluster A on the same branch).
- **Build state:** `dotnet build -c Debug` clean.
- **Tests:** 8 pre-existing failures unchanged (MotionInterpreter / BSPStepUp
baseline). All targeted test projects green.
- **Issues:** #84, #85, #87 CLOSED. #86 CLOSED (Phase 1). #88, #89 OPEN (new).
- **Diagnostic probes:** `[indoor-bsp]`, `[cell-cache]`, `[cell-transit]`,
`[check-bldg]` all active and wired.
- **Next:** M2 critical path (F.2 / F.3 / F.5a / L.1c / L.1b — kill-a-drudge
demo) or other candidates per work-order autonomy in CLAUDE.md.