refactor(runtime): own teleport destination correlation

This commit is contained in:
Erik 2026-07-26 17:52:34 +02:00
parent 38b3773cb9
commit 6a063a27d4
23 changed files with 1209 additions and 487 deletions

View file

@ -29,7 +29,9 @@ public sealed class WorldSceneRendererTests
{
var transit = new RuntimeWorldTransitState();
var availability = new WorldGenerationAvailabilityState(transit);
transit.BeginReveal(RuntimePortalKind.Portal, 0x11340021u);
RuntimeWorldTransitTestDriver.BeginPortal(
transit,
0x11340021u);
var rig = new Rig(
portalVisible: false,
waitingForLogin: false,

View file

@ -65,7 +65,13 @@ public sealed class CurrentGameRuntimeAdapterTests
};
harness.Objects.AddOrUpdate(item);
harness.Chat.OnSystemMessage("runtime parity", 0x1Au);
harness.WorldReveal.Begin(RuntimePortalKind.Portal, 0x12340001u);
RuntimeWorldTransitTestDriver.AcceptPortalDestination(
harness.WorldTransit,
0x12340001u);
Assert.True(harness.WorldReveal.TryBeginPortal(
1,
0x12340001u,
out _));
WorldRevealReadinessSnapshot readiness =
harness.WorldReveal.PrepareAndEvaluate(0x12340001u);
Assert.True(readiness.IsReady);

View file

@ -22,10 +22,18 @@ public sealed class RuntimeWorldTransitOwnershipTests
appRoot,
"Streaming",
"WorldRevealLifecycleTelemetry.cs")));
Assert.False(File.Exists(Path.Combine(
appRoot,
"Streaming",
"TeleportTransitCoordinator.cs")));
Assert.DoesNotContain(
"class WorldRevealLifecycleTelemetry",
app,
StringComparison.Ordinal);
Assert.DoesNotContain(
"class TeleportTransitCoordinator",
app,
StringComparison.Ordinal);
Assert.DoesNotContain(
"record struct WorldRevealLifecycleSnapshot",
app,
@ -74,6 +82,19 @@ public sealed class RuntimeWorldTransitOwnershipTests
field => field.Name is "_activeGeneration"
or "_worldSimulationReleased"
or "_lifecycle");
var teleportFields = typeof(LocalPlayerTeleportController)
.GetFields(
System.Reflection.BindingFlags.Instance
| System.Reflection.BindingFlags.NonPublic);
Assert.Single(
teleportFields,
field => field.FieldType == typeof(RuntimeWorldTransitState));
Assert.DoesNotContain(
teleportFields,
field => field.Name.Contains(
"acceptedDestination",
StringComparison.OrdinalIgnoreCase));
}
private static string FindRepositoryRoot()

View file

@ -0,0 +1,53 @@
using System.Numerics;
using AcDream.Core.Physics;
using AcDream.Runtime;
using AcDream.Runtime.World;
namespace AcDream.App.Tests;
internal static class RuntimeWorldTransitTestDriver
{
public static long BeginPortal(
RuntimeWorldTransitState transit,
uint destinationCell,
ushort sequence = 1)
{
AcceptPortalDestination(transit, destinationCell, sequence);
Assert.True(transit.TryBeginPortalReveal(
sequence,
destinationCell,
out long generation));
return generation;
}
public static void AcceptPortalDestination(
RuntimeWorldTransitState transit,
uint destinationCell,
ushort sequence = 1)
{
Assert.True(transit.TryQueueTeleportStart(sequence));
Assert.True(transit.ActivateQueuedTeleport());
Assert.True(transit.OfferTeleportDestination(
new RuntimeTeleportDestination(
EntityGuid: 0x50000001u,
InstanceSequence: 1,
PositionSequence: 1,
TeleportSequence: sequence,
ForcePositionSequence: 1,
Position: new Position(
destinationCell,
Vector3.Zero,
Quaternion.Identity)),
teleportTimestampAdvanced: true));
}
public static bool MaterializePortal(
RuntimeWorldTransitState transit,
long generation,
uint destinationCell,
ushort sequence = 1) =>
transit.AcknowledgePortalMaterialized(
generation,
sequence,
destinationCell);
}

View file

@ -622,7 +622,7 @@ public sealed class GpuWorldStateVisibilityTests
var transit = new RuntimeWorldTransitState();
var availability = new WorldGenerationAvailabilityState(transit);
long generation =
transit.BeginReveal(RuntimePortalKind.Portal, cell);
RuntimeWorldTransitTestDriver.BeginPortal(transit, cell);
var state = new GpuWorldState(availability: availability);
state.AddLandblock(new LoadedLandblock(
landblock,
@ -658,7 +658,10 @@ public sealed class GpuWorldStateVisibilityTests
IsRenderNeighborhoodReady: true,
AreCompositeTexturesReady: true,
IsCollisionReady: true)));
Assert.True(transit.AcknowledgeMaterialized(generation, cell));
Assert.True(RuntimeWorldTransitTestDriver.MaterializePortal(
transit,
generation,
cell));
Assert.True(transit.Complete(generation));
Assert.True(state.IsLiveEntityVisible(projectionKey));
Assert.True(record.IsSpatiallyVisible);
@ -705,7 +708,9 @@ public sealed class GpuWorldStateVisibilityTests
{
var transit = new RuntimeWorldTransitState();
var availability = new WorldGenerationAvailabilityState(transit);
transit.BeginReveal(RuntimePortalKind.Portal, 0x10100001u);
RuntimeWorldTransitTestDriver.BeginPortal(
transit,
0x10100001u);
var centerStatic = Entity(1, 0u);
var centerLive = Entity(2, 0x70000001u);
var neighborStatic = Entity(3, 0u);

View file

@ -38,7 +38,7 @@ public sealed class LocalPlayerTeleportControllerTests
public void DestinationBeforeStart_IsReplayedOnceAfterPresentationActivates()
{
var harness = new Harness();
WorldSession.EntityPositionUpdate destination = Position(
RuntimeTeleportDestination destination = Position(
cellId: 0x20210001u,
teleportSequence: 7,
x: 12f,
@ -231,6 +231,24 @@ public sealed class LocalPlayerTeleportControllerTests
Assert.Equal(1, harness.Reveal.PortalMaterializationCount);
}
[Fact]
public void Place_RechecksExactRuntimeLifetimeBeforeMutatingPresentation()
{
var harness = new Harness();
harness.Controller.OnTeleportStarted(81);
harness.Controller.OfferDestination(
Position(0x20210001u, 81, 4f, 5f, 6f),
teleportTimestampAdvanced: true);
long generation = harness.Reveal.Snapshot.Generation;
Assert.True(harness.Transit.Cancel(generation));
harness.Presentation.Enqueue(TeleportAnimEvent.Place);
harness.Controller.Tick(0.016f);
Assert.Equal(default, harness.Placement.Position);
Assert.Equal(0, harness.Reveal.PortalMaterializationCount);
}
[Fact]
public void LoginComplete_EntersWorldThenSendsThenCompletesAndResets()
{
@ -565,29 +583,21 @@ public sealed class LocalPlayerTeleportControllerTests
return int.MaxValue;
}
private static WorldSession.EntityPositionUpdate Position(
private static RuntimeTeleportDestination Position(
uint cellId,
ushort teleportSequence,
float x,
float y,
float z) => new(
0x50000001u,
new CreateObject.ServerPosition(
EntityGuid: 0x50000001u,
InstanceSequence: 1,
PositionSequence: 1,
TeleportSequence: teleportSequence,
ForcePositionSequence: 1,
Position: new Position(
cellId,
x,
y,
z,
1f,
0f,
0f,
0f),
null,
null,
true,
1,
1,
teleportSequence,
1);
new Vector3(x, y, z),
Quaternion.Identity));
private static WorldSession.EntitySpawn Spawn(uint guid, uint cell) => new(
Guid: guid,
@ -651,6 +661,7 @@ public sealed class LocalPlayerTeleportControllerTests
public readonly FakePlacement Placement;
public readonly FakeSession Session;
public readonly FakePresentation Presentation;
public readonly RuntimeWorldTransitState Transit;
public readonly WorldRevealCoordinator Reveal;
public readonly LocalPlayerTeleportController Controller;
@ -666,8 +677,9 @@ public sealed class LocalPlayerTeleportControllerTests
Placement = new FakePlacement(order);
Session = new FakeSession(order);
Presentation = new FakePresentation(order);
Transit = new RuntimeWorldTransitState(order.Add);
Reveal = new WorldRevealCoordinator(
new RuntimeWorldTransitState(order.Add),
Transit,
isRenderNeighborhoodReady: (_, _) => worldReady,
isSpawnCellReady: _ => worldReady,
isTerrainNeighborhoodReady: (_, _) => worldReady,
@ -681,6 +693,7 @@ public sealed class LocalPlayerTeleportControllerTests
Input,
Mode,
Streaming,
Transit,
Reveal,
Placement,
Session,
@ -830,7 +843,7 @@ public sealed class LocalPlayerTeleportControllerTests
public List<uint> Starts { get; } = [];
public void OnTeleportStarted(uint sequence) => Starts.Add(sequence);
public void OfferDestination(
WorldSession.EntityPositionUpdate update,
RuntimeTeleportDestination destination,
bool teleportTimestampAdvanced)
{
}

View file

@ -0,0 +1,50 @@
using AcDream.App.Streaming;
using AcDream.Core.Net;
using AcDream.Core.Net.Messages;
using AcDream.Runtime;
namespace AcDream.App.Tests.Streaming;
public sealed class RuntimeTeleportDestinationAdapterTests
{
[Fact]
public void AcceptedPosition_PreservesExactIdentityCellFrameAndSequences()
{
var update = new WorldSession.EntityPositionUpdate(
Guid: 0x50000001u,
Position: new CreateObject.ServerPosition(
LandblockId: 0x30310123u,
PositionX: -12.5f,
PositionY: 8.25f,
PositionZ: 91.75f,
RotationW: 0.5f,
RotationX: 0.25f,
RotationY: -0.125f,
RotationZ: 0.75f),
Velocity: new System.Numerics.Vector3(1f, 2f, 3f),
PlacementId: 0x65u,
IsGrounded: false,
InstanceSequence: 0x1234,
PositionSequence: 0x2345,
TeleportSequence: 0x3456,
ForcePositionSequence: 0x4567);
RuntimeTeleportDestination destination =
RuntimeTeleportDestinationAdapter.FromAcceptedPosition(update);
Assert.Equal(update.Guid, destination.EntityGuid);
Assert.Equal(update.InstanceSequence, destination.InstanceSequence);
Assert.Equal(update.PositionSequence, destination.PositionSequence);
Assert.Equal(update.TeleportSequence, destination.TeleportSequence);
Assert.Equal(
update.ForcePositionSequence,
destination.ForcePositionSequence);
Assert.Equal(update.Position.LandblockId, destination.CellId);
Assert.Equal(
new System.Numerics.Vector3(-12.5f, 8.25f, 91.75f),
destination.Position.Frame.Origin);
Assert.Equal(
new System.Numerics.Quaternion(0.25f, -0.125f, 0.75f, 0.5f),
destination.Position.Frame.Orientation);
}
}

View file

@ -1,133 +0,0 @@
using AcDream.App.Streaming;
namespace AcDream.App.Tests.Streaming;
public sealed class TeleportTransitCoordinatorTests
{
[Fact]
public void ReplacementRejectsDelayedPriorDestinationAndAcceptsCurrentAdvance()
{
var transit = new TeleportTransitCoordinator<int>();
Assert.True(transit.QueueStart(10));
Assert.False(transit.Activate(out _));
transit.EndActive();
Assert.True(transit.QueueStart(11));
Assert.False(transit.Activate(out _));
Assert.False(transit.OfferDestination(10, true, 100, out _));
Assert.True(transit.OfferDestination(11, true, 110, out int accepted));
Assert.Equal(110, accepted);
}
[Fact]
public void PositionBeforeF751IsBufferedAndEqualFollowupCannotReplaceIt()
{
var transit = new TeleportTransitCoordinator<int>();
Assert.False(transit.OfferDestination(11, true, 110, out _));
Assert.True(transit.QueueStart(11));
Assert.True(transit.Activate(out int buffered));
Assert.Equal(110, buffered);
Assert.False(transit.OfferDestination(11, false, 111, out _));
}
[Fact]
public void InactiveOrdinaryPositionIsNeverBuffered()
{
var transit = new TeleportTransitCoordinator<int>();
Assert.False(transit.OfferDestination(11, false, 110, out _));
Assert.True(transit.QueueStart(11));
Assert.False(transit.Activate(out _));
}
[Fact]
public void DuplicateStartIsRejectedButSequenceWrapIsFresh()
{
var transit = new TeleportTransitCoordinator<int>();
Assert.True(transit.QueueStart(ushort.MaxValue));
Assert.False(transit.Activate(out _));
transit.EndActive();
Assert.False(transit.CanBegin(ushort.MaxValue));
Assert.False(transit.QueueStart(ushort.MaxValue));
Assert.True(transit.CanBegin(0));
Assert.True(transit.QueueStart(0));
Assert.True(transit.HasPendingStart);
}
[Fact]
public void ExactHalfRangeUsesRetailLowerStampTieBreak()
{
var transit = new TeleportTransitCoordinator<int>();
Assert.True(transit.QueueStart(0x8000));
Assert.False(transit.Activate(out _));
transit.EndActive();
Assert.True(transit.CanBegin(0x0000));
Assert.True(transit.QueueStart(0x0000));
}
[Fact]
public void ClearSessionAllowsSameSequenceInNewSession()
{
var transit = new TeleportTransitCoordinator<int>();
Assert.True(transit.QueueStart(7));
Assert.False(transit.Activate(out _));
transit.ClearSession();
Assert.False(transit.IsActive);
Assert.True(transit.CanBegin(7));
}
[Fact]
public void PendingStartAcceptsEqualPositionUntilPresentationCanActivate()
{
var transit = new TeleportTransitCoordinator<int>();
Assert.True(transit.QueueStart(11));
Assert.False(transit.OfferDestination(11, false, 110, out _));
Assert.True(transit.Activate(out int buffered));
Assert.Equal(110, buffered);
Assert.True(transit.IsActive);
}
[Fact]
public void AdvancingPositionPrunesSkippedDestination()
{
var transit = new TeleportTransitCoordinator<int>();
Assert.False(transit.OfferDestination(11, true, 110, out _));
Assert.False(transit.OfferDestination(12, true, 120, out _));
Assert.True(transit.QueueStart(12));
Assert.True(transit.Activate(out int buffered));
Assert.Equal(120, buffered);
}
[Fact]
public void ObsoleteDestinationCannotSurviveSequenceWrap()
{
var transit = new TeleportTransitCoordinator<int>();
Assert.False(transit.OfferDestination(ushort.MaxValue, true, 999, out _));
Assert.False(transit.OfferDestination(0, true, 0, out _));
Assert.True(transit.QueueStart(0));
Assert.True(transit.Activate(out int wrapped));
Assert.Equal(0, wrapped);
transit.EndActive();
// Advance in legal retail-newer hops until 0xFFFF is current again.
Assert.True(transit.QueueStart(0x7FFF));
Assert.False(transit.Activate(out _));
transit.EndActive();
Assert.True(transit.QueueStart(0xFFFE));
Assert.False(transit.Activate(out _));
transit.EndActive();
Assert.True(transit.QueueStart(ushort.MaxValue));
Assert.False(transit.Activate(out _));
}
}

View file

@ -37,7 +37,9 @@ public sealed class WorldGenerationQuiescenceTests
Assert.Single(world.LandblockBounds);
long generation =
transit.BeginReveal(RuntimePortalKind.Portal, DestinationCell);
RuntimeWorldTransitTestDriver.BeginPortal(
transit,
DestinationCell);
world.CopyLiveEntitiesNearLandblock(LandblockId, 0, nearby);
Assert.False(availability.IsWorldAvailable);
@ -53,7 +55,8 @@ public sealed class WorldGenerationQuiescenceTests
Assert.False(availability.IsWorldAvailable);
Assert.True(transit.AcknowledgeDestinationReadiness(
Ready(generation, DestinationCell)));
Assert.True(transit.AcknowledgeMaterialized(
Assert.True(RuntimeWorldTransitTestDriver.MaterializePortal(
transit,
generation,
DestinationCell));
Assert.True(transit.Complete(generation));
@ -87,12 +90,14 @@ public sealed class WorldGenerationQuiescenceTests
WorldGenerationQuiescenceEdge firstEdge =
quiescence.CaptureBegin();
long first =
transit.BeginReveal(RuntimePortalKind.Login, DestinationCell);
transit.BeginLoginReveal(DestinationCell);
quiescence.CommitBegin(firstEdge);
WorldGenerationQuiescenceEdge secondEdge =
quiescence.CaptureBegin();
long second =
transit.BeginReveal(RuntimePortalKind.Portal, DestinationCell);
RuntimeWorldTransitTestDriver.BeginPortal(
transit,
DestinationCell);
quiescence.CommitBegin(secondEdge);
Assert.False(transit.Complete(first));
@ -104,7 +109,8 @@ public sealed class WorldGenerationQuiescenceTests
Assert.True(transit.AcknowledgeDestinationReadiness(
Ready(second, DestinationCell)));
Assert.True(transit.AcknowledgeMaterialized(
Assert.True(RuntimeWorldTransitTestDriver.MaterializePortal(
transit,
second,
DestinationCell));
Assert.True(transit.Complete(second));
@ -130,7 +136,9 @@ public sealed class WorldGenerationQuiescenceTests
audio: null);
WorldGenerationQuiescenceEdge edge = quiescence.CaptureBegin();
transit.BeginReveal(RuntimePortalKind.Portal, DestinationCell);
RuntimeWorldTransitTestDriver.BeginPortal(
transit,
DestinationCell);
quiescence.CommitBegin(edge);
Assert.Equal(inventoryGuid, selection.SelectedObjectId);

View file

@ -1,5 +1,7 @@
using System.Numerics;
using AcDream.App.Streaming;
using AcDream.App.World;
using AcDream.Core.Physics;
using AcDream.Core.Selection;
using AcDream.Runtime;
using AcDream.Runtime.World;
@ -10,6 +12,7 @@ public sealed class WorldRevealCoordinatorTests
{
private sealed class State
{
public RuntimeWorldTransitState Transit { get; private set; } = null!;
public bool RenderReady { get; set; }
public bool CollisionReady { get; set; }
public bool CompositesReady { get; set; }
@ -24,6 +27,7 @@ public sealed class WorldRevealCoordinatorTests
{
transit ??= new RuntimeWorldTransitState(
logs is null ? null : new Action<string>(logs.Add));
Transit = transit;
return new WorldRevealCoordinator(
transit,
isRenderNeighborhoodReady: (_, _) => RenderReady,
@ -48,7 +52,7 @@ public sealed class WorldRevealCoordinatorTests
var state = new State { CompositesReady = true };
WorldRevealCoordinator coordinator = state.Build();
long generation = coordinator.Begin(RuntimePortalKind.Login, 0x11340021u);
long generation = coordinator.BeginLogin(0x11340021u);
Assert.Equal(1, generation);
Assert.Equal(1, state.InvalidateCount);
@ -66,7 +70,7 @@ public sealed class WorldRevealCoordinatorTests
CollisionReady = true,
};
WorldRevealCoordinator coordinator = state.Build();
coordinator.Begin(RuntimePortalKind.Login, outdoorCell);
coordinator.BeginLogin(outdoorCell);
WorldRevealReadinessSnapshot first = coordinator.PrepareAndEvaluate(outdoorCell);
state.CompositesReady = true;
@ -91,12 +95,16 @@ public sealed class WorldRevealCoordinatorTests
CompositesReady = true,
};
WorldRevealCoordinator coordinator = state.Build(logs);
coordinator.Begin(RuntimePortalKind.Portal, outdoorCell);
long generation = BeginPortal(
coordinator,
state.Transit,
outdoorCell,
sequence: 1);
state.CompositesReady = true;
Assert.True(coordinator.Evaluate(outdoorCell).IsReady);
coordinator.ObserveMaterialized();
coordinator.ObserveMaterialized();
coordinator.ObserveMaterialized(generation, 1, outdoorCell);
coordinator.ObserveMaterialized(generation, 1, outdoorCell);
coordinator.ObserveWorldViewportVisible();
coordinator.Complete();
@ -108,6 +116,40 @@ public sealed class WorldRevealCoordinatorTests
Assert.Single(logs, line => line.Contains("event=materialized", StringComparison.Ordinal));
}
[Fact]
public void PortalBegin_WrongSequenceOrCellCannotAcquireHostResources()
{
const uint destinationCell = 0x3032001Cu;
var state = new State { CompositesReady = true };
var scheduler = new RecordingDestinationScheduler();
WorldRevealCoordinator coordinator = state.Build(streaming: scheduler);
RuntimeWorldTransitTestDriver.AcceptPortalDestination(
state.Transit,
destinationCell,
sequence: 7);
Assert.False(coordinator.TryBeginPortal(
8,
destinationCell,
out _));
Assert.False(coordinator.TryBeginPortal(
7,
0x11340021u,
out _));
Assert.Empty(scheduler.Begins);
Assert.Equal(0, state.InvalidateCount);
Assert.Equal(0, coordinator.Snapshot.Generation);
Assert.True(coordinator.TryBeginPortal(
7,
destinationCell,
out long generation));
Assert.Equal(
(generation, destinationCell, 1),
Assert.Single(scheduler.Begins));
Assert.Equal(1, state.InvalidateCount);
}
[Fact]
public void Cancel_PreventsLateDestinationWorkFromMutatingLifecycle()
{
@ -118,11 +160,18 @@ public sealed class WorldRevealCoordinatorTests
CompositesReady = true,
};
WorldRevealCoordinator coordinator = state.Build();
coordinator.Begin(RuntimePortalKind.Portal, 0x11340021u);
long generation = BeginPortal(
coordinator,
state.Transit,
0x11340021u,
sequence: 1);
coordinator.Cancel();
coordinator.PrepareAndEvaluate(0x11340021u);
coordinator.ObserveMaterialized();
coordinator.ObserveMaterialized(
generation,
1,
0x11340021u);
coordinator.ObserveWorldViewportVisible();
coordinator.Complete();
@ -155,14 +204,22 @@ public sealed class WorldRevealCoordinatorTests
isSpawnClaimUnhydratable: _ => false,
quiescence: quiescence);
Assert.Equal(1, coordinator.Begin(RuntimePortalKind.Login, 0x11340021u));
Assert.Equal(2, coordinator.Begin(RuntimePortalKind.Portal, 0x11340021u));
Assert.Equal(1, coordinator.BeginLogin(0x11340021u));
long generation = BeginPortal(
coordinator,
transit,
0x11340021u,
sequence: 1);
Assert.Equal(2, generation);
Assert.False(availability.IsWorldAvailable);
Assert.Equal(2, availability.QuiescedGeneration);
Assert.Equal(1, audio.SuspendCalls);
Assert.True(coordinator.Evaluate(0x11340021u).IsReady);
coordinator.ObserveMaterialized();
coordinator.ObserveMaterialized(
generation,
1,
0x11340021u);
coordinator.Complete();
Assert.True(availability.IsWorldAvailable);
@ -193,14 +250,21 @@ public sealed class WorldRevealCoordinatorTests
isSpawnClaimUnhydratable: _ => false,
quiescence: quiescence,
streaming: scheduler);
long generation = coordinator.Begin(
RuntimePortalKind.Portal,
0x3032001Cu);
const uint destinationCell = 0x3032001Cu;
const ushort teleportSequence = 1;
long generation = BeginPortal(
coordinator,
transit,
destinationCell,
teleportSequence);
Assert.False(availability.IsWorldAvailable);
Assert.True(coordinator.Evaluate(0x3032001Cu).IsReady);
Assert.True(coordinator.Evaluate(destinationCell).IsReady);
coordinator.ObserveMaterialized();
coordinator.ObserveMaterialized(
generation,
teleportSequence,
destinationCell);
Assert.True(availability.IsWorldAvailable);
Assert.False(coordinator.Snapshot.WorldViewportObserved);
@ -259,13 +323,21 @@ public sealed class WorldRevealCoordinatorTests
() => calls.Add("spatial")),
availability);
coordinator.Begin(RuntimePortalKind.Portal, 0x3032001Cu);
const uint destinationCell = 0x3032001Cu;
long generation = BeginPortal(
coordinator,
transit,
destinationCell,
sequence: 1);
frame.Tick(1f / 60f);
Assert.Equal(["network", "commands"], calls);
calls.Clear();
Assert.True(coordinator.Evaluate(0x3032001Cu).IsReady);
coordinator.ObserveMaterialized();
Assert.True(coordinator.Evaluate(destinationCell).IsReady);
coordinator.ObserveMaterialized(
generation,
1,
destinationCell);
frame.Tick(1f / 60f);
Assert.Equal(["objects", "network", "commands", "spatial"], calls);
@ -297,9 +369,11 @@ public sealed class WorldRevealCoordinatorTests
isSpawnClaimUnhydratable: _ => false,
quiescence: quiescence,
streaming: scheduler);
long generation = coordinator.Begin(
RuntimePortalKind.Portal,
0x3032001Cu);
long generation = BeginPortal(
coordinator,
transit,
0x3032001Cu,
sequence: 1);
coordinator.ResetSession();
@ -317,12 +391,17 @@ public sealed class WorldRevealCoordinatorTests
var scheduler = new RecordingDestinationScheduler();
WorldRevealCoordinator coordinator = state.Build(streaming: scheduler);
long first = coordinator.Begin(
RuntimePortalKind.Portal,
0x11340021u);
long second = coordinator.Begin(
RuntimePortalKind.Portal,
0x20210123u);
long first = BeginPortal(
coordinator,
state.Transit,
0x11340021u,
sequence: 1);
state.Transit.EndTeleport();
long second = BeginPortal(
coordinator,
state.Transit,
0x20210123u,
sequence: 2);
Assert.Equal(
[
@ -342,12 +421,17 @@ public sealed class WorldRevealCoordinatorTests
WorldRevealCoordinator coordinator =
state.Build(renderResources: resources);
long first = coordinator.Begin(
RuntimePortalKind.Portal,
0x11340021u);
long second = coordinator.Begin(
RuntimePortalKind.Portal,
0x20210123u);
long first = BeginPortal(
coordinator,
state.Transit,
0x11340021u,
sequence: 1);
state.Transit.EndTeleport();
long second = BeginPortal(
coordinator,
state.Transit,
0x20210123u,
sequence: 2);
Assert.Equal([first, second], resources.Begins);
coordinator.RevealWorldViewport();
@ -356,6 +440,33 @@ public sealed class WorldRevealCoordinatorTests
Assert.Equal([second], resources.Ends);
}
private static long BeginPortal(
WorldRevealCoordinator coordinator,
RuntimeWorldTransitState transit,
uint destinationCell,
ushort sequence)
{
Assert.True(transit.TryQueueTeleportStart(sequence));
Assert.True(transit.ActivateQueuedTeleport());
Assert.True(transit.OfferTeleportDestination(
new RuntimeTeleportDestination(
EntityGuid: 0x50000001u,
InstanceSequence: 1,
PositionSequence: 1,
TeleportSequence: sequence,
ForcePositionSequence: 1,
Position: new Position(
destinationCell,
Vector3.Zero,
Quaternion.Identity)),
teleportTimestampAdvanced: true));
Assert.True(coordinator.TryBeginPortal(
sequence,
destinationCell,
out long generation));
return generation;
}
private sealed class RecordingAudioQuiescence : AcDream.App.Audio.IWorldAudioQuiescence
{
public int SuspendCalls { get; private set; }

View file

@ -46,7 +46,9 @@ public sealed class RecallTeleportAnimationTests
var calls = new List<string>();
var transit = new RuntimeWorldTransitState();
var availability = new WorldGenerationAvailabilityState(transit);
transit.BeginReveal(RuntimePortalKind.Portal, 0x11340021u);
RuntimeWorldTransitTestDriver.BeginPortal(
transit,
0x11340021u);
var frame = new RetailLiveFrameCoordinator(
new TestLiveObjectFramePhase(_ => calls.Add("objects")),
new GpuWorldState(availability: availability),
@ -66,7 +68,9 @@ public sealed class RecallTeleportAnimationTests
var calls = new List<string>();
var transit = new RuntimeWorldTransitState();
var availability = new WorldGenerationAvailabilityState(transit);
transit.BeginReveal(RuntimePortalKind.Portal, 0x11340021u);
RuntimeWorldTransitTestDriver.BeginPortal(
transit,
0x11340021u);
var frame = new RetailLiveFrameCoordinator(
new TestLiveObjectFramePhase(_ => calls.Add("objects")),
new GpuWorldState(availability: availability),

View file

@ -142,7 +142,9 @@ public sealed class UpdateFrameOrchestratorTests
var transit = new RuntimeWorldTransitState();
var availability = new WorldGenerationAvailabilityState(transit);
long generation =
transit.BeginReveal(RuntimePortalKind.Portal, 0x11340021u);
RuntimeWorldTransitTestDriver.BeginPortal(
transit,
0x11340021u);
UpdateFrameOrchestrator frame = Create(
calls,
observed: observed,
@ -160,7 +162,8 @@ public sealed class UpdateFrameOrchestratorTests
IsRenderNeighborhoodReady: true,
AreCompositeTexturesReady: true,
IsCollisionReady: true)));
Assert.True(transit.AcknowledgeMaterialized(
Assert.True(RuntimeWorldTransitTestDriver.MaterializePortal(
transit,
generation,
0x11340021u));
transit.Complete(generation);
@ -514,9 +517,9 @@ public sealed class UpdateFrameOrchestratorTests
"LocalPlayerTeleportController.cs"));
AssertAppearsInOrder(
teleportSource,
"_transit.CanBegin(teleportSequence)",
"_transit.CanQueueTeleportStart(teleportSequence)",
"_input.EndMouseLook();",
"_transit.QueueStart(teleportSequence)");
"_transit.TryQueueTeleportStart(teleportSequence)");
string playerModeSource = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",

View file

@ -1,3 +1,5 @@
using System.Numerics;
using AcDream.Core.Physics;
using AcDream.Runtime.World;
namespace AcDream.Runtime.Tests.World;
@ -13,7 +15,7 @@ public sealed class RuntimeWorldTransitStateTests
var state = new RuntimeWorldTransitState();
long generation =
state.BeginReveal(RuntimePortalKind.Login, OutdoorCell);
state.BeginLoginReveal(OutdoorCell);
Assert.Equal(1, generation);
Assert.Equal(generation, state.Snapshot.Generation);
@ -29,7 +31,7 @@ public sealed class RuntimeWorldTransitStateTests
List<string> log = [];
var state = new RuntimeWorldTransitState(log.Add);
long generation =
state.BeginReveal(RuntimePortalKind.Portal, OutdoorCell);
BeginPortal(state, OutdoorCell);
Assert.False(state.AcknowledgeDestinationReadiness(
Ready(generation + 1, OutdoorCell)));
@ -56,12 +58,13 @@ public sealed class RuntimeWorldTransitStateTests
{
var state = new RuntimeWorldTransitState();
long generation =
state.BeginReveal(RuntimePortalKind.Portal, OutdoorCell);
BeginPortal(state, OutdoorCell);
Assert.True(state.AcknowledgeDestinationReadiness(
Ready(generation, OutdoorCell)));
Assert.True(state.AcknowledgeMaterialized(
Assert.True(state.AcknowledgePortalMaterialized(
generation,
1,
OutdoorCell));
Assert.True(state.IsWorldSimulationAvailable);
@ -69,8 +72,9 @@ public sealed class RuntimeWorldTransitStateTests
Assert.False(state.Snapshot.WorldViewportObserved);
Assert.False(state.Snapshot.Completed);
Assert.Equal(1, state.Snapshot.PortalMaterializationCount);
Assert.False(state.AcknowledgeMaterialized(
Assert.False(state.AcknowledgePortalMaterialized(
generation,
1,
OutdoorCell));
Assert.True(state.AcknowledgeWorldViewportVisible(generation));
@ -85,10 +89,11 @@ public sealed class RuntimeWorldTransitStateTests
{
var state = new RuntimeWorldTransitState();
long generation =
state.BeginReveal(RuntimePortalKind.Portal, OutdoorCell);
BeginPortal(state, OutdoorCell);
Assert.False(state.AcknowledgeMaterialized(
Assert.False(state.AcknowledgePortalMaterialized(
generation,
1,
OutdoorCell));
Assert.False(state.IsWorldSimulationAvailable);
@ -102,7 +107,7 @@ public sealed class RuntimeWorldTransitStateTests
{
var state = new RuntimeWorldTransitState();
long generation =
state.BeginReveal(RuntimePortalKind.Login, OutdoorCell);
state.BeginLoginReveal(OutdoorCell);
state.AcknowledgeDestinationReadiness(
Ready(generation, OutdoorCell));
@ -120,7 +125,7 @@ public sealed class RuntimeWorldTransitStateTests
{
var state = new RuntimeWorldTransitState();
long generation =
state.BeginReveal(RuntimePortalKind.Login, OutdoorCell);
state.BeginLoginReveal(OutdoorCell);
Assert.False(state.AcknowledgeWorldViewportVisible(generation));
@ -134,7 +139,7 @@ public sealed class RuntimeWorldTransitStateTests
{
var state = new RuntimeWorldTransitState();
long generation =
state.BeginReveal(RuntimePortalKind.Login, OutdoorCell);
state.BeginLoginReveal(OutdoorCell);
RuntimeDestinationReadiness invalid =
Ready(generation, OutdoorCell) with
{
@ -153,7 +158,7 @@ public sealed class RuntimeWorldTransitStateTests
{
var state = new RuntimeWorldTransitState();
long generation =
state.BeginReveal(RuntimePortalKind.Portal, OutdoorCell);
BeginPortal(state, OutdoorCell);
RuntimeDestinationReadiness accepted =
Ready(generation, OutdoorCell);
Assert.True(state.AcknowledgeDestinationReadiness(accepted));
@ -174,13 +179,13 @@ public sealed class RuntimeWorldTransitStateTests
{
var state = new RuntimeWorldTransitState();
long login =
state.BeginReveal(RuntimePortalKind.Login, OutdoorCell);
state.BeginLoginReveal(OutdoorCell);
Assert.False(state.Complete(login));
Assert.False(state.Snapshot.Completed);
long portal =
state.BeginReveal(RuntimePortalKind.Portal, OtherCell);
BeginPortal(state, OtherCell, sequence: 2);
Assert.True(state.AcknowledgeDestinationReadiness(
Ready(portal, OtherCell)));
Assert.False(state.Complete(portal));
@ -193,14 +198,17 @@ public sealed class RuntimeWorldTransitStateTests
{
var state = new RuntimeWorldTransitState();
long first =
state.BeginReveal(RuntimePortalKind.Login, OutdoorCell);
state.BeginLoginReveal(OutdoorCell);
long second =
state.BeginReveal(RuntimePortalKind.Portal, OtherCell);
BeginPortal(state, OtherCell);
Assert.True(second > first);
Assert.False(state.AcknowledgeDestinationReadiness(
Ready(first, OutdoorCell)));
Assert.False(state.AcknowledgeMaterialized(first, OutdoorCell));
Assert.False(state.AcknowledgePortalMaterialized(
first,
1,
OutdoorCell));
Assert.Equal(second, state.Snapshot.Generation);
Assert.Equal(OtherCell, state.Snapshot.DestinationCell);
@ -214,7 +222,7 @@ public sealed class RuntimeWorldTransitStateTests
{
var state = new RuntimeWorldTransitState();
long generation =
state.BeginReveal(RuntimePortalKind.Portal, OutdoorCell);
BeginPortal(state, OutdoorCell);
Assert.True(state.Cancel(generation));
Assert.True(state.IsWorldSimulationAvailable);
@ -222,8 +230,9 @@ public sealed class RuntimeWorldTransitStateTests
Assert.False(state.AcknowledgeDestinationReadiness(
Ready(generation, OutdoorCell)));
Assert.False(state.AcknowledgeMaterialized(
Assert.False(state.AcknowledgePortalMaterialized(
generation,
1,
OutdoorCell));
Assert.False(state.Complete(generation));
Assert.False(state.Snapshot.Materialized);
@ -235,10 +244,13 @@ public sealed class RuntimeWorldTransitStateTests
{
var state = new RuntimeWorldTransitState();
long generation =
state.BeginReveal(RuntimePortalKind.Portal, OutdoorCell);
BeginPortal(state, OutdoorCell);
state.AcknowledgeDestinationReadiness(
Ready(generation, OutdoorCell));
state.AcknowledgeMaterialized(generation, OutdoorCell);
state.AcknowledgePortalMaterialized(
generation,
1,
OutdoorCell);
Assert.True(state.Cancel(generation));
Assert.True(state.IsWorldSimulationAvailable);
@ -254,7 +266,7 @@ public sealed class RuntimeWorldTransitStateTests
{
var state = new RuntimeWorldTransitState();
long generation =
state.BeginReveal(RuntimePortalKind.Portal, OutdoorCell);
BeginPortal(state, OutdoorCell);
Assert.False(state.ObserveWait(
generation,
@ -278,7 +290,7 @@ public sealed class RuntimeWorldTransitStateTests
{
var state = new RuntimeWorldTransitState();
long generation =
state.BeginReveal(RuntimePortalKind.Portal, OutdoorCell);
BeginPortal(state, OutdoorCell);
var acknowledgement = new RuntimeDestinationReadiness(
generation,
OutdoorCell,
@ -291,8 +303,9 @@ public sealed class RuntimeWorldTransitStateTests
Assert.True(state.AcknowledgeDestinationReadiness(acknowledgement));
Assert.True(state.Snapshot.IsReady);
Assert.True(state.AcknowledgeMaterialized(
Assert.True(state.AcknowledgePortalMaterialized(
generation,
1,
OutdoorCell));
}
@ -301,14 +314,14 @@ public sealed class RuntimeWorldTransitStateTests
{
var state = new RuntimeWorldTransitState();
long first =
state.BeginReveal(RuntimePortalKind.Portal, OutdoorCell);
BeginPortal(state, OutdoorCell);
state.ResetSession();
Assert.Equal(RuntimePortalSnapshot.Idle, state.Snapshot);
Assert.True(state.IsWorldSimulationAvailable);
long second =
state.BeginReveal(RuntimePortalKind.Login, OtherCell);
state.BeginLoginReveal(OtherCell);
Assert.True(second > first);
Assert.False(state.AcknowledgeDestinationReadiness(
Ready(first, OutdoorCell)));
@ -322,12 +335,15 @@ public sealed class RuntimeWorldTransitStateTests
var first = new RuntimeWorldTransitState();
var second = new RuntimeWorldTransitState();
long firstGeneration =
first.BeginReveal(RuntimePortalKind.Portal, OutdoorCell);
BeginPortal(first, OutdoorCell);
long secondGeneration =
second.BeginReveal(RuntimePortalKind.Login, OtherCell);
second.BeginLoginReveal(OtherCell);
first.AcknowledgeDestinationReadiness(
Ready(firstGeneration, OutdoorCell));
first.AcknowledgeMaterialized(firstGeneration, OutdoorCell);
first.AcknowledgePortalMaterialized(
firstGeneration,
1,
OutdoorCell);
Assert.Equal(1, first.Snapshot.PortalMaterializationCount);
Assert.Equal(0, second.Snapshot.PortalMaterializationCount);
@ -344,11 +360,12 @@ public sealed class RuntimeWorldTransitStateTests
_ => throw new InvalidOperationException("diagnostic failed"));
long generation =
state.BeginReveal(RuntimePortalKind.Portal, OutdoorCell);
BeginPortal(state, OutdoorCell);
Assert.True(state.AcknowledgeDestinationReadiness(
Ready(generation, OutdoorCell)));
Assert.True(state.AcknowledgeMaterialized(
Assert.True(state.AcknowledgePortalMaterialized(
generation,
1,
OutdoorCell));
Assert.True(state.Complete(generation));
@ -359,6 +376,278 @@ public sealed class RuntimeWorldTransitStateTests
state.LastDiagnosticFailure);
}
[Fact]
public void ReplacementRejectsDelayedPriorDestinationAndAcceptsCurrentAdvance()
{
var state = new RuntimeWorldTransitState();
Assert.True(state.TryQueueTeleportStart(10));
Assert.True(state.ActivateQueuedTeleport());
state.EndTeleport();
Assert.True(state.TryQueueTeleportStart(11));
Assert.True(state.ActivateQueuedTeleport());
Assert.False(state.OfferTeleportDestination(
Destination(OutdoorCell, 10, x: 10f),
teleportTimestampAdvanced: true));
Assert.True(state.OfferTeleportDestination(
Destination(OtherCell, 11, x: 11f),
teleportTimestampAdvanced: true));
Assert.True(state.TryGetAcceptedTeleportDestination(
out RuntimeTeleportDestination accepted));
Assert.Equal(11f, accepted.Position.Frame.Origin.X);
Assert.Equal(OtherCell, accepted.CellId);
}
[Fact]
public void PositionBeforeF751IsBufferedAndEqualFollowupCannotReplaceIt()
{
var state = new RuntimeWorldTransitState();
Assert.False(state.OfferTeleportDestination(
Destination(OutdoorCell, 11, x: 110f),
teleportTimestampAdvanced: true));
Assert.Equal(1, state.BufferedTeleportDestinationCount);
Assert.True(state.TryQueueTeleportStart(11));
Assert.True(state.ActivateQueuedTeleport());
Assert.True(state.TryGetAcceptedTeleportDestination(
out RuntimeTeleportDestination buffered));
Assert.Equal(110f, buffered.Position.Frame.Origin.X);
Assert.False(state.OfferTeleportDestination(
Destination(OtherCell, 11, x: 111f),
teleportTimestampAdvanced: false));
Assert.True(state.TryGetAcceptedTeleportDestination(out buffered));
Assert.Equal(OutdoorCell, buffered.CellId);
}
[Fact]
public void InactiveOrdinaryPositionIsNeverBuffered()
{
var state = new RuntimeWorldTransitState();
Assert.False(state.OfferTeleportDestination(
Destination(OutdoorCell, 11),
teleportTimestampAdvanced: false));
Assert.Equal(0, state.BufferedTeleportDestinationCount);
Assert.True(state.TryQueueTeleportStart(11));
Assert.True(state.ActivateQueuedTeleport());
Assert.False(state.HasAcceptedTeleportDestination);
}
[Fact]
public void DuplicateStartIsRejectedButSequenceWrapIsFresh()
{
var state = new RuntimeWorldTransitState();
Assert.True(state.TryQueueTeleportStart(ushort.MaxValue));
Assert.True(state.ActivateQueuedTeleport());
state.EndTeleport();
Assert.False(state.CanQueueTeleportStart(ushort.MaxValue));
Assert.False(state.TryQueueTeleportStart(ushort.MaxValue));
Assert.True(state.CanQueueTeleportStart(0));
Assert.True(state.TryQueueTeleportStart(0));
Assert.True(state.HasPendingTeleportStart);
}
[Fact]
public void ExactHalfRangeUsesRetailLowerStampTieBreak()
{
var state = new RuntimeWorldTransitState();
Assert.True(state.TryQueueTeleportStart(0x8000));
Assert.True(state.ActivateQueuedTeleport());
state.EndTeleport();
Assert.True(state.CanQueueTeleportStart(0x0000));
Assert.True(state.TryQueueTeleportStart(0x0000));
}
[Fact]
public void PendingStartAcceptsEqualPositionUntilHostCanActivate()
{
var state = new RuntimeWorldTransitState();
Assert.True(state.TryQueueTeleportStart(11));
Assert.False(state.OfferTeleportDestination(
Destination(OutdoorCell, 11, x: 110f),
teleportTimestampAdvanced: false));
Assert.True(state.ActivateQueuedTeleport());
Assert.True(state.TryGetAcceptedTeleportDestination(
out RuntimeTeleportDestination buffered));
Assert.Equal(110f, buffered.Position.Frame.Origin.X);
Assert.True(state.IsTeleportActive);
}
[Fact]
public void AdvancingPositionPrunesSkippedDestination()
{
var state = new RuntimeWorldTransitState();
Assert.False(state.OfferTeleportDestination(
Destination(OutdoorCell, 11),
teleportTimestampAdvanced: true));
Assert.False(state.OfferTeleportDestination(
Destination(OtherCell, 12),
teleportTimestampAdvanced: true));
Assert.Equal(1, state.BufferedTeleportDestinationCount);
Assert.True(state.TryQueueTeleportStart(12));
Assert.True(state.ActivateQueuedTeleport());
Assert.True(state.TryGetAcceptedTeleportDestination(
out RuntimeTeleportDestination buffered));
Assert.Equal(OtherCell, buffered.CellId);
}
[Fact]
public void ObsoleteDestinationCannotSurviveSequenceWrap()
{
var state = new RuntimeWorldTransitState();
Assert.False(state.OfferTeleportDestination(
Destination(OutdoorCell, ushort.MaxValue),
teleportTimestampAdvanced: true));
Assert.False(state.OfferTeleportDestination(
Destination(OtherCell, 0),
teleportTimestampAdvanced: true));
Assert.True(state.TryQueueTeleportStart(0));
Assert.True(state.ActivateQueuedTeleport());
Assert.True(state.TryGetAcceptedTeleportDestination(
out RuntimeTeleportDestination wrapped));
Assert.Equal(OtherCell, wrapped.CellId);
state.EndTeleport();
Assert.True(state.TryQueueTeleportStart(0x7FFF));
Assert.True(state.ActivateQueuedTeleport());
state.EndTeleport();
Assert.True(state.TryQueueTeleportStart(0xFFFE));
Assert.True(state.ActivateQueuedTeleport());
state.EndTeleport();
Assert.True(state.TryQueueTeleportStart(ushort.MaxValue));
Assert.True(state.ActivateQueuedTeleport());
Assert.False(state.HasAcceptedTeleportDestination);
}
[Fact]
public void PortalRevealClaimsOnlyExactAcceptedSequenceAndCell()
{
var state = new RuntimeWorldTransitState();
Assert.True(state.TryQueueTeleportStart(7));
Assert.True(state.ActivateQueuedTeleport());
Assert.True(state.OfferTeleportDestination(
Destination(OutdoorCell, 7),
teleportTimestampAdvanced: true));
Assert.False(state.TryBeginPortalReveal(
8,
OutdoorCell,
out _));
Assert.False(state.TryBeginPortalReveal(
7,
OtherCell,
out _));
Assert.Equal(RuntimePortalSnapshot.Idle.Generation, state.Snapshot.Generation);
Assert.True(state.HasAcceptedTeleportDestination);
Assert.True(state.TryBeginPortalReveal(
7,
OutdoorCell,
out long generation));
Assert.True(generation > 0);
Assert.False(state.HasAcceptedTeleportDestination);
Assert.True(state.CanPlacePortalDestination(
generation,
7,
OutdoorCell));
Assert.False(state.CanPlacePortalDestination(
generation,
8,
OutdoorCell));
Assert.False(state.CanPlacePortalDestination(
generation,
7,
OtherCell));
}
[Fact]
public void MaterializationRequiresExactGenerationSequenceAndCell()
{
var state = new RuntimeWorldTransitState();
long generation = BeginPortal(state, OutdoorCell, sequence: 17);
Assert.True(state.AcknowledgeDestinationReadiness(
Ready(generation, OutdoorCell)));
Assert.False(state.AcknowledgePortalMaterialized(
generation + 1,
17,
OutdoorCell));
Assert.False(state.AcknowledgePortalMaterialized(
generation,
18,
OutdoorCell));
Assert.False(state.AcknowledgePortalMaterialized(
generation,
17,
OtherCell));
Assert.False(state.Snapshot.Materialized);
Assert.True(state.AcknowledgePortalMaterialized(
generation,
17,
OutdoorCell));
Assert.True(state.Snapshot.Materialized);
}
[Fact]
public void ResetSessionClearsCorrelationHistoryAndBufferedDestinations()
{
var state = new RuntimeWorldTransitState();
Assert.False(state.OfferTeleportDestination(
Destination(OutdoorCell, 7),
teleportTimestampAdvanced: true));
Assert.True(state.TryQueueTeleportStart(8));
state.ResetSession();
Assert.False(state.IsTeleportActive);
Assert.False(state.HasPendingTeleportStart);
Assert.False(state.HasAcceptedTeleportDestination);
Assert.Equal(0, state.BufferedTeleportDestinationCount);
Assert.True(state.TryQueueTeleportStart(8));
}
private static long BeginPortal(
RuntimeWorldTransitState state,
uint cell,
ushort sequence = 1)
{
Assert.True(state.TryQueueTeleportStart(sequence));
Assert.True(state.ActivateQueuedTeleport());
Assert.True(state.OfferTeleportDestination(
Destination(cell, sequence),
teleportTimestampAdvanced: true));
Assert.True(state.TryBeginPortalReveal(
sequence,
cell,
out long generation));
return generation;
}
private static RuntimeTeleportDestination Destination(
uint cell,
ushort sequence,
float x = 1f) =>
new(
EntityGuid: 0x50000001u,
InstanceSequence: 1,
PositionSequence: 1,
TeleportSequence: sequence,
ForcePositionSequence: 1,
Position: new Position(
cell,
new Vector3(x, 2f, 3f),
Quaternion.Identity));
private static RuntimeDestinationReadiness Ready(
long generation,
uint cell) =>