CornerSlide_AlcoveEastToCottageNorth_ShouldBlock test: - Registers cottage GfxObj 0x01000A2B (contains north exterior walls) - Registers cell 0xA9B40150 BSP via dat-direct load (alcove walls) - Places sphere at (132.95, 16.8, 94) inside alcove near east wall - Walks sphere +Y 50 times at walk speed (0.05 m/tick) Result: sphere STAYS at (132.95, 16.8) for all 50 ticks with collision normal cn=(0.71, -0.71, 0) — the average of alcove east wall normal and cottage north wall normal at their meeting corner. The corner handling works correctly in the harness. So production's inside-out walkthrough is NOT a geometric or BSP collision-detection bug. The geometry exists, the collision detection fires symmetrically at corners. The discrepancy must be a STATE difference between harness and production: - Real walkable polygons with edges (harness uses big quad) - Real terrain (harness uses Z=-1000 stub) - Accumulated body state across many prior ticks (harness uses fresh) - Possibly cell ping-pong between 0x0150 and 0x0029 in production Cottage GfxObj wall polygons at the doorway area confirmed: - North exterior wall east of doorway: polys 0x0032, 0x0033 X=[133.5, 136.3], Y=17.10, Z=[94, 97], normal +Y - North exterior wall west of doorway: polys 0x0030, 0x0031, 0x0034, 0x0035 (X<131.6 various ranges) - Lintel polys above doorway: 0x0037, 0x0038, 0x003A, 0x003B at Z>96.5 Next-session moves (per handoff): 1. Replay captured tick 2586 (where sphere went from cell 0x0150 to 0x0029 at X=134.022, way past alcove east wall). Inspect engine behavior at exactly that tick's body state. 2. cdb attach to retail at Holtburg cottage doorway — verify whether retail also lets sphere walk through at off-center, OR blocks cleanly. If retail also allows walkthrough, this might be retail-faithful behavior we should accept. Updated handoff: docs/research/2026-05-25-door-bug-inside-out-geometry-gap.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
265 lines
13 KiB
Markdown
265 lines
13 KiB
Markdown
# Door bug — inside-out walkthrough: missing cottage exterior wall (geometry gap)
|
||
2026-05-25, continuation of door-collision investigation
|
||
|
||
## TL;DR
|
||
|
||
The inside-out walkthrough that persisted after the
|
||
`AddAllOutsideCells` fix is **NOT a collision-detection bug**. It's a
|
||
**collision-geometry GAP**: the cottage's north exterior wall east
|
||
(and presumably west) of the doorway opening doesn't exist in any
|
||
registered entity our engine knows about. The sphere walks past the
|
||
door slab on its east side, clears the doorway alcove cell's small
|
||
east wall (Y range [16.5, 17.1]), and then has nothing in front of it
|
||
in the collision representation — even though the VISUAL cottage has
|
||
a wall there.
|
||
|
||
## Apparatus diagnostics
|
||
|
||
Three new tests landed (in `DoorBugTrajectoryReplayTests`):
|
||
|
||
1. `Directional_OutsideIn_SouthApproach_BlocksAtSlabSouthFace` — sphere
|
||
south moving north blocks. PASSES.
|
||
2. `Directional_InsideOut_NorthApproach_BlocksAtSlabNorthFace` — sphere
|
||
north moving south blocks. PASSES.
|
||
3. `Geometric_DoorSlabAtSphereHeight_OverlapsInZ` — pins slab world Z
|
||
range = [94.139, 96.630]; sphere top at Z=95.20 IS within slab.
|
||
The slab is at sphere height — BSP collision is geometrically active.
|
||
4. `InsideOut_Tick3254_WithCottageWalls_ShouldBlock` — hypothesis test
|
||
adds cottage GfxObj 0x01000A2B. Result: cottage DID block but with
|
||
cn=(0,0,1) — a floor-cap response, NOT a wall response.
|
||
5. `Diagnostic_CottagePolys_NearWalkthroughPosition` — dumps cottage
|
||
polygons near sphere XY=(133.655, 17.59), any Z. **Result: ZERO
|
||
cottage polygons in that area.** The cottage GfxObj has no
|
||
geometry where the sphere walks through.
|
||
|
||
`DoorSetupGfxObjInspectionTests.HoltburgCottage_CellPortals_DatInspection`
|
||
extended to dump cell 0xA9B40150's 4 physics polys in world frame:
|
||
|
||
```
|
||
[0] sides=Landblock X=[131.600, 133.500] Y=[16.500, 17.100] Z=[94.000, 94.000] FLOOR
|
||
[1] sides=Landblock X=[131.600, 131.600] Y=[16.500, 17.100] Z=[94.000, 96.500] WEST WALL
|
||
[2] sides=Landblock X=[131.600, 133.500] Y=[16.500, 17.100] Z=[96.500, 96.500] CEILING
|
||
[3] sides=Landblock X=[133.500, 133.500] Y=[16.500, 17.100] Z=[94.000, 96.500] EAST WALL
|
||
```
|
||
|
||
Cell 0xA9B40150 is the **doorway alcove** — a small ~1.9m × 0.6m × 2.5m
|
||
volume between the cottage interior and the outdoor area. Its east wall
|
||
only extends Y=[16.5, 17.1]. **North of Y=17.1, no wall** in this cell.
|
||
|
||
The captured failing sphere at (133.655, 17.59) is 0.155m east of the
|
||
east wall AND 0.49m NORTH of the wall's Y range. The wall doesn't
|
||
reach the sphere.
|
||
|
||
## The collision-geometry gap
|
||
|
||
Visual representation (in-client):
|
||
- Cottage has a north exterior wall east and west of the doorway opening
|
||
- The wall extends Y > 17.1 (north of the alcove)
|
||
- User sees their character partially clipping into this wall
|
||
|
||
Collision representation (what we register):
|
||
- Cottage GfxObj 0x01000A2B: **0 polygons** in the area (133.655, 17.59, 94-95.20)
|
||
- Cell 0xA9B40150 (alcove): walls only at Y=[16.5, 17.1]
|
||
- Door slab: only spans X=[131.635, 133.560] — too narrow to cover the cottage opening
|
||
- Outdoor cell 0xA9B40029: outdoor cell, no walls
|
||
|
||
**Net: no entity has wall polygons at (133.655, Y > 17.1).** Sphere can
|
||
walk there freely.
|
||
|
||
## Verification in production capture
|
||
|
||
`door-fix-inout2.launch.log` shows:
|
||
- Cottage GfxObj `[bsp-test]` fires 425 times during inside-out walking
|
||
(so visibility is correct post-fix)
|
||
- Door slab `[bsp-test]` fires 245 times
|
||
- Captured tick 3254: sphere at (133.655, 17.590), target (133.549,
|
||
17.599). Result: position X=133.655 unchanged (blocked westward),
|
||
position Y=17.599 (moved north freely). cn=(+1, 0, 0) = slab east
|
||
face normal.
|
||
- The slab east face blocks WEST motion correctly. The sphere is FREE
|
||
to move north because no geometry covers (133.655, Y > 17.1).
|
||
|
||
## UPDATE (2026-05-25 evening): the wall EXISTS, but isn't blocking
|
||
|
||
Continued investigation with a wider polygon search in
|
||
`Diagnostic_CottagePolys_NearWalkthroughPosition` revealed the cottage
|
||
DOES have the missing wall:
|
||
|
||
```
|
||
poly 0x0032 n=(0.00, +1.00, 0.00) X=[133.50, 136.30] Y=[17.10, 17.10] Z=[94.00, 97.00]
|
||
poly 0x0033 n=(0.00, +1.00, 0.00) X=[133.50, 136.30] Y=[17.10, 17.10] Z=[94.00, 97.00]
|
||
```
|
||
|
||
(Plus symmetric polys 0x0030, 0x0031, 0x0034, 0x0035 covering X<131.6,
|
||
0x0037, 0x0038, 0x003A, 0x003B above the doorway lintel.)
|
||
|
||
The cottage's north exterior wall east of doorway IS at world (X=[133.5,
|
||
136.3], Y=17.10, Z=[94, 97]), normal +Y. **This wall SHOULD block sphere
|
||
at X=133.655 (sphere west edge at 133.175 ≤ wall X range, sphere south
|
||
edge at 17.110 ≤ wall Y).**
|
||
|
||
The new question: WHY isn't the wall blocking in production?
|
||
|
||
Sphere at world (133.655, 17.59) at the captured failing tick:
|
||
- Sphere XY: X=[133.175, 134.135], Y=[17.110, 18.070]
|
||
- Sphere overlaps wall in X (133.175..134.135 vs 133.5..136.3) by 0.635m
|
||
- Sphere south edge at Y=17.110 ALIGNS with wall at Y=17.10 (0.010m past)
|
||
- Sphere CENTER at Y=17.59 is 0.49m north of wall
|
||
- Distance from sphere center to wall plane: 0.49m. Sphere radius 0.48m.
|
||
- |dist| (0.49) ≈ radius (0.48). Sphere is JUST grazing the wall plane.
|
||
|
||
At this exact tick the sphere CENTER is 0.49m north of wall; sphere
|
||
south edge is 0.01m north of wall. Sphere is BARELY past the wall.
|
||
|
||
So this tick isn't where the walkthrough happens. The walkthrough is
|
||
EARLIER — when sphere center Y went from 17.58 (just past wall by reach)
|
||
to 17.59. The crossing must have allowed the sphere through.
|
||
|
||
OR: the sphere never actually crossed the wall — it walked around it.
|
||
Cottage wall east of doorway is X=[133.5, 136.3]. Sphere at X=133.655
|
||
is barely in the wall's X range. If sphere came from X < 133.5 (where
|
||
no east wall exists) and shifted east while sliding along the slab,
|
||
it could end up at X > 133.5 having NEVER crossed the wall plane.
|
||
|
||
Cell transit data confirms: tick 1549 outdoor→indoor at X=132.859,
|
||
tick 2586 indoor→outdoor at X=134.022 (way past wall east edge).
|
||
**The sphere reached X=134.022 inside cottage geometry somehow.**
|
||
|
||
Sphere fitting through doorway opening requires center X in
|
||
[131.6+0.48, 133.5-0.48] = [132.08, 133.02]. Tight. The user's
|
||
off-center test (~50cm east) puts sphere at edge of opening or
|
||
past. Sphere is sliding against the slab east face (cn=(+1,0,0))
|
||
which gradually pushes it east. Eventually sphere center exceeds
|
||
X=133.5 — past the cottage east wall's start. From that position,
|
||
sphere can move north WITHOUT crossing the wall plane (sphere
|
||
center already north of Y=17.10 from prior sliding).
|
||
|
||
**This may be retail-faithful behavior** OR a bug in sphere-vs-corner
|
||
collision. The corner where alcove east wall (X=133.5, Y=[16.5,17.1])
|
||
meets cottage north wall (X=[133.5,136.3], Y=17.10) is a degenerate
|
||
edge. Sphere sliding along the alcove east wall (moving +Y) reaches
|
||
the corner at (133.5, 17.10) — should encounter the cottage wall
|
||
and be stopped. If our engine handles the corner transition
|
||
incorrectly, sphere slides past.
|
||
|
||
## What's next (revised AGAIN — corner test PASSED, bug is state-related)
|
||
|
||
**Corner-slide hypothesis: FALSIFIED.** `CornerSlide_AlcoveEastToCottageNorth_ShouldBlock`
|
||
test runs cottage GfxObj + cell 0x0150 BSP both registered. Places
|
||
sphere at (132.95, 16.8, 94) inside alcove near east wall. Walks +Y
|
||
50 times at 0.05 m/tick. **Sphere stays put at (132.95, 16.8) for all
|
||
50 ticks with cn=(0.71, -0.71, 0)** — the corner normal between
|
||
alcove east wall and cottage north wall. **The corner handling works
|
||
correctly in the harness.**
|
||
|
||
So production's walkthrough is **a STATE difference**, not a geometric
|
||
or collision-detection bug. The harness's sphere can't reach
|
||
X=133.655 inside the cottage geometry. Production's sphere does
|
||
reach it somehow.
|
||
|
||
Differences between harness and production:
|
||
- Harness uses identity walkable polygon (big quad). Production uses
|
||
real cell walkable polys (small, with edges).
|
||
- Harness has stub landblock terrain at Z=-1000. Production has real
|
||
terrain.
|
||
- Harness uses fresh body each tick. Production has accumulated state
|
||
from many prior ticks (velocity, contact plane history, etc.).
|
||
- Harness uses sphereRadius=0.48 + sphereHeight=1.20 exactly. Production
|
||
matches but might have different stepUp / stepDown.
|
||
|
||
**Next-session apparatus**: replay the EXACT captured tick 2586's body
|
||
state through the corner-blocking test setup. Tick 2586 was where
|
||
sphere went from indoor cell 0x0150 to outdoor cell 0x0029 at
|
||
PrevPy=17.586, Py=17.586 (no Y motion) with X=134.022 (way past alcove
|
||
east wall). That tick is the smoking-gun "how did sphere get to X=134
|
||
inside alcove" event. Load its body state into the harness, replay
|
||
the call, see what the engine reports about getting to that position.
|
||
|
||
If the harness blocks (sphere can't reach X=134), then production has
|
||
state we're not capturing — probably accumulated push/depenetration
|
||
across many earlier ticks. If the harness reproduces sphere at X=134,
|
||
the bug is in the specific body state at that moment.
|
||
|
||
The cleanest path forward is **cdb attach to retail** as the original
|
||
handoff recommended. Inspect what retail does FRAME-BY-FRAME at the
|
||
same doorway approach. If retail walks the user inside cottage at
|
||
off-center approach EXACTLY like we do — the bug isn't a bug, and
|
||
we should accept the behavior. If retail blocks cleanly — diff
|
||
retail's body state evolution vs ours to find the divergence.
|
||
|
||
## OLD (superseded) "what's next" candidates
|
||
|
||
**Identify which entity SHOULD own the cottage's north exterior wall
|
||
east of the doorway.** Three candidates:
|
||
|
||
1. **A different cottage GfxObj.** Holtburg cottages might be
|
||
multi-piece (separate GfxObjs for wall sections, doorway frame, roof).
|
||
The cottage we have (0x01000A2B) might be one of multiple. Check
|
||
the landblock's static-entity list for other GfxObjs at the cottage
|
||
position via `[entity-source]` log + Setup file.
|
||
|
||
2. **A landblock-baked "stab"** (separate static entity registered at
|
||
spawn time). LandblockLoader produces these. Check `LandBlockInfo`
|
||
dat record for landblock 0xA9B4 — what other entities are at world
|
||
(~133, ~18)?
|
||
|
||
3. **The cottage GfxObj's drawing geometry is wider than its physics.**
|
||
If 0x01000A2B has `Polygons` (visual) at the wall location but no
|
||
`PhysicsPolygons` (collision), the visual is wider than the
|
||
collision. This is a dat-data fact — not fixable without retail
|
||
re-engineering of the dat.
|
||
|
||
For candidates 1-2, the fix is "register the missing entity." For 3,
|
||
the bug is dat-side (or retail accepts the same walkthrough we do).
|
||
|
||
**Cheapest next-step test:** add a method to
|
||
`DoorSetupGfxObjInspectionTests` that loads `LandBlockInfo` 0xA9B4FFFE
|
||
(landblock-baked statics) and prints every static at world XY in
|
||
[131, 135] × [16, 19]. The output will name what other GfxObjs/Setups
|
||
are registered at the cottage doorway — if any include the missing
|
||
wall, we know what to register additionally.
|
||
|
||
## Apparatus committed
|
||
|
||
- `tests/AcDream.Core.Tests/Physics/DoorBugTrajectoryReplayTests.cs`:
|
||
faithful door registration, directional collision tests, geometric
|
||
pin test, cottage GfxObj hypothesis test, cottage polygon dump.
|
||
- `tests/AcDream.Core.Tests/Physics/DoorSetupGfxObjInspectionTests.cs`:
|
||
HoltburgCottage_CellPortals_DatInspection extended with cell-poly
|
||
world-frame dump.
|
||
|
||
All tests under `DoorBugTrajectoryReplayTests` and the extended
|
||
`DoorSetupGfxObjInspectionTests.HoltburgCottage_CellPortals_DatInspection`
|
||
PASS (skip on CI when dat dir absent).
|
||
|
||
## Pickup prompt for next session
|
||
|
||
```
|
||
A6.P4 door inside-out walkthrough: identified as collision-geometry
|
||
gap, NOT collision-detection bug. The cottage's north exterior wall
|
||
east+west of the doorway opening isn't represented in any registered
|
||
entity. Sphere walks freely at (133.655, 17.59) — no wall to block.
|
||
|
||
Read docs/research/2026-05-25-door-bug-inside-out-geometry-gap.md
|
||
+ Diagnostic_CottagePolys_NearWalkthroughPosition test output
|
||
+ HoltburgCottage_CellPortals_DatInspection dump for cell 0x0150
|
||
|
||
State both altitudes:
|
||
Currently working toward: M1.5 — Indoor world feels right
|
||
Current phase: A6.P4 door bug — find missing cottage wall entity.
|
||
The fix isn't in BSP, cells, or AddAllOutsideCells
|
||
— those are correct. The collision geometry has a
|
||
gap. Need to identify which entity SHOULD own the
|
||
wall and register it.
|
||
|
||
First move: add a LandblockStatics_DatInspection test to
|
||
DoorSetupGfxObjInspectionTests that loads LandBlockInfo 0xA9B4FFFE
|
||
+ iterates StaticObjects. Print every entity at world XY in
|
||
[131, 135] x [16, 19] — name + setup id + position. Will reveal
|
||
what other entities (if any) live at the cottage doorway.
|
||
|
||
If a wall-bearing entity exists but we're not registering it: fix
|
||
the registration path. If nothing exists: the dat doesn't have the
|
||
wall, and this might be retail-faithful behavior we have to accept
|
||
(or compensate for by widening the door slab via gameplay layer).
|
||
```
|