--- id: LSN-0030 ticket: scene-bank-and-viewport-cache-refactor title: Canonical Scene Ownership Must Stay Separate from Viewport Caching created: 2026-04-14 tags: [gfx, runtime, tilemap, scene, render, asset] --- ## Context This discussion started as a ringbuffer exploration for tilemap rendering, but the work converged on a broader renderer and scene-model refactor. The key pressure was not world streaming in the open-world sense; it was keeping the canonical scene model simple while still allowing efficient viewport materialization, parallax, and future renderer evolution. The final implementation introduced `SceneBank` as the canonical loaded scene, `SceneViewportCache` as the materialized render cache, `SceneViewportResolver` as the movement and rematerialization policy owner, and a versioned binary `SCENE` asset payload. ## Key Decisions ### Canonical Scene Data Must Not Inherit Viewport Mechanics **What:** `SceneBank` owns the canonical scene as four `SceneLayer`s, each with its own `TileMap`. Viewport concerns such as ringbuffer movement, cache origin, hysteresis, and copy offsets do not live in the canonical scene types. **Why:** Once camera motion, parallax, and cache refresh strategy leak into the source-of-truth model, physics, tooling, serialization, and renderer evolution all start depending on transient viewport state. Keeping the scene canonical and static preserves simpler reasoning across the runtime. **Trade-offs:** This creates more explicit layers in the architecture: canonical scene, cache, and resolver. The model is cleaner, but integration requires a little more plumbing. ### Ringbuffer Is Safer Inside the Cache Than Inside the Scene **What:** The ringbuffer is an implementation detail of `SceneViewportCache`, with one internal ringbuffer per layer. **Why:** The cache is the right place to optimize for partial refresh, wraparound, and parallax movement. The scene itself should remain plain, indexable, and serialization-friendly. **Trade-offs:** The renderer must consume cache materialization rather than reading the canonical scene directly. That is an extra step, but it avoids contaminating the domain model with cyclical storage concerns. ### Resolver Owns Movement Policy, Not the Renderer **What:** `SceneViewportResolver` owns the master anchor, per-layer anchors, clamp, hysteresis, drift handling, and the copy instrumentation needed by the renderer. **Why:** If the renderer starts owning anchor advancement or rematerialization policy, world composition and camera policy become entangled. The resolver keeps that logic centralized and testable. **Trade-offs:** The renderer becomes dependent on `ResolverUpdate`, but in return it stays focused on composition instead of movement semantics. ### Binary Scene Assets Need a Versioned Contract Early **What:** `SCENE` assets now use a versioned binary payload with fixed four-layer layout, per-layer metadata, and packed tile records. The decoder validates version, layer count, tile size, tile count, and decoded size. **Why:** JSON payloads were convenient as a temporary unblocker but were fundamentally the wrong runtime contract for scene assets. A binary format is smaller, deterministic, and easier to validate. **Trade-offs:** Authoring and tooling need to respect a stricter binary format, but the runtime contract becomes more stable and future-ready. ## Patterns and Algorithms - Split world rendering into three responsibilities: canonical scene ownership; viewport cache storage; movement and refresh policy. - Keep camera input in pixel space, but advance anchors in tile space. - Use hysteresis to prevent refresh thrash at tile boundaries. - Prefer cache-backed renderer inputs over direct canonical map reads once materialization exists. - Put binary asset validation close to decode and fail loudly on malformed payloads. ## Pitfalls - Starting from a ringbuffer optimization can accidentally turn into a scene-model redesign if the boundaries are not made explicit. - Letting the renderer read `SceneBank` directly after introducing `SceneViewportCache` leaves the codebase in a fragile dual-model state. - Storing anchor-like state in `SceneLayer` would mix canonical scene data with transient viewport state. - Treating binary scene format as “to be decided later” blocks load and preload integration much longer than necessary. ## Takeaways - Canonical scene ownership, viewport caching, and viewport movement policy should be separate runtime concerns. - Ringbuffer is valuable as an internal cache mechanism, not as the canonical world model. - Hysteresis belongs in the resolver when cache refresh must avoid boundary flicker and churn. - Scene asset formats should become binary and versioned before they become widely depended on.