prometeu-runtime/discussion/lessons/DSC-0026-render-all-scene-cache-and-camera-integration/LSN-0031-frame-composition-belongs-above-the-render-backend.md
bQUARKz 73cf96ed6c
All checks were successful
Intrepid/Prometeu/Runtime/pipeline/pr-master This commit looks good
housekeep
2026-04-18 16:31:23 +01:00

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
gfx
runtime
render
camera
scene
sprites
frame-composer

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;
  • SceneViewportCache and SceneViewportResolver;
  • 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 Gfx own 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 16x16 scenes hides regressions against valid 8x8 or 32x32 content.

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.