Followed up the geometry-gap diagnosis with a wider polygon search. Result: the cottage's north exterior wall east of doorway DOES exist in cottage GfxObj 0x01000A2B (polys 0x0032, 0x0033) at world X=[133.5, 136.3], Y=17.10, Z=[94, 97], normal +Y. Symmetric polys cover the west side and above the doorway lintel. The wall SHOULD block sphere at X=133.655 (sphere west edge at 133.175 overlaps wall X range; sphere south edge at 17.11 aligns with wall at Y=17.10). New hypothesis: the bug is sphere-vs-corner collision at the meeting point of cell 0x0150's east wall (X=133.5, Y=[16.5, 17.1]) and the cottage's north exterior wall (X=[133.5, 136.3], Y=17.10). Cell transit data shows sphere going from X=132.859 entering alcove to X=134.022 leaving alcove — sphere reached X=134.022 INSIDE cottage geometry somehow. The sliding along the slab east face (cn=(+1,0,0) in captured tick 3254) gradually pushes sphere east. Eventually it shifts past X=133.5 — the corner where alcove east wall meets cottage north wall. The corner-handling in our BSP collision may incorrectly let the sphere slide past, or the alcove cell's east wall and cottage GfxObj's north wall don't compose correctly at the corner. Diagnostic apparatus extensions: - HoltburgLandblockStatics_DatInspection: dumps LandBlockInfo for landblock 0xA9B4. Shows 114 stabs + 12 buildings. The cottage IS Building[6] with modelId=0x01000A2B (the GfxObj we already loaded). - Diagnostic_CottagePolys_NearWalkthroughPosition: widened search reveals the cottage's full north exterior wall geometry. - HoltburgCottage_CellPortals_DatInspection: extended with cell PhysicsPolygon world-frame dump (already in prior commit). Full updated handoff: docs/research/2026-05-25-door-bug-inside-out-geometry-gap.md Next-session move: add a "sphere walks +Y from inside alcove at X near 133" test. If harness slides past the corner like production, investigate BSPQuery's sphere-vs-edge case. If harness blocks at corner, the bug is elsewhere (cell 0x0150 BSP not queried, or cottage GfxObj BSP traversal misses the wall poly). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
242 lines
11 KiB
Markdown
242 lines
11 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)
|
||
|
||
**Investigate sphere-vs-corner collision behavior** at the alcove
|
||
east wall → cottage north wall meeting point at world (133.5, 17.10).
|
||
|
||
Apparatus to write:
|
||
- Load cottage GfxObj + cell 0x0150 BSP into harness
|
||
- Place sphere at (133.0, 16.8, 94) (inside alcove, near east wall)
|
||
- Walk sphere +Y in small increments
|
||
- Expected: sphere stops at Y=17.10-0.48 = 16.62 if east wall blocks
|
||
OR: sphere slides along corner staying in alcove
|
||
- Captured: sphere ends up at (133.655, 17.59) — sliding past corner
|
||
|
||
If harness reproduces "sphere slides past corner", the bug is in
|
||
the engine's corner-handling. Read BSPQuery for the sphere-vs-edge
|
||
case. Retail oracle: CTransition::find_obj_collisions corner
|
||
handling at acclient_2013_pseudo_c.txt.
|
||
|
||
If harness BLOCKS at corner (no sliding past), the bug is something
|
||
else — maybe cell 0x0150 BSP isn't being queried in production from
|
||
some sphere position.
|
||
|
||
## 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).
|
||
```
|