Compare commits

..

5 Commits

11 changed files with 507 additions and 42 deletions
+12
View File
@@ -979,6 +979,18 @@ $exe = Join-Path $env:TEMP 'godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe'
- PackedScene loading is currently synchronous on create/replace and is a documented - PackedScene loading is currently synchronous on create/replace and is a documented
prototype limitation, not an accepted network-scale frame-critical path. prototype limitation, not an accepted network-scale frame-critical path.
## 2026-07-16 Terrain Quality Mesh Cache Extraction
- `TerrainQualityMeshCache` now owns full-quality terrain revisit Mesh references,
tile-key LRU order and source admission outside `StreamingWorldLoader`.
- The historical rules are unchanged: successful restore touches newest; store
trims oldest to `terrain_quality_mesh_cache_limit`; zero/negative capacity keeps
no entries; control-splat/splat sources are never retained in this revisit cache.
- The loader keeps three narrow adapters: restore into tile state, store after a
full-quality result and clear during existing world reset/shutdown.
- ADT parsing, quality tasks/results, tile state, cache format versions, material/
Node/RID finalization, budgets and visible terrain behavior remain loader-owned.
## Practical Rule For Future Work ## Practical Rule For Future Work
If something improves quality but creates visible hitch, it is not done. Move it to bake/cache/background work, split finalization over frames, or prewarm it before the player can see it. If something improves quality but creates visible hitch, it is not done. Move it to bake/cache/background work, split finalization over frames, or prewarm it before the player can see it.
@@ -0,0 +1,118 @@
# M03-RND-TERRAIN-CACHE-SERVICE-001 — Terrain quality mesh cache service
<!-- OPENWC_CLAIM:M03-RND-TERRAIN-CACHE-SERVICE-001:sindo-main-codex:2026-07-18 -->
## Ownership
- Target: M03
- Program: RND
- Owner/Agent ID: sindo-main-codex
- Branch: `work/sindo-main-codex/m03-terrain-quality-cache`
- Lease expires UTC: 2026-07-18
- Integrator: M03 milestone integrator
## Outcome
Extract the full-quality terrain revisit Mesh cache, its LRU ownership and
source-admission rules from `StreamingWorldLoader` into a small scene-free
`TerrainQualityMeshCache`, preserving all call sites and observable behavior.
## Non-goals
- Move ADT parsing, tile state, quality-upgrade tasks/results or SceneTree/RID finalization.
- Change cache capacity defaults, quality profiles, source priority or cache formats.
- Cache control-splat/splat meshes that the current loader deliberately rejects.
- Extract chunk LOD, terrain material building, ground query or other asset services.
- Add dependencies, persistence, background work or mark the terrain service complete.
## Paths
- Exclusive: `src/render/terrain/terrain_quality_mesh_cache.gd`,
`src/tools/verify_terrain_quality_mesh_cache.gd`,
`docs/modules/terrain-quality-mesh-cache.md`, this claim
- Shared/hotspots: `src/scenes/streaming/streaming_world_loader.gd`,
`src/tools/verify_render_runtime_cache_shutdown.gd`,
`docs/modules/world-renderer.md`, `docs/modules/README.md`, `RENDER.md`,
`targets/03-renderer-facade.md`
- Generated/ignored: `.godot`, native DLL, generated resources, caches and
proprietary renderer corpus
## Contracts and data
- Public service API: `store`, `restore`, `clear`, detached `diagnostic_snapshot`
- Entry: tile key, retained Mesh reference and source string
- Admission: non-empty key, non-null Mesh, source other than
`control_splat_cache`, `splat_cache` or `splat`
- LRU: successful restore touches newest; store touches then trims oldest to
`max(0, capacity)`; invalid restored records are removed
- Persistence/cache format: none; session-memory ownership only
## Dependencies
- Requires: integrated facade contracts master `61bad09`
- Blocks: later terrain service extraction and cache/finalization acceptance audit
- External state: none; tests use synthetic ArrayMesh references
## Verification
- Commands: dedicated cache contract/LRU/ownership regression; streamer source
boundary; shutdown/facade/internal-access/manifest/planner/scheduler and repository gates
- Fixtures: admission/rejection, replacement, LRU touch/eviction, zero/negative
capacity, invalid record cleanup, clear and detached diagnostics
- Fidelity evidence: same sources/capacity/call sites; all seven baseline plans unchanged
- Performance budget: dictionary lookup plus bounded single-entry LRU operations;
no Node, task, file, cache serialization or RID creation
## Documentation deliverables
- Inline public API docs: store/restore/clear/diagnostics and ownership
- Module specification: new terrain quality mesh cache specification
- Data-flow diagram: upgrade result through cache to revisit state
- Sequence/state/dependency diagrams: store/restore/evict/reset and failure paths
- Source map/status updates: module registry, world renderer and `RENDER.md`
## Simplicity and naming
- Important names introduced: `TerrainQualityMeshCache`
- Simplest considered solution: one RefCounted with dictionary plus ordered tile-key array
- Rejected complexity/abstractions: generic cache framework, weak references,
callbacks, Resource cache integration, background eviction or persistence
- Unavoidable complexity and justification: Mesh references must survive unloaded
tile state while LRU keeps revisit memory bounded
- Measured optimization evidence: exact ownership extraction, no optimization claim
## Status
- State: ready-for-review
- Done: scene-free cache ownership/LRU extraction, loader adapter migration,
contract regression and module documentation
- Next: M03 integrator reviews and merges `97480e0`
- Blocked by:
<!-- OPENWC_HANDOFF:READY:M03-RND-TERRAIN-CACHE-SERVICE-001:97480e0 -->
<!-- OPENWC_INTEGRATION:ACCEPTED:M03-RND-TERRAIN-CACHE-SERVICE-001:630a0c1 -->
## Handoff
- Commit: `97480e0`
- Results: `TerrainQualityMeshCache` now owns retained full-quality Mesh
references, source admission and oldest-to-newest LRU. The loader retains only
restore-to-state, store-after-upgrade and clear lifecycle adapters.
- Verification: cache `contract_cases=15 loader_calls=3`; facade/entity/ground/
terrain regressions; internal access `private_symbols=42 gameplay=7
editor_sources=9 renderer_tools=1`; manifest `7/7`; shutdown; scheduler
`20000` iterations; planner `250` iterations; focus; editor parse; dry-run;
coordination, documentation and diff gates passed.
- Fidelity: `baked_full` revisit retention, three excluded splat sources, LRU
touch/trim, profile capacities and all call sites remain unchanged. All seven
checkpoint plans remain valid. No visible or build-12340 parity claim is added.
- External/local inputs: ignored native DLL and generated ADT scripts supported
parse/dry-run only; missing proprietary ADT/DBC/character data produced expected diagnostics.
- Remaining risks: entry count does not measure Mesh bytes; ordered-array touch
remains linear; asset-backed revisit flash, memory and p95/p99 evidence remain pending;
terrain build/tasks/state/finalization are still monolithic.
- Documentation updated: new terrain cache API/input-output/data-flow/lifecycle/
sequence/ownership/failure/configuration/migration/diagnostic/source-map spec;
world renderer, registry and `RENDER.md` updated.
- Integration: accepted in master merge `630a0c1`; post-merge cache, facade,
internal-access, manifest, shutdown, scheduler and planner gates passed.
+1
View File
@@ -12,6 +12,7 @@
| Player input | Partial | [`player-input.md`](player-input.md) | | Player input | Partial | [`player-input.md`](player-input.md) |
| Local player movement | Implemented | [`local-player-movement.md`](local-player-movement.md) | | Local player movement | Implemented | [`local-player-movement.md`](local-player-movement.md) |
| Terrain query | Implemented | [`terrain-query.md`](terrain-query.md) | | Terrain query | Implemented | [`terrain-query.md`](terrain-query.md) |
| Terrain quality Mesh cache | Implemented extraction | [`terrain-quality-mesh-cache.md`](terrain-quality-mesh-cache.md) |
| Third-person camera | Implemented | [`third-person-camera.md`](third-person-camera.md) | | Third-person camera | Implemented | [`third-person-camera.md`](third-person-camera.md) |
| Character presentation | Implemented boundary / Partial fidelity | [`character-presentation.md`](character-presentation.md) | | Character presentation | Implemented boundary / Partial fidelity | [`character-presentation.md`](character-presentation.md) |
| Renderer | Partial | [`world-renderer.md`](world-renderer.md), [`../../RENDER.md`](../../RENDER.md) | | Renderer | Partial | [`world-renderer.md`](world-renderer.md), [`../../RENDER.md`](../../RENDER.md) |
+198
View File
@@ -0,0 +1,198 @@
# 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)
+14 -2
View File
@@ -5,9 +5,9 @@
| Field | Value | | Field | Value |
|---|---| |---|---|
| Status | Partial | | Status | Partial |
| Target/work package | M00 baseline; `M01-RND-STREAMING-FOCUS-001`; `M01-QAR-SERVER-SPAWN-RENDERER-001`; `M03-RND-FACADE-001`; `M03-RND-STREAMING-PLANNER-001`; `M03-RND-SCHEDULER-001`; `M03-RND-INTERNAL-ACCESS-GATE-001`; `M03-RND-FACADE-GROUND-QUERY-001`; `M03-RND-FACADE-ENVIRONMENT-001`; `M03-RND-FACADE-ENTITY-001` | | Target/work package | M00 baseline; `M01-RND-STREAMING-FOCUS-001`; `M01-QAR-SERVER-SPAWN-RENDERER-001`; M03 facade/planner/scheduler/internal-access/ground/environment/entity packages; `M03-RND-TERRAIN-CACHE-SERVICE-001` |
| Owners | Renderer workstream / milestone integrator | | Owners | Renderer workstream / milestone integrator |
| Last verified | Worktree `work/sindo-main-codex/m03-facade-entity`, 2026-07-16 | | Last verified | Worktree `work/sindo-main-codex/m03-terrain-quality-cache`, 2026-07-16 |
| Profiles/capabilities | `Performance`, `Balanced`, `High`, `Custom`; Blizzlike fidelity incomplete | | Profiles/capabilities | `Performance`, `Balanced`, `High`, `Custom`; Blizzlike fidelity incomplete |
## Purpose ## Purpose
@@ -40,6 +40,8 @@ flowchart LR
Loader --> Budget[RenderBudgetScheduler] Loader --> Budget[RenderBudgetScheduler]
Assets[Extracted WDT/ADT/M2/WMO/BLP] --> Native[Native loaders] Assets[Extracted WDT/ADT/M2/WMO/BLP] --> Native[Native loaders]
Cache[Baked terrain/M2/WMO caches] --> Loader Cache[Baked terrain/M2/WMO caches] --> Loader
Loader --> TerrainCache[TerrainQualityMeshCache]
TerrainCache --> Loader
Native --> Parsed[Parsed tile/model data] Native --> Parsed[Parsed tile/model data]
Parsed --> Loader Parsed --> Loader
Loader --> Scene[SceneTree nodes] Loader --> Scene[SceneTree nodes]
@@ -103,6 +105,7 @@ from externally reading/writing loader-private queue, task, cache and tile-state
| `quality_preset` | Exported property | Applies renderer budgets/radii | Before/at runtime depending property | Invalid combination should be diagnosed | | `quality_preset` | Exported property | Applies renderer budgets/radii | Before/at runtime depending property | Invalid combination should be diagnosed |
| `runtime_stats_enabled` | Exported property | Enables periodic performance snapshot | Runtime mutable | Logging overhead only | | `runtime_stats_enabled` | Exported property | Enables periodic performance snapshot | Runtime mutable | Logging overhead only |
| `hitch_profiler_enabled` | Exported property | Enables named hitch sections | Runtime mutable | Logging overhead only | | `hitch_profiler_enabled` | Exported property | Enables named hitch sections | Runtime mutable | Logging overhead only |
| `TerrainQualityMeshCache.store/restore/clear` | Internal terrain service | Owns admitted full-quality revisit Mesh references and LRU | Renderer thread/map session | Rejected/missed entry leaves loader fallback unchanged |
Публичным contract не считаются `StreamingWorldLoader` methods/properties, Публичным contract не считаются `StreamingWorldLoader` methods/properties,
внутренние dictionaries, queues, job records и generated node names. Scene-owned внутренние dictionaries, queues, job records и generated node names. Scene-owned
@@ -124,6 +127,7 @@ loader configuration remains transitional composition data, not a caller API.
| Test input | Server-spawn render manifest | Coordinate fixture/verifier | Checkpoint capture tool | Repository-owned diagnostic data | Test-process lifetime | | Test input | Server-spawn render manifest | Coordinate fixture/verifier | Checkpoint capture tool | Repository-owned diagnostic data | Test-process lifetime |
| Output | `StreamingTargetPlan` | `StreamingTargetPlanner` | Streamer queue/state apply | Immutable ephemeral value | One refresh | | Output | `StreamingTargetPlan` | `StreamingTargetPlanner` | Streamer queue/state apply | Immutable ephemeral value | One refresh |
| Internal control | Per-frame lane limits and boolean permits | Streamer configuration / `RenderBudgetScheduler` | Ordered streamer drains | Scheduler-owned counters | Main thread/frame | | Internal control | Per-frame lane limits and boolean permits | Streamer configuration / `RenderBudgetScheduler` | Ordered streamer drains | Scheduler-owned counters | Main thread/frame |
| Internal cache | Full-quality terrain Mesh/source | Loader terrain upgrade / `TerrainQualityMeshCache` | Revisited tile state | Cache-retained Mesh reference | Map session until eviction/reset |
| Output | Desired tile/detail operations | Streamer plan application | Finalize queues | Loader-owned | Cross-frame | | Output | Desired tile/detail operations | Streamer plan application | Finalize queues | Loader-owned | Cross-frame |
| Output | Terrain/M2/WMO/liquid instances | Loader/builders | Godot world/renderer | Loader/world owner | Main-thread attach | | Output | Terrain/M2/WMO/liquid instances | Loader/builders | Godot world/renderer | Loader/world owner | Main-thread attach |
| Output | Detached renderer metrics | `WorldRenderFacade` | Capture/baseline tools | Caller-owned deep copy | On demand | | Output | Detached renderer metrics | `WorldRenderFacade` | Capture/baseline tools | Caller-owned deep copy | On demand |
@@ -161,6 +165,8 @@ flowchart TD
T --> Q[Tile load queue] T --> Q[Tile load queue]
Q --> Parse[Worker parse/cache load] Q --> Parse[Worker parse/cache load]
Parse --> R[Result queues] Parse --> R[Result queues]
R --> TerrainCache[TerrainQualityMeshCache store]
TerrainCache --> Revisit[Revisited tile state restore]
R --> B[RenderBudgetScheduler permits] R --> B[RenderBudgetScheduler permits]
B --> Terrain[Terrain attach/upgrade] B --> Terrain[Terrain attach/upgrade]
B --> M2[M2 group/MultiMesh attach] B --> M2[M2 group/MultiMesh attach]
@@ -263,6 +269,9 @@ sequenceDiagram
`EntityId`; it owns no authoritative entity state and excludes the local player. `EntityId`; it owns no authoritative entity state and excludes the local player.
- Entity presentation snapshots remain caller-owned inputs. Detached diagnostics - Entity presentation snapshots remain caller-owned inputs. Detached diagnostics
expose only identity debug keys, paths and visibility, never Node/Resource/RID. expose only identity debug keys, paths and visibility, never Node/Resource/RID.
- `TerrainQualityMeshCache` owns only full-quality revisit Mesh references and
LRU keys. The loader retains terrain tasks/results, tile state, source choice,
cache versions and every material/Node/RID finalization side effect.
- Rendered-ground query results and diagnostic snapshots are caller-owned values; - Rendered-ground query results and diagnostic snapshots are caller-owned values;
the facade never returns Mesh, Node, tile-state or queue references. the facade never returns Mesh, Node, tile-state or queue references.
- Loaded-mesh ground sampling is renderer diagnostics, not authoritative gameplay - Loaded-mesh ground sampling is renderer diagnostics, not authoritative gameplay
@@ -377,6 +386,7 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
| Stable renderer facade | Implemented boundary / Partial fidelity | Facade contracts cover focus, environment, entity visuals, metrics and loaded-mesh ground query | Production consumers and fidelity remain | | Stable renderer facade | Implemented boundary / Partial fidelity | Facade contracts cover focus, environment, entity visuals, metrics and loaded-mesh ground query | Production consumers and fidelity remain |
| Environment snapshot input | Implemented boundary / Partial fidelity | Immutable time contract, facade/controller delegation and checkpoint migration | Weather/indoor state and paired zone-transition fidelity remain | | Environment snapshot input | Implemented boundary / Partial fidelity | Immutable time contract, facade/controller delegation and checkpoint migration | Weather/indoor state and paired zone-transition fidelity remain |
| Entity presentation commands | Implemented boundary / Prototype visuals | Versioned typed snapshot plus present/remove lifecycle and runtime service composition | Display/equipment/animation/effects and async scale path remain | | Entity presentation commands | Implemented boundary / Prototype visuals | Versioned typed snapshot plus present/remove lifecycle and runtime service composition | Display/equipment/animation/effects and async scale path remain |
| Terrain quality Mesh cache service | Implemented extraction | Scene-free admission/LRU/clear contract and loader delegation | Remaining terrain build/state/finalization is monolithic |
| Pure streaming target planner | Implemented | `M03-RND-STREAMING-PLANNER-001` asset-free contract/performance regression | Queue scheduling and loaded-tile LOD application remain in streamer | | Pure streaming target planner | Implemented | `M03-RND-STREAMING-PLANNER-001` asset-free contract/performance regression | Queue scheduling and loaded-tile LOD application remain in streamer |
| Bounded render budget scheduler | Implemented | `M03-RND-SCHEDULER-001` asset-free permit/cancellation/performance regression | Queue storage, worker concurrency and operation execution remain in streamer/services | | Bounded render budget scheduler | Implemented | `M03-RND-SCHEDULER-001` asset-free permit/cancellation/performance regression | Queue storage, worker concurrency and operation execution remain in streamer/services |
| Gameplay/editor/tool internal-access boundary | Implemented | Access gate scans gameplay, EditorPlugin packages and terrain probe against actual private streamer declarations | Add future renderer diagnostic tools explicitly to the protected list | | Gameplay/editor/tool internal-access boundary | Implemented | Access gate scans gameplay, EditorPlugin packages and terrain probe against actual private streamer declarations | Add future renderer diagnostic tools explicitly to the protected list |
@@ -414,6 +424,7 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
| `src/render/entities/entity_presentation_snapshot.gd` | Immutable contract-v1 entity visual input and validation | | `src/render/entities/entity_presentation_snapshot.gd` | Immutable contract-v1 entity visual input and validation |
| `src/render/entities/world_entity_presenter.gd` | Main-thread entity visual subtree ownership and lifecycle | | `src/render/entities/world_entity_presenter.gd` | Main-thread entity visual subtree ownership and lifecycle |
| `src/render/terrain/rendered_ground_sample.gd` | Immutable renderer-owned available/unavailable ground query result | | `src/render/terrain/rendered_ground_sample.gd` | Immutable renderer-owned available/unavailable ground query result |
| `src/render/terrain/terrain_quality_mesh_cache.gd` | Full-quality terrain revisit Mesh retention and LRU ownership |
| `src/render/streaming/streaming_target_planner.gd` | Scene-free wanted/retained ADT target calculation | | `src/render/streaming/streaming_target_planner.gd` | Scene-free wanted/retained ADT target calculation |
| `src/render/streaming/streaming_target_policy.gd` | Immutable renderer radius/prefetch policy | | `src/render/streaming/streaming_target_policy.gd` | Immutable renderer radius/prefetch policy |
| `src/render/streaming/streaming_target_plan.gd` | Immutable planner result with read-only tile-key sets | | `src/render/streaming/streaming_target_plan.gd` | Immutable planner result with read-only tile-key sets |
@@ -425,6 +436,7 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
| `src/tools/verify_world_environment_snapshot.gd` | Cold-start environment value normalization and invalid-input regression | | `src/tools/verify_world_environment_snapshot.gd` | Cold-start environment value normalization and invalid-input regression |
| `src/tools/verify_world_entity_presentation.gd` | Entity snapshot, create/update/replace/remove and ownership regression | | `src/tools/verify_world_entity_presentation.gd` | Entity snapshot, create/update/replace/remove and ownership regression |
| `src/tools/verify_rendered_ground_sample.gd` | Cold-start renderer ground result invariant regression | | `src/tools/verify_rendered_ground_sample.gd` | Cold-start renderer ground result invariant regression |
| `src/tools/verify_terrain_quality_mesh_cache.gd` | Terrain cache admission, LRU, ownership and loader-boundary regression |
| `src/tools/verify_streaming_target_planner.gd` | Planner behavior, dependency and bounded timing regression | | `src/tools/verify_streaming_target_planner.gd` | Planner behavior, dependency and bounded timing regression |
| `src/tools/verify_render_budget_scheduler.gd` | Scheduler bounds, shared-lane priority, cancellation and timing regression | | `src/tools/verify_render_budget_scheduler.gd` | Scheduler bounds, shared-lane priority, cancellation and timing regression |
| `src/tools/verify_renderer_internal_access.gd` | Gameplay/EditorPlugin/registered renderer-tool boundary gate derived from private streamer fields | | `src/tools/verify_renderer_internal_access.gd` | Gameplay/EditorPlugin/registered renderer-tool boundary gate derived from private streamer fields |
@@ -0,0 +1,73 @@
class_name TerrainQualityMeshCache
extends RefCounted
## Session-memory LRU owner for full-quality terrain Mesh references retained
## across tile unload/revisit. It owns no tile state, Node, RID, task or file.
const EXCLUDED_QUALITY_SOURCES: Array[String] = [
"control_splat_cache",
"splat_cache",
"splat",
]
var _entries_by_tile_key: Dictionary = {}
var _tile_keys_oldest_to_newest: Array[String] = []
## Stores an admitted Mesh reference, touches it as newest and trims oldest
## entries to the supplied non-negative capacity. Returns false when rejected.
func store(tile_key: String, mesh: Mesh, source: String, capacity: int) -> bool:
if tile_key.is_empty() or mesh == null or EXCLUDED_QUALITY_SOURCES.has(source):
return false
_entries_by_tile_key[tile_key] = {"mesh": mesh, "source": source}
_touch(tile_key)
_trim(maxi(0, capacity))
return _entries_by_tile_key.has(tile_key)
## Returns a fresh record containing the retained Mesh/source and touches the key
## as newest. Missing or invalid records return empty and are removed.
func restore(tile_key: String) -> Dictionary:
if not _entries_by_tile_key.has(tile_key):
return {}
var entry: Dictionary = _entries_by_tile_key[tile_key]
var source := String(entry.get("source", "baked_full"))
var mesh := entry.get("mesh") as Mesh
if mesh == null or EXCLUDED_QUALITY_SOURCES.has(source):
_remove(tile_key)
return {}
_touch(tile_key)
return {"mesh": mesh, "source": source}
## Releases every retained Mesh reference and resets LRU order.
func clear() -> void:
_entries_by_tile_key.clear()
_tile_keys_oldest_to_newest.clear()
## Returns detached scalar diagnostics without exposing retained Mesh references.
func diagnostic_snapshot() -> Dictionary:
var entries: Array[Dictionary] = []
for tile_key in _tile_keys_oldest_to_newest:
var entry: Dictionary = _entries_by_tile_key.get(tile_key, {})
entries.append({"tile_key": tile_key, "source": String(entry.get("source", ""))})
return {
"entry_count": entries.size(),
"entries_oldest_to_newest": entries,
}
func _touch(tile_key: String) -> void:
_tile_keys_oldest_to_newest.erase(tile_key)
_tile_keys_oldest_to_newest.append(tile_key)
func _trim(capacity: int) -> void:
while _tile_keys_oldest_to_newest.size() > capacity:
_remove(_tile_keys_oldest_to_newest.front())
func _remove(tile_key: String) -> void:
_entries_by_tile_key.erase(tile_key)
_tile_keys_oldest_to_newest.erase(tile_key)
@@ -0,0 +1 @@
uid://y576l2omqwox
+12 -37
View File
@@ -18,6 +18,7 @@ const COORDINATE_MAPPER_SCRIPT := preload("res://src/domain/coordinates/coordina
const ADT_TILE_COORDINATE_SCRIPT := preload("res://src/domain/coordinates/adt_tile_coordinate.gd") const ADT_TILE_COORDINATE_SCRIPT := preload("res://src/domain/coordinates/adt_tile_coordinate.gd")
const ADT_TILE_LOCAL_POSITION_SCRIPT := preload("res://src/domain/coordinates/adt_tile_local_position.gd") const ADT_TILE_LOCAL_POSITION_SCRIPT := preload("res://src/domain/coordinates/adt_tile_local_position.gd")
const RENDERED_GROUND_SAMPLE_SCRIPT := preload("res://src/render/terrain/rendered_ground_sample.gd") const RENDERED_GROUND_SAMPLE_SCRIPT := preload("res://src/render/terrain/rendered_ground_sample.gd")
const TERRAIN_QUALITY_MESH_CACHE_SCRIPT := preload("res://src/render/terrain/terrain_quality_mesh_cache.gd")
const STREAMING_TARGET_PLANNER_SCRIPT := preload("res://src/render/streaming/streaming_target_planner.gd") const STREAMING_TARGET_PLANNER_SCRIPT := preload("res://src/render/streaming/streaming_target_planner.gd")
const STREAMING_TARGET_POLICY_SCRIPT := preload("res://src/render/streaming/streaming_target_policy.gd") const STREAMING_TARGET_POLICY_SCRIPT := preload("res://src/render/streaming/streaming_target_policy.gd")
const RENDER_BUDGET_SCHEDULER_SCRIPT := preload("res://src/render/streaming/render_budget_scheduler.gd") const RENDER_BUDGET_SCHEDULER_SCRIPT := preload("res://src/render/streaming/render_budget_scheduler.gd")
@@ -241,8 +242,7 @@ var _last_refresh_focus_pos := Vector3.ZERO
var _has_refresh_focus := false var _has_refresh_focus := false
var _tile_mesh_cache: Dictionary = {} var _tile_mesh_cache: Dictionary = {}
var _tile_mesh_cache_order: Array = [] var _tile_mesh_cache_order: Array = []
var _terrain_quality_mesh_cache: Dictionary = {} var _terrain_quality_mesh_cache: RefCounted = TERRAIN_QUALITY_MESH_CACHE_SCRIPT.new()
var _terrain_quality_mesh_cache_order: Array = []
var _wmo_prototype_cache: Dictionary = {} var _wmo_prototype_cache: Dictionary = {}
var _wmo_render_cache: Dictionary = {} var _wmo_render_cache: Dictionary = {}
var _wmo_render_missing_cache: Dictionary = {} var _wmo_render_missing_cache: Dictionary = {}
@@ -5500,53 +5500,28 @@ func _clear_tile_mesh_cache() -> void:
func _restore_quality_terrain_mesh(tile_key: String, state: Dictionary) -> Dictionary: func _restore_quality_terrain_mesh(tile_key: String, state: Dictionary) -> Dictionary:
if not _terrain_quality_mesh_cache.has(tile_key): var cached: Dictionary = _terrain_quality_mesh_cache.call("restore", tile_key)
if cached.is_empty():
return state return state
var cached: Dictionary = _terrain_quality_mesh_cache[tile_key]
var source := String(cached.get("source", "baked_full")) var source := String(cached.get("source", "baked_full"))
if source == "control_splat_cache" or source == "splat_cache" or source == "splat":
_terrain_quality_mesh_cache.erase(tile_key)
_terrain_quality_mesh_cache_order.erase(tile_key)
return state
var mesh: Mesh = cached.get("mesh", null) var mesh: Mesh = cached.get("mesh", null)
if mesh == null:
_terrain_quality_mesh_cache.erase(tile_key)
_terrain_quality_mesh_cache_order.erase(tile_key)
return state
state["quality_terrain_mesh"] = mesh state["quality_terrain_mesh"] = mesh
state["quality_terrain_source"] = source state["quality_terrain_source"] = source
_touch_quality_terrain_mesh_cache(tile_key)
return state return state
func _store_quality_terrain_mesh(tile_key: String, mesh: Mesh, source: String) -> void: func _store_quality_terrain_mesh(tile_key: String, mesh: Mesh, source: String) -> void:
if tile_key.is_empty() or mesh == null: _terrain_quality_mesh_cache.call(
return "store",
if source == "control_splat_cache" or source == "splat_cache" or source == "splat": tile_key,
return mesh,
_terrain_quality_mesh_cache[tile_key] = { source,
"mesh": mesh, terrain_quality_mesh_cache_limit
"source": source, )
}
_touch_quality_terrain_mesh_cache(tile_key)
_trim_quality_terrain_mesh_cache()
func _touch_quality_terrain_mesh_cache(tile_key: String) -> void:
_terrain_quality_mesh_cache_order.erase(tile_key)
_terrain_quality_mesh_cache_order.append(tile_key)
func _trim_quality_terrain_mesh_cache() -> void:
var limit := maxi(0, terrain_quality_mesh_cache_limit)
while _terrain_quality_mesh_cache_order.size() > limit:
var oldest: String = _terrain_quality_mesh_cache_order.pop_front()
_terrain_quality_mesh_cache.erase(oldest)
func _clear_quality_terrain_mesh_cache() -> void: func _clear_quality_terrain_mesh_cache() -> void:
_terrain_quality_mesh_cache.clear() _terrain_quality_mesh_cache.call("clear")
_terrain_quality_mesh_cache_order.clear()
func _position_camera_over_world() -> void: func _position_camera_over_world() -> void:
@@ -0,0 +1,67 @@
extends SceneTree
## Scene-free contract, admission and LRU regression for terrain revisit meshes.
const CACHE_SCRIPT := preload("res://src/render/terrain/terrain_quality_mesh_cache.gd")
const LOADER_PATH := "res://src/scenes/streaming/streaming_world_loader.gd"
func _initialize() -> void:
var failures: Array[String] = []
_verify_cache_contract(failures)
_verify_loader_boundary(failures)
if not failures.is_empty():
for failure in failures:
push_error("TERRAIN_QUALITY_MESH_CACHE: %s" % failure)
quit(1)
return
print("TERRAIN_QUALITY_MESH_CACHE PASS contract_cases=15 loader_calls=3")
quit(0)
func _verify_cache_contract(failures: Array[String]) -> void:
var cache: RefCounted = CACHE_SCRIPT.new()
var mesh_a := ArrayMesh.new()
var mesh_b := ArrayMesh.new()
var mesh_c := ArrayMesh.new()
_expect_true(not bool(cache.call("store", "", mesh_a, "baked_full", 2)), "empty key rejected", failures)
_expect_true(not bool(cache.call("store", "a", null, "baked_full", 2)), "null mesh rejected", failures)
for excluded_source in ["control_splat_cache", "splat_cache", "splat"]:
_expect_true(not bool(cache.call("store", "a", mesh_a, excluded_source, 2)), "%s rejected" % excluded_source, failures)
_expect_true(bool(cache.call("store", "a", mesh_a, "baked_full", 2)), "first store", failures)
_expect_true(bool(cache.call("store", "b", mesh_b, "baked_full", 2)), "second store", failures)
var restored_a: Dictionary = cache.call("restore", "a")
_expect_true(restored_a.get("mesh") == mesh_a, "restore retains mesh reference", failures)
_expect_true(String(restored_a.get("source", "")) == "baked_full", "restore retains source", failures)
_expect_true(bool(cache.call("store", "c", mesh_c, "baked_full", 2)), "third store", failures)
_expect_true((cache.call("restore", "b") as Dictionary).is_empty(), "oldest evicted after touch", failures)
_expect_true(not (cache.call("restore", "a") as Dictionary).is_empty(), "touched entry retained", failures)
var diagnostics: Dictionary = cache.call("diagnostic_snapshot")
_expect_true(int(diagnostics.get("entry_count", 0)) == 2, "bounded count", failures)
(diagnostics.get("entries_oldest_to_newest") as Array).clear()
_expect_true(int((cache.call("diagnostic_snapshot") as Dictionary).get("entry_count", 0)) == 2, "diagnostics detached", failures)
_expect_true(not bool(cache.call("store", "zero", ArrayMesh.new(), "baked_full", -1)), "negative capacity clamps to zero", failures)
cache.call("clear")
_expect_true(int((cache.call("diagnostic_snapshot") as Dictionary).get("entry_count", -1)) == 0, "clear releases entries", failures)
func _verify_loader_boundary(failures: Array[String]) -> void:
var source := _read_text(LOADER_PATH, failures)
_expect_true(source.contains("TERRAIN_QUALITY_MESH_CACHE_SCRIPT.new()"), "loader composes cache", failures)
_expect_true(source.contains('_terrain_quality_mesh_cache.call("restore", tile_key)'), "loader delegates restore", failures)
_expect_true(source.contains('_terrain_quality_mesh_cache.call("clear")'), "loader delegates clear", failures)
for removed_symbol in ["_terrain_quality_mesh_cache_order", "func _touch_quality_terrain_mesh_cache", "func _trim_quality_terrain_mesh_cache"]:
_expect_true(not source.contains(removed_symbol), "loader omits %s" % removed_symbol, failures)
func _read_text(path: String, failures: Array[String]) -> String:
var file := FileAccess.open(path, FileAccess.READ)
if file == null:
failures.append("cannot open %s" % path)
return ""
return file.get_as_text()
func _expect_true(actual_value: bool, label: String, failures: Array[String]) -> void:
if not actual_value:
failures.append("%s expected true" % label)
@@ -0,0 +1 @@
uid://khd1ths2b4ya
+10 -3
View File
@@ -27,10 +27,10 @@ Runtime и Editor используют facade; planner/scheduler тестиру
## Evidence ## Evidence
- Date: 2026-07-16 - Date: 2026-07-16
- Revision/worktree: master merges `7ece2ab`, `7e35de7`, `80cb084`, `d6e5b53`, `c69abd6`, `606770c`; - Revision/worktree: master merges `7ece2ab`, `7e35de7`, `80cb084`, `d6e5b53`, `c69abd6`, `606770c`, `630a0c1`;
packages `M03-RND-SCHEDULER-001`, `M03-RND-INTERNAL-ACCESS-GATE-001`, packages `M03-RND-SCHEDULER-001`, `M03-RND-INTERNAL-ACCESS-GATE-001`,
`M03-RND-FACADE-GROUND-QUERY-001`, `M03-RND-FACADE-ENVIRONMENT-001`, `M03-RND-FACADE-GROUND-QUERY-001`, `M03-RND-FACADE-ENVIRONMENT-001`,
`M03-RND-FACADE-ENTITY-001` `M03-RND-FACADE-ENTITY-001`, `M03-RND-TERRAIN-CACHE-SERVICE-001`
- Commands: dedicated scheduler/planner/facade/focus/coordinate/manifest/shutdown - Commands: dedicated scheduler/planner/facade/focus/coordinate/manifest/shutdown
headless verifiers; Godot cold editor parse; coordination, documentation and diff gates. headless verifiers; Godot cold editor parse; coordination, documentation and diff gates.
- Results: scheduler `cases=6 iterations=20000 elapsed_ms=10.216`; planner - Results: scheduler `cases=6 iterations=20000 elapsed_ms=10.216`; planner
@@ -45,6 +45,8 @@ Runtime и Editor используют facade; planner/scheduler тестиру
applied the dusk checkpoint at `time=19.00`. Entity presentation passed applied the dusk checkpoint at `time=19.00`. Entity presentation passed
`snapshot_cases=8 lifecycle_cases=14 runtime_scenes=2`; facade expanded to `snapshot_cases=8 lifecycle_cases=14 runtime_scenes=2`; facade expanded to
`delegation=9 entity_contract=6`, while character presentation remained `9/10/3/1`. `delegation=9 entity_contract=6`, while character presentation remained `9/10/3/1`.
Terrain quality cache passed `contract_cases=15 loader_calls=3`; internal-access
inventory decreased to `42` because the loader no longer owns the LRU-order field.
M02 terrain-query regression M02 terrain-query regression
remained green (13 pre-existing expired M00 claim warnings). remained green (13 pre-existing expired M00 claim warnings).
- Fidelity comparison: all 16 historical operation limits and drain sites were - Fidelity comparison: all 16 historical operation limits and drain sites were
@@ -56,16 +58,21 @@ Runtime и Editor используют facade; planner/scheduler тестиру
profile/area/skybox selection, interpolation, fog, sun and shader rules remain unchanged. profile/area/skybox selection, interpolation, fog, sun and shader rules remain unchanged.
Runtime produces no world-entity commands yet, so player/world output and all Runtime produces no world-entity commands yet, so player/world output and all
baseline plans remain unchanged by the new present/remove ownership boundary. baseline plans remain unchanged by the new present/remove ownership boundary.
Terrain revisit cache retains the same admitted/excluded sources, LRU touch/trim,
profile capacities and three loader adapter sites; no cache format changed.
No new build-12340 parity claim is made; asset-backed visual/p95/p99 comparison remains pending. No new build-12340 parity claim is made; asset-backed visual/p95/p99 comparison remains pending.
- Changed files: scene-free scheduler and verifier; streamer budget composition/ - Changed files: scene-free scheduler and verifier; streamer budget composition/
permit delegation; scheduler module spec; world renderer/module registry/RENDER notes; permit delegation; scheduler module spec; world renderer/module registry/RENDER notes;
renderer-owned ground sample, facade/loader ground query, migrated terrain probe, renderer-owned ground sample, facade/loader ground query, migrated terrain probe,
immutable environment snapshot, facade/sky adapter, migrated checkpoint time input, immutable environment snapshot, facade/sky adapter, migrated checkpoint time input,
contract-v1 entity snapshot, renderer-owned entity presenter, runtime composition, contract-v1 entity snapshot, renderer-owned entity presenter, runtime composition,
scene-free terrain quality Mesh cache and loader store/restore/clear adapters,
expanded facade/internal-access/coordinate verifiers; work-package claims and this evidence. expanded facade/internal-access/coordinate verifiers; work-package claims and this evidence.
- Remaining risks: queue storage length and worker concurrency/stale-result logic - Remaining risks: queue storage length and worker concurrency/stale-result logic
remain streamer-owned; cancellation stops new permits but does not interrupt remain streamer-owned; cancellation stops new permits but does not interrupt
in-flight work; the environment snapshot currently covers world time but not weather in-flight work; the environment snapshot currently covers world time but not weather
or indoor state; entity PackedScene loading is synchronous and not accepted for or indoor state; entity PackedScene loading is synchronous and not accepted for
network-scale hot paths; M03 still needs service extraction network-scale hot paths; terrain cache is entry-count bounded without Mesh-byte
metrics and the remaining terrain build/tasks/state/finalization stay monolithic;
M03 still needs further terrain/M2/WMO/liquid service extraction
and dependency/performance acceptance. and dependency/performance acceptance.