acdream/docs/research/2026-05-24-door-collision-task7-shipped-but-bug-remains.md
Erik 163a1f0d35 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>
2026-05-24 19:22:45 +02:00

10 KiB
Raw Blame History

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.


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.