fix(teleport #145): IsLandblockLoaded key mismatch — outdoor gate was permanently NotReady

The Slice 2 outdoor readiness gate queried IsLandblockLoaded(destCell &
0xFFFF0000) = e.g. 0x7D640000, but streaming stores landblocks under the
EncodeLandblockId form (low 16 = 0xFFFF), e.g. 0x7D64FFFF. The raw
ContainsKey never matched, so the outdoor teleport gate could NEVER flip
Ready and every outdoor arrival ran to the 600-frame (~10 s) timeout and
force-placed. The cascade was still prevented (the timeout force-place lands
cleanly), but the gate did no work — the 10 s freeze the apparatus showed
was this bug, NOT the #138 streaming stall I first suspected.

Root cause found via the apparatus re-test (3-agent investigation
wf_8b67a9d1-35c, all high-confidence) + verified against StreamingRegion.cs:99
(EncodeLandblockId | 0xFFFF), PhysicsEngine.cs:79 (stores as-is),
GameWindow.cs:5530 (queries & 0xFFFF0000).

Fix: IsLandblockLoaded normalizes its arg to the canonical 0xFFFF landblock
key, so the prefix form, any contained cell id, and the dat-id form all
resolve. Added the regression test the original Slice 2 test missed (it had
checked the same 0xFFFF form it added; the real caller passes the 0x..0000
form). Red on the prefix/cell forms before the fix, green after. 9/9.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-21 20:52:23 +02:00
parent 589c7cfb57
commit c8809735f3
2 changed files with 48 additions and 5 deletions

View file

@ -32,13 +32,23 @@ public sealed class PhysicsEngine
public int LandblockCount => _landblocks.Count;
/// <summary>
/// Returns true iff a landblock with this id has been registered via
/// <see cref="AddLandblock"/>. Because <see cref="AddLandblock"/> registers
/// terrain + collision cells + portals atomically, ContainsKey implies the
/// collision mesh is present and resolvable.
/// Returns true iff the landblock CONTAINING the given id has been registered via
/// <see cref="AddLandblock"/>. Because <see cref="AddLandblock"/> registers terrain +
/// collision cells + portals atomically, a hit implies the collision mesh is present
/// and resolvable.
/// <para>
/// The argument may be any id within the landblock — a full cell id (low 16 = cell
/// index), the landblock-prefix form (low 16 = 0x0000, as the teleport gate passes via
/// <c>destCell &amp; 0xFFFF0000</c>), or the dat-id storage form. The low 16 bits are
/// normalized to the canonical landblock key (<c>0xFFFF</c>) that streaming stores under
/// (<see cref="AcDream.App.Streaming.StreamingRegion.EncodeLandblockId"/> ORs in 0xFFFF),
/// so all three forms resolve. The earlier raw <c>ContainsKey(id &amp; 0xFFFF0000)</c>
/// never matched the 0xFFFF-terminated stored key, leaving the outdoor teleport gate
/// permanently NotReady (2026-06-21 #145 apparatus re-test).
/// </para>
/// </summary>
public bool IsLandblockLoaded(uint landblockId) =>
_landblocks.ContainsKey(landblockId);
_landblocks.ContainsKey((landblockId & 0xFFFF0000u) | 0xFFFFu);
/// <summary>
/// Cell-based spatial index for static object collision.