fix(phys): A6.P4 door bug — AddAllOutsideCells coord convention + replay apparatus

CellTransit.AddAllOutsideCells assumed sphere coords were absolute world
coords (subtracting lbXf = 0xA9 * 192 = 32448 from the sphere position).
Production has used landblock-local coords since Phase A.1
(streaming-center landblock at world origin), so the subtraction
produced localX = -32316, gridX = -1346 → out-of-range → early return
→ ZERO outdoor cells added.

For outdoor primary cells the bug was masked by GetNearbyObjects's
radial sweep. For indoor primary cells (where #98 gates the outdoor
sweep), the door's outdoor cell 0xA9B40029 never reached
portalReachableCells, the door's BSP was never queried, and the player
walked through Holtburg cottage doors unimpeded.

Fix: AddAllOutsideCells treats worldSphereCenter as landblock-local
directly. Matches retail CLandCell::add_all_outside_cells which uses
the per-cell 6-byte landblock-relative position struct.

Existing CellTransitAddAllOutsideCellsTests + CellTransitFindCellSetTests
updated to use landblock-local sphere coords (they were the only callers
using the world-coord convention; production never did).

Apparatus shipped:
- DoorBugTrajectoryReplayTests — live-capture-driven replay harness
  that pinpointed the bug per-field at unit-test speed (<500ms iteration)
- AddAllOutsideCells_LandblockLocalSphere_AddsDoorOutdoorCell — direct
  unit test that demonstrates the fix
- FindTransitCellsSphere_IndoorExitPortal_AddsOutsideForCapturedSpherePos
  — verifies cell-portal traversal at the captured sphere position
- DoorSetupGfxObjInspectionTests.HoltburgCottage_CellPortals_DatInspection
  — dat-direct EnvCell + Environment.Cells + portal-poly inspector
- Fixture: tests/AcDream.Core.Tests/Fixtures/door-bug/live-capture.jsonl
  (tick 13558 walkthrough + tick 22760 outdoor block)

Visual verification (user-driven at Holtburg cottage door, ~50cm off-center):
- outside→inside RUN: now BLOCKS (was: walks through)
- outside→inside WALK: presumed blocks (not retested)
- inside→outside RUN: PARTIAL — body intersects door, sphere slides through
- inside→outside WALK: same partial behavior

The remaining inside→outside asymmetry is a SEPARATE bug in BSP
collision response for two-sided polygons. The [bsp-test] probe now
fires 245 times for the door entity from indoor (was 0 pre-fix) —
door IS being queried; the BSP polygon-level collision response is
the new bug. Handoff at
docs/research/2026-05-25-door-bug-partial-fix-shipped.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-05-25 07:53:34 +02:00
parent 6a2c432e5a
commit 28cd97be62
8 changed files with 1134 additions and 40 deletions

View file

@ -168,6 +168,35 @@ public static class CellTransit
/// adds the primary cell plus up to 3 neighbours when the radius
/// reaches a cell boundary.
/// </para>
///
/// <para>
/// <see cref="worldSphereCenter"/> is in the landblock-local coord
/// space the rest of the engine uses (X/Y in [0, 192]; landblock
/// world origin is at the streaming center, so all landblock-local
/// positions are also world positions for the player's landblock).
/// </para>
///
/// <para>
/// A6.P4 door fix (2026-05-24): pre-fix this function subtracted the
/// landblock's "absolute" world origin (lbX=0xA9*192=32448) from the
/// sphere position, which made sense only if sphere coords were the
/// absolute world position (32580). But production has used
/// landblock-local coords since Phase A.1 (streaming-center landblock
/// at world origin, so lbOffset for the center is (0,0); see
/// <c>GameWindow.BuildInteriorEntitiesForStreaming</c>'s lbOffset
/// formula). With landblock-local sphere coords, the old subtraction
/// produced <c>localX = 132.36 - 32448 = -32316</c> → <c>gridX = -1346</c>
/// → out-of-range → early return → ZERO outdoor cells added. For
/// indoor primary cells (where issue #98 gates the GetNearbyObjects
/// outdoor radial sweep) this meant the cottage door's outdoor cell
/// 0xA9B40029 never reached <c>portalReachableCells</c>, the door's
/// BSP was never queried, and the player walked through unimpeded —
/// the user-reported Holtburg-door walkthrough bug. The fix:
/// treat <c>worldSphereCenter</c> as landblock-local directly, no
/// landblock-world-origin subtraction. This matches retail's
/// <c>CLandCell::add_all_outside_cells</c> which uses the per-cell
/// 6-byte position struct (landblock-relative).
/// </para>
/// </summary>
public static void AddAllOutsideCells(
Vector3 worldSphereCenter,
@ -179,10 +208,8 @@ public static class CellTransit
uint lbPrefix = currentCellId & 0xFFFF0000u;
float lbXf = ((lbPrefix >> 24) & 0xFFu) * 192f;
float lbYf = ((lbPrefix >> 16) & 0xFFu) * 192f;
float localX = worldSphereCenter.X - lbXf;
float localY = worldSphereCenter.Y - lbYf;
float localX = worldSphereCenter.X;
float localY = worldSphereCenter.Y;
float cellLocalX = localX % CellSize;
float cellLocalY = localY % CellSize;