--- id: PLN-0036 ticket: perf-host-desktop-frame-pacing-and-presentation title: Plan - Host Desktop Frame Pacing and Presentation status: done created: 2026-04-20 completed: 2026-04-20 tags: [perf, host, desktop, frame-pacing, presentation, debug] --- ## Briefing Implement `DEC-0019` by removing unconditional desktop redraw polling, introducing a canonical host-side frame-publication trigger, preserving the last visible frame during paused or idle states, and updating the normative specs to reflect the locked contract. ## Decisions de Origem - `DEC-0019` - Host Desktop Frame Pacing and Presentation ## Alvo Deliver a host desktop execution path where: - the winit loop no longer uses continuous polling by default; - redraw happens only after logical frame publication or explicit host-owned invalidation events; - RGB565 to RGBA8 conversion remains full-frame but only runs on demand; - paused, debugger-waiting, and no-cartridge states keep the last valid image without continuous redraw; - specs and tests match the accepted decision. ## Escopo - Update the desktop host loop in `crates/host/prometeu-host-desktop-winit/src/runner.rs`. - Introduce or stabilize a canonical "new frame published" signal at the runtime-host boundary. - Track host-owned invalidation causes for overlay, debugger, and window events. - Update the relevant runtime specs under `docs/specs/runtime/`. - Add or extend automated tests for redraw gating and paused-state behavior. ## Fora de Escopo - Dirty-region framebuffer conversion. - GPU/shader-based presentation paths. - New guest-visible ABI or debug surface. - Redesign of overlay visuals or debugger protocol beyond invalidation signaling. - Reopening the architecture covered by `DEC-0019`. ## Plano de Execucao ### Step 1 - Establish the canonical frame-publication trigger **What:** Define the single host-consumable signal that indicates a new logical frame has been published. **How:** Inspect the current buffer swap/publication point in the desktop runtime path and choose the existing internal publication event, dirty flag, or monotonic frame counter that can become the canonical trigger required by `DEC-0019`. If no stable signal exists, add a host-internal publication marker that changes exactly once per newly published logical frame and is not guest-visible. **File(s):** - `crates/host/prometeu-host-desktop-winit/src/runner.rs` - any directly related runtime/firmware module discovered as the current frame-publication owner **Depends on:** none ### Step 2 - Replace unconditional redraw requests with invalidation-driven scheduling **What:** Remove the unconditional redraw call from the idle loop and gate presentation on frame publication or host-owned invalidation. **How:** Refactor `about_to_wait()` and redraw scheduling in `runner.rs` so the host requests redraw only when: - a new logical frame was published since the last presentation; - the window receives a visible invalidation event such as resize/expose; - overlay state toggles or changes a visible host-owned layer; - debugger-visible state changes require host-side recomposition. Introduce explicit host-owned invalidation state if needed so redraw causes are tracked instead of inferred by polling. **File(s):** - `crates/host/prometeu-host-desktop-winit/src/runner.rs` **Depends on:** Step 1 ### Step 3 - Move the event loop from default `Poll` to deadline/event waiting **What:** Change the normal desktop control flow so the host waits for the next logical deadline or external event instead of spinning continuously. **How:** Update `resumed()` and any related loop control code in `runner.rs` to stop using `ControlFlow::Poll` as the default. Use `WaitUntil` when a next logical tick deadline is known and `Wait` when the host is effectively idle with no immediate timing deadline. Keep any aggressive polling path behind an explicit profiling-only switch rather than as baseline behavior. **File(s):** - `crates/host/prometeu-host-desktop-winit/src/runner.rs` - `crates/host/prometeu-host-desktop-winit/src/main.rs` if a profiling-only opt-in flag is wired at startup **Depends on:** Step 2 ### Step 4 - Preserve the last valid frame during paused, breakpoint, and no-cart states **What:** Ensure non-running machine states do not cause repeated conversion/presentation work while still allowing host-owned UI recomposition when something visible changes. **How:** Separate "machine produced a new frame" from "host-owned layer needs redraw". Keep the last presented RGBA output available for reuse, avoid logical buffer swaps during pause-only overlay activity, and ensure debugger waiting and no-cartridge states do not schedule continuous redraws. If overlay or debugger content changes while the machine is paused, redraw only the host-owned composition layer on explicit invalidation. **File(s):** - `crates/host/prometeu-host-desktop-winit/src/runner.rs` - `crates/host/prometeu-host-desktop-winit/src/overlay.rs` - `crates/host/prometeu-host-desktop-winit/src/debugger.rs` **Depends on:** Step 2 ### Step 5 - Update the normative specs **What:** Propagate the accepted decision into the canonical runtime specs. **How:** Update the portability and debug chapters so they explicitly state that desktop presentation observes published logical frames, that host-owned overlay/debug redraw is event-driven rather than continuously polled, and that stable host HUD state does not justify perpetual redraw. Keep the text aligned with the locked decision and do not introduce new architecture. **File(s):** - `docs/specs/runtime/10-debug-inspection-and-profiling.md` - `docs/specs/runtime/11-portability-and-cross-platform-execution.md` **Depends on:** Step 1 ### Step 6 - Add regression tests and evidence **What:** Add automated coverage for the redraw contract and document manual evidence where platform events are hard to model. **How:** Extend or add tests in the desktop host crate to cover: - no redraw request when no new frame and no host invalidation occurred; - redraw request after a newly published logical frame; - redraw request after overlay toggle or equivalent host-owned invalidation; - no continuous redraw in paused/debugger-waiting/no-cart states; - preservation of last visible frame semantics while paused. Where direct winit event simulation is difficult, factor logic into testable state transitions and keep any remaining platform verification in a short manual checklist. **File(s):** - `crates/host/prometeu-host-desktop-winit/src/runner.rs` - `crates/host/prometeu-host-desktop-winit/src/overlay.rs` if overlay invalidation helpers become testable - `crates/host/prometeu-host-desktop-winit/src/debugger.rs` if debugger invalidation hooks are exposed **Depends on:** Steps 2, 3, 4 ## Criterios de Aceite - The default desktop event loop no longer runs in continuous `ControlFlow::Poll`. - The host requests redraw only after canonical frame publication or explicit host-owned invalidation. - Full-frame RGB565 to RGBA8 conversion remains in place but is not executed continuously while the visible machine frame is unchanged. - Paused, breakpoint, debugger-waiting, and no-cartridge states preserve the last valid image without continuous redraw. - Overlay or debugger changes can still trigger targeted host-owned redraw when visually necessary. - No guest-visible ABI is added for this feature. - The relevant runtime specs state the same redraw and presentation contract as `DEC-0019`. - Automated tests cover the main redraw gating paths and paused-state behavior. ## Tests / Validacao ### Unit Tests - Add focused tests for redraw scheduling state transitions in the desktop host runner. - Add tests for any helper that tracks frame-publication state or host-owned invalidation causes. ### Integration Tests - Extend host crate tests to verify frame publication and debugger/overlay invalidation do not regress redraw policy. - Run `cargo test -p prometeu-host-desktop-winit --lib`. ### Manual Verification - Launch the desktop host with overlay disabled and confirm CPU usage drops when the machine is visually stable. - Toggle overlay on and off and confirm redraw happens on the visible transition without restoring continuous polling. - Pause execution or wait for debugger start and confirm the last frame remains visible without repeated redraw. - Resize or expose the window and confirm the surface is recomposed correctly. ## Riscos - The actual frame-publication point may be less explicit than assumed, requiring a small host-internal contract addition before redraw gating can be made reliable. - winit waiting semantics can introduce timing regressions if the next logical deadline is computed incorrectly. - Overlay or debugger code may currently assume redraw is always free, which can surface hidden invalidation bugs after polling is removed. - Manual verification may still be needed for some window-system invalidation behavior that is cumbersome to model in tests.