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:
parent
28e1cf8029
commit
9d7df1bfc5
25 changed files with 1675 additions and 279 deletions
|
|
@ -1,9 +1,9 @@
|
|||
# acdream — code structure & extraction sequence
|
||||
|
||||
**Status:** Living document. Created 2026-05-16; implementation reconciliation
|
||||
completed 2026-07-21; Slices 1–6 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 1–6 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
|
||||
|
|
|
|||
|
|
@ -260,7 +260,7 @@ AP-94..AP-112 for the confirmed retail-UI completion gaps.
|
|||
| TS-50 | `AnimationDone` executes semantically at each owner's retail `CPhysicsObj::process_hooks` boundary, but all other animation hooks are retained in `AnimationHookFrameQueue` until final root/part/equipped-child pose publication. Retail executes the complete hook stream before transition and the Target/Movement/PartArray/Position manager tail because its current CPartArray pose already exists in-place. Static owners correctly reach `process_hooks` only after their root, parts, and children are current. | `src/AcDream.App/Rendering/Vfx/AnimationHookFrameQueue.cs`; `src/AcDream.App/Rendering/RetailStaticAnimatingObjectScheduler.cs`; shared frame drain in `src/AcDream.App/Update/LiveObjectFrameController.cs` (`LiveEffectFrameController`) | The modern renderer publishes immutable effect-pose snapshots after all root/child composition; deferred visual sinks avoid attaching particles/lights/audio to the previous pose. Semantic `AnimationDone` is split out and exact, so motion completion and manager behavior are not delayed. Pose-owner lifetime tokens prevent deferred hooks from crossing delete/local-ID reuse. | A non-AnimationDone hook with same-quantum semantic consequences (notably `CallPES`, default-script chaining, audio/particle creation relative to a transition) runs later than retail and can observe post-tail state or start one render frame late. | `CPhysicsObj::process_hooks @ 0x00511550`; `CPhysicsObj::UpdatePositionInternal @ 0x00512C30`; `CPhysicsObj::animate_static_object @ 0x00513DF0`; retire by publishing the current per-object/child pose before hook routing or splitting semantic and presentation sinks without changing authored hook order |
|
||||
| TS-51 | Particle and PhysicsScript tails advance once per render frame after the complete ordinary/static object worksets. Retail advances each ordinary object's ParticleManager then ScriptManager inside every admitted `UpdateObjectInternal` quantum; `animate_static_object` instead advances that static owner's ScriptManager then ParticleManager and only then `process_hooks`, using its whole admitted elapsed interval. acdream's shared tail is Particle → Script after static hook capture. | `src/AcDream.App/Update/LiveObjectFrameController.cs` (`LiveObjectFrameController` + `LiveEffectFrameController` shared `_particles.Tick` / `_scripts.Tick` tail); `src/AcDream.App/Rendering/RetailStaticAnimatingObjectScheduler.cs` | The current managers are shared presentation/runtime owners rather than per-object manager instances. R6 makes root motion, animation, object clocks, workset membership, and ordinary manager order faithful without pretending the shared tails have per-owner timing or static-tail order. Splitting ownership safely requires a later effect-lifetime slice. | A render fragment below retail's minimum object quantum can advance an effect while its owner waits; a catch-up frame advances an owner's root through several quanta but its effect tail only once; static hooks can route before their script/particle managers and static default scripts/particles use render elapsed in Particle → Script order rather than `animate_static_object` elapsed/discard and Script → Particle → hooks timing. | `CPhysicsObj::UpdateObjectInternal @ 0x005156B0`; `CPhysicsObj::animate_static_object @ 0x00513DF0`; retire by giving live/static owners incarnation-bound particle/script managers and ticking each manager in the owning object quantum/order |
|
||||
| TS-52 | The terrain shader applies retail-authored base/overlay/road `TerrainTex.TexTiling` but omits the separate Environment Detail Textures pass and its viewer-distance fade (**#226**). | `src/AcDream.App/Rendering/TerrainAtlas.cs`; `src/AcDream.App/Rendering/TerrainModernRenderer.cs`; `src/AcDream.App/Rendering/Shaders/terrain_modern.frag` | `bb5acab9` fixed the user-visible stretched/blurry regression by porting the distinct base-tiling contract. An earlier experimental detail array darkened the whole ground because its source/neutral blend contract was wrong, so it was correctly reverted rather than guessed into production. | With retail's Environment Detail Textures preference enabled, close terrain lacks the extra high-frequency modulation/fade even though authored base texture scale is correct. | `LScape::GenerateDetailSurfaces` / `SetDetailTexturing @ 0x00506B40`; `ACRender::landPolyDraw @ 0x006B6450..0x006B6525`; issue #226 |
|
||||
| TS-53 | acdream advances retained UI time on the draw seam and local teleport/UI-camera presentation after its SmartBox-shaped object → inbound network → CommandInterpreter barrier. Retail `Client::UseTime` calls `UIElementManager::UseTime` first, whose global time message reaches `gmSmartBoxUI::UseTime`, and publishes player-camera work from the physics/player callback rather than one post-network camera tail. Slice 6 preserves the accepted host order as an ownership-only extraction. | `src/AcDream.App/Update/UpdateFrameOrchestrator.cs` (post-live-frame teleport/camera phases); `src/AcDream.App/Rendering/GameWindow.cs` (`_retailUiRuntime.Tick` in `OnRender`); `docs/plans/2026-07-21-gamewindow-slice-6-update-frame-orchestration.md` | Current retained UI, portal transit, reveal, camera, and connected movement traces are accepted; changing cross-subsystem host order while extracting ownership would combine a behavior change with the structural cutover. | Retained UI, teleport, and camera presentation can observe same-frame object/inbound/player state one host update earlier or later than retail at transition boundaries; a future exact host-order port must prove UI, input, reveal, and camera consequences together. | `Client::UseTime @ 0x00411C40`; `UIElementManager::UseTime`; `gmSmartBoxUI::UseTime @ 0x004D6E30`; `CPhysics::UseTime @ 0x00509950`; retire only with a focused host-order port and connected portal/camera comparison |
|
||||
| TS-53 | acdream advances retained UI time on the draw seam and local teleport/UI-camera presentation after its SmartBox-shaped object → inbound network → CommandInterpreter barrier. Retail `Client::UseTime` calls `UIElementManager::UseTime` first, whose global time message reaches `gmSmartBoxUI::UseTime`, and publishes player-camera work from the physics/player callback rather than one post-network camera tail. Slices 6–7 preserve the accepted host order as ownership-only extractions. | `src/AcDream.App/Update/UpdateFrameOrchestrator.cs` (post-live-frame teleport/camera phases); `src/AcDream.App/Rendering/PrivatePresentationRenderer.cs` (`RetainedGameplayUiFrame.Render`); `docs/plans/2026-07-21-gamewindow-slice-6-update-frame-orchestration.md`; `docs/plans/2026-07-22-gamewindow-slice-7-render-frame-orchestration.md` | Current retained UI, portal transit, reveal, camera, and connected movement traces are accepted; changing cross-subsystem host order while extracting ownership would combine a behavior change with the structural cutover. | Retained UI, teleport, and camera presentation can observe same-frame object/inbound/player state one host update earlier or later than retail at transition boundaries; a future exact host-order port must prove UI, input, reveal, and camera consequences together. | `Client::UseTime @ 0x00411C40`; `UIElementManager::UseTime`; `gmSmartBoxUI::UseTime @ 0x004D6E30`; `CPhysics::UseTime @ 0x00509950`; retire only with a focused host-order port and connected portal/camera comparison |
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ port, or resource-lifetime redesign.
|
|||
`RetailPViewPassExecutor` and data-only inputs.
|
||||
- [x] F — extract `WorldSceneRenderer`, including both shared-alpha scopes,
|
||||
PView/fallback world branches, particles, debug world draw, and completion.
|
||||
- [ ] G — compose `RenderFrameOrchestrator`, cut `GameWindow.OnRender` to one
|
||||
- [x] G — compose `RenderFrameOrchestrator`, cut `GameWindow.OnRender` to one
|
||||
handoff, and delete obsolete frame bodies, fields, helpers, and callbacks.
|
||||
- [ ] H — corrected-diff reviews, full Release suite, connected lifecycle and
|
||||
soak gates, framebuffer comparison, documentation, memory, and metrics.
|
||||
|
|
@ -231,12 +231,15 @@ contract requires separate approval and evidence.
|
|||
|
||||
### 4.2 Borrowed resources and shutdown
|
||||
|
||||
Slice 7 collaborators borrow the already-created GL/render owners. They do not
|
||||
dispose them. The existing retryable shutdown transaction keeps its proven
|
||||
dependency order: wait for submitted work; withdraw UI/render publications;
|
||||
release textures and mesh owners; then dispose GL backing stores, with
|
||||
`GpuFrameFlightController` last among render resources. Slice 8 may group
|
||||
construction/disposal after the render cutover is stable.
|
||||
The frame orchestrator and phase owners borrow the already-created GL/render
|
||||
owners and do not dispose them. The developer-tools backend is itself the
|
||||
canonical owner of its ImGui bootstrap and disposes that bootstrap when the
|
||||
existing retryable shutdown transaction retires the backend; the frame
|
||||
presenter only borrows it. Shutdown keeps its proven dependency order: wait for
|
||||
submitted work; withdraw UI/render publications; release textures and mesh
|
||||
owners; then dispose GL backing stores, with `GpuFrameFlightController` last
|
||||
among render resources. Slice 8 may group construction/disposal after the
|
||||
render cutover is stable.
|
||||
|
||||
`DisplayFramePacingController` is the focused shared owner for requested VSync,
|
||||
live display preview, monitor/window callbacks, and the separate
|
||||
|
|
@ -354,7 +357,8 @@ Completed 2026-07-22. Paperdoll clone/re-dress/pose/FBO work now belongs to
|
|||
`PaperdollFramePresenter`; retained surfaces are narrow and optional, while
|
||||
live identity and DAT pose application resolve through focused adapters.
|
||||
`DevToolsFramePresenter` owns the ImGui frame, menus, panels, layout, and input
|
||||
actions without taking shutdown ownership of the borrowed bootstrap.
|
||||
actions while borrowing the bootstrap; `ImGuiDevToolsFrameBackend` explicitly
|
||||
owns and disposes that bootstrap at shutdown.
|
||||
`WorldRenderDiagnostics`, `DebugVmRenderFactsPublisher`, and
|
||||
`RenderFrameDiagnosticsController` own the former render probes, debug facts,
|
||||
title/resource cadence, and public snapshot. The production handoff now reports
|
||||
|
|
@ -471,12 +475,29 @@ corrected-diff reviews are clean.
|
|||
|
||||
- compose concrete resource, world, private-presentation, and diagnostic owners;
|
||||
- replace `OnRender` with one `RenderFrameOrchestrator.Render` handoff;
|
||||
- remove obsolete fields/helpers/callbacks and prove the transitive render graph
|
||||
has no substantial path back to `GameWindow`;
|
||||
- preserve the existing shutdown resource list without transferring disposal;
|
||||
- remove obsolete fields/helpers/callbacks; prove immediate frame owners retain
|
||||
no direct `GameWindow`/anonymous-delegate back-reference, and document rather
|
||||
than deny recursive paths through borrowed canonical UI/input owners;
|
||||
- preserve the existing shutdown dependency order while making the
|
||||
developer-tools backend's pre-existing bootstrap ownership explicit;
|
||||
- measure line/field/method counts. Ownership decides completion; the expected
|
||||
signal is roughly 5,000–5,500 lines before Slice 8 composition cleanup.
|
||||
|
||||
Completed 2026-07-22. `GameWindow.OnRender` now creates one value-only
|
||||
`RenderFrameInput` and hands the complete frame to `RenderFrameOrchestrator`.
|
||||
Focused preparation, world-scene, private-presentation, and diagnostic owners
|
||||
preserve the accepted resource → ImGui begin → weather → world → portal →
|
||||
paperdoll → retained UI → ImGui submit → screenshot → diagnostics → GPU close
|
||||
order. Recovery now closes an active ImGui frame through the owning Silk
|
||||
controller and restores the exact text-render GL state after success or
|
||||
failure. Screenshot dimensions travel in the frame input rather than through a
|
||||
window callback, and immediate frame owners have no direct `GameWindow` or
|
||||
anonymous-delegate back-reference. `GameWindow.cs` is 4,666 raw lines / 196
|
||||
fields / 70 methods: 11,057 lines (70.3%) below the 15,723-line campaign
|
||||
baseline. The Release build and complete suite pass (7,341 passed / 5 skipped),
|
||||
and retail-conformance, architecture, and adversarial corrected-diff reviews
|
||||
are clean.
|
||||
|
||||
### H — release and connected gates
|
||||
|
||||
After three clean corrected-diff reviews:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue