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:
parent
589c7cfb57
commit
c8809735f3
2 changed files with 48 additions and 5 deletions
|
|
@ -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 & 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 & 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.
|
||||
|
|
|
|||
|
|
@ -54,4 +54,37 @@ public class PhysicsEngineIsLandblockLoadedTests
|
|||
|
||||
Assert.False(engine.IsLandblockLoaded(0xA9B4FFFFu));
|
||||
}
|
||||
|
||||
// Regression for the Slice 2 key-mismatch bug (#145 apparatus re-test, 2026-06-21):
|
||||
// landblocks are stored under the EncodeLandblockId form (low 16 = 0xFFFF), but the
|
||||
// teleport gate (TeleportArrivalReadiness) queries with (destCell & 0xFFFF0000) (low
|
||||
// 16 = 0x0000). A raw ContainsKey never matched, so the outdoor gate was permanently
|
||||
// NotReady and every outdoor teleport timed out at 10 s. IsLandblockLoaded must
|
||||
// normalize so ANY id within the landblock resolves to the stored entry.
|
||||
[Theory]
|
||||
[InlineData(0xA9B40000u)] // landblock-prefix form — exactly what the gate passes (destCell & 0xFFFF0000)
|
||||
[InlineData(0xA9B4000Du)] // an outdoor cell id within the landblock
|
||||
[InlineData(0xA9B40143u)] // an indoor cell id within the landblock
|
||||
[InlineData(0xA9B4FFFFu)] // the dat-id storage form
|
||||
public void IsLandblockLoaded_NormalizesLow16_FindsLandblockForAnyContainedId(uint queryId)
|
||||
{
|
||||
var engine = new PhysicsEngine();
|
||||
engine.AddLandblock(0xA9B4FFFFu, MakeFlatTerrain(),
|
||||
Array.Empty<CellSurface>(), Array.Empty<PortalPlane>(),
|
||||
worldOffsetX: 0f, worldOffsetY: 0f);
|
||||
|
||||
Assert.True(engine.IsLandblockLoaded(queryId));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsLandblockLoaded_DifferentLandblockPrefix_ReturnsFalse()
|
||||
{
|
||||
var engine = new PhysicsEngine();
|
||||
engine.AddLandblock(0xA9B4FFFFu, MakeFlatTerrain(),
|
||||
Array.Empty<CellSurface>(), Array.Empty<PortalPlane>(),
|
||||
worldOffsetX: 0f, worldOffsetY: 0f);
|
||||
|
||||
// A different landblock prefix must NOT match, even in the 0x..0000 query form.
|
||||
Assert.False(engine.IsLandblockLoaded(0xA9B50000u));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue