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>
13 KiB
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):
Directional_OutsideIn_SouthApproach_BlocksAtSlabSouthFace— sphere south moving north blocks. PASSES.Directional_InsideOut_NorthApproach_BlocksAtSlabNorthFace— sphere north moving south blocks. PASSES.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.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.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:
-
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. -
A landblock-baked "stab" (separate static entity registered at spawn time). LandblockLoader produces these. Check
LandBlockInfodat record for landblock 0xA9B4 — what other entities are at world (~133, ~18)? -
The cottage GfxObj's drawing geometry is wider than its physics. If 0x01000A2B has
Polygons(visual) at the wall location but noPhysicsPolygons(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).