Long session that shipped A6.P4 infrastructure (Tasks 1-6 of the per-part BSP plan) but discovered the specific shapes we register from door setup 0x020019FF don't catch the player. Per-part BSP at 0x010044B5 produced ZERO collision attributions in 188K+ resolve lines despite player walking at doors. Cylinder still blocks center-only. Task 7 (refactor RegisterLiveEntityCollision) was implemented and visually tested, but reverted because the new per-part BSP shape didn't actually fix the door bug. The infrastructure stays — it's correctly modeling retail's CPhysicsObj-with-parts model — but the shapes we feed it need to be re-investigated apparatus-first. Three hypotheses ranked: (A) part BSPs are visual-only, no collision polys; (B) building BSP has a wide doorway gap our tiny cylinder doesn't fill; (C) retail uses Setup.Radius/Height directly. Next- session move: dump GfxObj 0x010044B5's PhysicsBSP first, then cdb retail at a doorway. Recommends: stop speculation, build apparatus, decide fix from evidence. #99 stays OPEN. Slice 1's "Closes #99" claim was premature; the real close requires the per-part BSP work + correct shape identification. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
11 KiB
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:
- Shipped A6.P4 slice 1 (real cleanup, didn't close #99)
- Investigated why doors don't block (apparatus-first)
- Brainstormed + speced a per-part BSP collision design
- Shipped most of the implementation (Tasks 1-6 of 10)
- Discovered Task 7's per-part BSP doesn't actually fix the door bug
- Reverted Task 7 and paused for further investigation
TL;DR
Shipped (real commits):
b49ed90— A6.P4 slice 1: drop the< 0x0100ufilter inShadowObjectRegistry.GetNearbyObjects's portalReachableCells loop, renameindoorCellIds→portalReachableCells. Real cleanup; theFindCellSet-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:
type=Cylinder radius=0.100 height=0.200 localPos=(0,0,0.018)— from the Sphere converted to short Cylinder.type=BSP gfxObj=0x010044B5 radius=2.000 localPos=(-0.006,0.125,1.275)— from part 0 (the frame). The other two parts (0x010044B6x2) haveBSP=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 attributedDoor 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:
- The PhysicsBSP at that GfxObj has no collision-bearing polygons (only visual polys), OR
- Our world-to-part-local sphere transform is wrong, OR
- 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):
- Stop coding. Don't try another fix without evidence.
- Dump 0x010044B5's PhysicsBSP via
ACDREAM_DUMP_GFXOBJS=0x010044B5. If it has zero floor-touching polygons → Hypothesis A confirmed. - Attach cdb to retail at a cottage doorway. Trace which shapes
block the player. See
project_retail_debugger.mdfor the toolchain. - Cross-reference ACE source for Door collision (if any) — search
references/ACE/Source/ACE.Server/Physics/for door handling. - 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.