acdream/tests/AcDream.Runtime.Tests/World/RuntimeWorldTransitStateTests.cs

374 lines
13 KiB
C#

using AcDream.Runtime.World;
namespace AcDream.Runtime.Tests.World;
public sealed class RuntimeWorldTransitStateTests
{
private const uint OutdoorCell = 0x11340021u;
private const uint OtherCell = 0x3032001Cu;
[Fact]
public void BeginReveal_OwnsGenerationDestinationAndSimulationGate()
{
var state = new RuntimeWorldTransitState();
long generation =
state.BeginReveal(RuntimePortalKind.Login, OutdoorCell);
Assert.Equal(1, generation);
Assert.Equal(generation, state.Snapshot.Generation);
Assert.Equal(RuntimePortalKind.Login, state.Snapshot.Kind);
Assert.Equal(OutdoorCell, state.Snapshot.DestinationCell);
Assert.False(state.IsWorldSimulationAvailable);
Assert.False(state.Snapshot.IsReady);
}
[Fact]
public void TypedReadiness_RejectsWrongGenerationAndDestination()
{
List<string> log = [];
var state = new RuntimeWorldTransitState(log.Add);
long generation =
state.BeginReveal(RuntimePortalKind.Portal, OutdoorCell);
Assert.False(state.AcknowledgeDestinationReadiness(
Ready(generation + 1, OutdoorCell)));
Assert.False(state.AcknowledgeDestinationReadiness(
Ready(generation, OtherCell)));
Assert.Equal(OutdoorCell, state.Snapshot.DestinationCell);
Assert.False(state.Snapshot.IsReady);
Assert.Equal(1, state.Snapshot.InvariantFailureCount);
Assert.Equal(
1,
log.Count(value => value.Contains(
"event=invariant-failure",
StringComparison.Ordinal)));
Assert.Single(
log,
value => value.Contains(
"event=rejected",
StringComparison.Ordinal));
}
[Fact]
public void Materialization_ReleasesSimulationBeforeViewportAndCompletion()
{
var state = new RuntimeWorldTransitState();
long generation =
state.BeginReveal(RuntimePortalKind.Portal, OutdoorCell);
Assert.True(state.AcknowledgeDestinationReadiness(
Ready(generation, OutdoorCell)));
Assert.True(state.AcknowledgeMaterialized(
generation,
OutdoorCell));
Assert.True(state.IsWorldSimulationAvailable);
Assert.True(state.Snapshot.Materialized);
Assert.False(state.Snapshot.WorldViewportObserved);
Assert.False(state.Snapshot.Completed);
Assert.Equal(1, state.Snapshot.PortalMaterializationCount);
Assert.False(state.AcknowledgeMaterialized(
generation,
OutdoorCell));
Assert.True(state.AcknowledgeWorldViewportVisible(generation));
Assert.True(state.Complete(generation));
Assert.True(state.Snapshot.WorldViewportObserved);
Assert.True(state.Snapshot.Completed);
Assert.Equal(1, state.Snapshot.PortalMaterializationCount);
}
[Fact]
public void MaterializationBeforeReadiness_IsRejectedWithoutOpeningWorld()
{
var state = new RuntimeWorldTransitState();
long generation =
state.BeginReveal(RuntimePortalKind.Portal, OutdoorCell);
Assert.False(state.AcknowledgeMaterialized(
generation,
OutdoorCell));
Assert.False(state.IsWorldSimulationAvailable);
Assert.False(state.Snapshot.Materialized);
Assert.Equal(1, state.Snapshot.InvariantFailureCount);
Assert.Equal(0, state.Snapshot.PortalMaterializationCount);
}
[Fact]
public void LoginCompletion_ReleasesSimulationBeforeFirstVisibleFrame()
{
var state = new RuntimeWorldTransitState();
long generation =
state.BeginReveal(RuntimePortalKind.Login, OutdoorCell);
state.AcknowledgeDestinationReadiness(
Ready(generation, OutdoorCell));
Assert.True(state.Complete(generation));
Assert.True(state.IsWorldSimulationAvailable);
Assert.False(state.Snapshot.WorldViewportObserved);
Assert.True(state.AcknowledgeWorldViewportVisible(generation));
Assert.True(state.Snapshot.WorldViewportObserved);
Assert.Equal(0, state.Snapshot.InvariantFailureCount);
}
[Fact]
public void EarlyViewport_IsRejectedWithoutFabricatingVisibility()
{
var state = new RuntimeWorldTransitState();
long generation =
state.BeginReveal(RuntimePortalKind.Login, OutdoorCell);
Assert.False(state.AcknowledgeWorldViewportVisible(generation));
Assert.False(state.Snapshot.IsReady);
Assert.False(state.Snapshot.WorldViewportObserved);
Assert.Equal(1, state.Snapshot.InvariantFailureCount);
}
[Fact]
public void ReadinessShape_MustMatchDestinationCell()
{
var state = new RuntimeWorldTransitState();
long generation =
state.BeginReveal(RuntimePortalKind.Login, OutdoorCell);
RuntimeDestinationReadiness invalid =
Ready(generation, OutdoorCell) with
{
IsIndoor = true,
RequiredRenderRadius = 0,
};
Assert.False(state.AcknowledgeDestinationReadiness(invalid));
Assert.False(state.Snapshot.IsReady);
Assert.Equal(1, state.Snapshot.InvariantFailureCount);
}
[Fact]
public void AcceptedReadiness_IsGenerationSticky()
{
var state = new RuntimeWorldTransitState();
long generation =
state.BeginReveal(RuntimePortalKind.Portal, OutdoorCell);
RuntimeDestinationReadiness accepted =
Ready(generation, OutdoorCell);
Assert.True(state.AcknowledgeDestinationReadiness(accepted));
RuntimeDestinationReadiness regressed = accepted with
{
IsCollisionReady = false,
};
Assert.False(state.AcknowledgeDestinationReadiness(regressed));
Assert.Equal(accepted, state.Snapshot.Readiness);
Assert.True(state.Snapshot.IsReady);
Assert.Equal(0, state.Snapshot.InvariantFailureCount);
}
[Fact]
public void Completion_RejectsReorderedLoginAndPortalEdges()
{
var state = new RuntimeWorldTransitState();
long login =
state.BeginReveal(RuntimePortalKind.Login, OutdoorCell);
Assert.False(state.Complete(login));
Assert.False(state.Snapshot.Completed);
long portal =
state.BeginReveal(RuntimePortalKind.Portal, OtherCell);
Assert.True(state.AcknowledgeDestinationReadiness(
Ready(portal, OtherCell)));
Assert.False(state.Complete(portal));
Assert.False(state.Snapshot.Completed);
Assert.Equal(2, state.Snapshot.InvariantFailureCount);
}
[Fact]
public void Supersession_RejectsOldAcknowledgementsAndKeepsLatestDestination()
{
var state = new RuntimeWorldTransitState();
long first =
state.BeginReveal(RuntimePortalKind.Login, OutdoorCell);
long second =
state.BeginReveal(RuntimePortalKind.Portal, OtherCell);
Assert.True(second > first);
Assert.False(state.AcknowledgeDestinationReadiness(
Ready(first, OutdoorCell)));
Assert.False(state.AcknowledgeMaterialized(first, OutdoorCell));
Assert.Equal(second, state.Snapshot.Generation);
Assert.Equal(OtherCell, state.Snapshot.DestinationCell);
Assert.False(state.Snapshot.IsReady);
Assert.False(state.IsWorldSimulationAvailable);
Assert.Equal(0, state.Snapshot.InvariantFailureCount);
}
[Fact]
public void Cancel_ReleasesSimulationAndRejectsLateHostWork()
{
var state = new RuntimeWorldTransitState();
long generation =
state.BeginReveal(RuntimePortalKind.Portal, OutdoorCell);
Assert.True(state.Cancel(generation));
Assert.True(state.IsWorldSimulationAvailable);
Assert.True(state.Snapshot.Cancelled);
Assert.False(state.AcknowledgeDestinationReadiness(
Ready(generation, OutdoorCell)));
Assert.False(state.AcknowledgeMaterialized(
generation,
OutdoorCell));
Assert.False(state.Complete(generation));
Assert.False(state.Snapshot.Materialized);
Assert.False(state.Snapshot.Completed);
}
[Fact]
public void MaterializedThenCancelled_RemainsAvailableAndCannotComplete()
{
var state = new RuntimeWorldTransitState();
long generation =
state.BeginReveal(RuntimePortalKind.Portal, OutdoorCell);
state.AcknowledgeDestinationReadiness(
Ready(generation, OutdoorCell));
state.AcknowledgeMaterialized(generation, OutdoorCell);
Assert.True(state.Cancel(generation));
Assert.True(state.IsWorldSimulationAvailable);
Assert.True(state.Snapshot.Materialized);
Assert.True(state.Snapshot.Cancelled);
Assert.False(state.Complete(generation));
Assert.False(state.Snapshot.Completed);
Assert.Equal(1, state.Snapshot.PortalMaterializationCount);
}
[Fact]
public void WaitCue_UsesRetailFiveSecondEdgeWithoutCompletingReveal()
{
var state = new RuntimeWorldTransitState();
long generation =
state.BeginReveal(RuntimePortalKind.Portal, OutdoorCell);
Assert.False(state.ObserveWait(
generation,
RuntimeWorldTransitState.RetailWaitCueDelay
- TimeSpan.FromMilliseconds(1)));
Assert.True(state.ObserveWait(
generation,
RuntimeWorldTransitState.RetailWaitCueDelay));
Assert.True(state.ObserveWait(
generation,
RuntimeWorldTransitState.RetailWaitCueDelay
+ TimeSpan.FromSeconds(20)));
Assert.True(state.Snapshot.WaitCueShown);
Assert.False(state.Snapshot.Completed);
Assert.False(state.IsWorldSimulationAvailable);
}
[Fact]
public void UnhydratableDestination_IsAnExplicitReadyAcknowledgement()
{
var state = new RuntimeWorldTransitState();
long generation =
state.BeginReveal(RuntimePortalKind.Portal, OutdoorCell);
var acknowledgement = new RuntimeDestinationReadiness(
generation,
OutdoorCell,
IsIndoor: false,
IsUnhydratable: true,
RequiredRenderRadius: 1,
IsRenderNeighborhoodReady: false,
AreCompositeTexturesReady: false,
IsCollisionReady: false);
Assert.True(state.AcknowledgeDestinationReadiness(acknowledgement));
Assert.True(state.Snapshot.IsReady);
Assert.True(state.AcknowledgeMaterialized(
generation,
OutdoorCell));
}
[Fact]
public void ResetSession_ClearsVisibleStateButNeverReusesGeneration()
{
var state = new RuntimeWorldTransitState();
long first =
state.BeginReveal(RuntimePortalKind.Portal, OutdoorCell);
state.ResetSession();
Assert.Equal(RuntimePortalSnapshot.Idle, state.Snapshot);
Assert.True(state.IsWorldSimulationAvailable);
long second =
state.BeginReveal(RuntimePortalKind.Login, OtherCell);
Assert.True(second > first);
Assert.False(state.AcknowledgeDestinationReadiness(
Ready(first, OutdoorCell)));
Assert.Equal(second, state.Snapshot.Generation);
Assert.Equal(OtherCell, state.Snapshot.DestinationCell);
}
[Fact]
public void Instances_IsolateGenerationReadinessAndMaterializationCounts()
{
var first = new RuntimeWorldTransitState();
var second = new RuntimeWorldTransitState();
long firstGeneration =
first.BeginReveal(RuntimePortalKind.Portal, OutdoorCell);
long secondGeneration =
second.BeginReveal(RuntimePortalKind.Login, OtherCell);
first.AcknowledgeDestinationReadiness(
Ready(firstGeneration, OutdoorCell));
first.AcknowledgeMaterialized(firstGeneration, OutdoorCell);
Assert.Equal(1, first.Snapshot.PortalMaterializationCount);
Assert.Equal(0, second.Snapshot.PortalMaterializationCount);
Assert.True(first.IsWorldSimulationAvailable);
Assert.False(second.IsWorldSimulationAvailable);
Assert.Equal(OtherCell, second.Snapshot.DestinationCell);
Assert.Equal(1, secondGeneration);
}
[Fact]
public void DiagnosticCallbackFailure_CannotInterruptCanonicalTransition()
{
var state = new RuntimeWorldTransitState(
_ => throw new InvalidOperationException("diagnostic failed"));
long generation =
state.BeginReveal(RuntimePortalKind.Portal, OutdoorCell);
Assert.True(state.AcknowledgeDestinationReadiness(
Ready(generation, OutdoorCell)));
Assert.True(state.AcknowledgeMaterialized(
generation,
OutdoorCell));
Assert.True(state.Complete(generation));
Assert.True(state.Snapshot.Completed);
Assert.True(state.IsWorldSimulationAvailable);
Assert.True(state.DiagnosticFailureCount >= 4);
Assert.IsType<InvalidOperationException>(
state.LastDiagnosticFailure);
}
private static RuntimeDestinationReadiness Ready(
long generation,
uint cell) =>
new(
generation,
cell,
IsIndoor: (cell & 0xFFFFu) >= 0x0100u,
IsUnhydratable: false,
RequiredRenderRadius: (cell & 0xFFFFu) >= 0x0100u ? 0 : 1,
IsRenderNeighborhoodReady: true,
AreCompositeTexturesReady: true,
IsCollisionReady: true);
}