# Terrain Quality Mesh Cache ## Metadata | Field | Value | |---|---| | Status | Implemented extraction | | Target/work package | M03 / `M03-RND-TERRAIN-CACHE-SERVICE-001` | | Owners | Full-quality terrain revisit Mesh retention and LRU policy | | Last verified | Worktree `work/sindo-main-codex/m03-terrain-quality-cache`, 2026-07-16 | | Profiles/capabilities | Performance/Balanced/High/Custom capacity; session-memory only | ## Purpose Retain eligible full-quality terrain Mesh references after ADT tile state unload, so a revisit can restore quality without flashing to a lower-quality fallback, while bounding retained references with the existing least-recently-used policy. ## Non-goals - Parse/build terrain, own tile state or schedule quality-upgrade tasks. - Create/finalize Nodes or RIDs, mutate materials or choose terrain LOD. - Persist cache entries or change baked/control-splat/splat format versions. - Cache control-splat/splat sources whose active radius has separate ownership. - Provide a generic cache framework or change profile capacity defaults. ## Context and boundaries ```mermaid flowchart LR Upgrade[Full-quality baked upgrade result] --> Loader[StreamingWorldLoader adapter] Loader --> Cache[TerrainQualityMeshCache] Cache -->|retained Mesh reference| Revisit[Reloaded tile state] Revisit --> Finalize[Existing tile LOD/finalization path] Splat[Control/splat quality sources] -. rejected .-> Cache ``` Allowed dependencies: `Mesh`, Dictionary/Array scalar containers and the loader adapter. Forbidden dependencies: Node/SceneTree/RID, tasks/mutexes, ADT parsing, gameplay/network/editor UI, files and persistent Resource cache ownership. ## Public API | Symbol | Kind | Purpose | Thread/lifetime | Errors | |---|---|---|---|---| | `TerrainQualityMeshCache.store(tile_key, mesh, source, capacity)` | Command | Admit/touch a Mesh and trim oldest entries | Owning renderer thread/session | Returns false for rejected input or immediate zero-capacity eviction | | `TerrainQualityMeshCache.restore(tile_key)` | Query/touch | Return a fresh Mesh/source record and mark newest | Owning renderer thread | Empty on miss; invalid record is removed | | `TerrainQualityMeshCache.clear()` | Lifecycle command | Release all retained Mesh references and LRU keys | Reset/shutdown | Idempotent | | `TerrainQualityMeshCache.diagnostic_snapshot()` | Read model | Detached count and oldest-to-newest key/source list | Caller-owned result | Never exposes Mesh references | ## Inputs and outputs | Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime | |---|---|---|---|---|---| | Input | Tile key | Streamer tile catalog/state | Cache index | Copied string | Map session | | Input | Full-quality Mesh | Baked upgrade/fallback path | Cache | Retained shared reference | Until eviction/clear | | Input | Quality source | Streamer quality adapter | Admission/restore | Copied string | Entry lifetime | | Input | Current capacity | Active renderer profile | Store trim | Scalar copy | One store | | Output | Fresh `{mesh, source}` record | Cache | Loader state adapter | Caller dictionary; shared Mesh reference | One restore | | Output | Detached diagnostics | Cache | Tests/future metrics | Caller-owned scalars | One query | Side effects are limited to retained reference and LRU container mutation. There is no scene, GPU, task, file, database, network or persistent-cache mutation. ## Data flow ```mermaid flowchart TD Store[store tile/mesh/source/capacity] --> Admit{Valid key + Mesh + source?} Admit -->|no| Reject[Return false] Admit -->|yes| Touch[Insert/replace and touch newest] Touch --> Trim[Evict oldest until count <= capacity] Restore[restore tile] --> Hit{Valid admitted entry?} Hit -->|no| Miss[Remove invalid / return empty] Hit -->|yes| TouchRestore[Touch newest and return fresh record] Clear[world reset/shutdown] --> Empty[Release all references and keys] ``` ## Lifecycle/state ```mermaid stateDiagram-v2 [*] --> Empty Empty --> Populated: admitted store with capacity > 0 Populated --> Populated: store / restore touch / bounded eviction Populated --> Empty: capacity zero store evicts all Populated --> Empty: clear on reset/shutdown Empty --> Empty: rejected store / miss / clear Empty --> [*] Populated --> [*]: owner releases cache ``` ## Main sequence ```mermaid sequenceDiagram participant Upgrade as Terrain upgrade result participant Loader as StreamingWorldLoader participant Cache as TerrainQualityMeshCache Upgrade->>Loader: full Mesh + baked_full source Loader->>Cache: store(key, mesh, source, profile capacity) Note over Loader,Cache: tile state may later unload Loader->>Cache: restore(key) on tile creation/revisit Cache-->>Loader: fresh record or empty Loader->>Loader: apply Mesh/source to existing tile state Loader->>Cache: clear() on world reset/shutdown ``` ## Ownership, threading and resources - The cache owns its dictionary, ordered tile keys and retained Mesh references. - The loader retains tile state, async tasks/results, quality selection and all Node/RID/material finalization. - Returned records are new dictionaries; their Mesh is a shared reference. - Calls currently occur on the main renderer thread. The service has no lock and must not be mutated concurrently. - `clear` deterministically drops references during the existing world reset path. ## Errors, cancellation and recovery | Failure | Detection | Behavior | Diagnostic | Recovery | |---|---|---|---|---| | Empty key/null Mesh | Store guard | Reject unchanged | Boolean/test | Fix caller result | | Splat/control source | Admission list | Reject unchanged | Boolean/test | Keep source in active-radius ownership | | Zero/negative capacity | Clamp and trim | Entry immediately evicted; cache empty | Snapshot/count | Increase profile capacity | | Miss/invalid entry | Restore validation | Remove invalid and return empty | Empty record | Existing loader fallback/rebuild | | World reset/shutdown | Loader lifecycle | Clear every reference/key | Shutdown regression | Refill during next session | There is no asynchronous work or cancellation token inside this service. ## Configuration and capabilities | Setting/capability | Default | Profile | Runtime mutable | Effect | |---|---|---|---|---| | `terrain_quality_mesh_cache_limit` | Existing loader value | `0/24/48` in current presets | Read on every store | Maximum retained entries | | Admitted source | Non-splat (currently `baked_full`) | All | No hidden override | Revisit retention | | Excluded sources | `control_splat_cache`, `splat_cache`, `splat` | All | No | Preserve active quality ownership | ## Persistence, cache and migration - This is an in-memory reference cache, not a serialized cache format. - `REQUIRED_BAKED_TILE_FORMAT_VERSION=5`, control splat v3 and splat v1 remain loader/resource validation contracts and are unchanged. - No migration or rebuild is required. Entry keys retain the existing tile-key form. ## Diagnostics and observability - Detached diagnostics report count and oldest-to-newest tile key/source entries. - Mesh, material, Node and RID references are deliberately absent. - Performance is bounded dictionary lookup plus one ordered-array erase/append; no new optimization or scale claim is made. ## Verification - `verify_terrain_quality_mesh_cache.gd`: admission, exclusions, Mesh identity, source, LRU touch/eviction, detached diagnostics, negative capacity and clear. - Loader source boundary proves cache composition/delegation and removal of its former LRU array/touch/trim implementation. - Existing shutdown, facade, internal-access, manifest, planner and scheduler regressions remain required. - Fidelity: source rules, capacities and three loader adapter sites are unchanged; no visible terrain or build-12340 parity change is claimed. ## Extension points - Expose scalar cache counts through facade metrics when needed. - Move additional terrain responsibilities only as separate bounded services. - Replace ordered-array LRU only after measured scale evidence. ## Capability status | Capability | Status | Evidence | Gap/next step | |---|---|---|---| | Bounded quality Mesh retention | Implemented extraction | Contract/LRU fixtures | Asset-backed memory/revisit timing pending | | Deterministic reset ownership | Implemented | Clear path plus shutdown regression | Full terrain service still monolithic | | Persistent terrain cache formats | Unchanged | Loader version constants | Separate acceptance audit remains | ## Known gaps and risks - Ordered-array touch is linear in entry count, matching prior behavior. - Mesh memory size is not measured; capacity remains entry-count based. - Only revisit Mesh ownership moved; build/tasks/state/finalization remain loader-owned. - Asset-backed flash/revisit timing and p95/p99 evidence remain pending. ## Source map | Path | Responsibility | |---|---| | `src/render/terrain/terrain_quality_mesh_cache.gd` | Session cache entries, LRU and admission | | `src/scenes/streaming/streaming_world_loader.gd` | Store/restore state adapter and reset lifecycle | | `src/tools/verify_terrain_quality_mesh_cache.gd` | Scene-free cache and loader-boundary regression | ## Related decisions and references - [`world-renderer.md`](world-renderer.md) - [`terrain-query.md`](terrain-query.md) - [`../../RENDER.md`](../../RENDER.md) - [`../../targets/roadmap/02-rendering-and-graphics.md`](../../targets/roadmap/02-rendering-and-graphics.md)