From 9b940502293276e44da49a9d21680bd14064803a Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 18 Aug 2026 16:40:56 +0200 Subject: [PATCH] test: replace frame orchestration source freezes --- .../2026-08-18-r3-test-truth-ledger.md | 56 ++ .../RenderFrameResourceControllerTests.cs | 112 +-- .../World/UpdateFrameOrchestratorTests.cs | 799 +++++++++--------- 3 files changed, 501 insertions(+), 466 deletions(-) diff --git a/docs/reviews/2026-08-18-r3-test-truth-ledger.md b/docs/reviews/2026-08-18-r3-test-truth-ledger.md index 3534c5b4..0e2ad557 100644 --- a/docs/reviews/2026-08-18-r3-test-truth-ledger.md +++ b/docs/reviews/2026-08-18-r3-test-truth-ledger.md @@ -1391,3 +1391,59 @@ Verification: attributed methods, and reduces direct/total source readers from 39/55 to 32/46. The remaining 46 reconcile to the 22 approved retained policies/contracts and 24 staged replacements. + +## Batch AD frame orchestration and resource-preparation replacement + +Batch AD converts all fourteen approved source readers in +`RenderFrameResourceControllerTests` and `UpdateFrameOrchestratorTests`. No +product source changes and no test is removed. + +The three render-resource checks now inspect built call and field-store order. +They preserve the texture/dispatcher/environment/portal/clip/terrain/lighting +begin sequence, mesh publication before reveal evaluation and particle begin, +and weather evaluation before the elapsed-clock commit. The existing recording +phase test continues to exercise the public prepare sequence and one GPU-slot +read behaviorally; source comments and local expressions are no longer part of +the oracle. + +The update-frame replacements retain the full accepted phase graph and its +existing behavioral fake trace, then use compiled metadata only for production +wiring that cannot safely be invoked without a real session or window: + +- the physics script clock has one typed publisher behind `FrameRootCompositionPhase` + and no `GameWindow` publication edge; +- live-object, static-animation, effect, particle/script, and spatial-reconcile + calls preserve their registered adaptation order and exact-once tails; +- streaming precedes the live coordinator, whose inbound session and placement + retry precede command and spatial reconciliation on both available and + quiesced behavioral paths; +- session/frame composition constructs the live-object, spatial, streaming, + gameplay-input, teleport, liveness, auto-entry, camera, and update owners, + while reflected fields and compiled window calls reject the displaced bodies; +- focus loss, player-mode exit, and teleport start retain their typed + mouse-look cleanup edges, including freshness validation before input cleanup + and Runtime transit commit afterward; +- player presentation attachment retains Runtime-publication validation, + camera/shadow/host/mode order, and both cleanup routes; mouse filtering and + typed movement-input seams are checked from method and constructor metadata; + and +- camera sampling/reconciliation and the thin `OnUpdate` profile-to-graph + handoff remain ordered, while live-session event and world-origin consumers + stay behind their focused typed owners without `GameWindow` back-references. + +Where construction happens inside acquisition or transfer delegates, the +tests follow the compiled delegate target before inspecting its child edge. +This preserves real execution ownership without reverting to lexical source +order across nested expressions. + +Verification follows the user-approved proportionate policy for the remaining +R3 work: + +- all 34 focused frame/resource methods pass; +- the complete `AcDream.App.Tests` hermetic lane passes 5,381/5,381 with zero + skips or failures; +- the complete locked Release build covers all 44 projects with zero warnings + and zero errors; and +- a targeted scan confirms neither owning test file retains a source reader or + its source-slicing helper. The final full inventory and twelve-assembly + Release gate are intentionally deferred to the one R3 closeout run. diff --git a/tests/AcDream.App.Tests/Rendering/RenderFrameResourceControllerTests.cs b/tests/AcDream.App.Tests/Rendering/RenderFrameResourceControllerTests.cs index c19cff39..e16ba0a8 100644 --- a/tests/AcDream.App.Tests/Rendering/RenderFrameResourceControllerTests.cs +++ b/tests/AcDream.App.Tests/Rendering/RenderFrameResourceControllerTests.cs @@ -1,4 +1,13 @@ +using System.Reflection; +using System.Reflection.Emit; +using AcDream.App.Diagnostics; using AcDream.App.Rendering; +using AcDream.App.Rendering.Vfx; +using AcDream.App.Rendering.Wb; +using AcDream.App.Streaming; +using AcDream.App.Tests.Architecture; +using AcDream.App.World; +using AcDream.Core.World; namespace AcDream.App.Tests.Rendering; @@ -47,36 +56,40 @@ public sealed class RenderFrameResourceControllerTests [Fact] public void Production_begin_resources_preserve_the_exact_frame_order() { - string source = ResourceSource(); - // Campaign V slice V4a: the world-HUD and retained-UI TextRenderers no // longer take a per-slot begin call — they now read the shared // IGpuFrame ring reset once per frame by IGpuDevice.BeginFrame() (see // GpuDeviceFrameLifetime), so _worldText/_uiText are gone from this // sequence. - AssertAppearsInOrder( - source, - "_textures?.BeginCompositeTextureFrame();", - "_textures?.TickCompositeTextureCache();", - "_dispatcher?.BeginFrame(gpuSlot);", - "_environmentCells?.BeginFrame(gpuSlot);", - "_portalDepth?.BeginFrame(gpuSlot);", - "_clip?.BeginFrame(gpuSlot);", - "_terrain?.BeginFrame(gpuSlot);", - "_lighting?.BeginFrame(gpuSlot);"); - Assert.DoesNotContain("_profiler.FrameBoundary(", source); + MethodInfo begin = RequiredMethod( + typeof(RuntimeRenderFrameBeginResources), + nameof(RuntimeRenderFrameBeginResources.Begin)); + AssertCallOrder( + begin, + (typeof(TextureCache), nameof(TextureCache.BeginCompositeTextureFrame)), + (typeof(TextureCache), nameof(TextureCache.TickCompositeTextureCache)), + (typeof(WbDrawDispatcher), nameof(WbDrawDispatcher.BeginFrame)), + (typeof(EnvCellRenderer), nameof(EnvCellRenderer.BeginFrame)), + (typeof(PortalDepthMaskRenderer), nameof(PortalDepthMaskRenderer.BeginFrame)), + (typeof(ClipFrame), nameof(ClipFrame.BeginFrame)), + (typeof(TerrainModernRenderer), nameof(TerrainModernRenderer.BeginFrame)), + (typeof(SceneLightingUboBinding), nameof(SceneLightingUboBinding.BeginFrame))); + Assert.DoesNotContain( + CompiledCallGraph.Read(begin), + call => call.Target.DeclaringType == typeof(FrameProfiler) + && call.Target.Name == "FrameBoundary"); } [Fact] public void Production_live_preparation_publishes_meshes_before_reveal_and_particles() { - string source = ResourceSource(); - - AssertAppearsInOrder( - source, - "_meshes?.Tick();", - "_worldReveal?.PrepareAndEvaluate(revealCell);", - "_particles?.BeginFrame(gpuSlot);"); + AssertCallOrder( + RequiredMethod( + typeof(RuntimeRenderFrameLivePreparation), + nameof(RuntimeRenderFrameLivePreparation.Prepare)), + (typeof(WbMeshAdapter), nameof(WbMeshAdapter.Tick)), + (typeof(WorldRevealCoordinator), nameof(WorldRevealCoordinator.PrepareAndEvaluate)), + (typeof(ParticleRenderer), nameof(ParticleRenderer.BeginFrame))); } // Campaign V slice V11 deleted RuntimeRenderFrameClearPhase (the raw-GL @@ -101,11 +114,18 @@ public sealed class RenderFrameResourceControllerTests controller.Tick(0.5d); Assert.Equal(0.75d, controller.ElapsedSeconds); - AssertAppearsInOrder( - ResourceSource(), - "_weather.Tick(", - "nowSeconds: _elapsedSeconds,", - "_elapsedSeconds += deltaSeconds;"); + MethodInfo tick = RequiredMethod( + typeof(RenderWeatherFrameController), + nameof(RenderWeatherFrameController.Tick)); + CompiledCall weather = Assert.Single( + CompiledCallGraph.Read(tick), + call => call.Target.DeclaringType == typeof(WeatherSystem) + && call.Target.Name == nameof(WeatherSystem.Tick)); + CompiledFieldReference elapsedStore = Assert.Single( + CompiledCallGraph.ReadFieldReferences(tick), + reference => reference.Field.Name == "_elapsedSeconds" + && reference.OpCode == OpCodes.Stfld); + Assert.True(weather.Offset < elapsedStore.Offset); } [Fact] @@ -127,38 +147,30 @@ public sealed class RenderFrameResourceControllerTests Assert.False(live.IsWaitingForLogin); } - private static string ResourceSource() => File.ReadAllText(Path.Combine( - FindRepoRoot(), - "src", - "AcDream.App", - "Rendering", - "RenderFrameResourceController.cs")); + 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 void AssertAppearsInOrder(string source, params string[] needles) + private static void AssertCallOrder( + MethodBase method, + params (Type Type, string Method)[] expected) { + IReadOnlyList calls = CompiledCallGraph.Read(method); int cursor = -1; - foreach (string needle in needles) + foreach ((Type type, string name) in expected) { - int next = source.IndexOf(needle, cursor + 1, StringComparison.Ordinal); - Assert.True(next >= 0, $"Missing expected source fragment: {needle}"); - Assert.True(next > cursor, $"Out-of-order source fragment: {needle}"); - cursor = next; + 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 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 sealed class AdvancingSlotSource(int firstSlot) : IRenderFrameSlotSource { public int ReadCount { get; private set; } diff --git a/tests/AcDream.App.Tests/World/UpdateFrameOrchestratorTests.cs b/tests/AcDream.App.Tests/World/UpdateFrameOrchestratorTests.cs index 82149e71..a7b93ce2 100644 --- a/tests/AcDream.App.Tests/World/UpdateFrameOrchestratorTests.cs +++ b/tests/AcDream.App.Tests/World/UpdateFrameOrchestratorTests.cs @@ -1,9 +1,12 @@ 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; @@ -318,104 +321,84 @@ public sealed class UpdateFrameOrchestratorTests [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.DoesNotContain( + CompiledCallGraph.ReadDeclared(typeof(GameWindow)), + call => call.Target.Name == nameof(PhysicsScriptClockPublisher.PublishTime)); - Assert.Equal(0, CountOccurrences(source, "PublishTime(")); - Assert.Equal(1, CountOccurrences(adapters, "_runner.PublishTime(")); - Assert.Contains("new PhysicsScriptClockPublisher(", frameRoot, - StringComparison.Ordinal); + 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() { - string source = File.ReadAllText(Path.Combine( - FindRepoRoot(), - "src", - "AcDream.App", - "Update", - "LiveObjectFrameController.cs")); + 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")); - 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.ReconcileSpatialMutations", - "_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(")); + 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() { - string root = FindRepoRoot(); - string outer = File.ReadAllText(Path.Combine( - root, - "src", - "AcDream.App", - "Update", - "UpdateFrameOrchestrator.cs")); - string live = File.ReadAllText(Path.Combine( - root, - "src", - "AcDream.App", - "World", - "RetailLiveFrameCoordinator.cs")); - - AssertAppearsInOrder( - outer, - "_streaming.Tick();", - "_liveFrame.Tick("); - AssertAppearsInOrder( - live, - "_session.Tick();", - "_placementProjectionRetry?.RetryPending();", - "_localPlayer.RunPostNetworkCommandPhase();", - "_spatialReconciler.Reconcile();"); + 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] @@ -451,205 +434,162 @@ public sealed class UpdateFrameOrchestratorTests [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")); + IReadOnlyList 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); - 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); + IReadOnlyList 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() { - 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); + 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() { - 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( + CompiledCallGraph.Read(RequiredMethod( + typeof(SessionPlayerCompositionPhase), + "CompleteSessionPlayer")), + call => call.Target.DeclaringType == typeof(GameplayInputFrameController) + && call.Target.IsConstructor); - 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); + HashSet displacedWindowCalls = + [ + "TryTakeRawSample", + "CaptureMovementInput", + "EndMouseLookAndRestoreCursor", + "HideCursorForMouseLook", + "RestoreCursorAfterMouseLook", + "CanStartLiveCombatAttack", + "SendLiveCombatAttack", + "PreparePlayerForAttackRequest", + "DumpMovementTruthOutbound", + "DumpMovementTruthServerEcho", + "EndMouseLook", + ]; 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.CanQueueTeleportStart(teleportSequence)", - "_input.EndMouseLook();", - "_transit.TryQueueTeleportStart(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);"); + 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() { - 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")); + MethodInfo complete = RequiredMethod( + typeof(SessionPlayerCompositionPhase), + "CompleteSessionPlayer"); + MethodBase presentationFactory = ReferencedMethodConstructing( + complete, + typeof(LocalPlayerTeleportPresentation)); + Assert.NotNull(ReferencedMethodConstructing( + presentationFactory, + typeof(LocalPlayerTeleportController))); - 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", networkSource, - StringComparison.Ordinal); - Assert.Contains("ILocalPlayerTeleportNetworkSink", networkSource, - StringComparison.Ordinal); - AssertAppearsInOrder( - frameRoot, - "new LiveEntityLivenessFramePhase(", - "session.LocalTeleport,", - "new PlayerModeAutoEntryFramePhase(", - "cameraFrame,", - "live.WorldAvailability);"); + HashSet 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] @@ -734,12 +674,6 @@ public sealed class UpdateFrameOrchestratorTests BindingFlags.Instance | BindingFlags.NonPublic), field => typeof(Delegate).IsAssignableFrom(field.FieldType)); - string playerModeSource = File.ReadAllText(Path.Combine( - FindRepoRoot(), - "src", - "AcDream.App", - "Input", - "PlayerModeController.cs")); // 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 @@ -750,40 +684,45 @@ public sealed class UpdateFrameOrchestratorTests // CommitPreparedPosition / `_controllerSlot.Controller =`) were // exactly the App-side controller construction+commit this flip // deleted. - AssertAppearsInOrder( - playerModeSource, - "controller.IsRuntimePublished", - "_camera.EnterChaseMode(legacyCamera, retailCamera);", - "_shadow.SyncPose(", - "_hostSlot.Host = playerHost;", - "_mode.IsPlayerMode = true;"); - Assert.Contains("_shadow.Restore(playerEntity, priorShadow);", playerModeSource, - StringComparison.Ordinal); - Assert.Contains("_camera.RestoreState(priorCamera);", playerModeSource, - StringComparison.Ordinal); + 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"); - string mouseLookSource = File.ReadAllText(Path.Combine( - FindRepoRoot(), - "src", - "AcDream.App", - "Input", - "MouseLookController.cs")); - AssertAppearsInOrder( - mouseLookSource, - "controller?.StopMouseDrift", - "retail.FilterMouseDelta", - "_state.ApplyDelta"); + AssertNamedCallOrder( + RequiredMethod(typeof(MouseLookController), nameof(MouseLookController.Tick)), + ("PlayerMovementController", "StopMouseDrift"), + ("RetailChaseCamera", "FilterMouseDelta"), + ("MouseLookState", "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); + 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] @@ -809,66 +748,56 @@ public sealed class UpdateFrameOrchestratorTests 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); + MethodInfo onUpdate = RequiredMethod(typeof(GameWindow), "OnUpdate"); + Assert.Single( + CompiledCallGraph.Read(onUpdate), + call => call.Target.DeclaringType == typeof(GameFrameGraphSlot) + && call.Target.Name == nameof(GameFrameGraphSlot.Tick)); + HashSet 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"); - string cameraSource = File.ReadAllText(Path.Combine( - root, - "src", - "AcDream.App", - "Rendering", - "CameraFrameController.cs")); - AssertAppearsInOrder( - cameraSource, - "_localFrame.TryGetPresentationAfterNetwork", - "_spatialReconciler.Reconcile();", - "legacy.Update(", - "retail?.Update("); + AssertNamedCallOrder( + RequiredMethod(typeof(CameraFrameController), nameof(CameraFrameController.Tick)), + ("RetailLocalPlayerFrameController", "TryGetPresentationAfterNetwork"), + ("ILiveSpatialReconcilePhase", "Reconcile"), + ("ChaseCamera", "Update"), + ("RetailChaseCamera", "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)"); + 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] @@ -931,49 +860,50 @@ public sealed class UpdateFrameOrchestratorTests 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); + HashSet 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). - Assert.DoesNotContain("CreateLiveSessionEventRouter", source, - StringComparison.Ordinal); - Assert.Contains("_world.EntitySession.CreateSink()", sessionRuntime, - StringComparison.Ordinal); - Assert.DoesNotContain("GameWindow", sessionRuntime, - StringComparison.Ordinal); + 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( @@ -1151,31 +1081,68 @@ public sealed class UpdateFrameOrchestratorTests 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; - } + 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); - throw new DirectoryNotFoundException("Could not find AcDream.slnx."); - } + 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 void AssertAppearsInOrder(string source, params string[] markers) + 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) { - int previous = -1; - foreach (string marker in markers) + IReadOnlyList calls = CompiledCallGraph.Read(method); + int cursor = -1; + foreach ((Type type, string name) in expected) { - 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; + 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 int CountOccurrences(string source, string marker) => - source.Split(marker, StringSplitOptions.None).Length - 1; + private static void AssertNamedCallOrder( + MethodBase method, + params (string Type, string Method)[] expected) + { + IReadOnlyList 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); }