acdream/tests/AcDream.App.Tests/World/UpdateFrameOrchestratorTests.cs

1148 lines
46 KiB
C#

using System.Reflection;
using AcDream.App.Composition;
using AcDream.App.Input;
using AcDream.App.Rendering;
using AcDream.App.Net;
using AcDream.App.Streaming;
using AcDream.App.Update;
using AcDream.App.World;
using AcDream.App.Tests.Architecture;
using AcDream.Runtime;
using AcDream.Runtime.Session;
using AcDream.Runtime.World;
namespace AcDream.App.Tests.World;
public sealed class UpdateFrameOrchestratorTests
{
[Fact]
public void Tick_PreservesTheCompleteAcceptedPhaseGraph()
{
var calls = new List<string>();
UpdateFrameOrchestrator frame = Create(calls);
frame.Tick(new UpdateFrameInput(1.0 / 60.0));
Assert.Equal(
[
"teardown",
"clock",
"streaming",
"input",
"objects",
"network",
"commands",
"ordinary-reconcile",
"liveness",
"teleport",
"auto-entry",
"camera",
"commit",
],
calls);
}
[Fact]
public void ConditionalReconciles_RemainAtTheirOwningEdges()
{
// The outer ownership oracle is complemented by the production
// teleport and camera tests that pin both conditional reconcile edges.
var calls = new List<string>();
UpdateFrameOrchestrator frame = Create(
calls,
teleportPlace: true,
inboundCreatedPlayer: true);
frame.Tick(new UpdateFrameInput(1.0 / 60.0));
Assert.Equal(
[
"teardown",
"clock",
"streaming",
"input",
"objects",
"network",
"commands",
"ordinary-reconcile",
"liveness",
"teleport-place",
"teleport-reconcile",
"teleport-reveal",
"auto-entry",
"inbound-player-projection",
"inbound-player-reconcile",
"camera",
"commit",
],
calls);
}
[Theory]
[InlineData(double.NaN)]
[InlineData(double.NegativeInfinity)]
[InlineData(double.PositiveInfinity)]
[InlineData(-1.0)]
[InlineData(0.0)]
[InlineData(double.MaxValue)]
public void InvalidSimulationDelta_BecomesZeroWithoutSuppressingOuterPhases(
double hostDelta)
{
var calls = new List<string>();
var observed = new FrameObservations();
UpdateFrameOrchestrator frame = Create(calls, observed: observed);
frame.Tick(new UpdateFrameInput(hostDelta));
Assert.Equal(
[
"teardown", "clock", "streaming", "input", "objects",
"network", "commands", "ordinary-reconcile", "liveness",
"teleport", "auto-entry", "camera", "commit",
],
calls);
Assert.Equal([0.0], observed.PublishedTimes);
Assert.Equal([0.0], observed.Input.Select(value => value.SimulationDeltaSeconds));
Assert.Equal([0f], observed.LiveDeltas);
Assert.Equal([0f], observed.TeleportDeltas);
Assert.Equal([0.0], observed.Camera.Select(value => value.SimulationDeltaSeconds));
Assert.All(
observed.Input.Concat(observed.Camera),
timing => Assert.Equal(0.0, timing.ScriptTime));
}
[Fact]
public void Clock_AdvancesOnceAndPublishesTheSameTimeToEveryConsumer()
{
var calls = new List<string>();
var observed = new FrameObservations();
UpdateFrameOrchestrator frame = Create(calls, observed: observed);
frame.Tick(new UpdateFrameInput(0.25));
frame.Tick(new UpdateFrameInput(double.NaN));
frame.Tick(new UpdateFrameInput(0.5));
Assert.Equal([0.25, 0.25, 0.75], observed.PublishedTimes);
Assert.Equal(
[0.25, 0.25, 0.75],
observed.Input.Select(value => value.ScriptTime));
Assert.Equal(
[0.25, 0.25, 0.75],
observed.Camera.Select(value => value.ScriptTime));
Assert.Equal([0.25f, 0f, 0.5f], observed.LiveDeltas);
Assert.Equal([0.25f, 0f, 0.5f], observed.TeleportDeltas);
Assert.Equal(
[0.25, 0.0, 0.5],
observed.Input.Select(value => value.SimulationDeltaSeconds));
Assert.Equal(
[0.25, 0.0, 0.5],
observed.Camera.Select(value => value.SimulationDeltaSeconds));
}
[Fact]
public void QuiescedWorld_FreezesScriptClockAndLivenessWhilePortalPhasesContinue()
{
var calls = new List<string>();
var observed = new FrameObservations();
var transit = new RuntimeWorldTransitState();
var availability = new WorldGenerationAvailabilityState(transit);
long generation =
RuntimeWorldTransitTestDriver.BeginPortal(
transit,
0x11340021u);
UpdateFrameOrchestrator frame = Create(
calls,
observed: observed,
availability: availability);
frame.Tick(new UpdateFrameInput(0.25));
frame.Tick(new UpdateFrameInput(0.25));
Assert.True(transit.AcknowledgeDestinationReadiness(
new RuntimeDestinationReadiness(
generation,
0x11340021u,
IsIndoor: false,
IsUnhydratable: false,
RequiredRenderRadius: 1,
IsRenderNeighborhoodReady: true,
AreCompositeTexturesReady: true,
IsCollisionReady: true)));
Assert.True(RuntimeWorldTransitTestDriver.MaterializePortal(
transit,
generation,
0x11340021u));
transit.Complete(generation);
frame.Tick(new UpdateFrameInput(0.25));
Assert.Equal([0.0, 0.0, 0.25], observed.PublishedTimes);
Assert.Equal(1, calls.Count(call => call == "liveness"));
Assert.Equal(3, calls.Count(call => call == "streaming"));
Assert.Equal(3, calls.Count(call => call == "network"));
Assert.Equal(3, calls.Count(call => call == "teleport"));
Assert.Equal(3, calls.Count(call => call == "camera"));
Assert.Equal(3, calls.Count(call => call == "commit"));
Assert.Equal([0.25f, 0.25f, 0.25f], observed.LiveDeltas);
Assert.Equal([0.25f, 0.25f, 0.25f], observed.TeleportDeltas);
}
[Fact]
public void Clock_RejectsFiniteDeltaAboveTheSinglePrecisionConsumerRange()
{
double tooLarge = (double)float.MaxValue * 2.0;
Assert.True(double.IsFinite(tooLarge));
Assert.Equal(0.0, UpdateFrameClock.NormalizeDeltaSeconds(tooLarge));
}
[Fact]
public void AggregateTeardownFailure_IsReportedAndRetriedNextFrame()
{
var calls = new List<string>();
var teardown = new RecordingTeardown(calls)
{
Failure = new AggregateException(new InvalidOperationException("transient")),
};
var failures = new RecordingFailureSink();
UpdateFrameOrchestrator frame = Create(
calls,
teardown: teardown,
failureSink: failures);
frame.Tick(new UpdateFrameInput(0.1));
teardown.Failure = null;
frame.Tick(new UpdateFrameInput(0.1));
Assert.Equal(2, teardown.Attempts);
Assert.Single(failures.Errors);
string[] oneFrame =
[
"teardown", "clock", "streaming", "input", "objects", "network",
"commands", "ordinary-reconcile", "liveness", "teleport",
"auto-entry", "camera", "commit",
];
Assert.Equal(oneFrame.Concat(oneFrame), calls);
}
[Fact]
public void NonAggregateTeardownFailure_PropagatesBeforeTimeOrLaterPhases()
{
var calls = new List<string>();
var teardown = new RecordingTeardown(calls)
{
Failure = new InvalidOperationException("fatal"),
};
UpdateFrameOrchestrator frame = Create(calls, teardown: teardown);
InvalidOperationException error = Assert.Throws<InvalidOperationException>(
() => frame.Tick(new UpdateFrameInput(0.1)));
Assert.Equal("fatal", error.Message);
Assert.Equal(["teardown"], calls);
}
[Fact]
public void OrchestrationTypes_UseOnlyTheExplicitTypedOwnerGraph()
{
Type[] expectedFieldTypes =
[
typeof(IUpdateFrameTeardownPhase),
typeof(IUpdateFrameFailureSink),
typeof(UpdateFrameClock),
typeof(IUpdateFrameScriptClockPublisher),
typeof(IStreamingFramePhase),
typeof(IGameplayInputFramePhase),
typeof(IRetailLiveFramePhase),
typeof(ILiveEntityLivenessFramePhase),
typeof(ILocalPlayerTeleportFramePhase),
typeof(IPlayerModeAutoEntryFramePhase),
typeof(ICameraFramePhase),
typeof(IUpdateFrameCommitPhase),
typeof(IWorldGenerationAvailability),
];
FieldInfo[] fields = typeof(UpdateFrameOrchestrator).GetFields(
BindingFlags.Instance | BindingFlags.NonPublic);
Assert.Equal(
expectedFieldTypes.OrderBy(type => type.FullName),
fields.Select(field => field.FieldType).OrderBy(type => type.FullName));
Assert.All(fields, field =>
{
Assert.NotEqual(typeof(GameWindow), field.FieldType);
Assert.False(typeof(Delegate).IsAssignableFrom(field.FieldType));
});
Type[] phaseInterfaces = expectedFieldTypes.Where(type => type.IsInterface).ToArray();
Assert.DoesNotContain(
phaseInterfaces,
phaseInterface => phaseInterface.IsAssignableFrom(typeof(GameWindow)));
Type[] productionPhaseOwners = typeof(GameWindow).Assembly.GetTypes()
.Where(type => !type.IsAbstract && !type.IsInterface)
.Where(type => phaseInterfaces.Any(contract => contract.IsAssignableFrom(type)))
.ToArray();
foreach (Type owner in productionPhaseOwners)
{
FieldInfo[] ownerFields = owner.GetFields(
BindingFlags.Instance | BindingFlags.NonPublic);
Assert.DoesNotContain(ownerFields, field => field.FieldType == typeof(GameWindow));
Assert.DoesNotContain(
ownerFields,
field => typeof(Delegate).IsAssignableFrom(field.FieldType));
}
Assert.DoesNotContain(
typeof(GameWindow).GetFields(BindingFlags.Instance | BindingFlags.NonPublic),
field => field.Name == "_physicsScriptGameTime");
}
[Fact]
public void PhysicsScriptClockConsumers_RetainTheTypedSourceNotGameWindowClosures()
{
Type[] consumers =
[
typeof(DatLiveEntityProjectionMaterializer),
typeof(AcDream.App.Physics.LiveEntityNetworkUpdateController),
];
foreach (Type consumer in consumers)
{
FieldInfo clock = Assert.Single(
consumer.GetFields(BindingFlags.Instance | BindingFlags.NonPublic),
field => field.Name == "_gameTime");
Assert.Equal(typeof(IPhysicsScriptTimeSource), clock.FieldType);
Assert.DoesNotContain(
consumer.GetFields(BindingFlags.Instance | BindingFlags.NonPublic),
field => field.FieldType == typeof(GameWindow));
}
Assert.False(typeof(IPhysicsScriptTimeSource).IsAssignableFrom(typeof(GameWindow)));
}
[Fact]
public void ProductionFrame_PublishesPhysicsScriptTimeExactlyOnce()
{
Assert.DoesNotContain(
CompiledCallGraph.ReadDeclared(typeof(GameWindow)),
call => call.Target.Name == nameof(PhysicsScriptClockPublisher.PublishTime));
MethodInfo publish = RequiredMethod(
typeof(PhysicsScriptClockPublisher),
nameof(PhysicsScriptClockPublisher.PublishTime));
Assert.Single(
CompiledCallGraph.Read(publish),
call => call.Target.Name == "PublishTime");
Assert.Contains(
CompiledCallGraph.Read(RequiredMethod(
typeof(FrameRootCompositionPhase),
"ComposeCore")),
call => call.Target.DeclaringType == typeof(PhysicsScriptClockPublisher)
&& call.Target.IsConstructor);
}
[Fact]
public void ExtractedLiveObjectSource_PinsRetailAndRegisteredAdaptationOrder()
{
MethodInfo live = RequiredMethod(typeof(LiveObjectFrameController), "TickCore");
AssertNamedCallOrder(
live,
("RetailLocalPlayerFrameController", "AdvanceBeforeNetwork"),
("SelectionInteractionController", "DrainOutbound"),
("LiveEntityAnimationScheduler", "Tick"),
("RetailStaticAnimatingObjectScheduler", "Tick"),
("LiveEntityAnimationPresenter", "Present"),
("EquippedChildRenderController", "Tick"),
("RetailStaticAnimatingObjectScheduler", "ProcessHooks"),
("LiveEffectFrameController", "Tick"));
AssertNamedCallOrder(
RequiredMethod(typeof(LiveEffectFrameController), "Tick"),
("TranslucencyFadeManager", "AdvanceAll"),
("AnimationHookFrameQueue", "Drain"),
("EntityEffectController", "RefreshLiveOwnerPoses"),
("ParticleHookSink", "RefreshAttachedEmitters"),
("LiveEntityLightController", "Refresh"),
("ParticleVisibilityController", "Apply"),
("ParticleSystem", "Tick"),
("PhysicsScriptRunner", "Tick"));
AssertNamedCallOrder(
RequiredMethod(typeof(LiveSpatialPresentationReconciler), "Reconcile"),
("EntityEffectController", "RefreshLiveOwnerPoses"),
("EquippedChildRenderController", "ReconcileSpatialMutations"),
("ParticleHookSink", "RefreshAttachedEmitters"),
("LiveEntityLightController", "Refresh"));
AssertSingleNamedCall(live, "RetailStaticAnimatingObjectScheduler", "Tick");
AssertSingleNamedCall(live, "RetailStaticAnimatingObjectScheduler", "ProcessHooks");
AssertSingleNamedCall(live, "LiveEffectFrameController", "Tick");
AssertSingleNamedCall(
RequiredMethod(typeof(LiveEffectFrameController), "Tick"),
"ParticleSystem",
"Tick");
AssertSingleNamedCall(
RequiredMethod(typeof(LiveEffectFrameController), "Tick"),
"PhysicsScriptRunner",
"Tick");
}
[Fact]
public void PlacementRetryRunsAfterStreamingAndInboundBeforeCommandReconcile()
{
AssertCallOrder(
RequiredMethod(typeof(UpdateFrameOrchestrator), nameof(UpdateFrameOrchestrator.Tick)),
(typeof(IStreamingFramePhase), nameof(IStreamingFramePhase.Tick)),
(typeof(IRetailLiveFramePhase), nameof(IRetailLiveFramePhase.Tick)));
AssertCallOrder(
RequiredMethod(typeof(RetailLiveFrameCoordinator), nameof(RetailLiveFrameCoordinator.Tick)),
(typeof(IRuntimeLiveSessionFramePhase), nameof(IRuntimeLiveSessionFramePhase.Tick)),
(typeof(IRuntimePlacementProjectionRetryPhase),
nameof(IRuntimePlacementProjectionRetryPhase.RetryPending)),
(typeof(IPostNetworkCommandFramePhase),
nameof(IPostNetworkCommandFramePhase.RunPostNetworkCommandPhase)),
(typeof(ILiveSpatialReconcilePhase), nameof(ILiveSpatialReconcilePhase.Reconcile)));
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void LiveFrameRetriesPlacementAfterInboundEvenWhenWorldIsQuiesced(
bool worldAvailable)
{
var calls = new List<string>();
var phases = new RecordingLivePhases(calls)
{
IsWorldAvailable = worldAvailable,
};
var frame = new RetailLiveFrameCoordinator(
phases,
new GpuWorldState(),
phases,
phases,
phases,
phases,
phases,
phases);
frame.Tick(1f / 60f);
Assert.Equal(
worldAvailable
? ["objects", "network", "placement-retry", "commands", "reconcile"]
: ["network", "placement-retry", "commands", "render-sync"],
calls);
}
[Fact]
public void GameWindow_ComposesTheLiveFrameOwnersWithoutOwningTheirBodies()
{
IReadOnlyList<CompiledCall> session = CompiledCallGraph.Read(
RequiredMethod(typeof(SessionPlayerCompositionPhase), "CompleteSessionPlayer"));
Assert.Contains(session, call =>
call.Target.DeclaringType == typeof(LiveObjectFrameController)
&& call.Target.IsConstructor);
Assert.Contains(session, call =>
call.Target.DeclaringType == typeof(LiveSpatialPresentationReconciler)
&& call.Target.IsConstructor);
Assert.Contains(
CompiledCallGraph.Read(RequiredMethod(
typeof(FrameRootCompositionPhase),
"ComposeCore")),
call => call.Target.DeclaringType == typeof(RetailLiveFrameCoordinator)
&& call.Target.IsConstructor);
IReadOnlyList<CompiledCall> windowCalls =
CompiledCallGraph.ReadDeclared(typeof(GameWindow));
Assert.DoesNotContain(windowCalls, call =>
call.Target.Name is "AdvanceLiveObjectRuntime"
or "ReconcileLiveObjectSpatialPresentation");
Assert.DoesNotContain(
typeof(GameWindow).GetFields(BindingFlags.Instance | BindingFlags.NonPublic),
field => field.FieldType == typeof(ILiveAnimationPresentationContext));
}
[Fact]
public void GameWindow_DelegatesTheCompleteStreamingFrameBody()
{
Assert.Contains(
CompiledCallGraph.Read(RequiredMethod(
typeof(SessionPlayerCompositionPhase),
"CompleteSessionPlayer")),
call => call.Target.DeclaringType == typeof(StreamingFrameController)
&& call.Target.IsConstructor);
Assert.DoesNotContain(
typeof(GameWindow).GetFields(BindingFlags.Instance | BindingFlags.NonPublic),
field => field.Name == "_streamingFrame");
MethodInfo onUpdate = RequiredMethod(typeof(GameWindow), "OnUpdate");
Assert.Single(
CompiledCallGraph.Read(onUpdate),
call => call.Target.DeclaringType == typeof(GameFrameGraphSlot)
&& call.Target.Name == nameof(GameFrameGraphSlot.Tick));
Assert.DoesNotContain(
CompiledCallGraph.ReadDeclared(typeof(GameWindow)),
call => call.Target.Name is "Compute" or "DrainRescued"
|| call.Target.DeclaringType == typeof(StreamingController)
&& call.Target.Name == nameof(StreamingController.Tick));
}
[Fact]
public void GameWindow_DelegatesTheCompleteGameplayInputFrameBody()
{
Assert.Contains(
CompiledCallGraph.Read(RequiredMethod(
typeof(SessionPlayerCompositionPhase),
"CompleteSessionPlayer")),
call => call.Target.DeclaringType == typeof(GameplayInputFrameController)
&& call.Target.IsConstructor);
HashSet<string> displacedWindowCalls =
[
"TryTakeRawSample",
"CaptureMovementInput",
"EndMouseLookAndRestoreCursor",
"HideCursorForMouseLook",
"RestoreCursorAfterMouseLook",
"CanStartLiveCombatAttack",
"SendLiveCombatAttack",
"PreparePlayerForAttackRequest",
"DumpMovementTruthOutbound",
"DumpMovementTruthServerEcho",
"EndMouseLook",
];
Assert.DoesNotContain(
CompiledCallGraph.ReadDeclared(typeof(GameWindow)),
call => displacedWindowCalls.Contains(call.Target.Name)
|| call.Target.DeclaringType == typeof(GameplayInputFrameController)
&& call.Target.Name == nameof(GameplayInputFrameController.Tick));
Assert.Contains(
CompiledCallGraph.Read(RequiredMethod(
typeof(CameraPointerInputController),
nameof(CameraPointerInputController.HandleFocusChanged))),
call => call.Target.DeclaringType == typeof(GameplayInputFrameController)
&& call.Target.Name == nameof(GameplayInputFrameController.EndMouseLook));
AssertCallOrder(
RequiredMethod(
typeof(LocalPlayerTeleportController),
nameof(LocalPlayerTeleportController.OnTeleportStarted)),
(typeof(RuntimeWorldTransitState),
nameof(RuntimeWorldTransitState.CanQueueTeleportStart)),
(typeof(ILocalPlayerTeleportInputLifetime),
nameof(ILocalPlayerTeleportInputLifetime.EndMouseLook)),
(typeof(RuntimeWorldTransitState),
nameof(RuntimeWorldTransitState.TryQueueTeleportStart)));
Assert.Contains(
CompiledCallGraph.Read(RequiredMethod(typeof(PlayerModeController), "Exit")),
call => call.Target.Name == "EndMouseLook");
MethodInfo focus = RequiredMethod(typeof(GameWindow), "OnFocusChanged");
Assert.Single(
CompiledCallGraph.Read(focus),
call => call.Target.DeclaringType == typeof(CameraPointerInputController)
&& call.Target.Name == nameof(CameraPointerInputController.HandleFocusChanged));
}
[Fact]
public void GameWindow_DelegatesTheCompleteLocalTeleportLifetime()
{
MethodInfo complete = RequiredMethod(
typeof(SessionPlayerCompositionPhase),
"CompleteSessionPlayer");
MethodBase presentationFactory = ReferencedMethodConstructing(
complete,
typeof(LocalPlayerTeleportPresentation));
Assert.NotNull(ReferencedMethodConstructing(
presentationFactory,
typeof(LocalPlayerTeleportController)));
HashSet<string> removedFields =
[
"_teleportTransit",
"_teleportAnim",
"_teleportViewPlane",
"_pendingTeleport",
];
Assert.DoesNotContain(
typeof(GameWindow).GetFields(BindingFlags.Instance | BindingFlags.NonPublic),
field => removedFields.Contains(field.Name));
Assert.DoesNotContain(
CompiledCallGraph.ReadDeclared(typeof(GameWindow)),
call => call.Target.DeclaringType == typeof(LocalPlayerTeleportController)
&& call.Target.Name == nameof(LocalPlayerTeleportController.Tick)
|| call.Target.Name is "AimTeleportDestination"
or "ResetTeleportTransitState"
or "PlaceTeleportArrival"
or "TryActivatePendingTeleportPresentation");
FieldInfo teleport = Assert.Single(
typeof(AcDream.App.Physics.LiveEntityNetworkUpdateController).GetFields(
BindingFlags.Instance | BindingFlags.NonPublic),
field => field.FieldType == typeof(ILocalPlayerTeleportNetworkSink));
Assert.Equal(typeof(ILocalPlayerTeleportNetworkSink), teleport.FieldType);
Assert.DoesNotContain(
typeof(AcDream.App.Physics.LiveEntityNetworkUpdateController).GetFields(
BindingFlags.Instance | BindingFlags.NonPublic),
field => typeof(Delegate).IsAssignableFrom(field.FieldType));
AssertCallOrder(
RequiredMethod(typeof(FrameRootCompositionPhase), "ComposeCore"),
(typeof(LiveEntityLivenessFramePhase), ".ctor"),
(typeof(SessionPlayerResult), "get_LocalTeleport"),
(typeof(PlayerModeAutoEntryFramePhase), ".ctor"),
(typeof(LivePresentationResult), "get_WorldAvailability"),
(typeof(UpdateFrameOrchestrator), ".ctor"));
}
[Fact]
public void GameplayInputOwnersUseTypedSeamsWithoutGameWindowBackReferences()
{
Type[] owners =
[
typeof(AcDream.App.Input.GameplayInputFrameController),
typeof(AcDream.App.Input.DispatcherMovementInputSource),
typeof(AcDream.App.Input.MouseLookController),
];
foreach (Type owner in owners)
{
FieldInfo[] fields = owner.GetFields(
BindingFlags.Instance | BindingFlags.NonPublic);
Assert.DoesNotContain(fields, field => field.FieldType == typeof(GameWindow));
Assert.DoesNotContain(
fields,
field => typeof(Delegate).IsAssignableFrom(field.FieldType));
}
Assert.DoesNotContain(
typeof(AcDream.App.Input.MouseLookController).GetFields(
BindingFlags.Instance | BindingFlags.NonPublic),
field => field.FieldType == typeof(Silk.NET.Input.IMouse));
FieldInfo combatOperations = Assert.Single(
typeof(AcDream.Runtime.Gameplay.RuntimeCombatAttackState).GetFields(
BindingFlags.Instance | BindingFlags.NonPublic),
field => field.Name == "_operations");
Assert.Equal(
typeof(AcDream.Runtime.Gameplay.IRuntimeCombatAttackOperations),
combatOperations.FieldType);
Assert.DoesNotContain(
typeof(AcDream.App.Combat.LiveCombatAttackOperations).GetFields(
BindingFlags.Instance | BindingFlags.NonPublic),
field => typeof(Delegate).IsAssignableFrom(field.FieldType));
FieldInfo combatTargets = Assert.Single(
typeof(AcDream.App.Combat.LiveCombatAttackOperations).GetFields(
BindingFlags.Instance | BindingFlags.NonPublic),
field => field.Name == "_targets");
Assert.Equal(
typeof(AcDream.App.Combat.ICombatAttackTargetSource),
combatTargets.FieldType);
Assert.DoesNotContain(
typeof(AcDream.App.Combat.CombatAttackTargetSource).GetFields(
BindingFlags.Instance | BindingFlags.NonPublic),
field => typeof(Delegate).IsAssignableFrom(field.FieldType));
Assert.DoesNotContain(
typeof(AcDream.App.Combat.CombatAttackTargetSource).GetFields(
BindingFlags.Instance | BindingFlags.NonPublic),
field => field.FieldType
== typeof(AcDream.App.Interaction.SelectionInteractionController));
FieldInfo outboundDiagnostics = Assert.Single(
typeof(LocalPlayerOutboundController).GetFields(
BindingFlags.Instance | BindingFlags.NonPublic),
field => field.Name == "_diagnostic");
Assert.Equal(
typeof(IMovementTruthDiagnosticSink),
outboundDiagnostics.FieldType);
Assert.DoesNotContain(
typeof(AcDream.App.Input.MovementTruthDiagnosticController).GetFields(
BindingFlags.Instance | BindingFlags.NonPublic),
field => typeof(Delegate).IsAssignableFrom(field.FieldType));
FieldInfo approachCompletions = Assert.Single(
typeof(AcDream.App.Input.PlayerModeController).GetFields(
BindingFlags.Instance | BindingFlags.NonPublic),
field => field.Name == "_approachCompletions");
Assert.Equal(
typeof(AcDream.App.Interaction.IPlayerApproachCompletionLifetimeOwner),
approachCompletions.FieldType);
Assert.DoesNotContain(
typeof(AcDream.App.Input.PlayerModeController).GetFields(
BindingFlags.Instance | BindingFlags.NonPublic),
field => field.FieldType
== typeof(AcDream.App.Interaction.SelectionInteractionController));
Assert.DoesNotContain(
typeof(AcDream.App.Interaction.PlayerApproachCompletionState).GetFields(
BindingFlags.Instance | BindingFlags.NonPublic),
field => typeof(Delegate).IsAssignableFrom(field.FieldType));
// C3c (clause 4 — sealed-setter lifecycle routing): the movement
// controller, physics body, host, and committed placement are
// Runtime-owned, published by the first-entry conductor's
// publication transaction. Player-mode entry attaches presentation
// only: it gates on the Runtime-published controller, then wires
// camera -> shadow -> host slot -> mode flag. The old pinned
// markers (PreparePositionForCommit / InstallOrRebind /
// CommitPreparedPosition / `_controllerSlot.Controller =`) were
// exactly the App-side controller construction+commit this flip
// deleted.
MethodInfo attach = RequiredMethod(typeof(PlayerModeController), "BuildControllerAndCamera");
AssertNamedCallOrder(
attach,
("PlayerMovementController", "get_IsRuntimePublished"),
("CameraController", "EnterChaseMode"),
("LocalPlayerShadowSynchronizer", "SyncPose"),
("LocalPlayerPhysicsHostSlot", "set_Host"),
("LocalPlayerModeState", "set_IsPlayerMode"));
Assert.Contains(
CompiledCallGraph.Read(attach),
call => call.Target.DeclaringType?.Name == "LocalPlayerShadowSynchronizer"
&& call.Target.Name == "Restore");
Assert.Contains(
CompiledCallGraph.Read(attach),
call => call.Target.DeclaringType?.Name == "CameraController"
&& call.Target.Name == "RestoreState");
AssertNamedCallOrder(
RequiredMethod(typeof(MouseLookController), nameof(MouseLookController.Tick)),
("PlayerMovementController", "StopMouseDrift"),
("RetailChaseCamera", "FilterMouseDelta"),
("MouseLookState", "ApplyDelta"));
ConstructorInfo[] localFrameConstructors =
typeof(RetailLocalPlayerFrameController).GetConstructors(
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
Assert.NotEmpty(localFrameConstructors);
Assert.All(
localFrameConstructors,
constructor =>
{
Type[] parameters = constructor.GetParameters()
.Select(parameter => parameter.ParameterType)
.ToArray();
Assert.Contains(typeof(IMovementInputSource), parameters);
Assert.DoesNotContain(parameters, type =>
type.IsGenericType
&& type.GetGenericTypeDefinition() == typeof(Func<>));
});
}
[Fact]
public void CameraAndLocalPlayerFrameOwnersUseTypedSeamsWithoutWindowCallbacks()
{
Type[] owners =
[
typeof(AcDream.App.Rendering.CameraFrameController),
typeof(AcDream.App.Input.RetailLocalPlayerFrameController),
typeof(AcDream.App.Input.LocalPlayerProjectionController),
typeof(AcDream.App.Input.LiveLocalPlayerFrameRuntime),
typeof(AcDream.App.Input.LiveLocalPlayerProjectionRuntime),
typeof(AcDream.App.Combat.CombatCameraTargetSource),
];
foreach (Type owner in owners)
{
FieldInfo[] fields = owner.GetFields(
BindingFlags.Instance | BindingFlags.NonPublic);
Assert.DoesNotContain(fields, field => field.FieldType == typeof(GameWindow));
Assert.DoesNotContain(
fields,
field => typeof(Delegate).IsAssignableFrom(field.FieldType));
}
MethodInfo onUpdate = RequiredMethod(typeof(GameWindow), "OnUpdate");
Assert.Single(
CompiledCallGraph.Read(onUpdate),
call => call.Target.DeclaringType == typeof(GameFrameGraphSlot)
&& call.Target.Name == nameof(GameFrameGraphSlot.Tick));
HashSet<string> displacedCalls =
[
"CanAdvanceLocalPlayer",
"GetCombatCameraTargetPoint",
"TryGetPresentationAfterNetwork",
];
Assert.DoesNotContain(
CompiledCallGraph.ReadDeclared(typeof(GameWindow)),
call => displacedCalls.Contains(call.Target.Name)
|| call.Target.DeclaringType?.Name == "FlyCamera"
&& call.Target.Name == "Update");
Assert.DoesNotContain(
typeof(GameWindow).GetFields(BindingFlags.Instance | BindingFlags.NonPublic),
field => field.Name == "_cameraFrame");
AssertNamedCallOrder(
RequiredMethod(typeof(CameraFrameController), nameof(CameraFrameController.Tick)),
("RetailLocalPlayerFrameController", "TryGetPresentationAfterNetwork"),
("ILiveSpatialReconcilePhase", "Reconcile"),
("ChaseCamera", "Update"),
("RetailChaseCamera", "Update"));
}
[Fact]
public void GameWindow_OnUpdateOwnsOnlyProfilingAndOneOrchestratorTick()
{
MethodInfo update = RequiredMethod(typeof(GameWindow), "OnUpdate");
AssertCallOrder(
update,
(typeof(AcDream.App.Diagnostics.FrameProfiler), "BeginStage"),
(typeof(GameFrameGraphSlot), nameof(GameFrameGraphSlot.Tick)));
Assert.Single(
CompiledCallGraph.Read(update),
call => call.Target.DeclaringType == typeof(GameFrameGraphSlot)
&& call.Target.Name == nameof(GameFrameGraphSlot.Tick));
Assert.DoesNotContain(
CompiledCallGraph.Read(update),
call => call.Target.DeclaringType == typeof(UpdateFrameClock)
&& call.Target.Name == nameof(UpdateFrameClock.Advance)
|| call.Target.Name is "TryEnter"
|| call.Target.DeclaringType == typeof(LiveEntityLivenessFramePhase)
&& call.Target.Name == nameof(LiveEntityLivenessFramePhase.Tick));
Assert.DoesNotContain(
typeof(GameWindow).GetFields(BindingFlags.Instance | BindingFlags.NonPublic),
field => field.Name == "_liveFrameCoordinator");
}
[Fact]
public void ProductionFrameAdaptersRetainTypedOwnersWithoutWindowCallbacks()
{
Type[] adapters =
[
typeof(LiveEntityTeardownFramePhase),
typeof(ConsoleUpdateFrameFailureSink),
typeof(PhysicsScriptClockPublisher),
typeof(LiveEntityLivenessFramePhase),
typeof(PlayerModeAutoEntryFramePhase),
];
foreach (Type adapter in adapters)
{
FieldInfo[] fields = adapter.GetFields(
BindingFlags.Instance | BindingFlags.NonPublic);
Assert.DoesNotContain(fields, field => field.FieldType == typeof(GameWindow));
Assert.DoesNotContain(
fields,
field => typeof(Delegate).IsAssignableFrom(field.FieldType));
}
FieldInfo clock = Assert.Single(
typeof(LiveEntityLivenessFramePhase).GetFields(
BindingFlags.Instance | BindingFlags.NonPublic),
field => field.Name == "_clock");
Assert.Equal(typeof(IClientMonotonicTimeSource), clock.FieldType);
FieldInfo teardownRuntime = Assert.Single(
typeof(LiveEntityTeardownFramePhase).GetFields(
BindingFlags.Instance | BindingFlags.NonPublic));
Assert.Equal(typeof(LiveEntityRuntime), teardownRuntime.FieldType);
Type[] typedProductionOwners =
[
typeof(LiveEntityLivenessController),
typeof(AcDream.App.Input.LivePlayerModeAutoEntryContext),
typeof(LiveSessionLocalPhysicsTimestampPublisher),
typeof(AcDream.App.Physics.LiveEntityNetworkUpdateController),
typeof(AcDream.App.Rendering.LiveEntityPartArrayLifecycle),
typeof(AcDream.App.Net.LiveEntitySessionController),
];
foreach (Type owner in typedProductionOwners)
{
FieldInfo[] fields = owner.GetFields(
BindingFlags.Instance | BindingFlags.NonPublic);
Assert.DoesNotContain(fields, field => field.FieldType == typeof(GameWindow));
Assert.DoesNotContain(
fields,
field => typeof(Delegate).IsAssignableFrom(field.FieldType));
}
FieldInfo originIdentity = Assert.Single(
typeof(LiveEntityWorldOriginCoordinator).GetFields(
BindingFlags.Instance | BindingFlags.NonPublic),
field => field.Name == "_identity");
Assert.Equal(
typeof(AcDream.App.Input.ILocalPlayerIdentitySource),
originIdentity.FieldType);
HashSet<string> displacedWindowCalls =
[
"PublishLocalPhysicsTimestamps",
"LoginWorldReady",
"OnPrune",
"CreateLiveEntitySessionSink",
"CreateLiveSessionEventRouter",
"CellLocalForSeed",
"OnPlayScriptReceived",
];
Assert.DoesNotContain(
CompiledCallGraph.ReadDeclared(typeof(GameWindow)),
call => displacedWindowCalls.Contains(call.Target.Name));
Assert.DoesNotContain(
typeof(GameWindow).GetMethods(
BindingFlags.Instance | BindingFlags.Static
| BindingFlags.Public | BindingFlags.NonPublic),
method => displacedWindowCalls.Contains(method.Name));
MethodBase presentationFactory = ReferencedMethodConstructing(
RequiredMethod(typeof(LivePresentationCompositionPhase), "ComposeCore"),
typeof(LiveEntityPresentationController));
Assert.Contains(
CompiledCallGraph.ReadMethodReferences(presentationFactory),
call => call.Target.DeclaringType == typeof(LiveWorldOriginState)
&& call.Target.Name == nameof(LiveWorldOriginState.GetCenter));
// C4 route 4b-3 deleted the sole consumer of
// d.WorldOrigin.CellLocalForSeed in this file — the standalone
// RemoteTeleportController construction. LiveWorldOriginState.
// CellLocalForSeed itself is not deleted (LocalPlayerTeleportController
// still uses it).
MethodInfo eventRouter = RequiredMethod(typeof(LiveSessionRuntimeFactory), "CreateEventRouter");
Assert.Contains(
CompiledCallGraph.Read(eventRouter),
call => call.Target.DeclaringType == typeof(LiveEntitySessionController)
&& call.Target.Name == nameof(LiveEntitySessionController.CreateSink));
Assert.DoesNotContain(
typeof(LiveSessionRuntimeFactory).GetFields(
BindingFlags.Instance | BindingFlags.NonPublic),
field => field.FieldType == typeof(GameWindow));
Assert.DoesNotContain(
typeof(LiveSessionRuntimeFactory).GetConstructors(
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.SelectMany(constructor => constructor.GetParameters()),
parameter => parameter.ParameterType == typeof(GameWindow));
}
private static UpdateFrameOrchestrator Create(
List<string> calls,
RecordingTeardown? teardown = null,
RecordingFailureSink? failureSink = null,
FrameObservations? observed = null,
bool teleportPlace = false,
bool inboundCreatedPlayer = false,
IWorldGenerationAvailability? availability = null)
{
teardown ??= new RecordingTeardown(calls);
failureSink ??= new RecordingFailureSink();
return new UpdateFrameOrchestrator(
teardown,
failureSink,
new UpdateFrameClock(),
new RecordingClockPublisher(calls, observed),
new RecordingStreaming(calls),
new RecordingInput(calls, observed),
new RecordingLiveFrame(calls, observed),
new RecordingLiveness(calls),
new RecordingTeleport(calls, observed, teleportPlace),
new RecordingAutoEntry(calls),
new RecordingCamera(calls, observed, inboundCreatedPlayer),
new RecordingCommit(calls),
availability);
}
private sealed class RecordingTeardown(List<string> calls)
: IUpdateFrameTeardownPhase
{
public Exception? Failure { get; set; }
public int Attempts { get; private set; }
public void RetryPendingTeardowns()
{
calls.Add("teardown");
Attempts++;
if (Failure is not null)
throw Failure;
}
}
private sealed class RecordingFailureSink : IUpdateFrameFailureSink
{
public List<AggregateException> Errors { get; } = [];
public void ReportTeardownFailure(AggregateException error) => Errors.Add(error);
}
private sealed class RecordingClockPublisher(
List<string> calls,
FrameObservations? observed) : IUpdateFrameScriptClockPublisher
{
public void PublishTime(double scriptTime)
{
calls.Add("clock");
observed?.PublishedTimes.Add(scriptTime);
}
}
private sealed class RecordingStreaming(List<string> calls) : IStreamingFramePhase
{
public void Tick() => calls.Add("streaming");
}
private sealed class RecordingInput(
List<string> calls,
FrameObservations? observed) : IGameplayInputFramePhase
{
public void Tick(UpdateFrameTiming timing)
{
calls.Add("input");
observed?.Input.Add(timing);
}
}
private sealed class RecordingLiveFrame(
List<string> calls,
FrameObservations? observed) : IRetailLiveFramePhase
{
public void Tick(float deltaSeconds)
{
observed?.LiveDeltas.Add(deltaSeconds);
calls.Add("objects");
calls.Add("network");
calls.Add("commands");
calls.Add("ordinary-reconcile");
}
}
private sealed class RecordingLiveness(List<string> calls)
: ILiveEntityLivenessFramePhase
{
public void Tick() => calls.Add("liveness");
}
private sealed class RecordingTeleport(
List<string> calls,
FrameObservations? observed,
bool place)
: ILocalPlayerTeleportFramePhase
{
public void Tick(float deltaSeconds)
{
observed?.TeleportDeltas.Add(deltaSeconds);
if (!place)
{
calls.Add("teleport");
return;
}
calls.Add("teleport-place");
calls.Add("teleport-reconcile");
calls.Add("teleport-reveal");
}
}
private sealed class RecordingAutoEntry(List<string> calls)
: IPlayerModeAutoEntryFramePhase
{
public void TryEnter() => calls.Add("auto-entry");
}
private sealed class RecordingCamera(
List<string> calls,
FrameObservations? observed,
bool inboundCreatedPlayer) : ICameraFramePhase
{
public void Tick(UpdateFrameTiming timing)
{
if (inboundCreatedPlayer)
{
calls.Add("inbound-player-projection");
calls.Add("inbound-player-reconcile");
}
calls.Add("camera");
observed?.Camera.Add(timing);
}
}
private sealed class RecordingLivePhases(List<string> calls) :
ILiveObjectFramePhase,
IRuntimeLiveSessionFramePhase,
IPostNetworkCommandFramePhase,
ILiveSpatialReconcilePhase,
IRenderProjectionSyncPhase,
IWorldGenerationAvailability,
IRuntimePlacementProjectionRetryPhase
{
public bool IsWorldAvailable { get; set; }
public long QuiescedGeneration => IsWorldAvailable ? 0L : 1L;
public void Tick(float deltaSeconds) => calls.Add("objects");
public void Tick() => calls.Add("network");
public void RunPostNetworkCommandPhase() => calls.Add("commands");
public void Reconcile() => calls.Add("reconcile");
public void SynchronizeActiveSources() => calls.Add("render-sync");
public void RetryPending() => calls.Add("placement-retry");
}
private sealed class RecordingCommit(List<string> calls)
: IUpdateFrameCommitPhase
{
public void Commit() => calls.Add("commit");
}
private sealed class FrameObservations
{
public List<double> PublishedTimes { get; } = [];
public List<UpdateFrameTiming> Input { get; } = [];
public List<float> LiveDeltas { get; } = [];
public List<float> TeleportDeltas { get; } = [];
public List<UpdateFrameTiming> Camera { get; } = [];
}
private static MethodInfo RequiredMethod(Type owner, string name) =>
owner.GetMethod(
name,
BindingFlags.Instance | BindingFlags.Static
| BindingFlags.Public | BindingFlags.NonPublic)
?? throw new MissingMethodException(owner.FullName, name);
private static MethodBase ReferencedMethodConstructing(
MethodBase owner,
Type constructed) =>
Assert.Single(
CompiledCallGraph.ReadMethodReferences(owner)
.Select(reference => reference.Target)
.Where(method => method.GetMethodBody() is not null)
.Distinct(),
method => Constructs(method, constructed));
private static bool Constructs(MethodBase method, Type type) =>
method.GetMethodBody() is not null
&& CompiledCallGraph.Read(method).Any(call =>
call.Target.DeclaringType == type && call.Target.IsConstructor);
private static void AssertCallOrder(
MethodBase method,
params (Type Type, string Method)[] expected)
{
IReadOnlyList<CompiledCall> calls = CompiledCallGraph.Read(method);
int cursor = -1;
foreach ((Type type, string name) in expected)
{
int found = Enumerable.Range(cursor + 1, calls.Count - cursor - 1)
.FirstOrDefault(index => calls[index].Target.DeclaringType == type
&& calls[index].Target.Name == name, -1);
Assert.True(found > cursor,
$"Missing compiled edge after {cursor}: {type.FullName}.{name}.");
cursor = found;
}
}
private static void AssertNamedCallOrder(
MethodBase method,
params (string Type, string Method)[] expected)
{
IReadOnlyList<CompiledCall> calls = CompiledCallGraph.Read(method);
int cursor = -1;
foreach ((string type, string name) in expected)
{
int found = Enumerable.Range(cursor + 1, calls.Count - cursor - 1)
.FirstOrDefault(index => calls[index].Target.DeclaringType?.Name == type
&& calls[index].Target.Name == name, -1);
Assert.True(found > cursor,
$"Missing compiled edge after {cursor}: {type}.{name}.");
cursor = found;
}
}
private static void AssertSingleNamedCall(
MethodBase method,
string type,
string name) =>
Assert.Single(
CompiledCallGraph.Read(method),
call => call.Target.DeclaringType?.Name == type
&& call.Target.Name == name);
}