prometeu-runtime/discussion/lessons/DSC-0025-scene-bank-and-viewport-cache-refactor/LSN-0030-canonical-scene-cache-and-resolver-split.md
bQUARKz 1ad7554762
All checks were successful
Intrepid/Prometeu/Runtime/pipeline/head This commit looks good
housekeep
2026-04-14 05:31:34 +01:00

4.7 KiB

id ticket title created tags
LSN-0030 scene-bank-and-viewport-cache-refactor Canonical Scene Ownership Must Stay Separate from Viewport Caching 2026-04-14
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 SceneLayers, 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.