2.9 KiB
| id | ticket | title | created | tags | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| LSN-0033 | deferred-overlay-and-primitive-composition | Debug Primitives Should Be a Final Overlay, Not Part of Game Composition | 2026-04-18 |
|
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
FrameComposerstorage 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.