--- id: DEC-0013 ticket: scene-bank-and-viewport-cache-refactor title: Decision - Scene Bank and Viewport Cache Model status: accepted created: 2026-04-13 accepted: 2026-04-13 agenda: AGD-0025 plans: [PLN-0011, PLN-0012, PLN-0013, PLN-0014, PLN-0015] tags: [gfx, tilemap, runtime, render] --- ## Status Accepted. This decision normatively locks the V1 model for scene-backed tilemap rendering around: - `Scene` as canonical loaded state; - `SceneLayer` as the canonical layer unit; - `SceneViewportCache` as the materialized render cache; - `SceneViewportResolver` as the owner of totems, drift, hysteresis, clamp, and cache update decisions. ## Contexto The current runtime couples three concerns too tightly: - canonical layer data; - scroll state; - direct renderer consumption. In the current implementation, the `64x64` maps created for scrollable layers are not an independent viewport cache. They are the actual layer maps consumed directly by the renderer. That shape makes it harder to: - keep canonical world state simple; - materialize only the viewport-relevant subset for render; - evolve camera separately from the tilemap model; - reduce repeated tilemap resolution work before the final destructive blit into `back`. During agenda `AGD-0025`, the discussion initially explored ringbuffer as a general topic, but converged on a different architectural target: a clean split between canonical scene state and render-oriented viewport materialization. ## Decisao The runtime SHALL adopt the following V1 model: 1. Canonical loaded tilemap state SHALL be represented by `Scene`. 2. A `Scene` SHALL contain four canonical `SceneLayer`s. 3. Each `SceneLayer` SHALL own its canonical `TileMap`. 4. Each `TileMap` SHALL remain the canonical grid of `Tile`s for that layer. 5. The renderer MUST NOT consume canonical `Scene` data directly for normal world composition. 6. The renderer SHALL consume a `SceneViewportCache` derived from `Scene`. 7. `SceneViewportCache` SHALL be operational render state only and MUST NOT become the semantic source of truth for gameplay or physics. 8. `SceneViewportCache` SHOULD use internal ringbuffer storage as the preferred V1 implementation strategy. 9. Ringbuffer details MUST remain encapsulated inside `SceneViewportCache` and MUST NOT leak into the semantic contract of `Scene`, `SceneLayer`, `TileMap`, or `Tile`. 10. `ScrollableTileLayer` SHALL be removed from the architecture and MUST NOT be preserved as a canonical runtime concept. 11. `background` and `parallax` SHALL remain expressible through `SceneLayer` itself, not through a separate V1 background type. 12. Each `SceneLayer` SHALL carry a relative movement factor against a master totem, allowing normal layers, background-like layers, and parallax-like layers under the same layer contract. ## Rationale This decision prefers architectural clarity over preserving the current type graph. The key arguments are: - `Tile` remains light enough that keeping canonical tilemaps resident is acceptable in the current baseline. - The more pressing cost appears to be repeated world resolution and composition work, not merely the existence of tilemap data in memory. - A dedicated viewport cache gives the renderer a better contract without forcing gameplay and physics to depend on residency mechanics. - Internal ringbuffering in the cache is useful because the first expected gain is avoiding repeated cache-side copies, even if final composition into `back` remains destructive in V1. - Keeping parallax inside `SceneLayer` avoids prematurely splitting the scene model into separate layer families while still allowing differentiated movement behavior. This model also keeps room for later refinement: - richer camera semantics can evolve separately; - background-specific types can still be introduced later if the layer contract becomes insufficient; - bank-type wiring can still be finalized during planning/implementation; - cache implementation can improve while the canonical scene contract remains stable. ## Invariantes / Contrato ### 1. Canonical Scene Model - `Scene` SHALL be the canonical loaded aggregate. - `Scene` SHALL contain four canonical `SceneLayer`s. - `SceneLayer` SHALL be the canonical replacement for the old `TileLayer`. - `SceneLayer` SHALL minimally include: - `glyph_bank_id` - `tile_size` - `active` - `totem_factor (x, y)` - `tilemap` - `TileMap` SHALL remain a canonical grid of `Tile`. - `Tile` SHALL remain lightweight and MUST NOT be expanded into resolved RGB pixel payload for canonical storage. ### 2. Viewport Cache Model - `SceneViewportCache` SHALL be a single cache aggregate for one `Scene`. - It SHALL contain four internal layer caches, one per canonical scene layer. - The preferred V1 implementation is one internal ringbuffer per layer cache. - Ringbuffer details MUST stay internal to the cache implementation. - `SceneViewportCache` MAY store lightweight derived fields to accelerate raster, including: - resolved `glyph_id` - resolved `palette_id` - packed flip flags - `active/empty` markers - fast layer-local glyph bank references - `SceneViewportCache` MUST NOT duplicate full physics state unless a later decision explicitly requires it. - `SceneViewportCache` SHALL be the immediate source of copy data for world blits, but MUST NOT become the source of truth for world semantics. ### 3. Resolver Contract - `SceneViewportResolver` SHALL own: - master totem - per-layer derived totems - drift calculation - hysteresis - clamp logic - cache update decisions - An external caller SHALL provide camera position to the resolver. - The resolver SHALL treat that camera position as input for the master totem flow. - The resolver SHALL decide whether cache updates are needed. - The resolver SHALL trigger cache update operations when needed. - The resolver SHALL know how to instrument per-layer copy requests from the cache to the final compositor. - The resolver MUST NOT perform the actual framebuffer copy itself. ### 4. Viewport Size and Cache Update Policy - V1 world viewport cache size SHALL be materially larger than the minimum visible world tile window and MUST include explicit halo for cache stability. - An initial working target in the order of `25x16` tiles is accepted as planning guidance, but exact numeric sizing MAY be finalized in the implementation plan. - Normal cache update SHALL occur by line and/or column. - When simultaneous X and Y movement requires corner refresh, the cache MUST support area/region refresh to avoid reloading tiles already present. - Swapping to a different `Scene` SHALL fully invalidate `SceneViewportCache`. - Normal camera motion MUST NOT invalidate the whole viewport cache as the default path. ### 5. Totem, Drift, and Hysteresis - The resolver SHALL use a master totem in tile space. - Camera input SHALL remain in pixel space. - For `16x16` tiles, the master totem center SHALL be computed as: - `cx = 16 * i + 8` - `cy = 16 * j + 8` - Drift SHALL be computed as: - `dx = x - cx` - `dy = y - cy` - V1 SHALL use hysteresis with: - an internal safe band where no cache movement occurs; - an external trigger band that advances the totem and requests cache refresh. - Initial working values in the order of `safe = 12 px` and `trigger = 20 px` are accepted as planning guidance, but exact numeric tuning MAY be finalized in the implementation plan. - Inside the safe band, no cache movement SHALL occur. - Between safe and trigger, the system SHALL tolerate drift without rematerialization. - Beyond trigger, the resolver SHALL move the totem discretely in tile steps and request cache refresh. - Hysteresis is mandatory in V1 to prevent edge flick/thrash. ### 6. Clamp Behavior - The resolver SHALL clamp the master totem against scene bounds. - Initial clamp reasoning SHALL be based on minimums around `(w/2, h/2)` and maximums around `layer_size - (w/2, h/2)`. - Near bounds, the cache MAY remain asymmetrically aligned relative to camera expectations. - That asymmetry at scene edges SHALL be considered expected behavior, not an error. ### 7. Composition Contract - `HUD` SHALL remain above world composition. - `sprites` MAY appear between canonical world layers. - The observable composition order SHALL remain: - `layer 0` - intermediate sprites - `layer 1` - intermediate sprites - `layer 2` - intermediate sprites - `layer 3` - intermediate sprites - `HUD` - `sprites`, `HUD`, and `fades` MUST remain outside the invalidation contract of `SceneViewportCache`. - V1 MAY still use destructive full composition ordering in `back`. - The expected win in V1 comes from avoiding repeated brute-force canonical tilemap resolution before that final destructive composition. ## Impactos This decision impacts the runtime model directly: - `TileLayer` will be replaced by `SceneLayer`. - `ScrollableTileLayer` will be removed. - bank integration and naming at the asset-bank enum level SHALL be aligned during planning/implementation. - The renderer contract will shift from direct map consumption to cache consumption. - Scene loading, viewport cache maintenance, and composition responsibilities become explicitly separated. Expected propagation areas: - bank/domain model types; - tilemap/layer runtime structures; - renderer world composition flow; - camera-to-render adapter logic; - future plan/implementation artifacts derived from this decision. ## Referencias - Agenda: [AGD-0025-scene-bank-and-viewport-cache-refactor.md](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/discussion/workflow/agendas/AGD-0025-scene-bank-and-viewport-cache-refactor.md) - Current tile/layer model: - [tile_layer.rs](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/crates/console/prometeu-hal/src/tile_layer.rs:1) - [tile.rs](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/crates/console/prometeu-hal/src/tile.rs:1) - [glyph.rs](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/crates/console/prometeu-hal/src/glyph.rs:1) - Current renderer usage: - [gfx.rs](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/crates/console/prometeu-drivers/src/gfx.rs:291) - [gfx.rs](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/crates/console/prometeu-drivers/src/gfx.rs:594) - [gfx.rs](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/crates/console/prometeu-drivers/src/gfx.rs:673) ## Propagacao Necessaria The following work MUST derive from this decision: 1. Introduce the new canonical runtime types: - `Scene` - `SceneLayer` - `SceneViewportCache` - `SceneViewportResolver` 2. Remove or retire `ScrollableTileLayer`. 3. Migrate renderer world composition to consume `SceneViewportCache`. 4. Define the concrete cache update API for: - line - column - area/region 5. Define the concrete blit instrumentation API emitted by the resolver. 6. Write an implementation plan before code changes. ## Revision Log - 2026-04-13: Initial accepted decision from AGD-0025.