render: extract WMO render resource finalizer

This commit is contained in:
2026-07-18 14:03:27 +04:00
parent fb7c9f174e
commit a86f8f2212
12 changed files with 751 additions and 56 deletions
+19 -3
View File
@@ -32,6 +32,7 @@ Paired run 2026-07-11 подтвердил крупный coordinate/placement g
- `src/render/wmo/wmo_render_build_step_planner.gd` - mesh-first lightweight WMO group operation and cursor planning without Nodes or Resources. - `src/render/wmo/wmo_render_build_step_planner.gd` - mesh-first lightweight WMO group operation and cursor planning without Nodes or Resources.
- `src/render/wmo/wmo_render_build_queue.gd` / `wmo_render_build_job.gd` - typed pending group jobs, FIFO placement keys and strong root/resource references without engine destruction. - `src/render/wmo/wmo_render_build_queue.gd` / `wmo_render_build_job.gd` - typed pending group jobs, FIFO placement keys and strong root/resource references without engine destruction.
- `src/render/wmo/wmo_render_resource_cache_state.gd` - validated lightweight WMO render Resources, negative cache and pending cache paths without ResourceLoader I/O. - `src/render/wmo/wmo_render_resource_cache_state.gd` - validated lightweight WMO render Resources, negative cache and pending cache paths without ResourceLoader I/O.
- `src/render/wmo/wmo_render_resource_finalizer.gd` - lightweight WMO terminal polling, script/format validation and Resource/missing publication.
- `src/render/wmo/wmo_scene_resource_cache_state.gd` - validated cached-WMO PackedScenes, negative cache and pending `.tscn` paths without file/I/O/Node ownership. - `src/render/wmo/wmo_scene_resource_cache_state.gd` - validated cached-WMO PackedScenes, negative cache and pending `.tscn` paths without file/I/O/Node ownership.
- `src/render/liquid/adt_water_load_pipeline_state.gd` - ADT water pending FIFO/dedupe, active task IDs and worker-safe parsed-result mailbox without parser or Node ownership. - `src/render/liquid/adt_water_load_pipeline_state.gd` - ADT water pending FIFO/dedupe, active task IDs and worker-safe parsed-result mailbox without parser or Node ownership.
- `src/render/liquid/adt_water_scene_finalizer.gd` - stateless main-thread ADT water build/attach and optional persisted Editor ownership through the existing ADTBuilder. - `src/render/liquid/adt_water_scene_finalizer.gd` - stateless main-thread ADT water build/attach and optional persisted Editor ownership through the existing ADTBuilder.
@@ -1318,9 +1319,10 @@ $exe = Join-Path $env:TEMP 'godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe'
- `WmoRenderResourceCacheState` now owns validated lightweight-WMO render - `WmoRenderResourceCacheState` now owns validated lightweight-WMO render
Resources, negative entries and normalized-path to pending-cache-path records. Resources, negative entries and normalized-path to pending-cache-path records.
- `StreamingWorldLoader` still constructs cache paths, calls `ResourceLoader`, - `StreamingWorldLoader` still constructs cache paths and starts requests.
polls requests and validates `WMOStreamingResource` script identity plus `WmoRenderResourceFinalizer` polls terminal requests and validates exact
`FORMAT_VERSION` before completing cache state. `WMOStreamingResource` script identity plus `FORMAT_VERSION` before completing
cache state.
- Map reset and orderly request draining clear pending/negative state while - Map reset and orderly request draining clear pending/negative state while
retaining accepted Resources; final runtime cache release clears all state. retaining accepted Resources; final runtime cache release clears all state.
- Missing render-cache files still are not negatively cached, preserving retry - Missing render-cache files still are not negatively cached, preserving retry
@@ -1328,6 +1330,20 @@ $exe = Join-Path $env:TEMP 'godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe'
- Asset-backed corrupt-cache, traversal/leak p95/p99 and paired fidelity evidence - Asset-backed corrupt-cache, traversal/leak p95/p99 and paired fidelity evidence
remain pending. remain pending.
## 2026-07-18 WMO Render Resource Finalizer Extraction
- `WmoRenderResourceFinalizer` now owns lightweight WMO render-cache terminal
status polling, loaded Resource retrieval, exact script/current-format
validation and Resource/missing publication.
- Detached pending snapshots retain Dictionary insertion order. Non-terminal
requests remain pending; failed, null, wrong-script and stale-format outcomes
retain the historical negative-cache transition.
- Accepted current-or-newer Resources keep exact identity. Loader retains cache
path selection, request admission, fallback/build orchestration, Node lifetime
and shutdown drain order.
- Cache format, profiles and visible output are unchanged. Synthetic fixtures
are not serialized private assets, leak, p95/p99 or visual-fidelity evidence.
## 2026-07-17 WMO Scene Resource Cache State Extraction ## 2026-07-17 WMO Scene Resource Cache State Extraction
- `WmoSceneResourceCacheState` now owns validated cached-WMO PackedScenes, - `WmoSceneResourceCacheState` now owns validated cached-WMO PackedScenes,
+1
View File
@@ -46,6 +46,7 @@
| WMO render build step planner | Implemented extraction | [`wmo-render-build-step-planner.md`](wmo-render-build-step-planner.md) | | WMO render build step planner | Implemented extraction | [`wmo-render-build-step-planner.md`](wmo-render-build-step-planner.md) |
| WMO render build queue | Implemented extraction | [`wmo-render-build-queue.md`](wmo-render-build-queue.md) | | WMO render build queue | Implemented extraction | [`wmo-render-build-queue.md`](wmo-render-build-queue.md) |
| WMO render Resource cache state | Implemented extraction | [`wmo-render-resource-cache-state.md`](wmo-render-resource-cache-state.md) | | WMO render Resource cache state | Implemented extraction | [`wmo-render-resource-cache-state.md`](wmo-render-resource-cache-state.md) |
| WMO render Resource finalizer | Implemented extraction | [`wmo-render-resource-finalizer.md`](wmo-render-resource-finalizer.md) |
| WMO scene Resource cache state | Implemented extraction | [`wmo-scene-resource-cache-state.md`](wmo-scene-resource-cache-state.md) | | WMO scene Resource cache state | Implemented extraction | [`wmo-scene-resource-cache-state.md`](wmo-scene-resource-cache-state.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) |
+23 -17
View File
@@ -15,7 +15,8 @@
Own the mutually exclusive cached, missing and pending states for normalized Own the mutually exclusive cached, missing and pending states for normalized
lightweight-WMO render paths outside the monolithic streamer. The state holder lightweight-WMO render paths outside the monolithic streamer. The state holder
accepts only caller-validated `Resource` references and records cache paths for accepts only caller-validated `Resource` references and records cache paths for
threaded requests whose I/O lifecycle remains in `StreamingWorldLoader`. threaded requests whose terminal I/O lifecycle belongs to
`WmoRenderResourceFinalizer`.
## Non-goals ## Non-goals
@@ -34,7 +35,8 @@ flowchart LR
State -->|cached Resource| Loader State -->|cached Resource| Loader
Loader -->|cache path| ResourceLoader[Godot ResourceLoader] Loader -->|cache path| ResourceLoader[Godot ResourceLoader]
ResourceLoader -->|status and loaded Resource| Loader ResourceLoader -->|status and loaded Resource| Loader
Loader --> Validate[Script and FORMAT_VERSION validation] Loader --> Finalizer[WmoRenderResourceFinalizer]
Finalizer --> Validate[Script and FORMAT_VERSION validation]
Validate -->|accepted Resource or missing| State Validate -->|accepted Resource or missing| State
Loader --> Fallback[Cached scene or live-prototype fallback] Loader --> Fallback[Cached scene or live-prototype fallback]
``` ```
@@ -53,8 +55,8 @@ or editor dependency. Cache validation stays at the I/O boundary in the loader.
| `has_request(path)` | Query | Test pending threaded-request state | Renderer main thread | Empty returns false | | `has_request(path)` | Query | Test pending threaded-request state | Renderer main thread | Empty returns false |
| `remember_request(path, cache_path)` | Command/query | Record one loader-started request | Renderer main thread; until terminal/reset | Invalid or occupied state returns false | | `remember_request(path, cache_path)` | Command/query | Record one loader-started request | Renderer main thread; until terminal/reset | Invalid or occupied state returns false |
| `request_paths_snapshot()` | Query | Copy pending normalized/cache-path mapping | Poll or shutdown drain | Detached Dictionary | | `request_paths_snapshot()` | Query | Copy pending normalized/cache-path mapping | Poll or shutdown drain | Detached Dictionary |
| `complete_request_with_resource(path, resource)` | Command/query | Remove pending request and adopt caller-validated Resource | Terminal loader poll | Unknown/null returns false | | `complete_request_with_resource(path, resource)` | Command/query | Remove pending request and adopt caller-validated Resource | Terminal finalizer poll | Unknown/null returns false |
| `complete_request_as_missing(path)` | Command/query | Remove pending request and adopt negative state | Terminal loader poll | Unknown returns false | | `complete_request_as_missing(path)` | Command/query | Remove pending request and adopt negative state | Terminal finalizer poll | Unknown returns false |
| `clear_transient_state()` | Command | Clear pending and missing while retaining accepted Resources | Map reset/request drain | Idempotent | | `clear_transient_state()` | Command | Clear pending and missing while retaining accepted Resources | Map reset/request drain | Idempotent |
| `clear_all()` | Command | Release Resources, pending and missing | Final runtime cache release | Idempotent | | `clear_all()` | Command | Release Resources, pending and missing | Final runtime cache release | Idempotent |
| `pending_request_count()` | Query | Preserve renderer queue metric contribution | Renderer diagnostics | None | | `pending_request_count()` | Query | Preserve renderer queue metric contribution | Renderer diagnostics | None |
@@ -81,11 +83,11 @@ flowchart TD
Blocked -->|yes| Null[Return null; loader waits/falls back] Blocked -->|yes| Null[Return null; loader waits/falls back]
Blocked -->|no| Start[Loader starts threaded request] Blocked -->|no| Start[Loader starts threaded request]
Start --> Remember[remember_request] Start --> Remember[remember_request]
Remember --> Poll[Loader polls detached request snapshot] Remember --> Poll[Finalizer polls detached request snapshot]
Poll --> Terminal{Loaded or failed?} Poll --> Terminal{Loaded or failed?}
Terminal -->|no| Poll Terminal -->|no| Poll
Terminal -->|failed| Missing[complete as missing] Terminal -->|failed| Missing[complete as missing]
Terminal -->|loaded| Validate[Loader validates script/version] Terminal -->|loaded| Validate[Finalizer validates script/version]
Validate -->|valid| Adopt[complete with Resource] Validate -->|valid| Adopt[complete with Resource]
Validate -->|invalid| Missing Validate -->|invalid| Missing
``` ```
@@ -113,28 +115,31 @@ stateDiagram-v2
```mermaid ```mermaid
sequenceDiagram sequenceDiagram
participant Loader as StreamingWorldLoader participant Loader as StreamingWorldLoader
participant Finalizer as WmoRenderResourceFinalizer
participant State as WmoRenderResourceCacheState participant State as WmoRenderResourceCacheState
participant RL as ResourceLoader participant RL as ResourceLoader
Loader->>State: cached/missing/pending queries Loader->>State: cached/missing/pending queries
Loader->>RL: exists + load_threaded_request(cache path) Loader->>RL: exists + load_threaded_request(cache path)
Loader->>State: remember_request(normalized, cache path) Loader->>State: remember_request(normalized, cache path)
loop renderer tick loop renderer tick
Loader->>State: request_paths_snapshot() Loader->>Finalizer: poll_terminal_requests(State)
Loader->>RL: load_threaded_get_status(cache path) Finalizer->>State: request_paths_snapshot()
Finalizer->>RL: load_threaded_get_status(cache path)
end end
alt load failed alt load failed
Loader->>State: complete_request_as_missing(normalized) Finalizer->>State: complete_request_as_missing(normalized)
else loaded else loaded
Loader->>RL: load_threaded_get(cache path) Finalizer->>RL: load_threaded_get(cache path)
Loader->>Loader: validate script and FORMAT_VERSION Finalizer->>Finalizer: validate script and FORMAT_VERSION
Loader->>State: complete with Resource or as missing Finalizer->>State: complete with Resource or as missing
end end
``` ```
## Ownership, threading and resources ## Ownership, threading and resources
- The state owns three Dictionaries and strong references to accepted Resources. - The state owns three Dictionaries and strong references to accepted Resources.
- The loader owns normalization, cache paths, ResourceLoader calls and validation. - The loader owns normalization, cache paths and request admission; the finalizer
owns terminal ResourceLoader calls and validation.
- All mutation is serialized by the renderer main-thread lookup/drain lifecycle. - All mutation is serialized by the renderer main-thread lookup/drain lifecycle.
- No mutex or callback is needed; detached request snapshots permit safe removal. - No mutex or callback is needed; detached request snapshots permit safe removal.
- The loader/build queue borrow Resources without transferring ownership. - The loader/build queue borrow Resources without transferring ownership.
@@ -158,8 +163,8 @@ format version, request scheduling and WMO build budgets remain loader-owned.
## Persistence, cache and migration ## Persistence, cache and migration
The state is runtime-only and serializes nothing. `WMOStreamingResource` script The state is runtime-only and serializes nothing. `WMOStreamingResource` script
identity and `FORMAT_VERSION` validation remain unchanged in the loader; no cache identity and `FORMAT_VERSION` validation remain unchanged in the finalizer; no
migration or rebuild is introduced by this extraction. cache migration or rebuild is introduced by this extraction.
## Diagnostics and observability ## Diagnostics and observability
@@ -189,7 +194,7 @@ states; Resource references and cache file paths are not exposed. No logs emit.
| Capability | Status | Evidence | Gap/next step | | Capability | Status | Evidence | Gap/next step |
|---|---|---|---| |---|---|---|---|
| Lightweight WMO render Resource state | Implemented extraction | Lifecycle/source/timing and shutdown verifiers | Asset-backed traversal/leak evidence pending | | Lightweight WMO render Resource state | Implemented extraction | Lifecycle/source/timing and shutdown verifiers | Asset-backed traversal/leak evidence pending |
| Cache script/version validation | Preserved in loader | Source boundary and WMO regressions | Dedicated corrupt-cache fixture could follow | | Cache script/version validation | Implemented extraction | Finalizer source and synthetic validation verifier | Serialized corrupt-cache fixture could follow |
| Cached WMO scene state | Implemented extraction | Scene-cache lifecycle/source/timing verifier | Asset-backed traversal/leak evidence pending | | Cached WMO scene state | Implemented extraction | Scene-cache lifecycle/source/timing verifier | Asset-backed traversal/leak evidence pending |
| WMO materialization | Partial/loader-owned | Queue/planner regressions | Further safe extraction | | WMO materialization | Partial/loader-owned | Queue/planner regressions | Further safe extraction |
@@ -206,7 +211,8 @@ states; Resource references and cache file paths are not exposed. No logs emit.
| Path | Responsibility | | Path | Responsibility |
|---|---| |---|---|
| `src/render/wmo/wmo_render_resource_cache_state.gd` | Resource/missing/request state and resets | | `src/render/wmo/wmo_render_resource_cache_state.gd` | Resource/missing/request state and resets |
| `src/scenes/streaming/streaming_world_loader.gd` | Normalize, request, poll, validate, fallback and shutdown I/O | | `src/render/wmo/wmo_render_resource_finalizer.gd` | Terminal polling, script/format validation and publication |
| `src/scenes/streaming/streaming_world_loader.gd` | Normalize, request admission, fallback and shutdown order |
| `src/tools/verify_wmo_render_resource_cache_state.gd` | State, boundary and timing regression | | `src/tools/verify_wmo_render_resource_cache_state.gd` | State, boundary and timing regression |
| `src/tools/verify_render_runtime_cache_shutdown.gd` | Loader final Resource ownership regression | | `src/tools/verify_render_runtime_cache_shutdown.gd` | Loader final Resource ownership regression |
@@ -0,0 +1,219 @@
# WMO Render Resource Finalizer
## Metadata
| Field | Value |
|---|---|
| Status | Implemented extraction |
| Target/work package | M03 / `M03-RND-WMO-RENDER-RESOURCE-FINALIZER-001` |
| Owners | Lightweight WMO terminal status polling, Resource validation and cache/missing publication |
| Last verified | Worktree `work/sindo-main-codex/m03-wmo-render-resource-finalizer`, 2026-07-18 |
| Profiles/capabilities | Profile-independent lightweight WMO render-cache finalization |
## Purpose
Finalize pending lightweight WMO `.res` cache requests outside
`StreamingWorldLoader`: poll terminal status, retrieve a loaded Resource, enforce
exact script/current-format validation and publish either the exact Resource or
the historical missing outcome.
## Non-goals
- Select normalized WMO/cache paths or start threaded requests.
- Finalize cached WMO PackedScenes or live WMOBuilder prototypes.
- Own cache lifetime, placements, build queues, scheduler permits or Nodes.
- Change `WMOStreamingResource.FORMAT_VERSION`, fallback or visible behavior.
## Context and boundaries
```mermaid
flowchart LR
Loader[StreamingWorldLoader] -->|compose and tick| Finalizer[WmoRenderResourceFinalizer]
Cache[WmoRenderResourceCacheState] -->|pending snapshot| Finalizer
Finalizer --> ResourceLoader
Finalizer -->|validated Resource or missing| Cache
Cache --> Build[WMO render build queue]
```
The service may depend on `ResourceLoader`, the injected expected Script and the
render Resource cache-state API. File selection, FileAccess, SceneTree, WMOBuilder,
placement, scheduler and application layers remain outside it.
## Public API
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|---|---|---|---|---|
| `poll_terminal_requests(cache_state)` | Command/query | Poll all pending paths once and publish terminal outcomes | Renderer main thread; stateless between calls | Null state returns zero; non-terminal retained |
| `is_current_render_resource(resource)` | Query | Enforce exact injected Script and minimum format | Renderer main thread; stateless | Null/wrong/stale false |
| `load_threaded_get_status(path)` | Boundary query | Read opaque threaded status | Renderer main thread; injectable in tests | ResourceLoader semantics |
| `load_threaded_get(path)` | Boundary query | Retrieve terminal Resource | Renderer main thread; injectable in tests | Null accepted as failed outcome |
## Inputs and outputs
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|---|---|---|---|---|---|
| Input | Detached normalized-path to `.res` path Dictionary | Render cache state | Finalizer polling | Caller-owned copy | One poll pass |
| Input | Opaque ResourceLoader status | ResourceLoader | Finalizer | Integer value | One path poll |
| Input | Terminal Resource | ResourceLoader | Validator | Borrowed reference | One completion |
| Input | Expected Script/minimum format | Loader composition | Validator | Borrowed/value | Finalizer lifetime |
| Output | Exact validated Resource | Finalizer | Render cache state | Cache adopts strong reference | Until full clear |
| Output | Missing transition | Finalizer | Render cache state | Path-only state | Until transient clear |
## Data flow
```mermaid
flowchart TD
Snapshot[Detached pending snapshot] --> Next[Next path in insertion order]
Next --> Poll[Poll ResourceLoader status]
Poll --> Terminal{Loaded or failed?}
Terminal -->|no| Retain[Retain pending]
Terminal -->|failed| Missing[Complete as missing]
Terminal -->|loaded| Get[Get terminal Resource]
Get --> Validate{Exact script and format current?}
Validate -->|yes| Adopt[Complete with exact Resource]
Validate -->|no| Missing
Retain --> Next
Adopt --> Next
Missing --> Next
```
## Lifecycle/state
```mermaid
stateDiagram-v2
[*] --> Polling
Polling --> Pending: non-terminal status
Polling --> Missing: failed/null/wrong/stale
Polling --> Cached: exact script and current format
Pending --> Polling: later tick
Missing --> [*]
Cached --> [*]
```
The finalizer retains no per-path state. The sibling cache state owns all
Pending/Cached/Missing lifetime and reset transitions.
## Main sequence
```mermaid
sequenceDiagram
participant Loader as StreamingWorldLoader
participant Finalizer as WmoRenderResourceFinalizer
participant Cache as WmoRenderResourceCacheState
participant RL as ResourceLoader
Loader->>Finalizer: poll_terminal_requests(Cache)
Finalizer->>Cache: request_paths_snapshot()
loop insertion-ordered paths
Finalizer->>RL: load_threaded_get_status(cache path)
alt non-terminal
Finalizer->>Finalizer: retain request
else failed
Finalizer->>Cache: complete_request_as_missing(path)
else loaded
Finalizer->>RL: load_threaded_get(cache path)
Finalizer->>Finalizer: exact Script and format validation
Finalizer->>Cache: complete with Resource or missing
end
end
```
## Dependency diagram
```mermaid
flowchart TB
Loader[StreamingWorldLoader] --> Finalizer[WmoRenderResourceFinalizer]
Loader --> Cache[WmoRenderResourceCacheState]
Finalizer --> Cache
Finalizer --> ResourceLoader
Finalizer --> Script[WMOStreamingResource Script contract]
Finalizer -. no dependency .-> FileAccess
Finalizer -. no dependency .-> WMOBuilder
Finalizer -. no dependency .-> Node
Finalizer -. no dependency .-> Scheduler
```
## Ownership, threading and resources
- Renderer main thread serializes polling and cache-state publication.
- Finalizer owns no Resource, request, Node, RID or file lifetime.
- Cache state adopts accepted exact Resource references until its full clear.
- Loader owns request admission, fallback/build orchestration and shutdown order.
- Detached snapshots allow terminal cache-state mutation during iteration.
## Errors, cancellation and recovery
| Failure/state | Detection | Behavior | Diagnostic | Recovery |
|---|---|---|---|---|
| Null cache state | Guard | Return zero | Contract verifier | Correct composition |
| Non-terminal request | Status | Retain pending | Pending count | Poll next tick |
| Failed request | Terminal status | Complete missing | Negative cache | Transient reset/rebuilt cache |
| Null/wrong-script Resource | Validation | Complete missing | Synthetic fixture | Rebuild cache and reset |
| Stale format | Format below injected minimum | Complete missing | Synthetic fixture | Rebuild cache and reset |
| Shutdown | Loader lifecycle | Drain then cache clear | Shutdown verifier | New loader starts absent |
## Configuration and capabilities
The loader injects the exact `WMOStreamingResource` Script and its
`FORMAT_VERSION`. The service introduces no setting, profile, permit or cache
format. WMO cache paths and build budgets remain loader-owned.
## Persistence, cache and migration
The module serializes nothing and changes no cache format. It only enforces the
existing script identity and current-or-newer format rule before runtime cache
adoption, so no migration or rebake is introduced.
## Diagnostics and observability
- `poll_terminal_requests` returns terminal completion count for tests/future metrics.
- Existing cache pending count and renderer `wmobuild` metrics are unchanged.
- The service emits no logs; normalized relative path remains the correlation key.
## Verification
- `verify_wmo_render_resource_finalizer.gd`: null/non-terminal, insertion order,
failed/load boundary, null/wrong/stale rejection, current/newer identity,
source ownership and 1,000 terminal polls under one second.
- Cache-state, WMO queue/planner/registry/resolver, shutdown, facade,
internal-access and baseline regressions protect adjacent behavior.
- Fidelity evidence is exact orchestration extraction; no asset-backed visual or
original-client parity claim is made.
## Extension points
- A legal current/stale `.res` fixture can extend validation evidence unchanged.
- PackedScene finalization remains separate because it instantiates a probe Node
and uses WMOBuilder metadata rather than script/format Resource fields.
## Capability status
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| Lightweight WMO terminal polling | Implemented extraction | Status/order/source/timing verifier | Asset-backed traversal pending |
| Script/format validation | Implemented extraction | Wrong/stale/current/newer fixtures | Legal serialized fixture pending |
| Resource/missing publication | Implemented extraction | Exact identity/negative-state fixtures | Asset-backed lifetime/leak run pending |
| WMO materialization | Loader-owned | Existing build regressions | Further safe extraction pending |
## Known gaps and risks
- ResourceLoader status/get remains synchronous main-thread boundary polling.
- No proprietary WMO corpus, serialized corrupt-version fixture, leak run,
traversal p95/p99 or paired original-client capture is included.
## Source map
| Path | Responsibility |
|---|---|
| `src/render/wmo/wmo_render_resource_finalizer.gd` | Terminal polling, validation and publication |
| `src/render/wmo/wmo_render_resource_cache_state.gd` | Resource/missing/request ownership and resets |
| `src/scenes/streaming/streaming_world_loader.gd` | Composition, request admission, fallback and build orchestration |
| `src/resources/wmo_streaming_resource.gd` | Serialized cache contract and format version |
| `src/tools/verify_wmo_render_resource_finalizer.gd` | Terminal-I/O/validation/source/timing regression |
## Related decisions and references
- [`wmo-render-resource-cache-state.md`](wmo-render-resource-cache-state.md)
- [`wmo-render-build-queue.md`](wmo-render-build-queue.md)
- [`world-renderer.md`](world-renderer.md)
- [`../../RENDER.md`](../../RENDER.md)
- [`../../targets/roadmap/02-rendering-and-graphics.md`](../../targets/roadmap/02-rendering-and-graphics.md)
+11 -5
View File
@@ -165,6 +165,7 @@ from externally reading/writing loader-private queue, task, cache and tile-state
| `WmoRenderBuildStepPlanner.plan_step` | Internal pure WMO service | Selects one mesh-first lightweight render-group operation and next cursors | Main/any thread; stateless | Raw integer comparisons are preserved without clamping | | `WmoRenderBuildStepPlanner.plan_step` | Internal pure WMO service | Selects one mesh-first lightweight render-group operation and next cursors | Main/any thread; stateless | Raw integer comparisons are preserved without clamping |
| `WmoRenderBuildQueue` / `WmoRenderBuildJob` | Internal WMO pending-state service | Owns typed root/resource/cursor jobs and FIFO placement keys | Renderer main thread; map session | Invalid enqueue rejected; duplicate/stale behavior preserved | | `WmoRenderBuildQueue` / `WmoRenderBuildJob` | Internal WMO pending-state service | Owns typed root/resource/cursor jobs and FIFO placement keys | Renderer main thread; map session | Invalid enqueue rejected; duplicate/stale behavior preserved |
| `WmoRenderResourceCacheState` | Internal WMO cache-state service | Owns validated Resources, negative entries and pending cache paths | Renderer main thread; map/cache session | Invalid/occupied request and unknown completion are rejected | | `WmoRenderResourceCacheState` | Internal WMO cache-state service | Owns validated Resources, negative entries and pending cache paths | Renderer main thread; map/cache session | Invalid/occupied request and unknown completion are rejected |
| `WmoRenderResourceFinalizer` | Internal WMO terminal-I/O service | Polls lightweight render requests, validates script/format and publishes Resource/missing outcomes | Renderer main thread; stateless across calls | Non-terminal retained; failed/null/wrong/stale complete missing |
| `WmoSceneResourceCacheState` | Internal WMO cache-state service | Owns validated PackedScenes, negative entries and pending `.tscn` paths | Renderer main thread; map/cache session | Direct missing and terminal request transitions remain distinct | | `WmoSceneResourceCacheState` | Internal WMO cache-state service | Owns validated PackedScenes, negative entries and pending `.tscn` paths | Renderer main thread; map/cache session | Direct missing and terminal request transitions remain distinct |
| `AdtWaterLoadPipelineState` | Internal liquid async-state service | Owns ADT water FIFO/dedupe, active task IDs and mutex result mailbox | Main-thread state; worker result publication | Invalid/duplicate requests rejected; clear does not interrupt workers | | `AdtWaterLoadPipelineState` | Internal liquid async-state service | Owns ADT water FIFO/dedupe, active task IDs and mutex result mailbox | Main-thread state; worker result publication | Invalid/duplicate requests rejected; clear does not interrupt workers |
| `AdtWaterSceneFinalizer.attach_water_scene` | Internal liquid main-thread service | Builds and attaches one existing-format ADT Water subtree | Main thread; stateless, returned Node tile-owned | Empty/invalid/dry input returns null without attachment | | `AdtWaterSceneFinalizer.attach_water_scene` | Internal liquid main-thread service | Builds and attaches one existing-format ADT Water subtree | Main thread; stateless, returned Node tile-owned | Empty/invalid/dry input returns null without attachment |
@@ -412,10 +413,12 @@ sequenceDiagram
and cursor plan. `WmoRenderBuildQueue` owns typed pending jobs, FIFO keys and and cursor plan. `WmoRenderBuildQueue` owns typed pending jobs, FIFO keys and
strong root/resource references without freeing engine objects. strong root/resource references without freeing engine objects.
`WmoRenderResourceCacheState` owns validated render Resources, negative entries `WmoRenderResourceCacheState` owns validated render Resources, negative entries
and pending cache paths. `WmoSceneResourceCacheState` similarly owns validated and pending cache paths; `WmoRenderResourceFinalizer` owns its terminal
PackedScenes, negative entries and pending `.tscn` paths. The loader retains ResourceLoader polling and script/format validation. `WmoSceneResourceCacheState`
ResourceLoader/FileAccess I/O, size and cache-version validation, live fallback, similarly owns validated PackedScenes, negative entries and pending `.tscn`
materialization, permits, validity reactions and every Node lifecycle action. paths. The loader retains request admission, PackedScene/FileAccess I/O, size
and scene-cache validation, live fallback, materialization, permits, validity
reactions and every Node lifecycle action.
- `AdtWaterLoadPipelineState` owns pending request order/deduplication, opaque - `AdtWaterLoadPipelineState` owns pending request order/deduplication, opaque
active task IDs and the worker-safe parsed-result mailbox. The loader retains active task IDs and the worker-safe parsed-result mailbox. The loader retains
WorkerThreadPool start/wait, ADTLoader parsing, concurrency/finalize permits, WorkerThreadPool start/wait, ADTLoader parsing, concurrency/finalize permits,
@@ -613,7 +616,8 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
| WMO placement registry | Implemented extraction | Scene-free ownership/lifecycle/source/timing contract | Build/resource state and asset-backed cross-tile corpus pending | | WMO placement registry | Implemented extraction | Scene-free ownership/lifecycle/source/timing contract | Build/resource state and asset-backed cross-tile corpus pending |
| WMO render build step planner | Implemented extraction | Scene-free order/cursor/source/timing contract | Asset-backed traversal p95/p99 pending | | WMO render build step planner | Implemented extraction | Scene-free order/cursor/source/timing contract | Asset-backed traversal p95/p99 pending |
| WMO render build queue | Implemented extraction | Typed lifecycle/order/ownership/source/timing contract | Materialization and asset-backed traversal/leak evidence pending | | WMO render build queue | Implemented extraction | Typed lifecycle/order/ownership/source/timing contract | Materialization and asset-backed traversal/leak evidence pending |
| WMO render Resource cache state | Implemented extraction | Scene-free lifecycle/exclusivity/source/timing plus shutdown contract | ResourceLoader I/O and asset-backed traversal/leak evidence pending | | WMO render Resource cache state | Implemented extraction | Scene-free lifecycle/exclusivity/source/timing plus shutdown contract | Asset-backed traversal/leak evidence pending |
| WMO render Resource finalizer | Implemented extraction | Status/order/script/format/adoption/source/timing contract | Serialized/asset-backed corrupt-cache and leak evidence pending |
| WMO scene Resource cache state | Implemented extraction | Scene-free lifecycle/direct-missing/source/timing plus shutdown contract | ResourceLoader/live-fallback extraction and asset-backed traversal/leak evidence pending | | WMO scene Resource cache state | Implemented extraction | Scene-free lifecycle/direct-missing/source/timing plus shutdown contract | ResourceLoader/live-fallback extraction and asset-backed traversal/leak evidence pending |
| WMO rendering | Partial | Cached group rendering | Portals/rooms/material parity | | WMO rendering | Partial | Cached group rendering | Portals/rooms/material parity |
| ADT water load pipeline state | Implemented extraction | Scene-free FIFO/task/thread/source/timing contract | Parse/finalization and asset-backed traversal/leak evidence pending | | ADT water load pipeline state | Implemented extraction | Scene-free FIFO/task/thread/source/timing contract | Parse/finalization and asset-backed traversal/leak evidence pending |
@@ -689,6 +693,7 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
| `src/render/m2/m2_raw_model_repository.gd` | Stateless static/animated native M2 file boundary | | `src/render/m2/m2_raw_model_repository.gd` | Stateless static/animated native M2 file boundary |
| `src/render/m2/m2_native_animation_resource_observer.gd` | Native candidate/read/build/cache observation | | `src/render/m2/m2_native_animation_resource_observer.gd` | Native candidate/read/build/cache observation |
| `src/render/m2/m2_prototype_cache_state.gd` | Detached prototype ownership and negative lookup outcomes | | `src/render/m2/m2_prototype_cache_state.gd` | Detached prototype ownership and negative lookup outcomes |
| `src/render/wmo/wmo_render_resource_finalizer.gd` | Lightweight WMO terminal polling, validation and publication |
| `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 |
@@ -723,6 +728,7 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
| `src/tools/verify_m2_raw_model_repository.gd` | M2 invalid/missing/native-boundary/dependency/timing regression | | `src/tools/verify_m2_raw_model_repository.gd` | M2 invalid/missing/native-boundary/dependency/timing regression |
| `src/tools/verify_m2_native_animation_resource_observer.gd` | Native candidate/cache/raw/build/adoption/boundary/timing regression | | `src/tools/verify_m2_native_animation_resource_observer.gd` | Native candidate/cache/raw/build/adoption/boundary/timing regression |
| `src/tools/verify_m2_prototype_cache_state.gd` | M2 prototype identity/negative/lifecycle/boundary/timing regression | | `src/tools/verify_m2_prototype_cache_state.gd` | M2 prototype identity/negative/lifecycle/boundary/timing regression |
| `src/tools/verify_wmo_render_resource_finalizer.gd` | WMO render status/order/validation/adoption/boundary/timing 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,90 @@
class_name WmoRenderResourceFinalizer
extends RefCounted
## Polls lightweight WMO render-cache requests and publishes terminal outcomes.
## Request admission, cache lifetime and WMO materialization remain caller-owned.
var _expected_resource_script: Script
var _minimum_format_version: int
var _resource_loader_adapter: Object
func _init(
expected_resource_script: Script,
minimum_format_version: int,
resource_loader_adapter: Object = null
) -> void:
_expected_resource_script = expected_resource_script
_minimum_format_version = minimum_format_version
_resource_loader_adapter = resource_loader_adapter
## Polls a detached pending snapshot in insertion order. Only terminal requests
## are removed and published as an exact Resource reference or missing outcome.
func poll_terminal_requests(render_resource_cache_state: RefCounted) -> int:
if render_resource_cache_state == null:
return 0
var completed_request_count := 0
var request_paths: Dictionary = render_resource_cache_state.call(
"request_paths_snapshot"
)
for normalized_relative_path_variant in request_paths.keys():
var normalized_relative_path := String(normalized_relative_path_variant)
var resource_path := String(request_paths[normalized_relative_path_variant])
var load_status := load_threaded_get_status(resource_path)
if (
load_status != ResourceLoader.THREAD_LOAD_LOADED
and load_status != ResourceLoader.THREAD_LOAD_FAILED
):
continue
if load_status != ResourceLoader.THREAD_LOAD_LOADED:
render_resource_cache_state.call(
"complete_request_as_missing",
normalized_relative_path
)
completed_request_count += 1
continue
var loaded_resource := load_threaded_get(resource_path)
if is_current_render_resource(loaded_resource):
render_resource_cache_state.call(
"complete_request_with_resource",
normalized_relative_path,
loaded_resource
)
else:
render_resource_cache_state.call(
"complete_request_as_missing",
normalized_relative_path
)
completed_request_count += 1
return completed_request_count
## Accepts only the exact configured script and its current-or-newer format.
func is_current_render_resource(resource: Resource) -> bool:
return (
resource != null
and _expected_resource_script != null
and resource.get_script() == _expected_resource_script
and int(resource.get("format_version")) >= _minimum_format_version
)
## Production ResourceLoader status boundary; injectable for synthetic tests.
func load_threaded_get_status(resource_path: String) -> int:
if _resource_loader_adapter != null:
return int(_resource_loader_adapter.call(
"load_threaded_get_status",
resource_path
))
return ResourceLoader.load_threaded_get_status(resource_path)
## Production ResourceLoader result boundary; injectable for synthetic tests.
func load_threaded_get(resource_path: String) -> Resource:
if _resource_loader_adapter != null:
return _resource_loader_adapter.call(
"load_threaded_get",
resource_path
) as Resource
return ResourceLoader.load_threaded_get(resource_path)
@@ -0,0 +1 @@
uid://dtvba6g1grttk
+10 -22
View File
@@ -24,6 +24,9 @@ const WMO_RENDER_BUILD_QUEUE_SCRIPT := preload(
const WMO_RENDER_RESOURCE_CACHE_STATE_SCRIPT := preload( const WMO_RENDER_RESOURCE_CACHE_STATE_SCRIPT := preload(
"res://src/render/wmo/wmo_render_resource_cache_state.gd" "res://src/render/wmo/wmo_render_resource_cache_state.gd"
) )
const WMO_RENDER_RESOURCE_FINALIZER_SCRIPT := preload(
"res://src/render/wmo/wmo_render_resource_finalizer.gd"
)
const WMO_SCENE_RESOURCE_CACHE_STATE_SCRIPT := preload( const WMO_SCENE_RESOURCE_CACHE_STATE_SCRIPT := preload(
"res://src/render/wmo/wmo_scene_resource_cache_state.gd" "res://src/render/wmo/wmo_scene_resource_cache_state.gd"
) )
@@ -361,6 +364,10 @@ var _tile_mesh_cache_order: Array = []
var _terrain_quality_mesh_cache: RefCounted = TERRAIN_QUALITY_MESH_CACHE_SCRIPT.new() var _terrain_quality_mesh_cache: RefCounted = TERRAIN_QUALITY_MESH_CACHE_SCRIPT.new()
var _wmo_prototype_cache: Dictionary = {} var _wmo_prototype_cache: Dictionary = {}
var _wmo_render_resource_cache_state := WMO_RENDER_RESOURCE_CACHE_STATE_SCRIPT.new() var _wmo_render_resource_cache_state := WMO_RENDER_RESOURCE_CACHE_STATE_SCRIPT.new()
var _wmo_render_resource_finalizer := WMO_RENDER_RESOURCE_FINALIZER_SCRIPT.new(
WMO_STREAMING_SCRIPT,
WMO_STREAMING_SCRIPT.FORMAT_VERSION
)
var _wmo_render_build_queue := WMO_RENDER_BUILD_QUEUE_SCRIPT.new() var _wmo_render_build_queue := WMO_RENDER_BUILD_QUEUE_SCRIPT.new()
var _wmo_scene_resource_cache_state := WMO_SCENE_RESOURCE_CACHE_STATE_SCRIPT.new() var _wmo_scene_resource_cache_state := WMO_SCENE_RESOURCE_CACHE_STATE_SCRIPT.new()
var _wmo_missing_cache: Dictionary = {} var _wmo_missing_cache: Dictionary = {}
@@ -3725,28 +3732,9 @@ func _drain_wmo_scene_loads() -> void:
func _drain_wmo_render_loads() -> void: func _drain_wmo_render_loads() -> void:
var request_paths := _wmo_render_resource_cache_state.request_paths_snapshot() _wmo_render_resource_finalizer.poll_terminal_requests(
for normalized_rel_variant in request_paths.keys(): _wmo_render_resource_cache_state
var normalized_rel := String(normalized_rel_variant) )
var path := String(request_paths[normalized_rel])
var status := ResourceLoader.load_threaded_get_status(path)
if status != ResourceLoader.THREAD_LOAD_LOADED and status != ResourceLoader.THREAD_LOAD_FAILED:
continue
if status != ResourceLoader.THREAD_LOAD_LOADED:
_wmo_render_resource_cache_state.complete_request_as_missing(normalized_rel)
continue
var resource: Resource = ResourceLoader.load_threaded_get(path)
if (
resource != null
and resource.get_script() == WMO_STREAMING_SCRIPT
and int(resource.get("format_version")) >= WMO_STREAMING_SCRIPT.FORMAT_VERSION
):
_wmo_render_resource_cache_state.complete_request_with_resource(
normalized_rel,
resource
)
else:
_wmo_render_resource_cache_state.complete_request_as_missing(normalized_rel)
func _get_wmo_render_or_request(rel_path: String) -> Resource: func _get_wmo_render_or_request(rel_path: String) -> Resource:
@@ -7,6 +7,7 @@ const CACHE_STATE_SCRIPT := preload(
"res://src/render/wmo/wmo_render_resource_cache_state.gd" "res://src/render/wmo/wmo_render_resource_cache_state.gd"
) )
const CACHE_STATE_PATH := "res://src/render/wmo/wmo_render_resource_cache_state.gd" const CACHE_STATE_PATH := "res://src/render/wmo/wmo_render_resource_cache_state.gd"
const FINALIZER_PATH := "res://src/render/wmo/wmo_render_resource_finalizer.gd"
const LOADER_PATH := "res://src/scenes/streaming/streaming_world_loader.gd" const LOADER_PATH := "res://src/scenes/streaming/streaming_world_loader.gd"
@@ -209,6 +210,7 @@ func _verify_detached_sorted_diagnostics(failures: Array[String]) -> void:
func _verify_ownership_boundaries(failures: Array[String]) -> void: func _verify_ownership_boundaries(failures: Array[String]) -> void:
var loader_source := _read_text(LOADER_PATH, failures) var loader_source := _read_text(LOADER_PATH, failures)
var cache_state_source := _read_text(CACHE_STATE_PATH, failures) var cache_state_source := _read_text(CACHE_STATE_PATH, failures)
var finalizer_source := _read_text(FINALIZER_PATH, failures)
_expect_true( _expect_true(
loader_source.contains("WMO_RENDER_RESOURCE_CACHE_STATE_SCRIPT.new()"), loader_source.contains("WMO_RENDER_RESOURCE_CACHE_STATE_SCRIPT.new()"),
"loader composes cache state", "loader composes cache state",
@@ -241,13 +243,13 @@ func _verify_ownership_boundaries(failures: Array[String]) -> void:
failures failures
) )
_expect_true( _expect_true(
loader_source.contains("resource.get_script() == WMO_STREAMING_SCRIPT"), finalizer_source.contains("resource.get_script() == _expected_resource_script"),
"loader retains script validation", "finalizer owns script validation",
failures failures
) )
_expect_true( _expect_true(
loader_source.contains("WMO_STREAMING_SCRIPT.FORMAT_VERSION"), loader_source.contains("WMO_STREAMING_SCRIPT.FORMAT_VERSION"),
"loader retains cache format validation", "loader injects cache format version",
failures failures
) )
@@ -0,0 +1,356 @@
extends SceneTree
## Asset-free terminal-I/O, validation, adoption, boundary and timing regression
## for the lightweight WMO render Resource finalizer.
const FINALIZER_SCRIPT := preload(
"res://src/render/wmo/wmo_render_resource_finalizer.gd"
)
const CACHE_STATE_SCRIPT := preload(
"res://src/render/wmo/wmo_render_resource_cache_state.gd"
)
const FINALIZER_PATH := "res://src/render/wmo/wmo_render_resource_finalizer.gd"
const CACHE_STATE_PATH := "res://src/render/wmo/wmo_render_resource_cache_state.gd"
const LOADER_PATH := "res://src/scenes/streaming/streaming_world_loader.gd"
class FakeWmoRenderResource extends Resource:
var format_version: int = 0
class FakeResourceLoaderAdapter extends RefCounted:
var status_by_path: Dictionary = {}
var resource_by_path: Dictionary = {}
var status_paths: Array[String] = []
var loaded_paths: Array[String] = []
func load_threaded_get_status(resource_path: String) -> int:
status_paths.append(resource_path)
return int(status_by_path.get(
resource_path,
ResourceLoader.THREAD_LOAD_IN_PROGRESS
))
func load_threaded_get(resource_path: String) -> Resource:
loaded_paths.append(resource_path)
return resource_by_path.get(resource_path) as Resource
func _initialize() -> void:
var failures: Array[String] = []
_verify_null_and_non_terminal_poll(failures)
_verify_failed_and_loaded_poll_order(failures)
_verify_loaded_resource_rejections(failures)
_verify_current_resource_adoption(failures)
_verify_validation_contract(failures)
_verify_source_boundaries(failures)
var elapsed_milliseconds := _verify_bounded_timing(failures)
if not failures.is_empty():
for failure in failures:
push_error("WMO_RENDER_RESOURCE_FINALIZER: %s" % failure)
quit(1)
return
print(
"WMO_RENDER_RESOURCE_FINALIZER PASS cases=24 iterations=1000 elapsed_ms=%.3f"
% elapsed_milliseconds
)
quit(0)
func _verify_null_and_non_terminal_poll(failures: Array[String]) -> void:
var adapter := FakeResourceLoaderAdapter.new()
var expected_resource := FakeWmoRenderResource.new()
var finalizer: RefCounted = FINALIZER_SCRIPT.new(
expected_resource.get_script(),
2,
adapter
)
_expect_equal(
int(finalizer.call("poll_terminal_requests", null)),
0,
"null cache state is rejected",
failures
)
var cache_state: RefCounted = CACHE_STATE_SCRIPT.new()
cache_state.call(
"remember_request",
"world/pending.wmo",
"res://cache/pending.res"
)
_expect_equal(
int(finalizer.call("poll_terminal_requests", cache_state)),
0,
"non-terminal request remains pending",
failures
)
_expect_true(
bool(cache_state.call("has_request", "world/pending.wmo")),
"pending request retained",
failures
)
_expect_string_array(
adapter.status_paths,
["res://cache/pending.res"],
"pending path polled once",
failures
)
_expect_equal(adapter.loaded_paths.size(), 0, "pending request is not loaded", failures)
func _verify_failed_and_loaded_poll_order(failures: Array[String]) -> void:
var adapter := FakeResourceLoaderAdapter.new()
var valid_resource := FakeWmoRenderResource.new()
valid_resource.format_version = 2
var finalizer: RefCounted = FINALIZER_SCRIPT.new(
valid_resource.get_script(),
2,
adapter
)
var cache_state: RefCounted = CACHE_STATE_SCRIPT.new()
var requests := {
"world/pending.wmo": "res://cache/pending.res",
"world/failed.wmo": "res://cache/failed.res",
"world/loaded.wmo": "res://cache/loaded.res",
}
for normalized_relative_path in requests:
cache_state.call(
"remember_request",
normalized_relative_path,
requests[normalized_relative_path]
)
adapter.status_by_path = {
"res://cache/failed.res": ResourceLoader.THREAD_LOAD_FAILED,
"res://cache/loaded.res": ResourceLoader.THREAD_LOAD_LOADED,
}
adapter.resource_by_path["res://cache/loaded.res"] = valid_resource
_expect_equal(
int(finalizer.call("poll_terminal_requests", cache_state)),
2,
"failed and loaded requests complete",
failures
)
_expect_string_array(
adapter.status_paths,
[
"res://cache/pending.res",
"res://cache/failed.res",
"res://cache/loaded.res",
],
"detached insertion order preserved",
failures
)
_expect_string_array(
adapter.loaded_paths,
["res://cache/loaded.res"],
"only loaded terminal Resource retrieved",
failures
)
_expect_true(
bool(cache_state.call("is_missing", "world/failed.wmo")),
"failed request publishes missing",
failures
)
_expect_true(
cache_state.call("resource_for", "world/loaded.wmo") == valid_resource,
"loaded Resource identity adopted",
failures
)
_expect_true(
bool(cache_state.call("has_request", "world/pending.wmo")),
"non-terminal sibling stays pending",
failures
)
func _verify_loaded_resource_rejections(failures: Array[String]) -> void:
var expected_resource := FakeWmoRenderResource.new()
var adapter := FakeResourceLoaderAdapter.new()
var finalizer: RefCounted = FINALIZER_SCRIPT.new(
expected_resource.get_script(),
2,
adapter
)
var stale_resource := FakeWmoRenderResource.new()
stale_resource.format_version = 1
var rejected_resources := {
"null": null,
"wrong_script": Resource.new(),
"stale": stale_resource,
}
var cache_state: RefCounted = CACHE_STATE_SCRIPT.new()
for rejection_name in rejected_resources:
var normalized_relative_path := "world/%s.wmo" % rejection_name
var cache_path := "res://cache/%s.res" % rejection_name
cache_state.call("remember_request", normalized_relative_path, cache_path)
adapter.status_by_path[cache_path] = ResourceLoader.THREAD_LOAD_LOADED
adapter.resource_by_path[cache_path] = rejected_resources[rejection_name]
_expect_equal(
int(finalizer.call("poll_terminal_requests", cache_state)),
3,
"all invalid loaded Resources complete",
failures
)
for rejection_name in rejected_resources:
_expect_true(
bool(cache_state.call("is_missing", "world/%s.wmo" % rejection_name)),
"%s Resource publishes missing" % rejection_name,
failures
)
func _verify_current_resource_adoption(failures: Array[String]) -> void:
var current_resource := FakeWmoRenderResource.new()
current_resource.format_version = 2
var newer_resource := FakeWmoRenderResource.new()
newer_resource.format_version = 5
var adapter := FakeResourceLoaderAdapter.new()
var finalizer: RefCounted = FINALIZER_SCRIPT.new(
current_resource.get_script(),
2,
adapter
)
var cache_state: RefCounted = CACHE_STATE_SCRIPT.new()
for resource_name in ["current", "newer"]:
var cache_path := "res://cache/%s.res" % resource_name
cache_state.call(
"remember_request",
"world/%s.wmo" % resource_name,
cache_path
)
adapter.status_by_path[cache_path] = ResourceLoader.THREAD_LOAD_LOADED
adapter.resource_by_path["res://cache/current.res"] = current_resource
adapter.resource_by_path["res://cache/newer.res"] = newer_resource
finalizer.call("poll_terminal_requests", cache_state)
_expect_true(
cache_state.call("resource_for", "world/current.wmo") == current_resource,
"current exact Resource adopted",
failures
)
_expect_true(
cache_state.call("resource_for", "world/newer.wmo") == newer_resource,
"newer exact Resource adopted",
failures
)
func _verify_validation_contract(failures: Array[String]) -> void:
var resource := FakeWmoRenderResource.new()
resource.format_version = 2
var valid_finalizer: RefCounted = FINALIZER_SCRIPT.new(resource.get_script(), 2)
_expect_true(
bool(valid_finalizer.call("is_current_render_resource", resource)),
"exact script and minimum format accepted",
failures
)
var missing_script_finalizer: RefCounted = FINALIZER_SCRIPT.new(null, 2)
_expect_false(
bool(missing_script_finalizer.call("is_current_render_resource", resource)),
"missing expected script rejects Resource",
failures
)
_expect_false(
bool(valid_finalizer.call("is_current_render_resource", null)),
"null Resource rejected",
failures
)
func _verify_source_boundaries(failures: Array[String]) -> void:
var loader_source := _read_text(LOADER_PATH, failures)
var finalizer_source := _read_text(FINALIZER_PATH, failures)
var cache_source := _read_text(CACHE_STATE_PATH, failures)
_expect_true(
loader_source.contains("WMO_RENDER_RESOURCE_FINALIZER_SCRIPT.new("),
"loader composes finalizer",
failures
)
_expect_true(
loader_source.contains("_wmo_render_resource_finalizer.poll_terminal_requests("),
"loader delegates terminal polling",
failures
)
_expect_false(
loader_source.contains("resource.get_script() == WMO_STREAMING_SCRIPT"),
"loader releases script validation",
failures
)
for required_token in [
"ResourceLoader.load_threaded_get_status(resource_path)",
"ResourceLoader.load_threaded_get(resource_path)",
"resource.get_script() == _expected_resource_script",
"complete_request_with_resource",
"complete_request_as_missing",
]:
_expect_true(
finalizer_source.contains(required_token),
"finalizer owns %s" % required_token,
failures
)
_expect_false(
cache_source.contains("ResourceLoader."),
"cache state remains free of ResourceLoader",
failures
)
func _verify_bounded_timing(failures: Array[String]) -> float:
var resource := FakeWmoRenderResource.new()
resource.format_version = 2
var adapter := FakeResourceLoaderAdapter.new()
var finalizer: RefCounted = FINALIZER_SCRIPT.new(
resource.get_script(),
2,
adapter
)
var started_microseconds := Time.get_ticks_usec()
for iteration in range(1000):
var cache_state: RefCounted = CACHE_STATE_SCRIPT.new()
var normalized_relative_path := "world/object_%d.wmo" % iteration
var cache_path := "res://cache/object_%d.res" % iteration
cache_state.call("remember_request", normalized_relative_path, cache_path)
adapter.status_by_path[cache_path] = ResourceLoader.THREAD_LOAD_FAILED
finalizer.call("poll_terminal_requests", cache_state)
var elapsed_milliseconds := float(
Time.get_ticks_usec() - started_microseconds
) / 1000.0
if elapsed_milliseconds >= 1000.0:
failures.append(
"1000 terminal polls took %.3fms (budget < 1000ms)"
% elapsed_milliseconds
)
return elapsed_milliseconds
func _read_text(path: String, failures: Array[String]) -> String:
var file := FileAccess.open(path, FileAccess.READ)
if file == null:
failures.append("cannot read %s" % path)
return ""
var source := file.get_as_text()
file.close()
return source
func _expect_true(value: bool, label: String, failures: Array[String]) -> void:
if not value:
failures.append(label)
func _expect_false(value: bool, label: String, failures: Array[String]) -> void:
if value:
failures.append(label)
func _expect_equal(actual: int, expected: int, label: String, failures: Array[String]) -> void:
if actual != expected:
failures.append("%s: expected %d, got %d" % [label, expected, actual])
func _expect_string_array(
actual: Array,
expected: Array,
label: String,
failures: Array[String]
) -> void:
if actual != expected:
failures.append("%s: expected %s, got %s" % [label, expected, actual])
@@ -0,0 +1 @@
uid://corj7g3e1mush
+15 -6
View File
@@ -41,6 +41,7 @@ Runtime и Editor используют facade; planner/scheduler тестиру
`M03-RND-WMO-RENDER-BUILD-PLANNER-001`, `M03-RND-WMO-RENDER-BUILD-PLANNER-001`,
`M03-RND-WMO-RENDER-BUILD-QUEUE-001`, `M03-RND-WMO-RENDER-BUILD-QUEUE-001`,
`M03-RND-WMO-RENDER-RESOURCE-CACHE-001`, `M03-RND-WMO-RENDER-RESOURCE-CACHE-001`,
`M03-RND-WMO-RENDER-RESOURCE-FINALIZER-001`,
`M03-RND-WMO-SCENE-RESOURCE-CACHE-001`, `M03-RND-WMO-SCENE-RESOURCE-CACHE-001`,
`M03-RND-ADT-WATER-LOAD-PIPELINE-001`, `M03-RND-ADT-WATER-LOAD-PIPELINE-001`,
`M03-RND-ADT-WATER-SCENE-FINALIZER-001`, `M03-RND-ADT-WATER-SCENE-FINALIZER-001`,
@@ -124,6 +125,13 @@ Runtime и Editor используют facade; planner/scheduler тестиру
Post-merge cache state (`51.949ms`), shutdown, WMO queue/resolver, facade, Post-merge cache state (`51.949ms`), shutdown, WMO queue/resolver, facade,
internal-access `41`, `7/7` manifest, documentation and coordination gates internal-access `41`, `7/7` manifest, documentation and coordination gates
remained green. remained green.
WMO render Resource finalizer passed `cases=24 iterations=1000
elapsed_ms=3.938` with insertion-order status polling, non-terminal retention,
failed/null/wrong-script/stale rejection, current/newer exact Resource
adoption and source boundaries. All 60 autonomous headless regressions passed;
internal-access remained `30`, documentation covered `47` module
specifications, coordination retained `34` historical expired-claim warnings
and checkpoint dry-run kept all `7/7` plans.
WMO scene Resource cache state passed `cases=11 iterations=100 WMO scene Resource cache state passed `cases=11 iterations=100
elapsed_ms=40.768`; its initial direct no-`.godot` run passed (`43.636ms`), elapsed_ms=40.768`; its initial direct no-`.godot` run passed (`43.636ms`),
render-cache state, WMO queue/planner/registry/resolver/material/shutdown and render-cache state, WMO queue/planner/registry/resolver/material/shutdown and
@@ -391,8 +399,9 @@ Runtime и Editor используют facade; planner/scheduler тестиру
retains validity checks, materialization, permits and Node destruction. retains validity checks, materialization, permits and Node destruction.
Lightweight WMO render cache paths retain the same cached/pending/missing Lightweight WMO render cache paths retain the same cached/pending/missing
transitions, retry behavior for absent files and map-reset versus final-shutdown transitions, retry behavior for absent files and map-reset versus final-shutdown
lifetime. `ResourceLoader`, script identity and `FORMAT_VERSION` validation lifetime. Terminal `ResourceLoader` polling, exact script identity and
remain loader-owned before the extracted state accepts a Resource. `FORMAT_VERSION` validation now belong to the WMO render Resource finalizer;
loader-owned request admission and shutdown drain order are unchanged.
Cached WMO scene paths retain direct negative caching for missing/oversize/ Cached WMO scene paths retain direct negative caching for missing/oversize/
request-start failures, terminal missing state for load/validation failures and request-start failures, terminal missing state for load/validation failures and
the prior transient-reset versus final-shutdown lifetime. File-size limits, the prior transient-reset versus final-shutdown lifetime. File-size limits,
@@ -473,8 +482,8 @@ Runtime и Editor используют facade; planner/scheduler тестиру
stateful WMO placement registry and global/tile add/release/clear adapters, stateful WMO placement registry and global/tile add/release/clear adapters,
stateless WMO render build step planner and loader cursor-adoption adapter, stateless WMO render build step planner and loader cursor-adoption adapter,
typed WMO render build job/queue and loader enqueue/drain/cancel/clear adapters, typed WMO render build job/queue and loader enqueue/drain/cancel/clear adapters,
lightweight WMO render Resource/missing/request state and loader I/O/validation/ lightweight WMO render Resource/missing/request state and loader lookup/request/
reset adapters, reset adapters, plus terminal Resource polling/validation/publication finalizer,
cached WMO PackedScene/missing/request state and loader file-size/I/O/probe/ cached WMO PackedScene/missing/request state and loader file-size/I/O/probe/
validation/reset adapters, validation/reset adapters,
ADT water pending/task/result pipeline state and loader enqueue/start/publish/ ADT water pending/task/result pipeline state and loader enqueue/start/publish/
@@ -536,8 +545,8 @@ Runtime и Editor используют facade; planner/scheduler тестиру
observers produce typed per-operation state, including native observation; observers produce typed per-operation state, including native observation;
animation and static Mesh terminal ResourceLoader drains are extracted, while animation and static Mesh terminal ResourceLoader drains are extracted, while
action execution, material-prototype lookup and root cleanup remain loader-owned; action execution, material-prototype lookup and root cleanup remain loader-owned;
WMO ResourceLoader/FileAccess I/O, live fallback and group materialization cached-scene WMO ResourceLoader/FileAccess I/O, live fallback and group
remain in the loader; materialization remain in the loader; lightweight render terminal I/O is separated;
asset-backed WMO placement/portal/material/leak/p95/p99 evidence remains pending; asset-backed WMO placement/portal/material/leak/p95/p99 evidence remains pending;
ADT water parsing and active worker interruption remain loader-owned; ADT water parsing and active worker interruption remain loader-owned;
materialization is separated but remains synchronous main-thread work behind materialization is separated but remains synchronous main-thread work behind