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 ec0a708f..3534c5b4 100644
--- a/docs/reviews/2026-08-18-r3-test-truth-ledger.md
+++ b/docs/reviews/2026-08-18-r3-test-truth-ledger.md
@@ -1341,3 +1341,53 @@ Verification:
attributed methods, and reduces direct/total source readers from 41/64 to
39/55. The remaining 55 reconcile to the 22 approved retained
policies/contracts and 33 staged replacements.
+
+## Batch AC graphical-host and lifetime source-freeze replacement
+
+Batch AC converts all nine approved `GameWindowHostBoundaryTests` source
+readers. No product source changes and no test is removed.
+
+Startup now follows the compiled settings snapshot, frame-pacing, native
+window, callback-target, callback-binding, attachment, and frame-loop edges.
+The callback delegate targets retain their exact load/update/render/closing/
+focus/resize order without treating local names, comments, or nested argument
+spelling as behavior. `OnLoad` phase ordering is recovered from its compiled
+delegate targets, while frame-root and session-player completion checks follow
+their typed construction, binding, attachment, publication, and transfer
+edges.
+
+The remaining owner checks reflect the one `WorldEnvironmentController` and
+`RuntimeSettingsController`, reject their displaced window mirrors, and
+follow environment routing, settings startup/runtime binding, terrain-atlas
+acquisition, input-action attachment, and framebuffer-resize publication
+through the built methods. Focus, update, render, close, and dispose are
+verified as narrow handoffs. In particular, the render check uses semantic IL
+execution order—window size, immutable input construction, then graph render—
+rather than the misleading lexical order of a nested source expression.
+
+Shutdown now uses reflected root types, field stores, built call order, and
+the compiled manifest's stage/operation labels. It preserves the twenty-stage
+dependency order, plugin transfer before startup, ingress cleanup ordering,
+Runtime/UI/resource release boundaries, `TryComplete` before native-window
+release, and the final `_window` clear after the shared shutdown funnel. A
+label such as `input context` may legitimately identify both an operation and
+its stage; the replacement therefore verifies dependency order instead of
+mistaking duplicate human-readable labels for duplicate ownership.
+
+The old checks for HostInputCamera local `if` spelling are retired rather than
+translated. `HostInputCameraCompositionTests` already exercise the complete
+production acquisition and every fault boundary; the names and arrangement
+of compiler locals are not part of the host contract. All valuable ownership,
+ordering, and cleanup rationale remains in the compiled guards above.
+
+Verification:
+
+- all nine focused graphical-host/lifetime methods pass;
+- the complete locked Release build covers all 44 projects with zero warnings
+ and zero errors;
+- the no-retry complete hermetic Release gate remains 14,346/14,346 with zero
+ skips or failures across all 12 test assemblies; and
+- the regenerated 1,254-file inventory parses every file, remains at 11,414
+ 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.
diff --git a/tests/AcDream.App.Tests/Rendering/GameWindowHostBoundaryTests.cs b/tests/AcDream.App.Tests/Rendering/GameWindowHostBoundaryTests.cs
index 7a6243f1..4b085bb0 100644
--- a/tests/AcDream.App.Tests/Rendering/GameWindowHostBoundaryTests.cs
+++ b/tests/AcDream.App.Tests/Rendering/GameWindowHostBoundaryTests.cs
@@ -1,736 +1,723 @@
+using System.Reflection;
+using System.Reflection.Emit;
+using AcDream.App.Combat;
+using AcDream.App.Composition;
+using AcDream.App.Diagnostics;
+using AcDream.App.Input;
+using AcDream.App.Net;
+using AcDream.App.Plugins;
+using AcDream.App.Rendering;
+using AcDream.App.Rendering.Sky;
+using AcDream.App.Settings;
+using AcDream.App.Streaming;
+using AcDream.App.Tests.Architecture;
+using AcDream.App.UI;
+using AcDream.App.Update;
+using AcDream.App.World;
+using AcDream.Runtime.Session;
+using Silk.NET.Windowing;
+
namespace AcDream.App.Tests.Rendering;
///
-/// Source-level architecture guards for the narrow graphical-host composition
-/// boundary. R3 tracks these literal source-shape checks for staged replacement
-/// with semantic owner and lifecycle contracts.
+/// Compiled architecture guards for the narrow graphical-host composition
+/// boundary. These checks protect ownership, publication, and lifecycle edges
+/// without freezing comments, local-variable names, or source formatting.
///
public sealed class GameWindowHostBoundaryTests
{
+ private const BindingFlags Declared = BindingFlags.Instance
+ | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic
+ | BindingFlags.DeclaredOnly;
+
[Fact]
public void Run_PreservesNativeAttributeAndCallbackOrder()
{
- string body = MethodBody("public void Run()", "private void OnLoad()");
+ MethodInfo run = RequiredMethod(typeof(GameWindow), nameof(GameWindow.Run));
+ AssertCallOrder(
+ run,
+ (typeof(RuntimeSettingsController), "get_Startup"),
+ (typeof(DisplayFramePacingController),
+ nameof(DisplayFramePacingController.InitializeStartup)),
+ (typeof(Silk.NET.Windowing.Window), nameof(Silk.NET.Windowing.Window.Create)),
+ (typeof(DisplayFramePacingController),
+ nameof(DisplayFramePacingController.BindSurface)),
+ (typeof(WindowCallbackTargets), ".ctor"),
+ (typeof(SilkWindowCallbackBinding), nameof(SilkWindowCallbackBinding.Create)),
+ (typeof(SilkWindowCallbackBinding), nameof(SilkWindowCallbackBinding.Attach)));
+ AssertSilkWindowCallAfter(
+ run,
+ nameof(IWindow.Run),
+ typeof(SilkWindowCallbackBinding),
+ nameof(SilkWindowCallbackBinding.Attach));
- AssertAppearsInOrder(
- body,
- "RuntimeSettingsSnapshot startup = _runtimeSettings.Startup",
- "_displayFramePacing.InitializeStartup(startup.Display.VSync)",
- "VSync = startupPacing.UseVSync",
- // Vulkan takes MSAA as an RHI attachment property rather than a
- // window attribute. _startupQuality carries it forward instead.
- "_startupQuality = startup.Quality;",
- "Window.Create(options)",
- "_displayFramePacing.BindSurface(",
- "_windowCallbacks = SilkWindowCallbackBinding.Create(",
- "new WindowCallbackTargets(",
- "OnLoad,",
- "OnUpdate,",
- "OnRender,",
- "OnClosing,",
- "OnFocusChanged,",
- "OnFramebufferResize),",
- "_displayFramePacing,",
- "_hostQuiescence);",
- "_windowCallbacks.Attach();",
- "_window.Run();");
-
- Assert.Equal(1, CountOccurrences(body, "SilkWindowCallbackBinding.Create("));
- Assert.Equal(1, CountOccurrences(body, "_windowCallbacks.Attach();"));
- Assert.DoesNotContain("_window.Load +=", body, StringComparison.Ordinal);
- Assert.DoesNotContain("_window.Update +=", body, StringComparison.Ordinal);
- Assert.DoesNotContain("_window.Render +=", body, StringComparison.Ordinal);
- Assert.DoesNotContain("_window.Closing +=", body, StringComparison.Ordinal);
+ IReadOnlyList references =
+ CompiledCallGraph.ReadMethodReferences(run);
+ AssertTargetOrder(
+ references,
+ typeof(GameWindow),
+ "OnLoad",
+ "OnUpdate",
+ "OnRender",
+ "OnClosing",
+ "OnFocusChanged",
+ "OnFramebufferResize");
+ Assert.DoesNotContain(
+ references,
+ call => call.Target.Name is "add_Load" or "add_Update" or "add_Render"
+ or "add_Closing");
}
[Fact]
public void SessionStart_FollowsFrameGraphPublication()
{
- string body = MethodBody(
- "private void OnLoad()",
- "private void OnUpdate(double dt)");
+ AssertOnLoadPhaseOrder(
+ typeof(HostInputCameraCompositionPhase),
+ typeof(ContentEffectsAudioCompositionPhase),
+ typeof(SettingsDevToolsCompositionPhase),
+ typeof(WorldRenderCompositionPhase),
+ typeof(InteractionRetainedUiCompositionPhase),
+ typeof(LivePresentationCompositionPhase),
+ typeof(SessionPlayerCompositionPhase),
+ typeof(FrameRootCompositionPhase),
+ typeof(SessionStartCompositionPhase));
- AssertAppearsInOrder(
- body,
- "GameWindowPlatformResult platform = AcquirePlatform();",
- "GameWindowCompositionPipeline.Run<",
- "new HostInputCameraCompositionPhase(",
- "this).Compose(platformResult),",
- "new ContentEffectsAudioCompositionPhase(",
- "this).Compose(platformResult, hostInputCamera),",
- "new SettingsDevToolsCompositionPhase(",
- ".Compose(platformResult, hostInputCamera, contentEffectsAudio),",
- "new InteractionRetainedUiCompositionPhase(",
- "this).Compose(",
- "new LivePresentationCompositionPhase(",
- "this).Compose(",
- "new SessionPlayerCompositionPhase(",
- "new FrameRootCompositionPhase(",
- "new SessionStartCompositionPhase(",
- ".Start(frameRoots));");
+ AssertCallOrder(
+ RequiredMethod(typeof(FrameRootCompositionPhase), "ComposeCore"),
+ (typeof(RenderFrameOrchestrator), ".ctor"),
+ (typeof(UpdateFrameOrchestrator), ".ctor"),
+ (typeof(GameFrameGraphSlot), nameof(GameFrameGraphSlot.PublishOwned)),
+ (typeof(IGameWindowFrameRootPublication),
+ nameof(IGameWindowFrameRootPublication.PublishFrameRoots)));
- string framePhase = File.ReadAllText(Path.Combine(
- FindRepoRoot(),
- "src",
- "AcDream.App",
- "Composition",
- "FrameRootComposition.cs"));
- AssertAppearsInOrder(
- framePhase,
- "new RenderFrameOrchestrator(",
- "new UpdateFrameOrchestrator(",
- "d.FrameGraphs.PublishOwned(",
- "_publication.PublishFrameRoots(result);");
+ MethodInfo complete = RequiredMethod(
+ typeof(SessionPlayerCompositionPhase),
+ "CompleteSessionPlayer");
+ AssertCallOrder(
+ complete,
+ (typeof(LiveSessionRuntimeFactory), nameof(LiveSessionRuntimeFactory.Create)),
+ (typeof(LiveCombatModeCommandSlot), "BindOwned"),
+ (typeof(RuntimeDiagnosticCommandSlot), "BindOwned"),
+ (typeof(GameplayInputActionRouter), nameof(GameplayInputActionRouter.Create)),
+ (typeof(GameplayInputActionRouter), nameof(GameplayInputActionRouter.Attach)),
+ (typeof(IGameWindowSessionPlayerPublication),
+ nameof(IGameWindowSessionPlayerPublication.PublishSessionPlayer)));
- string sessionPhase = File.ReadAllText(Path.Combine(
- FindRepoRoot(),
- "src",
- "AcDream.App",
- "Composition",
- "SessionPlayerComposition.cs"));
- AssertAppearsInOrder(
- sessionPhase,
- "LiveSessionHost sessionHost = sessionRuntimeFactory.Create(",
- "d.CombatModeCommands.BindOwned(combatCommand)",
- "d.RuntimeDiagnosticCommands.BindOwned(runtimeDiagnostics)",
- "GameplayInputActionRouter.Create(",
- "gameplayActions.Attach();",
- "_publication.PublishSessionPlayer(result);");
- Assert.Equal(1, CountOccurrences(
- body,
- ".Start(frameRoots));"));
- string phaseOne = Slice(
- File.ReadAllText(Path.Combine(
- FindRepoRoot(),
- "src",
- "AcDream.App",
- "Composition",
- "HostInputCameraComposition.cs")),
- "private HostInputCameraResult ComposeCore(",
- "private void Fault(");
- Assert.Contains("if (firstKeyboard is not null)", phaseOne, StringComparison.Ordinal);
- Assert.Contains("if (firstMouse is not null)", phaseOne, StringComparison.Ordinal);
- Assert.Contains(
- "if (keyboard is not null && mouse is not null)",
- phaseOne,
- StringComparison.Ordinal);
- Assert.DoesNotContain(
- "if (firstKeyboard is not null && firstMouse is not null)",
- phaseOne,
- StringComparison.Ordinal);
- string startPhase = File.ReadAllText(Path.Combine(
- FindRepoRoot(),
- "src",
- "AcDream.App",
- "Composition",
- "SessionStartComposition.cs"));
- AssertAppearsInOrder(
- startPhase,
- "frame.GameRuntime.Session.Start(frame.GameRuntime.Generation)",
- "switch (result.Status)",
- "case RuntimeSessionStartStatus.MissingCredentials:",
- "case RuntimeSessionStartStatus.Failed:");
- int start = body.IndexOf(
- ".Start(frameRoots));",
- StringComparison.Ordinal);
- string postStart = body[(start + ".Start(frameRoots));".Length)..];
- Assert.Empty(System.Text.RegularExpressions.Regex.Matches(
- postStart,
- @"(?m)^\s+_[A-Za-z]\w*\s*(?:\?\?=|=)"));
- Assert.DoesNotContain("new ", postStart, StringComparison.Ordinal);
- Assert.DoesNotContain(".Mount(", postStart, StringComparison.Ordinal);
- Assert.DoesNotContain(".Bind(", postStart, StringComparison.Ordinal);
- Assert.DoesNotContain(".Attach(", postStart, StringComparison.Ordinal);
- Assert.DoesNotContain(".Compose(", postStart, StringComparison.Ordinal);
- Assert.DoesNotContain("PrepareResources(", postStart, StringComparison.Ordinal);
+ MethodInfo start = RequiredMethod(typeof(SessionStartCompositionPhase), "Start");
+ IReadOnlyList startCalls = CompiledCallGraph.Read(start);
+ Assert.Contains(startCalls, call => call.Target.Name == "Start");
+ Assert.Contains(startCalls, call => call.Target.Name == "Report");
}
[Fact]
public void WorldEnvironment_IsOwnedAndGameWindowOnlyComposesItsTypedEdges()
{
- string source = GameWindowSource();
- string load = MethodBody(
- "private void OnLoad()",
- "private void OnUpdate(double dt)");
- string sessionFactorySource = File.ReadAllText(Path.Combine(
- FindRepoRoot(),
- "src",
- "AcDream.App",
- "Net",
- "LiveSessionRuntimeFactory.cs"));
- string sessionFactory = Slice(
- sessionFactorySource,
- "private ILiveSessionEventRouting CreateEventRouter(",
- "private LiveInventorySessionBindings CreateInventoryBindings()");
- string worldPhase = File.ReadAllText(Path.Combine(
- FindRepoRoot(),
- "src",
- "AcDream.App",
- "Composition",
- "WorldRenderComposition.cs"));
+ AssertField(typeof(GameWindow), "_worldEnvironment", typeof(WorldEnvironmentController));
+ MethodBase worldDelegate = OnLoadMethodConstructing(
+ typeof(WorldRenderCompositionPhase));
Assert.Contains(
- "private readonly AcDream.App.World.WorldEnvironmentController _worldEnvironment;",
- source,
- StringComparison.Ordinal);
- Assert.Contains(
- "new WorldRenderDependencies(\n _worldEnvironment,",
- load.Replace("\r\n", "\n", StringComparison.Ordinal),
- StringComparison.Ordinal);
- string worldCompose = Slice(
- worldPhase,
- "public WorldRenderResult Compose(",
- "private (BitmapFont? Font, TextRenderer? Text) ComposeOptionalHudResources(");
- AssertAppearsInOrder(
- worldCompose,
- "WorldRegionData region = _factory.LoadRegion(content.Dats);",
- "_factory.InitializeEnvironment(_dependencies.Environment, region.Region);");
- Assert.Contains("environment.Initialize(region);", worldPhase, StringComparison.Ordinal);
- AssertAppearsInOrder(
- sessionFactory,
- "new LiveEnvironmentSessionSink(",
- "_world.Environment.ApplyAdminEnvirons,",
- "_world.Environment.SynchronizeFromServer)");
- Assert.DoesNotContain("_loadedSkyDesc", source, StringComparison.Ordinal);
- Assert.DoesNotContain("_loadedSkyDayIndex", source, StringComparison.Ordinal);
- Assert.DoesNotContain("private void RefreshSkyForCurrentDay()", source, StringComparison.Ordinal);
- Assert.DoesNotContain("private void OnEnvironChanged(", source, StringComparison.Ordinal);
+ CompiledCallGraph.ReadFieldReferences(worldDelegate),
+ reference => reference.Field.Name == "_worldEnvironment");
- Assert.DoesNotContain("private void CycleTimeOfDay()", source, StringComparison.Ordinal);
- Assert.DoesNotContain("private void CycleWeather()", source, StringComparison.Ordinal);
- string sessionPhase = File.ReadAllText(Path.Combine(
- FindRepoRoot(),
- "src",
- "AcDream.App",
- "Composition",
- "SessionPlayerComposition.cs"));
- AssertAppearsInOrder(
- sessionPhase,
- "new RuntimeDiagnosticCommandController(",
- "d.RuntimeDiagnosticCommands.BindOwned(runtimeDiagnostics)");
+ MethodInfo compose = RequiredMethod(typeof(WorldRenderCompositionPhase), "Compose");
+ AssertCallOrder(
+ compose,
+ (typeof(IWorldRenderCompositionFactory),
+ nameof(IWorldRenderCompositionFactory.LoadRegion)),
+ (typeof(IWorldRenderCompositionFactory),
+ nameof(IWorldRenderCompositionFactory.InitializeEnvironment)));
+ Assert.Contains(
+ CompiledCallGraph.Read(RequiredMethod(
+ typeof(RetailWorldRenderCompositionFactory),
+ nameof(RetailWorldRenderCompositionFactory.InitializeEnvironment))),
+ call => call.Target.DeclaringType == typeof(WorldEnvironmentController)
+ && call.Target.Name == nameof(WorldEnvironmentController.Initialize));
+
+ MethodInfo router = RequiredMethod(typeof(LiveSessionRuntimeFactory), "CreateEventRouter");
+ IReadOnlyList routerReferences =
+ CompiledCallGraph.ReadMethodReferences(router);
+ Assert.Contains(
+ routerReferences,
+ call => call.Target.DeclaringType == typeof(LiveEnvironmentSessionSink)
+ && call.Target.IsConstructor);
+ AssertTargetOrder(
+ routerReferences,
+ typeof(WorldEnvironmentController),
+ nameof(WorldEnvironmentController.ApplyAdminEnvirons),
+ nameof(WorldEnvironmentController.SynchronizeFromServer));
+
+ AssertMembersAbsent(
+ typeof(GameWindow),
+ ["_loadedSkyDesc", "_loadedSkyDayIndex"],
+ ["RefreshSkyForCurrentDay", "OnEnvironChanged", "CycleTimeOfDay", "CycleWeather"]);
+
+ MethodInfo complete = RequiredMethod(
+ typeof(SessionPlayerCompositionPhase),
+ "CompleteSessionPlayer");
+ AssertCallOrder(
+ complete,
+ (typeof(RuntimeDiagnosticCommandController), ".ctor"),
+ (typeof(RuntimeDiagnosticCommandSlot), "BindOwned"));
}
[Fact]
public void InputAction_IsOneTypedOwnerHandoff()
{
- string source = GameWindowSource();
- string load = MethodBody(
- "private void OnLoad()",
- "private void OnUpdate(double dt)");
- string sessionPhase = File.ReadAllText(Path.Combine(
- FindRepoRoot(),
- "src",
- "AcDream.App",
- "Composition",
- "SessionPlayerComposition.cs"));
- string shutdown = GameWindowLifetimeSource();
+ AssertMembersAbsent(
+ typeof(GameWindow),
+ [],
+ ["OnInputAction", "SetInputCombatScope", "ToggleLiveCombatMode",
+ "OnUiDragReleasedOutside"]);
+ Assert.DoesNotContain(
+ CompiledCallGraph.ReadDeclared(typeof(GameWindow)),
+ call => call.Target.Name == "add_DragReleasedOutsideUi");
- Assert.DoesNotContain("private void OnInputAction(", source, StringComparison.Ordinal);
- Assert.DoesNotContain("private void SetInputCombatScope(", source, StringComparison.Ordinal);
- Assert.DoesNotContain("private void ToggleLiveCombatMode()", source, StringComparison.Ordinal);
- Assert.DoesNotContain("private void OnUiDragReleasedOutside(", source, StringComparison.Ordinal);
- Assert.DoesNotContain("DragReleasedOutsideUi +=", source, StringComparison.Ordinal);
- Assert.Equal(1, CountOccurrences(
- sessionPhase,
- "GameplayInputActionRouter.Create("));
- Assert.Equal(1, CountOccurrences(
- sessionPhase,
- "gameplayActions.Attach();"));
- Assert.Equal(1, CountOccurrences(
- LivePresentationSource(),
- "RetainedUiGameplayBinding.Create("));
- Assert.Equal(1, CountOccurrences(
- LivePresentationSource(),
- "retainedGameplayLease.Resource.Attach();"));
- AssertAppearsInOrder(
- shutdown,
- "Hard(\"combat command slot\", ingress.CombatCommands.Deactivate)",
- "Hard(\"diagnostic command slot\", ingress.DiagnosticCommands.Deactivate)",
- "Hard(\"retained gameplay\", () => ingress.RetainedGameplay?.Deactivate())",
- "Hard(\"gameplay actions\", () => ingress.GameplayActions?.Deactivate())",
- "Hard(\"game runtime session\", ingress.Runtime.StopSession)",
- "new ResourceShutdownStage(\"physical ingress cleanup\"",
- "Soft(\"retained gameplay\", () => DisposeRetainedGameplay(ingress.RetainedGameplay))",
- "Soft(\"gameplay actions\", () => DisposeGameplayActions(ingress.GameplayActions))",
- "Soft(\"camera pointer\", () => DisposeCameraPointer(ingress.CameraPointer))",
- "new ResourceShutdownStage(\"session dependents\"",
- "Hard(\"mouse capture\", () => live.CameraPointer?.ReleaseMouseLookAfterSessionRetirement())");
+ MethodInfo session = RequiredMethod(
+ typeof(SessionPlayerCompositionPhase),
+ "CompleteSessionPlayer");
+ AssertSingleCall(session, typeof(GameplayInputActionRouter),
+ nameof(GameplayInputActionRouter.Create));
+ AssertSingleCall(session, typeof(GameplayInputActionRouter),
+ nameof(GameplayInputActionRouter.Attach));
+
+ MethodInfo live = RequiredMethod(
+ typeof(LivePresentationCompositionPhase),
+ "CompletePresentation");
+ MethodBase retainedFactory = ReferencedMethodConstructingOrCalling(
+ live,
+ typeof(RetainedUiGameplayBinding),
+ nameof(RetainedUiGameplayBinding.Create));
+ AssertSingleCall(retainedFactory, typeof(RetainedUiGameplayBinding),
+ nameof(RetainedUiGameplayBinding.Create));
+ Assert.Contains(
+ CompiledCallGraph.Read(live),
+ call => call.Target.DeclaringType == typeof(RetainedUiGameplayBinding)
+ && call.Target.Name == nameof(RetainedUiGameplayBinding.Attach));
+
+ IReadOnlyList labels = ShutdownLabels();
+ AssertLabelOrder(
+ labels,
+ "combat command slot",
+ "diagnostic command slot",
+ "retained gameplay",
+ "gameplay actions",
+ "game runtime session",
+ "physical ingress cleanup",
+ "retained gameplay",
+ "gameplay actions",
+ "camera pointer",
+ "session dependents",
+ "mouse capture");
}
[Fact]
public void FramebufferResize_IsOneTypedOwnerHandoff()
{
- string load = MethodBody(
- "private void OnLoad()",
- "private void OnUpdate(double dt)");
- string body = MethodBody(
- "private void OnFramebufferResize(",
- "private void OnClosing()");
-
- Assert.Contains("=> _framebufferResize.Resize(newSize);", body, StringComparison.Ordinal);
- Assert.DoesNotContain("Viewport(", body, StringComparison.Ordinal);
- Assert.DoesNotContain("SetAspect(", body, StringComparison.Ordinal);
- Assert.DoesNotContain("ResetLayout(", body, StringComparison.Ordinal);
- string phaseOne = Slice(
- File.ReadAllText(Path.Combine(
- FindRepoRoot(),
- "src",
- "AcDream.App",
- "Composition",
- "HostInputCameraComposition.cs")),
- "private HostInputCameraResult ComposeCore(",
- "private void Fault(");
- AssertAppearsInOrder(
- phaseOne,
- "_dependencies.FramebufferResize.BindViewport(",
- "_publication.PublishCameraController(camera)",
- "_dependencies.FramebufferResize.BindCamera(",
- "_dependencies.FramebufferResize.Resize(");
+ MethodInfo resize = RequiredMethod(typeof(GameWindow), "OnFramebufferResize");
+ AssertSingleCall(
+ resize,
+ typeof(FramebufferResizeController),
+ nameof(FramebufferResizeController.Resize));
Assert.DoesNotContain(
- "_viewportAspect.Update(_window",
- load,
- StringComparison.Ordinal);
+ CompiledCallGraph.Read(resize),
+ call => call.Target.Name is "Viewport" or "SetAspect" or "ResetLayout");
+
+ AssertCallOrder(
+ RequiredMethod(typeof(HostInputCameraCompositionPhase), "ComposeCore"),
+ (typeof(FramebufferResizeController),
+ nameof(FramebufferResizeController.BindViewport)),
+ (typeof(IGameWindowHostInputCameraPublication),
+ nameof(IGameWindowHostInputCameraPublication.PublishCameraController)),
+ (typeof(FramebufferResizeController),
+ nameof(FramebufferResizeController.BindCamera)),
+ (typeof(FramebufferResizeController), nameof(FramebufferResizeController.Resize)));
+
+ Assert.DoesNotContain(
+ CompiledCallGraph.ReadMethodReferences(RequiredMethod(typeof(GameWindow), "OnLoad")),
+ call => call.Target.Name == "Update"
+ && call.Target.DeclaringType?.Name == "ViewportAspectState");
}
[Fact]
public void RuntimeSettings_IsOneTwoPhaseOwnerWithoutWindowStateMirrors()
{
- string source = GameWindowSource();
- string run = MethodBody("public void Run()", "private void OnLoad()");
- string load = MethodBody(
- "private void OnLoad()",
- "private void OnUpdate(double dt)");
- string shutdown = GameWindowLifetimeSource();
+ AssertField(typeof(GameWindow), "_runtimeSettings", typeof(RuntimeSettingsController));
+ AssertSingleCall(
+ FindConstructorConstructing(typeof(GameWindow), typeof(RuntimeSettingsController)),
+ typeof(RuntimeSettingsController),
+ ".ctor");
+ Assert.DoesNotContain(
+ CompiledCallGraph.ReadDeclared(typeof(GameWindow)),
+ call => call.Target.DeclaringType?.Name == "SettingsStore"
+ || call.Target.Name is "LoadDisplay" or "LoadAudio" or "LoadGameplay"
+ or "LoadChat" or "LoadCharacter");
+ AssertMembersAbsent(
+ typeof(GameWindow),
+ ["_persistedDisplay", "_persistedAudio", "_persistedGameplay",
+ "_persistedChat", "_persistedCharacter", "_activeToonKey",
+ "_settingsStore", "_settingsVm"],
+ ["LoadAndApplyPersistedSettings", "ApplyDisplayWindowState",
+ "ReapplyQualityPreset"]);
+ MethodInfo session = RequiredMethod(
+ typeof(SessionPlayerCompositionPhase),
+ "ComposeCore");
Assert.Contains(
- "private readonly RuntimeSettingsController _runtimeSettings;",
- source,
- StringComparison.Ordinal);
+ CompiledCallGraph.Read(session),
+ call => call.Target.DeclaringType == typeof(RuntimeSettingsController)
+ && call.Target.Name == nameof(RuntimeSettingsController.BindRuntimeTargetsOwned));
Assert.Contains(
- "_runtimeSettings = new RuntimeSettingsController(",
- source,
- StringComparison.Ordinal);
- Assert.DoesNotContain("new SettingsStore(", source, StringComparison.Ordinal);
- Assert.DoesNotContain("new AcDream.UI.Abstractions.Panels.Settings.SettingsStore(", source, StringComparison.Ordinal);
- Assert.DoesNotContain(".LoadDisplay()", source, StringComparison.Ordinal);
- Assert.DoesNotContain(".LoadAudio()", source, StringComparison.Ordinal);
- Assert.DoesNotContain(".LoadGameplay()", source, StringComparison.Ordinal);
- Assert.DoesNotContain(".LoadChat()", source, StringComparison.Ordinal);
- Assert.DoesNotContain(".LoadCharacter(", source, StringComparison.Ordinal);
- Assert.DoesNotContain("_persistedDisplay", source, StringComparison.Ordinal);
- Assert.DoesNotContain("_persistedAudio", source, StringComparison.Ordinal);
- Assert.DoesNotContain("_persistedGameplay", source, StringComparison.Ordinal);
- Assert.DoesNotContain("_persistedChat", source, StringComparison.Ordinal);
- Assert.DoesNotContain("_persistedCharacter", source, StringComparison.Ordinal);
- Assert.DoesNotContain("_activeToonKey", source, StringComparison.Ordinal);
- Assert.DoesNotContain("_settingsStore", source, StringComparison.Ordinal);
- Assert.DoesNotContain("_settingsVm", source, StringComparison.Ordinal);
- Assert.DoesNotContain("LoadAndApplyPersistedSettings", source, StringComparison.Ordinal);
- Assert.DoesNotContain("private void ApplyDisplayWindowState", source, StringComparison.Ordinal);
- Assert.DoesNotContain("public void ReapplyQualityPreset", source, StringComparison.Ordinal);
+ CompiledCallGraph.Read(RequiredMethod(
+ typeof(SettingsDevToolsCompositionPhase),
+ "Compose")),
+ call => call.Target.DeclaringType == typeof(RuntimeSettingsController)
+ && call.Target.Name == nameof(RuntimeSettingsController.ApplyStartup));
- AssertAppearsInOrder(
- run,
- "RuntimeSettingsSnapshot startup = _runtimeSettings.Startup",
- "_displayFramePacing.InitializeStartup(startup.Display.VSync)",
- // Campaign V slice V11: Vulkan needs a client-API-less window and
- // takes neither MSAA nor the stencil bit count as a window
- // attribute (both are RHI attachment properties instead), so the
- // raw-GL "Samples = ..." window option this used to assert is
- // gone. _startupQuality carries MsaaSamples forward instead, into
- // CreateGraphics' VulkanGraphicsContext.Acquire call.
- "_startupQuality = startup.Quality;",
- "Window.Create(options)");
- AssertAppearsInOrder(
- load,
- "GameWindowCompositionPipeline.Run<",
- "new SettingsDevToolsCompositionPhase(",
- ".Compose(platformResult, hostInputCamera, contentEffectsAudio),",
- "new WorldRenderCompositionPhase(",
- "new SessionPlayerCompositionPhase(",
- "new FrameRootCompositionPhase(",
- "new SessionStartCompositionPhase(",
- ".Start(frameRoots));");
- string sessionPhase = File.ReadAllText(Path.Combine(
- FindRepoRoot(),
- "src",
- "AcDream.App",
- "Composition",
- "SessionPlayerComposition.cs"));
+ MethodInfo acquireAtlas = RequiredMethod(
+ typeof(RetailWorldRenderCompositionFactory),
+ nameof(RetailWorldRenderCompositionFactory.AcquireBackendNeutralTerrainAtlas));
Assert.Contains(
- "d.Settings.BindRuntimeTargetsOwned(settingsTargets)",
- sessionPhase,
- StringComparison.Ordinal);
- string settingsPhase = File.ReadAllText(Path.Combine(
- FindRepoRoot(),
- "src",
- "AcDream.App",
- "Composition",
- "SettingsDevToolsComposition.cs"));
- Assert.Contains(
- "_dependencies.Settings.ApplyStartup(_dependencies.StartupTarget);",
- settingsPhase,
- StringComparison.Ordinal);
- string worldPhase = File.ReadAllText(Path.Combine(
- FindRepoRoot(),
- "src",
- "AcDream.App",
- "Composition",
- "WorldRenderComposition.cs"));
- AssertAppearsInOrder(
- worldPhase,
- "TerrainAtlas.BuildBackendNeutral(device, dats)",
- "settings.ResolvedQuality.AnisotropicLevel",
- "_factory.CreateBackendNeutralTerrain(");
- AssertAppearsInOrder(
- shutdown,
- "new ResourceShutdownStage(\"physical ingress cleanup\"",
- "new ResourceShutdownStage(\"frame borrowers\"",
- "Hard(\"frame-root bindings\", () => frame.FrameBindings?.Dispose())",
- "Hard(\"retail UI\", () => DisposeRetailUi(live.RetailUi))",
- "Hard(\"streamer\", () => live.Streamer?.Dispose())",
- "Hard(\"mesh draw dispatcher\", () => render.DrawDispatcher?.Dispose())",
- "Hard(\"terrain\", () => render.Terrain?.Dispose())");
+ CompiledCallGraph.Read(acquireAtlas),
+ call => call.Target.DeclaringType == typeof(IGameRenderResourceLifetime)
+ && call.Target.Name == nameof(IGameRenderResourceLifetime.AcquireTerrainAtlas));
+ MethodBase atlasFactory = ReferencedMethodConstructingOrCalling(
+ acquireAtlas,
+ typeof(TerrainAtlas),
+ nameof(TerrainAtlas.BuildBackendNeutral));
+ Assert.NotNull(atlasFactory);
+
+ AssertLabelOrder(
+ ShutdownLabels(),
+ "physical ingress cleanup",
+ "frame borrowers",
+ "frame-root bindings",
+ "retail UI",
+ "streamer",
+ "mesh draw dispatcher",
+ "terrain");
}
[Fact]
public void UpdateRenderFocusAndCloseRemainNarrowHostEdges()
{
- string source = GameWindowSource();
- string update = Slice(
- source,
- "private void OnUpdate(double dt)",
- "private void OnRender(double deltaSeconds)");
- string render = Slice(
- source,
- "private void OnRender(double deltaSeconds)",
- "private void OnFramebufferResize(");
- string focus = Slice(
- source,
- "private void OnFocusChanged(bool focused)",
- "public void Dispose()");
- string close = Slice(
- source,
- "private void OnClosing()",
- "private void OnFocusChanged(bool focused)");
+ MethodInfo update = RequiredMethod(typeof(GameWindow), "OnUpdate");
+ AssertSingleCall(update, typeof(GameFrameGraphSlot), nameof(GameFrameGraphSlot.Tick));
- Assert.Equal(1, CountOccurrences(update, "_frameGraphs.Tick("));
- Assert.Equal(1, CountOccurrences(render, "_frameGraphs.Render("));
- AssertAppearsInOrder(
+ MethodInfo render = RequiredMethod(typeof(GameWindow), "OnRender");
+ AssertSilkWindowCallBefore(render, "get_Size", typeof(RenderFrameInput), ".ctor");
+ AssertCallOrder(
render,
- "Vector2D size = _window!.Size;",
- "_frameGraphs.Render(",
- "new AcDream.App.Rendering.RenderFrameInput(");
- Assert.DoesNotContain("FramebufferSize", render, StringComparison.Ordinal);
- Assert.Contains(
- "=> _cameraPointerInput?.HandleFocusChanged(focused);",
- focus,
- StringComparison.Ordinal);
- Assert.Contains(
- "private void OnClosing() => CompleteShutdown(releaseNativeWindow: false);",
- close,
- StringComparison.Ordinal);
- AssertAppearsInOrder(
- close,
- "private void CompleteShutdown(bool releaseNativeWindow)",
- "if (!_lifetime.HasShutdownRoots)",
- "_lifetime.PublishShutdownRoots(CaptureShutdownRoots());",
- "? _lifetime.CompleteAndReleaseNativeWindow()",
- ": _lifetime.TryComplete();");
- Assert.DoesNotContain("new ResourceShutdownStage(", close, StringComparison.Ordinal);
- Assert.DoesNotContain("CompleteOrThrow", close, StringComparison.Ordinal);
+ (typeof(RenderFrameInput), ".ctor"),
+ (typeof(GameFrameGraphSlot), nameof(GameFrameGraphSlot.Render)));
+ Assert.DoesNotContain(
+ CompiledCallGraph.Read(render),
+ call => call.Target.Name == "get_FramebufferSize");
+
+ AssertOnlyProductionCall(
+ RequiredMethod(typeof(GameWindow), "OnFocusChanged"),
+ typeof(CameraPointerInputController),
+ nameof(CameraPointerInputController.HandleFocusChanged));
+ AssertOnlyProductionCall(
+ RequiredMethod(typeof(GameWindow), "OnClosing"),
+ typeof(GameWindow),
+ "CompleteShutdown");
+
+ MethodInfo shutdown = RequiredMethod(typeof(GameWindow), "CompleteShutdown");
+ IReadOnlyList calls = CompiledCallGraph.Read(shutdown);
+ Assert.Contains(calls, call => call.Target.DeclaringType == typeof(GameWindowLifetime)
+ && call.Target.Name == "get_HasShutdownRoots");
+ Assert.Contains(calls, call => call.Target.DeclaringType == typeof(GameWindow)
+ && call.Target.Name == "CaptureShutdownRoots");
+ Assert.Contains(calls, call => call.Target.DeclaringType == typeof(GameWindowLifetime)
+ && call.Target.Name == nameof(GameWindowLifetime.PublishShutdownRoots));
+ Assert.Contains(calls, call => call.Target.DeclaringType == typeof(GameWindowLifetime)
+ && call.Target.Name == nameof(GameWindowLifetime.CompleteAndReleaseNativeWindow));
+ Assert.Contains(calls, call => call.Target.DeclaringType == typeof(GameWindowLifetime)
+ && call.Target.Name == nameof(GameWindowLifetime.TryComplete));
+ Assert.DoesNotContain(calls, call => call.Target.DeclaringType?.Name == "ResourceShutdownStage"
+ || call.Target.Name == "CompleteOrThrow");
}
[Fact]
public void Shutdown_PreservesDependencyStagesAndNativeWindowLast()
{
- string source = GameWindowSource();
- string program = File.ReadAllText(Path.Combine(
- FindRepoRoot(),
- "src",
- "AcDream.App",
- "Program.cs"));
- string lifetime = GameWindowLifetimeSource();
- string manifest = Slice(
- lifetime,
- "internal static class GameWindowShutdownManifest",
- "private static ResourceShutdownOperation Hard");
- string nativeRelease = Slice(
- lifetime,
- "public GameWindowLifetimeReport CompleteAndReleaseNativeWindow()",
- "private void EnsureTransaction()");
- int disposeStart = source.IndexOf("public void Dispose()", StringComparison.Ordinal);
- Assert.True(disposeStart >= 0);
- string dispose = source[disposeStart..];
-
+ IReadOnlyList labels = ShutdownLabels();
string[] stages =
[
- "new ResourceShutdownStage(\"host and session barriers\"",
- "new ResourceShutdownStage(\"physical ingress cleanup\"",
- "new ResourceShutdownStage(\"plugin host\"",
- "new ResourceShutdownStage(\"frame borrowers\"",
- "new ResourceShutdownStage(\"session dependents\"",
- "new ResourceShutdownStage(\"live entities\"",
- "new ResourceShutdownStage(\"effect dispatch edges\"",
- "new ResourceShutdownStage(\"live entity dependents\"",
- "new ResourceShutdownStage(\"submitted GPU work\"",
- "new ResourceShutdownStage(\"render frontends\"",
- "new ResourceShutdownStage(\"game runtime root\"",
- "new ResourceShutdownStage(\"shared texture owners\"",
- "new ResourceShutdownStage(\"mesh adapter\"",
- "new ResourceShutdownStage(\"remaining render owners\"",
- "new ResourceShutdownStage(\"dedicated render resources\"",
- "new ResourceShutdownStage(\"failed render construction cleanup\"",
- "new ResourceShutdownStage(\"frame flight owner\"",
- "new ResourceShutdownStage(\"content mappings\"",
- "new ResourceShutdownStage(\"input context\"",
- "new ResourceShutdownStage(\"graphics API context\"",
+ "host and session barriers",
+ "physical ingress cleanup",
+ "plugin host",
+ "frame borrowers",
+ "session dependents",
+ "live entities",
+ "effect dispatch edges",
+ "live entity dependents",
+ "submitted GPU work",
+ "render frontends",
+ "game runtime root",
+ "shared texture owners",
+ "mesh adapter",
+ "remaining render owners",
+ "dedicated render resources",
+ "failed render construction cleanup",
+ "frame flight owner",
+ "content mappings",
+ "input context",
+ "graphics API context",
];
- AssertAppearsInOrder(manifest, stages);
- Assert.Equal(stages.Length, CountOccurrences(manifest, "new ResourceShutdownStage("));
- foreach (string stage in stages)
- Assert.Equal(1, CountOccurrences(manifest, stage));
- AssertAppearsInOrder(
- manifest,
- "Hard(\"combat command slot\", ingress.CombatCommands.Deactivate)",
- "Hard(\"diagnostic command slot\", ingress.DiagnosticCommands.Deactivate)",
- "Hard(\"retained gameplay\", () => ingress.RetainedGameplay?.Deactivate())",
- "Hard(\"gameplay actions\", () => ingress.GameplayActions?.Deactivate())",
- "Hard(\"camera pointer\", () => ingress.CameraPointer?.Deactivate())",
- "Hard(\"game runtime session\", ingress.Runtime.StopSession)",
- "new ResourceShutdownStage(\"physical ingress cleanup\"",
- "Soft(\"retained gameplay\", () => DisposeRetainedGameplay(ingress.RetainedGameplay))",
- "Soft(\"gameplay actions\", () => DisposeGameplayActions(ingress.GameplayActions))",
- "Soft(\"camera pointer\", () => DisposeCameraPointer(ingress.CameraPointer))",
- "Soft(\"native window callbacks\", () => DisposeWindowCallbacks(ingress.WindowCallbacks))",
- "new ResourceShutdownStage(\"plugin host\"",
- "Hard(\"plugins\", () => ingress.Plugins?.Dispose())",
- "new ResourceShutdownStage(\"session dependents\"",
- "Hard(\"mouse capture\", () => live.CameraPointer?.ReleaseMouseLookAfterSessionRetirement())");
- Assert.Contains(
- "new(name, action, ResourceShutdownOperationPolicy.ReportAndContinue)",
- lifetime,
- StringComparison.Ordinal);
- Assert.Contains(
- "UiHost? RetainedUiHost,",
- lifetime,
- StringComparison.Ordinal);
- Assert.Contains(
- "IDisposable? Plugins,",
- lifetime,
- StringComparison.Ordinal);
- Assert.Contains("_uiHost,", source, StringComparison.Ordinal);
- Assert.Contains("_pluginSession,", source, StringComparison.Ordinal);
- AssertAppearsInOrder(
- program,
- "GraphicalPluginSession pluginSession = GraphicalPluginSession.Create(",
- "window.StartPluginHosting(pluginSession);",
- "window.Run();");
- AssertAppearsInOrder(
- source,
- "_pluginSession = pluginSession;",
- "pluginSession.Start();",
- "public void Run()");
- AssertAppearsInOrder(
- manifest,
- "Hard(\"plugins\", () => ingress.Plugins?.Dispose())",
- "Hard(\"retail UI\", () => DisposeRetailUi(live.RetailUi))",
- "Hard(\"game runtime\", () => DisposeGameRuntime(live.Runtime))");
- AssertAppearsInOrder(
- nativeRelease,
- "TryComplete();",
- "ReleaseNativeWindow();");
- AssertAppearsInOrder(
- dispose,
- "CompleteShutdown(releaseNativeWindow: true);",
- "_window = null;");
- Assert.Equal(1, CountOccurrences(dispose, "CompleteShutdown(releaseNativeWindow: true);"));
- Assert.DoesNotContain("_window?.Dispose();", dispose, StringComparison.Ordinal);
- Assert.Equal(1, CountOccurrences(dispose, "_window = null;"));
- int nativeReleased = dispose.IndexOf("_window = null;", StringComparison.Ordinal)
- + "_window = null;".Length;
- string afterNativeRelease = dispose[nativeReleased..];
- int fallbackDocumentation = afterNativeRelease.IndexOf(
- "/// ",
- StringComparison.Ordinal);
- if (fallbackDocumentation >= 0)
- afterNativeRelease = afterNativeRelease[..fallbackDocumentation];
- Assert.All(
- afterNativeRelease,
- character => Assert.True(char.IsWhiteSpace(character) || character == '}'));
+ AssertLabelOrder(labels, stages);
+ AssertLabelOrder(
+ labels,
+ "combat command slot",
+ "diagnostic command slot",
+ "retained gameplay",
+ "gameplay actions",
+ "game runtime session",
+ "physical ingress cleanup");
+
+ Assert.Equal(typeof(UiHost),
+ Nullable.GetUnderlyingType(typeof(IngressShutdownRoots)
+ .GetProperty("RetainedUiHost", Declared)?.PropertyType
+ ?? throw new MissingMemberException())
+ ?? typeof(IngressShutdownRoots)
+ .GetProperty("RetainedUiHost", Declared)!.PropertyType);
+ Assert.Equal(typeof(IDisposable),
+ typeof(IngressShutdownRoots).GetProperty("Plugins", Declared)?.PropertyType);
+ IReadOnlyList captureFields =
+ CompiledCallGraph.ReadFieldReferences(
+ RequiredMethod(typeof(GameWindow), "CaptureShutdownRoots"));
+ Assert.Contains(captureFields, field => field.Field.Name == "_uiHost");
+ Assert.Contains(captureFields, field => field.Field.Name == "_pluginSession");
+
+ MethodBase entry = typeof(GameWindow).Assembly.GetTypes()
+ .Where(type => type.Name == "Program")
+ .SelectMany(type => type.GetMethods(Declared))
+ .Single(method => CompiledCallGraph.Read(method).Any(call =>
+ call.Target.DeclaringType == typeof(GraphicalPluginSession)
+ && call.Target.Name == nameof(GraphicalPluginSession.Create)));
+ AssertCallOrder(
+ entry,
+ (typeof(GraphicalPluginSession), nameof(GraphicalPluginSession.Create)),
+ (typeof(GameWindow), nameof(GameWindow.StartPluginHosting)),
+ (typeof(GameWindow), nameof(GameWindow.Run)));
+
+ MethodInfo attach = RequiredMethod(typeof(GameWindow), nameof(GameWindow.StartPluginHosting));
+ CompiledFieldReference store = Assert.Single(
+ CompiledCallGraph.ReadFieldReferences(attach),
+ reference => reference.Field.Name == "_pluginSession"
+ && reference.OpCode == OpCodes.Stfld);
+ CompiledCall start = Assert.Single(
+ CompiledCallGraph.Read(attach),
+ call => call.Target.DeclaringType == typeof(GraphicalPluginSession)
+ && call.Target.Name == nameof(GraphicalPluginSession.Start));
+ Assert.True(store.Offset < start.Offset);
+
+ AssertLabelOrder(labels, "plugins", "retail UI", "game runtime");
+ AssertCallOrder(
+ RequiredMethod(
+ typeof(GameWindowLifetime),
+ nameof(GameWindowLifetime.CompleteAndReleaseNativeWindow)),
+ (typeof(GameWindowLifetime), nameof(GameWindowLifetime.TryComplete)),
+ (typeof(GameWindowLifetime), "ReleaseNativeWindow"));
+
+ MethodInfo dispose = RequiredMethod(typeof(GameWindow), nameof(GameWindow.Dispose));
+ CompiledCall complete = Assert.Single(
+ CompiledCallGraph.Read(dispose),
+ call => call.Target.DeclaringType == typeof(GameWindow)
+ && call.Target.Name == "CompleteShutdown");
+ CompiledFieldReference clear = Assert.Single(
+ CompiledCallGraph.ReadFieldReferences(dispose),
+ reference => reference.Field.Name == "_window"
+ && reference.OpCode == OpCodes.Stfld);
+ Assert.True(complete.Offset < clear.Offset);
+ Assert.DoesNotContain(
+ CompiledCallGraph.Read(dispose),
+ call => call.Target.DeclaringType == typeof(IWindow)
+ && call.Target.Name == nameof(IDisposable.Dispose));
}
[Fact]
public void ResourceRootsAndFramePairHaveExplicitAcquireTransferAndReleaseBoundaries()
{
- string source = GameWindowSource();
- string load = MethodBody(
- "private void OnLoad()",
- "private void OnUpdate(double dt)");
- string shutdown = GameWindowLifetimeSource();
- string terrainAtlas = File.ReadAllText(Path.Combine(
- FindRepoRoot(),
- "src",
- "AcDream.App",
- "Rendering",
- "TerrainAtlas.cs"));
- string worldPhase = File.ReadAllText(Path.Combine(
- FindRepoRoot(),
- "src",
- "AcDream.App",
- "Composition",
- "WorldRenderComposition.cs"));
- string livePhase = LivePresentationSource();
- string sessionPhase = File.ReadAllText(Path.Combine(
- FindRepoRoot(),
- "src",
- "AcDream.App",
- "Composition",
- "SessionPlayerComposition.cs"));
- string framePhase = File.ReadAllText(Path.Combine(
- FindRepoRoot(),
- "src",
- "AcDream.App",
- "Composition",
- "FrameRootComposition.cs"));
+ MethodInfo live = RequiredMethod(
+ typeof(LivePresentationCompositionPhase),
+ "CompletePresentation");
+ IReadOnlyList liveCalls = CompiledCallGraph.Read(live);
+ CompiledCall acquirePrepared = Assert.Single(
+ liveCalls,
+ call => call.Target.Name == "AcquirePrepared");
+ CompiledCall skyFactory = Assert.Single(
+ CompiledCallGraph.ReadMethodReferences(live),
+ call => call.Offset > acquirePrepared.Offset
+ && call.Target.GetMethodBody() is not null
+ && Constructs(call.Target, typeof(SkyRenderer)));
+ Assert.True(acquirePrepared.Offset < skyFactory.Offset);
+ Assert.NotNull(ReferencedMethodConstructingOrCalling(
+ live,
+ typeof(PortalTunnelPresentation),
+ nameof(PortalTunnelPresentation.PrepareResources)));
- AssertAppearsInOrder(
- livePhase,
- "d.PortalTunnelFallback.AcquirePrepared(",
- "static tunnel => tunnel.PrepareResources());",
- "new SkyRenderer(");
- AssertAppearsInOrder(
- load,
- "new WorldRenderCompositionPhase(",
- "new LivePresentationCompositionPhase(",
- "new SessionPlayerCompositionPhase(",
- "new FrameRootCompositionPhase(");
- AssertAppearsInOrder(
- framePhase,
- "new RenderFrameOrchestrator(",
- "new UpdateFrameOrchestrator(",
- "d.FrameGraphs.PublishOwned(");
- AssertAppearsInOrder(
- sessionPhase,
- "d.PortalTunnelFallback.Transfer(",
- "new LocalPlayerTeleportController(",
- "_publication.PublishSessionPlayer(result);");
- AssertAppearsInOrder(
- worldPhase,
- "lifetime.AcquireTerrainAtlas(",
- "TerrainAtlas.BuildBackendNeutral(device, dats)",
- "TerrainModernRenderer CreateBackendNeutralTerrain(",
- // Campaign V slice V6j: terrain is composed on both arms, so its
- // acquisition is unconditional. The boundary this test pins — that
- // the atlas is acquired, then the factory names the renderer, then
- // the renderer is acquired AND published in one step — is unchanged.
- // The raw-GL arm (CreateTerrain) was deleted at slice V11.
- "TerrainModernRenderer terrain = AcquireAndPublish(");
- AssertAppearsInOrder(
- shutdown,
- "frame.FrameGraphPublication?.Dispose()",
- "frame.FrameBindings?.Dispose()",
- "DisposeRetailUi(live.RetailUi)",
- "render.LocalTeleport?.Dispose();",
- "render.PortalTunnelFallback.ReleaseFallback();",
- "render.Sky?.Dispose()",
- "render.Terrain?.Dispose()",
- "render.DedicatedResources.ReleaseTerrainAtlas",
- "render.ConstructionCleanup.Dispose",
- "platform.Graphics?.Dispose()");
+ AssertOnLoadPhaseOrder(
+ typeof(WorldRenderCompositionPhase),
+ typeof(LivePresentationCompositionPhase),
+ typeof(SessionPlayerCompositionPhase),
+ typeof(FrameRootCompositionPhase));
+ AssertCallOrder(
+ RequiredMethod(typeof(FrameRootCompositionPhase), "ComposeCore"),
+ (typeof(RenderFrameOrchestrator), ".ctor"),
+ (typeof(UpdateFrameOrchestrator), ".ctor"),
+ (typeof(GameFrameGraphSlot), nameof(GameFrameGraphSlot.PublishOwned)));
- Assert.DoesNotContain(
- "RetailUiRuntime.Mount(",
- load,
- StringComparison.Ordinal);
- Assert.DoesNotContain("gl.GenTexture()", terrainAtlas, StringComparison.Ordinal);
- Assert.DoesNotContain(
- "portalTunnel.PrepareResources();",
- load,
- StringComparison.Ordinal);
- Assert.DoesNotContain("_portalTunnel =", source, StringComparison.Ordinal);
- Assert.DoesNotContain("_renderFrameOrchestrator", source, StringComparison.Ordinal);
- Assert.DoesNotContain("_updateFrameOrchestrator", source, StringComparison.Ordinal);
- AssertAppearsInOrder(
- source,
- "_window.Run();",
- "_constructionCleanup.RetainFrom(failure);",
- "private GameWindowShutdownRoots CaptureShutdownRoots()",
- "_constructionCleanup)");
+ MethodInfo session = RequiredMethod(
+ typeof(SessionPlayerCompositionPhase),
+ "CompleteSessionPlayer");
+ AssertCallOrder(
+ session,
+ (typeof(TransferableResourceSlot<>), "Transfer"),
+ (typeof(IGameWindowSessionPlayerPublication),
+ nameof(IGameWindowSessionPlayerPublication.PublishSessionPlayer)));
+ MethodBase teleportFactory = ReferencedMethodConstructingOrCalling(
+ session,
+ typeof(LocalPlayerTeleportPresentation),
+ ".ctor");
+ Assert.NotNull(ReferencedMethodConstructingOrCalling(
+ teleportFactory,
+ typeof(LocalPlayerTeleportController),
+ ".ctor"));
+
+ MethodInfo atlas = RequiredMethod(
+ typeof(RetailWorldRenderCompositionFactory),
+ nameof(RetailWorldRenderCompositionFactory.AcquireBackendNeutralTerrainAtlas));
+ Assert.NotNull(ReferencedMethodConstructingOrCalling(
+ atlas,
+ typeof(TerrainAtlas),
+ nameof(TerrainAtlas.BuildBackendNeutral)));
+
+ IReadOnlyList labels = ShutdownLabels();
+ AssertLabelOrder(
+ labels,
+ "world frame composition",
+ "frame-root bindings",
+ "retail UI",
+ "portal tunnel",
+ "sky",
+ "terrain",
+ "terrain atlas",
+ "resource construction ledger",
+ "graphics API");
+
+ MethodInfo onLoad = RequiredMethod(typeof(GameWindow), "OnLoad");
+ IReadOnlyList loadCalls =
+ CompiledCallGraph.ReadMethodReferences(onLoad);
+ Assert.DoesNotContain(loadCalls, call =>
+ call.Target.DeclaringType == typeof(RetailUiRuntime)
+ && call.Target.Name == "Mount");
+ Assert.DoesNotContain(loadCalls, call =>
+ call.Target.DeclaringType == typeof(PortalTunnelPresentation)
+ && call.Target.Name == nameof(PortalTunnelPresentation.PrepareResources));
+ AssertMembersAbsent(
+ typeof(GameWindow),
+ ["_portalTunnel", "_renderFrameOrchestrator", "_updateFrameOrchestrator"],
+ []);
+
+ AssertCallOrder(
+ RequiredMethod(typeof(GameWindow), nameof(GameWindow.Run)),
+ (typeof(ResourceConstructionCleanupLedger),
+ nameof(ResourceConstructionCleanupLedger.RetainFrom)));
+ AssertSilkWindowCallBefore(
+ RequiredMethod(typeof(GameWindow), nameof(GameWindow.Run)),
+ nameof(IWindow.Run),
+ typeof(ResourceConstructionCleanupLedger),
+ nameof(ResourceConstructionCleanupLedger.RetainFrom));
Assert.Contains(
- "Hard(\"resource construction ledger\", render.ConstructionCleanup.Dispose)",
- shutdown,
- StringComparison.Ordinal);
+ CompiledCallGraph.ReadFieldReferences(
+ RequiredMethod(typeof(GameWindow), "CaptureShutdownRoots")),
+ reference => reference.Field.Name == "_constructionCleanup");
+ Assert.Contains(labels, label => label == "resource construction ledger");
}
- private static string MethodBody(string start, string end) =>
- Slice(GameWindowSource(), start, end);
+ private static MethodInfo RequiredMethod(Type type, string name) =>
+ type.GetMethod(
+ name,
+ BindingFlags.Instance | BindingFlags.Static
+ | BindingFlags.Public | BindingFlags.NonPublic)
+ ?? throw new MissingMethodException(type.FullName, name);
- private static string Slice(string source, string start, string end)
+ private static ConstructorInfo FindConstructorConstructing(Type owner, Type constructed) =>
+ Assert.Single(
+ owner.GetConstructors(Declared),
+ constructor => Constructs(constructor, constructed));
+
+ private static void AssertField(Type owner, string name, Type expectedType)
{
- int first = source.IndexOf(start, StringComparison.Ordinal);
- int last = source.IndexOf(end, first + 1, StringComparison.Ordinal);
- Assert.True(first >= 0, $"Missing source boundary: {start}");
- Assert.True(last > first, $"Missing source boundary: {end}");
- return source[first..last];
+ FieldInfo field = owner.GetField(name, Declared)
+ ?? throw new MissingFieldException(owner.FullName, name);
+ Assert.Equal(expectedType, field.FieldType);
}
- private static int CountOccurrences(string source, string value)
+ private static void AssertMembersAbsent(
+ Type owner,
+ IReadOnlyCollection fields,
+ IReadOnlyCollection methods)
{
- int count = 0;
- int cursor = 0;
- while ((cursor = source.IndexOf(value, cursor, StringComparison.Ordinal)) >= 0)
+ Assert.DoesNotContain(owner.GetFields(Declared), field => fields.Contains(field.Name));
+ Assert.DoesNotContain(owner.GetMethods(Declared), method => methods.Contains(method.Name));
+ }
+
+ private static void AssertOnLoadPhaseOrder(params Type[] phases)
+ {
+ IReadOnlyList references = CompiledCallGraph.ReadMethodReferences(
+ RequiredMethod(typeof(GameWindow), "OnLoad"));
+ int cursor = -1;
+ foreach (Type phase in phases)
{
- count++;
- cursor += value.Length;
+ CompiledCall found = references
+ .Where(reference => reference.Offset > cursor)
+ .FirstOrDefault(reference => Constructs(reference.Target, phase));
+ Assert.NotNull(found.Target);
+ Assert.True(found.Offset > cursor, $"Missing OnLoad phase: {phase.Name}.");
+ cursor = found.Offset;
}
-
- return count;
}
- private static void AssertAppearsInOrder(string source, params string[] fragments)
+ private static MethodBase OnLoadMethodConstructing(Type type) =>
+ Assert.Single(
+ CompiledCallGraph.ReadMethodReferences(
+ RequiredMethod(typeof(GameWindow), "OnLoad"))
+ .Select(reference => reference.Target)
+ .Where(method => method.GetMethodBody() is not null)
+ .Distinct(),
+ method => Constructs(method, type));
+
+ 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 MethodBase ReferencedMethodConstructingOrCalling(
+ MethodBase owner,
+ Type type,
+ string methodName) =>
+ Assert.Single(
+ CompiledCallGraph.ReadMethodReferences(owner)
+ .Select(reference => reference.Target)
+ .Where(method => method.GetMethodBody() is not null)
+ .Distinct(),
+ method => CompiledCallGraph.Read(method).Any(call =>
+ call.Target.DeclaringType == type
+ && call.Target.Name == methodName));
+
+ private static void AssertSingleCall(MethodBase owner, Type type, string name) =>
+ Assert.Single(
+ CompiledCallGraph.Read(owner),
+ call => MatchesType(call.Target.DeclaringType, type)
+ && call.Target.Name == name);
+
+ private static void AssertOnlyProductionCall(MethodBase owner, Type type, string name)
+ {
+ IReadOnlyList calls = CompiledCallGraph.Read(owner)
+ .Where(call => call.Target.DeclaringType?.Namespace?.StartsWith("AcDream",
+ StringComparison.Ordinal) == true)
+ .ToArray();
+ CompiledCall call = Assert.Single(calls);
+ Assert.True(MatchesType(call.Target.DeclaringType, type));
+ Assert.Equal(name, call.Target.Name);
+ }
+
+ private static void AssertSilkWindowCallAfter(
+ MethodBase owner,
+ string silkMethod,
+ Type predecessorType,
+ string predecessorMethod)
+ {
+ IReadOnlyList calls = CompiledCallGraph.Read(owner);
+ CompiledCall predecessor = Assert.Single(calls, call =>
+ call.Target.DeclaringType == predecessorType
+ && call.Target.Name == predecessorMethod);
+ CompiledCall silk = Assert.Single(calls, call =>
+ call.Target.DeclaringType?.Namespace == "Silk.NET.Windowing"
+ && call.Target.Name == silkMethod);
+ Assert.True(predecessor.Offset < silk.Offset);
+ }
+
+ private static void AssertSilkWindowCallBefore(
+ MethodBase owner,
+ string silkMethod,
+ Type successorType,
+ string successorMethod)
+ {
+ IReadOnlyList calls = CompiledCallGraph.Read(owner);
+ CompiledCall silk = Assert.Single(calls, call =>
+ call.Target.DeclaringType?.Namespace == "Silk.NET.Windowing"
+ && call.Target.Name == silkMethod);
+ CompiledCall successor = Assert.Single(calls, call =>
+ call.Target.DeclaringType == successorType
+ && call.Target.Name == successorMethod);
+ Assert.True(silk.Offset < successor.Offset);
+ }
+
+ private static void AssertCallOrder(
+ MethodBase method,
+ params (Type Type, string Method)[] expected)
+ {
+ IReadOnlyList calls = CompiledCallGraph.Read(method);
+ int cursor = -1;
+ foreach ((Type type, string name) in expected)
+ {
+ int found = Enumerable.Range(cursor + 1, calls.Count - cursor - 1)
+ .FirstOrDefault(index =>
+ MatchesType(calls[index].Target.DeclaringType, type)
+ && calls[index].Target.Name == name,
+ -1);
+ Assert.True(found > cursor,
+ $"Missing compiled edge after {cursor}: {type.FullName}.{name}. "
+ + $"Observed: {string.Join(", ", calls.Select(call =>
+ $"{call.Target.DeclaringType?.Name}.{call.Target.Name}"))}");
+ cursor = found;
+ }
+ }
+
+ private static void AssertTargetOrder(
+ IReadOnlyList calls,
+ Type type,
+ params string[] names)
{
int cursor = -1;
- foreach (string fragment in fragments)
+ foreach (string name in names)
{
- int next = source.IndexOf(fragment, cursor + 1, StringComparison.Ordinal);
- Assert.True(next >= 0, $"Missing expected source fragment: {fragment}");
- Assert.True(next > cursor, $"Out-of-order source fragment: {fragment}");
- 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 referenced target after {cursor}: {type.FullName}.{name}.");
+ cursor = found;
}
}
- private static string GameWindowSource() => File.ReadAllText(Path.Combine(
- FindRepoRoot(),
- "src",
- "AcDream.App",
- "Rendering",
- "GameWindow.cs"));
+ private static bool MatchesType(Type? actual, Type expected) =>
+ actual == expected
+ || expected.IsGenericTypeDefinition
+ && actual?.IsGenericType == true
+ && actual.GetGenericTypeDefinition() == expected;
- private static string GameWindowLifetimeSource() => File.ReadAllText(Path.Combine(
- FindRepoRoot(),
- "src",
- "AcDream.App",
- "Rendering",
- "GameWindowLifetime.cs"));
+ private static IReadOnlyList ShutdownLabels() =>
+ CompiledCallGraph.ReadStringLiterals(RequiredMethod(
+ typeof(GameWindowShutdownManifest),
+ nameof(GameWindowShutdownManifest.Create)));
- private static string LivePresentationSource() => File.ReadAllText(Path.Combine(
- FindRepoRoot(),
- "src",
- "AcDream.App",
- "Composition",
- "LivePresentationComposition.cs"));
-
- private static string FindRepoRoot()
+ private static void AssertLabelOrder(
+ IReadOnlyList labels,
+ params string[] expected)
{
- DirectoryInfo? directory = new(AppContext.BaseDirectory);
- while (directory is not null)
+ int cursor = -1;
+ foreach (string label in expected)
{
- if (File.Exists(Path.Combine(directory.FullName, "AcDream.slnx")))
- return directory.FullName;
- directory = directory.Parent;
+ int found = Enumerable.Range(cursor + 1, labels.Count - cursor - 1)
+ .FirstOrDefault(index => labels[index] == label, -1);
+ Assert.True(found > cursor,
+ $"Missing shutdown label after {cursor}: {label}.");
+ cursor = found;
}
-
- throw new DirectoryNotFoundException("Could not find AcDream.slnx.");
}
}