refactor(runtime): own teleport destination correlation
This commit is contained in:
parent
38b3773cb9
commit
6a063a27d4
23 changed files with 1209 additions and 487 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
53
tests/AcDream.App.Tests/RuntimeWorldTransitTestDriver.cs
Normal file
53
tests/AcDream.App.Tests/RuntimeWorldTransitTestDriver.cs
Normal 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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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 _));
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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) =>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue