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(); 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() { // Checkpoint A freezes only the outer ownership contract. Checkpoints // E and F must replace these probes with production teleport/camera // owner tests before the conditional edges can be claimed guarded. var calls = new List(); 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(); 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(); 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(); 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(); var teardown = new RecordingTeardown(calls) { Failure = new InvalidOperationException("fatal"), }; UpdateFrameOrchestrator frame = Create(calls, teardown: teardown); InvalidOperationException error = Assert.Throws( () => 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)); if (owner == typeof(AcDream.App.Input.RetailLocalPlayerFrameController)) { // Checkpoint D replaced input capture with its typed owner. // Checkpoint F still owns the remaining player presentation // callback composition. No other phase owner may add one. Assert.Contains( ownerFields, field => typeof(Delegate).IsAssignableFrom(field.FieldType)); Assert.DoesNotContain( ownerFields, field => field.FieldType == typeof(Func)); continue; } 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 source = File.ReadAllText(Path.Combine( FindRepoRoot(), "src", "AcDream.App", "Rendering", "GameWindow.cs")); Assert.Equal(1, source.Split("PublishTime(", StringSplitOptions.None).Length - 1); } [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")); Assert.Contains("new AcDream.App.Update.LiveObjectFrameController(", source); Assert.Contains("new AcDream.App.Update.LiveSpatialPresentationReconciler(", source); Assert.Contains("new AcDream.App.World.RetailLiveFrameCoordinator(", source); 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")); Assert.Contains("new AcDream.App.Streaming.StreamingFrameController(", source); Assert.Equal(1, CountOccurrences(source, "_streamingFrame.Tick();")); AssertAppearsInOrder( source, "_streamingFrame.Tick();", "_gameplayInputFrame!.Tick(frameTiming);", "_liveFrameCoordinator.Tick(frameDelta);"); 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")); Assert.Contains("new AcDream.App.Input.GameplayInputFrameController(", source); Assert.Equal(1, CountOccurrences(source, "_gameplayInputFrame!.Tick(frameTiming);")); 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.Equal(2, CountOccurrences( source, "_gameplayInputFrame?.EndMouseLook();")); Assert.Contains( "new(\"mouse capture\", () => _gameplayInputFrame?.EndMouseLook())", source, 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)", "if (!focused)", "_gameplayInputFrame?.EndMouseLook();"); } [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")); Assert.Contains( "new AcDream.App.Streaming.LocalPlayerTeleportController(", source, StringComparison.Ordinal); Assert.Equal(1, CountOccurrences( source, "_localPlayerTeleport!.Tick(frameDelta);")); 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", networkSource, StringComparison.Ordinal); Assert.Contains("ILocalPlayerTeleportNetworkSink", networkSource, StringComparison.Ordinal); AssertAppearsInOrder( source, "_liveEntityLiveness?.Tick(ClientTimerNow());", "_localPlayerTeleport!.Tick(frameDelta);", "_playerModeAutoEntry?.TryEnter();"); } [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", localPlayerSource, StringComparison.Ordinal); Assert.Contains("IMovementInputSource", localPlayerSource, StringComparison.Ordinal); } private static UpdateFrameOrchestrator Create( List 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 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 Errors { get; } = []; public void ReportTeardownFailure(AggregateException error) => Errors.Add(error); } private sealed class RecordingClockPublisher( List calls, FrameObservations? observed) : IUpdateFrameScriptClockPublisher { public void PublishTime(double scriptTime) { calls.Add("clock"); observed?.PublishedTimes.Add(scriptTime); } } private sealed class RecordingStreaming(List calls) : IStreamingFramePhase { public void Tick() => calls.Add("streaming"); } private sealed class RecordingInput( List calls, FrameObservations? observed) : IGameplayInputFramePhase { public void Tick(UpdateFrameTiming timing) { calls.Add("input"); observed?.Input.Add(timing); } } private sealed class RecordingLiveFrame( List 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 calls) : ILiveEntityLivenessFramePhase { public void Tick() => calls.Add("liveness"); } private sealed class RecordingTeleport( List 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 calls) : IPlayerModeAutoEntryFramePhase { public void TryEnter() => calls.Add("auto-entry"); } private sealed class RecordingCamera( List 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 PublishedTimes { get; } = []; public List Input { get; } = []; public List LiveDeltas { get; } = []; public List TeleportDeltas { get; } = []; public List 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; }