fix(teleport D): cell-march preserves seed landblock id when no resident LB (no more lbX=0 outbound)

BuildCellSetAndPickContaining discarded the bool from TryGetTerrainOrigin — when
the current landblock's terrain hadn't been applied yet (priority-apply in flight
after a teleport or dungeon exit), blockOrigin was silently set to (0,0,0). The
AdjustToOutside/GetOutsideLcoord math treated world-frame sphere coordinates as
block-local and marched the cell one landblock per tick in the direction of movement
until lbX or lbY underflowed to 0x00. ACE rejected every subsequent move as a
failed transition.

Fix: honor the bool return. When terrain is unregistered for an OUTDOOR seed
(low < 0x0100), return currentCellId verbatim — "no block-local frame →
preserve". This mirrors the NO-LANDBLOCK verbatim contract in PhysicsEngine.Resolve
and is correct: the cell stays last-known-correct until terrain registers.
Indoor seeds are explicitly excluded (blockOrigin is never consumed by the indoor
pick path; outdoorPickAllowed=false for indoor seeds).

Reproduce + verify via CellMarchLandblockPreservationTests (two new FAILING-before
tests: WestEdge and SouthEdge with empty cache, no anchor → lbX/lbY preserved).
TeleportFarTownRunawayTests updated: no-anchor path now also preserves (pre-fix it
marched south to 0x59; post-fix returns currentCell unchanged).
CellTransitFindCellSetTests, Issue112MembershipTests, PhysicsEngineTests: added
RegisterTerrain for the streaming-center block (in production it is always resident
before outdoor resolves run; tests that used blockOrigin=(0,0,0) as an implicit
fallback now register the block explicitly). All 1567 tests pass.

Divergence AD-30 added to retail-divergence-register.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-22 13:38:48 +02:00
parent aba882cec6
commit 02f4be72c0
7 changed files with 144 additions and 15 deletions

View file

@ -459,7 +459,13 @@ public class PhysicsEngineTests
// FindObjCollisions early-returns when DataCache is null. An empty
// cache is enough for cylinder objects; only BSP objects look up
// entries inside.
engine.DataCache = new PhysicsDataCache();
// #145 D: register terrain for 0xA9B4 at (0,0,0) so TryGetTerrainOrigin
// succeeds for the outdoor seed cell (0xA9B40039). In production the
// streaming-center landblock is always resident before outdoor resolves run;
// we replicate that invariant here by registering a flat dummy terrain.
var freshCache = new PhysicsDataCache();
freshCache.CellGraph.RegisterTerrain(0xA9B4FFFFu, new TerrainSurface(FlatHeightmap(50), LinearHeightTable()), Vector3.Zero);
engine.DataCache = freshCache;
const uint movingEntityId = 0xDEADBEEFu;
var bodyPos = new Vector3(96f, 96f, 50f);