# Door collision per-part BSP session — handoff **Date:** 2026-05-24 (long session, multiple phases) **Branch:** `claude/strange-albattani-3fc83c` **Worktree:** `C:\Users\erikn\source\repos\acdream\.claude\worktrees\strange-albattani-3fc83c` This handoff documents an A6.P4-driven session that: 1. Shipped A6.P4 slice 1 (real cleanup, didn't close #99) 2. Investigated why doors don't block (apparatus-first) 3. Brainstormed + speced a per-part BSP collision design 4. Shipped most of the implementation (Tasks 1-6 of 10) 5. Discovered Task 7's per-part BSP doesn't actually fix the door bug 6. Reverted Task 7 and paused for further investigation --- ## TL;DR **Shipped (real commits):** - `b49ed90` — A6.P4 slice 1: drop the `< 0x0100u` filter in `ShadowObjectRegistry.GetNearbyObjects`'s portalReachableCells loop, rename `indoorCellIds` → `portalReachableCells`. Real cleanup; the `FindCellSet`-already-includes-outdoor-cells discovery means doors at building thresholds should be reachable from indoor primary spheres via the exit-portal logic. But the user-visible #99 close was wrongly claimed in the commit message — see below. - `d71ceab` — design spec: per-part BSP for server-spawned entities. - `8d4f14c` — 10-task implementation plan. - `ab4278c` — Task 1: ShadowShape record. - `7f5c287` — Task 2: ShadowShapeBuilder.FromSetup + 7 unit tests. - `1454eab` — Task 3: ShadowEntry adds LocalPosition + LocalRotation. - `fca0a13` — Task 4: ShadowObjectRegistry.RegisterMultiPart + 6 tests + Deregister clears `_entityShapes` (Task 6 folded in). - `d5ffb03` — Task 5: UpdatePosition recomposes multi-part transforms via `_entityShapes`. - `3e5dc8c` — Task 6 regression test: stray UpdatePosition after Deregister is no-op. - `1498697` — `[cyl-test]` diagnostic probe (broadly useful). **Reverted (Task 7 staged, then `git restore`):** the `RegisterLiveEntityCollision` refactor at `GameWindow.cs:3076`. Reverted because visual verification showed the per-part BSP shape didn't actually block the door — only the small Cylinder did, and even that only at dead-center approach. **Still pending:** Tasks 7-10 in the plan + the real fix for door collision. --- ## What we learned (apparatus-first findings) ### Door Setup 0x020019FF shape inventory (live dump captured 2026-05-24) ``` [door-setup-dump] setupId=0x020019FF setupRadius=0.141 setupHeight=0.200 cylSpheres=0 spheres=1 parts=3 placementFrames=1 stepUp=0.090 stepDown=0.090 [door-setup-dump] sphere[0] r=0.100 origin=(0.000,0.000,0.018) [door-setup-dump] part[0] gfxObj=0x010044B5 [door-setup-dump] part[1] gfxObj=0x010044B6 [door-setup-dump] part[2] gfxObj=0x010044B6 ``` ### Per-shape registration (post-Task-7-experiment) With `ShadowShapeBuilder.FromSetup` running over Setup 0x020019FF in the live launch, doors registered 2 shadows each: 1. `type=Cylinder radius=0.100 height=0.200 localPos=(0,0,0.018)` — from the Sphere converted to short Cylinder. 2. `type=BSP gfxObj=0x010044B5 radius=2.000 localPos=(-0.006,0.125,1.275)` — from part 0 (the frame). The other two parts (`0x010044B6` x2) have `BSP=null` → skipped. ### Collision behavior (visual verified by user, 2026-05-24) | Scenario | Result | |---|---| | Cellar climb (#98 regression check) | ✅ Works | | Door from outside, dead center | ⚠️ Partial — only the small Cylinder blocks; player stops at the center | | Door from outside, ~50 cm off-center | ❌ Pass through | | Door from outside (Use → swing) | ✅ Swing animation works, door opens | | Indoor furniture (#91 regression check) | ✅ Works | | Outdoor exterior wall (regression check) | ✅ Works | | Door from inside walking out | ❌ Pass through | ### Diagnostic evidence In 188K+ resolve lines from the launch: - `Door 0xF4249 : 85 cyl-tests, 13 resolve hits attributed` - `Door 0xF424F : 227 cyl-tests, 16 resolve hits attributed` - **Zero `[resolve-bldg]` attributions for any door** Conclusion: the per-part BSP at `0x010044B5` produces NO collision hits. Either: 1. The PhysicsBSP at that GfxObj has no collision-bearing polygons (only visual polys), OR 2. Our world-to-part-local sphere transform is wrong, OR 3. The broadphase rejects it (unlikely with radius=2.0 default). --- ## Why this differs from M1 visual verification on 2026-05-13 The user remembers doors blocking on the M1 demo verification. That demo was "open the inn door" — clicking + watching the swing animation. The walking-through-an-open-door part was not deliberately tested. The closed-door blocking was probably observed accidentally when the user walked directly at a center-of-doorway cylinder; the 14 cm cylinder is just wide enough to catch a sphere at exactly the centerline. Today's careful off-center test exposed the gap. So nothing regressed since 2026-05-13. The bug has been latent. Our investigation just exposed it. --- ## Investigation gap to close before the next implementation attempt The per-part BSP design IS retail-faithful in shape (matches `CPhysicsObj::FindObjCollisions` → `CPartArray::FindObjCollisions` → `CPhysicsPart::find_obj_collisions` → `CGfxObj::find_obj_collisions`). But it didn't surface a working blocker for the cottage doors. Three hypotheses, ranked by likelihood: ### Hypothesis A (most likely): Part 0x010044B5 has no collision-bearing PhysicsBSP polygons The Setup defines visual parts. Some parts (especially decorative hardware) may have a PhysicsBSP that's just the visual mesh's bounding volume, with no walls or threshold polygons. The door's collision might genuinely be just the small Cylinder by retail design, and retail gets full doorway blocking from the **building's BSP** having a narrow gap exactly the size of the door's Cylinder (~28 cm × 28 cm). **How to verify:** Dump `0x010044B5`'s PhysicsBSP polygons via `ACDREAM_DUMP_GFXOBJS=0x010044B5`. Inspect the polygons. If they're just an axis-aligned bounding box matching the visual mesh, no useful collision data exists at the part level. ### Hypothesis B: Building BSP has a wide doorway gap that retail's tiny cylinder doesn't fill A retail building (e.g., cottage interior 0x020XXXXX) has its walls as BSP polygons. The doorway is a gap. If the gap is ~2 m wide (visual opening), the 28 cm cylinder doesn't span it — even retail wouldn't block. **How to verify:** Open RenderDoc on retail (or our client) and inspect the cottage interior GfxObj BSP at the doorway. Measure the gap. If it's narrow (~30 cm), the small cylinder fills it. If wide (~2 m), the cylinder is decorative and the actual blocker must come from elsewhere. ### Hypothesis C: Retail uses a different collision mechanism entirely Doors might use Setup.Radius / Setup.Height (the bounding cylinder dimensions, 0.141 × 0.200 — slightly larger than our Sphere-derived 0.100 × 0.200) AS THE PRIMARY BLOCKER, not the Sphere. Or retail overrides shape selection for `ItemType==Door` specifically. **How to verify:** Attach cdb to a live retail client at a cottage doorway, set a breakpoint on `CPhysicsObj::FindObjCollisions` for the door's PhysicsObj, observe which shape branch fires. --- ## Recommended next-session approach Per the project's "apparatus-first for physics divergences" rule (`feedback_apparatus_for_physics_bugs.md`): 1. **Stop coding.** Don't try another fix without evidence. 2. **Dump 0x010044B5's PhysicsBSP** via `ACDREAM_DUMP_GFXOBJS=0x010044B5`. If it has zero floor-touching polygons → Hypothesis A confirmed. 3. **Attach cdb to retail** at a cottage doorway. Trace which shapes block the player. See `project_retail_debugger.md` for the toolchain. 4. **Cross-reference ACE source** for Door collision (if any) — search `references/ACE/Source/ACE.Server/Physics/` for door handling. 5. **Re-brainstorm** with the new evidence. The Task 1-6 infrastructure stays (it's correctly modeling retail's CPhysicsObj-per-entity with parts iterated for collision). Only the SHAPES we register need to change. The infrastructure investment was not wasted. The architecture is right. We just registered the wrong shapes from the door setup. --- ## What's in the tree right now ``` $ git log --oneline -15 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 1454eab feat(phys): ShadowEntry adds LocalPosition + LocalRotation 7f5c287 feat(phys): ShadowShapeBuilder.FromSetup ab4278c feat(phys): add ShadowShape record (no callers yet) 8d4f14c docs(phys): implementation plan — per-part BSP for server-spawned entities d71ceab docs(phys): design spec — per-part BSP collision for server-spawned entities b49ed90 feat(phys): A6.P4 slice 1 — portal-reachable cellSet includes outdoor cells b3ce505 fix(phys): A6.P3 #98 — gate outdoor shadow radial sweep on indoor primary cell b55ae83 docs: A6.P3 #98 resolution + A6.P4 design + #99/#100 filed 3e3cd77 docs(handoff): A6.P4 pickup handoff — full session-resume artifact ``` All 49+ tests pass: - 24 ShadowObjectRegistryTests - 7 ShadowShapeBuilderTests - 8 ShadowObjectRegistryMultiPartTests - 11 CellarUpTrajectoryReplayTests Pre-existing 6-8 baseline static-state-leakage failures in the broader Physics suite are unchanged from prior sessions. **No-commit state:** working tree is clean. `git status --short` shows only untracked investigation logs (`a6-issue98-*.log`, `launch-task7-*.log`, etc. — these accumulate from launches and don't get committed). --- ## #99 status: still open The A6.P4 slice 1 commit message claimed "Closes #99" but the visual verification today proves that's premature. Slice 1 did a real cleanup (removed a misleading filter) but didn't fully address the user-visible door-block bug. Update `docs/ISSUES.md` accordingly (issue #99 remains OPEN; the per-part BSP architecture is NEW infrastructure built today that will support the eventual fix once we identify the right shapes). --- ## Pickup prompt for next session ``` Door collision still doesn't fully block in M1.5 Holtburg. Per-part BSP infrastructure shipped 2026-05-24 (Tasks 1-6 of A6.P4 plan), but the specific shapes we register from door setup 0x020019FF don't catch the player. Need apparatus-first investigation: Read docs/research/2026-05-24-door-collision-session-handoff.md (this doc — recent session handoff) State both altitudes: Currently working toward: M1.5 — Indoor world feels right Current phase: A6.P4 — investigation phase to find the right door collision shapes; per-part BSP infrastructure already shipped; need to verify Hypothesis A/B/C before any more implementation First moves (in order): 1. Dump GfxObj 0x010044B5's PhysicsBSP via ACDREAM_DUMP_GFXOBJS. Does it have collision-bearing polygons or just visual? 2. If yes → debug the per-part transform (likely Hypothesis B/C wrong); if no → confirm Hypothesis A and pivot strategy. 3. Either way, attach cdb to retail at a cottage doorway to see what retail actually blocks with. DO NOT speculate-and-fix again. The session 2026-05-24 already burned a Task 7 attempt on a hypothesis that turned out wrong. The 6 committed implementation tasks (Tasks 1-6) are correct and stay. Only Tasks 7-10 of the plan need to change once we know the right shapes. ```