3.7 KiB
| id | ticket | title | created | tags | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| LSN-0031 | render-all-scene-cache-and-camera-integration | Frame Composition Belongs Above the Render Backend | 2026-04-18 |
|
Context
DSC-0025 split canonical scene ownership from viewport caching and resolver policy, but the runtime still treated Gfx.render_all() as the operational frame entrypoint. That left scene binding, camera state, cache refresh, and sprite submission spread across the wrong layer.
DSC-0026 completed the integration by making FrameComposer the owner of frame orchestration and reducing Gfx to a backend that consumes already prepared render state.
Key Decisions
Frame Orchestration Must Not Live in the Backend
What:
FrameComposer.render_frame() became the canonical frame service, while Gfx.render_all() was retired from the runtime-facing flow.
Why:
The backend should execute composition, not decide scene binding, camera policy, cache refresh, or sprite lifecycle. Keeping those responsibilities above Gfx preserves a cleaner ownership model and avoids re-entangling policy with raster code.
Trade-offs: This adds an explicit orchestration layer between runtime callsites and the renderer, but the resulting boundaries are easier to evolve and test.
Scene Binding, Camera, Cache, and Sprites Form One Operational Unit
What:
FrameComposer owns:
- active scene binding by bank id and shared scene reference;
- camera coordinates in top-left world pixel space;
SceneViewportCacheandSceneViewportResolver;- a frame-emission
SpriteController.
Why: These concerns all define what a frame is. Splitting them across multiple owners would recreate stale-state bugs and make no-scene behavior ambiguous.
Trade-offs: The composer becomes a richer subsystem, but it carries policy in one place instead of leaking it into unrelated APIs.
The World Path Must Stay Tile-Size Agnostic
What:
The integrated frame path derives cache sizing, resolver math, and world-copy behavior from per-layer scene metadata instead of a hard-coded 16x16 assumption.
Why:
The scene contract already allows canonical 8x8, 16x16, and 32x32 tile sizes. The frame service has to consume that contract faithfully or it becomes a hidden compatibility break.
Trade-offs: The integration and tests need to exercise more than the legacy default path, but the renderer no longer bakes in a false invariant.
Patterns and Algorithms
- Put frame policy in a dedicated orchestration layer and keep the renderer backend-oriented.
- Treat scene binding, camera state, cache lifetime, and sprite submission as one cohesive frame model.
- Refresh cache state inside the orchestrator before composition instead of letting the renderer discover refresh policy.
- Prefer frame-emission sprite submission with internal ordering over caller-owned sprite slots.
- Keep the no-scene path valid so world composition remains optional, not mandatory.
Pitfalls
- Leaving
render_all()alive as a canonical path creates a fragile dual-service model. - Letting
Gfxown cache refresh semantics collapses the boundary between policy and execution. - Requiring a scene for every frame quietly breaks sprite-only or fade-only operation.
- Testing only
16x16scenes hides regressions against valid8x8or32x32content.
Takeaways
- Frame composition belongs in a subsystem that owns policy, not in the backend that draws pixels.
- Scene binding, camera, cache, resolver, and sprite submission should converge under one frame owner.
- No-scene rendering is part of the contract and should stay valid throughout integration work.
- Tile-size assumptions must be derived from canonical scene metadata, never from renderer habit.