acdream/tests/AcDream.App.Tests/World/UpdateFrameOrchestratorTests.cs
Erik 3628aeb520 refactor(app): compose atomic frame roots
Move the complete update/render construction graph into a typed Phase-8 owner, explicitly carry the content dependencies it consumes, and publish both roots through one exact lease. Extract lifecycle resource sampling and frame-owned late bindings so partial startup and shutdown withdraw the same generation without window callbacks.

Co-authored-by: Codex <codex@openai.com>
2026-07-22 19:02:08 +02:00

1030 lines
38 KiB
C#

using System.Reflection;
using AcDream.App.Rendering;
using AcDream.App.Update;
using AcDream.App.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",
],
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",
],
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",
],
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 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",
];
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),
];
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()
{
string root = FindRepoRoot();
string source = File.ReadAllText(Path.Combine(
root,
"src",
"AcDream.App",
"Rendering",
"GameWindow.cs"));
string adapters = File.ReadAllText(Path.Combine(
root,
"src",
"AcDream.App",
"Update",
"UpdateFrameRuntimeAdapters.cs"));
string frameRoot = File.ReadAllText(Path.Combine(
root,
"src",
"AcDream.App",
"Composition",
"FrameRootComposition.cs"));
Assert.Equal(0, CountOccurrences(source, "PublishTime("));
Assert.Equal(1, CountOccurrences(adapters, "_runner.PublishTime("));
Assert.Contains("new PhysicsScriptClockPublisher(", frameRoot,
StringComparison.Ordinal);
}
[Fact]
public void ExtractedLiveObjectSource_PinsRetailAndRegisteredAdaptationOrder()
{
string source = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Update",
"LiveObjectFrameController.cs"));
AssertAppearsInOrder(
source,
"_localPlayerFrame.AdvanceBeforeNetwork",
"_selectionInteractions?.DrainOutbound",
"_animations.Tick",
"_staticAnimations.Tick",
"_animationPresenter.Present",
"_equippedChildren.Tick",
"_staticAnimations.ProcessHooks",
"_effects.Tick");
AssertAppearsInOrder(
source,
"_translucencyFades.AdvanceAll",
"_animationHooks.Drain",
"_entityEffects.RefreshLiveOwnerPoses",
"_particleSink.RefreshAttachedEmitters",
"_lights.Refresh",
"_particleVisibility.Apply",
"_particles.Tick",
"_scripts.Tick");
AssertAppearsInOrder(
source,
"public void Reconcile()",
"_entityEffects.RefreshLiveOwnerPoses",
"_equippedChildren.Tick",
"_particleSink.RefreshAttachedEmitters",
"_lights.Refresh");
Assert.Equal(1, CountOccurrences(source, "_staticAnimations.Tick("));
Assert.Equal(1, CountOccurrences(source, "_staticAnimations.ProcessHooks("));
Assert.Equal(1, CountOccurrences(source, "_effects.Tick("));
Assert.Equal(1, CountOccurrences(source, "_particles.Tick("));
Assert.Equal(1, CountOccurrences(source, "_scripts.Tick("));
}
[Fact]
public void GameWindow_ComposesTheLiveFrameOwnersWithoutOwningTheirBodies()
{
string source = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Rendering",
"GameWindow.cs"));
string livePresentation = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Composition",
"LivePresentationComposition.cs"));
string sessionPlayer = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Composition",
"SessionPlayerComposition.cs"));
string frameRoot = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Composition",
"FrameRootComposition.cs"));
Assert.Contains("new LiveObjectFrameController(", sessionPlayer);
Assert.Contains("new LiveSpatialPresentationReconciler(", sessionPlayer);
Assert.Contains("new RetailLiveFrameCoordinator(", frameRoot);
Assert.DoesNotContain("AdvanceLiveObjectRuntime", source, StringComparison.Ordinal);
Assert.DoesNotContain("ReconcileLiveObjectSpatialPresentation", source,
StringComparison.Ordinal);
Assert.DoesNotContain("ILiveAnimationPresentationContext", source,
StringComparison.Ordinal);
}
[Fact]
public void GameWindow_DelegatesTheCompleteStreamingFrameBody()
{
string source = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Rendering",
"GameWindow.cs"));
string sessionPlayer = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Composition",
"SessionPlayerComposition.cs"));
Assert.Contains("new StreamingFrameController(", sessionPlayer);
Assert.DoesNotContain("_streamingFrame", source, StringComparison.Ordinal);
Assert.Equal(1, CountOccurrences(
source,
"_frameGraphs.Tick("));
Assert.DoesNotContain("_streamingController.Tick(observerCx", source,
StringComparison.Ordinal);
Assert.DoesNotContain("DungeonStreamingGate.Compute", source,
StringComparison.Ordinal);
Assert.DoesNotContain("DrainRescued()", source, StringComparison.Ordinal);
}
[Fact]
public void GameWindow_DelegatesTheCompleteGameplayInputFrameBody()
{
string source = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Rendering",
"GameWindow.cs"));
string sessionPlayer = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Composition",
"SessionPlayerComposition.cs"));
Assert.Contains("new GameplayInputFrameController(", sessionPlayer);
Assert.DoesNotContain("_gameplayInputFrame!.Tick", source,
StringComparison.Ordinal);
Assert.DoesNotContain("_inputDispatcher?.Tick()", source,
StringComparison.Ordinal);
Assert.DoesNotContain("TryTakeRawSample", source,
StringComparison.Ordinal);
Assert.DoesNotContain("_combatAttackController?.Tick()", source,
StringComparison.Ordinal);
Assert.DoesNotContain("CaptureMovementInput", source,
StringComparison.Ordinal);
Assert.DoesNotContain("EndMouseLookAndRestoreCursor", source,
StringComparison.Ordinal);
Assert.DoesNotContain("HideCursorForMouseLook", source,
StringComparison.Ordinal);
Assert.DoesNotContain("RestoreCursorAfterMouseLook", source,
StringComparison.Ordinal);
Assert.DoesNotContain("CanStartLiveCombatAttack", source,
StringComparison.Ordinal);
Assert.DoesNotContain("SendLiveCombatAttack", source,
StringComparison.Ordinal);
Assert.DoesNotContain("PreparePlayerForAttackRequest", source,
StringComparison.Ordinal);
Assert.DoesNotContain("DumpMovementTruthOutbound", source,
StringComparison.Ordinal);
Assert.DoesNotContain("DumpMovementTruthServerEcho", source,
StringComparison.Ordinal);
Assert.DoesNotContain("wantCaptureMouse: ()", source,
StringComparison.Ordinal);
Assert.DoesNotContain(
"_gameplayInputFrame?.EndMouseLook",
source,
StringComparison.Ordinal);
string pointerSource = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Input",
"CameraPointerInputController.cs"));
Assert.Contains("_gameplayFrame?.EndMouseLook();", pointerSource,
StringComparison.Ordinal);
string teleportSource = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Streaming",
"LocalPlayerTeleportController.cs"));
AssertAppearsInOrder(
teleportSource,
"_transit.CanBegin(teleportSequence)",
"_input.EndMouseLook();",
"_transit.QueueStart(teleportSequence)");
string playerModeSource = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Input",
"PlayerModeController.cs"));
Assert.Contains("_input.EndMouseLook();", playerModeSource,
StringComparison.Ordinal);
AssertAppearsInOrder(
source,
"private void OnFocusChanged(bool focused)",
"_cameraPointerInput?.HandleFocusChanged(focused);");
}
[Fact]
public void GameWindow_DelegatesTheCompleteLocalTeleportLifetime()
{
string root = FindRepoRoot();
string source = File.ReadAllText(Path.Combine(
root,
"src",
"AcDream.App",
"Rendering",
"GameWindow.cs"));
string networkSource = File.ReadAllText(Path.Combine(
root,
"src",
"AcDream.App",
"Physics",
"LiveEntityNetworkUpdateController.cs"));
string sessionPlayer = File.ReadAllText(Path.Combine(
root,
"src",
"AcDream.App",
"Composition",
"SessionPlayerComposition.cs"));
string frameRoot = File.ReadAllText(Path.Combine(
root,
"src",
"AcDream.App",
"Composition",
"FrameRootComposition.cs"));
Assert.Contains(
"new LocalPlayerTeleportController(",
sessionPlayer,
StringComparison.Ordinal);
Assert.DoesNotContain("_localPlayerTeleport!.Tick", source,
StringComparison.Ordinal);
Assert.DoesNotContain("_teleportTransit", source, StringComparison.Ordinal);
Assert.DoesNotContain("_teleportAnim", source, StringComparison.Ordinal);
Assert.DoesNotContain("_teleportViewPlane", source, StringComparison.Ordinal);
Assert.DoesNotContain("_pendingTeleport", source, StringComparison.Ordinal);
Assert.DoesNotContain("AimTeleportDestination", source, StringComparison.Ordinal);
Assert.DoesNotContain("ResetTeleportTransitState", source, StringComparison.Ordinal);
Assert.DoesNotContain("PlaceTeleportArrival", source, StringComparison.Ordinal);
Assert.DoesNotContain("TryActivatePendingTeleportPresentation", source,
StringComparison.Ordinal);
Assert.DoesNotContain("Action<WorldSession.EntityPositionUpdate>", networkSource,
StringComparison.Ordinal);
Assert.Contains("ILocalPlayerTeleportNetworkSink", networkSource,
StringComparison.Ordinal);
AssertAppearsInOrder(
frameRoot,
"new LiveEntityLivenessFramePhase(",
"session.LocalTeleport,",
"new PlayerModeAutoEntryFramePhase(",
"cameraFrame);");
}
[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.App.Combat.CombatAttackController).GetFields(
BindingFlags.Instance | BindingFlags.NonPublic),
field => field.Name == "_operations");
Assert.Equal(
typeof(AcDream.App.Combat.ICombatAttackOperations),
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(AcDream.App.Input.LocalPlayerOutboundController).GetFields(
BindingFlags.Instance | BindingFlags.NonPublic),
field => field.Name == "_diagnostic");
Assert.Equal(
typeof(AcDream.App.Input.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));
string playerModeSource = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Input",
"PlayerModeController.cs"));
AssertAppearsInOrder(
playerModeSource,
"controller.PreparePositionForCommit(",
"_camera.EnterChaseMode(legacyCamera, retailCamera);",
"EntityPhysicsHost.SelectStableHostWithoutRebind(",
"_shadow.SyncPose(",
"EntityPhysicsHost.InstallOrRebind(",
"playerEntity.SetPosition(initial.Position);",
"controller.CommitPreparedPosition();",
"_controllerSlot.Controller = controller;",
"_mode.IsPlayerMode = true;");
Assert.Contains("_shadow.Restore(playerEntity, priorShadow);", playerModeSource,
StringComparison.Ordinal);
Assert.Contains("_camera.RestoreState(priorCamera);", playerModeSource,
StringComparison.Ordinal);
string mouseLookSource = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Input",
"MouseLookController.cs"));
AssertAppearsInOrder(
mouseLookSource,
"controller?.StopMouseDrift",
"retail.FilterMouseDelta",
"_state.ApplyDelta");
string localPlayerSource = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Input",
"RetailLocalPlayerFrameController.cs"));
Assert.DoesNotContain("Func<MovementInput>", localPlayerSource,
StringComparison.Ordinal);
Assert.Contains("IMovementInputSource", localPlayerSource,
StringComparison.Ordinal);
}
[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));
}
string root = FindRepoRoot();
string source = File.ReadAllText(Path.Combine(
root,
"src",
"AcDream.App",
"Rendering",
"GameWindow.cs"));
Assert.Equal(1, CountOccurrences(
source,
"_frameGraphs.Tick("));
Assert.DoesNotContain("CanAdvanceLocalPlayer", source, StringComparison.Ordinal);
Assert.DoesNotContain("GetCombatCameraTargetPoint()", source,
StringComparison.Ordinal);
Assert.DoesNotContain("_cameraController.Fly.Update(", source,
StringComparison.Ordinal);
Assert.DoesNotContain("_localPlayerFrame.TryGetPresentationAfterNetwork", source,
StringComparison.Ordinal);
Assert.DoesNotContain("_cameraFrame", source, StringComparison.Ordinal);
string cameraSource = File.ReadAllText(Path.Combine(
root,
"src",
"AcDream.App",
"Rendering",
"CameraFrameController.cs"));
AssertAppearsInOrder(
cameraSource,
"_localFrame.TryGetPresentationAfterNetwork",
"_spatialReconciler.Reconcile();",
"legacy.Update(",
"retail?.Update(");
}
[Fact]
public void GameWindow_OnUpdateOwnsOnlyProfilingAndOneOrchestratorTick()
{
string source = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Rendering",
"GameWindow.cs"));
Assert.Equal(1, CountOccurrences(
source,
"_frameGraphs.Tick("));
Assert.DoesNotContain("_updateFrameClock.Advance(", source,
StringComparison.Ordinal);
Assert.DoesNotContain("_liveFrameCoordinator", source,
StringComparison.Ordinal);
Assert.DoesNotContain("_liveEntityLiveness?.Tick", source,
StringComparison.Ordinal);
Assert.DoesNotContain("_playerModeAutoEntry?.TryEnter", source,
StringComparison.Ordinal);
AssertAppearsInOrder(
source,
"private void OnUpdate(double dt)",
"_frameProfiler.BeginStage(",
"_frameGraphs.Tick(",
"private void OnRender(double deltaSeconds)");
}
[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.Physics.RemoteShadowPlacementSynchronizer),
typeof(AcDream.App.Physics.RemoteTeleportPlacementPresentation),
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);
string source = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Rendering",
"GameWindow.cs"));
string livePresentation = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Composition",
"LivePresentationComposition.cs"));
string sessionRuntime = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Net",
"LiveSessionRuntimeFactory.cs"));
Assert.DoesNotContain("PublishLocalPhysicsTimestamps", source,
StringComparison.Ordinal);
Assert.DoesNotContain("LoginWorldReady", source,
StringComparison.Ordinal);
Assert.DoesNotContain("candidate => _liveEntityHydration.OnPrune", source,
StringComparison.Ordinal);
Assert.DoesNotContain("CreateLiveEntitySessionSink", source,
StringComparison.Ordinal);
Assert.DoesNotContain("private System.Numerics.Vector3 CellLocalForSeed", source,
StringComparison.Ordinal);
Assert.DoesNotContain("OnPlayScriptReceived", source,
StringComparison.Ordinal);
Assert.Contains("d.WorldOrigin.GetCenter", livePresentation,
StringComparison.Ordinal);
Assert.Contains("d.WorldOrigin.CellLocalForSeed", livePresentation,
StringComparison.Ordinal);
Assert.DoesNotContain("CreateLiveSessionEventRouter", source,
StringComparison.Ordinal);
Assert.Contains("_world.EntitySession.CreateSink()", sessionRuntime,
StringComparison.Ordinal);
Assert.DoesNotContain("GameWindow", sessionRuntime,
StringComparison.Ordinal);
}
private static UpdateFrameOrchestrator Create(
List<string> calls,
RecordingTeardown? teardown = null,
RecordingFailureSink? failureSink = null,
FrameObservations? observed = null,
bool teleportPlace = false,
bool inboundCreatedPlayer = false)
{
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));
}
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 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 string FindRepoRoot()
{
DirectoryInfo? directory = new(AppContext.BaseDirectory);
while (directory is not null)
{
if (File.Exists(Path.Combine(directory.FullName, "AcDream.slnx")))
return directory.FullName;
directory = directory.Parent;
}
throw new DirectoryNotFoundException("Could not find AcDream.slnx.");
}
private static void AssertAppearsInOrder(string source, params string[] markers)
{
int previous = -1;
foreach (string marker in markers)
{
int current = source.IndexOf(marker, previous + 1, StringComparison.Ordinal);
Assert.True(current >= 0, $"Missing source marker: {marker}");
Assert.True(current > previous, $"Out-of-order source marker: {marker}");
previous = current;
}
}
private static int CountOccurrences(string source, string marker) =>
source.Split(marker, StringSplitOptions.None).Length - 1;
}