# Door collision — Task 7 shipped, partial fix, deeper bug remains **Date:** 2026-05-24 (evening, continuation of door collision investigation) **Branch:** `claude/strange-albattani-3fc83c` **Status:** A6.P4 architecture is correct. Multi-part registration works. The Holtburg door bug PARTIALLY fixed — center blocks, but off-center and inside-out still walk through. Root cause is downstream in the engine's grounded BSP collision path (Path 5 + step-up), NOT in the multi-part registration we just shipped. --- ## TL;DR **Three commits shipped** (composable foundation): | SHA | Title | What it does | |---|---|---| | `e1d94d7` | dat-inspection test | Confirmed door part `0x010044B5` has full 1.9×0.26×2.5 m BSP slab (6 Landblock polys). Hypothesis A from prior handoff was wrong. | | `3b7dc46` | `GetNearbyObjects` dedup fix | Changed `HashSet` (entityId) → `HashSet`. Multi-part shapes no longer silently dropped. | | `ca9341c` | Task 7 live wiring | `RegisterLiveEntityCollision` uses `ShadowShapeBuilder.FromSetup` + `RegisterMultiPart`. Doors now register cyl+bsp instead of just cyl. | **Live verification (visual user test):** | Scenario | Result | |---|---| | Dead center, walk into closed door (outside) | ✅ Blocks | | 50 cm off-center, walk into closed door (outside) | ❌ Walks through | | Inside walking out (closed door) | ❌ Walks through | | Use door → swing → walk through | ✅ Works (ETHEREAL flip path) | **Probe-instrumented live capture confirms multi-part registration works:** - Every door spawn shows `[entity-source] shapes=cyl1+bsp1` — both shapes register. - BSP part `0x010044B5` is visited 135 times for a single door at player approaches as close as `distXY=0.415 m`. - `cacheHit=True` for every visit — the cache is populated. - BUT: zero `[resolve-bldg]` attributions for the BSP shape (all 19 attributed hits show `gfxObj=0x00000000` = the Cylinder shape). So the BSP is being QUERIED but never produces an attributed hit. The sphere walks through despite the BSP geometry being present and visited. --- ## What's in the tree right now ``` $ git log --oneline -8 ca9341c feat(phys): A6.P4 Task 7 — RegisterLiveEntityCollision uses ShadowShapeBuilder + RegisterMultiPart 3b7dc46 fix(phys): GetNearbyObjects dedup-by-entityId silently drops multi-part shadows e1d94d7 test(phys): door setup + GfxObj dat-inspection — Hypothesis A falsified c89df8e docs(handoff): door collision per-part BSP session handoff (2026-05-24) 1498697 diag(phys): [cyl-test] probe — log every Cylinder shadow collision test 3e5dc8c test(phys): Task 6 regression — Deregister clears _entityShapes cache d5ffb03 feat(phys): UpdatePosition handles multi-part entities fca0a13 feat(phys): ShadowObjectRegistry.RegisterMultiPart ``` **Uncommitted (to commit next):** - `src/AcDream.Core/Physics/TransitionTypes.cs` — new `[bsp-test]` probe in the BSP collision dispatch, mirrors `[cyl-test]`. Fires when a BSP entry is visited, BEFORE the cache lookup. Distinguishes "cache miss → silent skip" from "queried but no hit." Gated on `ACDREAM_PROBE_BUILDING=1`. - `tests/AcDream.Core.Tests/Physics/DoorCollisionApparatusTests.cs` — new test `Apparatus_Grounded_50cmOffCenter_FrontApproach_DocumentsBug` that attempts to reproduce the production bug with a grounded body + seeded ContactPlane. Currently fails because the apparatus's behavior diverges from production (apparatus blocks immediately at tick 0 with a Z+ normal from the synthetic floor; production walks through). --- ## Path 5 vs Path 6 — the divergence `BSPQuery.FindCollisions` dispatches to 6 paths based on `ObjectInfo` state. The crucial difference: - **Path 6 (Default)** — fires when `obj.State` has no `Contact` flag. Calls `SphereIntersectsPolyInternal` and `SetCollide` on hit. **Apparatus tests use this path** (no body, `isOnGround=false`). They all PASS — the door's BSP blocks the sphere correctly. - **Path 5 (Contact branch)** — fires when `obj.State.HasFlag(Contact)`. Calls `SphereIntersectsPolyInternal`; on hit, calls `StepSphereUp → DoStepUp → DoStepDown` to attempt climbing over the obstacle. Returns OK if step succeeds, Slid if step fails. **Production uses this path** (player grounded → `isOnGround=true` → engine sets `Contact` flag at `PhysicsEngine.cs:631`). Production WALKS THROUGH. So the bug is somewhere in Path 5's step-up logic. The leading hypothesis (not yet proven): > When the player is standing on flat ground in front of the door, > step-up's `DoStepDown` probes 0.6 m downward from the sphere's > current position. It finds the SAME flat ground extending to the > OTHER SIDE of the door (Holtburg cottages have no Z change between > exterior and interior floor — both at Z=94). `find_walkable` > declares step-up SUCCESS, the BSP collision returns `OK`, and the > sphere walks through the door. > > The fix probably involves: step-up should reject if a forward probe > at the lifted height STILL hits the same obstacle. The current > DoStepDown probes only DOWNWARD; it doesn't verify that the > forward motion at the lifted height is clear. This is speculation — needs apparatus verification. --- ## Why the apparatus didn't reproduce the bug The grounded apparatus test (`Apparatus_Grounded_50cmOffCenter_*`) was supposed to fail in the same way as production (walk through). Instead it BLOCKED at tick 0 with normal=(0,0,1) — sphere position unchanged. Diagnostic output: ``` [bsp-test] obj=0x000F424F gfx=0x010044B5 ... pos=(11.99,12.12,1.27) distXY=1.234 cacheHit=True [resolve] in=(12.500,11.000,0.480) tgt=(12.500,11.100,0.480) out=(12.500,11.000,0.480) ok=True hit=yes n=(0,0,1) walkable=True ``` `ACDREAM_DUMP_STEPUP=1` produced no `stepup: ENTER` lines, so `DoStepUp` was NOT called. The hit normal `(0,0,1)` came from somewhere else (likely the seeded walkable polygon or the synthetic floor interaction with the engine's terrain step-down). The apparatus's stub terrain (Z=-1000) + synthetic walkable poly at Z=0 may be causing the engine to take a different code path than production's real Holtburg terrain. Reproducing production fully would require: 1. Real terrain heightmap covering the test landblock at Z=94 2. EnvCell or stab geometry near the test door 3. Proper cottage/cell setup so portal-reachable cells include the door's outdoor cell when player is indoor This is significant apparatus investment. Worth it IF the bug requires multi-tick simulation in real geometry to surface. For now, the apparatus shows the broad shape: with proper grounded state + seeded body, the engine doesn't take the same path as the airborne (Path 6) test. --- ## Recommended next steps (ranked) ### Option A — Live diagnostic with ACDREAM_DUMP_STEPUP=1 (cheapest) Relaunch with `ACDREAM_PROBE_BUILDING=1` + `ACDREAM_DUMP_STEPUP=1`. Walk into a closed door off-center. The step-up dump will show: - Whether `DoStepUp` fires at all when the BSP hits - If so, what the input normal is - Whether `stepDown` succeeds or fails If `stepDown` succeeds (i.e., step-up climbs over the door), we've confirmed the hypothesis above and can target the fix. ### Option B — Build a richer apparatus Replace the stub terrain with a real heightmap-like surface at Z=94 spanning the test landblock. Replace the synthetic walkable poly with a proper terrain polygon at the door's world XY. This should let Path 5 run the SAME way as production. Then iterate on the fix locally in <500 ms. Estimated effort: 1-2 hours of apparatus work. ### Option C — Direct retail cdb trace Attach cdb to a running retail client at a Holtburg cottage doorway, break on `CTransition::step_up` or `CTransition::step_down`, and observe how retail handles step-up against a door. Compare against acdream's behavior. Estimated effort: 30 min - 2 hours depending on what we find. ### Option D — Pivot to fix-and-verify Hypothesis-based fix: in `DoStepUp`, reject step-up if the input collision normal is mostly horizontal AND the obstacle's bounding sphere height range significantly exceeds the step-up height. The door has BS radius 1.975 m centered at Z=1.275 → top of BS at Z=3.25, way above step-up=0.6. If we detect "this obstacle is too tall to step over," fall back to wall-slide. Risk: might break stairs / ramps. Need apparatus to verify. ### Recommendation Option A first (~5 min, no code changes needed). If hypothesis confirmed, then Option D (with apparatus from Option B for regression testing). --- ## Files touched this session (cumulative) **Committed:** - `src/AcDream.Core/Physics/ShadowObjectRegistry.cs` (dedup fix) - `src/AcDream.App/Rendering/GameWindow.cs` (Task 7 wiring) - `tests/AcDream.Core.Tests/Physics/DoorSetupGfxObjInspectionTests.cs` (NEW) - `tests/AcDream.Core.Tests/Physics/DoorCollisionApparatusTests.cs` (NEW) - `docs/research/2026-05-24-door-dat-inspection-findings.md` (NEW) **Uncommitted (this doc + 2 file changes):** - `src/AcDream.Core/Physics/TransitionTypes.cs` (added `[bsp-test]` probe) - `tests/AcDream.Core.Tests/Physics/DoorCollisionApparatusTests.cs` (added grounded test scenario — fails for unrelated apparatus reasons but the probe wiring is sound) **Memory updated:** `feedback_dedup_keys_after_cardinality_change.md` --- ## Pickup prompt for next session ``` A6.P4 Task 7 shipped (RegisterLiveEntityCollision uses ShadowShapeBuilder + RegisterMultiPart) and the foundation fix (GetNearbyObjects dedup on full ShadowEntry instead of entityId). Production verification: center blocks, but off-center + inside-out still walk through closed doors. The multi-part registration is correct (verified by live probes); the remaining bug is downstream in BSPQuery Path 5's step-up logic. Read docs/research/2026-05-24-door-collision-task7-shipped-but-bug-remains.md State both altitudes: Currently working toward: M1.5 — Indoor world feels right Current phase: A6.P4 door collision — step-up misbehavior investigation. Multi-part registration shipped; step-up at thin tall obstacles is the remaining bug. Recommended first move: Option A from the findings doc — relaunch with ACDREAM_PROBE_BUILDING=1 + ACDREAM_DUMP_STEPUP=1, walk into a Holtburg cottage door off-center. The step-up dump will reveal whether DoStepUp is incorrectly succeeding for the door's BSP slab hit (the leading hypothesis: DoStepDown finds the same flat floor on the other side of the door, declaring step-up success). DO NOT re-investigate the multi-part registration or GetNearbyObjects dedup — both are confirmed working. Focus on the step-up path 5 behavior for thin tall obstacles. ```