docs(handoff): door collision per-part BSP session handoff (2026-05-24)
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>
This commit is contained in:
parent
1498697bc5
commit
c89df8e4c0
1 changed files with 265 additions and 0 deletions
265
docs/research/2026-05-24-door-collision-session-handoff.md
Normal file
265
docs/research/2026-05-24-door-collision-session-handoff.md
Normal file
|
|
@ -0,0 +1,265 @@
|
||||||
|
# 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.
|
||||||
|
```
|
||||||
Loading…
Add table
Add a link
Reference in a new issue