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>
247 lines
10 KiB
Markdown
247 lines
10 KiB
Markdown
# 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.
|
||
```
|