refactor(render): compose render frame orchestrator

Move the accepted draw transaction and failure recovery behind typed frame-phase owners so GameWindow only supplies immutable frame input. Preserve retail draw order, make ImGui/bootstrap shutdown ownership explicit, and restore exact text-render GL state on failures.\n\nCo-authored-by: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-22 08:03:49 +02:00
parent 28e1cf8029
commit 9d7df1bfc5
25 changed files with 1675 additions and 279 deletions

View file

@ -1,9 +1,9 @@
# acdream — code structure & extraction sequence
**Status:** Living document. Created 2026-05-16; implementation reconciliation
completed 2026-07-21; Slices 16 landed by 2026-07-22. Slice 7 draw-frame
orchestration is the active structural program before new M4 subsystems enter
the App layer.
completed 2026-07-21; Slices 16 and the Slice 7 draw-frame cutover landed by
2026-07-22. Slice 7 connected closeout is active before the final Slice 8
composition/shutdown cleanup and new M4 subsystems enter the App layer.
**Purpose:** Describe the desired structural state of the App layer,
explain the rules we've adopted, and lay out the safe extraction
sequence from today's reality (one 15,723-line `GameWindow.cs` at the
@ -30,6 +30,7 @@ after Slice 3 14,310 lines / 274 fields / 190 method
after Slice 4 10,301 lines / 267 fields / 163 methods
after Slice 5 closeout 8,811 lines / 247 fields / 153 methods
after Slice 6 closeout 7,026 lines / 241 fields / 108 methods
after Slice 7 draw-frame cutover 4,666 lines / 196 fields / 70 methods
```
`GameWindow` is the single object that:
@ -168,6 +169,45 @@ App currently wires it.
---
### Render-frame ownership versus recursive reachability
The typed `RenderFrameOrchestrator` graph owns frame sequencing and borrows
long-lived runtime/presentation owners. The enforceable ownership invariant is
that the orchestrator and its immediate phase owners retain neither
`GameWindow`, an anonymous callback bag, nor direct window delegates. It is
**not** an invariant that recursively walking every borrowed canonical owner
can never reach an event subscriber or callback that was composed by
`GameWindow`; that claim would be false and would confuse object-graph
reachability with frame ownership.
Known recursive callback-bearing paths include (this is evidence and design
documentation, not an exhaustive allowlist):
- `RetainedGameplayUiFrame -> RetailUiRuntime -> RetailUiRuntimeBindings` for
retained gameplay UI state and commands.
- `PrivatePresentationRenderer -> PaperdollFramePresenter ->
RetailPaperdollFrameView/PaperdollInventoryVisibility -> UiElement tree` for
private paperdoll visibility and texture publication. This is a second,
independent retained-UI entry because `UiElement.Parent` links back to the
shared root and sibling controls.
- `DevToolsFramePresenter -> DevToolsPanelSet -> panel/ViewModel bindings` for
the optional developer UI.
- `WorldRenderFrameBuilder -> RuntimeWorldFrameSettingsPreview -> SettingsVM`
for the live settings draft preview applied before world drawing.
- `LocalPlayerPortalViewport -> LocalPlayerTeleportController ->
GameplayInputFrameController -> InputDispatcher.Fired -> GameWindow` for the
canonical portal/input lifetime and the host's input-action subscription.
These are presentation state/command seams, not alternative owners of the
window or frame transaction. The screenshot path is deliberately not on this
list: viewport width/height now travel in `RenderFrameInput`, so
`FrameScreenshotController` no longer stores a window-size callback. New
immediate phase owners must still satisfy the direct ownership invariant;
recursive shared-owner paths are reviewed against their canonical subsystem
lifetime rather than an impossible global no-callback assertion.
---
## 3. Target structure of the App layer
The end state — not what we're shipping in one pass, but the shape
@ -608,7 +648,7 @@ registered; this behavior-preserving extraction introduced no new divergence.
Detailed execution ledger:
[`docs/plans/2026-07-22-gamewindow-slice-7-render-frame-orchestration.md`](../plans/2026-07-22-gamewindow-slice-7-render-frame-orchestration.md)
(active).
(connected closeout active).
Move the complete draw graph and its reusable frame-local scratch state into a
GL-owning App collaborator. Preserve the exact modern pipeline order, clip
@ -622,6 +662,14 @@ Automated acceptance uses framebuffer artifacts and render/resource
checkpoints. Visual acceptance compares outdoor, building, dungeon, portal
exit, translucent lifestone/particles, and UI at the same camera positions.
The production draw cutover is complete. `GameWindow.OnRender` now performs one
value-only handoff to `RenderFrameOrchestrator`; focused resource, world,
private-presentation, and diagnostic owners contain the former frame body and
failure recovery. The Release suite passes 7,341 tests with five intentional
skips. `GameWindow` is 4,666 raw lines / 196 fields / 70 methods: 11,057 lines
(70.3%) smaller than the campaign baseline. The connected lifecycle and
resource-soak closeout remains before Slice 7 is final.
#### Slice 8 — composition and shutdown cleanup
Keep GL/window construction in `GameWindow.OnLoad`, but group creation into