acdream/tests/AcDream.App.Tests/Rendering/GameWindowSlice8BoundaryTests.cs
Erik 8a7a0837e1 feat(render): Vulkan campaign V11 step 2 — delete the OpenGL backend
Vulkan is the sole, user-signed-off backend (V10 landed) and step 1
already removed ImGui/Studio/DevTools. This step deletes the GL
rendering backend itself: every Gpu/Gl/** implementation, the Wb
ManagedGL*/GLHelpers/GLSLShader/GLStateScope/RenderStateCache/
BindlessSupport family, Shader/ShaderProgramConstruction/SamplerCache,
RenderBootstrap, and RenderFrameGlStateController.

GameWindow.cs's Run()/CreateGraphics()/CreateBackbufferReader()/
OnLoad() collapse to their Vulkan-only arm; GameWindowGraphics loses
its OpenGlGameWindowGraphics subclass. RuntimeOptions.RenderBackend and
RenderBackendKind (incl. the Gl member of GpuBackendKind) are gone —
there is nothing left to select between. The five world-draw dual-arm
renderers (WbDrawDispatcher, EnvCellRenderer, TerrainModernRenderer,
ParticleRenderer, SkyRenderer) and the composition roots
(WorldRenderComposition, HostInputCameraComposition,
LivePresentationComposition, FrameRootComposition) collapse to their
RHI-only arm. GL-only diagnostic properties with a live external reader
(DynamicBufferCount and friends) simplify to a documented `=> 0`/no-op
rather than disappearing, since the reader is out of this commit's
scope.

A few GL-flavored mechanisms turned out to be backend-neutral once
isolated: GlConstructionCleanupLedger is renamed
ResourceConstructionCleanupLedger (exception-chain walking has nothing
to do with GL), and GlfwNativePlatformProbe moved out of the otherwise
GL-only GraphicalCapabilityRecord.cs into
GraphicalWindowBackendSelection.cs before the rest of that file was
deleted.

Test files with no surviving subject are deleted outright
(GraphicalCapabilityRequirementsTests, ShaderProgramConstructionTests,
PortalDepthShaderParityTests, TextureCacheBindlessTests,
TextRendererFailureSafetyTests, ClipFrameUploadTests, every
Gpu/Gl/*Tests, GlTextureOwnershipTests, RenderFrameGlStateControllerTests);
others get their dead GL-only members trimmed while their live
assertions stay (ClipFrameLayoutTests' MeshClipSsboBinding check now
reads GpuBindingModel.StorageClipRegions, the same binding index under
its new backend-neutral name; GpuResourceRetirementTransactionTests
drops its OpenGLGraphicsDevice-subclassing test double and the two GL
queue tests it existed for). EnvCellRendererTests' construction helper
now builds a real ObjectMeshManager via VulkanMeshPipelineDevice
instead of passing null through a null-forgiving operator, since the
RHI constructor never tolerated a null mesh manager and the old GL
constructor (which did) is gone.

Deferred to the next two steps, deliberately not touched here: the
Silk.NET.OpenGL/.Extensions.ARB package references, IMeshPipelineDevice.Gl
(WbMeshAdapter's GL? threading stays in place), Chorizite.Core's stale
csproj comment (the package itself is still load-bearing —
TextureFormat and friends are used well beyond the deleted
ManagedGLUniformBuffer), and the CI/gate scripts.

Build: `dotnet build AcDream.slnx -c Release` — 0 warnings, 0 errors.
Tests: full-solution `dotnet test` green across every project
(App.Tests 3937/3940 + 3 skips, Core.Tests 3296/3298 + 2 skips, all
others 100%); the 2 App.Tests names that flake under full-suite
parallel execution (#250-family, documented pre-existing) pass in
isolation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 02:19:53 +02:00

710 lines
32 KiB
C#

namespace AcDream.App.Tests.Rendering;
/// <summary>
/// Temporary source-shape freeze for the Slice 8 cutover. Each later checkpoint
/// replaces the corresponding assertion with functional tests on the extracted
/// owner; this file prevents a mechanical move from silently reordering host
/// behavior before that owner exists.
/// </summary>
public sealed class GameWindowSlice8BoundaryTests
{
[Fact]
public void Run_PreservesNativeAttributeAndCallbackOrder()
{
string body = MethodBody("public void Run()", "private void OnLoad()");
AssertAppearsInOrder(
body,
"RuntimeSettingsSnapshot startup = _runtimeSettings.Startup",
"_displayFramePacing.InitializeStartup(startup.Display.VSync)",
"VSync = startupPacing.UseVSync",
// Campaign V slice V11: the raw-GL "Samples = ..." window option
// is gone — Vulkan takes MSAA as an RHI attachment property, not
// 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);
}
[Fact]
public void SessionStart_FollowsFrameGraphPublication()
{
string body = MethodBody(
"private void OnLoad()",
"private void OnUpdate(double dt)");
AssertAppearsInOrder(
body,
"GameWindowPlatformResult<GameWindowGraphics, IInputContext> 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));");
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);");
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);
}
[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 LiveSessionEventRouter CreateEventRouter(",
"private LiveInventorySessionBindings CreateInventoryBindings()");
string worldPhase = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Composition",
"WorldRenderComposition.cs"));
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);
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)");
}
[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();
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())");
}
[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(");
Assert.DoesNotContain(
"_viewportAspect.Update(_window",
load,
StringComparison.Ordinal);
}
[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();
Assert.Contains(
"private readonly RuntimeSettingsController _runtimeSettings;",
source,
StringComparison.Ordinal);
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);
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"));
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,
"Soft(\"settings view model\", () => ingress.Settings.UnbindViewModel())",
"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())");
}
[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)");
Assert.Equal(1, CountOccurrences(update, "_frameGraphs.Tick("));
Assert.Equal(1, CountOccurrences(render, "_frameGraphs.Render("));
AssertAppearsInOrder(
render,
"Vector2D<int> 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);
}
[Fact]
public void Shutdown_PreservesDependencyStagesAndNativeWindowLast()
{
string source = GameWindowSource();
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..];
string[] stages =
[
"new ResourceShutdownStage(\"host and session barriers\"",
"new ResourceShutdownStage(\"physical ingress cleanup\"",
"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\"",
];
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(\"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("_uiHost,", source, StringComparison.Ordinal);
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(
"/// <summary>",
StringComparison.Ordinal);
if (fallbackDocumentation >= 0)
afterNativeRelease = afterNativeRelease[..fallbackDocumentation];
Assert.All(
afterNativeRelease,
character => Assert.True(char.IsWhiteSpace(character) || character == '}'));
}
[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"));
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()");
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)");
Assert.Contains(
"Hard(\"resource construction ledger\", render.ConstructionCleanup.Dispose)",
shutdown,
StringComparison.Ordinal);
}
private static string MethodBody(string start, string end) =>
Slice(GameWindowSource(), start, end);
private static string Slice(string source, string start, string end)
{
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];
}
private static int CountOccurrences(string source, string value)
{
int count = 0;
int cursor = 0;
while ((cursor = source.IndexOf(value, cursor, StringComparison.Ordinal)) >= 0)
{
count++;
cursor += value.Length;
}
return count;
}
private static void AssertAppearsInOrder(string source, params string[] fragments)
{
int cursor = -1;
foreach (string fragment in fragments)
{
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;
}
}
private static string GameWindowSource() => File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Rendering",
"GameWindow.cs"));
private static string GameWindowLifetimeSource() => File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Rendering",
"GameWindowLifetime.cs"));
private static string LivePresentationSource() => File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Composition",
"LivePresentationComposition.cs"));
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.");
}
}