--- id: LSN-0033 ticket: deferred-overlay-and-primitive-composition title: Debug Primitives Should Be a Final Overlay, Not Part of Game Composition created: 2026-04-18 tags: [gfx, runtime, render, frame-composer, overlay, primitives, hud, debug] --- ## Context After `FrameComposer.render_frame()` became the canonical game-frame entrypoint, immediate `gfx.*` primitive writes were no longer stable. Scene-backed composition could rebuild the framebuffer after `draw_text(...)` or other debug primitives had already written to it. `DSC-0028` resolved that conflict by moving `gfx.*` primitives into a deferred overlay/debug stage outside `FrameComposer`, drained only after canonical game composition and fades are complete. ## Key Decisions ### Debug Overlay Must Stay Outside the Canonical Game Pipeline **What:** `FrameComposer` keeps ownership of canonical game composition, while debug/text/primitive commands are captured separately and drained later as a final overlay. **Why:** Game composition and debug overlay have different purposes. The first must remain canonical and deterministic; the second must remain opportunistic, screen-space, and independent from scene or sprite semantics. **Trade-offs:** The renderer needs a second deferred path, but the game pipeline no longer depends on transient debug state. ### Final Visual Ordering Matters More Than Immediate Writes **What:** Overlay/debug commands are drained after scene composition, sprite composition, and fades, with parity between scene-bound and no-scene frame paths. **Why:** The stable user-visible contract is that debug primitives appear on top. Immediate writes were only an implementation detail, and they stopped preserving that contract once frame composition became deferred and canonical. **Trade-offs:** This changes primitive semantics from "write now" to "show at frame end," but it produces the behavior users actually rely on. ## Patterns and Algorithms - Separate canonical composition state from debug-overlay state even when both reuse the same raster backend. - Capture primitives as commands first, then drain them at the final stage where visual priority is unambiguous. - Preserve the same overlay semantics whether a scene is bound or not. - Keep implementation reuse internal while maintaining a clear semantic boundary in the public model. ## Pitfalls - Treating debug primitives as part of HUD or scene composition will eventually couple tooling/debug behavior to gameplay pipeline rules. - Draining overlay before fades or before final frame composition breaks the visible "always on top" contract. - Reusing `FrameComposer` storage for overlay state collapses the ownership split that prevents these bugs. ## Takeaways - Immediate framebuffer writes are not a reliable contract once final composition is orchestrated elsewhere. - Debug primitives work best as a dedicated final overlay layer. - Ownership separation is what keeps debug behavior stable while the canonical render pipeline evolves.