--- id: DEC-0014 ticket: render-all-scene-cache-and-camera-integration title: Frame Composer Render Integration status: accepted created: 2026-04-14 accepted: 2026-04-14 agenda: AGD-0026 plans: [PLN-0017, PLN-0018, PLN-0019, PLN-0020, PLN-0021] tags: [gfx, runtime, render, camera, scene, sprites] --- ## Status Accepted. ## Contexto `DSC-0025` closed the canonical scene model around `SceneBank`, `SceneViewportCache`, and `SceneViewportResolver`, but the operational frame loop still remained split. `Gfx` still exposed `render_all()`, while the new world path already existed separately as `render_scene_from_cache(...)`. This left the runtime with an incomplete composition model: - canonical scene/camera/cache architecture had already changed; - the normal frame entrypoint had not yet been integrated with that architecture; - sprite ownership was still too coupled to `Gfx` and to a slot-first `active` model. This decision closes the ownership and composition model for the next integration phase. ## Decisao The runtime SHALL converge to a `FrameComposer`-owned frame orchestration model. Normatively: - `Gfx.render_all()` MUST be retired as the canonical frame service. - The canonical operational frame entrypoint SHALL become `FrameComposer.render_frame()`. - `FrameComposer` SHALL live in `hardware/drivers`, alongside `Gfx`. - `Hardware` SHALL aggregate both `FrameComposer` and `Gfx`. - `FrameComposer` SHALL own the frame-operational state: - active scene binding; - camera / viewport state; - `SceneViewportCache`; - `SceneViewportResolver`; - sprite submission state through `SpriteController`. - `Gfx` SHALL remain a low-level visual backend responsible for composition, blit, and raster execution. - `Gfx` MUST NOT remain the owner of scene state or sprite submission state. ## Rationale This split preserves a clean ownership model: - `FrameComposer` decides what the frame is; - `Gfx` executes how the frame is drawn. Keeping orchestration in `FrameComposer` avoids re-entangling renderer code with camera policy, cache refresh policy, and scene binding. Keeping `FrameComposer` in `hardware/drivers` instead of `hal` preserves room for backend-specific acceleration while avoiding a policy-heavy abstraction in HAL. This also preserves future backend freedom: - software path today; - hardware-assisted blit path later; - or a more PPU-like backend in bare-metal environments. It also preserves the scene-model contract already accepted below the frame layer: - tile size is a property of each scene layer; - the frame orchestrator must consume that contract, not redefine it; - `FrameComposer` must therefore remain tile-size agnostic rather than hard-coding `16x16` assumptions. ## Invariantes / Contrato ### 1. Frame Entry - The canonical public frame orchestration path SHALL be `FrameComposer.render_frame()`. - `FrameComposer.render_frame()` SHALL be capable of producing a valid frame 100% of the time. - A valid frame MUST NOT require a scene to be bound. ### 2. No-Scene Behavior - If no scene is bound, `FrameComposer.render_frame()` SHALL compose only: - emitted sprites; - fades already owned by the visual backend. - No implicit clear SHALL be performed. - Clearing the back buffer SHALL remain the responsibility of the caller / developer. - In the no-scene state: - cache MUST be absent or inert; - resolver MUST be absent or inert; - the system SHALL expose explicit scene-availability status. ### 3. Scene Binding - Scene binding SHALL be performed by `bind_scene(scene_bank_id)`. - `FrameComposer` SHALL depend on `SceneBankPoolAccess`. - `FrameComposer` MUST resolve scenes through the pool, not through copied scene values. - Scene access MUST be pointer-based / shared-reference based only. - On bind, `FrameComposer` SHALL store: - `scene_bank_id`; - `Arc` for the resolved scene. - The `SceneViewportCache` SHALL live inside `FrameComposer` while the scene remains bound. - `unbind_scene()` SHALL: - remove the active scene; - discard the associated cache; - invalidate the world path; - keep the frame path valid for no-scene composition. - Replacing the contents of a bound scene slot SHALL require a new explicit bind. - `FrameComposer` MUST NOT poll the scene bank pool each frame to revalidate the binding. - A new `bind_scene(...)` SHALL replace the previous bound scene completely. ### 4. Camera - The V1 camera contract SHALL be minimal. - `set_camera(x, y)` SHALL accept `i32` pixel coordinates. - `x` and `y` SHALL represent the top-left of the viewport in world space. - Camera follow, smoothing, shake, cinematic transitions, and similar behaviors are OUT OF SCOPE for this decision. ### 5. Cache and Resolver - `FrameComposer` SHALL own both `SceneViewportCache` and `SceneViewportResolver`. - `FrameComposer` SHALL apply `CacheRefreshRequest`s to the cache. - `Gfx` MUST NOT own cache refresh policy. - `Gfx` MUST only consume already prepared render state. ### 5A. Tile Size Contract - `FrameComposer` SHALL remain tile-size agnostic. - `FrameComposer` MUST accept scene layers whose canonical `tile_size` is `8x8`, `16x16`, or `32x32`. - `FrameComposer` MUST NOT impose `16x16` as a bind-time, cache-time, resolver-time, or render-time precondition. - Cache sizing, resolver math, and world-copy preparation SHALL derive from the `tile_size` declared by each bound scene layer. - Compatibility checks, when needed, MUST be derived from canonical scene-layer and glyph-bank metadata rather than from a hard-coded compositor default. - Any implementation path that only works for `16x16` tiles is NON-COMPLIANT with this decision. ### 6. Sprite Model - `Sprite.active` MUST be removed from the canonical operational model. - Sprite submission SHALL become frame-emission based. - `SpriteController` SHALL be the sprite submission subsystem owned by `FrameComposer`. - The sprite frame capacity SHALL remain capped at `512` for V1. - The sprite counter SHALL be reset at the start of each frame. - The caller MUST NOT provide sprite indices directly. - Each `emit_sprite(...)` call SHALL occupy the next available internal slot. - Overflow beyond capacity SHALL be ignored. - Overflow SHOULD leave room for system logging / telemetry. - Future certification MAY penalize sprite overflow, but that is not part of this decision. - `emit_sprite(...)` SHALL NOT require a dedicated reset API beyond the normal frame lifecycle. ### 7. Sprite Ordering - Each sprite SHALL carry: - `layer`; - `priority`. - `layer` SHALL remain numeric for now. - The sprite `layer` type SHALL match the scene layer reference type used by the scene model. - Composition SHALL be layer-based. - Within a layer: - lower `priority` SHALL render first; - ties SHALL resolve FIFO by emission order. ### 8. Composition Scope - HUD integration is OUT OF SCOPE for the first integration phase covered by this decision. - The first integration phase SHALL focus on: - world scene path; - sprites; - fades. ## Impactos ### HAL - `GfxBridge` and adjacent visual contracts will need to stop treating `render_all()` as the canonical operational frame path. ### Drivers / Hardware - `Hardware` will need to aggregate `FrameComposer` next to `Gfx`. - `Gfx` will need to lose ownership of scene/sprite operational state. - Sprite submission state will need to move into `SpriteController`. - `FrameComposer`, cache, and resolver integration must preserve per-layer `tile_size` semantics, including `8x8`. ### Runtime / VM - The VM runtime will eventually trigger frame composition through the new `FrameComposer` path rather than depending on `Gfx.render_all()`. - The VM/runtime side should not own the detailed cache or scene orchestration policy directly once `FrameComposer` exists in hardware/drivers. ### Asset / Scene Flow - Scene activation will become explicit through bank-id binding. - Scene slot replacement will require explicit rebinding behavior from callers. - Scene-driven tile-size metadata must propagate unchanged into `FrameComposer` orchestration and backend copy preparation. ## Referencias - [AGD-0026-render-all-scene-cache-and-camera-integration.md](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/discussion/workflow/agendas/AGD-0026-render-all-scene-cache-and-camera-integration.md) - [LSN-0030-canonical-scene-cache-and-resolver-split.md](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/discussion/lessons/DSC-0025-scene-bank-and-viewport-cache-refactor/LSN-0030-canonical-scene-cache-and-resolver-split.md) ## Propagacao Necessaria - A new implementation plan MUST be created from this decision before code changes. - `FrameComposer` and `SpriteController` need explicit planning and migration sequencing. - `Gfx.render_all()` retirement MUST be planned rather than removed ad hoc. - The frame service rename and integration path MUST be propagated through the frame loop callsites. - Plan steps and tests that cover world composition MUST explicitly include `8x8` tile-size coverage. ## Revision Log - 2026-04-14: Initial accepted decision from `AGD-0026`. - 2026-04-15: Revision accepted to make `FrameComposer` explicitly tile-size agnostic and to require `8x8` support alongside `16x16` and `32x32`.