diag(phys): [bsp-test] probe + grounded apparatus test + handoff

Visual verification of Task 7 ship: doors block at dead-center (the
small Cylinder catches) but the BSP slab doesn't catch off-center
or inside-walking-out approaches. Probe-instrumented live capture
proves multi-part registration is correct — every door spawns with
shapes=cyl1+bsp1, and the BSP part is visited 135 times for a single
door at player approaches as close as 0.42 m, with cacheHit=True.
But zero [resolve-bldg] attributions for the BSP shape.

Three artifacts added:

1. TransitionTypes.cs — new [bsp-test] probe in the BSP collision
   dispatch, fires BEFORE the cache lookup. Mirrors [cyl-test] on
   the Cylinder branch. Distinguishes "cache miss → silent skip"
   from "queried but no hit" (the latter doesn't show up in
   [resolve-bldg] which only fires on attributed hits).

2. DoorCollisionApparatusTests.cs — new grounded test
   (Apparatus_Grounded_50cmOffCenter_*) attempts to reproduce the
   production bug via a seeded PhysicsBody (Contact + OnWalkable
   + ContactPlane + WalkablePolygon). Currently doesn't reproduce
   because the apparatus's stub-terrain + synthetic-floor setup
   diverges from production's real Holtburg geometry. Captured as
   "documents-the-bug" — flip the assertion shape when the fix
   lands.

3. docs/research/2026-05-24-door-collision-task7-shipped-but-bug-remains.md
   — full session handoff. Identifies the remaining bug as a Path 5
   (Contact branch + StepSphereUp) misbehavior at thin tall
   obstacles, not in the multi-part registration we just shipped.
   Leading hypothesis: DoStepUp's downward probe finds the same
   flat floor on the OTHER side of the door (Holtburg cottages have
   no Z change between exterior and interior floor), declares
   step-up success, BSP collision returns OK, sphere walks through.
   Recommended next move: relaunch with ACDREAM_DUMP_STEPUP=1 to
   verify the hypothesis.

What this commit DOES NOT do: fix the remaining step-up bug. The
A6.P4 multi-part registration foundation is correct and stays.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-05-24 19:22:45 +02:00
parent ca9341c2cb
commit 163a1f0d35
3 changed files with 360 additions and 0 deletions

View file

@ -0,0 +1,247 @@
# 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<uint>` (entityId) → `HashSet<ShadowEntry>`. 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.
```