Portals only worked once per session: teleporting OUT of a dungeon
mis-rooted the player into the SOURCE dungeon's coordinate frame, so every
move was sent dungeon-framed and ACE rejected it ("failed transition") —
the player couldn't move, never reached a portal, and the world wouldn't
re-render (only skybox).
Root cause: acdream's streaming-relative frame recenters on teleport, but
resident physics landblocks keep their load-time world-offset. After
recentering onto the outdoor destination, the collapsed source dungeon
(offset 0,0 as the prior center) and the destination (offset 0,0 as the
new center) overlap, and the Z-agnostic outdoor cell-snap returns the
dungeon for both the arrival placement and every per-frame resolve.
Fix (server-authoritative teleport placement):
- Drop the stale source center landblock from physics at the teleport
recenter (GameWindow.OnLivePositionUpdated) so the resolve falls through
to the server position (Resolve NO-LANDBLOCK verbatim) until the
destination streams in.
- Place outdoor teleports immediately (TeleportArrivalRules) — holding is
futile because streaming does not progress during a PortalSpace hold.
- Clear a dangling CellGraph.CurrCell when its landblock is removed
(PhysicsEngine.RemoveLandblock) — otherwise the dungeon-streaming gate
keeps streaming collapsed onto the gone dungeon (only skybox renders).
Keeps DungeonStreamingGate (gate suppression during the hold). Indoor
(dungeon-entry) placement is unchanged (cell-keyed, IsSpawnCellReady).
User-verified: in->out->re-enter works repeatedly, no ACE errors, world
renders. Remaining facets (server objects + own avatar not rendering after
a teleport-out) are entity render/lifecycle — split to #138.
Registers AP-36 + AD-2 updated. New: DungeonStreamingGate (+4 tests),
TeleportArrivalRules (+4 tests). Build + 2727 tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
52 lines
2.1 KiB
C#
52 lines
2.1 KiB
C#
using AcDream.App.World;
|
|
using Xunit;
|
|
|
|
namespace AcDream.App.Tests.World;
|
|
|
|
/// <summary>
|
|
/// 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()
|
|
{
|
|
Assert.Equal(ArrivalReadiness.Impossible,
|
|
TeleportArrivalRules.Decide(claimUnhydratable: true, indoor: false, indoorCellReady: false));
|
|
// …even for an indoor claim.
|
|
Assert.Equal(ArrivalReadiness.Impossible,
|
|
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));
|
|
}
|
|
|
|
[Fact]
|
|
public void Indoor_CellNotReady_HoldsNotReady()
|
|
{
|
|
Assert.Equal(ArrivalReadiness.NotReady,
|
|
TeleportArrivalRules.Decide(claimUnhydratable: false, indoor: true, indoorCellReady: false));
|
|
}
|
|
|
|
[Fact]
|
|
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));
|
|
}
|
|
}
|