--- id: DEC-0016 ticket: deferred-overlay-and-primitive-composition title: Deferred GFX Overlay Outside FrameComposer status: accepted created: 2026-04-18 accepted: 2026-04-18 agenda: AGD-0028 plans: [PLN-0026, PLN-0027, PLN-0028, PLN-0029] tags: [gfx, runtime, render, frame-composer, overlay, primitives, hud] --- ## Status Accepted. ## Contexto `DEC-0014` and `DEC-0015` established `FrameComposer` as the canonical orchestration path for game-frame composition and exposed that orchestration publicly through `composer.*`. That migration left `gfx.draw_text(...)` and other `gfx` primitives with their historical immediate-write behavior against the working framebuffer. Once the runtime moved to end-of-frame composition through `FrameComposer.render_frame()`, those immediate writes became unstable: scene-backed frame composition can rebuild the backbuffer after primitive calls have already touched it. The resulting conflict is not about whether primitives should remain available. It is about their semantic place in the pipeline. The accepted direction of this thread is that `gfx` primitives are not part of the canonical game composition model. They are primarily for debug, quick visual instrumentation, and rapid artifacts, and they must remain agnostic to scene/tile/sprite/HUD composition. Relevant performance context migrated from `AGD-0010` also remains in force: - the renderer continues to be a destructive software framebuffer model, not a retained scene graph or GPU-style renderer; - internal primitive fast paths remain desirable; - memory growth must remain constrained for the handheld target; - optimization of primitive execution must not alter observable semantics. ## Decisao `gfx.*` primitives and text SHALL move to a deferred final overlay model that lives outside `FrameComposer`. Normatively: - `FrameComposer` SHALL remain responsible only for canonical game-frame composition: - scene composition; - sprite composition; - canonical HUD composition when such a HUD stage exists. - `FrameComposer` MUST NOT become the owner of debug/primitive overlay state. - Public `gfx.*` primitives, including `gfx.draw_text(...)`, SHALL belong to a V1 `gfx` overlay/debug family. - That overlay/debug family SHALL be deferred rather than written immediately as the stable operational contract. - The deferred overlay/debug stage SHALL be drained after `hud_fade`. - The deferred overlay/debug stage SHALL be above scene, sprites, and canonical HUD in final visual order. - The no-scene path MUST preserve the same final overlay/debug semantics. - `gfx.*` primitives MUST remain semantically separate from scene/tile/sprite/HUD composition. - The implementation MUST preserve operational separation sufficient to prevent the canonical game pipeline from depending on transient primitive/debug state. ## Rationale This decision keeps the architectural boundary clean. `FrameComposer` exists to own the canonical game frame. Debug primitives do not belong to that contract. Pulling them into `FrameComposer` would make the orchestration service responsible for a second semantic domain with different goals: - game composition must be deterministic and canonical; - primitive/text overlay must be opportunistic, screen-space, and pipeline-agnostic. Keeping overlay/debug outside `FrameComposer` also aligns with the stated product intent: these primitives are useful helpers, but they are not meant to become a second composition language for games. Draining them after `hud_fade` preserves the user-visible requirement that debug/overlay content stay truly on top and legible. This is more faithful to the accepted intent than treating primitives as part of HUD or world composition. Finally, separating semantic ownership still leaves room for implementation reuse. Raster backends, span paths, and buffer-writing helpers may still be shared internally, provided the public operational model remains separate. ## Invariantes / Contrato ### 1. Ownership Boundary - `FrameComposer` MUST own only canonical game-frame composition. - Primitive/debug overlay state MUST live outside `FrameComposer`. - The canonical game pipeline MUST NOT depend on primitive/debug overlay state for correctness. ### 2. Overlay Semantics - `gfx.draw_text(...)` and sibling `gfx` primitives SHALL be treated as deferred final overlay/debug operations. - Immediate direct writes to `back` MUST NOT remain the stable operational contract for these primitives. - Final overlay/debug output MUST appear after: - scene composition; - sprite composition; - canonical HUD composition, if present; - `scene_fade`; - `hud_fade`. ### 3. Separation from Game Composition - Primitive/debug overlay MUST NOT be reinterpreted as scene content. - Primitive/debug overlay MUST NOT be reinterpreted as sprite content. - Primitive/debug overlay MUST NOT be the vehicle for canonical HUD composition. - The public `gfx.*` primitive surface SHALL remain pipeline-agnostic relative to `composer.*`. ### 4. Consistency Across Frame Paths - The scene-bound path and no-scene path MUST expose the same final overlay/debug behavior. - Users MUST NOT need to know whether a scene is bound for `gfx.*` primitives to appear as final overlay/debug content. ### 5. Internal Optimization Contract - Internal fast paths for lines, spans, fills, clears, or similar primitive operations MAY be introduced. - Such fast paths MUST preserve the observable deferred overlay/debug semantics. - This decision DOES NOT require fine-grained dirtying or per-primitive-class invalidation in the first migration. ## Impactos ### Runtime / Drivers - The runtime frame-end sequence must gain a distinct overlay/debug drain stage outside `FrameComposer`. - `gfx.draw_text(...)` and peer primitives can no longer rely on stable immediate framebuffer writes once this migration lands. ### GFX Backend - `Gfx` will need an explicit deferred overlay/debug command path or equivalent subsystem boundary. - Shared raster helpers remain allowed, but the overlay/debug phase must stay semantically distinct from scene/sprite/HUD composition. ### FrameComposer - `FrameComposer` must remain free of primitive/debug overlay ownership. - Any future HUD integration must not collapse that boundary. ### Spec / Docs - The canonical graphics/runtime spec must describe `gfx.*` primitives as deferred final overlay/debug operations rather than stable immediate backbuffer writes. - Documentation that describes frame ordering must show overlay/debug after `hud_fade`. ### Performance Follow-up - `AGD-0010` remains the home for broader renderer performance work, dirtying strategy, and low-level primitive optimization policy. - Primitive optimization carried out under that thread must respect the normative separation established here. ## Referencias - [AGD-0028-deferred-overlay-and-primitive-composition.md](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/discussion/workflow/agendas/AGD-0028-deferred-overlay-and-primitive-composition.md) - [AGD-0010-perf-gfx-render-pipeline-and-dirty-regions.md](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/discussion/workflow/agendas/AGD-0010-perf-gfx-render-pipeline-and-dirty-regions.md) - [DEC-0014-frame-composer-render-integration.md](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/discussion/workflow/decisions/DEC-0014-frame-composer-render-integration.md) - [DEC-0015-frame-composer-public-syscall-surface.md](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/discussion/workflow/decisions/DEC-0015-frame-composer-public-syscall-surface.md) ## Propagacao Necessaria - A new implementation plan MUST be created before code changes. - That plan MUST cover: - deferred overlay/debug ownership outside `FrameComposer`; - runtime frame-end ordering changes; - no-scene path parity; - spec/documentation updates for `gfx.*` primitive semantics. - The implementation plan MUST NOT reopen the ownership boundary accepted here. ## Revision Log - 2026-04-18: Initial accepted decision from `AGD-0028`. - 2026-04-18: Linked implementation plan family `PLN-0026` through `PLN-0029`.