From a86f8f2212f7976929355466b770a0a523c185d8 Mon Sep 17 00:00:00 2001 From: sindoring Date: Sat, 18 Jul 2026 14:03:27 +0400 Subject: [PATCH 1/2] render: extract WMO render resource finalizer --- RENDER.md | 22 +- docs/modules/README.md | 1 + .../wmo-render-resource-cache-state.md | 40 +- docs/modules/wmo-render-resource-finalizer.md | 219 +++++++++++ docs/modules/world-renderer.md | 16 +- .../wmo/wmo_render_resource_finalizer.gd | 90 +++++ .../wmo/wmo_render_resource_finalizer.gd.uid | 1 + .../streaming/streaming_world_loader.gd | 32 +- .../verify_wmo_render_resource_cache_state.gd | 8 +- .../verify_wmo_render_resource_finalizer.gd | 356 ++++++++++++++++++ ...erify_wmo_render_resource_finalizer.gd.uid | 1 + targets/03-renderer-facade.md | 21 +- 12 files changed, 751 insertions(+), 56 deletions(-) create mode 100644 docs/modules/wmo-render-resource-finalizer.md create mode 100644 src/render/wmo/wmo_render_resource_finalizer.gd create mode 100644 src/render/wmo/wmo_render_resource_finalizer.gd.uid create mode 100644 src/tools/verify_wmo_render_resource_finalizer.gd create mode 100644 src/tools/verify_wmo_render_resource_finalizer.gd.uid diff --git a/RENDER.md b/RENDER.md index ec58666..cf734c6 100644 --- a/RENDER.md +++ b/RENDER.md @@ -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_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_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/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. @@ -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 Resources, negative entries and normalized-path to pending-cache-path records. -- `StreamingWorldLoader` still constructs cache paths, calls `ResourceLoader`, - polls requests and validates `WMOStreamingResource` script identity plus - `FORMAT_VERSION` before completing cache state. +- `StreamingWorldLoader` still constructs cache paths and starts requests. + `WmoRenderResourceFinalizer` polls terminal requests and validates exact + `WMOStreamingResource` script identity plus `FORMAT_VERSION` before completing + cache state. - Map reset and orderly request draining clear pending/negative state while retaining accepted Resources; final runtime cache release clears all state. - 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 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 - `WmoSceneResourceCacheState` now owns validated cached-WMO PackedScenes, diff --git a/docs/modules/README.md b/docs/modules/README.md index c77f1a5..d54e626 100644 --- a/docs/modules/README.md +++ b/docs/modules/README.md @@ -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 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 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) | | Third-person camera | Implemented | [`third-person-camera.md`](third-person-camera.md) | | Character presentation | Implemented boundary / Partial fidelity | [`character-presentation.md`](character-presentation.md) | diff --git a/docs/modules/wmo-render-resource-cache-state.md b/docs/modules/wmo-render-resource-cache-state.md index 717a9b4..5b6293a 100644 --- a/docs/modules/wmo-render-resource-cache-state.md +++ b/docs/modules/wmo-render-resource-cache-state.md @@ -15,7 +15,8 @@ Own the mutually exclusive cached, missing and pending states for normalized lightweight-WMO render paths outside the monolithic streamer. The state holder 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 @@ -34,7 +35,8 @@ flowchart LR State -->|cached Resource| Loader Loader -->|cache path| ResourceLoader[Godot ResourceLoader] 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 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 | | `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 | -| `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_as_missing(path)` | Command/query | Remove pending request and adopt negative state | Terminal loader poll | Unknown 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 finalizer poll | Unknown returns false | | `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 | | `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 -->|no| Start[Loader starts threaded request] Start --> Remember[remember_request] - Remember --> Poll[Loader polls detached request snapshot] + Remember --> Poll[Finalizer polls detached request snapshot] Poll --> Terminal{Loaded or failed?} Terminal -->|no| Poll 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 -->|invalid| Missing ``` @@ -113,28 +115,31 @@ stateDiagram-v2 ```mermaid sequenceDiagram participant Loader as StreamingWorldLoader + participant Finalizer as WmoRenderResourceFinalizer participant State as WmoRenderResourceCacheState participant RL as ResourceLoader Loader->>State: cached/missing/pending queries Loader->>RL: exists + load_threaded_request(cache path) Loader->>State: remember_request(normalized, cache path) loop renderer tick - Loader->>State: request_paths_snapshot() - Loader->>RL: load_threaded_get_status(cache path) + Loader->>Finalizer: poll_terminal_requests(State) + Finalizer->>State: request_paths_snapshot() + Finalizer->>RL: load_threaded_get_status(cache path) end alt load failed - Loader->>State: complete_request_as_missing(normalized) + Finalizer->>State: complete_request_as_missing(normalized) else loaded - Loader->>RL: load_threaded_get(cache path) - Loader->>Loader: validate script and FORMAT_VERSION - Loader->>State: complete with Resource or as missing + Finalizer->>RL: load_threaded_get(cache path) + Finalizer->>Finalizer: validate script and FORMAT_VERSION + Finalizer->>State: complete with Resource or as missing end ``` ## Ownership, threading and 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. - No mutex or callback is needed; detached request snapshots permit safe removal. - 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 The state is runtime-only and serializes nothing. `WMOStreamingResource` script -identity and `FORMAT_VERSION` validation remain unchanged in the loader; no cache -migration or rebuild is introduced by this extraction. +identity and `FORMAT_VERSION` validation remain unchanged in the finalizer; no +cache migration or rebuild is introduced by this extraction. ## 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 | |---|---|---|---| | 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 | | 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 | |---|---| | `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_render_runtime_cache_shutdown.gd` | Loader final Resource ownership regression | diff --git a/docs/modules/wmo-render-resource-finalizer.md b/docs/modules/wmo-render-resource-finalizer.md new file mode 100644 index 0000000..01bd060 --- /dev/null +++ b/docs/modules/wmo-render-resource-finalizer.md @@ -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) diff --git a/docs/modules/world-renderer.md b/docs/modules/world-renderer.md index fc45b05..8efc031 100644 --- a/docs/modules/world-renderer.md +++ b/docs/modules/world-renderer.md @@ -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 | | `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 | +| `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 | | `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 | @@ -412,10 +413,12 @@ sequenceDiagram and cursor plan. `WmoRenderBuildQueue` owns typed pending jobs, FIFO keys and strong root/resource references without freeing engine objects. `WmoRenderResourceCacheState` owns validated render Resources, negative entries - and pending cache paths. `WmoSceneResourceCacheState` similarly owns validated - PackedScenes, negative entries and pending `.tscn` paths. The loader retains - ResourceLoader/FileAccess I/O, size and cache-version validation, live fallback, - materialization, permits, validity reactions and every Node lifecycle action. + and pending cache paths; `WmoRenderResourceFinalizer` owns its terminal + ResourceLoader polling and script/format validation. `WmoSceneResourceCacheState` + similarly owns validated PackedScenes, negative entries and pending `.tscn` + 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 active task IDs and the worker-safe parsed-result mailbox. The loader retains 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 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 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 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 | @@ -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_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/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_policy.gd` | Immutable renderer radius/prefetch policy | | `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_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_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_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 | diff --git a/src/render/wmo/wmo_render_resource_finalizer.gd b/src/render/wmo/wmo_render_resource_finalizer.gd new file mode 100644 index 0000000..28d4aa5 --- /dev/null +++ b/src/render/wmo/wmo_render_resource_finalizer.gd @@ -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) diff --git a/src/render/wmo/wmo_render_resource_finalizer.gd.uid b/src/render/wmo/wmo_render_resource_finalizer.gd.uid new file mode 100644 index 0000000..135371d --- /dev/null +++ b/src/render/wmo/wmo_render_resource_finalizer.gd.uid @@ -0,0 +1 @@ +uid://dtvba6g1grttk diff --git a/src/scenes/streaming/streaming_world_loader.gd b/src/scenes/streaming/streaming_world_loader.gd index c273d0b..440e8dc 100644 --- a/src/scenes/streaming/streaming_world_loader.gd +++ b/src/scenes/streaming/streaming_world_loader.gd @@ -24,6 +24,9 @@ const WMO_RENDER_BUILD_QUEUE_SCRIPT := preload( const WMO_RENDER_RESOURCE_CACHE_STATE_SCRIPT := preload( "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( "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 _wmo_prototype_cache: Dictionary = {} 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_scene_resource_cache_state := WMO_SCENE_RESOURCE_CACHE_STATE_SCRIPT.new() var _wmo_missing_cache: Dictionary = {} @@ -3725,28 +3732,9 @@ func _drain_wmo_scene_loads() -> void: func _drain_wmo_render_loads() -> void: - var request_paths := _wmo_render_resource_cache_state.request_paths_snapshot() - for normalized_rel_variant in request_paths.keys(): - 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) + _wmo_render_resource_finalizer.poll_terminal_requests( + _wmo_render_resource_cache_state + ) func _get_wmo_render_or_request(rel_path: String) -> Resource: diff --git a/src/tools/verify_wmo_render_resource_cache_state.gd b/src/tools/verify_wmo_render_resource_cache_state.gd index efadc27..b4bfea9 100644 --- a/src/tools/verify_wmo_render_resource_cache_state.gd +++ b/src/tools/verify_wmo_render_resource_cache_state.gd @@ -7,6 +7,7 @@ const CACHE_STATE_SCRIPT := preload( "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" @@ -209,6 +210,7 @@ func _verify_detached_sorted_diagnostics(failures: Array[String]) -> void: func _verify_ownership_boundaries(failures: Array[String]) -> void: var loader_source := _read_text(LOADER_PATH, failures) var cache_state_source := _read_text(CACHE_STATE_PATH, failures) + var finalizer_source := _read_text(FINALIZER_PATH, failures) _expect_true( loader_source.contains("WMO_RENDER_RESOURCE_CACHE_STATE_SCRIPT.new()"), "loader composes cache state", @@ -241,13 +243,13 @@ func _verify_ownership_boundaries(failures: Array[String]) -> void: failures ) _expect_true( - loader_source.contains("resource.get_script() == WMO_STREAMING_SCRIPT"), - "loader retains script validation", + finalizer_source.contains("resource.get_script() == _expected_resource_script"), + "finalizer owns script validation", failures ) _expect_true( loader_source.contains("WMO_STREAMING_SCRIPT.FORMAT_VERSION"), - "loader retains cache format validation", + "loader injects cache format version", failures ) diff --git a/src/tools/verify_wmo_render_resource_finalizer.gd b/src/tools/verify_wmo_render_resource_finalizer.gd new file mode 100644 index 0000000..5d0cedf --- /dev/null +++ b/src/tools/verify_wmo_render_resource_finalizer.gd @@ -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]) diff --git a/src/tools/verify_wmo_render_resource_finalizer.gd.uid b/src/tools/verify_wmo_render_resource_finalizer.gd.uid new file mode 100644 index 0000000..9d84538 --- /dev/null +++ b/src/tools/verify_wmo_render_resource_finalizer.gd.uid @@ -0,0 +1 @@ +uid://corj7g3e1mush diff --git a/targets/03-renderer-facade.md b/targets/03-renderer-facade.md index 1095ea8..c16964d 100644 --- a/targets/03-renderer-facade.md +++ b/targets/03-renderer-facade.md @@ -41,6 +41,7 @@ Runtime и Editor используют facade; planner/scheduler тестиру `M03-RND-WMO-RENDER-BUILD-PLANNER-001`, `M03-RND-WMO-RENDER-BUILD-QUEUE-001`, `M03-RND-WMO-RENDER-RESOURCE-CACHE-001`, + `M03-RND-WMO-RENDER-RESOURCE-FINALIZER-001`, `M03-RND-WMO-SCENE-RESOURCE-CACHE-001`, `M03-RND-ADT-WATER-LOAD-PIPELINE-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, internal-access `41`, `7/7` manifest, documentation and coordination gates 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 elapsed_ms=40.768`; its initial direct no-`.godot` run passed (`43.636ms`), 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. Lightweight WMO render cache paths retain the same cached/pending/missing transitions, retry behavior for absent files and map-reset versus final-shutdown - lifetime. `ResourceLoader`, script identity and `FORMAT_VERSION` validation - remain loader-owned before the extracted state accepts a Resource. + lifetime. Terminal `ResourceLoader` polling, exact script identity and + `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/ request-start failures, terminal missing state for load/validation failures and 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, stateless WMO render build step planner and loader cursor-adoption adapter, 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/ - reset adapters, + lightweight WMO render Resource/missing/request state and loader lookup/request/ + reset adapters, plus terminal Resource polling/validation/publication finalizer, cached WMO PackedScene/missing/request state and loader file-size/I/O/probe/ validation/reset adapters, 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; animation and static Mesh terminal ResourceLoader drains are extracted, while action execution, material-prototype lookup and root cleanup remain loader-owned; - WMO ResourceLoader/FileAccess I/O, live fallback and group materialization - remain in the loader; + cached-scene WMO ResourceLoader/FileAccess I/O, live fallback and group + materialization remain in the loader; lightweight render terminal I/O is separated; asset-backed WMO placement/portal/material/leak/p95/p99 evidence remains pending; ADT water parsing and active worker interruption remain loader-owned; materialization is separated but remains synchronous main-thread work behind From 5a1cd9d7c805d5db57c748aaa8c310bd76b2b585 Mon Sep 17 00:00:00 2001 From: sindoring Date: Sat, 18 Jul 2026 14:03:41 +0400 Subject: [PATCH 2/2] coordination: hand off WMO render resource finalizer --- ...3-RND-WMO-RENDER-RESOURCE-FINALIZER-001.md | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/coordination/claims/M03-RND-WMO-RENDER-RESOURCE-FINALIZER-001.md b/coordination/claims/M03-RND-WMO-RENDER-RESOURCE-FINALIZER-001.md index 645c98a..76869f2 100644 --- a/coordination/claims/M03-RND-WMO-RENDER-RESOURCE-FINALIZER-001.md +++ b/coordination/claims/M03-RND-WMO-RENDER-RESOURCE-FINALIZER-001.md @@ -54,14 +54,21 @@ validation and Resource/missing publication from the loader into one service. ## Status -- State: active -- Done: ownership/compatibility contract -- Next: implementation, verification and documentation +- State: ready +- Done: implementation, verification and documentation +- Next: merge and post-merge acceptance - Blocked by: ## Handoff -- Commit: -- Results: -- Remaining risks: -- Documentation updated: +- Commit: `a86f8f2` (`render: extract WMO render resource finalizer`) +- Results: finalizer PASS `cases=24 iterations=1000 elapsed_ms=3.938`; + adjacent WMO cache/queue/planner/registry/resolver/scene-cache/shutdown checks + passed; autonomous headless suite `60/60`; documentation `47`; coordination + passed with `34` historical warnings; checkpoint dry-run `7/7`. +- Remaining risks: terminal ResourceLoader polling remains a synchronous + main-thread boundary; no serialized private WMO, corrupt-cache, visual, leak, + descriptor-pressure or p95/p99 evidence was added. +- Documentation updated: new full module specification with API/I/O and + data-flow/state/sequence/dependency diagrams; adjacent WMO cache/world-renderer + specs, module registry, `RENDER.md` and M03 Evidence.