refactor(app): compose atomic frame roots

Move the complete update/render construction graph into a typed Phase-8 owner, explicitly carry the content dependencies it consumes, and publish both roots through one exact lease. Extract lifecycle resource sampling and frame-owned late bindings so partial startup and shutdown withdraw the same generation without window callbacks.

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-22 19:02:08 +02:00
parent 826f9ea9b5
commit 3628aeb520
21 changed files with 1052 additions and 374 deletions

View file

@ -65,10 +65,21 @@ public sealed class GameWindowSlice8BoundaryTests
"new LivePresentationCompositionPhase(",
"this).Compose(",
"new SessionPlayerCompositionPhase(",
"var renderFrameOrchestrator =",
"var updateFrameOrchestrator = new AcDream.App.Update.UpdateFrameOrchestrator(",
"_frameGraphs.Publish(updateFrameOrchestrator, renderFrameOrchestrator);",
"sessionPlayer.SessionHost.Start(_options)");
"FrameRootResult frameRoots = new FrameRootCompositionPhase(",
"frameRoots.SessionHost.Start(_options)");
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(),
@ -86,7 +97,7 @@ public sealed class GameWindowSlice8BoundaryTests
"_publication.PublishSessionPlayer(result);");
Assert.Equal(1, CountOccurrences(
body,
"sessionPlayer.SessionHost.Start(_options)"));
"frameRoots.SessionHost.Start(_options)"));
string phaseOne = Slice(
File.ReadAllText(Path.Combine(
FindRepoRoot(),
@ -107,7 +118,7 @@ public sealed class GameWindowSlice8BoundaryTests
phaseOne,
StringComparison.Ordinal);
int start = body.IndexOf(
"sessionPlayer.SessionHost.Start(_options)",
"frameRoots.SessionHost.Start(_options)",
StringComparison.Ordinal);
string postStart = body[start..];
Assert.Contains("switch (liveStart.Status)", postStart, StringComparison.Ordinal);
@ -331,7 +342,8 @@ public sealed class GameWindowSlice8BoundaryTests
"this).Compose(platform, hostInputCamera, contentEffectsAudio);",
"new WorldRenderCompositionPhase(",
"new SessionPlayerCompositionPhase(",
"sessionPlayer.SessionHost.Start(_options)");
"new FrameRootCompositionPhase(",
"frameRoots.SessionHost.Start(_options)");
string sessionPhase = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
@ -385,7 +397,7 @@ public sealed class GameWindowSlice8BoundaryTests
string render = Slice(
source,
"private void OnRender(double deltaSeconds)",
"private AcDream.App.Diagnostics.WorldLifecycleResourceSnapshot");
"private void OnFramebufferResize(");
string focus = Slice(
source,
"private void OnFocusChanged(bool focused)",
@ -524,6 +536,12 @@ public sealed class GameWindowSlice8BoundaryTests
"AcDream.App",
"Composition",
"SessionPlayerComposition.cs"));
string framePhase = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Composition",
"FrameRootComposition.cs"));
AssertAppearsInOrder(
livePhase,
@ -536,7 +554,12 @@ public sealed class GameWindowSlice8BoundaryTests
"new WorldRenderCompositionPhase(",
"new LivePresentationCompositionPhase(",
"new SessionPlayerCompositionPhase(",
"_frameGraphs.Publish(updateFrameOrchestrator, renderFrameOrchestrator);");
"new FrameRootCompositionPhase(");
AssertAppearsInOrder(
framePhase,
"new RenderFrameOrchestrator(",
"new UpdateFrameOrchestrator(",
"d.FrameGraphs.PublishOwned(");
AssertAppearsInOrder(
sessionPhase,
"d.PortalTunnelFallback.Transfer(",
@ -550,7 +573,8 @@ public sealed class GameWindowSlice8BoundaryTests
"TerrainModernRenderer terrain = AcquireAndPublish(");
AssertAppearsInOrder(
shutdown,
"_frameGraphs.Withdraw();",
"publication.Dispose();",
"bindings.Dispose();",
"_retailUiLease.Dispose();",
"_localPlayerTeleport?.Dispose();",
"_portalTunnelFallback.ReleaseFallback();",