revert(teleport): drop the Slice 2 outdoor readiness-gate hold
User-tested: the Slice 2 'hold outdoor until landblock loaded' gate made EVERY outdoor teleport a ~10 s freeze, because the destination landblock does NOT load fast during the hold (lbs=0 the whole time — the #138 streaming gap + _datLock starvation from the CreateObject flood). The hold was band-aiding a broken/slow foundation rather than fixing it, and it never actually prevented the #145 edge cascade anyway (it force-snapped onto NO-LANDBLOCK after the timeout regardless). Reverts ad8c24e..c880973 to the pre-Slice-2 state (00ef47e): outdoor places immediately again (fast teleports). The genuine bug found along the way — IsLandblockLoaded queried the wrong key form (& 0xFFFF0000 vs the stored | 0xFFFF) — is preserved in the history (c880973) and will be re-applied when we re-introduce a proper hold ON A FIXED FOUNDATION. Decision (user, 2026-06-21): fix the foundation FIRST — fast/complete streaming during teleport (#138), the post-teleport lost-collision bug, and the FPS leak (Work item C) — then revisit the teleport-flow animation. Slice 1 (the pure TeleportAnimSequencer) stays in (dormant, unwired, harmless). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c8809735f3
commit
dd2eb8b39d
6 changed files with 43 additions and 195 deletions
|
|
@ -4,63 +4,49 @@ using Xunit;
|
|||
namespace AcDream.App.Tests.World;
|
||||
|
||||
/// <summary>
|
||||
/// Full truth-table for TeleportArrivalRules.Decide after the §3.4 readiness-gate change.
|
||||
/// The teleport-arrival readiness decision (#145/#138).
|
||||
/// <list type="bullet">
|
||||
/// <item>Unhydratable claim → place immediately via the caller's safety-net (Impossible).</item>
|
||||
/// <item>Indoor (sealed dungeon / building interior) → hold until the EnvCell floor hydrates.</item>
|
||||
/// <item>Outdoor → place IMMEDIATELY on the server-authoritative position. Holding is futile —
|
||||
/// streaming does not progress while the player is held in PortalSpace, so the
|
||||
/// destination can't stream in during a hold. The stale source landblock is dropped at
|
||||
/// recenter (GameWindow.OnLivePositionUpdated), so the resolve falls through to the
|
||||
/// server cell (Resolve NO-LANDBLOCK verbatim) until the destination streams in.</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public class TeleportArrivalRulesTests
|
||||
{
|
||||
[Fact]
|
||||
public void Unhydratable_IsImpossible_Outdoor()
|
||||
public void Unhydratable_IsImpossible()
|
||||
{
|
||||
Assert.Equal(ArrivalReadiness.Impossible,
|
||||
TeleportArrivalRules.Decide(
|
||||
claimUnhydratable: true, indoor: false,
|
||||
indoorCellReady: false, outdoorReady: false));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Unhydratable_IsImpossible_Indoor()
|
||||
{
|
||||
TeleportArrivalRules.Decide(claimUnhydratable: true, indoor: false, indoorCellReady: false));
|
||||
// …even for an indoor claim.
|
||||
Assert.Equal(ArrivalReadiness.Impossible,
|
||||
TeleportArrivalRules.Decide(
|
||||
claimUnhydratable: true, indoor: true,
|
||||
indoorCellReady: true, outdoorReady: false));
|
||||
TeleportArrivalRules.Decide(claimUnhydratable: true, indoor: true, indoorCellReady: false));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Indoor_CellReady_IsReady()
|
||||
{
|
||||
Assert.Equal(ArrivalReadiness.Ready,
|
||||
TeleportArrivalRules.Decide(
|
||||
claimUnhydratable: false, indoor: true,
|
||||
indoorCellReady: true, outdoorReady: false));
|
||||
TeleportArrivalRules.Decide(claimUnhydratable: false, indoor: true, indoorCellReady: true));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Indoor_CellNotReady_IsNotReady()
|
||||
public void Indoor_CellNotReady_HoldsNotReady()
|
||||
{
|
||||
Assert.Equal(ArrivalReadiness.NotReady,
|
||||
TeleportArrivalRules.Decide(
|
||||
claimUnhydratable: false, indoor: true,
|
||||
indoorCellReady: false, outdoorReady: false));
|
||||
TeleportArrivalRules.Decide(claimUnhydratable: false, indoor: true, indoorCellReady: false));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Outdoor_LandblockLoaded_IsReady()
|
||||
public void Outdoor_PlacesImmediately()
|
||||
{
|
||||
// #145: never hold an outdoor arrival — place at once on the server position. The
|
||||
// indoorCellReady flag is irrelevant for an outdoor destination.
|
||||
Assert.Equal(ArrivalReadiness.Ready,
|
||||
TeleportArrivalRules.Decide(
|
||||
claimUnhydratable: false, indoor: false,
|
||||
indoorCellReady: false, outdoorReady: true));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Outdoor_LandblockNotLoaded_IsNotReady()
|
||||
{
|
||||
// §3.4: the #145 residual — outdoor arrival on a not-yet-streamed landblock
|
||||
// must hold. Previously returned Ready immediately.
|
||||
Assert.Equal(ArrivalReadiness.NotReady,
|
||||
TeleportArrivalRules.Decide(
|
||||
claimUnhydratable: false, indoor: false,
|
||||
indoorCellReady: false, outdoorReady: false));
|
||||
TeleportArrivalRules.Decide(claimUnhydratable: false, indoor: false, indoorCellReady: false));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,90 +0,0 @@
|
|||
using AcDream.Core.Physics;
|
||||
using Xunit;
|
||||
|
||||
namespace AcDream.Core.Tests.Physics;
|
||||
|
||||
public class PhysicsEngineIsLandblockLoadedTests
|
||||
{
|
||||
private static TerrainSurface MakeFlatTerrain()
|
||||
{
|
||||
var heights = new byte[81];
|
||||
Array.Fill(heights, (byte)50);
|
||||
var heightTable = new float[256];
|
||||
for (int i = 0; i < 256; i++) heightTable[i] = i * 1f;
|
||||
return new TerrainSurface(heights, heightTable);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsLandblockLoaded_AbsentId_ReturnsFalse()
|
||||
{
|
||||
var engine = new PhysicsEngine();
|
||||
Assert.False(engine.IsLandblockLoaded(0xA9B4FFFFu));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsLandblockLoaded_AddedId_ReturnsTrue()
|
||||
{
|
||||
var engine = new PhysicsEngine();
|
||||
engine.AddLandblock(0xA9B4FFFFu, MakeFlatTerrain(),
|
||||
Array.Empty<CellSurface>(), Array.Empty<PortalPlane>(),
|
||||
worldOffsetX: 0f, worldOffsetY: 0f);
|
||||
|
||||
Assert.True(engine.IsLandblockLoaded(0xA9B4FFFFu));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsLandblockLoaded_AnotherIdAbsent_ReturnsFalse()
|
||||
{
|
||||
var engine = new PhysicsEngine();
|
||||
engine.AddLandblock(0xA9B4FFFFu, MakeFlatTerrain(),
|
||||
Array.Empty<CellSurface>(), Array.Empty<PortalPlane>(),
|
||||
worldOffsetX: 0f, worldOffsetY: 0f);
|
||||
|
||||
Assert.False(engine.IsLandblockLoaded(0xDEADBEEFu));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsLandblockLoaded_AfterRemove_ReturnsFalse()
|
||||
{
|
||||
var engine = new PhysicsEngine();
|
||||
engine.AddLandblock(0xA9B4FFFFu, MakeFlatTerrain(),
|
||||
Array.Empty<CellSurface>(), Array.Empty<PortalPlane>(),
|
||||
worldOffsetX: 0f, worldOffsetY: 0f);
|
||||
engine.RemoveLandblock(0xA9B4FFFFu);
|
||||
|
||||
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