Compare commits

..

78 Commits

Author SHA1 Message Date
sindoring 1fd87b3132 обновление 2026-09-05 13:02:03 +04:00
sindoring bc4440562d обновление документации и сабмодулей 2026-08-12 15:53:15 +04:00
sindoring 5b73ddadc9 обновление аудита 2026-08-12 14:15:22 +04:00
sindoring a11eb50219 актуализация референсов 2026-08-12 14:02:08 +04:00
sindoring adc441ffca coord(M04): accept renderer roadmap migration 2026-08-02 17:42:18 +04:00
sindoring afbc4faab3 coord(M04): hand off renderer roadmap migration 2026-08-02 17:41:41 +04:00
sindoring f77f50c0f9 docs(M04): prioritize renderer fidelity milestone
Work-Package: M04-RND-FIDELITY-ROADMAP-001
Agent: sindo-main-codex-renderer-roadmap
Tests: coordination, documentation, target sequence, old-link and diff gates pass
Fidelity: original build-12340 captures are authoritative; Noggit is secondary reference
2026-08-02 17:41:18 +04:00
sindoring 4369dd3c91 coord(M04): claim renderer fidelity roadmap migration 2026-08-02 15:44:52 +04:00
sindoring fe6d0deab9 Record M03 closeout integration 2026-08-02 15:29:34 +04:00
sindoring 203d40bd0e Mark M03 renderer milestone complete 2026-08-02 15:28:40 +04:00
sindoring 3bf1c2c657 Complete M03 renderer closeout validation 2026-08-02 15:28:10 +04:00
sindoring d9d20cb53f qar: add M03 renderer closeout gates 2026-08-01 16:49:43 +04:00
sindoring ef6d324b4f coordination: claim M03 integrator closeout 2026-08-01 14:54:00 +04:00
sindoring f978a16806 targets: accept WMO scene instance factory
Work-Package: M03-RND-WMO-SCENE-INSTANCE-FACTORY-001
Agent: sindo-main-codex
Tests: post-merge WMO/placement/facade/internal-access/manifest/docs/coordination smoke passed
Fidelity: valid cached/live behavior preserved; invalid-root lifetime leak fixed
2026-08-01 10:51:35 +04:00
sindoring 541279ed45 merge: WMO scene instance factory
Work-Package: M03-RND-WMO-SCENE-INSTANCE-FACTORY-001
Agent: sindo-main-codex
Tests: 65/66 headless verifiers; proprietary ADT probe unavailable; checkpoint dry-run 7/7
Fidelity: valid cached/live behavior preserved; invalid-root leak fixed
2026-08-01 10:50:26 +04:00
sindoring 8da41dc17c coordination: hand off WMO scene instance factory
Work-Package: M03-RND-WMO-SCENE-INSTANCE-FACTORY-001
Agent: sindo-main-codex
Tests: documented in handoff
Fidelity: valid cached/live behavior retained; invalid-root leak fixed
2026-08-01 10:50:16 +04:00
sindoring 7e97b19095 render: extract WMO scene instance factory
Work-Package: M03-RND-WMO-SCENE-INSTANCE-FACTORY-001
Agent: sindo-main-codex
Tests: 65/66 headless verifiers; proprietary ADT probe unavailable; checkpoint dry-run 7/7; docs and coordination passed
Fidelity: valid cached/live naming, placement, validation and Resource identity preserved; invalid-root leak fixed
2026-08-01 10:49:56 +04:00
sindoring 07521ee6a4 coordination: claim WMO scene instance factory
Work-Package: M03-RND-WMO-SCENE-INSTANCE-FACTORY-001
Agent: sindo-main-codex
Tests: not run (coordination only)
Fidelity: behavior-preserving extraction planned
2026-08-01 10:42:14 +04:00
sindoring f8348cf0cb targets: accept WMO runtime scene preparer
Work-Package: M03-RND-WMO-RUNTIME-SCENE-PREPARER-001
Agent: sindo-main-codex
Tests: post-merge WMO/facade/internal-access/manifest/docs/coordination smoke passed
Fidelity: cached/live, occluder and shadow behavior preserved; asset-backed evidence pending
2026-08-01 10:38:59 +04:00
sindoring 57d0a9f8bd merge: WMO runtime scene preparer
Work-Package: M03-RND-WMO-RUNTIME-SCENE-PREPARER-001
Agent: sindo-main-codex
Tests: 64/65 headless verifiers; proprietary ADT probe unavailable; checkpoint dry-run 7/7
Fidelity: preserves cached/live preparation, occluder and shadow semantics
2026-08-01 10:37:53 +04:00
sindoring 73b30ca699 coordination: hand off WMO runtime scene preparer
Work-Package: M03-RND-WMO-RUNTIME-SCENE-PREPARER-001
Agent: sindo-main-codex
Tests: documented in handoff
Fidelity: exact cached/live preparation behavior retained
2026-08-01 10:37:45 +04:00
sindoring ffed91c364 render: extract WMO runtime scene preparer
Work-Package: M03-RND-WMO-RUNTIME-SCENE-PREPARER-001
Agent: sindo-main-codex
Tests: 64/65 headless verifiers passed; proprietary ADT probe unavailable; checkpoint dry-run 7/7; docs and coordination passed
Fidelity: preserves cached/live preparation distinction, direct occluder policy and shadow semantics
2026-08-01 10:37:23 +04:00
sindoring cfa3dc1009 coordination: claim WMO runtime scene preparer
Work-Package: M03-RND-WMO-RUNTIME-SCENE-PREPARER-001
Agent: sindo-main-codex
Tests: not run (coordination only)
Fidelity: behavior-preserving extraction planned
2026-08-01 10:28:50 +04:00
sindoring 57d3330944 targets: accept WMO render group materializer
Work-Package: M03-RND-WMO-RENDER-GROUP-MATERIALIZER-001
Agent: sindo-main-codex
Tests: post-merge WMO/facade/internal-access/manifest/docs/coordination smoke passed
Fidelity: behavior-preserving extraction; asset-backed visual evidence remains pending
2026-08-01 10:25:15 +04:00
sindoring 705354dc14 merge: WMO render group materializer
Work-Package: M03-RND-WMO-RENDER-GROUP-MATERIALIZER-001
Agent: sindo-main-codex
Tests: 63/64 autonomous headless verifiers; proprietary ADT probe unavailable; checkpoint dry-run 7/7
Fidelity: behavior-preserving WMO group materialization extraction
2026-08-01 10:22:58 +04:00
sindoring 6deb80c0dd coordination: hand off WMO render group materializer
Work-Package: M03-RND-WMO-RENDER-GROUP-MATERIALIZER-001
Agent: sindo-main-codex
Tests: documented in handoff
Fidelity: exact behavior-preserving extraction
2026-08-01 10:22:44 +04:00
sindoring f4d5e29cc9 render: extract WMO render group materializer
Work-Package: M03-RND-WMO-RENDER-GROUP-MATERIALIZER-001
Agent: sindo-main-codex
Tests: 63/64 autonomous verifiers passed; proprietary ADT probe unavailable; baseline dry-run 7/7; documentation and coordination gates passed
Fidelity: exact behavior-preserving node materialization extraction; no new 3.3.5a parity claim
2026-08-01 10:22:21 +04:00
sindoring 543ee1572b coordination: claim WMO render group materializer
Work-Package: M03-RND-WMO-RENDER-GROUP-MATERIALIZER-001
Agent: sindo-main-codex
Tests: not run (coordination only)
Fidelity: behavior-preserving extraction planned
2026-08-01 10:11:29 +04:00
sindoring f35dcf3a09 targets: accept WMO runtime Mesh finalizer 2026-07-18 14:35:39 +04:00
sindoring d65ebee57f merge: WMO runtime Mesh finalizer 2026-07-18 14:34:02 +04:00
sindoring a59086fdf0 coordination: hand off WMO runtime Mesh finalizer 2026-07-18 14:33:57 +04:00
sindoring ae40e3f1d4 render: extract WMO runtime Mesh finalizer 2026-07-18 14:33:48 +04:00
sindoring 49e70260ed coordination: claim WMO runtime Mesh finalizer 2026-07-18 14:27:17 +04:00
sindoring 406dc464c3 targets: accept WMO scene resource finalizer 2026-07-18 14:18:29 +04:00
sindoring 6a0f9bd3ba merge: WMO scene resource finalizer 2026-07-18 14:17:09 +04:00
sindoring 3525cc0ead coordination: hand off WMO scene resource finalizer 2026-07-18 14:17:04 +04:00
sindoring 7779e9e75f render: extract WMO scene resource finalizer 2026-07-18 14:16:49 +04:00
sindoring 9ba7ca3263 coordination: claim WMO scene resource finalizer 2026-07-18 14:08:02 +04:00
sindoring 8c54313f7d targets: accept WMO render resource finalizer 2026-07-18 14:04:51 +04:00
sindoring 1acddab8a5 merge: WMO render resource finalizer 2026-07-18 14:03:48 +04:00
sindoring 5a1cd9d7c8 coordination: hand off WMO render resource finalizer 2026-07-18 14:03:41 +04:00
sindoring a86f8f2212 render: extract WMO render resource finalizer 2026-07-18 14:03:27 +04:00
sindoring fb7c9f174e coordination: claim WMO render resource finalizer 2026-07-18 13:55:50 +04:00
sindoring f1a7400ed1 targets: accept M2 Mesh resource finalizer 2026-07-18 13:52:55 +04:00
sindoring 34b700051f merge: M2 Mesh resource finalizer 2026-07-18 13:51:25 +04:00
sindoring 71a1012779 coordination: hand off M2 Mesh resource finalizer 2026-07-18 13:51:21 +04:00
sindoring ddeb708c08 render: extract M2 Mesh resource finalizer 2026-07-18 13:51:08 +04:00
sindoring c328d86554 coordination: claim M2 Mesh resource finalizer 2026-07-18 13:38:02 +04:00
sindoring 75ddf6a2ed targets: accept M2 animation resource finalizer 2026-07-18 13:31:34 +04:00
sindoring 06f6394043 merge: M2 animation resource finalizer 2026-07-18 13:30:43 +04:00
sindoring b20f0d7f6f coordination: hand off M2 animation resource finalizer 2026-07-18 13:30:39 +04:00
sindoring e7cd967dce render: extract M2 animation resource finalizer 2026-07-18 13:30:24 +04:00
sindoring ff952da7d8 coordination: claim M2 animation resource finalizer 2026-07-18 13:18:53 +04:00
sindoring 70729bb341 targets: accept native M2 animation resource observer 2026-07-18 10:57:18 +04:00
sindoring d37c799850 merge: native M2 animation resource observer 2026-07-18 10:56:08 +04:00
sindoring c24c3f159c coordination: hand off native M2 animation resource observer 2026-07-18 10:56:04 +04:00
sindoring 1cb0101a73 render: extract native M2 animation resource observer 2026-07-18 10:55:47 +04:00
sindoring d22a9cd743 coordination: claim native M2 animation resource observer 2026-07-18 10:42:13 +04:00
sindoring 6f385ed261 targets: accept cached M2 animation resource observer 2026-07-18 10:24:30 +04:00
sindoring f79e064d25 merge: cached M2 animation resource observer 2026-07-18 10:23:29 +04:00
sindoring 6a23c1b996 coordination: hand off cached M2 animation resource observer 2026-07-18 10:23:22 +04:00
sindoring df87619220 render: extract cached M2 animation resource observer 2026-07-18 10:23:03 +04:00
sindoring 3519f183bb coordination: claim cached M2 animation resource observer 2026-07-18 10:10:19 +04:00
sindoring 5ebf4de2ff targets: accept static M2 build resource observer 2026-07-18 03:04:01 +04:00
sindoring a043c79654 merge: static M2 build resource observer 2026-07-18 03:02:06 +04:00
sindoring 194b64d030 coordination: hand off static M2 build resource observer 2026-07-18 03:02:02 +04:00
sindoring 7cb3e3412f render: extract static M2 build resource observer 2026-07-18 03:01:43 +04:00
sindoring f062ea91cd coordination: claim static M2 build resource observer 2026-07-18 02:51:46 +04:00
sindoring 032a256e70 targets: accept M2 build resource snapshot 2026-07-18 02:48:54 +04:00
sindoring 4354834c50 merge: M2 build resource snapshot 2026-07-18 02:47:35 +04:00
sindoring 41b3b63215 coordination: hand off M2 build resource snapshot 2026-07-18 02:47:26 +04:00
sindoring ec1b90f1e4 render: add M2 build resource snapshot 2026-07-18 02:47:10 +04:00
sindoring 0decd10e09 coordination: claim M2 build resource snapshot 2026-07-18 02:38:46 +04:00
sindoring b113db01cd targets: accept M2 build dispatch planner 2026-07-18 02:35:49 +04:00
sindoring 99a90ddfb3 merge: M2 build dispatch planner 2026-07-18 02:34:36 +04:00
sindoring ed71b36aec coordination: hand off M2 build dispatch planner 2026-07-18 02:34:31 +04:00
sindoring 17f5cc0faa render: extract M2 build dispatch planner 2026-07-18 02:34:18 +04:00
sindoring 1fb566f9bc coordination: claim M2 build dispatch planner 2026-07-18 02:27:16 +04:00
158 changed files with 12893 additions and 1002 deletions
+28
View File
@@ -5,3 +5,31 @@
path = third_party/godot-cpp path = third_party/godot-cpp
url = https://github.com/godotengine/godot-cpp url = https://github.com/godotengine/godot-cpp
branch = 4.5 branch = 4.5
[submodule "reference/open-realm"]
path = reference/open-realm
url = https://github.com/corepunch/open-realm.git
branch = main
[submodule "reference/whoa"]
path = reference/whoa
url = https://github.com/whoahq/whoa
branch = master
[submodule "reference/WoWee"]
path = reference/WoWee
url = https://github.com/Kelsidavis/WoWee.git
branch = master
[submodule "reference/WowUnreal"]
path = reference/WowUnreal
url = https://github.com/Clancey/WowUnreal
branch = main
[submodule "reference/wow.export"]
path = reference/wow.export
url = https://github.com/Kruithne/wow.export.git
branch = main
[submodule "reference/blender-wow-studio/pywowlib"]
path = reference/blender-wow-studio-3.4-1.1.0_Experimental/io_scene_wmo/pywowlib
url = https://github.com/wowdev/pywowlib.git
branch = master
[submodule "reference/benilla"]
path = reference/benilla
url = https://github.com/samwhosung/benilla.git
branch = main
+252 -16
View File
@@ -32,6 +32,8 @@ 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_finalizer.gd` - cached WMO terminal polling, PackedScene probe validation/lifetime and scene/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.
@@ -41,7 +43,13 @@ Paired run 2026-07-11 подтвердил крупный coordinate/placement g
- `src/render/m2/m2_animated_instance_materializer.gd` - main-thread animated instance duplication, render settings, playback startup and non-empty batch attachment. - `src/render/m2/m2_animated_instance_materializer.gd` - main-thread animated instance duplication, render settings, playback startup and non-empty batch attachment.
- `src/render/m2/m2_static_batch_materializer.gd` - main-thread static M2 MultiMesh construction, render settings and attachment. - `src/render/m2/m2_static_batch_materializer.gd` - main-thread static M2 MultiMesh construction, render settings and attachment.
- `src/render/m2/m2_build_queue.gd` / `m2_build_job.gd` - typed pending M2 jobs, FIFO/stale tile keys, grouped-transform references and progress cursors without engine destruction. - `src/render/m2/m2_build_queue.gd` / `m2_build_job.gd` - typed pending M2 jobs, FIFO/stale tile keys, grouped-transform references and progress cursors without engine destruction.
- `src/render/m2/m2_build_dispatch_planner.gd` - pure animation/static wait, materialization and missing-model advance decision.
- `src/render/m2/m2_build_resource_snapshot.gd` - typed per-step animated/static resource observations without engine destruction.
- `src/render/m2/m2_static_build_resource_observer.gd` - static Mesh cache lookup, threaded request selection and missing transition.
- `src/render/m2/m2_cached_animation_resource_observer.gd` - cached animated GLB eligibility, threaded request admission and snapshot production.
- `src/render/m2/m2_animation_load_pipeline_state.gd` - animated M2 threaded-load request records and completion-order finalize FIFO without I/O or Node ownership. - `src/render/m2/m2_animation_load_pipeline_state.gd` - animated M2 threaded-load request records and completion-order finalize FIFO without I/O or Node ownership.
- `src/render/m2/m2_animation_resource_finalizer.gd` - cached animated M2 terminal status polling, Resource load, scene finalization and prototype/static-only outcome.
- `src/render/m2/m2_mesh_resource_finalizer.gd` - static M2 terminal status polling, Mesh extraction/preparation and cache/missing outcome.
- `src/render/m2/m2_mesh_load_pipeline_state.gd` - static M2 threaded-load request records, terminal statuses and completion-order finalize FIFO without I/O or Mesh ownership. - `src/render/m2/m2_mesh_load_pipeline_state.gd` - static M2 threaded-load request records, terminal statuses and completion-order finalize FIFO without I/O or Mesh ownership.
- `src/render/m2/m2_mesh_resource_cache_state.gd` - normalized-path prepared static M2 Mesh references with final-shutdown lifetime. - `src/render/m2/m2_mesh_resource_cache_state.gd` - normalized-path prepared static M2 Mesh references with final-shutdown lifetime.
- `src/render/m2/m2_mesh_resource_extractor.gd` - first-Mesh selection from direct/PackedScene/Node inputs with temporary instance cleanup. - `src/render/m2/m2_mesh_resource_extractor.gd` - first-Mesh selection from direct/PackedScene/Node inputs with temporary instance cleanup.
@@ -325,7 +333,7 @@ Native M2 animation first pass for composite doodads:
- `M2NativeAnimator` evaluates the selected Stand sequence and applies WoW-style bone matrices: - `M2NativeAnimator` evaluates the selected Stand sequence and applies WoW-style bone matrices:
`T(pivot + translation) * R * S * T(-pivot) * parent`. `T(pivot + translation) * R * S * T(-pivot) * parent`.
- Vertex influences are resolved through `.skin` local bone indices and the M2 `boneCombos` palette, matching the original section/batch renderer model used by WoW/whoa. - Vertex influences are resolved through `.skin` local bone indices and the M2 `boneCombos` palette, matching the original section/batch renderer model used by WoW/whoa.
- `StreamingWorldLoader` routes only `gryphonroost` through this native path; simple critters still use the existing GLB allowlist and most M2 world props stay on static MultiMesh. - `M2NativeAnimationResourceObserver` routes only `gryphonroost` through this native path; simple critters still use the existing GLB allowlist and most M2 world props stay on static MultiMesh.
- Current implementation rebuilds the animated `ArrayMesh` on CPU. This is correct enough for the problematic rare doodads and gives us the same data layout that can later move to shader/GPU skinning. - Current implementation rebuilds the animated `ArrayMesh` on CPU. This is correct enough for the problematic rare doodads and gives us the same data layout that can later move to shader/GPU skinning.
Причина: композитные doodad вроде `GryphonRoost` снова ломают визуал при GLB-анимации. До M2-native renderer все world placement M2 должны оставаться статическими. GLB-анимация оставлена только как вручную включаемый debug experiment через allowlist. Причина: композитные doodad вроде `GryphonRoost` снова ломают визуал при GLB-анимации. До M2-native renderer все world placement M2 должны оставаться статическими. GLB-анимация оставлена только как вручную включаемый debug experiment через allowlist.
@@ -678,6 +686,42 @@ SKYBOX_MODEL ...
- полноценный liquid rendering там не реализован; - полноценный liquid rendering там не реализован;
- skybox/liquid не стоит напрямую переносить как готовый код. - skybox/liquid не стоит напрямую переносить как готовый код.
По WoWee (reviewed update `626243e937fb93965fa583a6507ed5a1aa7dda4b` →
`607ea3b8369851014721416293f8e95dfbe64eec`, 2026-09-05):
- сильнейший M04 signal — не новый renderer целиком, а узкие regression fixtures:
M2 global-sequence/color-alpha sampling, `$FSD` event timing, 8x8 liquid masks,
rotated WMO liquid projection и CPU vertex/shader interface checks;
- общий M2+skin/external-`.anim` resolver и selective animation loading полезны как
decomposition, но должны использовать существующие OpenWC repository/worker/
main-thread finalization boundaries;
- track sampler пока линейно обрабатывает Hermite/Bezier, а particle/ribbon path
содержит flame/smoke и orientation heuristics; это не fidelity oracle. Для
effect architecture Benilla остаётся более полным secondary reference;
- placement rotation order для наклонённых doodads в WoWee явно не решён.
OpenWC не меняет calibrated MDDF/MODF transforms без tilted build-12340 fixture;
- real-asset tests, которые превращают отсутствие assets в success, не считаются
evidence. OpenWC fixture обязан иметь provenance/hash и явно fail/skip-report;
- modified MIT license WoWee запрещает использование как основы/компонента
commercial video game без разрешения: используем только независимо проверенные
идеи и tests, не копируем/не вендорим код без legal review.
По Benilla (pinned research commit
`bc1a2428dd7e00ca8abbfb8f0bf53750dae7b123`):
- полезна граница `world renderer -> asset/formats` без зависимости от game/UI;
- M2 animation разделяет selection policy и renderer machinery: pose sampling,
parent-order composition, global sequences, billboard replacement, attachment
anchors и palette upload;
- particles и ribbons симулируются на CPU для temporal fidelity, но записывают
геометрию в один shared effect vertex/index stream с сортировкой, batching и
camera-relative upload вместо отдельных dynamic Mesh/Material на emitter;
- WMO portal flood, material pass ordering, fog/blend policy, effect lifecycle и
corpus/probe tooling являются полезными sources для M04 fixtures;
- это Vanilla 1.12.1/Bevy reference, не build-12340 oracle: record layouts,
shader flags, effect timing и performance должны быть повторно проверены в
Godot против оригинального WoW 3.3.5a.
По WoW 3.3.5a: По WoW 3.3.5a:
- старый клиент не рендерил все как modern physically based renderer; - старый клиент не рендерил все как modern physically based renderer;
@@ -1058,6 +1102,39 @@ $exe = Join-Path $env:TEMP 'godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe'
- Cache formats, quality profiles, batching output and visible rules are unchanged. - Cache formats, quality profiles, batching output and visible rules are unchanged.
Asset-backed p95/p99 and spatial-cell batching evidence remain pending. Asset-backed p95/p99 and spatial-cell batching evidence remain pending.
## 2026-07-18 M2 Build Dispatch Planner Extraction
- `M2BuildDispatchPlanner` now owns the pure action priority between pending
animation, animated materialization, pending/ready static Mesh and terminally
missing-model advancement.
- Pending animation still wins before batch planning. Unresolved static Meshes
still rotate and consume one `M2_BUILD` permit without advancing progress.
- Empty batches advance without serial change; animated, static and terminally
missing positive batches retain the historical serial increment.
- Resource lookup/request order, queue/cursor adoption, materialization, permits,
Node lifetime, cache formats, profiles and visible rules remain unchanged.
## 2026-07-18 M2 Build Resource Snapshot Extraction
- `M2BuildResourceSnapshot` now carries one build-step normalized path, optional
animated prototype, pending-animation state, optional static Mesh and terminal
missing-model state through typed accessors.
- Animation observation is captured first. Static observation is adopted only
for a positive non-animated batch after animation is no longer pending.
- The dispatch planner consumes the snapshot; the loader borrows the selected
prototype/Mesh for the existing materializer calls.
- ResourceLoader/cache requests, permit/cursor transitions, engine lifetime,
cache formats, profiles and visible rules remain unchanged.
## 2026-07-18 M2 Static Build Resource Observer Extraction
- `M2StaticBuildResourceObserver` now owns prepared-Mesh lookup, existing-request
detection, `.tscn`-before-`.glb` candidate selection, pivot-prefix GLB rejection,
threaded request admission and terminal missing transition for build jobs.
- The observer fills `M2BuildResourceSnapshot`; the loader retains animation
observation, finalize drains, materialization, permits and engine lifetime.
- Candidate order, request errors, cache formats, profiles and visuals are unchanged.
## 2026-07-17 M2 Runtime Mesh Rebuild Classifier Extraction ## 2026-07-17 M2 Runtime Mesh Rebuild Classifier Extraction
- `M2RuntimeMeshRebuildClassifier` now owns the memoized decision used when a - `M2RuntimeMeshRebuildClassifier` now owns the memoized decision used when a
@@ -1098,12 +1175,70 @@ $exe = Join-Path $env:TEMP 'godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe'
order. All three historical `m2_animation` metrics still count both stages. order. All three historical `m2_animation` metrics still count both stages.
- Shutdown still drains pending ResourceLoader paths before clear; map reset and - Shutdown still drains pending ResourceLoader paths before clear; map reset and
shutdown preserve the same two clear sites. shutdown preserve the same two clear sites.
- `StreamingWorldLoader` retains GLB eligibility/cache selection, every - At this extraction stage, `StreamingWorldLoader` retained GLB eligibility/cache selection, every
ResourceLoader call, `M2_ANIMATION_FINALIZE` permits, scene instantiation, ResourceLoader call, `M2_ANIMATION_FINALIZE` permits, scene instantiation,
material repair and prototype/static-fallback decisions. material repair and prototype/static-fallback decisions.
- Cache formats, animation behavior and visible output are unchanged. Synthetic - Cache formats, animation behavior and visible output are unchanged. Synthetic
timing is not asset-backed I/O, leak, animation-fidelity or p95/p99 evidence. timing is not asset-backed I/O, leak, animation-fidelity or p95/p99 evidence.
## 2026-07-18 M2 Cached Animation Resource Observer Extraction
- `M2CachedAnimationResourceObserver` now owns cached animated-prototype lookup,
allow/deny matching, historical `.glb` candidate selection, GLB animation/
primitive/schema safety validation, request admission and static-only fallback.
- The observer produces `M2BuildResourceSnapshot` with the exact borrowed cached
prototype or pending state and retains/frees no engine object.
- Native GryphonRoost raw-data build/debug logging belongs to the sibling native
observer; terminal polling, finalization, permits and SceneTree mutation remain
loader-owned.
- Defaults, path order, accepted empty/`pivot_prefix_v1` schemas, cache format,
metrics and visible behavior are unchanged. Generated GLB metadata fixtures
are not private asset, leak, p95/p99 or original-client animation evidence.
## 2026-07-18 M2 Animation Resource Finalizer Extraction
- `M2AnimationResourceFinalizer` now owns cached animated M2 terminal status
polling, completion-FIFO pops, terminal Resource retrieval, candidate
instantiation, repair/validation, prototype/static-only adoption and success log.
- The two-phase preparation/completion API preserves the prior ordering: the
loader resolves a material prototype only after a loaded PackedScene produces
a detached Node3D candidate. One scheduler permit still pops one record.
- `StreamingWorldLoader` retains the `M2_ANIMATION_FINALIZE` permit loop,
material-prototype lookup, build dispatch, materialization and SceneTree lifetime.
- No request order, status rule, cache format, profile or visible behavior changed.
Synthetic fixtures are not asset-backed animation/leak/p95/p99 evidence.
## 2026-07-18 M2 Mesh Resource Finalizer Extraction
- `M2MeshResourceFinalizer` now owns static M2 terminal status polling,
completion-FIFO pops, terminal Resource retrieval, first-Mesh extraction,
stale/current runtime preparation and Mesh/missing cache adoption.
- Pending paths are still polled in insertion order. One
`M2_MESH_FINALIZE` permit still pops at most one terminal record; cached Mesh
outcomes skip terminal retrieval exactly as before.
- Current Meshes retain exact identity and skip raw reads. Stale Meshes still
request raw data only when `M2RuntimeMeshFinalizer` requires it, preserving
refresh-version, rebuild and original-Mesh fallback rules.
- `StreamingWorldLoader` retains request admission, scheduler permits,
composition, static materialization and shutdown drain ordering. Cache paths,
profiles and visible behavior are unchanged; synthetic fixtures are not
asset-backed visual/leak/p95/p99 evidence.
## 2026-07-18 M2 Native Animation Resource Observer Extraction
- `M2NativeAnimationResourceObserver` now owns the exact case-insensitive
`gryphonroost` candidate rule, animated prototype/static-only cache checks,
synchronous raw animated read, native builder call, adoption and success log.
- The native attempt remains first. Empty raw data/surfaces and null/childless
builds retain the same static-only fallback; accepted Nodes remain owned by
`M2PrototypeCacheState` until final shutdown.
- `StreamingWorldLoader` retains observer order, typed snapshot construction,
cached-GLB fallback, terminal ResourceLoader polling/finalize, permits,
materialization and SceneTree lifetime.
- The historical unfreed childless builder result is deliberately preserved and
documented as a leak risk. No parser, cache, profile or visible rule changed;
synthetic fixtures are not asset-backed animation or performance evidence.
## 2026-07-18 M2 Animated Scene Finalizer Extraction ## 2026-07-18 M2 Animated Scene Finalizer Extraction
- `M2AnimatedSceneFinalizer` now owns terminal animated PackedScene candidate - `M2AnimatedSceneFinalizer` now owns terminal animated PackedScene candidate
@@ -1164,12 +1299,14 @@ $exe = Join-Path $env:TEMP 'godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe'
- `M2RuntimeMeshFinalizer` now owns material refresh version `2`, stale-Mesh - `M2RuntimeMeshFinalizer` now owns material refresh version `2`, stale-Mesh
rebuild classification, M2Builder rebuild and original-Mesh fallback. rebuild classification, M2Builder rebuild and original-Mesh fallback.
- Current Meshes still skip raw `.m2` loading. `M2RawModelRepository` now owns - Current Meshes still skip raw `.m2` loading. `M2RawModelRepository` owns
FileAccess/ClassDB M2Loader I/O and supplies raw data through the loader only FileAccess/ClassDB M2Loader I/O and supplies raw data through
when the finalizer reports a stale Mesh; both historical clear sites persist. `M2MeshResourceFinalizer` only when the runtime finalizer reports a stale Mesh;
both historical clear sites persist.
- Billboard/UV-rotation predicates, rebuild extraction, metadata key and failure - Billboard/UV-rotation predicates, rebuild extraction, metadata key and failure
fallback are unchanged. Cache adoption decisions, permits and MultiMesh fallback are unchanged. Mesh resource finalization owns cache adoption;
materialization remain loader-owned; negative outcomes belong to prototype state. permits and MultiMesh materialization remain loader-owned, while negative
outcomes belong to prototype state.
- Synthetic triangle rebuild/fallback timing is not asset-backed material, - Synthetic triangle rebuild/fallback timing is not asset-backed material,
descriptor-pressure/leak or p95/p99 evidence. descriptor-pressure/leak or p95/p99 evidence.
@@ -1178,10 +1315,10 @@ $exe = Join-Path $env:TEMP 'godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe'
- `M2RawModelRepository` now owns the repeated extracted-file and optional - `M2RawModelRepository` now owns the repeated extracted-file and optional
native `M2Loader` boundary for static `load_m2` and animated native `M2Loader` boundary for static `load_m2` and animated
`load_m2_animated` raw Dictionaries. `load_m2_animated` raw Dictionaries.
- `StreamingWorldLoader` delegates the stale-Mesh refresh, static prototype and - `StreamingWorldLoader` delegates stale-Mesh refresh and static reads directly;
native animated prototype reads. It retains normalization, `.tscn/.glb` `M2NativeAnimationResourceObserver` delegates native animated reads. The loader
fallback order, builders, permits and Node/Mesh use; prototype/negative state retains normalization, `.tscn/.glb` fallback order, permits and Node/Mesh use;
is now isolated in `M2PrototypeCacheState`. prototype/negative state is isolated in `M2PrototypeCacheState`.
- The repository retains no path, native object or parsed data. Empty paths, - The repository retains no path, native object or parsed data. Empty paths,
absent files/classes/methods and invalid results produce the same empty-value absent files/classes/methods and invalid results produce the same empty-value
fallback contract; path join/globalization and native method names are exact. fallback contract; path join/globalization and native method names are exact.
@@ -1219,9 +1356,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
@@ -1229,13 +1367,28 @@ $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,
negative entries and normalized-path to pending-`.tscn` records. negative entries and normalized-path to pending-`.tscn` records.
- `StreamingWorldLoader` still checks file existence and - `StreamingWorldLoader` still checks file existence and
`wmo_max_runtime_scene_mb`, calls `ResourceLoader`, instantiates a validation `wmo_max_runtime_scene_mb` and starts requests. `WmoSceneResourceFinalizer`
probe, checks WMOBuilder cache metadata and frees the probe before adoption. owns terminal ResourceLoader I/O, validation-probe instantiation, WMOBuilder
metadata validation and probe release before adoption.
- Missing files, oversize scenes, request errors, load failures and stale scenes - Missing files, oversize scenes, request errors, load failures and stale scenes
retain their prior negative-cache and live-prototype fallback behavior. retain their prior negative-cache and live-prototype fallback behavior.
- Map reset clears pending/negative state while retaining accepted scenes; final - Map reset clears pending/negative state while retaining accepted scenes; final
@@ -1243,6 +1396,21 @@ $exe = Join-Path $env:TEMP 'godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe'
- Asset-backed oversize/stale fixtures, traversal/leak p95/p99 and paired fidelity - Asset-backed oversize/stale fixtures, traversal/leak p95/p99 and paired fidelity
evidence remain pending. evidence remain pending.
## 2026-07-18 WMO Scene Resource Finalizer Extraction
- `WmoSceneResourceFinalizer` now owns cached WMO `.tscn` terminal status
polling, loaded Resource/PackedScene validation, call-local probe lifetime and
exact scene/missing publication.
- Pending snapshots preserve Dictionary insertion order. Non-terminal requests
remain pending; failed, null, wrong-type, wrong-root and stale outcomes keep
the existing negative-cache transition.
- Current scenes retain exact PackedScene identity. Accepted and rejected probes
are freed before return; the rejected non-Node3D root now also releases its
temporary Node, closing a leak without changing fallback or visible output.
- Loader retains file/size admission, oversize log, request start, live fallback,
placed Node materialization and shutdown order. Synthetic fixtures are not
serialized private assets, long leak, p95/p99 or visual-fidelity evidence.
## 2026-07-17 ADT Water Load Pipeline State Extraction ## 2026-07-17 ADT Water Load Pipeline State Extraction
- `AdtWaterLoadPipelineState` now owns ADT water pending FIFO/deduplication, - `AdtWaterLoadPipelineState` now owns ADT water pending FIFO/deduplication,
@@ -1317,6 +1485,74 @@ $exe = Join-Path $env:TEMP 'godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe'
- ADT parsing, quality tasks/results, tile state, cache format versions, material/ - ADT parsing, quality tasks/results, tile state, cache format versions, material/
Node/RID finalization, budgets and visible terrain behavior remain loader-owned. Node/RID finalization, budgets and visible terrain behavior remain loader-owned.
## 2026-07-18 WMO Runtime Mesh Finalizer Extraction
- `WmoRuntimeMeshFinalizer` now owns cached WMO Mesh material refresh version
`10`, in-place ArrayMesh surface iteration and WMOBuilder material-definition
reconstruction.
- `StreamingWorldLoader` retains cached-scene traversal, lightweight build-job
traversal, Node/MultiMesh attachment, permits and lifetime, and delegates each
borrowed Mesh to the service.
- The historical metadata keys, compact texture0/texture1/texture2 ordering,
flags/shader/blend values, cached shader colors and exact Mesh identity are
unchanged. Null/unmarked surfaces and null builder results retain their prior
Material.
- Asset-free verification covers `27` identity/version/type/material/source
cases and 1,000 current-Mesh calls. This is orchestration extraction, not new
build-12340 material or visual parity evidence.
## 2026-08-01 WMO Render Group Materializer Extraction
- `WmoRenderGroupMaterializer` now owns creation and single attachment of the
lightweight cached WMO `MeshInstance3D` and `MultiMeshInstance3D` groups.
- Indexed names/transforms, `Group_N`/`DoodadGroup_N` fallbacks, exact
Mesh/MultiMesh identity, shadow mode and positive visibility range/margin are
unchanged.
- `StreamingWorldLoader` retains build-step selection, runtime Mesh finalization,
queue cursors, permits, optional Editor ownership and placement lifetime.
- Asset-free verification covers `37` presentation/ownership/source cases and
1,000 simple group attachments. This extraction adds no asset-backed GPU,
leak, p95/p99 or original-client visual-fidelity evidence.
## 2026-08-01 WMO Runtime Scene Preparer Extraction
- `WmoRuntimeScenePreparer` now owns cached WMO parent-before-children Mesh/
MultiMesh finalization and the shared cached/live render-policy preparation.
- The historical path distinction is unchanged: live-built duplicates do not
cross the cached runtime Mesh finalizer boundary.
- Disabled occlusion still removes only the direct child named `Occluders`;
enabled shadows still set descendant GeometryInstance3D nodes ON, while the
disabled shadow branch preserves existing values.
- Instantiation, placement, attachment, registry lifetime, Editor ownership,
queues and permits remain loader-owned. Synthetic traversal timing is not
private-asset visual, leak, GPU or p95/p99 evidence.
## 2026-08-01 WMO Scene Instance Factory Extraction
- `WmoSceneInstanceFactory` now owns cached PackedScene instantiation/currentness
validation and live-prototype duplication with shared basename/placement rules.
- Cached validation still precedes placement; live duplicates still skip the
scene-cache validator. Accepted descendant Resources retain exact identity.
- Invalid non-Node3D cached roots are now freed synchronously, closing an
error-path lifetime leak that normal scene-cache admission already prevents.
- Source lookup, ResourceLoader, runtime preparation, attachment, registry,
queues and permits remain loader-owned. Synthetic factory timing is not
private-asset visual, leak/GPU or p95/p99 evidence.
## 2026-08-02 M03 Renderer Closeout
- M03 preserves the M00 `High` topology and batching while enforcing four
CPU-only worker boundaries, fifteen main-thread finalization lanes and seven
explicit cache versions through the renderer closeout contract verifier.
- Performance acceptance uses exact-cache paired M00/M03 captures plus a second
ten-second window. A metric must exceed its unchanged 10% budget in both
protocols to be a repeatable regression; the closeout result is `0/84`.
- Native M2 startup no longer copies an ArrayMesh that is immediately discarded.
It creates an instance-local mesh, reapplies shared Materials and performs one
phased rebuild before attachment; `_ready()` is idempotent afterward.
- Checkpoint evidence is asset-backed but is not an original-client pixel-parity
claim. Long traversal and original-client approval remain release gates.
## Practical Rule For Future Work ## Practical Rule For Future Work
If something improves quality but creates visible hitch, it is not done. Move it to bake/cache/background work, split finalization over frames, or prewarm it before the player can see it. If something improves quality but creates visible hitch, it is not done. Move it to bake/cache/background work, split finalization over frames, or prewarm it before the player can see it.
@@ -0,0 +1,80 @@
# M03-QAR-INTEGRATOR-CLOSEOUT-001
<!-- OPENWC_CLAIM:M03-QAR-INTEGRATOR-CLOSEOUT-001:sindo-main-codex-m03-integrator:2026-08-03 -->
<!-- OPENWC_INTEGRATION:ACCEPTED:M03-QAR-INTEGRATOR-CLOSEOUT-001:203d40b -->
## Owner
- Agent ID: `sindo-main-codex-m03-integrator`
- Target: M03 Renderer Facade and Safe Extraction
- Branch: `work/sindo-main-codex-m03-integrator/m03-closeout`
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-closeout`
## Outcome
Close M03 only after an overall renderer performance comparison, cache-version
and main-thread-finalization contract gates, the complete autonomous verifier
suite, documentation/coordination gates and integrator review all pass.
## Non-goals
- Change renderer visuals, placements, materials, animations or quality policy.
- Add new renderer features or advance to M04/M08.
- Claim original-client visual parity beyond existing M00 evidence.
## Paths
- Exclusive: M03 closeout verifier/comparator, closeout specification, this claim
- Shared: renderer verification runner, renderer/module registries, `RENDER.md`,
`targets/03-renderer-facade.md`, `targets/README.md`
## Contracts and data
- Compare identical checkpoint/pass pairs against the accepted M00 report.
- Require matching environment/profile/cache state and exact cache version keys.
- Enforce the accepted 10% ceilings for p95, p99, max hitch, load time and memory.
- Verify GPU/SceneTree finalization remains main-thread and budget-gated by source.
- Keep all generated/private reports outside Git; commit only aggregate evidence.
## Dependencies
- Requires: all accepted M03 facade/planner/scheduler and terrain/M2/WMO/liquid packages
- Blocks: M03 DONE marker and activation of the next user-selected plan
## Verification
- Full cold/warm renderer capture; deterministic performance comparison; complete
headless verifier suite; project/editor parse; baseline manifest/checkpoint dry
run; dependency, documentation and coordination gates.
## Documentation deliverables
- Inline comparator/gate docs; closeout module specification with API/I/O,
ownership and sequence/dependency diagrams; renderer docs and final M03 Evidence.
## Status
- State: integrated
- Done: renderer closeout gates, exact-cache performance evidence, full verifier
suite, documentation, M03 DONE transition and M04 administrative activation
- Next: receive the user's plan correction before beginning M04 work
- Blocked by:
<!-- OPENWC_HANDOFF:READY:M03-QAR-INTEGRATOR-CLOSEOUT-001:203d40b -->
## Handoff
- Commits: `d9d20cb`, `3bf1c2c`, `203d40b`; fast-forwarded to `master`.
- Verification: all `67/67` autonomous Godot verifiers; M2 playback `20`
cases; renderer contracts `workers=4 frame_steps=15 cache_versions=7
nested_glb=1`; performance stability `84` metrics with `0` repeatable
regressions; documentation, coordination and diff gates passed.
- Fidelity: exact cache inventory and asset-backed checkpoints cover terrain,
ADT boundary, dense M2, large WMO, liquid, native animation and dusk sky.
This is extraction/performance evidence, not original-client pixel parity.
- Remaining risks: original-client visual approval, long-traversal descriptor
pressure, scaling native animation beyond CPU deformation and interruption of
already in-flight worker jobs remain later release concerns.
- Documentation: added the renderer closeout module specification with data-flow
and sequence diagrams; updated testing policy, renderer source maps, M2
animation ownership/sequence documentation, `RENDER.md` and M03 Evidence.
@@ -0,0 +1,84 @@
# M03-RND-M2-ANIMATION-RESOURCE-FINALIZER-001
<!-- OPENWC_CLAIM:M03-RND-M2-ANIMATION-RESOURCE-FINALIZER-001:sindo-main-codex:2026-07-20 -->
## Owner
- Agent ID: `sindo-main-codex`
- Target: M03 Renderer Facade and Safe Extraction
- Branch: `work/sindo-main-codex/m03-m2-animation-resource-finalizer`
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-m2-animation-resource-finalizer`
## Outcome
Move cached animated M2 ResourceLoader status polling, terminal Resource load,
candidate finalization, prototype/static-only adoption and success diagnostics
from the loader into a dedicated two-phase finalization service.
## Non-goals
- Change cached animation eligibility, request admission or native-first order.
- Move material-prototype lookup, permit ownership, playback or materialization.
- Change ResourceLoader status order, cache formats, profiles or visible output.
- Generalize static and animated ResourceLoader paths behind callbacks/frameworks.
## Paths
- Exclusive: `src/render/m2/m2_animation_resource_finalizer.gd`,
`src/tools/verify_m2_animation_resource_finalizer.gd`,
`docs/modules/m2-animation-resource-finalizer.md`, this claim
- Shared: loader, animation pipeline/scene finalizer/prototype cache/cached
observer/world-renderer specs and verifiers, module registry, `RENDER.md`, M03 Evidence
## Contracts and data
- Pending request snapshots are polled in existing Dictionary insertion order.
- Empty Resource paths are discarded and marked static-only immediately.
- Only LOADED/FAILED statuses move into completion-order finalize FIFO.
- One permit processes one popped terminal record, including skipped/stale records.
- Existing prototype/static-only state suppresses terminal Resource loading.
- LOADED Resource is instantiated before material-prototype lookup is requested.
- Candidate repair/finalize/adoption and exact `M2_ANIM_CACHE` debug fields remain.
- Rejected candidates are released by the existing animated scene finalizer.
## Dependencies
- Requires: accepted animation pipeline, scene finalizer, cached/native observers
- Blocks: remaining loader-owned M2 material-prototype lookup and action execution
## Verification
- Synthetic polling/status/FIFO/skip/load/candidate/repair/adoption/log/source and
timing contracts; adjacent M2/renderer gates; full headless suite and checkpoint dry-run.
## Documentation deliverables
- Inline API docs; module API/I/O/ownership; data-flow/state/sequence/dependency
diagrams; adjacent renderer docs and M03 Evidence.
## Status
- State: accepted
- Done: implementation, verification and documentation
- Next: static M2 Resource terminal polling/finalize extraction
- Blocked by:
## Handoff
- Commit: `e7cd967`
- Results: finalizer `28` cases / `1,000` timing iterations; all `58/58`
autonomous headless regressions; facade/internal-access/shutdown/manifest;
documentation `45`; coordination; checkpoint dry-run `7/7`.
- Remaining risks: private asset animation/visual/leak/p95/p99 evidence is absent;
terminal Resource get/instantiation/repair stays synchronous; immediate prepared-
candidate completion is a composition precondition; material lookup stays loader-owned.
- Documentation updated: new finalizer API/I/O/data-flow/state/sequence/dependency/
ownership spec; pipeline, scene finalizer, cached observer, prototype cache,
world renderer, module registry, `RENDER.md` and M03 Evidence.
## Integration
- Merge: `06f6394`
- Post-merge: finalizer, pipeline, scene finalizer, cached observer, prototype
cache, snapshot, dispatch, shutdown, facade, internal-access `30`, manifest
`7/7`, documentation `45`, coordination and checkpoint dry-run `7/7` passed.
@@ -0,0 +1,97 @@
# M03-RND-M2-BUILD-DISPATCH-PLANNER-001
<!-- OPENWC_CLAIM:M03-RND-M2-BUILD-DISPATCH-PLANNER-001:sindo-main-codex:2026-07-20 -->
## Owner
- Agent ID: `sindo-main-codex`
- Target: M03 Renderer Facade and Safe Extraction
- Branch: `work/sindo-main-codex/m03-m2-build-dispatch-planner`
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-m2-build-dispatch-planner`
## Outcome
Extract the pure M2 build-step dispatch decision from the loader while preserving
animation-wait priority, static-resource retry, missing-model advancement and
exact permit/cursor behavior.
## Non-goals
- Move ResourceLoader I/O, path lookup, cache state or retry ownership.
- Create/free Nodes, Meshes, MultiMeshes or animated instances in the planner.
- Change batch sizing, queue ordering, transforms, visuals or cache versions.
- Add spatial-cell batching or new diagnostics/profile settings.
## Paths
- Exclusive: `src/render/m2/m2_build_dispatch_planner.gd`,
`src/tools/verify_m2_build_dispatch_planner.gd`,
`docs/modules/m2-build-dispatch-planner.md`, this claim
- Shared/hotspots: `src/scenes/streaming/streaming_world_loader.gd`, M2 build/
queue/materializer/cache verifiers and specs, `docs/modules/world-renderer.md`,
`docs/modules/README.md`, `RENDER.md`, `targets/03-renderer-facade.md`
- Generated/ignored: `.godot`, native DLL, generated resources/caches and proprietary corpus
## Contracts and data
- Pending animation request wins before empty/animated/static decisions.
- Positive batch with animated prototype selects animated materialization.
- Positive static batch selects prepared Mesh, waits when unresolved, and advances
without materialization only when the model is terminally missing.
- Zero/non-positive batch advances without materialization.
- Planner returns a fresh action plan and owns no state or engine reference.
## Dependencies
- Requires: accepted typed M2 build queue on master `c74b90a`
- Blocks: extracting stateful M2 resource readiness/dispatch orchestration
- External state: cache lookups, requests and engine lifetime remain loader APIs
## Verification
- Commands: dedicated action-priority/matrix/fresh-result/source/timing verifier;
M2 queue/planner/materializer/cache/prototype/shutdown/facade/internal-access/
manifest regressions; documentation, coordination and checkpoint dry-run gates
- Fixtures: scalar availability and terminal-state combinations only
- Fidelity evidence: exact existing branch priority and retry/advance behavior
- Performance budget: 20,000 pure dispatch plans under one second
## Documentation deliverables
- Inline API docs
- Module spec with inputs/outputs, ownership and source map
- Data-flow, lifecycle/state, sequence and dependency diagrams
- Adjacent M2 build/world-renderer/module-registry/RENDER updates
## Simplicity and naming
- Important name: `M2BuildDispatchPlanner`
- Simplest solution: one stateless RefCounted with explicit action constants
- Rejected complexity: callbacks, signals, cache references and generic state machine
- Unavoidable complexity: animation-pending priority precedes every other action
- Measured optimization evidence: bounded synthetic decision loop
## Status
- State: integration accepted
- Done: implementation, verification, documentation and worktree handoff
- Next: extract typed M2 resource observation/request orchestration
- Blocked by:
## Handoff
- Commit: `17f5cc0faa376c8ac84a7ac72d5b8c0366866980`
- Results: dispatch planner `cases=10 iterations=20000 elapsed_ms=11.933`;
all autonomous headless regressions `53/53`; internal access
`private_symbols=30`; baseline manifest/dry-run `7/7`; documentation
`module_specs=40`; coordination passed with 30 historical expired warnings.
- Remaining risks: proprietary ADT visual/p95/p99 evidence is unavailable;
resource observation/requests, action execution, permits and engine lifetime
intentionally remain loader-owned.
- Documentation updated: new `m2-build-dispatch-planner.md` with API/I/O,
data-flow, lifecycle, sequence, dependency and ownership diagrams; batch/queue/
world renderer specs, module registry, RENDER and M03 Evidence updated.
- Integration: master merge `99a90ddfb3364d6672198887198602b84dd19e58`;
post-merge dispatch planner `cases=10 iterations=20000 elapsed_ms=12.291`,
twelve adjacent M2/resource/lifetime/facade/internal-access/manifest checks,
documentation and coordination all passed; checkpoint dry-run retained `7/7` plans.
@@ -0,0 +1,98 @@
# M03-RND-M2-BUILD-RESOURCE-SNAPSHOT-001
<!-- OPENWC_CLAIM:M03-RND-M2-BUILD-RESOURCE-SNAPSHOT-001:sindo-main-codex:2026-07-20 -->
## Owner
- Agent ID: `sindo-main-codex`
- Target: M03 Renderer Facade and Safe Extraction
- Branch: `work/sindo-main-codex/m03-m2-build-resource-snapshot`
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-m2-build-resource-snapshot`
## Outcome
Replace loose per-step M2 animated/static resource variables with a typed
observation snapshot consumed by the dispatch planner and materializer adapter.
## Non-goals
- Move ResourceLoader I/O, cache lookup/request execution or retry ownership.
- Own or free prototype Nodes or Mesh Resources.
- Change dispatch priority, batch sizing, queue ordering, permits or visuals.
- Change M2 cache paths, formats, allowlists or native animation selection.
## Paths
- Exclusive: `src/render/m2/m2_build_resource_snapshot.gd`,
`src/tools/verify_m2_build_resource_snapshot.gd`,
`docs/modules/m2-build-resource-snapshot.md`, this claim
- Shared/hotspots: `src/render/m2/m2_build_dispatch_planner.gd`, its verifier/spec,
`src/scenes/streaming/streaming_world_loader.gd`, M2 queue/materializer/cache
specs and tests, `docs/modules/world-renderer.md`, `docs/modules/README.md`,
`RENDER.md`, `targets/03-renderer-facade.md`
- Generated/ignored: `.godot`, native DLL, generated resources/caches and proprietary corpus
## Contracts and data
- Snapshot retains normalized path, optional animated prototype, animation
request state, optional static Mesh and terminal missing-model state.
- Static observation is adopted only after animation is not pending, the batch
is positive and no animated prototype exists.
- Accessors borrow exact engine references without transferring/freeing ownership.
- Diagnostics expose only path and scalar availability flags.
## Dependencies
- Requires: accepted M2 dispatch planner on master `b113db0`
- Blocks: extracting M2 resource observation/request service ownership
- External state: prototype/cache/pipeline/resource lifetime remains loader-owned
## Verification
- Commands: dedicated identity/adoption/diagnostic/lifetime/source/timing verifier;
dispatch/queue/batch/materializer/cache/pipeline/shutdown/facade/internal-access/
manifest regressions; documentation, coordination and checkpoint dry-run gates
- Fixtures: synthetic Node3D prototype and ArrayMesh resources
- Fidelity evidence: exact resource presence/pending/missing values and branch order
- Performance budget: 20,000 snapshot construct/adopt/read cycles under one second
## Documentation deliverables
- Inline API docs
- Module spec with inputs/outputs, ownership and source map
- Data-flow, lifecycle/state, sequence and dependency diagrams
- Adjacent dispatch/queue/world-renderer/module-registry/RENDER updates
## Simplicity and naming
- Important name: `M2BuildResourceSnapshot`
- Simplest solution: one small RefCounted value holder with explicit accessors
- Rejected complexity: generic resource union, signals, callbacks and cache ownership
- Unavoidable complexity: two-phase animation then optional static observation
- Measured optimization evidence: bounded synthetic lifecycle loop
## Status
- State: integration accepted
- Done: implementation, verification, documentation and worktree handoff
- Next: extract the M2 resource observation/request producer service
- Blocked by:
## Handoff
- Commit: `ec1b90f1e44037572e3e5052e6dedc016c29859a`
- Results: resource snapshot `cases=12 iterations=20000 elapsed_ms=42.995`;
dispatch planner remained green; all autonomous headless regressions `54/54`;
internal access `private_symbols=30`; baseline manifest/dry-run `7/7`;
documentation `module_specs=41`; coordination passed with 30 historical warnings.
- Remaining risks: proprietary ADT visual/p95/p99 evidence is unavailable;
resource lookup/request execution, action execution, permits and engine lifetime
intentionally remain loader-owned.
- Documentation updated: new `m2-build-resource-snapshot.md` with API/I/O,
data-flow, lifecycle, sequence, dependency and ownership diagrams; dispatch/
queue/world renderer specs, module registry, RENDER and M03 Evidence updated.
- Integration: master merge `4354834c50ae426640c721041af67d5265e82205`;
post-merge snapshot `cases=12 iterations=20000 elapsed_ms=42.211`, dispatch
planner and twelve adjacent resource/materializer/lifetime/facade/internal-
access/manifest checks, documentation and coordination all passed; checkpoint
dry-run retained `7/7` plans.
@@ -0,0 +1,91 @@
# M03-RND-M2-CACHED-ANIMATION-RESOURCE-OBSERVER-001
<!-- OPENWC_CLAIM:M03-RND-M2-CACHED-ANIMATION-RESOURCE-OBSERVER-001:sindo-main-codex:2026-07-20 -->
## Owner
- Agent ID: `sindo-main-codex`
- Target: M03 Renderer Facade and Safe Extraction
- Branch: `work/sindo-main-codex/m03-m2-cached-animation-resource-observer`
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-m2-cached-animation-resource-observer`
## Outcome
Move cached-GLB animated M2 lookup, allow/deny and primitive/schema safety
selection, threaded request admission and static-only transition from the loader
into a service that produces `M2BuildResourceSnapshot`.
## Non-goals
- Move native GryphonRoost raw-data build, debug logging or finalize drains.
- Own/free prototype Nodes or change cache/pipeline ownership.
- Change allow/deny defaults, primitive limits, candidate order or GLB rules.
- Change batching, permits, cache formats, profiles or visible output.
## Paths
- Exclusive: `src/render/m2/m2_cached_animation_resource_observer.gd`,
`src/tools/verify_m2_cached_animation_resource_observer.gd`,
`docs/modules/m2-cached-animation-resource-observer.md`, this claim
- Shared: loader, M2 snapshot/animation pipeline/prototype specs and verifiers,
world renderer, module registry, `RENDER.md` and M03 Evidence
## Contracts and data
- Cached animated prototype wins; static-only and pending states do not request.
- Empty allowlist rejects; denylist wins; matching is stripped/case-insensitive.
- Historical nested/lowercase/basename `.glb` order remains exact.
- Candidate requires animations, primitive limit and accepted empty or
`pivot_prefix_v1` schema; other schemas are rejected.
- Successful/`ERR_BUSY` request is remembered and reported pending; no usable
candidate or request failure marks animation static.
- Service produces a snapshot borrowing the exact prototype and frees nothing.
## Dependencies
- Requires: accepted static observer `5ebf4de`, snapshot and animation pipeline
- Blocks: native animation observation and remaining finalize extraction
## Verification
- Synthetic cache/pipeline/snapshot lifecycle, policy/path/GLB metadata fixtures,
source boundaries and timing; adjacent M2/renderer gates; full headless suite
and checkpoint dry-run.
## Documentation deliverables
- Inline API docs; module API/I/O/ownership; data-flow/state/sequence/dependency
diagrams; adjacent renderer docs and M03 Evidence.
## Status
- State: accepted
- Done: implementation, integration and post-merge acceptance
- Next: native M2 animation observation extraction
- Blocked by:
## Handoff
- Commit: `df87619` (`render: extract cached M2 animation resource observer`)
- Results: observer PASS (`33` cases, `20,000` iterations, `61.745ms`);
full autonomous suite PASS (`56/56`); renderer checkpoint dry-run PASS (`7/7`);
internal-access PASS (`30` private symbols); documentation PASS (`43` module
specs); coordination PASS with `34` historical warnings.
- Remaining risks: successful asynchronous admission is covered by source and
existing pipeline regressions because an undrained unit request leaks; generated
GLB metadata does not prove Godot import fidelity; private traversal, p95/p99,
leak and paired visual evidence remain unavailable; native observation and
terminal ResourceLoader polling/finalize remain loader-owned.
- Documentation updated: new cached observer API/I/O/data-flow/state/sequence/
dependency/ownership specification; M2 snapshot, batch/dispatch, animation
pipeline/prototype, world-renderer, module registry, `RENDER.md` and M03 Evidence.
## Integration
- Merge: `f79e064` (`merge: cached M2 animation resource observer`)
- Post-merge: observer `33` cases / `20,000` iterations / `61.514ms`;
animation pipeline `11` / `66.049ms`; snapshot `12` / `39.947ms`;
dispatch `10` / `17.915ms`; prototype cache `16` / `35.053ms`; shutdown,
facade, internal-access `30`, manifest `7/7`, documentation `43` and
coordination passed. Checkpoint dry-run completed `7/7`; expected private
Azeroth/character assets remained unavailable.
@@ -0,0 +1,84 @@
# M03-RND-M2-MESH-RESOURCE-FINALIZER-001
<!-- OPENWC_CLAIM:M03-RND-M2-MESH-RESOURCE-FINALIZER-001:sindo-main-codex:2026-07-20 -->
## Owner
- Agent ID: `sindo-main-codex`
- Target: M03 Renderer Facade and Safe Extraction
- Branch: `work/sindo-main-codex/m03-m2-mesh-resource-finalizer`
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-m2-mesh-resource-finalizer`
## Outcome
Move static M2 ResourceLoader status polling, terminal Resource/Mesh extraction,
runtime Mesh preparation, Mesh-cache adoption and missing outcome from the
loader into one dedicated finalization service.
## Non-goals
- Change static cache path selection or threaded request admission.
- Merge static and animated terminal services behind a generic callback layer.
- Own permits, MultiMesh materialization, prototype Nodes or SceneTree roots.
- Change material refresh version, rebuild rules, cache formats or visible output.
## Paths
- Exclusive: `src/render/m2/m2_mesh_resource_finalizer.gd`,
`src/tools/verify_m2_mesh_resource_finalizer.gd`,
`docs/modules/m2-mesh-resource-finalizer.md`, this claim
- Shared: loader, Mesh pipeline/cache/extractor/runtime-finalizer/raw-repository/
prototype specs and verifiers, world renderer, registry, `RENDER.md`, M03 Evidence
## Contracts and data
- Pending requests are polled in existing Dictionary insertion order.
- Empty Resource paths are discarded and marked missing immediately.
- Only LOADED/FAILED statuses enter completion-order finalize FIFO.
- One permit pops exactly one terminal record, including skipped/cached records.
- Existing cached Mesh suppresses terminal Resource retrieval.
- Loaded Resource uses exact first-Mesh extraction and current refresh-version path.
- Raw M2 data loads only when the extracted Mesh is stale.
- Prepared Mesh is stored by existing replacement semantics; failures mark missing.
## Dependencies
- Requires: accepted Mesh pipeline/cache/extractor/runtime finalizer/raw repository
- Blocks: remaining loader-owned M2 materialization and orchestration cleanup
## Verification
- Synthetic polling/FIFO/skip/load/extract/refresh/store/missing/source/timing
contracts; adjacent M2/renderer gates; full headless suite and checkpoint dry-run.
## Documentation deliverables
- Inline API docs; module API/I/O/ownership; data-flow/state/sequence/dependency
diagrams; adjacent renderer docs and M03 Evidence.
## Status
- State: accepted
- Done: implementation, verification and documentation
- Next: next unclaimed M03 renderer extraction package
- Blocked by:
## Handoff
- Commit: `ddeb708` (`render: extract M2 Mesh resource finalizer`)
- Results: finalizer PASS `cases=31 iterations=1000 elapsed_ms=0.767`;
adjacent Mesh pipeline/cache/extractor/runtime-finalizer/raw-repository/
prototype/shutdown checks passed; autonomous headless suite `59/59`;
documentation `46`; coordination passed with `34` historical warnings;
checkpoint dry-run `7/7`.
- Remaining risks: terminal instantiation/raw parsing/rebuild remain synchronous
main-thread work behind existing permits; no private-asset 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 M2 module specs,
world-renderer registry/spec, `RENDER.md` and M03 Evidence.
- Merge: `34b7000` (`merge: M2 Mesh resource finalizer`)
- Post-merge: finalizer PASS `cases=31 iterations=1000 elapsed_ms=0.826`;
adjacent M2 pipeline/cache/extractor/runtime/repository/prototype/shutdown,
facade, internal-access `30`, manifest `7/7`, documentation `46` and
coordination gates passed.
@@ -0,0 +1,84 @@
# M03-RND-M2-NATIVE-ANIMATION-RESOURCE-OBSERVER-001
<!-- OPENWC_CLAIM:M03-RND-M2-NATIVE-ANIMATION-RESOURCE-OBSERVER-001:sindo-main-codex:2026-07-20 -->
## Owner
- Agent ID: `sindo-main-codex`
- Target: M03 Renderer Facade and Safe Extraction
- Branch: `work/sindo-main-codex/m03-m2-native-animation-resource-observer`
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-m2-native-animation-resource-observer`
## Outcome
Move native GryphonRoost M2 animation candidate selection, synchronous raw read,
native animated build, prototype/static-only adoption and debug diagnostics from
the loader into a dedicated observer.
## Non-goals
- Change the exact native candidate predicate or add more animated models.
- Move cached-GLB observation, ResourceLoader polling/finalize or playback.
- Own attached instances, consume permits or change queue/batch behavior.
- Change native parsing/building, cache formats, profiles or visible output.
## Paths
- Exclusive: `src/render/m2/m2_native_animation_resource_observer.gd`,
`src/tools/verify_m2_native_animation_resource_observer.gd`,
`docs/modules/m2-native-animation-resource-observer.md`, this claim
- Shared: loader, M2 cached observer/snapshot/raw repository/prototype specs and
verifiers, world renderer, module registry, `RENDER.md` and M03 Evidence
## Contracts and data
- Only case-insensitive paths containing `gryphonroost` are native candidates.
- Cached animated prototype wins; static-only state suppresses repeated reads.
- Empty raw data or animated surfaces marks animation static.
- Builder result must be non-null with at least one child; rejection marks static.
- Successful result is adopted through first-wins prototype cache semantics.
- Existing `M2_NATIVE_ANIM_CACHE` fields and debug gating remain exact.
- Observer attaches/frees no Node and starts no async work.
## Dependencies
- Requires: accepted cached observer `6f385ed`, raw repository and prototype state
- Blocks: remaining ResourceLoader polling/finalize extraction
## Verification
- Synthetic candidate/cache/static/raw/build/adoption/lifetime/logging/source and
timing contracts; adjacent M2/renderer gates; full headless suite and checkpoint
dry-run.
## Documentation deliverables
- Inline API docs; module API/I/O/ownership; data-flow/state/sequence/dependency
diagrams; adjacent renderer docs and M03 Evidence.
## Status
- State: accepted
- Done: implementation, verification and documentation
- Next: remaining cached ResourceLoader terminal polling/finalize extraction
- Blocked by:
## Handoff
- Commit: `1cb0101`
- Results: observer `33` cases / `20,000` timing iterations; all `57/57`
autonomous headless regressions; facade/internal-access/shutdown/manifest;
documentation `44`; coordination; checkpoint dry-run `7/7`.
- Remaining risks: private asset animation/visual/leak/p95/p99 evidence is absent;
native parse/build stays synchronous; historical childless rejected Node remains
unfreed; cached ResourceLoader terminal polling/finalize stays loader-owned.
- Documentation updated: new native observer API/I/O/data-flow/state/sequence/
dependency/ownership spec; cached observer, snapshot, dispatch, repository,
prototype cache, world renderer, module registry, `RENDER.md` and M03 Evidence.
## Integration
- Merge: `d37c799`
- Post-merge: native/cached observers, repository, cache, snapshot, dispatch,
shutdown, facade, internal-access `30`, manifest `7/7`, documentation `44`,
coordination and checkpoint dry-run `7/7` passed on master.
@@ -0,0 +1,86 @@
# M03-RND-M2-STATIC-BUILD-RESOURCE-OBSERVER-001
<!-- OPENWC_CLAIM:M03-RND-M2-STATIC-BUILD-RESOURCE-OBSERVER-001:sindo-main-codex:2026-07-20 -->
## Owner
- Agent ID: `sindo-main-codex`
- Target: M03 Renderer Facade and Safe Extraction
- Branch: `work/sindo-main-codex/m03-m2-static-build-resource-observer`
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-m2-static-build-resource-observer`
## Outcome
Move static M2 build cache lookup, threaded request selection and terminal
missing transition from the loader into a service that fills
`M2BuildResourceSnapshot`.
## Non-goals
- Move animation/native prototype observation or finalize drains.
- Own/free Meshes, Nodes or cache entries.
- Change path candidate order, GLB filtering, retry behavior, permits or visuals.
- Change cache formats, profiles or batching.
## Paths
- Exclusive: `src/render/m2/m2_static_build_resource_observer.gd`,
`src/tools/verify_m2_static_build_resource_observer.gd`,
`docs/modules/m2-static-build-resource-observer.md`, this claim
- Shared: loader, M2 snapshot/dispatch/pipeline/cache specs and verifiers,
world renderer, module registry, RENDER and M03 Evidence
## Contracts and data
- Cached Mesh wins; terminal missing returns missing; existing pending request waits.
- Candidate order remains normalized/lowercase nested then basename per extension.
- `.tscn` precedes `.glb`; pivot-prefix GLB is skipped for static build.
- Successful/`ERR_BUSY` request is remembered; no usable candidate marks missing.
- Snapshot adopts exact Mesh/missing result; service never frees engine objects.
## Dependencies
- Requires: accepted resource snapshot `032a256`
- Blocks: remaining animated resource observer extraction
## Verification
- Synthetic cache/pipeline/snapshot lifecycle, path order, source boundaries and timing;
adjacent M2 and renderer gates; full headless suite and checkpoint dry-run.
## Documentation deliverables
- Inline API docs; module API/I/O/ownership; data-flow/state/sequence/dependency
diagrams; adjacent renderer docs and M03 Evidence.
## Status
- State: accepted
- Done: implementation, integration and post-merge acceptance
- Next: remaining animated resource observer extraction
- Blocked by:
## Handoff
- Commit: `7cb3e34` (`render: extract static M2 build resource observer`)
- Results: observer PASS (`11` cases, `20,000` iterations, `64.654ms`);
full autonomous suite PASS (`55/55`); renderer checkpoint dry-run PASS (`7/7`);
internal-access PASS (`30` private symbols); documentation PASS (`42` module
specs); coordination PASS with `30` historical warnings.
- Remaining risks: successful asynchronous admission is covered by source and
existing pipeline regressions because an undrained unit request leaks; private
asset traversal, p95/p99, leak and visual evidence remain unavailable;
animated observation and static finalize polling remain loader-owned.
- Documentation updated: new observer module API/I/O/data-flow/state/sequence/
dependency/ownership specification; M2 snapshot, pipeline/cache, world-renderer,
module registry, `RENDER.md` and M03 Evidence updated.
## Integration
- Merge: `a043c79` (`merge: static M2 build resource observer`)
- Post-merge: observer `11` cases / `20,000` iterations / `64.202ms`;
snapshot `12` / `42.895ms`; dispatch `10` / `18.096ms`; Mesh pipeline
`11` / `73.615ms`; Mesh cache `9` / `40.229ms`; prototype cache `16` /
`35.947ms`; shutdown, facade, internal-access `30`, manifest `7/7`,
documentation `42` and coordination passed. Checkpoint dry-run completed
`7/7`; expected private Azeroth/character assets remained unavailable.
@@ -0,0 +1,88 @@
# M03-RND-WMO-RENDER-GROUP-MATERIALIZER-001
<!-- OPENWC_CLAIM:M03-RND-WMO-RENDER-GROUP-MATERIALIZER-001:sindo-main-codex:2026-08-03 -->
## Owner
- Agent ID: `sindo-main-codex`
- Target: M03 Renderer Facade and Safe Extraction
- Branch: `work/sindo-main-codex/m03-wmo-render-group-materializer`
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-wmo-render-group-materializer`
## Outcome
Move one-step WMO MeshInstance3D and MultiMeshInstance3D creation, render
settings and attachment from the loader into one main-thread materializer.
## Non-goals
- Change WMO placement, resource loading, build-step planning or queue state.
- Change runtime Mesh finalization, WMOBuilder material behavior or cache formats.
- Change permits, visibility distances, shadow policy or visible output.
- Generalize M2 and WMO materialization behind a shared abstraction.
## Paths
- Exclusive: `src/render/wmo/wmo_render_group_materializer.gd`,
`src/tools/verify_wmo_render_group_materializer.gd`,
`docs/modules/wmo-render-group-materializer.md`, this claim
- Shared: loader, renderer module registry/specification, `RENDER.md`, M03 Evidence
## Contracts and data
- Mesh and MultiMesh resources retain exact identity.
- Names and optional transforms preserve existing indexed fallback behavior.
- Shadow and positive visibility-range settings are applied unchanged.
- The supplied WMO root becomes the sole SceneTree owner of the created node.
- Editor persisted ownership remains an explicit loader composition policy.
## Dependencies
- Requires: accepted WMO render build queue/planner and runtime Mesh finalizer
- Blocks: remaining loader-owned WMO runtime traversal cleanup
## Verification
- Synthetic Mesh/MultiMesh identity, naming, transform, rendering, attachment,
invalid-input, ownership, source and bounded-timing contracts; adjacent WMO and
renderer gates; full autonomous suite and checkpoint dry-run.
## Documentation deliverables
- Inline API docs; module API/I/O/ownership; data-flow/sequence/dependency
diagrams; adjacent renderer docs and M03 Evidence.
## Status
- State: ready
- Done: implementation, verification and documentation
- Next: integrator review and merge
- Blocked by:
## Handoff
- Commit: `f4d5e29` (`render: extract WMO render group materializer`)
- Results: materializer PASS `cases=37 iterations=1000 elapsed_ms=2.771`;
autonomous headless suite `63/64`, with only the proprietary
`verify_adt_m2_placements.gd` probe unavailable because `data/extracted` is
absent; cold/editor parse completed without script diagnostics after restoring
ignored generated/native worktree artifacts; checkpoint dry-run retained
`7/7`; documentation passed with `50` module specifications; coordination
passed with `77` historical expired-claim warnings.
- Fidelity: exact Mesh/MultiMesh identity, indexed/fallback presentation,
render settings, attachment and scheduler/queue boundaries are unchanged.
No private-asset or original-client visual parity claim is added.
- Remaining risks: synchronous main-thread Node creation remains; no private WMO
visual comparison, long traversal, leak/GPU or p95/p99 evidence.
- Documentation: new full module specification with API/I/O and data-flow,
sequence/dependency diagrams; renderer registry/source map and `RENDER.md`
updated.
<!-- OPENWC_HANDOFF:READY:M03-RND-WMO-RENDER-GROUP-MATERIALIZER-001:f4d5e29 -->
<!-- OPENWC_INTEGRATION:ACCEPTED:M03-RND-WMO-RENDER-GROUP-MATERIALIZER-001:705354d -->
- Merge: `705354d` (`merge: WMO render group materializer`)
- Post-merge: materializer PASS `cases=37 iterations=1000 elapsed_ms=3.236`;
all nine adjacent WMO services, shutdown, materials, facade, internal-access
`30`, manifest `7/7`, documentation `50` and coordination passed.
@@ -0,0 +1,79 @@
# M03-RND-WMO-RENDER-RESOURCE-FINALIZER-001
<!-- OPENWC_CLAIM:M03-RND-WMO-RENDER-RESOURCE-FINALIZER-001:sindo-main-codex:2026-07-20 -->
## Owner
- Agent ID: `sindo-main-codex`
- Target: M03 Renderer Facade and Safe Extraction
- Branch: `work/sindo-main-codex/m03-wmo-render-resource-finalizer`
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-wmo-render-resource-finalizer`
## Outcome
Move lightweight WMO render-cache terminal ResourceLoader polling, script/format
validation and Resource/missing publication from the loader into one service.
## Non-goals
- Change `.res` cache path selection or threaded request admission.
- Own WMO build permits, queues, placement state, Nodes or materialization.
- Change `WMOStreamingResource.FORMAT_VERSION`, fallback order or visible output.
- Combine render Resource and PackedScene finalization behind a generic callback.
## Paths
- Exclusive: `src/render/wmo/wmo_render_resource_finalizer.gd`,
`src/tools/verify_wmo_render_resource_finalizer.gd`,
`docs/modules/wmo-render-resource-finalizer.md`, this claim
- Shared: loader, WMO render cache state spec/verifier, world renderer registry/spec,
`RENDER.md`, M03 Evidence
## Contracts and data
- Pending requests are polled in detached Dictionary insertion order.
- Only LOADED/FAILED statuses complete requests.
- Failed, null, wrong-script and stale-format Resources publish missing outcome.
- Current exact-script Resources are adopted without duplication.
- Request admission, reset/shutdown drain order and cache lifetime are unchanged.
## Dependencies
- Requires: accepted WMO render Resource cache state
- Blocks: remaining loader-owned WMO orchestration/materialization cleanup
## Verification
- Synthetic status/order/load/validation/adoption/source/timing contracts;
adjacent WMO/renderer gates; full headless suite and checkpoint dry-run.
## Documentation deliverables
- Inline API docs; module API/I/O/ownership; data-flow/state/sequence/dependency
diagrams; adjacent renderer docs and M03 Evidence.
## Status
- State: accepted
- Done: implementation, verification and documentation
- Next: next unclaimed M03 renderer extraction package
- Blocked by:
## Handoff
- 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.
- Merge: `1acddab` (`merge: WMO render resource finalizer`)
- Post-merge: finalizer PASS `cases=24 iterations=1000 elapsed_ms=3.777`;
WMO cache/queue/planner/registry/resolver/scene-cache/shutdown, facade,
internal-access `30`, manifest and checkpoint dry-run `7/7`, documentation
`47` and coordination gates passed.
@@ -0,0 +1,78 @@
# M03-RND-WMO-RUNTIME-MESH-FINALIZER-001
<!-- OPENWC_CLAIM:M03-RND-WMO-RUNTIME-MESH-FINALIZER-001:sindo-main-codex:2026-07-20 -->
## Owner
- Agent ID: `sindo-main-codex`
- Target: M03 Renderer Facade and Safe Extraction
- Branch: `work/sindo-main-codex/m03-wmo-runtime-mesh-finalizer`
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-wmo-runtime-mesh-finalizer`
## Outcome
Move cached WMO runtime Mesh material refresh versioning, surface iteration and
material-definition reconstruction from the loader into one service.
## Non-goals
- Change WMO traversal, placement, build-job scheduling or Node materialization.
- Change WMOBuilder shader/material behavior or cache formats.
- Change texture path ordering, metadata names or visible output.
- Generalize M2 and WMO finalization behind a shared abstraction.
## Paths
- Exclusive: `src/render/wmo/wmo_runtime_mesh_finalizer.gd`,
`src/tools/verify_wmo_runtime_mesh_finalizer.gd`,
`docs/modules/wmo-runtime-mesh-finalizer.md`, this claim
- Shared: loader, WMO build/cache and world renderer specs/verifiers,
`RENDER.md`, M03 Evidence
## Contracts and data
- Null meshes remain null; accepted meshes retain exact Resource identity.
- Refresh metadata version `10` remains the admission boundary.
- Only stale `ArrayMesh` surfaces with cached WMO texture metadata are rebuilt.
- Texture indices remain compact and ordered texture0, texture1, texture2.
- WMO flags, shader, blend mode and cached colors are forwarded unchanged.
- Scene traversal, builder material semantics and shutdown order are unchanged.
## Dependencies
- Requires: accepted WMO render and scene Resource finalizers
- Blocks: remaining loader-owned WMO runtime materialization cleanup
## Verification
- Synthetic identity/version/type/surface/metadata/color/definition/source/timing
contracts; adjacent WMO/renderer gates; full suite and checkpoint dry-run.
## Documentation deliverables
- Inline API docs; module API/I/O/ownership; data-flow/state/sequence/dependency
diagrams; adjacent renderer docs and M03 Evidence.
## Status
- State: accepted
- Done: implementation, verification and documentation
- Next: next unclaimed M03 renderer extraction package
- Blocked by:
## Handoff
- Commit: `ae40e3f` (`render: extract WMO runtime Mesh finalizer`)
- Results: finalizer PASS `cases=27 iterations=1000 elapsed_ms=0.402` in the
full run; autonomous headless suite `63/63`; documentation `49`; coordination
passed with `34` historical warnings; checkpoint dry-run `7/7`.
- Remaining risks: WMOBuilder surface mutation remains synchronous main-thread
work; no private WMO corpus, visual comparison, leak/GPU or p95/p99 evidence.
- Documentation updated: new full module specification with API/I/O and
data-flow/state/sequence/dependency diagrams; renderer registry/source map,
`RENDER.md` and M03 Evidence.
- Merge: `d65ebee` (`merge: WMO runtime Mesh finalizer`)
- Post-merge: finalizer PASS `cases=27 iterations=1000 elapsed_ms=0.202`;
WMO scene/render cache/finalizer/queue/planner/registry/resolver, shutdown,
materials, facade, internal-access `30`, manifest and checkpoint dry-run
`7/7`, documentation `49` and coordination gates passed.
@@ -0,0 +1,93 @@
# M03-RND-WMO-RUNTIME-SCENE-PREPARER-001
<!-- OPENWC_CLAIM:M03-RND-WMO-RUNTIME-SCENE-PREPARER-001:sindo-main-codex:2026-08-03 -->
## Owner
- Agent ID: `sindo-main-codex`
- Target: M03 Renderer Facade and Safe Extraction
- Branch: `work/sindo-main-codex/m03-wmo-runtime-scene-preparer`
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-wmo-runtime-scene-preparer`
## Outcome
Move cached/live WMO subtree preparation—recursive Mesh finalization, direct
Occluders-child policy and recursive shadow enabling—from the loader into one
stateless main-thread service.
## Non-goals
- Change scene/prototype loading, validation, placement or attachment.
- Change WMO material refresh rules, cache versions or builder behavior.
- Change occlusion/shadow defaults or add visibility/portal behavior.
- Change queue, scheduler, ownership or shutdown lifecycle.
## Paths
- Exclusive: `src/render/wmo/wmo_runtime_scene_preparer.gd`,
`src/tools/verify_wmo_runtime_scene_preparer.gd`,
`docs/modules/wmo-runtime-scene-preparer.md`, this claim
- Shared: loader, renderer module registry/specification, `RENDER.md`, M03 Evidence
## Contracts and data
- Cached preparation finalizes every MeshInstance3D Mesh and non-null
MultiMeshInstance3D Mesh in depth-first traversal order.
- Live preparation does not cross the runtime Mesh finalizer boundary.
- Disabled occlusion removes only the direct child named `Occluders` and queues
it for deletion; enabled occlusion retains it.
- Enabled shadows recursively set every GeometryInstance3D to ON; disabled
shadows preserve existing per-node values.
- The service borrows the subtree and retains no Node or Resource.
## Dependencies
- Requires: accepted WMO runtime Mesh finalizer
- Blocks: remaining loader-owned WMO scene/prototype orchestration cleanup
## Verification
- Synthetic cached/live traversal, exact resource/order, occluder, shadow,
invalid-input, ownership, source and bounded-timing contracts; adjacent WMO,
renderer, documentation and coordination gates; checkpoint dry-run.
## Documentation deliverables
- Inline API docs; module API/I/O/ownership; data-flow/sequence/dependency
diagrams; adjacent renderer docs and M03 Evidence.
## Status
- State: ready
- Done: implementation, verification and documentation
- Next: integrator review and merge
- Blocked by:
## Handoff
- Commit: `ffed91c` (`render: extract WMO runtime scene preparer`)
- Results: preparer PASS `cases=31 iterations=1000 elapsed_ms=3.669`;
updated Mesh-finalizer boundary PASS `cases=28 iterations=1000
elapsed_ms=0.204`; headless suite `64/65` with no unexpected failures and
only the proprietary ADT placement probe unavailable without `data/extracted`;
editor parse had zero script diagnostics; checkpoint dry-run retained `7/7`;
documentation passed with `51` module specifications; coordination passed
with `77` historical expired-claim warnings.
- Fidelity: cached/live preparation distinction, exact Mesh traversal order,
direct `Occluders` lookup/removal and enabled/preserved shadow semantics are
unchanged. No private-asset or original-client parity claim is added.
- Remaining risks: recursive Resource/SceneTree mutation remains synchronous;
no asset-backed WMO portal/room, visual, leak/GPU or p95/p99 evidence.
- Documentation: new full module specification with API/I/O and data-flow,
sequence/dependency diagrams; Mesh-finalizer ownership spec, renderer registry,
world-renderer source map and `RENDER.md` updated.
<!-- OPENWC_HANDOFF:READY:M03-RND-WMO-RUNTIME-SCENE-PREPARER-001:ffed91c -->
<!-- OPENWC_INTEGRATION:ACCEPTED:M03-RND-WMO-RUNTIME-SCENE-PREPARER-001:57d0a9f -->
- Merge: `57d0a9f` (`merge: WMO runtime scene preparer`)
- Post-merge: preparer PASS `cases=31 iterations=1000 elapsed_ms=3.486`;
Mesh finalizer `cases=28`/`0.199ms`; all nine adjacent WMO services, shutdown,
materials, facade, internal-access `30`, manifest `7/7`, documentation `51`
and coordination passed.
@@ -0,0 +1,90 @@
# M03-RND-WMO-SCENE-INSTANCE-FACTORY-001
<!-- OPENWC_CLAIM:M03-RND-WMO-SCENE-INSTANCE-FACTORY-001:sindo-main-codex:2026-08-03 -->
## Owner
- Agent ID: `sindo-main-codex`
- Target: M03 Renderer Facade and Safe Extraction
- Branch: `work/sindo-main-codex/m03-wmo-scene-instance-factory`
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-wmo-scene-instance-factory`
## Outcome
Move WMO cached-scene instantiation/currentness validation and live-prototype
duplication with shared name/placement application into one main-thread factory.
## Non-goals
- Change cache lookup/admission, ResourceLoader or prototype construction.
- Change runtime Mesh/material/occluder/shadow preparation.
- Change attachment, placement registry, queues, permits or lifetime.
- Change scene cache version rules or placement formulas.
## Paths
- Exclusive: `src/render/wmo/wmo_scene_instance_factory.gd`,
`src/tools/verify_wmo_scene_instance_factory.gd`,
`docs/modules/wmo-scene-instance-factory.md`, this claim
- Shared: loader, adjacent WMO verifier/spec, renderer registry/specification,
`RENDER.md`, M03 Evidence
## Contracts and data
- Null scenes/prototypes and non-Node3D instantiation/duplication return null.
- Cached instances failing the injected currentness validator are synchronously freed.
- Accepted instances retain exact descendant Resource identities.
- Both paths apply `relative_path.get_file().get_basename()` and the exact
placement-resolver Transform3D.
- The factory returns detached roots and retains no Node or Resource.
## Dependencies
- Requires: accepted WMO placement resolver and runtime scene preparer
- Blocks: remaining loader-owned WMO cache/prototype orchestration cleanup
## Verification
- Synthetic cached/live type, validation, freeing, exact identity, name,
transform, dependency/order, ownership, source and bounded-timing contracts;
adjacent WMO/renderer gates and checkpoint dry-run.
## Documentation deliverables
- Inline API docs; module API/I/O/ownership; data-flow/sequence/dependency
diagrams; adjacent renderer docs and M03 Evidence.
## Status
- State: ready
- Done: implementation, verification and documentation
- Next: integrator review and merge
- Blocked by:
## Handoff
- Commit: `7e97b19` (`render: extract WMO scene instance factory`)
- Results: factory PASS `cases=41 iterations=1000 elapsed_ms=5.297`;
placement resolver dependency PASS `cases=10 iterations=20000
elapsed_ms=28.740`; suite `65/66` with no unexpected failures and only the
proprietary ADT placement probe unavailable; editor parse had zero script
diagnostics; checkpoint dry-run retained `7/7`; documentation passed with
`52` module specifications; coordination passed with `77` historical warnings.
- Fidelity: valid cached/live cache-validation distinction, basename, exact
Transform3D and descendant Resource identity are unchanged. Invalid non-Node3D
cached roots are now freed synchronously, fixing an error-path leak without
changing valid visible output.
- Remaining risks: instantiation/duplication remains synchronous; no private WMO
portal/room, visual, long-traversal, leak/GPU or p95/p99 evidence.
- Documentation: new full factory module specification; placement resolver
consumers/sequence, renderer registry/source map and `RENDER.md` updated.
<!-- OPENWC_HANDOFF:READY:M03-RND-WMO-SCENE-INSTANCE-FACTORY-001:7e97b19 -->
<!-- OPENWC_INTEGRATION:ACCEPTED:M03-RND-WMO-SCENE-INSTANCE-FACTORY-001:541279e -->
- Merge: `541279e` (`merge: WMO scene instance factory`)
- Post-merge: factory PASS `cases=41 iterations=1000 elapsed_ms=5.308`;
placement resolver `cases=10`/`31.207ms`; runtime preparation/finalization,
adjacent WMO services, shutdown, materials, facade, internal-access `30`,
manifest `7/7`, documentation `52` and coordination passed.
@@ -0,0 +1,80 @@
# M03-RND-WMO-SCENE-RESOURCE-FINALIZER-001
<!-- OPENWC_CLAIM:M03-RND-WMO-SCENE-RESOURCE-FINALIZER-001:sindo-main-codex:2026-07-20 -->
## Owner
- Agent ID: `sindo-main-codex`
- Target: M03 Renderer Facade and Safe Extraction
- Branch: `work/sindo-main-codex/m03-wmo-scene-resource-finalizer`
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-wmo-scene-resource-finalizer`
## Outcome
Move cached WMO PackedScene terminal ResourceLoader polling, probe validation,
probe lifetime and scene/missing publication from the loader into one service.
## Non-goals
- Change `.tscn` path selection, file-size admission or request start behavior.
- Own live fallback, placement/build jobs, attached Nodes or scheduler permits.
- Change WMOBuilder cache metadata/version rules or visible output.
- Merge PackedScene and lightweight render Resource finalization generically.
## Paths
- Exclusive: `src/render/wmo/wmo_scene_resource_finalizer.gd`,
`src/tools/verify_wmo_scene_resource_finalizer.gd`,
`docs/modules/wmo-scene-resource-finalizer.md`, this claim
- Shared: loader, WMO scene cache state spec/verifier, world renderer registry/spec,
`RENDER.md`, M03 Evidence
## Contracts and data
- Pending requests are polled in detached Dictionary insertion order.
- Only LOADED/FAILED statuses complete requests.
- Failed, null, non-PackedScene, non-Node3D and stale scenes publish missing.
- Accepted PackedScenes retain exact Resource identity.
- Validation instantiates once and releases accepted/rejected Node3D probes once.
- Admission, oversize logging, live fallback and shutdown order are unchanged.
## Dependencies
- Requires: accepted WMO scene Resource cache state
- Blocks: remaining loader-owned WMO live fallback/materialization cleanup
## Verification
- Synthetic status/order/type/probe/validation/lifetime/adoption/source/timing
contracts; adjacent WMO/renderer gates; full suite and checkpoint dry-run.
## Documentation deliverables
- Inline API docs; module API/I/O/ownership; data-flow/state/sequence/dependency
diagrams; adjacent renderer docs and M03 Evidence.
## Status
- State: accepted
- Done: implementation, verification and documentation
- Next: next unclaimed M03 renderer extraction package
- Blocked by:
## Handoff
- Commit: `7779e9e` (`render: extract WMO scene resource finalizer`)
- Results: finalizer PASS `cases=26 iterations=1000 elapsed_ms=4.186`;
adjacent WMO scene/render cache/finalizer/queue/planner/registry/resolver and
shutdown checks passed; autonomous headless suite `61/61`; documentation `48`;
coordination passed with `34` historical warnings; checkpoint dry-run `7/7`.
- Remaining risks: PackedScene probe validation remains synchronous main-thread
work; no serialized private WMO stale/oversize, long leak, visual,
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.
- Merge: `6a0f9bd` (`merge: WMO scene resource finalizer`)
- Post-merge: finalizer PASS `cases=26 iterations=1000 elapsed_ms=3.970`;
WMO scene/render cache/finalizer/queue/planner/registry/resolver/shutdown,
facade, internal-access `30`, manifest and checkpoint dry-run `7/7`,
documentation `48` and coordination gates passed.
@@ -0,0 +1,95 @@
# M04-RND-FIDELITY-ROADMAP-001
<!-- OPENWC_CLAIM:M04-RND-FIDELITY-ROADMAP-001:sindo-main-codex-renderer-roadmap:2026-08-04 -->
<!-- OPENWC_INTEGRATION:ACCEPTED:M04-RND-FIDELITY-ROADMAP-001:afbc4fa -->
## Owner
- Agent ID: `sindo-main-codex-renderer-roadmap`
- Target: M04 Renderer Fidelity and Graphics Foundation
- Branch: `work/sindo-main-codex-renderer-roadmap/m04-renderer-fidelity-plan`
- Worktree: `C:\Users\sindo\open-wc-worktrees\m04-renderer-fidelity-plan`
## Outcome
Insert an evidence-driven renderer-fidelity milestone immediately after M03 and
shift the existing M04–M13 executable targets to M05–M14 without losing their
content, dependencies or status semantics.
## Non-goals
- Implement renderer behavior or claim visual parity in this package.
- Capture or commit proprietary original-client screenshots.
- Begin the shifted Editor, content, network or gameplay milestones.
## Paths
- Exclusive: `targets/04-renderer-fidelity.md`, this claim
- Shared/hotspot: all numbered target filenames and cross-target dependencies,
`targets/README.md`, `targets/DEVELOPMENT_ROADMAP.md`, `targets/roadmap/`,
`docs/ROADMAP.md`, renderer/testing/feature documentation where milestone IDs
or fidelity gates are normative
## Contracts and data
- M03 remains `DONE`; new M04 becomes the only `ACTIVE` target.
- Existing executable targets shift monotonically: old M04–M13 become M05–M14.
- The new renderer gate uses original build-12340 captures as the authoritative
oracle and treats Noggit as a secondary composition/reference tool.
- Proprietary images remain outside Git; manifests, metadata, hashes, metrics and
human approval records are repository evidence.
- `Blizzlike335` and opt-in enhanced/racing graphics remain explicit profiles.
## Dependencies
- Requires: completed M03 renderer facade and closeout contracts.
- Blocks: renderer-fidelity implementation and all shifted milestones.
## Verification
- Coordination and documentation gates; target marker/index consistency; link
and milestone-reference audit; `git diff --check`.
## Fidelity evidence
- Planning evidence only: exact local client build `3.3.5.12340`, seven existing
paired checkpoints and the user-proposed two-viewpoint-per-location corpus.
No new parity claim is made.
## Documentation deliverables
- Executable M04 target with reference, static, temporal, subsystem, performance
and acceptance gates; shifted target dependencies; development/subsystem
roadmap and testing/feature-map alignment.
## Status
- State: integrated
- Done: inserted M04 renderer fidelity, shifted old M04–M13 to M05–M14,
updated dependencies, normative roadmaps, profile boundaries and test policy
- Next: begin the M04 reference manifest/capture contract work package when the
user supplies or approves the original-client CSV/capture corpus
- Blocked by:
<!-- OPENWC_HANDOFF:READY:M04-RND-FIDELITY-ROADMAP-001:f77f50c -->
## Handoff
- Commit: `f77f50c` on
`work/sindo-main-codex-renderer-roadmap/m04-renderer-fidelity-plan`.
- Outcome: M03 remains `DONE`; new M04 is the only `ACTIVE` target; Editor,
content, server, world editor, network, gameplay, playable client, quest,
completeness and dungeon targets moved intact to M05–M14.
- Contracts: original build-12340 captures are authoritative; Noggit is a
secondary composition reference; proprietary pixels remain outside Git;
Blizzlike/Enhanced/Racing graphics profiles are explicit boundaries.
- Verification: coordination passed `targets=15 active=1`; documentation passed
`module_specs=53 required_files=7`; target sequence passed `M00–M14`; old
executable-target links `0`; `git diff --check` passed.
- Fidelity: planning and corpus provenance only; no renderer implementation or
new parity claim is included.
- Documentation: executable M04, target index/dependency graph, renderer
subsystem plan, high-level roadmap, architecture, testing/tooling/coding/
documentation policies and affected future milestone references updated.
- Cache/migration: no runtime/cache/data format changes. The exact CSV schema is
intentionally deferred to the first M04 contract work package.
+12
View File
@@ -9,6 +9,12 @@ OpenWC состоит из двух продуктов на общей плат
Оба продукта используют общие канонические модели, импорт данных и renderer preview, но не разделяют UI, lifecycle и права на изменение данных. Оба продукта используют общие канонические модели, импорт данных и renderer preview, но не разделяют UI, lifecycle и права на изменение данных.
Планируемый racing fork является третьим потребителем graphics foundation. Он
может использовать world streaming, materials, lighting, shadows, liquids,
characters, animation и effects contracts, но не зависит от WoW network,
gameplay state или proprietary asset repository. Его улучшения выбираются
отдельным `Racing` graphics profile и не меняют `Blizzlike335`.
## Архитектурные принципы ## Архитектурные принципы
1. Сервер авторитетен для боя, ресурсов, инвентаря, квестового прогресса и общего мира. 1. Сервер авторитетен для боя, ресурсов, инвентаря, квестового прогресса и общего мира.
@@ -57,6 +63,11 @@ TrinityCore/AzerothCore ◄── Network Adapter ◄── Runtime Client
Renderer получает `StreamingFocus`, `WorldVisualSnapshot` и presentation-команды. Он НЕ ДОЛЖЕН читать packets, SQL или gameplay input. Renderer получает `StreamingFocus`, `WorldVisualSnapshot` и presentation-команды. Он НЕ ДОЛЖЕН читать packets, SQL или gameplay input.
`GraphicsProfile` выбирается на composition boundary и задаёт material,
lighting, shadow, liquid, effects, distance и post-processing capabilities.
Внутренние shader/services не смешивают Blizzlike/Enhanced/Racing policy через
неявные глобальные switches.
### UI ### UI
Владеет login/realm/character screens, HUD и FrameXML/Lua-compatible presentation. UI читает immutable view models и отправляет intents. Lua API получает capability-based facade; прямой доступ к network, filesystem и editor API запрещён. Владеет login/realm/character screens, HUD и FrameXML/Lua-compatible presentation. UI читает immutable view models и отправляет intents. Lua API получает capability-based facade; прямой доступ к network, filesystem и editor API запрещён.
@@ -157,6 +168,7 @@ addons/
- `ServerSchemaAdapter` — inspect, import, diff, generate, validate. - `ServerSchemaAdapter` — inspect, import, diff, generate, validate.
- `ContentTypeDescriptor` — schema, inspector, validator, compiler. - `ContentTypeDescriptor` — schema, inspector, validator, compiler.
- `WorldRenderer` — streaming focus и entity presentation. - `WorldRenderer` — streaming focus и entity presentation.
- `GraphicsProfile` — explicit Blizzlike/Enhanced/Racing visual capabilities.
- `GameplaySystem` — commands/events без scene dependency. - `GameplaySystem` — commands/events без scene dependency.
- `EditorTool` — selection, command creation и gizmo, без прямой записи. - `EditorTool` — selection, command creation и gizmo, без прямой записи.
- `TestFixtureProvider` — обезличенные packets, DB snapshots и content fixtures. - `TestFixtureProvider` — обезличенные packets, DB snapshots и content fixtures.
+1 -1
View File
@@ -197,7 +197,7 @@ func request_streaming_tile_load(request: StreamingTileLoadRequest) -> void:
- Предпочитать именованные predicates длинным boolean expressions. - Предпочитать именованные predicates длинным boolean expressions.
- Branch по capability/profile должен быть локальным и типизированным. - Branch по capability/profile должен быть локальным и типизированным.
- Не распространять `if core == "azerothcore"` по проекту; использовать adapter/capability. - Не распространять `if core == "azerothcore"` по проекту; использовать adapter/capability.
- Не смешивать Blizzlike и Enhanced branches в каждом shader/service: выбирать profile/strategy на boundary. - Не смешивать Blizzlike, Enhanced и Racing branches в каждом shader/service: выбирать profile/strategy на boundary.
- Pattern/table-driven mapping предпочтительнее сотен одинаковых `if`, если таблица остаётся читаемой и валидируемой. - Pattern/table-driven mapping предпочтительнее сотен одинаковых `if`, если таблица остаётся читаемой и валидируемой.
## Comments ## Comments
+1 -1
View File
@@ -181,7 +181,7 @@ stateDiagram-v2
- Uniform/global parameter: coordinate/color space, range, units и producer. - Uniform/global parameter: coordinate/color space, range, units и producer.
- Material profile: supported WoW shader/blend modes и approximations. - Material profile: supported WoW shader/blend modes и approximations.
- Expensive branch/texture dependency имеет cost/fallback note. - Expensive branch/texture dependency имеет cost/fallback note.
- Blizzlike и Enhanced behavior документируются отдельно. - Blizzlike, Enhanced и Racing behavior документируются отдельно.
### Network codecs ### Network codecs
+1 -1
View File
@@ -61,7 +61,7 @@ Gameplay state, network session, renderer world, editor session, caches конк
- Visibility ranges/HLOD, occlusion и automatic mesh LOD использовать совместно по профилю. - Visibility ranges/HLOD, occlusion и automatic mesh LOD использовать совместно по профилю.
- Unique materials/textures минимизировать; descriptor/resource counts являются budget metric. - Unique materials/textures минимизировать; descriptor/resource counts являются budget metric.
- RenderingServer RIDs имеют явного владельца и освобождаются в deterministic shutdown test. - RenderingServer RIDs имеют явного владельца и освобождаются в deterministic shutdown test.
- Shader/material profiles разделяют Blizzlike и Enhanced; runtime не компилирует тяжёлые варианты при пересечении ADT boundary. - Shader/material profiles разделяют Blizzlike, Enhanced и Racing; runtime не компилирует тяжёлые варианты при пересечении ADT boundary.
## EditorPlugin lifecycle ## EditorPlugin lifecycle
+15 -1
View File
@@ -2,6 +2,11 @@
Reference-код используется для исследования форматов, поведения и архитектурных вариантов. Он не определяет API OpenWC и не копируется без проверки лицензии, корректности и соответствия Godot. Reference-код используется для исследования форматов, поведения и архитектурных вариантов. Он не определяет API OpenWC и не копируется без проверки лицензии, корректности и соответствия Godot.
Актуальные branches и pinned commits локальных Git-референсов зафиксированы в
[`reference/README.md`](../reference/README.md#git-reference-revisions). Gitlinks
точно фиксируют ревизию, а `.gitmodules` задаёт canonical remote и ветку
для контролируемого обновления.
## Основные источники ## Основные источники
### OpenWC renderer ### OpenWC renderer
@@ -17,12 +22,13 @@ Reference-код используется для исследования фор
### WoWee ### WoWee
- Исследование обновлено до `master` commit `607ea3b8369851014721416293f8e95dfbe64eec` (2026-09-05), относительно прежнего reviewed pin `8456c236b57140e98667d6d8188f5cd1cc226daf`.
- `reference/WoWee/docs/architecture.md` — разделение renderer/network/game/UI/pipeline. - `reference/WoWee/docs/architecture.md` — разделение renderer/network/game/UI/pipeline.
- `reference/WoWee/tools/editor/FORMAT_SPEC.md` — open formats, coordinates, collision, packaging и SQL export. - `reference/WoWee/tools/editor/FORMAT_SPEC.md` — open formats, coordinates, collision, packaging и SQL export.
- `reference/WoWee/TESTING.md` — единая точка запуска тестов, fixtures, sanitizers и CI discipline. - `reference/WoWee/TESTING.md` — единая точка запуска тестов, fixtures, sanitizers и CI discipline.
- `reference/WoWee/EXPANSION_GUIDE.md` — protocol/data profile separation. - `reference/WoWee/EXPANSION_GUIDE.md` — protocol/data profile separation.
Используем: детерминированные authoring formats, headless parity, adapter profiles и validation-first pipeline. Не принимаем автоматически конкретные форматы или заявленную полноту реализации. Особенно полезны: WotLK M2 track/footstep fixtures, liquid mask/surface-grid tests, shader-interface checks, retained widget tree, единый XML→Lua `CreateFrame` path, поэлементный takeover default UI и большая система headless/static FrameXML/Lua audits. Не переносим монолитный Lua/game binding, unknown-API fallback, Vulkan-specific ownership или native-padding `.w*` formats. Particle/render rules, placement rotations и UI semantics требуют независимых build-12340 fixtures; часть real-asset tests WoWee пропускает отсутствие assets как success. Лицензия содержит дополнительный запрет commercial-game use, поэтому WoWee остаётся research-only reference без копирования или вендоринга кода. Полная evaluation card: [`TOOLING_CATALOG.md`](TOOLING_CATALOG.md#wowee--карточка-референса).
### Noggit Red ### Noggit Red
@@ -51,6 +57,14 @@ Reference-код используется для исследования фор
Не переносим напрямую старый browser stack, WebSocket proxy, React/Three.js abstractions или pipeline server. Заявленное поведение проверяем по TrinityCore/AzerothCore и оригинальному клиенту; proof-of-concept не является спецификацией полноты. Не переносим напрямую старый browser stack, WebSocket proxy, React/Three.js abstractions или pipeline server. Заявленное поведение проверяем по TrinityCore/AzerothCore и оригинальному клиенту; proof-of-concept не является спецификацией полноты.
### Benilla
- [`reference/benilla`](../reference/benilla) ([upstream](https://github.com/samwhosung/benilla)) — pinned research submodule с независимым клиентом WoW 1.12.1 build 5875 на Rust/Bevy; исследование OpenWC зафиксировано на commit `bc1a2428dd7e00ca8abbfb8f0bf53750dae7b123`.
- Renderer references: M2 pose/palette animation, global sequences, material/pass ordering, общий dynamic-effect stream, particles, model particles, ribbons, WMO portal visibility, lighting, sky/weather и streaming.
- UI references: отдельный engine-free TOC/FrameXML/Lua core, template/layout/widget model, deterministic event/`OnUpdate` ordering, legacy+modern handler arguments, sandbox, SavedVariables и большой набор compatibility tests.
Используем decomposition, algorithms, diagnostics и test ideas. Не переносим Bevy/ECS architecture и не считаем Vanilla behavior доказательством WotLK: M2/DBC/layout/API/security differences повторно проверяются по WoW 3.3.5a build 12340. Benilla пока не является oracle для third-party addons или secure/taint semantics; его Lua 5.1 runtime содержит compatibility work для Lua 5.0 target. Полная evaluation card находится в [`TOOLING_CATALOG.md`](TOOLING_CATALOG.md#benilla--карточка-референса).
### recast-rs ### recast-rs
- [`wowemulation-dev/recast-rs`](https://github.com/wowemulation-dev/recast-rs) — Rust-порт Recast/Detour: navmesh generation, tiled pathfinding, spatial queries, crowd simulation и dynamic obstacles. - [`wowemulation-dev/recast-rs`](https://github.com/wowemulation-dev/recast-rs) — Rust-порт Recast/Detour: navmesh generation, tiled pathfinding, spatial queries, crowd simulation и dynamic obstacles.
+27 -14
View File
@@ -18,7 +18,20 @@
Готово, когда render sandbox сохраняет текущее качество, а domain тестируется без scene tree. Готово, когда render sandbox сохраняет текущее качество, а domain тестируется без scene tree.
## M1 — Content Project и Editor shell ## M1 — Renderer fidelity и graphics foundation
- Original-client build-12340 corpus: две базовые позиции выбранной локации,
settings/time/weather/camera metadata, static и temporal evidence.
- Точные color, terrain, M2/WMO material, sky/fog/weather и shadow rules.
- MH2O/MCLQ/MLIQ liquids, GPU animation, characters, particles и ribbons.
- Раздельные `Blizzlike335` и opt-in `Enhanced/Racing` graphics profiles.
- Paired semantic-region comparison, human approval и traversal/GPU budgets.
Готово, когда утверждённая capture matrix не содержит неклассифицированных
визуально различимых gaps, а общие graphics contracts пригодны клиенту, Editor
preview и racing fork.
## M2 — Content Project и Editor shell
- `addons/openwc_editor` с workspace и docks. - `addons/openwc_editor` с workspace и docks.
- Content Project schema, stable IDs, save/load/migration. - Content Project schema, stable IDs, save/load/migration.
@@ -28,7 +41,7 @@
Готово, когда небольшой synthetic project можно создать, изменить, undo, перезапустить Editor и получить идентичное состояние. Готово, когда небольшой synthetic project можно создать, изменить, undo, перезапустить Editor и получить идентичное состояние.
## M2 — Server inspector и adapters ## M3 — Server inspector и adapters
- TrinityCore/AzerothCore connection profiles. - TrinityCore/AzerothCore connection profiles.
- Schema detection и capabilities. - Schema detection и capabilities.
@@ -38,7 +51,7 @@
Готово, когда одна сущность round-trip проходит оба поддерживаемых adapter profile без молчаливой потери данных. Готово, когда одна сущность round-trip проходит оба поддерживаемых adapter profile без молчаливой потери данных.
## M3 — World Editor MVP ## M4 — World Editor MVP
- Map viewport и coordinate overlays. - Map viewport и coordinate overlays.
- Server spawn visualization. - Server spawn visualization.
@@ -48,7 +61,7 @@
Готово, когда NPC размещается в Editor и появляется на тестовом core в ожидаемой позиции. Готово, когда NPC размещается в Editor и появляется на тестовом core в ожидаемой позиции.
## M4 — Quest vertical slice ## M5 — Quest vertical slice
- Quest form и chain graph. - Quest form и chain graph.
- Kill/collect/explore objectives, giver/ender, rewards и localization. - Kill/collect/explore objectives, giver/ender, rewards и localization.
@@ -57,7 +70,7 @@
Готово, когда созданный в Editor квест полностью проходится клиентом. Готово, когда созданный в Editor квест полностью проходится клиентом.
## M5 — Playable network client ## M6 — Playable network client
- Auth, realm, character selection и world session. - Auth, realm, character selection и world session.
- Entity/update fields и world spawn. - Entity/update fields и world spawn.
@@ -66,7 +79,7 @@
Готово, когда клиент стабильно входит в мир, перемещается и видит синхронизированные entities. Готово, когда клиент стабильно входит в мир, перемещается и видит синхронизированные entities.
## M6 — Core gameplay ## M7 — Core gameplay
- Combat/spells/auras/death. - Combat/spells/auras/death.
- Inventory/equipment/loot/vendors. - Inventory/equipment/loot/vendors.
@@ -76,7 +89,7 @@
Готово, когда базовый leveling loop проходит без внешнего клиента. Готово, когда базовый leveling loop проходит без внешнего клиента.
## M7 — Dungeon authoring ## M8 — Dungeon authoring
- DungeonPackage, encounters, triggers, doors и spawn groups. - DungeonPackage, encounters, triggers, doors и spawn groups.
- SmartAI/script skeleton generation. - SmartAI/script skeleton generation.
@@ -85,7 +98,7 @@
Готово, когда custom dungeon собирается, разворачивается и проходится группой на test core. Готово, когда custom dungeon собирается, разворачивается и проходится группой на test core.
## M8 — Compatibility и completeness ## M9 — Compatibility и completeness
- Feature matrix WoW 3.3.5a. - Feature matrix WoW 3.3.5a.
- Addon compatibility tiers. - Addon compatibility tiers.
@@ -98,17 +111,17 @@
При равной ценности порядок такой: При равной ценности порядок такой:
1. безопасность данных и воспроизводимость; 1. безопасность данных и воспроизводимость;
2. корректность протокола и authoritative state; 2. original-client renderer fidelity и frame pacing текущего M04;
3. пользовательский vertical slice; 3. корректность протокола и authoritative state;
4. diagnostics и testability; 4. пользовательский vertical slice;
5. frame pacing; 5. diagnostics и testability;
6. визуальная точность и polish. 6. opt-in визуальные улучшения после Blizzlike evidence.
## Не делать раньше времени ## Не делать раньше времени
- прямую запись в production DB; - прямую запись в production DB;
- универсальный visual scripting для любой C++ механики; - универсальный visual scripting для любой C++ механики;
- массовую реализацию Lua API без работающего UI slice; - массовую реализацию Lua API без работающего UI slice;
- большой rewrite существующего renderer; - полный custom renderer до bounded shader/backend spike и profiler evidence;
- multi-expansion abstraction до устойчивого профиля 3.3.5a; - multi-expansion abstraction до устойчивого профиля 3.3.5a;
- proprietary asset packaging в репозитории. - proprietary asset packaging в репозитории.
+3 -3
View File
@@ -44,8 +44,8 @@ ID записывается в claim, ветке, PR/MR и handoff. Нельзя
```text ```text
M01-FND-COORDS-001 M01-FND-COORDS-001
M03-RND-SCHEDULER-001 M03-RND-SCHEDULER-001
M08-NET-SRP-001 M09-NET-SRP-001
M12-UIA-LUA-SPIKE-001 M13-UIA-LUA-SPIKE-001
``` ```
Program codes определены в [`../targets/DEVELOPMENT_ROADMAP.md`](../targets/DEVELOPMENT_ROADMAP.md). Program codes определены в [`../targets/DEVELOPMENT_ROADMAP.md`](../targets/DEVELOPMENT_ROADMAP.md).
@@ -214,7 +214,7 @@ Merge order:
```text ```text
fnd(M01): add canonical coordinate mapper fnd(M01): add canonical coordinate mapper
net(M08): decode auth challenge safely net(M09): decode auth challenge safely
rnd(M03): extract streaming target planner rnd(M03): extract streaming target planner
test(M00): add paired checkpoint manifest test(M00): add paired checkpoint manifest
``` ```
+36
View File
@@ -54,6 +54,42 @@
- dense WMO/M2, water, character equipment и UI scale matrices. - dense WMO/M2, water, character equipment и UI scale matrices.
- navmesh overlay checkpoints и bake/query budgets для больших tiles/dungeons. - navmesh overlay checkpoints и bake/query budgets для больших tiles/dungeons.
Для M03 renderer closeout сравнение выполняется на точных M00/M03 revisions с
одинаковыми viewport, rendering backend и полным cache inventory. Короткий
протокол агрегирует повторные captures медианой каждого показателя; независимый
протокол использует увеличенное десятисекундное окно. Регрессия считается
воспроизводимой, только если один и тот же checkpoint/pass/metric превышает
неизменённый 10% budget в обоих протоколах:
```powershell
tools/compare_render_performance.ps1 -BaselineReport <m00-reports> -CandidateReport <m03-reports> -OutputReport <repeated.json>
tools/compare_render_performance.ps1 -BaselineReport <m00-long.json> -CandidateReport <m03-long.json> -OutputReport <long.json>
tools/verify_render_performance_stability.ps1 -RepeatedSampleComparison <repeated.json> -LongWindowComparison <long.json> -OutputReport <stability.json>
```
Локальные превышения одного протокола сохраняются как diagnostics; gate падает
только на повторяемой регрессии. Полный контракт и схема evidence описаны в
[`modules/renderer-closeout-verification.md`](modules/renderer-closeout-verification.md).
M04 renderer fidelity использует оригинальный клиент build 12340 как
authoritative visual oracle. Noggit допускается как дополнительный reference
композиции/placements и Editor UX, но не подтверждает lighting, shadows, liquids,
materials, animation или effects.
Reference corpus импортируется через versioned CSV manifest. Для каждой
выбранной локации базово снимаются `wide` и `ground` viewpoints; отдельные
specialized captures добавляются для уникальных interior, liquid, shadow,
character и effect policies. Capture metadata фиксирует WoW/server coordinates,
доступные camera fields, time, weather, graphics profile, viewport, artifact
name и provenance. Отсутствующее значение хранится как `Unknown`, а не
восстанавливается предположением.
Proprietary screenshots/video остаются вне Git. Repository evidence включает
schema, SHA-256, static/temporal metrics, semantic region classification и human
approval. Static parity требует согласованных geometry/framing и material/light/
shadow/liquid regions; temporal parity отдельно проверяет phase, duration,
trajectory, UV motion и emitter lifetime.
### Navigation compatibility ### Navigation compatibility
- Golden synthetic meshes проверяют slope, climb, radius erosion, holes, tiled seams и off-mesh connections. - Golden synthetic meshes проверяют slope, climb, radius erosion, holes, tiled seams и off-mesh connections.
+40 -2
View File
@@ -40,13 +40,15 @@ Decision/ADR:
| OpenWC native loaders | ADOPTED | MPQ/BLP/ADT/WDT/M2/WMO | Текущий import/render pipeline | Неполная fidelity форматов | | OpenWC native loaders | ADOPTED | MPQ/BLP/ADT/WDT/M2/WMO | Текущий import/render pipeline | Неполная fidelity форматов |
| StormLib | ADOPTED | MPQ | Чтение архивов через native extension | Version/license/update audit | | StormLib | ADOPTED | MPQ | Чтение архивов через native extension | Version/license/update audit |
| WowUnreal | REFERENCE | Полный клиент | Coverage, acceptance criteria, networking/UI research | Unreal-specific design | | WowUnreal | REFERENCE | Полный клиент | Coverage, acceptance criteria, networking/UI research | Unreal-specific design |
| WoWee | REFERENCE | Клиент/editor/formats | Architecture, editor workflows, tests, open formats | Заявления требуют независимой проверки | | WoWee | REFERENCE | WotLK client/render/UI/editor | M2/liquid fixtures, retained FrameXML UI, Lua diagnostics, progressive UI takeover и authoring validation | Modified MIT запрещает commercial-game use; fidelity claims требуют build-12340 oracle |
| Noggit Red | REFERENCE | World editor | Terrain/placement UX, UID workflows | Не Godot architecture | | Original WoW 3.3.5a build 12340 client | ADOPTED | Visual/behavior oracle | Private static/temporal capture corpus, settings and paired comparison | Proprietary artifacts remain outside Git; camera/settings provenance must be explicit |
| Noggit Red | REFERENCE | World editor | Terrain/placement UX, UID workflows и secondary render-composition reference | Не Godot architecture; не authoritative lighting/material/shadow/liquid oracle |
| open-realm | REFERENCE | Formats/runtime | Независимая проверка parsers/render behavior | Другая архитектура и coverage | | open-realm | REFERENCE | Formats/runtime | Независимая проверка parsers/render behavior | Другая архитектура и coverage |
| whoa | REFERENCE | Client behavior | 3.3.5a runtime semantics и fixtures | Лицензия и переносимость отдельных решений | | whoa | REFERENCE | Client behavior | 3.3.5a runtime semantics и fixtures | Лицензия и переносимость отдельных решений |
| wow.export | REFERENCE | Asset conversion | M2/WMO/material/export edge cases | Web-specific pipeline | | wow.export | REFERENCE | Asset conversion | M2/WMO/material/export edge cases | Web-specific pipeline |
| Blender WoW Studio | REFERENCE | Authoring/conversion | WMO/M2/ADT authoring knowledge | Blender-specific UI/data model | | Blender WoW Studio | REFERENCE | Authoring/conversion | WMO/M2/ADT authoring knowledge | Blender-specific UI/data model |
| [Wowser](https://github.com/wowserhq/wowser) | REFERENCE | 3.3.5a web client | Auth/realm/character/world protocol, binary parsing, asset pipeline и render research | Старый JS/WebGL proof-of-concept, неполный клиент | | [Wowser](https://github.com/wowserhq/wowser) | REFERENCE | 3.3.5a web client | Auth/realm/character/world protocol, binary parsing, asset pipeline и render research | Старый JS/WebGL proof-of-concept, неполный клиент |
| [Benilla](https://github.com/samwhosung/benilla) | REFERENCE | WoW 1.12.1 client/render/UI | M2 GPU animation, materials, particles/ribbons, WMO portals, FrameXML/Lua architecture и compatibility-test ideas | Vanilla build 5875 и Bevy-specific implementation не доказывают WotLK build-12340 fidelity |
| [warcraft-rs](https://github.com/wowemulation-dev/warcraft-rs) | CANDIDATE | WoW formats/CLI | Independent MPQ/DBC/BLP/ADT/WDT/WDL/M2/WMO validation и conversion oracle | Rust/tool duplication; claims require fixtures | | [warcraft-rs](https://github.com/wowemulation-dev/warcraft-rs) | CANDIDATE | WoW formats/CLI | Independent MPQ/DBC/BLP/ADT/WDT/WDL/M2/WMO validation и conversion oracle | Rust/tool duplication; claims require fixtures |
| [WoWDBDefs](https://github.com/wowdev/WoWDBDefs) | REFERENCE | Client DB schemas | Versioned DBC definitions и typed-code generation input | Definitions still require build-specific validation | | [WoWDBDefs](https://github.com/wowdev/WoWDBDefs) | REFERENCE | Client DB schemas | Versioned DBC definitions и typed-code generation input | Definitions still require build-specific validation |
| [wow_dbc](https://github.com/gtker/wow_dbc) | REFERENCE | DBC | 1.12/2.4.3/3.3.5 read/write и SQLite conversion ideas | Older release, Rust integration unnecessary by default | | [wow_dbc](https://github.com/gtker/wow_dbc) | REFERENCE | DBC | 1.12/2.4.3/3.3.5 read/write и SQLite conversion ideas | Older release, Rust integration unnecessary by default |
@@ -57,6 +59,23 @@ Decision/ADR:
| [Keira3](https://github.com/azerothcore/Keira3) | REFERENCE | AzerothCore DB editor | Field semantics, SQL generation и DB editor UX | AGPL; schema-specific web architecture | | [Keira3](https://github.com/azerothcore/Keira3) | REFERENCE | AzerothCore DB editor | Field semantics, SQL generation и DB editor UX | AGPL; schema-specific web architecture |
| [WowBench](https://sourceforge.net/projects/wowbench/) | REFERENCE | WoW UI/API | Offline XML/Lua API emulation и addon test ideas | Старый и неполный implementation | | [WowBench](https://sourceforge.net/projects/wowbench/) | REFERENCE | WoW UI/API | Offline XML/Lua API emulation и addon test ideas | Старый и неполный implementation |
## WoWee — карточка референса
- **Name / URL:** [WoWee](https://github.com/WoWee-Dev/WoWee), локально `reference/WoWee`.
- **Status:** `REFERENCE`; код не подключается как dependency и не вендорится.
- **Reviewed update:** `master` от `626243e937fb93965fa583a6507ed5a1aa7dda4b` до `8456c236b57140e98667d6d8188f5cd1cc226daf` (2026-08-12): 2557 commits. Raw diff содержит 1622 files, 164940 additions и 1595923 deletions, но объём deletions в основном создают удалённые generated SQL/data/build artifacts; полезный signal сосредоточен в renderer, UI/FrameXML, pipeline и tests.
- **Checkout refresh:** текущий upstream checkout — `607ea3b8369851014721416293f8e95dfbe64eec` (2026-09-05). Это обновление pins не является новой fidelity evaluation; релевантные изменения требуют отдельного bounded review перед использованием.
- **Renderer findings:** новый общий M2 track sampler отделяет global-sequence time от animation sequence и добавляет finite-value guards, но Hermite/Bezier пока линейно аппроксимируются. Централизованный M2+skin/external-`.anim` loader и selective animation loading полезны как pattern для границы parser/resolver. Новые water-mask и water-surface-grid tests фиксируют LSB-first 8x8 chunk masks, solid fallback, rotated WMO liquid projection, inclusive far edge и degenerate axes. Vertex-layout tests сверяют CPU declarations с shader inputs. GPU lifetime/deferred-release решения полезны концептуально, но Vulkan implementation не переносится в Godot.
- **Animation/effects evidence:** WotLK asset tests покрывают color/alpha tracks, независимый wrap global sequences и `$FSD` footstep events. Отсутствующие private assets в части тестов дают `SUCCEED`, поэтому зелёный run не доказывает, что fixture реально исполнялся. Particle/ribbon delta в основном добавляет sampling, batching, descriptor reuse и diagnostics; flame/smoke/ribbon поведение содержит эвристики и не заменяет build-12340 capture oracle. Для архитектуры shared effect stream Benilla остаётся более сильным secondary reference.
- **Placement warning:** WoWee прямо оставляет rotation order наклонённых doodad placements нерешённым; имеющийся test различает только upright yaw. OpenWC не меняет calibrated transforms без tilted `MDDF`/`MODF` fixture из build 12340.
- **UI/FrameXML findings:** retained widget tree отделён от renderer, сохраняет WoW bottom-left/y-up coordinates до единственного draw-boundary flip и покрывает anchors, draw order, hit testing, visibility, scroll, controls и ownership headless tests. XML компилируется в Lua и проходит тот же `CreateFrame`/template path, что Lua-created widgets. Progressive takeover передаёт отдельные default-UI элементы FrameXML с явными bridges для portrait/model/minimap/world-map content. Это сильная модель поэтапной миграции M05, но не готовая архитектура OpenWC.
- **Lua/tooling findings:** runtime ограничивает стандартные библиотеки, ставит instruction-hook timeout, копирует listener list перед dispatch, ограничивает event recursion, нормализует event argument types и диагностирует stack/source/line. Большой headless corpus и статические audits проверяют XML emission, templates, `$parent`, handler arity, event order/arity, missing APIs, nil arithmetic, globals и keybinding takeover. Однако `lua_engine.cpp` монолитен, unknown-API fallback маскирует отсутствующие контракты, а настоящих taint/secure execution semantics нет; OpenWC сохраняет отдельный `LuaRuntime`, view models/intents и fail-closed compatibility tiers.
- **Pipeline/editor findings:** общий bounded binary-I/O layer, finite vertex sanitization, DXT block tests, streaming manifest parse и единые validation/save reports полезны как test/validation patterns. Собственные `.w*` formats зависят от native padding/endianness и не подходят как canonical portable OpenWC artifacts без отдельной спецификации.
- **License/data risk:** repository LICENSE — MIT с дополнительным запретом использовать software как основу или компонент commercial video games без письменного разрешения; original music имеет отдельный all-rights-reserved notice. Поэтому допустимы research, decomposition и независимо реализованные fixtures; копирование/вендоринг кода требует предварительного legal review.
- **Fidelity limits:** проект WotLK-aware и использует build 12340, но остаётся active WIP. В исследованном diff нет систематического paired original-client visual corpus; часть tests повторяет internal implementation, а README перечисляет runtime regressions. WoWee — источник гипотез и test cases, не oracle. Oracle OpenWC остаётся оригинальный клиент 3.3.5a build 12340.
- **Bounded adoption:** для M04 приоритетны обязательные (не silent-skip) build-12340 fixtures для M2 global-sequence/color-alpha sampling, MH2O/MCLQ/MLIQ masks и rotated WMO water, CPU-mesh/shader interface verification и tilted placement order. Для M05 — engine-free widget/layout core, единый XML/Lua creation path, progressive takeover и headless audit taxonomy. Любое поведение принимается только после paired capture/fixture с provenance и hash.
- **Decision / ADR:** `REFERENCE`, не dependency. ADR нужен, если WoWee-inspired решение меняет публичный renderer/UI/Lua contract, artifact schema, engine boundary или вводит third-party code.
## rilua — карточка кандидата ## rilua — карточка кандидата
- **Problem solved:** Lua 5.1.1 VM, bytecode, embedding и официальный compatibility corpus для addon runtime. - **Problem solved:** Lua 5.1.1 VM, bytecode, embedding и официальный compatibility corpus для addon runtime.
@@ -77,6 +96,25 @@ Decision/ADR:
- **Known gaps:** proof-of-concept, неполный gameplay/UI, browser WebSocket proxy вместо native TCP, устаревший JS ecosystem (React 0.14/Three.js 0.77 era), архитектура не переносится напрямую в Godot. - **Known gaps:** proof-of-concept, неполный gameplay/UI, browser WebSocket proxy вместо native TCP, устаревший JS ecosystem (React 0.14/Three.js 0.77 era), архитектура не переносится напрямую в Godot.
- **Decision policy:** использовать для cross-check и test ideas; не добавлять Node/Web dependencies и не копировать browser-specific abstractions в OpenWC. - **Decision policy:** использовать для cross-check и test ideas; не добавлять Node/Web dependencies и не копировать browser-specific abstractions в OpenWC.
## Benilla — карточка референса
- **Name / URL:** [samwhosung/benilla](https://github.com/samwhosung/benilla), локально `reference/benilla`.
- **Status:** `REFERENCE`; исходники доступны как pinned research submodule, но не подключены к build/runtime как dependency и не вендорятся.
- **Pinned research revision:** `bc1a2428dd7e00ca8abbfb8f0bf53750dae7b123` (проверен 2026-09-05). Репозиторий публикуется автором как squashed snapshots из private tree, поэтому при повторном исследовании revision фиксируется заново.
- **Problem studied:** полный независимый клиент WoW 1.12.1 build 5875 на Rust/Bevy, включая streamed world renderer, M2 animation/materials, WMO portal visibility, liquids/sky/weather, particles/ribbons/spell visuals и собственный TOC/FrameXML/Lua UI runtime.
- **Planned boundary:** использовать только как независимый источник алгоритмов, decomposition, diagnostics и test cases. OpenWC сохраняет свои `WorldRenderFacade`, Godot main-thread/finalization boundaries, `LuaRuntime`, immutable UI view models/intents и build-12340 contracts; прямой Bevy/ECS или Rust-type bridge не вводится.
- **Useful renderer source map:** `crates/benilla-world/src/model_render*`, `rig_anim*`, `rig_palette*`, `particles*`, `ribbons.rs`, `wmo_portal*`, `lighting*`, `weather*`, `terrain_stream*` и `shaders/wow_effect.wgsl`; format-side M2 animation/particle/ribbon tracks находятся в `crates/benilla-formats/src/models/anim.rs`, `particles.rs` и `ribbons.rs`.
- **Useful UI/Lua source map:** engine-free crate `crates/benilla-ui`: `toc.rs`, `framexml.rs`, `loader/*`, `layout.rs`, `widget/*`, `order.rs`, `script/event.rs`, `script/tick.rs`, `script/saved.rs`, sandbox/stdlib bindings и compatibility tests. Особенно полезны сохранение document/load order, bottom-up `OnLoad`, nested-safe restore legacy globals `this/event/argN`, deterministic event/`OnUpdate` order и разделение plain host state от engine adapter.
- **License:** `MIT OR Apache-2.0`; конкретное заимствование кода требует сохранения license/attribution и отдельной проверки совместимости с лицензиями OpenWC и third-party dependencies.
- **Platforms/toolchain:** stable Rust, Bevy `0.18.1`, `mlua 0.11` с vendored Lua 5.1 и локальным `lua-src` patch pipeline. Это research stack, не предлагаемый OpenWC toolchain.
- **Fidelity evidence:** исходники содержат подробные build-5875 byte-law annotations, corpus tests, parser fixtures и temporal/render probes. Сам приватный reverse-engineering corpus не входит в repository, а опубликованный код ориентирован на Vanilla 1.12.1, поэтому ни один его результат сам по себе не является evidence для WoW 3.3.5a build 12340.
- **Known gaps:** README прямо ограничивает Lua built-in UI и пока не заявляет third-party addon support; taint/secure execution отсутствуют; runtime использует Lua 5.1 с точечными Lua-5.0 compatibility patches для Vanilla; renderer и scheduling зависят от Bevy render phases/ECS. Layouts, M2 records, shader flags, spell visuals, UI API и security semantics должны повторно проверяться для WotLK.
- **Security/data risks:** Lua sandbox удаляет filesystem/OS/package/debug/native reach и принимает только text chunks, но без taint/protected-action модели этого недостаточно для 3.3.5a addons. Proprietary client assets и private RE artifacts не копируются; тесты OpenWC используют легально полученные локальные data и разрешённые metadata/fixtures.
- **Performance evidence:** Benilla документирует collapsed rig pose/palette arrays вместо десятков тысяч bone entities и один shared effect vertex/index stream вместо per-emitter dynamic meshes; source comments приводят локальные measurements (включая около 145 mesh changes/frame до shared stream). Эти числа не воспроизведены на Godot и служат только гипотезой для bounded spike.
- **Spike and acceptance criteria:** (1) renderer — на build-12340 M2 fixtures сравнить bone palette/global sequences, material ordering, particle plane/sphere/spline emitters, head/tail/model particles и ribbons по phase/duration/lifetime; затем проверить Godot-friendly shared effect buffer/pool без нарушения M03 budgets; (2) UI — прогнать WotLK TOC/FrameXML/Lua corpus на document order, inheritance, anchors, handler calling convention, event order, `OnUpdate`, SavedVariables, restricted libraries и error text; secure actions/taint имеют отдельный обязательный corpus. Любое принятое решение требует original-client paired evidence, p50/p95/p99 и regression fixtures.
- **Pinned version/update policy:** исследование и ссылки привязаны к указанному commit. Обновление проводится вручную по diff релевантных directories; moving `main` не становится новым oracle автоматически.
- **Decision / ADR:** `REFERENCE`, не dependency. ADR требуется только если Benilla-inspired решение меняет публичный renderer/effects/UI/Lua contract, cache/schema, engine boundary или добавляет dependency.
## recast-rs — карточка кандидата ## recast-rs — карточка кандидата
- **Problem solved:** Recast navmesh generation, Detour queries, tiled navigation и dynamic obstacles. - **Problem solved:** Recast navmesh generation, Detour queries, tiled navigation и dynamic obstacles.
+14
View File
@@ -21,6 +21,11 @@
| M2 placement transform resolver | Implemented | [`m2-placement-transform-resolver.md`](m2-placement-transform-resolver.md) | | M2 placement transform resolver | Implemented | [`m2-placement-transform-resolver.md`](m2-placement-transform-resolver.md) |
| M2 placement grouper | Implemented extraction | [`m2-placement-grouper.md`](m2-placement-grouper.md) | | M2 placement grouper | Implemented extraction | [`m2-placement-grouper.md`](m2-placement-grouper.md) |
| M2 build batch planner | Implemented extraction | [`m2-build-batch-planner.md`](m2-build-batch-planner.md) | | M2 build batch planner | Implemented extraction | [`m2-build-batch-planner.md`](m2-build-batch-planner.md) |
| M2 build dispatch planner | Implemented extraction | [`m2-build-dispatch-planner.md`](m2-build-dispatch-planner.md) |
| M2 build resource snapshot | Implemented extraction | [`m2-build-resource-snapshot.md`](m2-build-resource-snapshot.md) |
| M2 static build resource observer | Implemented extraction | [`m2-static-build-resource-observer.md`](m2-static-build-resource-observer.md) |
| M2 cached animation resource observer | Implemented extraction | [`m2-cached-animation-resource-observer.md`](m2-cached-animation-resource-observer.md) |
| M2 native animation resource observer | Implemented extraction | [`m2-native-animation-resource-observer.md`](m2-native-animation-resource-observer.md) |
| M2 build queue | Implemented extraction | [`m2-build-queue.md`](m2-build-queue.md) | | M2 build queue | Implemented extraction | [`m2-build-queue.md`](m2-build-queue.md) |
| M2 static batch materializer | Implemented extraction | [`m2-static-batch-materializer.md`](m2-static-batch-materializer.md) | | M2 static batch materializer | Implemented extraction | [`m2-static-batch-materializer.md`](m2-static-batch-materializer.md) |
| M2 runtime mesh rebuild classifier | Implemented extraction | [`m2-runtime-mesh-rebuild-classifier.md`](m2-runtime-mesh-rebuild-classifier.md) | | M2 runtime mesh rebuild classifier | Implemented extraction | [`m2-runtime-mesh-rebuild-classifier.md`](m2-runtime-mesh-rebuild-classifier.md) |
@@ -28,9 +33,11 @@
| M2 animation playback controller | Implemented extraction | [`m2-animation-playback-controller.md`](m2-animation-playback-controller.md) | | M2 animation playback controller | Implemented extraction | [`m2-animation-playback-controller.md`](m2-animation-playback-controller.md) |
| M2 animated instance materializer | Implemented extraction | [`m2-animated-instance-materializer.md`](m2-animated-instance-materializer.md) | | M2 animated instance materializer | Implemented extraction | [`m2-animated-instance-materializer.md`](m2-animated-instance-materializer.md) |
| M2 animation load pipeline state | Implemented extraction | [`m2-animation-load-pipeline-state.md`](m2-animation-load-pipeline-state.md) | | M2 animation load pipeline state | Implemented extraction | [`m2-animation-load-pipeline-state.md`](m2-animation-load-pipeline-state.md) |
| M2 animation resource finalizer | Implemented extraction | [`m2-animation-resource-finalizer.md`](m2-animation-resource-finalizer.md) |
| M2 mesh load pipeline state | Implemented extraction | [`m2-mesh-load-pipeline-state.md`](m2-mesh-load-pipeline-state.md) | | M2 mesh load pipeline state | Implemented extraction | [`m2-mesh-load-pipeline-state.md`](m2-mesh-load-pipeline-state.md) |
| M2 mesh resource cache state | Implemented extraction | [`m2-mesh-resource-cache-state.md`](m2-mesh-resource-cache-state.md) | | M2 mesh resource cache state | Implemented extraction | [`m2-mesh-resource-cache-state.md`](m2-mesh-resource-cache-state.md) |
| M2 mesh resource extractor | Implemented extraction | [`m2-mesh-resource-extractor.md`](m2-mesh-resource-extractor.md) | | M2 mesh resource extractor | Implemented extraction | [`m2-mesh-resource-extractor.md`](m2-mesh-resource-extractor.md) |
| M2 mesh resource finalizer | Implemented extraction | [`m2-mesh-resource-finalizer.md`](m2-mesh-resource-finalizer.md) |
| M2 runtime mesh finalizer | Implemented extraction | [`m2-runtime-mesh-finalizer.md`](m2-runtime-mesh-finalizer.md) | | M2 runtime mesh finalizer | Implemented extraction | [`m2-runtime-mesh-finalizer.md`](m2-runtime-mesh-finalizer.md) |
| M2 raw model repository | Implemented extraction | [`m2-raw-model-repository.md`](m2-raw-model-repository.md) | | M2 raw model repository | Implemented extraction | [`m2-raw-model-repository.md`](m2-raw-model-repository.md) |
| M2 prototype cache state | Implemented extraction | [`m2-prototype-cache-state.md`](m2-prototype-cache-state.md) | | M2 prototype cache state | Implemented extraction | [`m2-prototype-cache-state.md`](m2-prototype-cache-state.md) |
@@ -39,10 +46,17 @@
| 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) |
| WMO scene Resource finalizer | Implemented extraction | [`wmo-scene-resource-finalizer.md`](wmo-scene-resource-finalizer.md) |
| WMO runtime Mesh finalizer | Implemented extraction | [`wmo-runtime-mesh-finalizer.md`](wmo-runtime-mesh-finalizer.md) |
| WMO render group materializer | Implemented extraction | [`wmo-render-group-materializer.md`](wmo-render-group-materializer.md) |
| WMO runtime scene preparer | Implemented extraction | [`wmo-runtime-scene-preparer.md`](wmo-runtime-scene-preparer.md) |
| WMO scene instance factory | Implemented extraction | [`wmo-scene-instance-factory.md`](wmo-scene-instance-factory.md) |
| Third-person camera | Implemented | [`third-person-camera.md`](third-person-camera.md) | | Third-person camera | Implemented | [`third-person-camera.md`](third-person-camera.md) |
| Character presentation | Implemented boundary / Partial fidelity | [`character-presentation.md`](character-presentation.md) | | Character presentation | Implemented boundary / Partial fidelity | [`character-presentation.md`](character-presentation.md) |
| Renderer | Partial | [`world-renderer.md`](world-renderer.md), [`../../RENDER.md`](../../RENDER.md) | | Renderer | Partial | [`world-renderer.md`](world-renderer.md), [`../../RENDER.md`](../../RENDER.md) |
| Renderer closeout verification | Implemented | [`renderer-closeout-verification.md`](renderer-closeout-verification.md) |
| World entity presentation | Implemented boundary / Prototype visuals | [`world-entity-presentation.md`](world-entity-presentation.md) | | World entity presentation | Implemented boundary / Prototype visuals | [`world-entity-presentation.md`](world-entity-presentation.md) |
| Streaming target planner | Implemented | [`streaming-target-planner.md`](streaming-target-planner.md) | | Streaming target planner | Implemented | [`streaming-target-planner.md`](streaming-target-planner.md) |
| Render budget scheduler | Implemented | [`render-budget-scheduler.md`](render-budget-scheduler.md) | | Render budget scheduler | Implemented | [`render-budget-scheduler.md`](render-budget-scheduler.md) |
+1 -1
View File
@@ -241,7 +241,7 @@ runtime data migration.
| Current starter outfit | Partial | Existing resolver reused unchanged | Needs extracted DBC fixture and client comparison | | Current starter outfit | Partial | Existing resolver reused unchanged | Needs extracted DBC fixture and client comparison |
| Current skin/geoset composition | Partial | Existing components reused | Full equipment/customization fidelity incomplete | | Current skin/geoset composition | Partial | Existing components reused | Full equipment/customization fidelity incomplete |
| Build-12340 animation semantics | Planned | No original-client fixture | Capture animation IDs/transitions/timing | | Build-12340 animation semantics | Planned | No original-client fixture | Capture animation IDs/transitions/timing |
| Runtime equipment/network updates | Planned | No snapshot contract | M08/M09/M12 work | | Runtime equipment/network updates | Planned | No snapshot contract | M09/M10/M13 work |
## Known gaps and risks ## Known gaps and risks
+3 -3
View File
@@ -237,7 +237,7 @@ separate versioned movement snapshot contract.
- Terrain/collision policy can consume displacement without changing input or velocity ownership. - Terrain/collision policy can consume displacement without changing input or velocity ownership.
- A future server-aware predictor may replace this controller behind the scene composition boundary. - A future server-aware predictor may replace this controller behind the scene composition boundary.
- A typed movement snapshot may expose deterministic replay state when M08/M09 require it. - A typed movement snapshot may expose deterministic replay state when M09/M10 require it.
- A future application profile can map to this narrow capability value without - A future application profile can map to this narrow capability value without
coupling the movement controller to the application shell. coupling the movement controller to the application shell.
@@ -252,8 +252,8 @@ separate versioned movement snapshot contract.
| Typed sprint/free-flight exclusion | Implemented | Pure and real-scene profile regressions | Application shell must select profile explicitly later | | Typed sprint/free-flight exclusion | Implemented | Pure and real-scene profile regressions | Application shell must select profile explicitly later |
| Terrain height query | Implemented | Typed `TerrainQuery` and injected player regression | Ground-snap policy remains scene-owned | | Terrain height query | Implemented | Typed `TerrainQuery` and injected player regression | Ground-snap policy remains scene-owned |
| Terrain collision movement policy | Planned | Height-only query does not model collision | Add slopes/holes/collision later | | Terrain collision movement policy | Planned | Height-only query does not model collision | Add slopes/holes/collision later |
| Jump/fall/swim | Planned | M02/M09 roadmap | Requires terrain/liquid and server contracts | | Jump/fall/swim | Planned | M02/M10 roadmap | Requires terrain/liquid and server contracts |
| Prediction/reconciliation | Planned | M08/M09 roadmap | Requires movement snapshot/network contract | | Prediction/reconciliation | Planned | M09/M10 roadmap | Requires movement snapshot/network contract |
## Known gaps and risks ## Known gaps and risks
+22 -22
View File
@@ -27,9 +27,9 @@ and accept only candidates containing AnimationPlayer descendants.
```mermaid ```mermaid
flowchart LR flowchart LR
Loader[StreamingWorldLoader] -->|loaded Resource and material source| Finalizer[M2AnimatedSceneFinalizer] ResourceFinalizer[M2AnimationResourceFinalizer] -->|loaded Resource and material source| Finalizer[M2AnimatedSceneFinalizer]
Finalizer -->|accepted Node3D and player count| Loader Finalizer -->|accepted Node3D and player count| ResourceFinalizer
Loader --> Cache[M2PrototypeCacheState] ResourceFinalizer --> Cache[M2PrototypeCacheState]
``` ```
Allowed dependencies are Godot scene/resource/material types. ResourceLoader, Allowed dependencies are Godot scene/resource/material types. ResourceLoader,
@@ -50,10 +50,10 @@ other application layers are forbidden.
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime | | Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|---|---|---|---|---|---| |---|---|---|---|---|---|
| Input | Loaded Resource | Loader I/O adapter | Candidate instantiator | Borrowed Resource | One finalize permit | | Input | Loaded Resource | Animation resource finalizer | Candidate instantiator | Borrowed Resource | One finalize permit |
| Input | Static material prototype Node3D | Loader cache/build adapter | Material repair | Borrowed Node | One repair call | | Input | Static material prototype Node3D | Loader cache/build adapter | Material repair | Borrowed Node | One repair call |
| Input | Detached animated candidate | Instantiator | Repair/final validation | Finalizer then caller/release | One attempt | | Input | Detached animated candidate | Instantiator | Repair/final validation | Finalizer then caller/release | One attempt |
| Output | Accepted prototype and player count | Finalizer | Loader adoption/log adapter | Exact Node transferred | Shutdown cache lifetime | | Output | Accepted prototype and player count | Finalizer | Resource-finalizer adoption/log adapter | Exact Node transferred | Shutdown cache lifetime |
| Output | Depth-first engine-node arrays | Traversal | Loader preparation/playback | Borrowed references | One call | | Output | Depth-first engine-node arrays | Traversal | Loader preparation/playback | Borrowed references | One call |
Side effects are PackedScene instantiation, surface override assignment and Side effects are PackedScene instantiation, surface override assignment and
@@ -91,20 +91,19 @@ stateDiagram-v2
```mermaid ```mermaid
sequenceDiagram sequenceDiagram
participant L as StreamingWorldLoader participant R as M2AnimationResourceFinalizer
participant F as M2AnimatedSceneFinalizer participant F as M2AnimatedSceneFinalizer
participant C as M2PrototypeCacheState participant C as M2PrototypeCacheState
L->>F: instantiate_candidate(Resource) R->>F: instantiate_candidate(Resource)
F-->>L: detached Node3D or null F-->>R: detached Node3D or null
L->>L: get static material prototype R->>F: repair_materials(candidate, source)
L->>F: repair_materials(candidate, source) R->>F: finalize_candidate(candidate)
L->>F: finalize_candidate(candidate)
alt accepted alt accepted
F-->>L: exact Node3D and player count F-->>R: exact Node3D and player count
L->>C: adopt animated prototype R->>C: adopt animated prototype
else rejected else rejected
F-->>L: empty; candidate freed F-->>R: empty; candidate freed
L->>C: mark animation static R->>C: mark animation static
end end
``` ```
@@ -112,11 +111,11 @@ sequenceDiagram
```mermaid ```mermaid
flowchart TB flowchart TB
Loader[StreamingWorldLoader] --> Finalizer[M2AnimatedSceneFinalizer] ResourceFinalizer[M2AnimationResourceFinalizer] --> Finalizer[M2AnimatedSceneFinalizer]
Finalizer --> Engine[PackedScene / Node3D / Mesh / Material / AnimationPlayer] Finalizer --> Engine[PackedScene / Node3D / Mesh / Material / AnimationPlayer]
Loader --> Resource[ResourceLoader] ResourceFinalizer --> Resource[ResourceLoader]
Loader --> Budget[RenderBudgetScheduler] Loader --> Budget[RenderBudgetScheduler]
Loader --> Prototype[M2PrototypeCacheState] ResourceFinalizer --> Prototype[M2PrototypeCacheState]
Finalizer -. no dependency .-> Resource Finalizer -. no dependency .-> Resource
Finalizer -. no dependency .-> Budget Finalizer -. no dependency .-> Budget
Finalizer -. no dependency .-> Prototype Finalizer -. no dependency .-> Prototype
@@ -126,7 +125,7 @@ flowchart TB
- Every method runs synchronously on the renderer main thread. - Every method runs synchronously on the renderer main thread.
- A valid candidate is detached and finalizer-owned until acceptance. - A valid candidate is detached and finalizer-owned until acceptance.
- Acceptance transfers the exact Node3D to the loader/prototype cache path. - Acceptance transfers the exact Node3D to the resource-finalizer/cache path.
- Rejection frees the candidate synchronously, including wrong-type roots. - Rejection frees the candidate synchronously, including wrong-type roots.
- Traversal results borrow Nodes; source materials remain Resource-owned. - Traversal results borrow Nodes; source materials remain Resource-owned.
@@ -134,11 +133,11 @@ flowchart TB
| Failure | Detection | Behavior | Diagnostic | Recovery | | Failure | Detection | Behavior | Diagnostic | Recovery |
|---|---|---|---|---| |---|---|---|---|---|
| Null/unsupported Resource | Type guard | Return null | Dedicated verifier | Loader marks static | | Null/unsupported Resource | Type guard | Return null | Dedicated verifier | Resource finalizer marks static |
| Wrong root type | Instantiated type guard | Free and return null | Node-count regression | Rebuild cache | | Wrong root type | Instantiated type guard | Free and return null | Node-count regression | Rebuild cache |
| No material source/meshes | Null/empty traversal | Leave materials unchanged | Material fixture | Imported materials remain | | No material source/meshes | Null/empty traversal | Leave materials unchanged | Material fixture | Imported materials remain |
| Missing source surface | Material lookup | First source material fallback | Mapping fixture | Repair static source | | Missing source surface | Material lookup | First source material fallback | Mapping fixture | Repair static source |
| No AnimationPlayer | Descendant inventory | Free and return empty | Lifetime regression | Loader marks static | | No AnimationPlayer | Descendant inventory | Free and return empty | Lifetime regression | Resource finalizer marks static |
| Shutdown/cancellation | Not owned | No retained state | N/A | Loader drains first | | Shutdown/cancellation | Not owned | No retained state | N/A | Loader drains first |
## Configuration and capabilities ## Configuration and capabilities
@@ -199,7 +198,8 @@ by `M2AnimatedInstanceMaterializer`.
| `src/render/m2/m2_animated_instance_materializer.gd` | Per-duplicate player inventory consumer and batch owner | | `src/render/m2/m2_animated_instance_materializer.gd` | Per-duplicate player inventory consumer and batch owner |
| `src/render/m2/m2_animation_load_pipeline_state.gd` | Pending/terminal records before finalization | | `src/render/m2/m2_animation_load_pipeline_state.gd` | Pending/terminal records before finalization |
| `src/render/m2/m2_prototype_cache_state.gd` | Accepted prototype/static-only outcomes | | `src/render/m2/m2_prototype_cache_state.gd` | Accepted prototype/static-only outcomes |
| `src/scenes/streaming/streaming_world_loader.gd` | I/O, permits, material source, adoption and logs | | `src/render/m2/m2_animation_resource_finalizer.gd` | I/O, candidate composition, adoption and logs |
| `src/scenes/streaming/streaming_world_loader.gd` | Permits and material-source lookup |
| `src/tools/verify_m2_animated_scene_finalizer.gd` | Scene/material/lifetime/boundary/timing regression | | `src/tools/verify_m2_animated_scene_finalizer.gd` | Scene/material/lifetime/boundary/timing regression |
## Related decisions and references ## Related decisions and references
@@ -14,8 +14,9 @@
Own cross-frame bookkeeping between a successful animated M2 ResourceLoader Own cross-frame bookkeeping between a successful animated M2 ResourceLoader
request, terminal polling and budgeted main-thread scene finalization. This is request, terminal polling and budgeted main-thread scene finalization. This is
an exact state extraction; animation eligibility and loading remain in an exact state extraction; cached animation eligibility and request admission
`StreamingWorldLoader`, while scene finalization and retained Node lifecycle belong to `M2CachedAnimationResourceObserver`; terminal I/O/outcomes belong to
`M2AnimationResourceFinalizer`, while validation and retained Node lifetime
belong to `M2AnimatedSceneFinalizer` and `M2PrototypeCacheState`. belong to `M2AnimatedSceneFinalizer` and `M2PrototypeCacheState`.
## Non-goals ## Non-goals
@@ -29,11 +30,14 @@ belong to `M2AnimatedSceneFinalizer` and `M2PrototypeCacheState`.
```mermaid ```mermaid
flowchart LR flowchart LR
Loader[StreamingWorldLoader] --> IO[ResourceLoader] Observer[M2CachedAnimationResourceObserver] --> IO[ResourceLoader request]
Loader --> State[M2AnimationLoadPipelineState] Observer --> State[M2AnimationLoadPipelineState]
State -->|detached pending records| Loader Finalizer[M2AnimationResourceFinalizer] --> IO
Loader -->|opaque terminal status| State Finalizer --> State
State -->|completion FIFO| Loader State -->|detached pending records| Finalizer
Finalizer -->|opaque terminal status| State
State -->|completion FIFO| Finalizer
Loader[StreamingWorldLoader] --> Finalizer
Loader --> Budget[M2_ANIMATION_FINALIZE permit] Loader --> Budget[M2_ANIMATION_FINALIZE permit]
Loader --> Prototype[M2PrototypeCacheState] Loader --> Prototype[M2PrototypeCacheState]
``` ```
@@ -62,9 +66,9 @@ renderer services are forbidden.
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime | | Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|---|---|---|---|---|---| |---|---|---|---|---|---|
| Input | Normalized M2 path and GLB Resource path | Loader request adapter | Pending map | Copied Strings | Until completion/discard/clear | | Input | Normalized M2 path and GLB Resource path | Loader request adapter | Pending map | Copied Strings | Until completion/discard/clear |
| Input | Opaque terminal status | Loader polling adapter | Finalize FIFO | Integer value | Until pop/clear | | Input | Opaque terminal status | Resource finalizer | Finalize FIFO | Integer value | Until pop/clear |
| Output | Detached pending records | State | Loader poll/shutdown adapter | Caller-owned copies | One pass | | Output | Detached pending records | State | Resource finalizer/shutdown adapter | Caller-owned copies | One pass |
| Output | Oldest completion record | State | Loader finalizer | Transferred Dictionary | One finalize attempt | | Output | Oldest completion record | State | Resource finalizer | Transferred Dictionary | One finalize attempt |
| Output | Detached diagnostics | State | Verifier/future metrics | Caller-owned copies | Snapshot lifetime | | Output | Detached diagnostics | State | Verifier/future metrics | Caller-owned copies | Snapshot lifetime |
Side effects are limited to collection mutation and retaining String/integer values. Side effects are limited to collection mutation and retaining String/integer values.
@@ -74,7 +78,7 @@ Side effects are limited to collection mutation and retaining String/integer val
```mermaid ```mermaid
flowchart TD flowchart TD
Start[Successful threaded request] --> Remember[Remember request] Start[Successful threaded request] --> Remember[Remember request]
Remember --> Poll[Loader polls detached snapshot] Remember --> Poll[Resource finalizer polls detached snapshot]
Poll --> Terminal{Loaded or failed?} Poll --> Terminal{Loaded or failed?}
Terminal -->|no| Poll Terminal -->|no| Poll
Terminal -->|yes| Complete[Complete with opaque status] Terminal -->|yes| Complete[Complete with opaque status]
@@ -82,7 +86,7 @@ flowchart TD
FIFO --> Permit{Permit available?} FIFO --> Permit{Permit available?}
Permit -->|no| FIFO Permit -->|no| FIFO
Permit -->|yes| Pop[Pop oldest record] Permit -->|yes| Pop[Pop oldest record]
Pop --> Finalize[Loader loads/instantiates or marks static] Pop --> Finalize[Resource finalizer loads/instantiates or marks static]
``` ```
## Lifecycle/state ## Lifecycle/state
@@ -100,20 +104,24 @@ stateDiagram-v2
```mermaid ```mermaid
sequenceDiagram sequenceDiagram
participant O as CachedAnimationObserver
participant L as StreamingWorldLoader participant L as StreamingWorldLoader
participant F as AnimationResourceFinalizer
participant R as ResourceLoader participant R as ResourceLoader
participant S as M2AnimationLoadPipelineState participant S as M2AnimationLoadPipelineState
participant P as M2PrototypeCacheState participant P as M2PrototypeCacheState
L->>R: load_threaded_request(GLB) O->>R: load_threaded_request(GLB)
L->>S: remember_request(path, GLB) O->>S: remember_request(path, GLB)
loop frames loop frames
L->>S: request_records_snapshot() L->>F: poll terminal requests
L->>R: load_threaded_get_status(GLB) F->>S: request_records_snapshot()
F->>R: load_threaded_get_status(GLB)
end end
L->>S: complete_request(path, status) F->>S: complete_request(path, status)
L->>S: pop_finalize_record() after permit L->>F: prepare after permit
L->>R: load_threaded_get(GLB) F->>S: pop_finalize_record()
L->>P: adopt animated prototype or mark static F->>R: load_threaded_get(GLB)
F->>P: adopt animated prototype or mark static
``` ```
## Ownership, threading and resources ## Ownership, threading and resources
@@ -121,15 +129,18 @@ sequenceDiagram
- Main thread serializes all mutation. - Main thread serializes all mutation.
- State owns only request/finalize Dictionaries with copied paths and statuses. - State owns only request/finalize Dictionaries with copied paths and statuses.
- Loader drains pending ResourceLoader paths before orderly shutdown clear. - Loader drains pending ResourceLoader paths before orderly shutdown clear.
- Loader owns PackedScene instantiation and material repair; prototype state owns - Resource finalizer owns terminal I/O/outcomes and composes scene validation;
accepted detached Node references and static-only outcomes. prototype state owns accepted detached Nodes and static-only outcomes.
## Dependency diagram ## Dependency diagram
```mermaid ```mermaid
flowchart TB flowchart TB
Loader[StreamingWorldLoader] --> State[M2AnimationLoadPipelineState] Observer[M2CachedAnimationResourceObserver] --> State[M2AnimationLoadPipelineState]
Loader --> Resource[ResourceLoader] Observer --> Resource[ResourceLoader request]
Finalizer[M2AnimationResourceFinalizer] --> State[M2AnimationLoadPipelineState]
Finalizer --> Resource
Loader[StreamingWorldLoader] --> Finalizer
Loader --> Budget[RenderBudgetScheduler] Loader --> Budget[RenderBudgetScheduler]
Loader --> Prototype[M2PrototypeCacheState] Loader --> Prototype[M2PrototypeCacheState]
State -. no dependency .-> Resource State -. no dependency .-> Resource
@@ -142,7 +153,7 @@ flowchart TB
| Failure | Detection | Behavior | Diagnostic | Recovery | | Failure | Detection | Behavior | Diagnostic | Recovery |
|---|---|---|---|---| |---|---|---|---|---|
| Empty/duplicate request | State guard | Reject unchanged | Contract verifier | Correct caller or request later | | Empty/duplicate request | State guard | Reject unchanged | Contract verifier | Correct caller or request later |
| Request start/cache miss | Loader | No insertion; mark static | Existing loader path | Cache correction/reload | | Request start/cache miss | Cached observer | No insertion; mark static | Observer contract | Cache correction/reload |
| Non-terminal status | Loader | Keep pending | Existing metric | Poll next frame | | Non-terminal status | Loader | Keep pending | Existing metric | Poll next frame |
| Failed terminal load | Popped status | Loader marks static | Existing behavior | Future map/session reload | | Failed terminal load | Popped status | Loader marks static | Existing behavior | Future map/session reload |
| Empty defensive path | Loader poll | Discard and mark static | Source contract | Correct producer | | Empty defensive path | Loader poll | Discard and mark static | Source contract | Correct producer |
@@ -154,7 +165,7 @@ flowchart TB
|---|---|---|---|---| |---|---|---|---|---|
| `enable_m2_animated_instances` | `true` | Existing renderer profile | Yes | Enables caller request path | | `enable_m2_animated_instances` | `true` | Existing renderer profile | Yes | Enables caller request path |
| `m2_animation_finalize_ops_per_tick` | `1` | Quality/custom | Yes | Bounds caller FIFO drain | | `m2_animation_finalize_ops_per_tick` | `1` | Quality/custom | Yes | Bounds caller FIFO drain |
| Animated allow/deny/primitive rules | Existing values | Existing renderer profile | Yes | Filter before state insertion | | Animated allow/deny/primitive rules | Existing values | Existing renderer profile | Yes | Observer filters before state insertion |
## Persistence, cache and migration ## Persistence, cache and migration
@@ -179,16 +190,16 @@ no rebake or migration is required.
## Extension points ## Extension points
ResourceLoader polling may later move behind a separate adapter without ResourceLoader polling/finalization now belongs to a sibling service without
changing this value-only state contract. Animated-scene finalization is now a changing this value-only state contract.
sibling service.
## Capability status ## Capability status
| Capability | Status | Evidence | Gap/next step | | Capability | Status | Evidence | Gap/next step |
|---|---|---|---| |---|---|---|---|
| Animated request/finalize state | Implemented extraction | Synthetic contract/source/timing verifier | Asset-backed traversal/leak/p95/p99 pending | | Animated request/finalize state | Implemented extraction | Synthetic contract/source/timing verifier | Asset-backed traversal/leak/p95/p99 pending |
| ResourceLoader and GLB selection | Existing loader-owned | Adjacent renderer tests | I/O adapter extraction optional | | Cached request admission and GLB selection | Implemented in observer | Policy/GLB/source verifier | Asset-backed traversal pending |
| Terminal ResourceLoader polling | Implemented finalizer extraction | Status/order/source regressions | Asset-backed traversal pending |
| Animated prototype outcomes | Implemented extraction | Prototype cache verifier | Asset-backed animation fidelity pending | | Animated prototype outcomes | Implemented extraction | Prototype cache verifier | Asset-backed animation fidelity pending |
## Known gaps and risks ## Known gaps and risks
@@ -202,14 +213,17 @@ sibling service.
| Path | Responsibility | | Path | Responsibility |
|---|---| |---|---|
| `src/render/m2/m2_animation_load_pipeline_state.gd` | Pending records, completion FIFO and metrics | | `src/render/m2/m2_animation_load_pipeline_state.gd` | Pending records, completion FIFO and metrics |
| `src/render/m2/m2_animation_resource_finalizer.gd` | Terminal polling, Resource load and prototype outcome |
| `src/render/m2/m2_cached_animation_resource_observer.gd` | Eligibility, GLB selection and request admission |
| `src/render/m2/m2_animated_scene_finalizer.gd` | Candidate instantiation, material repair and player validation | | `src/render/m2/m2_animated_scene_finalizer.gd` | Candidate instantiation, material repair and player validation |
| `src/render/m2/m2_prototype_cache_state.gd` | Animated prototype/static-only outcomes | | `src/render/m2/m2_prototype_cache_state.gd` | Animated prototype/static-only outcomes |
| `src/scenes/streaming/streaming_world_loader.gd` | Eligibility, I/O, permits, instantiation and adoption | | `src/scenes/streaming/streaming_world_loader.gd` | Permit loop, material lookup and composition |
| `src/tools/verify_m2_animation_load_pipeline_state.gd` | Lifecycle/boundary/timing regression | | `src/tools/verify_m2_animation_load_pipeline_state.gd` | Lifecycle/boundary/timing regression |
## Related decisions and references ## Related decisions and references
- [`m2-animated-scene-finalizer.md`](m2-animated-scene-finalizer.md) - [`m2-animated-scene-finalizer.md`](m2-animated-scene-finalizer.md)
- [`m2-animation-resource-finalizer.md`](m2-animation-resource-finalizer.md)
- [`m2-prototype-cache-state.md`](m2-prototype-cache-state.md) - [`m2-prototype-cache-state.md`](m2-prototype-cache-state.md)
- [`m2-mesh-load-pipeline-state.md`](m2-mesh-load-pipeline-state.md) - [`m2-mesh-load-pipeline-state.md`](m2-mesh-load-pipeline-state.md)
- [`world-renderer.md`](world-renderer.md) - [`world-renderer.md`](world-renderer.md)
@@ -7,7 +7,7 @@
| Status | Implemented extraction | | Status | Implemented extraction |
| Target/work package | M03 / `M03-RND-M2-ANIMATION-PLAYBACK-001` | | Target/work package | M03 / `M03-RND-M2-ANIMATION-PLAYBACK-001` |
| Owners | Per-instance AnimationPlayer/native animator playback mutation | | Owners | Per-instance AnimationPlayer/native animator playback mutation |
| Last verified | Worktree `work/sindo-main-codex/m03-m2-animation-playback`, 2026-07-18 | | Last verified | Worktree `work/sindo-main-codex-m03-integrator/m03-closeout`, 2026-08-02 |
| Profiles/capabilities | Imported GLB and native experimental animated M2 instances | | Profiles/capabilities | Imported GLB and native experimental animated M2 instances |
## Purpose ## Purpose
@@ -59,7 +59,7 @@ MultiMesh, SceneTree attachment and application layers are forbidden.
| Output | Mutated native/imported playback | Controller | Rendered instance | Nodes retain state/resources | Instance lifetime | | Output | Mutated native/imported playback | Controller | Rendered instance | Nodes retain state/resources | Instance lifetime |
| Output | Detached native diagnostic records | Controller | Loader log adapter | Caller-owned Dictionaries | Debug call | | Output | Detached native diagnostic records | Controller | Loader log adapter | Caller-owned Dictionaries | Debug call |
Side effects are native field assignment, prepare/phase calls, animation loop Side effects are native field assignment, phased preparation calls, animation loop
mutation, play and seek. The service retains no inputs. mutation, play and seek. The service retains no inputs.
## Data flow ## Data flow
@@ -67,13 +67,13 @@ mutation, play and seek. The service retains no inputs.
```mermaid ```mermaid
flowchart TD flowchart TD
Identity[Path and index] --> Phase[Stable hash phase] Identity[Path and index] --> Phase[Stable hash phase]
NativeInventory[Exact-script native inventory] --> Prepare[prepare runtime if available] NativeInventory[Exact-script native inventory] --> Prepare[prepare local runtime mesh]
Phase --> NativePhase[set native phase] Phase --> Prepare
Players[AnimationPlayers] --> Select[Choose path-specific default] Players[AnimationPlayers] --> Select[Choose path-specific default]
Select --> Loop[Set every animation LOOP_LINEAR] Select --> Loop[Set every animation LOOP_LINEAR]
Loop --> Play[Play selected name] Loop --> Play[Play selected name]
Phase --> Seek[Seek positive-length selection] Phase --> Seek[Seek positive-length selection]
NativePhase --> Diagnostics{Debug requested?} Prepare --> Diagnostics{Debug requested?}
Diagnostics -->|yes| Snapshot[Detached runtime state] Diagnostics -->|yes| Snapshot[Detached runtime state]
``` ```
@@ -103,7 +103,7 @@ sequenceDiagram
M->>F: animation_players_in_subtree(duplicate) M->>F: animation_players_in_subtree(duplicate)
F-->>M: ordered players F-->>M: ordered players
M->>P: start_instance_playback(path, index, players, debug) M->>P: start_instance_playback(path, index, players, debug)
P->>N: prepare_runtime and set_phase P->>N: prepare_runtime_at_phase
P->>A: choose, loop, play and seek P->>A: choose, loop, play and seek
P-->>M: optional detached native diagnostics P-->>M: optional detached native diagnostics
M-->>M: tag states with instance index M-->>M: tag states with instance index
@@ -132,6 +132,11 @@ flowchart TB
- Native arrays are assigned by reference exactly as before extraction. - Native arrays are assigned by reference exactly as before extraction.
- Diagnostic Dictionaries are deep-duplicated before return. - Diagnostic Dictionaries are deep-duplicated before return.
- Main thread performs all engine-object mutation; pure phase math is thread-safe. - Main thread performs all engine-object mutation; pure phase math is thread-safe.
- A duplicated native animator resolves and duplicates its local Mesh, applies
phase and deforms once before attachment. Its later `_ready()` is idempotent.
- Preparation allocates an empty instance-local ArrayMesh because deformation
immediately rebuilds every surface from retained native arrays. Captured
immutable Material resources remain shared and are reapplied.
## Errors, cancellation and recovery ## Errors, cancellation and recovery
@@ -168,7 +173,8 @@ and material versions are unchanged; no rebake is required.
- `verify_m2_animation_playback_controller.gd` covers exact phase, ordinary/ - `verify_m2_animation_playback_controller.gd` covers exact phase, ordinary/
fish/bird priorities, substring/first fallback, loop/play/seek, native exact- fish/bird priorities, substring/first fallback, loop/play/seek, native exact-
script order, five-field copy, phase, detached diagnostics and boundaries. script order, five-field copy, single-rebuild phased preparation, idempotent
ready, detached diagnostics and boundaries.
- Finalizer/build/prototype/material/shutdown regressions protect adjacent behavior. - Finalizer/build/prototype/material/shutdown regressions protect adjacent behavior.
- Fidelity evidence is exact policy/mutation extraction; no private asset or - Fidelity evidence is exact policy/mutation extraction; no private asset or
original-client animation comparison is claimed. original-client animation comparison is claimed.
@@ -192,7 +198,8 @@ for world doodads and compatibility fixtures.
- Hash phase intentionally depends on existing Godot String hashing behavior. - Hash phase intentionally depends on existing Godot String hashing behavior.
- Default-name heuristics are not a complete WoW animation-state mapping. - Default-name heuristics are not a complete WoW animation-state mapping.
- No proprietary traversal, animation timing comparison, p95/p99 or paired-client run exists. - Native CPU deformation remains proportional to vertex count and is unsuitable
for large numbers of independently animated instances without a future GPU path.
## Source map ## Source map
@@ -0,0 +1,249 @@
# M2 Animation Resource Finalizer
## Metadata
| Field | Value |
|---|---|
| Status | Implemented extraction |
| Target/work package | M03 / `M03-RND-M2-ANIMATION-RESOURCE-FINALIZER-001` |
| Owners | Cached animated Resource polling, terminal load and prototype outcome |
| Last verified | Worktree `work/sindo-main-codex/m03-m2-animation-resource-finalizer`, 2026-07-18 |
| Profiles/capabilities | Existing optional cached-GLB animated M2 path |
## Purpose
Drain cached animated M2 ResourceLoader work without keeping terminal I/O and
prototype outcome logic in `StreamingWorldLoader`. The service polls pending
requests, prepares a detached scene candidate and completes material repair,
validation, cache adoption or static-only fallback.
## Non-goals
- Select cached GLB paths, apply allow/deny policy or admit threaded requests.
- Select/build native animated M2 prototypes.
- Look up the static material prototype used for repair.
- Own render permits, build jobs, playback, instances or SceneTree roots.
- Change ResourceLoader ordering, cache formats, profiles or visible output.
## Context and boundaries
```mermaid
flowchart LR
Observer[M2CachedAnimationResourceObserver] --> Pipeline[M2AnimationLoadPipelineState]
Loader[StreamingWorldLoader] --> Finalizer[M2AnimationResourceFinalizer]
Pipeline --> Finalizer
ResourceLoader --> Finalizer
Finalizer --> SceneFinalizer[M2AnimatedSceneFinalizer]
Finalizer --> Cache[M2PrototypeCacheState]
Loader --> Material[Material prototype lookup]
Material --> Finalizer
```
The two-phase API preserves the historical ordering: a loaded Resource must
instantiate a candidate before the loader performs material-prototype lookup.
## Public API
| Symbol | Kind | Purpose | Failure behavior |
|---|---|---|---|
| `poll_terminal_requests(pipeline, cache)` | I/O command | Move LOADED/FAILED requests to completion FIFO | Invalid composition returns zero |
| `prepare_next_candidate(pipeline, cache)` | I/O command/query | Pop one record, load Resource and instantiate candidate | Skip/failed/invalid outcome returns empty Dictionary |
| `finalize_prepared_candidate(preparation, material_source, cache, debug)` | Command/query | Repair, validate, adopt/log or mark static-only | Invalid/finalization failure returns null |
| `load_threaded_get_status(path)` | I/O adapter | Production status query with injectable test seam | Returns ResourceLoader status |
| `load_threaded_get(path)` | I/O adapter | Production terminal Resource retrieval with injectable test seam | May return null |
## Inputs and outputs
| Direction | Data | Producer | Consumer | Ownership/lifetime |
|---|---|---|---|---|
| Input | Pending/finalize records | Animation pipeline | Finalizer | Pipeline-owned copied Dictionaries |
| Input | ResourceLoader status/Resource | Godot ResourceLoader | Finalizer | Resource reference for one operation |
| Input | Material source root | Loader lookup | Scene finalizer | Borrowed exact Node3D |
| Input | Prototype/static-only state | Prototype cache | Finalizer | Borrowed service; loader session |
| Output | Prepared candidate Dictionary | Finalizer | Loader/finalizer completion | Caller-owned, immediate operation |
| Output | Canonical animated prototype | Prototype cache | Loader build path | Borrowed exact Node3D |
| Output | Static-only outcome | Finalizer | Prototype cache | Copied path; loader session |
| Output | Success diagnostic | Finalizer | Runtime log | Debug-gated one-line record |
## Data flow
```mermaid
flowchart TD
Poll[Poll pending records in insertion order] --> Path{Resource path empty?}
Path -->|yes| Discard[Discard and mark static-only]
Path -->|no| Status{LOADED or FAILED?}
Status -->|no| Pending[Keep pending]
Status -->|yes| Complete[Append completion FIFO]
Complete --> Permit[Loader consumes one finalize permit]
Permit --> Pop[Pop oldest terminal record]
Pop --> Existing{Empty/cached/static-only?}
Existing -->|yes| Skip[Finish permit operation]
Existing -->|no| Loaded{Status LOADED?}
Loaded -->|no| Mark[Mark static-only]
Loaded -->|yes| Get[Get terminal Resource]
Get --> Instantiate[Instantiate detached candidate]
Instantiate --> Valid{Candidate exists?}
Valid -->|no| Mark
Valid -->|yes| Material[Loader resolves material source]
Material --> Repair[Repair and validate]
Repair --> Prototype{Prototype accepted?}
Prototype -->|no| Mark
Prototype -->|yes| Adopt[Adopt and optionally log]
```
## Lifecycle/state
```mermaid
stateDiagram-v2
[*] --> Polling
Polling --> Pending: nonterminal status
Polling --> TerminalQueued: LOADED or FAILED
Polling --> StaticOnly: empty Resource path
TerminalQueued --> Skipped: cached/static/invalid record
TerminalQueued --> StaticOnly: failed load or invalid candidate
TerminalQueued --> Prepared: detached candidate
Prepared --> Adopted: repair/finalize succeeds
Prepared --> StaticOnly: validation fails
Pending --> [*]
Skipped --> [*]
StaticOnly --> [*]
Adopted --> [*]
```
## Main sequence
```mermaid
sequenceDiagram
participant L as StreamingWorldLoader
participant F as AnimationResourceFinalizer
participant P as AnimationLoadPipelineState
participant R as ResourceLoader
participant S as AnimatedSceneFinalizer
participant C as PrototypeCacheState
L->>F: poll_terminal_requests(P, C)
F->>R: load_threaded_get_status(path)
F->>P: complete terminal request
L->>L: consume M2_ANIMATION_FINALIZE permit
L->>F: prepare_next_candidate(P, C)
F->>P: pop oldest finalize record
F->>R: load_threaded_get(path)
F->>S: instantiate_candidate(Resource)
alt candidate available
F-->>L: normalized/path/candidate
L->>L: resolve material prototype
L->>F: finalize_prepared_candidate(...)
F->>S: repair_materials + finalize_candidate
F->>C: adopt prototype or mark static-only
else rejected
F->>C: mark static-only
end
```
## Dependency diagram
```mermaid
flowchart TB
Finalizer[M2AnimationResourceFinalizer] --> ResourceLoader
Finalizer --> Pipeline[M2AnimationLoadPipelineState]
Finalizer --> SceneFinalizer[M2AnimatedSceneFinalizer]
Finalizer --> Cache[M2PrototypeCacheState]
Loader[StreamingWorldLoader] --> Finalizer
Loader --> Scheduler[RenderBudgetScheduler]
Loader --> MaterialLookup[Material prototype lookup]
Finalizer -. no dependency .-> Scheduler
Finalizer -. no dependency .-> BuildQueue[M2BuildQueue]
Finalizer -. no dependency .-> Playback[M2AnimationPlaybackController]
```
## Ownership, threading and resources
- All methods run synchronously on the renderer main thread.
- Pipeline owns pending and completion records; one loader permit pops one record.
- The prepared Dictionary temporarily owns the detached candidate reference. The
loader must call completion synchronously after material lookup.
- Existing scene finalizer frees rejected candidate roots. Prototype cache owns
accepted detached prototypes until final shutdown.
- Service retains only scene-finalizer and optional test-adapter references. The
production ResourceLoader path creates no self-reference or retained Resource.
- Loader retains scheduler permits, material lookup and all instance/SceneTree work.
## Errors, cancellation and recovery
| Failure/state | Behavior | Recovery |
|---|---|---|
| Missing service dependency | Return zero/empty/null | Correct renderer composition |
| Empty Resource path | Discard pending request and mark static-only | Repair cache and start new session |
| Nonterminal status | Keep request pending | Poll next tick |
| FAILED terminal status | Pop one record and mark static-only | Repair cache and start new session |
| Existing cached/static state | Pop and skip without terminal Resource get | None |
| Null/unsupported Resource | Mark static-only | Repair imported cache |
| Candidate validation failure | Scene finalizer frees candidate; mark static-only | Repair animation/cache |
| Tile cancellation | No direct transition; shared cache work continues | Loader queue remains authoritative |
| Shutdown | Loader drains ResourceLoader before pipeline/cache clear | New loader starts empty |
## Configuration and capabilities
The module adds no setting. Existing cached-animation allow/deny policy,
`m2_animation_finalize_ops_per_tick`, debug flag and scheduler lane remain exact.
## Persistence, cache and migration
No persistence, schema or cache path changes are introduced. Existing `.glb`
imports and `pivot_prefix_v1` eligibility remain owned by the cached observer.
## Diagnostics and observability
Successful debug-enabled adoption emits the unchanged `M2_ANIM_CACHE` record
with normalized path, Resource cache path and AnimationPlayer count. Pipeline
work metrics remain pending plus completion FIFO size.
## Verification
- Dedicated verifier covers pending/loaded/failed polling order, empty path,
completion FIFO, cached/static skips, terminal Resource get, candidate identity,
material repair arguments, adoption, rejection, source ownership and timing.
- Pipeline, scene-finalizer, prototype-cache, cached-observer, shutdown, facade,
internal-access and baseline regressions protect adjacent behavior.
- Fidelity evidence is exact lifecycle/I/O extraction only; no private asset,
original-client animation, visual, leak-pressure or p95/p99 claim is made.
## Extension points
Material-prototype lookup may later become a dedicated resource observer, which
would allow the entire two-phase operation to compose outside the loader. Static
Mesh Resource finalization remains separate to avoid premature generalization.
## Capability status
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| Animated request terminal polling | Implemented extraction | Status/order/source verifier | Asset-backed long traversal pending |
| Animated candidate finalization/outcome | Implemented extraction | Identity/repair/adoption verifier | Private visual/leak/p95/p99 pending |
| Material prototype lookup | Loader-owned | Existing renderer regressions | Dedicated observer optional |
## Known gaps and risks
- Terminal Resource get, instantiation and repair remain synchronous main-thread work.
- Prepared candidate ownership relies on immediate completion by the loader adapter.
- Private asset traversal, descriptor pressure, leak and p95/p99 evidence is absent.
## Source map
| Path | Responsibility |
|---|---|
| `src/render/m2/m2_animation_resource_finalizer.gd` | Poll/load/prepare/finalize/cache/log service |
| `src/render/m2/m2_animation_load_pipeline_state.gd` | Pending and completion FIFO ownership |
| `src/render/m2/m2_animated_scene_finalizer.gd` | Candidate instantiation/repair/validation/rejection cleanup |
| `src/render/m2/m2_prototype_cache_state.gd` | Accepted prototype/static-only ownership |
| `src/scenes/streaming/streaming_world_loader.gd` | Permits, material lookup and synchronous composition |
| `src/tools/verify_m2_animation_resource_finalizer.gd` | Polling/finalization/boundary/timing regression |
## Related decisions and references
- [`m2-animation-load-pipeline-state.md`](m2-animation-load-pipeline-state.md)
- [`m2-animated-scene-finalizer.md`](m2-animated-scene-finalizer.md)
- [`m2-cached-animation-resource-observer.md`](m2-cached-animation-resource-observer.md)
- [`m2-prototype-cache-state.md`](m2-prototype-cache-state.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)
+10 -6
View File
@@ -32,8 +32,8 @@ flowchart LR
Loader --> Planner Loader --> Planner
Planner --> Plan[Detached batch plan] Planner --> Plan[Detached batch plan]
Plan --> Loader Plan --> Loader
Loader --> Ready{Resource ready?} Loader --> Dispatch[M2BuildDispatchPlanner]
Ready --> Materialize[Animated or MultiMesh materialization] Dispatch --> Materialize[Animated or MultiMesh materialization]
Loader --> Budget[RenderBudgetScheduler permit] Loader --> Budget[RenderBudgetScheduler permit]
``` ```
@@ -103,7 +103,9 @@ sequenceDiagram
- The planner owns only call-local scalar values and the returned Dictionary. - The planner owns only call-local scalar values and the returned Dictionary.
- `M2BuildQueue` owns typed jobs, FIFO ordering, serial numbers and group/offset - `M2BuildQueue` owns typed jobs, FIFO ordering, serial numbers and group/offset
cursors. The loader owns tile checks, resource readiness/retry and adoption calls. cursors. `M2BuildDispatchPlanner` owns the pure resource-state action decision;
the loader owns tile checks and native-first orchestration, while static and
cached animation observers own their observation/retry phases.
- Materializers own main-thread Node/MultiMesh construction under loader roots. - Materializers own main-thread Node/MultiMesh construction under loader roots.
- The scheduler owns the frame-local `M2_BUILD` counter. - The scheduler owns the frame-local `M2_BUILD` counter.
- Pure planning is thread-safe, though the current adapter calls it on main thread. - Pure planning is thread-safe, though the current adapter calls it on main thread.
@@ -149,8 +151,8 @@ queue depth, build activity and hitch observability.
## Extension points ## Extension points
- A later package may extract resource readiness/dispatch while retaining the - Remaining native resource observation may be extracted while retaining the
typed build-job and FIFO contracts defined by `M2BuildQueue`. dispatch and typed build-job/FIFO contracts.
- Spatial-cell batching must use measured culling/performance evidence and must - Spatial-cell batching must use measured culling/performance evidence and must
not silently change this model-path batch cursor. not silently change this model-path batch cursor.
@@ -160,7 +162,8 @@ queue depth, build activity and hitch observability.
|---|---|---|---| |---|---|---|---|
| Static/animated batch cursor planning | Implemented extraction | Contract/source/timing verifier | Asset-backed p95/p99 pending | | Static/animated batch cursor planning | Implemented extraction | Contract/source/timing verifier | Asset-backed p95/p99 pending |
| Typed build queue/cursor state | Implemented extraction | M2 build queue lifecycle verifier | Asset-backed traversal pending | | Typed build queue/cursor state | Implemented extraction | M2 build queue lifecycle verifier | Asset-backed traversal pending |
| Resource readiness/dispatch | Remains in loader | Existing lifecycle regressions | Stateful extraction pending | | Resource dispatch decision | Implemented extraction | M2 build dispatch planner verifier | Asset-backed traversal pending |
| Resource observation/requests | Remains in loader | Existing lifecycle regressions | Stateful extraction pending |
| Spatial-cell batching | Planned | Renderer roadmap | Culling evidence/design pending | | Spatial-cell batching | Planned | Renderer roadmap | Culling evidence/design pending |
## Known gaps and risks ## Known gaps and risks
@@ -176,6 +179,7 @@ queue depth, build activity and hitch observability.
| Path | Responsibility | | Path | Responsibility |
|---|---| |---|---|
| `src/render/m2/m2_build_batch_planner.gd` | Pure limit/count/cursor planning | | `src/render/m2/m2_build_batch_planner.gd` | Pure limit/count/cursor planning |
| `src/render/m2/m2_build_dispatch_planner.gd` | Pure observed-state action/transition planning |
| `src/render/m2/m2_build_queue.gd` | Typed pending jobs, FIFO order and cursor ownership | | `src/render/m2/m2_build_queue.gd` | Typed pending jobs, FIFO order and cursor ownership |
| `src/scenes/streaming/streaming_world_loader.gd` | Tile checks, resource readiness, progress adoption, materializer adapters and budgets | | `src/scenes/streaming/streaming_world_loader.gd` | Tile checks, resource readiness, progress adoption, materializer adapters and budgets |
| `src/render/m2/m2_static_batch_materializer.gd` | Planned static-slice MultiMesh construction and attachment | | `src/render/m2/m2_static_batch_materializer.gd` | Planned static-slice MultiMesh construction and attachment |
+237
View File
@@ -0,0 +1,237 @@
# M2 Build Dispatch Planner
## Metadata
| Field | Value |
|---|---|
| Status | Implemented extraction |
| Target/work package | M03 / `M03-RND-M2-BUILD-DISPATCH-PLANNER-001` |
| Owners | Pure M2 resource-state to build-action decision |
| Last verified | Worktree `work/sindo-main-codex/m03-m2-build-dispatch-planner`, 2026-07-18 |
| Profiles/capabilities | Existing static MultiMesh and animated-instance build paths |
## Purpose
Select one M2 build action after renderer observers produce animation/static
resource state. The planner makes wait priority, materializer selection and the
historical missing-model serial transition explicit without owning resources or
engine work.
## Non-goals
- Locate, request, load, cache or validate M2 resources.
- Size batches, own build jobs/queues or consume renderer permits.
- Create, attach or free Nodes, Meshes, MultiMeshes or animated instances.
- Change transforms, ordering, render settings, profiles or visible output.
- Add spatial-cell batching or M2 format/fidelity behavior.
## Context and boundaries
```mermaid
flowchart LR
Loader[StreamingWorldLoader resource observations] --> Planner[M2BuildDispatchPlanner]
Planner --> Action[Detached action and transition plan]
Action --> Loader
Loader --> Queue[M2BuildQueue rotate/progress]
Loader --> Animated[M2AnimatedInstanceMaterializer]
Loader --> Static[M2StaticBatchMaterializer]
Loader --> Budget[RenderBudgetScheduler]
```
The planner depends only on `RefCounted`, scalar values, `StringName` constants
and a fresh Dictionary. ResourceLoader, caches, Nodes, Meshes, RenderingServer,
SceneTree, files, workers, mutexes, gameplay, network and Editor APIs are forbidden.
## Public API
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|---|---|---|---|---|
| `ACTION_WAIT_FOR_ANIMATION` | Constant | Pending animation request blocks all other actions | Immutable | None |
| `ACTION_MATERIALIZE_ANIMATED` | Constant | Build the selected slice from animated prototype | Immutable | None |
| `ACTION_WAIT_FOR_STATIC_MESH` | Constant | Static resource is unresolved and queue must rotate | Immutable | None |
| `ACTION_MATERIALIZE_STATIC` | Constant | Build the selected slice from prepared static Mesh | Immutable | None |
| `ACTION_ADVANCE_WITHOUT_MATERIALIZATION` | Constant | Empty or terminally missing slice advances without a Node | Immutable | None |
| `plan_step(batch_count, resource_snapshot)` | Pure query | Return action, queue-rotation and serial-transition values | Any thread; call-local result | Null snapshot waits for static Mesh; non-positive count advances |
## Inputs and outputs
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|---|---|---|---|---|---|
| Input | Planned batch count | `M2BuildBatchPlanner` through loader | Dispatch planner | Integer copy | One call |
| Input | Typed animated/static availability, pending and missing observations | Loader / `M2BuildResourceSnapshot` | Dispatch planner | Borrowed snapshot; engine references are not read | One call |
| Output | Action | Dispatch planner | Loader materializer/wait branch | Immutable StringName | One operation |
| Output | `rotate_queue` | Dispatch planner | Loader queue adapter | Detached boolean | One operation |
| Output | `increment_batch_serial` | Dispatch planner | Loader progress adapter | Detached boolean | One operation |
The output Dictionary is newly allocated and caller-owned. The planner retains
no state, resource or engine-object reference.
## Data flow
```mermaid
flowchart TD
Input[Observed state] --> AnimationPending{Animation request pending?}
AnimationPending -->|yes| WaitAnimation[Wait animation; rotate]
AnimationPending -->|no| Positive{Batch count positive?}
Positive -->|no| AdvanceEmpty[Advance; keep serial]
Positive -->|yes| Animated{Animated prototype?}
Animated -->|yes| BuildAnimated[Materialize animated; increment serial]
Animated -->|no| StaticReady{Static Mesh ready?}
StaticReady -->|yes| BuildStatic[Materialize static; increment serial]
StaticReady -->|no| Missing{Model terminally missing?}
Missing -->|yes| AdvanceMissing[Advance without Node; increment serial]
Missing -->|no| WaitStatic[Wait static Mesh; rotate]
```
## Lifecycle/state
```mermaid
stateDiagram-v2
[*] --> Observe
Observe --> Waiting: animation/static unresolved
Observe --> Materializing: animated/static resource ready
Observe --> Advancing: empty or terminally missing batch
Waiting --> [*]: detached wait plan
Materializing --> [*]: detached materialization plan
Advancing --> [*]: detached advance plan
```
The planner itself is stateless. Queue rotation, cursor adoption, cancellation,
map reset and shutdown remain external lifecycle transitions.
## Main sequence
```mermaid
sequenceDiagram
participant L as StreamingWorldLoader
participant B as M2BuildBatchPlanner
participant D as M2BuildDispatchPlanner
participant Q as M2BuildQueue
participant M as M2 materializer
participant S as RenderBudgetScheduler
L->>L: observe animation request/prototype
alt animation not pending
L->>B: plan_batch
L->>L: lookup/request static Mesh when required
end
L->>D: plan_step(batch count, resource snapshot)
D-->>L: action, rotate flag, serial flag
alt wait action
L->>Q: rotate_front
else materialize action
L->>M: materialize selected slice
L->>Q: adopt progress and serial
else advance action
L->>Q: adopt progress and optional serial
end
L->>S: consume one M2_BUILD permit
```
## Dependency diagram
```mermaid
flowchart TB
Loader[StreamingWorldLoader] --> Dispatch[M2BuildDispatchPlanner]
Loader --> Batch[M2BuildBatchPlanner]
Loader --> Queue[M2BuildQueue]
Loader --> Caches[M2 cache/pipeline state]
Loader --> Materializers[M2 materializers]
Dispatch --> Snapshot[M2BuildResourceSnapshot accessors]
Dispatch --> Values[Integers + booleans + StringName]
Dispatch -. no dependency .-> Engine[Node / Mesh / ResourceLoader]
Dispatch -. no dependency .-> State[Queue / cache / scheduler state]
```
## Ownership, threading and resources
- Planner owns call-local comparisons and the returned Dictionary only.
- `M2BuildResourceSnapshot` owns the typed call-local observation contract;
dispatch reads availability/pending/missing accessors but no engine references.
- Loader owns native-first observation order, action execution and permit use;
native/static/cached-animation observers own their resource phases.
- `M2BuildQueue` owns pending jobs, FIFO keys and progress cursors.
- Materializers own main-thread scene construction under loader-owned roots.
- Pure calls are thread-safe; the current loader adapter calls on main thread.
## Errors, cancellation and recovery
| Failure/state | Detection | Behavior | Diagnostic | Recovery |
|---|---|---|---|---|
| Animation request pending | Boolean observation | Wait wins even if other flags are true | Priority fixture | Retry when queue returns front |
| Non-positive batch | Count comparison | Advance without serial increment | Zero/negative fixtures | Next group/cursor operation |
| Static Mesh unresolved | No Mesh and not missing | Rotate without serial increment | Retry fixture | Loader request/cache completes |
| Static model missing | Terminal missing flag | Advance without Node and increment serial | Missing fixture | Later group continues |
| Contradictory static ready/missing | Both true | Ready Mesh wins | Priority fixture | Loader normally prevents contradiction |
| Tile cancellation/root invalid | Outside planner | Loader cancels before dispatch | Queue/shutdown regressions | Eligible tile may requeue |
## Configuration and capabilities
The planner adds no setting. Existing animated/static batch limits, animation
enable flag, visibility/shadow settings and `M2_BUILD` permits remain external.
## Persistence, cache and migration
No state is serialized. No ADT/M2 cache format or version changes; no rebake or
migration is required.
## Diagnostics and observability
The planner emits no logs. Its verifier reports the action matrix, source
boundaries and bounded 20,000-call timing. Existing loader queue/hitch metrics
remain unchanged.
## Verification
- `verify_m2_build_dispatch_planner.gd` covers wait priority, zero/negative
counts, animated/static priority, unresolved retry, terminal missing, detached
results, loader/dependency boundaries and bounded timing.
- Queue, batch planner, materializers, caches, shutdown, facade, internal-access
and checkpoint tests protect adjacent behavior.
- Fidelity evidence is exact branch/transition extraction only. No private asset
or original-client visual comparison is claimed.
## Extension points
Static, cached animated and native animated observation now live behind sibling
services that produce `M2BuildResourceSnapshot`. Generic callbacks, signals and a shared state-machine
framework remain intentionally excluded.
## Capability status
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| M2 build action selection | Implemented extraction | Priority/matrix/source/timing verifier | Asset-backed traversal pending |
| Static/cached/native animation observation | Implemented separately | Observer/cache/pipeline regressions | Asset-backed traversal pending |
| Queue/cursor state | Implemented separately | M2 build queue verifier | Asset-backed traversal pending |
| Materialization | Implemented separately | Static/animated materializer verifiers | GPU/p95/p99 evidence pending |
## Known gaps and risks
- Native prototype attempts remain synchronous through the observer; loader still
owns static request I/O and terminal animated ResourceLoader polling.
- Synthetic timing does not measure ResourceLoader, Node or GPU work.
- Private traversal, leak, visual and p95/p99 evidence remains unavailable.
## Source map
| Path | Responsibility |
|---|---|
| `src/render/m2/m2_build_dispatch_planner.gd` | Pure action priority and transition plan |
| `src/render/m2/m2_build_resource_snapshot.gd` | Typed per-step resource observation contract |
| `src/render/m2/m2_cached_animation_resource_observer.gd` | Cached animation observation/request phase |
| `src/render/m2/m2_native_animation_resource_observer.gd` | Native candidate/read/build/cache observation |
| `src/scenes/streaming/streaming_world_loader.gd` | Observation order, action execution, permits and engine lifetime |
| `src/render/m2/m2_build_batch_planner.gd` | Batch count and cursor plan |
| `src/render/m2/m2_build_queue.gd` | Pending jobs, FIFO and cursor ownership |
| `src/tools/verify_m2_build_dispatch_planner.gd` | Matrix, boundary and timing regression |
## Related decisions and references
- [`m2-build-batch-planner.md`](m2-build-batch-planner.md)
- [`m2-build-resource-snapshot.md`](m2-build-resource-snapshot.md)
- [`m2-build-queue.md`](m2-build-queue.md)
- [`m2-static-batch-materializer.md`](m2-static-batch-materializer.md)
- [`m2-animated-instance-materializer.md`](m2-animated-instance-materializer.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)
+8 -1
View File
@@ -33,6 +33,7 @@ flowchart LR
Queue --> Job[M2BuildJob] Queue --> Job[M2BuildJob]
Queue --> Loader Queue --> Loader
Loader --> Planner[M2BuildBatchPlanner] Loader --> Planner[M2BuildBatchPlanner]
Loader --> Dispatch[M2BuildDispatchPlanner]
Loader --> Static[M2StaticBatchMaterializer] Loader --> Static[M2StaticBatchMaterializer]
Loader --> Animated[M2AnimatedInstanceMaterializer] Loader --> Animated[M2AnimatedInstanceMaterializer]
Loader --> Scheduler[RenderBudgetScheduler] Loader --> Scheduler[RenderBudgetScheduler]
@@ -171,6 +172,8 @@ flowchart TB
- Job retains the exact groups Dictionary, a fresh group-key snapshot and root. - Job retains the exact groups Dictionary, a fresh group-key snapshot and root.
- Root ownership remains with the loader/tile SceneTree; queue release never frees it. - Root ownership remains with the loader/tile SceneTree; queue release never frees it.
- Loader validates `is_instance_valid`, frees empty/aborted roots and mutates tile state. - Loader validates `is_instance_valid`, frees empty/aborted roots and mutates tile state.
- `M2BuildDispatchPlanner` selects wait/materializer/advance actions without
borrowing queue-owned engine references.
- All current operations run on the renderer main thread; no mutex is required. - All current operations run on the renderer main thread; no mutex is required.
- Group worker results cross their existing mutex mailbox before enqueue. - Group worker results cross their existing mutex mailbox before enqueue.
@@ -229,7 +232,8 @@ queue base, signals and callbacks are intentionally excluded.
| Typed keyed M2 jobs and FIFO | Implemented extraction | Lifecycle/order/source/timing verifier | Asset-backed traversal pending | | Typed keyed M2 jobs and FIFO | Implemented extraction | Lifecycle/order/source/timing verifier | Asset-backed traversal pending |
| Cursor/serial ownership | Implemented extraction | Atomic progress fixtures | Typed batch-plan result remains Dictionary | | Cursor/serial ownership | Implemented extraction | Atomic progress fixtures | Typed batch-plan result remains Dictionary |
| Root destruction | Existing loader-owned | Lifetime/source/shutdown regressions | Keep outside state service | | Root destruction | Existing loader-owned | Lifetime/source/shutdown regressions | Keep outside state service |
| Resource readiness/dispatch | Existing loader-owned | Adjacent cache/build tests | Safe extraction remains | | Resource dispatch decision | Implemented separately | Dispatch planner verifier | Asset-backed traversal pending |
| Resource observation/requests | Existing loader-owned | Adjacent cache/build tests | Safe extraction remains |
## Known gaps and risks ## Known gaps and risks
@@ -246,11 +250,14 @@ queue base, signals and callbacks are intentionally excluded.
| `src/render/m2/m2_build_queue.gd` | Keyed job ownership, FIFO/stale/rotation lifecycle and diagnostics | | `src/render/m2/m2_build_queue.gd` | Keyed job ownership, FIFO/stale/rotation lifecycle and diagnostics |
| `src/scenes/streaming/streaming_world_loader.gd` | Eligibility, readiness, permits, materialization, tile state and root cleanup | | `src/scenes/streaming/streaming_world_loader.gd` | Eligibility, readiness, permits, materialization, tile state and root cleanup |
| `src/render/m2/m2_build_batch_planner.gd` | Batch count and cursor-plan calculation | | `src/render/m2/m2_build_batch_planner.gd` | Batch count and cursor-plan calculation |
| `src/render/m2/m2_build_dispatch_planner.gd` | Resource-state action and transition planning |
| `src/tools/verify_m2_build_queue.gd` | Lifecycle/order/lifetime/boundary/timing regression | | `src/tools/verify_m2_build_queue.gd` | Lifecycle/order/lifetime/boundary/timing regression |
## Related decisions and references ## Related decisions and references
- [`m2-build-batch-planner.md`](m2-build-batch-planner.md) - [`m2-build-batch-planner.md`](m2-build-batch-planner.md)
- [`m2-build-dispatch-planner.md`](m2-build-dispatch-planner.md)
- [`m2-build-resource-snapshot.md`](m2-build-resource-snapshot.md)
- [`m2-static-batch-materializer.md`](m2-static-batch-materializer.md) - [`m2-static-batch-materializer.md`](m2-static-batch-materializer.md)
- [`m2-animated-instance-materializer.md`](m2-animated-instance-materializer.md) - [`m2-animated-instance-materializer.md`](m2-animated-instance-materializer.md)
- [`world-renderer.md`](world-renderer.md) - [`world-renderer.md`](world-renderer.md)
+239
View File
@@ -0,0 +1,239 @@
# M2 Build Resource Snapshot
## Metadata
| Field | Value |
|---|---|
| Status | Implemented extraction |
| Target/work package | M03 / `M03-RND-M2-BUILD-RESOURCE-SNAPSHOT-001` |
| Owners | Per-build-step observed M2 resource references and outcome flags |
| Last verified | Worktree `work/sindo-main-codex/m03-m2-build-resource-snapshot`, 2026-07-18 |
| Profiles/capabilities | Existing static MultiMesh and animated-instance build paths |
## Purpose
Carry the resource observations for one M2 build operation as a typed contract:
normalized path, optional animated prototype, pending-animation state, optional
static Mesh and terminal missing-model state. The snapshot never owns engine lifetime.
## Non-goals
- Locate, request, load, validate or cache M2 resources.
- Decide dispatch action, batch count, queue order or permit consumption.
- Create, attach or free Nodes, Meshes, MultiMeshes or animated instances.
- Change animation allowlists, native candidate rules or cache paths/formats.
- Persist observations beyond one build operation.
## Context and boundaries
```mermaid
flowchart LR
Native[M2NativeAnimationResourceObserver] --> Snapshot[M2BuildResourceSnapshot]
Cached[M2CachedAnimationResourceObserver] --> Snapshot
Static[M2StaticBuildResourceObserver] --> Snapshot
Snapshot --> Dispatch[M2BuildDispatchPlanner]
Snapshot --> Loader
Loader --> Animated[M2AnimatedInstanceMaterializer]
Loader --> Static[M2StaticBatchMaterializer]
```
The snapshot depends only on `RefCounted`, String, booleans and borrowed Node3D/
Mesh references. ResourceLoader, cache/pipeline state, SceneTree mutation,
RenderingServer/RIDs, files, workers, mutexes, gameplay, network and Editor APIs
are forbidden.
## Public API
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|---|---|---|---|---|
| Constructor `(normalized_path, animated_prototype, animation_pending)` | Command | Capture first-phase animation observation | Main thread; one build operation | Values retained exactly |
| `normalized_relative_path()` | Query | Return observed normalized path | Snapshot lifetime | None |
| `animated_prototype()` | Query | Borrow exact animated prototype | Main thread; no ownership transfer | May be null |
| `has_animated_prototype()` | Query | Report prototype availability | Any serialized caller | None |
| `animation_request_pending()` | Query | Report pending animation request | Any serialized caller | None |
| `adopt_static_observation(mesh, missing)` | Command | Replace second-phase static observation | Main thread | Values retained exactly, including contradictory inputs |
| `static_mesh()` | Query | Borrow exact prepared Mesh | Main thread; no ownership transfer | May be null |
| `has_static_mesh()` | Query | Report static Mesh availability | Any serialized caller | None |
| `static_model_missing()` | Query | Report terminal missing outcome | Any serialized caller | None |
| `diagnostic_snapshot()` | Query | Return detached path/availability flags | Any serialized caller | Omits engine references |
## Inputs and outputs
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|---|---|---|---|---|---|
| Input | Normalized relative path | Loader path adapter | Snapshot/diagnostics | Copied String | Snapshot lifetime |
| Input | Optional animated prototype | Native/cached animation observer | Snapshot/materializer adapter | Borrowed exact Node3D reference | One build operation |
| Input | Animation request pending | Cached animation observer | Snapshot/dispatch planner | Boolean copy | One build operation |
| Input | Optional prepared static Mesh | Mesh cache/request path | Snapshot/materializer adapter | Borrowed exact Mesh reference | One build operation |
| Input | Static model terminally missing | Prototype cache state | Snapshot/dispatch planner | Boolean copy | One build operation |
| Output | Availability/pending/missing values | Snapshot | Dispatch planner | Scalar reads | One dispatch plan |
| Output | Borrowed prototype or Mesh | Snapshot | Loader materializer adapter | No ownership transfer | One materialization call |
| Output | Detached diagnostics | Snapshot | Tests/future metrics | Caller-owned Dictionary | Call result |
The snapshot is released after the build-loop operation. Releasing or replacing
it never frees the borrowed Node3D or Mesh.
## Data flow
```mermaid
flowchart TD
Path[Normalized M2 path] --> Construct[Create snapshot]
Animated[Animated prototype or null] --> Construct
Pending[Animation request pending] --> Construct
Construct --> PendingCheck{Animation pending?}
PendingCheck -->|yes| Dispatch[Dispatch planner]
PendingCheck -->|no| Batch[Batch planner]
Batch --> StaticNeeded{Positive static batch?}
StaticNeeded -->|yes| Adopt[Adopt Mesh and missing observation]
StaticNeeded -->|no| Dispatch
Adopt --> Dispatch
Dispatch --> Borrow[Loader borrows selected resource]
```
## Lifecycle/state
```mermaid
stateDiagram-v2
[*] --> AnimationObserved: construct
AnimationObserved --> AnimationObserved: animation pending or animated path
AnimationObserved --> StaticObserved: adopt static result
StaticObserved --> StaticObserved: replace static result
AnimationObserved --> Released: operation ends
StaticObserved --> Released: operation ends
Released --> [*]
```
## Main sequence
```mermaid
sequenceDiagram
participant L as StreamingWorldLoader
participant S as M2BuildResourceSnapshot
participant B as M2BuildBatchPlanner
participant D as M2BuildDispatchPlanner
participant M as M2 materializer
L->>L: delegate native animated prototype observation
L->>S: construct native result
opt no native prototype
L->>L: delegate cached animation observer
L->>S: receive cached/pending snapshot
end
alt animation not pending
L->>B: plan batch using snapshot availability
opt positive static batch
L->>L: lookup/request static Mesh
L->>S: adopt_static_observation
end
end
L->>D: plan_step(batch count, snapshot)
D-->>L: action plan
opt materialization action
L->>S: borrow prototype or Mesh
L->>M: materialize selected slice
end
```
## Dependency diagram
```mermaid
flowchart TB
Loader[StreamingWorldLoader] --> Snapshot[M2BuildResourceSnapshot]
Dispatch[M2BuildDispatchPlanner] --> Snapshot
Snapshot --> Values[String and booleans]
Snapshot --> Borrowed[Borrowed Node3D and Mesh]
Snapshot -. no dependency .-> IO[ResourceLoader / FileAccess]
Snapshot -. no dependency .-> State[Cache / pipeline / queue / scheduler]
Snapshot -. no dependency .-> Mutation[SceneTree / RenderingServer]
```
## Ownership, threading and resources
- Snapshot owns only copied scalar state and temporary references.
- Prototype/cache services retain resource state; the native observer owns its
phase while loader owns native-first ordering among resource observers.
- Scene roots remain loader/tile-owned; Mesh lifetime follows cache/resource refs.
- Dispatch planner reads only snapshot accessors and never mutates the snapshot.
- Current construction/adoption occurs on renderer main thread.
## Errors, cancellation and recovery
| Failure/state | Detection | Behavior | Diagnostic | Recovery |
|---|---|---|---|---|
| Empty path | Exact String retained | No validation or hidden normalization | Contract fixture | Loader already normalizes input |
| Null prototype/Mesh | Availability accessor false | Dispatch uses pending/missing state | Contract fixture | Loader request/cache path retries |
| Mesh and missing both true | Exact adoption | Both retained; dispatch gives ready Mesh priority | Contradiction fixture | Loader normally prevents combination |
| Static observation replaced | Explicit adoption | Latest Mesh/missing values win | Replacement fixture | None required |
| Borrowed Node externally freed | Outside snapshot | Loader validity/lifecycle gates remain authoritative | Shutdown regressions | Tile may cancel/requeue |
| Tile cancellation | Outside snapshot | Ephemeral snapshot releases references | Queue/shutdown regressions | Eligible tile may rebuild |
## Configuration and capabilities
The snapshot adds no setting. Existing animation enable/allow/deny rules, batch
limits, cache paths, render settings and scheduler permits remain external.
## Persistence, cache and migration
No observation is serialized. No ADT/M2 cache version changes and no rebake or
migration are required.
## Diagnostics and observability
`diagnostic_snapshot()` exposes normalized path and four availability/outcome
flags without Node/Mesh references. The module emits no logs. Existing loader
queue/hitch metrics remain unchanged.
## Verification
- `verify_m2_build_resource_snapshot.gd` covers exact path/reference identity,
defaults, static adoption/replacement/clear, contradictory flags, detached
diagnostics, engine lifetime, loader/dispatch boundaries and bounded timing.
- Dispatch, queue, batch, materializer, cache/pipeline, shutdown, facade,
internal-access and checkpoint regressions protect adjacent behavior.
- Fidelity evidence is exact observation transfer only; no private asset or
original-client visual claim is made.
## Extension points
`M2CachedAnimationResourceObserver` produces the cached animated phase and
`M2StaticBuildResourceObserver` produces the static phase. Native animation is
produced by `M2NativeAnimationResourceObserver`. The value stays independent of all
producers and of generic callback frameworks.
## Capability status
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| Typed per-step M2 resource observation | Implemented extraction | Identity/adoption/lifetime/source/timing verifier | Asset-backed traversal pending |
| Dispatch decision | Implemented separately | Dispatch planner verifier | Asset-backed traversal pending |
| Static lookup/request execution | Implemented separately | Static observer verifier | Asset-backed traversal pending |
| Cached animated lookup/request execution | Implemented separately | Cached observer verifier | Asset-backed traversal pending |
| Native animated lookup/build execution | Implemented observer extraction | Native observer lifecycle/source verifier | Asset-backed traversal pending |
| Engine lifetime/materialization | Loader/materializer-owned | Lifetime/materializer regressions | GPU/p95/p99 evidence pending |
## Known gaps and risks
- Static observation is mutable during its short two-phase construction.
- Raw Node3D/Mesh references remain necessary for current materializer APIs.
- Native parsing/build remains synchronous; loader still owns terminal polling.
- Synthetic timing does not measure resource, Node or GPU work.
## Source map
| Path | Responsibility |
|---|---|
| `src/render/m2/m2_build_resource_snapshot.gd` | Typed per-step resource observations and diagnostics |
| `src/render/m2/m2_build_dispatch_planner.gd` | Snapshot-to-action planning |
| `src/render/m2/m2_static_build_resource_observer.gd` | Static snapshot production and requests |
| `src/render/m2/m2_cached_animation_resource_observer.gd` | Cached animated snapshot production and requests |
| `src/render/m2/m2_native_animation_resource_observer.gd` | Native prototype observation producer |
| `src/scenes/streaming/streaming_world_loader.gd` | Observer ordering and materializer borrowing |
| `src/tools/verify_m2_build_resource_snapshot.gd` | Identity/adoption/lifetime/boundary/timing regression |
## Related decisions and references
- [`m2-build-dispatch-planner.md`](m2-build-dispatch-planner.md)
- [`m2-build-batch-planner.md`](m2-build-batch-planner.md)
- [`m2-build-queue.md`](m2-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)
@@ -0,0 +1,242 @@
# M2 Cached Animation Resource Observer
## Metadata
| Field | Value |
|---|---|
| Status | Implemented extraction |
| Target/work package | M03 / `M03-RND-M2-CACHED-ANIMATION-RESOURCE-OBSERVER-001` |
| Owners | Cached animated M2 eligibility, request admission and initial snapshot |
| Last verified | Worktree `work/sindo-main-codex/m03-m2-cached-animation-resource-observer`, 2026-07-18 |
| Profiles/capabilities | Existing optional cached-GLB animated M2 path |
## Purpose
Produce the cached-animation phase of `M2BuildResourceSnapshot`: reuse an exact
animated prototype, wait for a pending request, admit the first safe GLB request,
or record a terminal static-only animation outcome without changing behavior.
## Non-goals
- Load/build native GryphonRoost animation or emit its diagnostics.
- Poll/finalize threaded Resources or instantiate/fix imported scenes.
- Own/free prototype Nodes, snapshots, pipeline entries or cache state.
- Materialize instances, consume permits or change animation policy defaults.
- Change GLB layout, cache format, batching, profiles or visible output.
## Context and boundaries
```mermaid
flowchart LR
Loader[StreamingWorldLoader] --> Native[M2NativeAnimationResourceObserver]
Native -->|no prototype| Observer[M2CachedAnimationResourceObserver]
Prototype[M2PrototypeCacheState] --> Observer
Pipeline[M2AnimationLoadPipelineState] --> Observer
Resource[ResourceLoader and GLB JSON] --> Observer
Observer --> Snapshot[M2BuildResourceSnapshot]
Snapshot --> Dispatch[M2BuildDispatchPlanner]
```
## Public API
| Symbol | Kind | Purpose | Failure behavior |
|---|---|---|---|
| `observe(path, cache_dir, max_primitives, allow, deny, prototype_state, pipeline_state, debug)` | Command/query | Produce cached prototype/pending/static-only snapshot | Invalid input returns empty non-pending snapshot |
| `find_eligible_glb_cache_path(...)` | I/O query | Select first existing safe historical candidate | Returns empty String |
| `is_animation_path_allowed(path, allow, deny)` | Pure query | Apply stripped case-insensitive substring policy | Empty path/allowlist rejects; deny wins |
| `cache_resource_paths(cache_dir, path)` | Pure query | Return nested/lowercase/basename GLB candidates | Empty values yield fewer candidates |
| `glb_cache_is_safe_for_runtime_animation(path, max, debug)` | I/O query | Validate animations, primitive limit and schema | Invalid/missing GLB rejects |
| `read_glb_json(path)` | I/O query | Read version-2 GLB JSON chunk | Returns empty Dictionary |
| `glb_primitive_count(gltf)` | Pure query | Count primitives across meshes | Invalid entries contribute zero |
| `glb_animation_schema(gltf)` | Pure query | Read OpenWC schema marker | Missing marker returns empty String |
## Inputs and outputs
| Direction | Data | Producer | Consumer | Ownership/lifetime |
|---|---|---|---|---|
| Input | Normalized M2 path/cache directory | Loader | Observer | Copied Strings; one observation |
| Input | Primitive cap and allow/deny patterns | Loader exports | Observer | Copied/read-only call values |
| Input | Prototype and request states | M2 state services | Observer | Borrowed services; map/session |
| Input | Existing GLB cache file | Offline cache pipeline | Observer | Read-only file access |
| Output | Resource snapshot | Observer | Loader/dispatch/materializer | Caller-owned snapshot; one build step |
| Output | Borrowed exact prototype | Prototype state | Snapshot/materializer | No ownership transfer |
| Output | Threaded request record | Observer | Animation pipeline | Pipeline-owned copied paths |
| Output | Static-only transition | Observer | Prototype state | State-owned copied path |
## Data flow
```mermaid
flowchart TD
Start[Observe normalized path] --> Valid{Valid composition?}
Valid -->|no| Empty[Empty non-pending snapshot]
Valid -->|yes| Cached{Prototype cached?}
Cached -->|yes| Ready[Snapshot with exact prototype]
Cached -->|no| Static{Already static-only?}
Static -->|yes| Empty
Static -->|no| Pending{Request exists?}
Pending -->|yes| Wait[Pending snapshot]
Pending -->|no| Policy{Allow and not deny?}
Policy -->|no| Mark[Mark static-only]
Policy -->|yes| Candidate[Historical GLB candidates]
Candidate --> Safe{Animations, primitive cap, schema safe?}
Safe -->|no usable| Mark
Safe -->|yes| Request[Threaded request]
Request -->|OK or busy| Remember[Remember and return pending snapshot]
Request -->|error| Mark
Mark --> Empty
```
## Lifecycle/state
```mermaid
stateDiagram-v2
[*] --> Observing
Observing --> Cached
Observing --> Pending
Observing --> Requested
Observing --> StaticOnly
Observing --> Rejected
Cached --> [*]
Pending --> [*]
Requested --> [*]
StaticOnly --> [*]
Rejected --> [*]
```
## Main sequence
```mermaid
sequenceDiagram
participant L as StreamingWorldLoader
participant O as CachedAnimationObserver
participant C as PrototypeCacheState
participant P as AnimationLoadPipelineState
participant R as ResourceLoader
participant S as M2BuildResourceSnapshot
L->>L: attempt native candidate
alt no native prototype
L->>O: observe(path, cache/policy/state)
O->>C: find animated / static-only
O->>P: has request
opt eligible cache candidate
O->>R: exists + GLB JSON + threaded request
O->>P: remember request
end
O-->>L: snapshot
else native prototype
L->>S: construct native snapshot
end
```
## Dependency diagram
```mermaid
flowchart TB
Observer[M2CachedAnimationResourceObserver] --> Snapshot[M2BuildResourceSnapshot]
Observer --> Prototype[M2PrototypeCacheState]
Observer --> Pipeline[M2AnimationLoadPipelineState]
Observer --> ResourceLoader
Observer --> FileAccess[GLB header and JSON]
Observer -. no dependency .-> Native[M2 native builder/repository]
Observer -. no dependency .-> Finalizer[M2AnimatedSceneFinalizer]
Observer -. no dependency .-> Scheduler[RenderBudgetScheduler]
Observer -. no dependency .-> SceneTree
```
## Ownership, threading and resources
- Observer retains no service, request, snapshot, Resource or Node reference.
- Prototype state retains accepted Nodes and static-only paths until shutdown.
- Pipeline state retains copied request paths until terminal polling/finalize.
- Snapshot borrows the exact cached prototype; observer never frees it.
- Resource existence/request and synchronous GLB JSON inspection run on the
renderer main thread, matching the previous loader behavior.
- The sibling native observer owns the native build attempt and the animation
resource finalizer owns terminal polling/finalize. Loader retains permits,
material lookup, materialization and SceneTree mutation.
## Errors, cancellation and recovery
| Failure/state | Behavior | Recovery |
|---|---|---|
| Empty path/dependency | Return empty non-pending snapshot without mutation | Correct composition |
| Cached prototype | Return exact reference immediately | None |
| Existing request | Return pending without duplicate admission | Retry after terminal drain |
| Disallowed/denied path | Mark static-only | New loader session/configuration |
| Missing/invalid/unsafe GLB | Continue candidates, then mark static-only | Repair/rebake cache and restart session |
| Request error | Mark static-only | New loader session/cache repair |
| Tile cancellation | Observer retains nothing | Loader queue remains authoritative |
| Shutdown | Loader drains requests before state clear | New loader starts empty |
## Configuration and capabilities
The service consumes existing `m2_cache_dir`, `m2_animated_max_primitives`,
`m2_animated_allowlist_patterns`, `m2_animated_denylist_patterns` and debug flag.
It adds no setting and preserves runtime mutability and defaults.
## Persistence, cache and migration
No persistence or cache format changes are introduced. Existing `.glb` candidate
layout and OpenWC schema markers are read unchanged; no rebake is required.
## Diagnostics and observability
Unsupported non-empty schemas preserve the historical optional
`M2_ANIM_REJECT` debug line. Snapshot and pipeline diagnostics remain detached;
queue and hitch metrics are unchanged.
## Verification
- Dedicated verifier covers invalid/cached identity/static/pending/missing
lifecycle, allow/deny semantics, exact path order, generated GLB header/JSON,
animation presence, primitive cap, accepted/rejected schemas, source boundaries
and bounded timing.
- Snapshot, dispatch, pipeline, prototype, shutdown, facade, internal-access and
checkpoint regressions protect adjacent behavior.
- Fidelity evidence is exact policy/I/O extraction. No original-client visual or
animation parity claim is made.
## Extension points
Native and cached observation remain sibling services. Terminal ResourceLoader
polling/finalization uses the dedicated finalizer while retaining pipeline state.
## Capability status
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| Cached animation observation/request | Implemented extraction | Lifecycle/policy/GLB/source/timing verifier | Asset-backed traversal pending |
| Native animation observation | Implemented sibling extraction | Native observer lifecycle/source verifier | Asset-backed traversal pending |
| Animated finalize/preparation | Implemented finalizer extraction | Pipeline/finalizer regressions | Asset-backed traversal pending |
## Known gaps and risks
- A successful asynchronous request is not started by the unit fixture because
leaving it undrained leaks process work; source and pipeline tests cover admission.
- Generated GLB metadata fixtures validate policy, not Godot import fidelity.
- Private traversal, leak/descriptor pressure, p95/p99 and paired visuals remain
unavailable without private assets.
## Source map
| Path | Responsibility |
|---|---|
| `src/render/m2/m2_cached_animation_resource_observer.gd` | Cached GLB policy, request and snapshot production |
| `src/render/m2/m2_build_resource_snapshot.gd` | Typed per-step observation value |
| `src/render/m2/m2_animation_load_pipeline_state.gd` | Pending request/finalize state |
| `src/render/m2/m2_animation_resource_finalizer.gd` | Terminal polling/load/finalize outcomes |
| `src/render/m2/m2_prototype_cache_state.gd` | Prototype and static-only outcomes |
| `src/render/m2/m2_native_animation_resource_observer.gd` | Native-first candidate/read/build/cache observation |
| `src/scenes/streaming/streaming_world_loader.gd` | Composition, fallback order, finalize and execution |
| `src/tools/verify_m2_cached_animation_resource_observer.gd` | Lifecycle/policy/GLB/boundary/timing regression |
## Related decisions and references
- [`m2-build-resource-snapshot.md`](m2-build-resource-snapshot.md)
- [`m2-native-animation-resource-observer.md`](m2-native-animation-resource-observer.md)
- [`m2-animation-load-pipeline-state.md`](m2-animation-load-pipeline-state.md)
- [`m2-prototype-cache-state.md`](m2-prototype-cache-state.md)
- [`m2-animated-scene-finalizer.md`](m2-animated-scene-finalizer.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)
+24 -15
View File
@@ -78,7 +78,7 @@ Side effects are limited to collection mutation and retaining String/integer val
flowchart TD flowchart TD
Start[Successful ResourceLoader request] --> Remember[remember request] Start[Successful ResourceLoader request] --> Remember[remember request]
Remember --> Snapshot[request records snapshot] Remember --> Snapshot[request records snapshot]
Snapshot --> Poll[Loader polls status] Snapshot --> Poll[Mesh resource finalizer polls status]
Poll --> Active{In progress?} Poll --> Active{In progress?}
Active -->|yes| Snapshot Active -->|yes| Snapshot
Active -->|no| Complete[complete request with terminal status] Active -->|no| Complete[complete request with terminal status]
@@ -86,7 +86,7 @@ flowchart TD
FIFO --> Permit{Loader permit available?} FIFO --> Permit{Loader permit available?}
Permit -->|no| FIFO Permit -->|no| FIFO
Permit -->|yes| Pop[Pop oldest terminal record] Permit -->|yes| Pop[Pop oldest terminal record]
Pop --> Finalize[Loader gets Resource and delegates first-Mesh extraction] Pop --> Finalize[Mesh resource finalizer gets Resource and extracts first Mesh]
Finalize --> Prepare[M2RuntimeMeshFinalizer prepares Mesh] Finalize --> Prepare[M2RuntimeMeshFinalizer prepares Mesh]
Prepare --> Adopt[Loader adopts Mesh or marks prototype outcome state] Prepare --> Adopt[Loader adopts Mesh or marks prototype outcome state]
``` ```
@@ -113,6 +113,7 @@ again only if loader cache/missing rules permit it.
```mermaid ```mermaid
sequenceDiagram sequenceDiagram
participant Loader as StreamingWorldLoader participant Loader as StreamingWorldLoader
participant Finalizer as M2MeshResourceFinalizer
participant Resource as ResourceLoader participant Resource as ResourceLoader
participant State as M2MeshLoadPipelineState participant State as M2MeshLoadPipelineState
participant Budget as RenderBudgetScheduler participant Budget as RenderBudgetScheduler
@@ -120,14 +121,16 @@ sequenceDiagram
Resource-->>Loader: OK or ERR_BUSY Resource-->>Loader: OK or ERR_BUSY
Loader->>State: remember_request(normalized, cache path) Loader->>State: remember_request(normalized, cache path)
loop frames loop frames
Loader->>State: request_records_snapshot() Loader->>Finalizer: poll_terminal_requests(state, prototype cache)
Loader->>Resource: load_threaded_get_status(path) Finalizer->>State: request_records_snapshot()
Finalizer->>Resource: load_threaded_get_status(path)
end end
Loader->>State: complete_request(normalized, terminal status) Finalizer->>State: complete_request(normalized, terminal status)
Loader->>Budget: try_consume_permit(M2_MESH_FINALIZE) Loader->>Budget: try_consume_permit(M2_MESH_FINALIZE)
Loader->>State: pop_finalize_record() Loader->>Finalizer: finalize_next_resource(state, caches, directory)
Loader->>Resource: load_threaded_get(path) Finalizer->>State: pop_finalize_record()
Loader->>Loader: extract/refresh/adopt Mesh or mark missing Finalizer->>Resource: load_threaded_get(path)
Finalizer->>Finalizer: extract/refresh/adopt Mesh or mark missing
``` ```
## Dependency diagram ## Dependency diagram
@@ -135,7 +138,9 @@ sequenceDiagram
```mermaid ```mermaid
flowchart TB flowchart TB
Loader[StreamingWorldLoader] --> State[M2MeshLoadPipelineState] Loader[StreamingWorldLoader] --> State[M2MeshLoadPipelineState]
Loader --> Resource[ResourceLoader] Loader --> MeshFinalizer[M2MeshResourceFinalizer]
MeshFinalizer --> State
MeshFinalizer --> Resource[ResourceLoader]
Loader --> Budget[RenderBudgetScheduler] Loader --> Budget[RenderBudgetScheduler]
Loader --> MeshCache[M2 Mesh resource and prototype outcome cache states] Loader --> MeshCache[M2 Mesh resource and prototype outcome cache states]
Loader --> Finalizer[M2RuntimeMeshFinalizer] Loader --> Finalizer[M2RuntimeMeshFinalizer]
@@ -148,7 +153,9 @@ flowchart TB
- Main thread serializes all state mutation and snapshots. - Main thread serializes all state mutation and snapshots.
- State owns only request/finalize Dictionaries containing Strings and status integers. - State owns only request/finalize Dictionaries containing Strings and status integers.
- Loader owns ResourceLoader request lifetime and drains active paths before shutdown clear. - `M2MeshResourceFinalizer` owns terminal status polling, terminal Resource
retrieval, first-Mesh extraction, runtime preparation and cache/missing adoption.
- Loader owns request admission, scheduler permits and shutdown drain ordering.
- `M2MeshResourceCacheState` owns prepared static Mesh references and - `M2MeshResourceCacheState` owns prepared static Mesh references and
`M2RuntimeMeshFinalizer` owns refresh/rebuild/fallback. The loader owns shared `M2RuntimeMeshFinalizer` owns refresh/rebuild/fallback. The loader owns shared
adoption decisions, raw I/O and remaining engine resources; the prototype adoption decisions, raw I/O and remaining engine resources; the prototype
@@ -161,9 +168,9 @@ flowchart TB
|---|---|---|---|---| |---|---|---|---|---|
| Empty/duplicate request | State guard | Reject unchanged | Contract verifier | Correct caller/request later | | Empty/duplicate request | State guard | Reject unchanged | Contract verifier | Correct caller/request later |
| Request start failure | Loader return code | No state insert; mark missing | Existing loader behavior | Cache/source correction | | Request start failure | Loader return code | No state insert; mark missing | Existing loader behavior | Cache/source correction |
| Non-terminal status | Loader poll | Keep pending | Existing queue metric | Poll next frame | | Non-terminal status | Finalizer poll | Keep pending | Existing queue metric | Poll next frame |
| Terminal load failure | Status in popped record | Loader marks missing | Existing missing behavior | World/cache reload | | Terminal load failure | Status in popped record | Finalizer marks missing | Existing missing behavior | World/cache reload |
| Empty defensive path | Loader before poll | Discard and mark missing | Source regression | Correct request producer | | Empty defensive path | Finalizer before poll | Discard and mark missing | Source regression | Correct request producer |
| Shutdown | Loader drains pending Resource paths | Clear state | Shutdown verifier | New loader starts empty | | Shutdown | Loader drains pending Resource paths | Clear state | Shutdown verifier | New loader starts empty |
## Configuration and capabilities ## Configuration and capabilities
@@ -206,7 +213,8 @@ rebuild policy are unchanged; no migration or rebake is required.
| Capability | Status | Evidence | Gap/next step | | Capability | Status | Evidence | Gap/next step |
|---|---|---|---| |---|---|---|---|
| Static M2 request/finalize state | Implemented extraction | Contract/source/timing verifier | Asset-backed long traversal pending | | Static M2 request/finalize state | Implemented extraction | Contract/source/timing verifier | Asset-backed long traversal pending |
| ResourceLoader I/O | Existing loader-owned | Shutdown/material regressions | I/O adapter extraction optional | | Static request admission I/O | Implemented in observer | Observer/shutdown regressions | Asset-backed traversal pending |
| Static request polling/finalize I/O | Implemented extraction | Finalizer contract/source/timing verifier | Asset-backed traversal pending |
| Mesh cache | Implemented extraction | Mesh resource cache state verifier | Asset-backed memory/leak run pending | | Mesh cache | Implemented extraction | Mesh resource cache state verifier | Asset-backed memory/leak run pending |
| First-Mesh extraction | Implemented extraction | Resource/order/lifetime verifier | Asset-backed corrupt-scene fixture pending | | First-Mesh extraction | Implemented extraction | Resource/order/lifetime verifier | Asset-backed corrupt-scene fixture pending |
| Mesh preparation | Implemented extraction | Runtime finalizer transition/rebuild verifier | Asset-backed material comparison pending | | Mesh preparation | Implemented extraction | Runtime finalizer transition/rebuild verifier | Asset-backed material comparison pending |
@@ -224,11 +232,12 @@ rebuild policy are unchanged; no migration or rebake is required.
| Path | Responsibility | | Path | Responsibility |
|---|---| |---|---|
| `src/render/m2/m2_mesh_load_pipeline_state.gd` | Pending records, terminal FIFO and metrics | | `src/render/m2/m2_mesh_load_pipeline_state.gd` | Pending records, terminal FIFO and metrics |
| `src/render/m2/m2_mesh_resource_finalizer.gd` | Terminal polling, Resource extraction, preparation and adoption |
| `src/render/m2/m2_mesh_resource_cache_state.gd` | Prepared static Mesh references and final clear | | `src/render/m2/m2_mesh_resource_cache_state.gd` | Prepared static Mesh references and final clear |
| `src/render/m2/m2_mesh_resource_extractor.gd` | First-Mesh selection and temporary PackedScene lifetime | | `src/render/m2/m2_mesh_resource_extractor.gd` | First-Mesh selection and temporary PackedScene lifetime |
| `src/render/m2/m2_runtime_mesh_finalizer.gd` | Refresh/rebuild/fallback preparation | | `src/render/m2/m2_runtime_mesh_finalizer.gd` | Refresh/rebuild/fallback preparation |
| `src/render/m2/m2_prototype_cache_state.gd` | Missing-model outcome retention | | `src/render/m2/m2_prototype_cache_state.gd` | Missing-model outcome retention |
| `src/scenes/streaming/streaming_world_loader.gd` | Cache path choice, I/O polling, permits and adoption decisions | | `src/scenes/streaming/streaming_world_loader.gd` | Cache path choice, request admission, permits and composition |
| `src/render/m2/m2_runtime_mesh_rebuild_classifier.gd` | Downstream stale Mesh rebuild decision | | `src/render/m2/m2_runtime_mesh_rebuild_classifier.gd` | Downstream stale Mesh rebuild decision |
| `src/tools/verify_m2_mesh_load_pipeline_state.gd` | Lifecycle/boundary/timing regression | | `src/tools/verify_m2_mesh_load_pipeline_state.gd` | Lifecycle/boundary/timing regression |
+9 -7
View File
@@ -103,9 +103,9 @@ sequenceDiagram
participant Pipeline as M2MeshLoadPipelineState participant Pipeline as M2MeshLoadPipelineState
Loader->>Cache: find/has normalized path Loader->>Cache: find/has normalized path
alt cache miss alt cache miss
Loader->>Pipeline: request/poll/finalize record Loader->>Pipeline: request record
Loader->>Loader: ResourceLoader get; delegate extract + prepare Loader->>Finalizer: poll/finalize one permitted record
Loader->>Cache: store_mesh(path, prepared Mesh) Finalizer->>Cache: store_mesh(path, prepared Mesh)
end end
Cache-->>Loader: exact retained Mesh Cache-->>Loader: exact retained Mesh
Loader->>Loader: materialize static M2 batch Loader->>Loader: materialize static M2 batch
@@ -133,8 +133,9 @@ flowchart TB
- Borrowed Mesh lookups do not transfer ownership or duplicate resources. - Borrowed Mesh lookups do not transfer ownership or duplicate resources.
- `M2MeshResourceExtractor` owns first-Mesh selection and temporary PackedScene - `M2MeshResourceExtractor` owns first-Mesh selection and temporary PackedScene
instances. `M2PrototypeCacheState` owns missing/prototype/animated state; the instances. `M2PrototypeCacheState` owns missing/prototype/animated state; the
static materializer owns MultiMesh construction/attachment; the loader owns static materializer owns MultiMesh construction/attachment;
resource adoption and build-job decisions. `M2MeshResourceFinalizer` owns resource adoption, while the loader owns
build-job and scheduler-permit decisions.
- The loader drains asynchronous work before the final cache clear. - The loader drains asynchronous work before the final cache clear.
## Errors, cancellation and recovery ## Errors, cancellation and recovery
@@ -186,7 +187,7 @@ the historical Mesh cache had no queue contribution or log site.
| Capability | Status | Evidence | Gap/next step | | Capability | Status | Evidence | Gap/next step |
|---|---|---|---| |---|---|---|---|
| Prepared static M2 Mesh cache | Implemented extraction | Contract/source/timing verifier | Asset-backed memory/leak run pending | | Prepared static M2 Mesh cache | Implemented extraction | Contract/source/timing verifier | Asset-backed memory/leak run pending |
| M2 Mesh request lifecycle | Implemented extraction | Pipeline state verifier | ResourceLoader I/O remains loader-owned | | M2 Mesh request lifecycle | Implemented extraction | Pipeline/finalizer verifiers | Asset-backed traversal pending |
| M2 Mesh extraction | Implemented extraction | Resource/order/lifetime verifier | Asset-backed corrupt-scene fixture pending | | M2 Mesh extraction | Implemented extraction | Resource/order/lifetime verifier | Asset-backed corrupt-scene fixture pending |
| M2 Mesh preparation | Implemented extraction | Runtime finalizer transition/rebuild verifier | Asset-backed material comparison pending | | M2 Mesh preparation | Implemented extraction | Runtime finalizer transition/rebuild verifier | Asset-backed material comparison pending |
@@ -203,8 +204,9 @@ the historical Mesh cache had no queue contribution or log site.
|---|---| |---|---|
| `src/render/m2/m2_mesh_resource_cache_state.gd` | Prepared Mesh references and final clear | | `src/render/m2/m2_mesh_resource_cache_state.gd` | Prepared Mesh references and final clear |
| `src/render/m2/m2_mesh_resource_extractor.gd` | First-Mesh selection and temporary PackedScene lifetime | | `src/render/m2/m2_mesh_resource_extractor.gd` | First-Mesh selection and temporary PackedScene lifetime |
| `src/render/m2/m2_mesh_resource_finalizer.gd` | Prepared Mesh producer and cache adoption |
| `src/render/m2/m2_prototype_cache_state.gd` | Prototype references and negative lookup outcomes | | `src/render/m2/m2_prototype_cache_state.gd` | Prototype references and negative lookup outcomes |
| `src/scenes/streaming/streaming_world_loader.gd` | Normalization, raw/resource I/O and materialization | | `src/scenes/streaming/streaming_world_loader.gd` | Normalization, request admission, permits and materialization |
| `src/render/m2/m2_runtime_mesh_finalizer.gd` | Refresh/rebuild/fallback preparation | | `src/render/m2/m2_runtime_mesh_finalizer.gd` | Refresh/rebuild/fallback preparation |
| `src/render/m2/m2_mesh_load_pipeline_state.gd` | Pending request and terminal finalize records | | `src/render/m2/m2_mesh_load_pipeline_state.gd` | Pending request and terminal finalize records |
| `src/tools/verify_m2_mesh_resource_cache_state.gd` | Cache ownership/lifetime/boundary/timing regression | | `src/tools/verify_m2_mesh_resource_cache_state.gd` | Cache ownership/lifetime/boundary/timing regression |
+10 -9
View File
@@ -92,27 +92,27 @@ No state or Resource reference is retained between calls.
```mermaid ```mermaid
sequenceDiagram sequenceDiagram
participant Loader as StreamingWorldLoader participant Finalizer as M2MeshResourceFinalizer
participant Extractor as M2MeshResourceExtractor participant Extractor as M2MeshResourceExtractor
participant Scene as PackedScene participant Scene as PackedScene
Loader->>Extractor: extract_first_mesh(loaded Resource) Finalizer->>Extractor: extract_first_mesh(loaded Resource)
alt direct Mesh alt direct Mesh
Extractor-->>Loader: same Mesh reference Extractor-->>Finalizer: same Mesh reference
else PackedScene else PackedScene
Extractor->>Scene: instantiate() Extractor->>Scene: instantiate()
Scene-->>Extractor: temporary root Scene-->>Extractor: temporary root
Extractor->>Extractor: depth-first first-Mesh search Extractor->>Extractor: depth-first first-Mesh search
Extractor->>Extractor: temporary_root.free() Extractor->>Extractor: temporary_root.free()
Extractor-->>Loader: Mesh or null Extractor-->>Finalizer: Mesh or null
end end
Loader->>Loader: prepare and cache Mesh or mark missing Finalizer->>Finalizer: prepare and cache Mesh or mark missing
``` ```
## Dependency diagram ## Dependency diagram
```mermaid ```mermaid
flowchart TB flowchart TB
Loader[StreamingWorldLoader] --> Extractor[M2MeshResourceExtractor] Finalizer[M2MeshResourceFinalizer] --> Extractor[M2MeshResourceExtractor]
Extractor --> Types[Resource / PackedScene / Node / Mesh] Extractor --> Types[Resource / PackedScene / Node / Mesh]
Loader --> Pipeline[M2MeshLoadPipelineState] Loader --> Pipeline[M2MeshLoadPipelineState]
Loader --> Cache[M2MeshResourceCacheState] Loader --> Cache[M2MeshResourceCacheState]
@@ -168,8 +168,8 @@ normalized M2 path. Its verifier measures traversal time and temporary Node coun
## Extension points ## Extension points
- `M2RuntimeMeshFinalizer` consumes the Mesh returned here and the loader stores - `M2MeshResourceFinalizer` passes the Mesh to `M2RuntimeMeshFinalizer` and
its result in `M2MeshResourceCacheState`. stores its result in `M2MeshResourceCacheState`.
- Broader generic scene traversal is intentionally excluded until another real - Broader generic scene traversal is intentionally excluded until another real
consumer requires the same exact contract. consumer requires the same exact contract.
@@ -194,8 +194,9 @@ normalized M2 path. Its verifier measures traversal time and temporary Node coun
| Path | Responsibility | | Path | Responsibility |
|---|---| |---|---|
| `src/render/m2/m2_mesh_resource_extractor.gd` | First-Mesh traversal and temporary PackedScene lifetime | | `src/render/m2/m2_mesh_resource_extractor.gd` | First-Mesh traversal and temporary PackedScene lifetime |
| `src/render/m2/m2_mesh_resource_finalizer.gd` | Terminal Resource consumer and extraction caller |
| `src/render/m2/m2_prototype_cache_state.gd` | Prototype and missing/static-only lookup state | | `src/render/m2/m2_prototype_cache_state.gd` | Prototype and missing/static-only lookup state |
| `src/scenes/streaming/streaming_world_loader.gd` | ResourceLoader/raw I/O, cache decisions and materialization | | `src/scenes/streaming/streaming_world_loader.gd` | Request admission, permits and materialization |
| `src/render/m2/m2_runtime_mesh_finalizer.gd` | Refresh/rebuild/fallback preparation | | `src/render/m2/m2_runtime_mesh_finalizer.gd` | Refresh/rebuild/fallback preparation |
| `src/render/m2/m2_mesh_resource_cache_state.gd` | Prepared Mesh retention | | `src/render/m2/m2_mesh_resource_cache_state.gd` | Prepared Mesh retention |
| `src/tools/verify_m2_mesh_resource_extractor.gd` | Resource/order/lifetime/boundary/timing regression | | `src/tools/verify_m2_mesh_resource_extractor.gd` | Resource/order/lifetime/boundary/timing regression |
+251
View File
@@ -0,0 +1,251 @@
# M2 Mesh Resource Finalizer
## Metadata
| Field | Value |
|---|---|
| Status | Implemented extraction |
| Target/work package | M03 / `M03-RND-M2-MESH-RESOURCE-FINALIZER-001` |
| Owners | Static M2 terminal Resource I/O, Mesh preparation and cache outcome |
| Last verified | Worktree `work/sindo-main-codex/m03-m2-mesh-resource-finalizer`, 2026-07-18 |
| Profiles/capabilities | Existing cached/static MultiMesh M2 path |
## Purpose
Drain static M2 ResourceLoader work and publish a prepared Mesh or missing-model
outcome without keeping terminal I/O and refresh logic in `StreamingWorldLoader`.
The same preparation method serves threaded and synchronous Mesh consumers.
## Non-goals
- Select cache candidates or admit threaded ResourceLoader requests.
- Own scheduler permits, prototype Nodes, MultiMeshes or SceneTree roots.
- Change first-Mesh traversal, refresh version, rebuild rules or raw parser.
- Merge static and animated terminal services through callbacks/frameworks.
- Change cache formats, profiles or visible output.
## Context and boundaries
```mermaid
flowchart LR
Observer[M2StaticBuildResourceObserver] --> Pipeline[M2MeshLoadPipelineState]
Loader[StreamingWorldLoader] --> Finalizer[M2MeshResourceFinalizer]
Pipeline --> Finalizer
ResourceLoader --> Finalizer
Finalizer --> Extractor[M2MeshResourceExtractor]
Finalizer --> Runtime[M2RuntimeMeshFinalizer]
Finalizer --> Raw[M2RawModelRepository]
Finalizer --> MeshCache[M2MeshResourceCacheState]
Finalizer --> Prototype[M2PrototypeCacheState]
```
## Public API
| Symbol | Kind | Purpose | Failure behavior |
|---|---|---|---|
| `poll_terminal_requests(pipeline, prototype_cache)` | I/O command | Move LOADED/FAILED records into completion FIFO | Invalid composition returns zero |
| `finalize_next_resource(pipeline, mesh_cache, prototype_cache, extracted_dir)` | I/O command | Pop one record and publish Mesh/missing outcome | Empty/invalid composition returns false |
| `prepare_mesh_for_runtime(path, mesh, extracted_dir)` | I/O preparation | Preserve current Mesh or load raw data and refresh stale Mesh | Null returns null |
| `load_threaded_get_status(path)` | I/O adapter | Production status query with injectable test seam | Returns ResourceLoader status |
| `load_threaded_get(path)` | I/O adapter | Production terminal Resource retrieval with injectable test seam | May return null |
## Inputs and outputs
| Direction | Data | Producer | Consumer | Ownership/lifetime |
|---|---|---|---|---|
| Input | Pending/finalize records | Mesh pipeline | Finalizer | Pipeline-owned copied Dictionaries |
| Input | Terminal Resource | ResourceLoader | Mesh extractor | Borrowed one operation |
| Input | Extracted/cached Mesh | Mesh extractor/loader consumer | Runtime preparation | Borrowed Resource reference |
| Input | Raw M2 Dictionary | Raw repository | Runtime finalizer | Call-local value |
| Output | Prepared Mesh | Runtime finalizer | Mesh cache/loader consumer | Resource reference |
| Output | Missing-model outcome | Finalizer | Prototype cache | Copied path; loader session |
| Output | Processed-record flag | Finalizer | Loader permit loop/tests | Boolean value |
## Data flow
```mermaid
flowchart TD
Poll[Poll pending records in insertion order] --> Path{Resource path empty?}
Path -->|yes| Discard[Discard and mark missing]
Path -->|no| Status{LOADED or FAILED?}
Status -->|no| Pending[Keep pending]
Status -->|yes| Complete[Append completion FIFO]
Complete --> Permit[Loader consumes M2_MESH_FINALIZE permit]
Permit --> Pop[Pop oldest terminal record]
Pop --> Cached{Empty path or Mesh cached?}
Cached -->|yes| Skip[Finish one permit operation]
Cached -->|no| Loaded{Status LOADED?}
Loaded -->|no| Missing[Mark model missing]
Loaded -->|yes| Get[Get terminal Resource]
Get --> Extract[Extract first Mesh]
Extract --> Found{Mesh found?}
Found -->|no| Missing
Found -->|yes| Current{Refresh version current?}
Current -->|yes| Store[Store exact Mesh]
Current -->|no| Raw[Read raw M2 data]
Raw --> Refresh[Finalize/rebuild/fallback Mesh]
Refresh --> Store
```
## Lifecycle/state
```mermaid
stateDiagram-v2
[*] --> Polling
Polling --> Pending: nonterminal status
Polling --> TerminalQueued: LOADED or FAILED
Polling --> Missing: empty Resource path
TerminalQueued --> Skipped: empty/cached record
TerminalQueued --> Missing: failed load or no Mesh
TerminalQueued --> Extracted: Mesh found
Extracted --> Current: refresh version current
Extracted --> Refreshing: stale Mesh
Refreshing --> Prepared: rebuild or fallback
Current --> Cached
Prepared --> Cached
Pending --> [*]
Skipped --> [*]
Missing --> [*]
Cached --> [*]
```
## Main sequence
```mermaid
sequenceDiagram
participant L as StreamingWorldLoader
participant F as M2MeshResourceFinalizer
participant P as M2MeshLoadPipelineState
participant R as ResourceLoader
participant E as M2MeshResourceExtractor
participant N as M2RuntimeMeshFinalizer
participant C as M2MeshResourceCacheState
L->>F: poll_terminal_requests(P, prototype cache)
F->>R: load_threaded_get_status(path)
F->>P: complete terminal request
L->>L: consume M2_MESH_FINALIZE permit
L->>F: finalize_next_resource(...)
F->>P: pop oldest record
F->>R: load_threaded_get(path)
F->>E: extract_first_mesh(Resource)
F->>N: requires_raw_data_for_refresh(Mesh)
opt stale Mesh
F->>F: read raw M2 data
F->>N: finalize_mesh(...)
end
F->>C: store prepared Mesh
```
## Dependency diagram
```mermaid
flowchart TB
Finalizer[M2MeshResourceFinalizer] --> ResourceLoader
Finalizer --> Pipeline[M2MeshLoadPipelineState]
Finalizer --> Extractor[M2MeshResourceExtractor]
Finalizer --> Runtime[M2RuntimeMeshFinalizer]
Finalizer --> Raw[M2RawModelRepository]
Finalizer --> MeshCache[M2MeshResourceCacheState]
Finalizer --> Prototype[M2PrototypeCacheState]
Loader[StreamingWorldLoader] --> Finalizer
Loader --> Scheduler[RenderBudgetScheduler]
Finalizer -. no dependency .-> Scheduler
Finalizer -. no dependency .-> MultiMesh
Finalizer -. no dependency .-> SceneTree
```
## Ownership, threading and resources
- All operations run synchronously on the renderer main thread.
- Pipeline owns pending/completion records; one permit pops one terminal record.
- Extractor owns temporary PackedScene instance cleanup and returns a Mesh reference.
- Runtime finalizer owns stale/current/rebuild/fallback rules; raw repository owns
only call-local native I/O.
- Mesh cache retains prepared Mesh references until final shutdown; prototype
cache retains missing-model paths.
- Service retains dependency references but no pending record, Resource, Mesh or
raw Dictionary between calls. Loader retains permits and materialization.
## Errors, cancellation and recovery
| Failure/state | Behavior | Recovery |
|---|---|---|
| Missing composition dependency | Return zero/false; preparation keeps Mesh where possible | Correct composition |
| Empty Resource path | Discard request and mark missing | Repair cache and restart session |
| Nonterminal status | Keep pending | Poll next tick |
| FAILED status | Pop and mark missing | Repair cache and restart session |
| Existing cached Mesh | Pop and skip terminal Resource get | None |
| Null/unsupported/no-Mesh Resource | Mark missing | Repair imported cache |
| Raw read/rebuild unavailable | Runtime finalizer marks/reuses original Mesh | Existing fallback |
| Tile cancellation | Shared cache work continues | Loader queue remains authoritative |
| Shutdown | Loader drains ResourceLoader before pipeline/cache clear | New loader starts empty |
## Configuration and capabilities
The service consumes the existing extracted directory. The scheduler retains
`m2_mesh_finalize_ops_per_tick`; material refresh version remains `2`.
## Persistence, cache and migration
No cache path, schema or version changes are introduced. Existing `.tscn/.glb`
Resources and in-memory refresh metadata remain compatible; no rebake is required.
## Diagnostics and observability
The service adds no log. Existing pending-plus-finalize metrics and refresh
metadata remain unchanged. Normalized relative path remains the correlation key.
## Verification
- Dedicated verifier covers status insertion order, empty-path discard, FIFO,
failed/cached/null outcomes, terminal get, exact Mesh identity, stale-only raw
read, exact finalizer arguments, cache adoption, source ownership and timing.
- Pipeline/cache/extractor/runtime-finalizer/raw-repository/prototype/shutdown,
facade, internal-access and baseline regressions protect adjacent behavior.
- Fidelity evidence is exact lifecycle/I/O extraction only; no private asset,
original-client visual, leak-pressure or p95/p99 claim is made.
## Extension points
Further extraction can move static prototype/material-source construction without
changing this terminal Resource contract. Animated finalization remains separate
because its candidate-before-material ordering and Node ownership differ.
## Capability status
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| Static terminal polling/load | Implemented extraction | Status/FIFO/load/source verifier | Asset-backed traversal pending |
| Mesh extraction/preparation/cache outcome | Implemented extraction | Identity/raw/finalizer/store verifier | Private leak/p95/p99 pending |
| Static MultiMesh materialization | Separate implemented service | Materializer verifier | GPU/asset-backed evidence pending |
## Known gaps and risks
- Terminal Resource get, extraction and stale rebuild remain synchronous main-thread work.
- Prepared Mesh cache remains unbounded until final shutdown.
- Private asset traversal, descriptor pressure, leak and p95/p99 evidence is absent.
## Source map
| Path | Responsibility |
|---|---|
| `src/render/m2/m2_mesh_resource_finalizer.gd` | Poll/load/extract/prepare/cache/missing service |
| `src/render/m2/m2_mesh_load_pipeline_state.gd` | Pending and completion FIFO ownership |
| `src/render/m2/m2_mesh_resource_extractor.gd` | First-Mesh and temporary-instance ownership |
| `src/render/m2/m2_runtime_mesh_finalizer.gd` | Refresh/rebuild/fallback rules |
| `src/render/m2/m2_raw_model_repository.gd` | Optional native raw M2 boundary |
| `src/render/m2/m2_mesh_resource_cache_state.gd` | Prepared Mesh ownership |
| `src/render/m2/m2_prototype_cache_state.gd` | Missing-model outcome ownership |
| `src/scenes/streaming/streaming_world_loader.gd` | Permit loop and materialization composition |
| `src/tools/verify_m2_mesh_resource_finalizer.gd` | Polling/preparation/boundary/timing regression |
## Related decisions and references
- [`m2-mesh-load-pipeline-state.md`](m2-mesh-load-pipeline-state.md)
- [`m2-mesh-resource-cache-state.md`](m2-mesh-resource-cache-state.md)
- [`m2-mesh-resource-extractor.md`](m2-mesh-resource-extractor.md)
- [`m2-runtime-mesh-finalizer.md`](m2-runtime-mesh-finalizer.md)
- [`m2-raw-model-repository.md`](m2-raw-model-repository.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)
@@ -0,0 +1,245 @@
# M2 Native Animation Resource Observer
## Metadata
| Field | Value |
|---|---|
| Status | Implemented extraction |
| Target/work package | M03 / `M03-RND-M2-NATIVE-ANIMATION-RESOURCE-OBSERVER-001` |
| Owners | Native animation candidate policy, raw read, build and cache outcome |
| Last verified | Worktree `work/sindo-main-codex/m03-m2-native-animation-resource-observer`, 2026-07-18 |
| Profiles/capabilities | Existing native GryphonRoost animated M2 path |
## Purpose
Resolve the historical native animated M2 path as one synchronous observation:
select a GryphonRoost candidate, reuse cached state, read animated raw data,
build a detached prototype and publish either the prototype or static-only result.
## Non-goals
- Add native animation candidates or change the `gryphonroost` predicate.
- Observe/request cached GLB animation or poll/finalize ResourceLoader work.
- Duplicate, attach, animate or free accepted instances in the SceneTree.
- Consume render permits or change build queue, batching and dispatch policy.
- Change native parsing/building, cache formats, profiles or visible output.
## Context and boundaries
```mermaid
flowchart LR
Loader[StreamingWorldLoader] --> Observer[M2NativeAnimationResourceObserver]
Observer --> Repository[M2RawModelRepository]
Observer --> Builder[M2NativeAnimatedBuilder]
Observer --> Cache[M2PrototypeCacheState]
Observer --> Prototype[Detached animated prototype]
Prototype --> Snapshot[M2BuildResourceSnapshot]
Observer -->|no prototype| Cached[M2CachedAnimationResourceObserver]
```
The observer is the native-first resource boundary. The loader supplies an
already-normalized path and remains responsible for fallback ordering and for
constructing the snapshot consumed by dispatch.
## Public API
| Symbol | Kind | Purpose | Failure behavior |
|---|---|---|---|
| `is_native_animation_candidate(path)` | Pure query | Apply the historical case-insensitive GryphonRoost substring policy | Empty/unmatched path returns false |
| `observe(path, extracted_dir, repository, cache, debug)` | Command/query | Return an exact cached/new native prototype or record static-only fallback | Invalid/non-candidate input returns null without mutation |
| `build_animated_prototype(data, extracted_dir)` | Adapter command | Call the retained native animated builder dependency | Missing builder or invalid result returns null |
The optional constructor dependency exists for deterministic tests; production
uses `M2NativeAnimatedBuilder` without a new abstraction or runtime setting.
## Inputs and outputs
| Direction | Data | Producer | Consumer | Ownership/lifetime |
|---|---|---|---|---|
| Input | Normalized relative M2 path | Loader path adapter | Observer | Copied String; one call |
| Input | Extracted directory | Loader configuration | Repository/builder | Copied String; one call |
| Input | Raw animated Dictionary | Repository | Observer/builder | Call-local value |
| Input | Prototype cache state | Loader composition | Observer | Borrowed service; loader session |
| Output | Cached or adopted prototype | Cache/observer | Loader snapshot | Borrowed exact Node3D reference |
| Output | Static-only transition | Observer | Prototype cache | Copied path; loader session |
| Output | Debug line | Observer | Runtime log | Emitted only when enabled and build succeeds |
## Data flow
```mermaid
flowchart TD
Start[Observe normalized path] --> Candidate{Valid native candidate?}
Candidate -->|no| None[Return null]
Candidate -->|yes| Cached{Cached prototype?}
Cached -->|yes| Return[Return exact prototype]
Cached -->|no| Static{Marked static-only?}
Static -->|yes| None
Static -->|no| Read[Read animated raw data]
Read --> Surfaces{Data and animated surfaces?}
Surfaces -->|no| Mark[Mark static-only]
Surfaces -->|yes| Build[Build detached prototype]
Build --> Children{Prototype has children?}
Children -->|no| Mark
Children -->|yes| Adopt[Adopt first cache prototype]
Adopt --> Log{Debug enabled?}
Log -->|yes| Emit[Emit exact native cache fields]
Log -->|no| Return
Emit --> Return
Mark --> None
```
## Lifecycle/state
```mermaid
stateDiagram-v2
[*] --> Observing
Observing --> Rejected: invalid or non-candidate
Observing --> Cached: positive cache hit
Observing --> StaticOnly: negative cache hit
Observing --> Reading: uncached candidate
Reading --> StaticOnly: no animated surfaces
Reading --> Building: usable raw data
Building --> StaticOnly: null or childless result
Building --> Adopted: valid result
Adopted --> [*]
Cached --> [*]
StaticOnly --> [*]
Rejected --> [*]
```
## Main sequence
```mermaid
sequenceDiagram
participant L as StreamingWorldLoader
participant O as NativeAnimationObserver
participant C as M2PrototypeCacheState
participant R as M2RawModelRepository
participant B as M2NativeAnimatedBuilder
L->>O: observe(path, directory, repository, cache, debug)
O->>C: find animated / is static-only
alt uncached native candidate
O->>R: load_animated_model_data(directory, path)
R-->>O: raw Dictionary
O->>B: build(raw, directory)
alt valid prototype
O->>C: adopt_animated_prototype(path, prototype)
O-->>L: canonical prototype
else unavailable
O->>C: mark_animation_static(path)
O-->>L: null
end
else cached result
O-->>L: prototype or null
end
```
## Dependency diagram
```mermaid
flowchart TB
Observer[M2NativeAnimationResourceObserver] --> Repository[M2RawModelRepository]
Observer --> Builder[M2NativeAnimatedBuilder]
Observer --> Cache[M2PrototypeCacheState]
Loader[StreamingWorldLoader] --> Observer
Observer -. no dependency .-> ResourceLoader
Observer -. no dependency .-> Pipeline[M2AnimationLoadPipelineState]
Observer -. no dependency .-> Scheduler[RenderBudgetScheduler]
Observer -. no dependency .-> SceneTree
```
## Ownership, threading and resources
- Observation and native parsing/building remain synchronous on the renderer
main thread, exactly as before extraction; there is no mid-call cancellation.
- The observer retains no path, raw Dictionary, Node or service reference.
- Prototype cache owns an accepted detached Node until final shutdown; callers
only borrow the canonical reference.
- The historical childless-builder rejection does not free its detached Node.
This deliberately preserved lifetime edge is documented as a remaining leak
risk rather than silently changed during architectural extraction.
- Loader owns snapshots, fallback sequencing, permits, materialization and every
SceneTree mutation.
## Errors, cancellation and recovery
| Failure/state | Behavior | Recovery |
|---|---|---|
| Empty/non-candidate path or missing dependency | Return null without state mutation | Correct composition/path |
| Cached prototype | Return exact canonical Node immediately | None |
| Existing static-only outcome | Return null without repeated I/O | New loader session after source repair |
| Empty raw data or animated surfaces | Mark static-only and return null | Repair source/parser and start new session |
| Null/childless build | Mark static-only and return null | Repair builder/data and start new session |
| Duplicate successful build | Cache releases later candidate and returns first | None |
| Cancellation during native call | Not supported; call completes | Loader controls whether the call starts |
| Shutdown | Observer retains nothing | Cache releases adopted Nodes after drains |
## Configuration and capabilities
The observer consumes the existing extracted directory and `debug_streaming`
flag. Candidate policy remains the case-insensitive substring `gryphonroost`.
No profile, cache, budget or animation setting is added.
## Persistence, cache and migration
No persistence or format changes are introduced. Positive and static-only state
continues to use the shutdown-lifetime prototype cache; no asset rebake is needed.
## Diagnostics and observability
A successful debug-enabled build emits the unchanged `M2_NATIVE_ANIM_CACHE`
record with path, surface count, bone count, animation id, sequence index,
activity score and length. Rejections remain silent as before.
## Verification
- Dedicated synthetic verifier covers candidate policy, invalid dependencies,
cache identity, static suppression, raw/surface/build failures, preserved
childless lifetime, exact repository/builder arguments, adoption, source
ownership and bounded candidate checks.
- Cached observer, raw repository, prototype cache, snapshot, dispatch, shutdown,
facade, internal-access and render-baseline checks protect adjacent behavior.
- Fidelity evidence is exact control-flow and diagnostic extraction. It is not
original-client animation, visual, memory or asset-backed performance evidence.
## Extension points
Terminal cached-GLB polling/finalization can move behind a separate adapter
without changing this native-first contract. Candidate expansion requires its
own fidelity evidence and must not silently change `Blizzlike335` behavior.
## Capability status
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| Native candidate selection | Implemented extraction | Policy/source/timing verifier | Additional candidates require fidelity work |
| Raw read/build/cache outcome | Implemented extraction | Failure/identity/adoption verifier | Asset-backed traversal pending |
| Terminal cached ResourceLoader polling | Loader-owned | Existing pipeline regressions | Dedicated finalize adapter |
## Known gaps and risks
- Native parsing/building is synchronous and not bounded by a render permit.
- Rejected childless builder Nodes retain the historical unowned lifetime edge.
- Private asset traversal, descriptor/leak pressure, p95/p99 and paired visual/
animation evidence remain unavailable in this package.
## Source map
| Path | Responsibility |
|---|---|
| `src/render/m2/m2_native_animation_resource_observer.gd` | Candidate/cache/raw/build/outcome/logging observation |
| `src/render/m2/m2_raw_model_repository.gd` | Optional native file/parser boundary |
| `src/render/m2/m2_prototype_cache_state.gd` | Positive prototype and static-only ownership |
| `addons/mpq_extractor/loaders/m2_native_animated_builder.gd` | Existing animated prototype construction |
| `src/scenes/streaming/streaming_world_loader.gd` | Composition, native-first fallback order and execution |
| `src/tools/verify_m2_native_animation_resource_observer.gd` | Lifecycle/identity/boundary/timing regression |
## Related decisions and references
- [`m2-cached-animation-resource-observer.md`](m2-cached-animation-resource-observer.md)
- [`m2-build-resource-snapshot.md`](m2-build-resource-snapshot.md)
- [`m2-raw-model-repository.md`](m2-raw-model-repository.md)
- [`m2-prototype-cache-state.md`](m2-prototype-cache-state.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)
+19 -10
View File
@@ -28,7 +28,9 @@ paths and paths whose animation fallback is static-only.
```mermaid ```mermaid
flowchart LR flowchart LR
Loader[StreamingWorldLoader] --> State[M2PrototypeCacheState] Loader[StreamingWorldLoader] --> State[M2PrototypeCacheState]
Observer[M2 resource observers] --> State
Resource[ResourceLoader / raw builders] --> Loader Resource[ResourceLoader / raw builders] --> Loader
Resource --> Observer
State --> Static[Static prototype Node3D] State --> Static[Static prototype Node3D]
State --> Animated[Animated prototype Node3D] State --> Animated[Animated prototype Node3D]
State --> Missing[Missing-model path set] State --> Missing[Missing-model path set]
@@ -62,7 +64,7 @@ Mesh traversal and other application layers are forbidden.
| Input | Normalized non-empty M2 path | Loader normalization | Cache state | Copied String key | Shutdown lifetime | | Input | Normalized non-empty M2 path | Loader normalization | Cache state | Copied String key | Shutdown lifetime |
| Input | Detached static Node3D | Cached scene/raw builder adapter | Cache state | Adopted on success | Until final shutdown | | Input | Detached static Node3D | Cached scene/raw builder adapter | Cache state | Adopted on success | Until final shutdown |
| Input | Detached animated Node3D | GLB/native animation adapter | Cache state | Adopted on success | Until final shutdown | | Input | Detached animated Node3D | GLB/native animation adapter | Cache state | Adopted on success | Until final shutdown |
| Input | Missing/static-only outcome | Loader failure/fallback adapter | Cache state | Boolean set entry | Until final shutdown | | Input | Missing/static-only outcome | Resource observers/finalizers | Cache state | Boolean set entry | Until final shutdown |
| Output | Canonical prototype Node3D | Cache state | Loader instance/material adapter | Borrowed exact reference | One lookup/use | | Output | Canonical prototype Node3D | Cache state | Loader instance/material adapter | Borrowed exact reference | One lookup/use |
| Output | Detached path-only snapshot | Cache state | Tests/diagnostics | Fresh caller-owned arrays | One query | | Output | Detached path-only snapshot | Cache state | Tests/diagnostics | Fresh caller-owned arrays | One query |
@@ -135,11 +137,12 @@ sequenceDiagram
```mermaid ```mermaid
flowchart TB flowchart TB
Loader[StreamingWorldLoader] --> State[M2PrototypeCacheState] Loader[StreamingWorldLoader] --> State[M2PrototypeCacheState]
Observer[M2 resource observers] --> State
State --> Node3D State --> Node3D
Loader --> Raw[M2RawModelRepository] Loader --> Raw[M2RawModelRepository]
Loader --> ResourceLoader Observer --> ResourceLoader
Loader --> StaticBuilder[M2Builder] Loader --> StaticBuilder[M2Builder]
Loader --> AnimatedBuilder[M2NativeAnimatedBuilder] Observer --> AnimatedBuilder[M2NativeAnimatedBuilder]
State -. no dependency .-> Raw State -. no dependency .-> Raw
State -. no dependency .-> ResourceLoader State -. no dependency .-> ResourceLoader
State -. no dependency .-> StaticBuilder State -. no dependency .-> StaticBuilder
@@ -165,7 +168,7 @@ flowchart TB
| Unknown positive lookup | Map miss | Return null | Path snapshot | Loader continues existing load/fallback | | Unknown positive lookup | Map miss | Return null | Path snapshot | Loader continues existing load/fallback |
| Duplicate candidate | Occupied valid path | Release candidate; return first | Identity fixture | None required | | Duplicate candidate | Occupied valid path | Release candidate; return first | Identity fixture | None required |
| Model source/load failure | Loader result | Mark missing outcome | Existing fallback behavior | New loader session/source repair | | Model source/load failure | Loader result | Mark missing outcome | Existing fallback behavior | New loader session/source repair |
| Animation unavailable/unsafe | Loader policy/result | Mark static-only outcome | Existing fallback behavior | New loader session/cache repair | | Animation unavailable/unsafe | Observer/native result | Mark static-only outcome | Existing fallback behavior | New loader session/cache repair |
| Shutdown | Loader lifecycle | Release positive and clear all state | Shutdown verifier | New loader begins empty | | Shutdown | Loader lifecycle | Release positive and clear all state | Shutdown verifier | New loader begins empty |
| Cancellation | Not owned | No state transition inside this service | N/A | Loader drains/cancels before shutdown clear | | Cancellation | Not owned | No state transition inside this service | N/A | Loader drains/cancels before shutdown clear |
@@ -177,8 +180,8 @@ flowchart TB
| Negative cache lifetime | Final loader shutdown | All | No | Preserves historical fallback suppression | | Negative cache lifetime | Final loader shutdown | All | No | Preserves historical fallback suppression |
| Eviction capacity | Unbounded historical behavior | All | No | No mid-session Node destruction | | Eviction capacity | Unbounded historical behavior | All | No | No mid-session Node destruction |
Animation enablement, candidate/allow/deny rules, cache paths and per-frame Animation enablement, candidate/allow/deny rules and cache paths remain loader
permits remain loader configuration. configuration consumed by the cached observer. Per-frame permits remain loader-owned.
## Persistence, cache and migration ## Persistence, cache and migration
@@ -188,7 +191,10 @@ and native M2 formats are unchanged; no migration or rebake is introduced.
## Diagnostics and observability ## Diagnostics and observability
- `diagnostic_snapshot` exposes four sorted path arrays without Node references. - `diagnostic_snapshot` exposes four sorted path arrays without Node references.
- Existing `M2_ANIM_CACHE` and native animation logs remain loader-owned. - Cached eligibility rejection logging belongs to the cached observer; native
success logging belongs to the native observer and cached terminal success
logging belongs to the animation resource finalizer. Static terminal missing
outcomes are produced by `M2MeshResourceFinalizer`.
- Existing renderer queue metrics remain unchanged because these tables never - Existing renderer queue metrics remain unchanged because these tables never
contributed work counts. contributed work counts.
- Normalized relative path remains the correlation key. - Normalized relative path remains the correlation key.
@@ -208,8 +214,8 @@ and native M2 formats are unchanged; no migration or rebake is introduced.
## Extension points ## Extension points
Eviction or byte/count budgets require measured memory evidence and explicit Eviction or byte/count budgets require measured memory evidence and explicit
prototype-user lifetime rules. Animation request-state extraction can consume prototype-user lifetime rules. Cached and native animation observers consume
this service without moving ResourceLoader or builder ownership into it. this state without moving ResourceLoader or builder ownership into cache state.
## Capability status ## Capability status
@@ -233,7 +239,10 @@ this service without moving ResourceLoader or builder ownership into it.
| Path | Responsibility | | Path | Responsibility |
|---|---| |---|---|
| `src/render/m2/m2_prototype_cache_state.gd` | Positive Node ownership, negative path state and shutdown release | | `src/render/m2/m2_prototype_cache_state.gd` | Positive Node ownership, negative path state and shutdown release |
| `src/scenes/streaming/streaming_world_loader.gd` | Normalization, I/O/build/fallback decisions and cache adapters | | `src/render/m2/m2_native_animation_resource_observer.gd` | Animated raw/build and positive/static-only transitions |
| `src/render/m2/m2_animation_resource_finalizer.gd` | Cached terminal positive/static-only transitions |
| `src/render/m2/m2_mesh_resource_finalizer.gd` | Static terminal missing transitions |
| `src/scenes/streaming/streaming_world_loader.gd` | Normalization, observer order, remaining I/O/fallback adapters |
| `src/tools/verify_m2_prototype_cache_state.gd` | Admission/identity/lifecycle/source/timing regression | | `src/tools/verify_m2_prototype_cache_state.gd` | Admission/identity/lifecycle/source/timing regression |
| `src/tools/verify_render_runtime_cache_shutdown.gd` | Integrated final-shutdown release regression | | `src/tools/verify_render_runtime_cache_shutdown.gd` | Integrated final-shutdown release regression |
+18 -9
View File
@@ -27,11 +27,15 @@ static or animated Dictionary consumed by existing builders and classifiers.
```mermaid ```mermaid
flowchart LR flowchart LR
Loader[StreamingWorldLoader] --> Repository[M2RawModelRepository] MeshFinalizer[M2MeshResourceFinalizer] --> Repository[M2RawModelRepository]
Loader[StreamingWorldLoader] --> Repository
Observer[M2NativeAnimationResourceObserver] --> Repository
Repository --> File[Extracted M2 file] Repository --> File[Extracted M2 file]
Repository --> Native[ClassDB M2Loader] Repository --> Native[ClassDB M2Loader]
Native --> Raw[Raw Dictionary] Native --> Raw[Raw Dictionary]
Raw --> MeshFinalizer
Raw --> Loader Raw --> Loader
Raw --> Observer
Loader --> Builder[Existing M2 builders/finalizer] Loader --> Builder[Existing M2 builders/finalizer]
``` ```
@@ -53,8 +57,8 @@ renderer policy and other application layers are forbidden.
| Input | Extracted directory String | Loader configuration | Repository path resolution | Copied value | One call | | Input | Extracted directory String | Loader configuration | Repository path resolution | Copied value | One call |
| Input | Already-normalized relative M2 path String | Loader normalization | Repository | Copied value | One call | | Input | Already-normalized relative M2 path String | Loader normalization | Repository | Copied value | One call |
| Input | Extracted `.m2` bytes | Local legal extraction | Native M2Loader | File-owned | Native call | | Input | Extracted `.m2` bytes | Local legal extraction | Native M2Loader | File-owned | Native call |
| Output | Static raw M2 Dictionary | Native `load_m2` | Loader/finalizer/M2Builder | Fresh native result | One caller operation | | Output | Static raw M2 Dictionary | Native `load_m2` | Mesh resource finalizer/M2Builder | Fresh native result | One caller operation |
| Output | Animated raw M2 Dictionary | Native `load_m2_animated` | Loader/animated builder | Fresh native result | One caller operation | | Output | Animated raw M2 Dictionary | Native `load_m2_animated` | Native animation observer/builder | Fresh native result | One caller operation |
| Output | Empty Dictionary | Repository guards | Loader fallback and prototype outcome adapter | Fresh value | One failed call | | Output | Empty Dictionary | Repository guards | Loader fallback and prototype outcome adapter | Fresh value | One failed call |
Side effects are limited to file-existence inspection, synchronous native file Side effects are limited to file-existence inspection, synchronous native file
@@ -98,18 +102,18 @@ No request, result or failure state survives a call.
```mermaid ```mermaid
sequenceDiagram sequenceDiagram
participant Loader as StreamingWorldLoader participant Caller as Loader or NativeAnimationObserver
participant Repo as M2RawModelRepository participant Repo as M2RawModelRepository
participant File as FileAccess participant File as FileAccess
participant Native as M2Loader participant Native as M2Loader
Loader->>Repo: load static/animated(directory, path) Caller->>Repo: load static/animated(directory, path)
Repo->>File: file_exists(globalized joined path) Repo->>File: file_exists(globalized joined path)
alt dependency or file unavailable alt dependency or file unavailable
Repo-->>Loader: empty Dictionary Repo-->>Caller: empty Dictionary
else available else available
Repo->>Native: instantiate and call exact native method Repo->>Native: instantiate and call exact native method
Native-->>Repo: Variant Native-->>Repo: Variant
Repo-->>Loader: Dictionary or empty Dictionary Repo-->>Caller: Dictionary or empty Dictionary
end end
``` ```
@@ -118,13 +122,14 @@ sequenceDiagram
```mermaid ```mermaid
flowchart TB flowchart TB
Loader[StreamingWorldLoader] --> Repository[M2RawModelRepository] Loader[StreamingWorldLoader] --> Repository[M2RawModelRepository]
Observer[M2NativeAnimationResourceObserver] --> Repository
Repository --> ProjectSettings Repository --> ProjectSettings
Repository --> FileAccess Repository --> FileAccess
Repository --> ClassDB Repository --> ClassDB
ClassDB --> Native[M2Loader extension] ClassDB --> Native[M2Loader extension]
Loader --> Finalizer[M2RuntimeMeshFinalizer] Loader --> Finalizer[M2RuntimeMeshFinalizer]
Loader --> StaticBuilder[M2Builder] Loader --> StaticBuilder[M2Builder]
Loader --> AnimatedBuilder[M2NativeAnimatedBuilder] Observer --> AnimatedBuilder[M2NativeAnimatedBuilder]
Repository -. no dependency .-> Finalizer Repository -. no dependency .-> Finalizer
Repository -. no dependency .-> StaticBuilder Repository -. no dependency .-> StaticBuilder
Repository -. no dependency .-> Cache[Renderer caches/queues] Repository -. no dependency .-> Cache[Renderer caches/queues]
@@ -133,7 +138,9 @@ flowchart TB
## Ownership, threading and resources ## Ownership, threading and resources
- The repository owns only call-local path, native instance and result values. - The repository owns only call-local path, native instance and result values.
- The loader owns path normalization and fallback selection; - The loader owns path normalization and fallback selection; the Mesh resource
finalizer owns static refresh reads, while the native observer owns the
animated raw-read/build decision;
`M2PrototypeCacheState` owns prototype/negative adoption. `M2PrototypeCacheState` owns prototype/negative adoption.
- Native `M2Loader` owns parsing behavior and returns a new Dictionary value. - Native `M2Loader` owns parsing behavior and returns a new Dictionary value.
- Calls are synchronous on the caller's thread; current renderer callers use the - Calls are synchronous on the caller's thread; current renderer callers use the
@@ -211,6 +218,8 @@ measured work packages rather than expansion of this repository.
| Path | Responsibility | | Path | Responsibility |
|---|---| |---|---|
| `src/render/m2/m2_raw_model_repository.gd` | Stateless native class/file/method boundary | | `src/render/m2/m2_raw_model_repository.gd` | Stateless native class/file/method boundary |
| `src/render/m2/m2_mesh_resource_finalizer.gd` | Static refresh repository consumer |
| `src/render/m2/m2_native_animation_resource_observer.gd` | Animated raw-data consumer and builder adapter |
| `src/render/m2/m2_prototype_cache_state.gd` | Prototype and negative-result retention | | `src/render/m2/m2_prototype_cache_state.gd` | Prototype and negative-result retention |
| `src/scenes/streaming/streaming_world_loader.gd` | Path normalization, fallback decisions and result consumers | | `src/scenes/streaming/streaming_world_loader.gd` | Path normalization, fallback decisions and result consumers |
| `src/native/src/m2_loader.cpp` | Native static/animated parsing implementation | | `src/native/src/m2_loader.cpp` | Native static/animated parsing implementation |
+18 -17
View File
@@ -27,9 +27,8 @@ UV-rotation cases and returning the historical original-Mesh fallback.
```mermaid ```mermaid
flowchart LR flowchart LR
Extractor[M2MeshResourceExtractor] --> Loader[StreamingWorldLoader] ResourceFinalizer[M2MeshResourceFinalizer] -->|is raw data required?| Finalizer[M2RuntimeMeshFinalizer]
Loader -->|is raw data required?| Finalizer[M2RuntimeMeshFinalizer] ResourceFinalizer --> Raw[M2RawModelRepository]
Loader --> Raw[M2RawModelRepository]
Raw --> Finalizer Raw --> Finalizer
Finalizer --> Classifier[M2RuntimeMeshRebuildClassifier] Finalizer --> Classifier[M2RuntimeMeshRebuildClassifier]
Finalizer --> Builder[M2Builder] Finalizer --> Builder[M2Builder]
@@ -54,8 +53,8 @@ Nodes outside temporary rebuild roots and other application layers are forbidden
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime | | Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|---|---|---|---|---|---| |---|---|---|---|---|---|
| Input | Extracted Mesh and normalized M2 path | Loader/extractor adapter | Finalizer | Borrowed Mesh reference/String copy | One call | | Input | Extracted Mesh and normalized M2 path | Mesh resource finalizer | Finalizer | Borrowed Mesh reference/String copy | One call |
| Input | Already-loaded raw M2 Dictionary | `M2RawModelRepository` through loader adapter | Classifier/M2Builder | Caller-owned value container | One call | | Input | Already-loaded raw M2 Dictionary | `M2RawModelRepository` through resource finalizer | Classifier/M2Builder | Caller-owned value container | One call |
| Input | Extracted directory path | Loader configuration | M2Builder texture resolution | Copied String | One rebuild | | Input | Extracted directory path | Loader configuration | M2Builder texture resolution | Copied String | One rebuild |
| Output | Original or rebuilt current Mesh | Finalizer | Cache/prototype adapter | Borrowed/new Resource reference | Cache may retain | | Output | Original or rebuilt current Mesh | Finalizer | Cache/prototype adapter | Borrowed/new Resource reference | Cache may retain |
@@ -101,24 +100,24 @@ map/reset clear site calls `clear()`.
```mermaid ```mermaid
sequenceDiagram sequenceDiagram
participant Loader as StreamingWorldLoader participant ResourceFinalizer as M2MeshResourceFinalizer
participant Finalizer as M2RuntimeMeshFinalizer participant Finalizer as M2RuntimeMeshFinalizer
participant Raw as M2Loader boundary participant Raw as M2Loader boundary
participant Classifier as RebuildClassifier participant Classifier as RebuildClassifier
participant Builder as M2Builder participant Builder as M2Builder
Loader->>Finalizer: requires_raw_data_for_refresh(mesh) ResourceFinalizer->>Finalizer: requires_raw_data_for_refresh(mesh)
alt current Mesh alt current Mesh
Finalizer-->>Loader: false; reuse Mesh Finalizer-->>ResourceFinalizer: false; reuse Mesh
else stale Mesh else stale Mesh
Finalizer-->>Loader: true Finalizer-->>ResourceFinalizer: true
Loader->>Raw: load static raw Dictionary ResourceFinalizer->>Raw: load static raw Dictionary
Loader->>Finalizer: finalize_mesh(path, mesh, raw, extracted dir) ResourceFinalizer->>Finalizer: finalize_mesh(path, mesh, raw, extracted dir)
Finalizer->>Classifier: needs_runtime_mesh_rebuild(path, raw) Finalizer->>Classifier: needs_runtime_mesh_rebuild(path, raw)
opt rebuild required opt rebuild required
Finalizer->>Builder: build(raw, extracted dir) Finalizer->>Builder: build(raw, extracted dir)
Finalizer->>Finalizer: extract first Mesh; free prototype Finalizer->>Finalizer: extract first Mesh; free prototype
end end
Finalizer-->>Loader: rebuilt or marked fallback Mesh Finalizer-->>ResourceFinalizer: rebuilt or marked fallback Mesh
end end
``` ```
@@ -126,12 +125,12 @@ sequenceDiagram
```mermaid ```mermaid
flowchart TB flowchart TB
Loader[StreamingWorldLoader] --> Finalizer[M2RuntimeMeshFinalizer] ResourceFinalizer[M2MeshResourceFinalizer] --> Finalizer[M2RuntimeMeshFinalizer]
Finalizer --> Classifier[M2RuntimeMeshRebuildClassifier] Finalizer --> Classifier[M2RuntimeMeshRebuildClassifier]
Finalizer --> Extractor[M2MeshResourceExtractor] Finalizer --> Extractor[M2MeshResourceExtractor]
Finalizer --> Builder[M2Builder] Finalizer --> Builder[M2Builder]
Loader --> Raw[M2RawModelRepository] ResourceFinalizer --> Raw[M2RawModelRepository]
Loader --> Cache[M2MeshResourceCacheState] ResourceFinalizer --> Cache[M2MeshResourceCacheState]
Finalizer -. no dependency .-> Raw Finalizer -. no dependency .-> Raw
Finalizer -. no dependency .-> Cache Finalizer -. no dependency .-> Cache
``` ```
@@ -139,7 +138,8 @@ flowchart TB
## Ownership, threading and resources ## Ownership, threading and resources
- Renderer main thread executes metadata changes and M2Builder work. - Renderer main thread executes metadata changes and M2Builder work.
- `M2RawModelRepository` owns raw M2 file/native calls; loader supplies its value result. - `M2RawModelRepository` owns raw M2 file/native calls; the Mesh resource
finalizer supplies its value result.
- Finalizer owns classifier memoization and temporary rebuild prototype lifetime. - Finalizer owns classifier memoization and temporary rebuild prototype lifetime.
- M2Builder owns construction rules; extractor selects the first rebuilt Mesh. - M2Builder owns construction rules; extractor selects the first rebuilt Mesh.
- Cache/prototype adapters decide where the returned Mesh reference is retained. - Cache/prototype adapters decide where the returned Mesh reference is retained.
@@ -208,7 +208,8 @@ logs/queue metrics are unchanged. Normalized M2 path remains the correlation key
|---|---| |---|---|
| `src/render/m2/m2_runtime_mesh_finalizer.gd` | Refresh version, classification, rebuild and fallback | | `src/render/m2/m2_runtime_mesh_finalizer.gd` | Refresh version, classification, rebuild and fallback |
| `src/render/m2/m2_raw_model_repository.gd` | Raw-file/native I/O and Dictionary result | | `src/render/m2/m2_raw_model_repository.gd` | Raw-file/native I/O and Dictionary result |
| `src/scenes/streaming/streaming_world_loader.gd` | Repository call and returned-Mesh adoption | | `src/render/m2/m2_mesh_resource_finalizer.gd` | Repository call and returned-Mesh adoption |
| `src/scenes/streaming/streaming_world_loader.gd` | Service composition, permits and materialization |
| `src/render/m2/m2_runtime_mesh_rebuild_classifier.gd` | Memoized billboard/UV-rotation predicate | | `src/render/m2/m2_runtime_mesh_rebuild_classifier.gd` | Memoized billboard/UV-rotation predicate |
| `src/render/m2/m2_mesh_resource_extractor.gd` | First rebuilt-Mesh selection | | `src/render/m2/m2_mesh_resource_extractor.gd` | First rebuilt-Mesh selection |
| `src/tools/verify_m2_runtime_mesh_finalizer.gd` | Transition/rebuild/boundary/timing regression | | `src/tools/verify_m2_runtime_mesh_finalizer.gd` | Transition/rebuild/boundary/timing regression |
@@ -0,0 +1,211 @@
# M2 Static Build Resource Observer
## Metadata
| Field | Value |
|---|---|
| Status | Implemented extraction |
| Target/work package | M03 / `M03-RND-M2-STATIC-BUILD-RESOURCE-OBSERVER-001` |
| Owners | Static build Mesh lookup, request selection and missing transition |
| Last verified | Worktree `work/sindo-main-codex/m03-m2-static-build-resource-observer`, 2026-07-18 |
| Profiles/capabilities | Existing static MultiMesh M2 build path |
## Purpose
Produce the static phase of `M2BuildResourceSnapshot`: reuse a prepared Mesh,
wait for an existing request, start the first eligible cache request, or record
a terminal missing model while preserving historical path and GLB rules.
## Non-goals
- Observe animated/native prototypes or finalize threaded loads.
- Own/free Meshes, Nodes, cache entries or snapshot lifetime.
- Materialize instances, plan batches, rotate queues or consume permits.
- Change cache format, candidate order, profiles or visible output.
## Context and boundaries
```mermaid
flowchart LR
Loader[StreamingWorldLoader] --> Observer[M2StaticBuildResourceObserver]
MeshCache[M2MeshResourceCacheState] --> Observer
Missing[M2PrototypeCacheState] --> Observer
Pipeline[M2MeshLoadPipelineState] --> Observer
Observer --> Snapshot[M2BuildResourceSnapshot]
Snapshot --> Dispatch[M2BuildDispatchPlanner]
```
## Public API
| Symbol | Kind | Purpose | Errors |
|---|---|---|---|
| `observe(snapshot, path, cache_dir, mesh_cache, prototype_cache, pipeline)` | Command/query | Adopt cached/pending/requested/missing static result | Invalid inputs return `rejected` |
| `cache_resource_paths(cache_dir, path, extensions)` | Pure query | Return unique historical nested/lowercase/basename candidates | Empty values yield fewer/empty candidates |
| `glb_animation_schema(path)` | Read query | Read OpenWC schema marker from GLB JSON | Invalid/missing GLB returns empty String |
| `OUTCOME_*` | Constants | Stable diagnostic transition identifiers | None |
## Inputs and outputs
| Direction | Data | Producer | Consumer | Ownership/lifetime |
|---|---|---|---|---|
| Input | Normalized M2 path/cache directory | Loader | Observer | Copied Strings; one observation |
| Input | Mesh/missing/request states | M2 state services | Observer | Borrowed services; map/session |
| Input/output | Resource snapshot | Loader | Observer/dispatch/materializer | Borrowed; one build operation |
| Output | Cached Mesh or missing/pending values | Observer | Snapshot | Exact borrowed Mesh/scalars |
| Output | Threaded request record | Observer | Mesh pipeline | Pipeline-owned path record |
| Output | Outcome StringName | Observer | Tests/future diagnostics | Immutable scalar |
## Data flow
```mermaid
flowchart TD
Start[Observe static path] --> Cached{Mesh cached?}
Cached -->|yes| AdoptMesh[Adopt exact Mesh; cached]
Cached -->|no| Missing{Already missing?}
Missing -->|yes| AdoptMissing[Adopt missing]
Missing -->|no| Pending{Request pending?}
Pending -->|yes| AdoptPending[Adopt unresolved; pending]
Pending -->|no| Candidates[Generate tscn then glb candidates]
Candidates --> Eligible{Exists and not pivot-prefix GLB?}
Eligible -->|yes| Request[Threaded request]
Request -->|OK or busy| Remember[Remember request; requested]
Eligible -->|none/error| MarkMissing[Mark/adopt missing]
```
## Lifecycle/state
```mermaid
stateDiagram-v2
[*] --> Observing
Observing --> Cached
Observing --> Pending
Observing --> Requested
Observing --> Missing
Observing --> Rejected
Cached --> [*]
Pending --> [*]
Requested --> [*]
Missing --> [*]
Rejected --> [*]
```
## Main sequence
```mermaid
sequenceDiagram
participant L as StreamingWorldLoader
participant O as StaticBuildResourceObserver
participant C as Mesh/prototype cache state
participant P as MeshLoadPipelineState
participant R as ResourceLoader
participant S as M2BuildResourceSnapshot
L->>O: observe(snapshot, path, cache_dir, states)
O->>C: cached/missing lookup
alt cache hit or missing
O->>S: adopt static observation
else pending
O->>P: has_request
O->>S: adopt unresolved observation
else request candidate
O->>R: exists + load_threaded_request
O->>P: remember_request
O->>S: adopt unresolved observation
end
```
## Dependency diagram
```mermaid
flowchart TB
Observer[M2StaticBuildResourceObserver] --> ResourceLoader
Observer --> FileAccess[GLB header/JSON read]
Observer --> MeshCache[M2MeshResourceCacheState]
Observer --> PrototypeCache[M2PrototypeCacheState]
Observer --> Pipeline[M2MeshLoadPipelineState]
Observer --> Snapshot[M2BuildResourceSnapshot]
Observer -. no dependency .-> SceneTree
Observer -. no dependency .-> Materializers
Observer -. no dependency .-> Scheduler
```
## Ownership, threading and resources
- Observer retains no request, cache, snapshot or engine reference.
- Cache/pipeline services retain their existing state and resource ownership.
- Snapshot borrows the exact cached Mesh; observer never frees it.
- Resource existence/request and GLB inspection run on renderer main thread.
- `M2MeshResourceFinalizer` performs terminal polling, Resource extraction,
Mesh preparation and cache/missing adoption from the loader permit loop.
## Errors, cancellation and recovery
| State | Behavior | Recovery |
|---|---|---|
| Invalid dependency/path | Return `rejected`; no mutation | Correct composition |
| Existing request | Return `pending`; no duplicate request | Retry when queue rotates |
| Pivot-prefix GLB | Skip as static candidate | Continue next candidate |
| Request error | Stop historical search and mark missing | Later reset/new session |
| No candidate | Mark/adopt terminal missing | Cache content becomes available in new session |
| Tile cancellation | Observer retains nothing | Loader/queue cancellation remains authoritative |
## Configuration and capabilities
The observer consumes existing `m2_cache_dir`. It adds no profile, cache version,
batch, visibility, shadow or scheduler setting.
## Persistence, cache and migration
No new persistence is introduced. Existing `.tscn/.glb` layout, pipeline records
and missing-cache lifetime are unchanged; no rebake is required.
## Diagnostics and observability
Returned outcomes distinguish rejected/cached/pending/requested/missing without
logging. Existing queue and hitch metrics remain unchanged.
## Verification
- Dedicated verifier covers invalid, cached identity, missing, pending, path
order/deduplication, absent GLB schema, source ownership and bounded timing.
- Snapshot/dispatch/pipeline/cache/shutdown/facade/internal-access/checkpoint
regressions protect adjacent behavior.
- No original-client visual claim is made; this is bookkeeping/I/O extraction.
## Extension points
Animated/native observation may become a separate producer of the animation
phase. Shared cache candidate/GLB inspection can later be reused without adding
a generic resource framework.
## Capability status
| Capability | Status | Evidence | Gap |
|---|---|---|---|
| Static build lookup/request production | Implemented extraction | Lifecycle/path/source/timing verifier | Asset-backed traversal pending |
| Static finalize/preparation | Implemented extraction | Mesh resource finalizer verifier | Asset-backed traversal pending |
| Animated observation | Existing loader-owned | Animation regressions | Separate producer pending |
## Known gaps and risks
- Successful request behavior is protected by source plus existing pipeline
tests; starting an undrained asynchronous request in the unit fixture would leak.
- GLB schema parser reads synchronously, matching the previous loader behavior.
- Private traversal, p95/p99, leak and visual evidence remain unavailable.
## Source map
| Path | Responsibility |
|---|---|
| `src/render/m2/m2_static_build_resource_observer.gd` | Static cache/request/missing observation |
| `src/render/m2/m2_build_resource_snapshot.gd` | Typed observation result |
| `src/render/m2/m2_mesh_resource_finalizer.gd` | Terminal polling, preparation and adoption |
| `src/scenes/streaming/streaming_world_loader.gd` | Composition, permits and action execution |
| `src/tools/verify_m2_static_build_resource_observer.gd` | Lifecycle/path/boundary/timing regression |
## Related decisions and references
- [`m2-build-resource-snapshot.md`](m2-build-resource-snapshot.md)
- [`m2-mesh-load-pipeline-state.md`](m2-mesh-load-pipeline-state.md)
- [`m2-mesh-resource-cache-state.md`](m2-mesh-resource-cache-state.md)
- [`world-renderer.md`](world-renderer.md)
- [`../../RENDER.md`](../../RENDER.md)
@@ -0,0 +1,183 @@
# Renderer Closeout Verification
## Metadata
| Field | Value |
|---|---|
| Status | Implemented |
| Target/work package | M03 / `M03-QAR-INTEGRATOR-CLOSEOUT-001` |
| Owner | Renderer structural, cache and performance acceptance gates |
| Last verified | Worktree `work/sindo-main-codex-m03-integrator/m03-closeout`, 2026-08-02 |
| Profile | `Blizzlike335`; quality preset `High` for checkpoint evidence |
## Purpose
Provide reproducible closeout checks for the M03 renderer decomposition. The
checks prove that worker boundaries remain CPU-only, main-thread frame steps are
budgeted, cache versions remain explicit, and paired M00/M03 reports stay within
the agreed 10% performance budgets.
## Non-goals
- Claim pixel-level parity with the original build-12340 client.
- Replace subsystem unit verifiers or long-traversal release tests.
- Generate, mutate or migrate production cache payloads.
- Hide incompatible environments or cache inventories by normalizing reports.
## Context and boundaries
```mermaid
flowchart LR
M00[M00 commit capture] --> Reports[Render checkpoint reports]
M03[M03 commit capture] --> Reports
Reports --> Comparator[compare_render_performance.ps1]
Sources[Renderer source and cache versions] --> Contracts[verify_renderer_closeout_contracts.gd]
Comparator --> Stability[repeatability gate across short and long windows]
Stability --> Evidence[JSON comparison and exit code]
Contracts --> Evidence
Evidence --> Target[M03 Evidence / DONE decision]
```
The capture command owns SceneTree execution and PNG/report writes. The
PowerShell comparator is read-only except for its requested JSON output. The
GDScript contract verifier reads source files and creates no renderer resources.
## Public API
| Symbol | Kind | Purpose | Preconditions | Failure |
|---|---|---|---|---|
| `compare_render_performance.ps1 -BaselineReport <paths> -CandidateReport <paths> [-OutputReport <path>]` | CLI | Compare one report or median of repeated reports | Same schema, profile, environment, cache contract/inventory and result keys | Exit 1 and enumerate incompatible fields or budget regressions |
| `verify_render_performance_stability.ps1 -RepeatedSampleComparison <path> -LongWindowComparison <path>` | CLI | Reject only a metric regression reproduced by both independent protocols | Both comparator reports contain the same 84 metrics | Exit 1 with every repeatable result/metric key |
| `verify_renderer_closeout_contracts.gd` | Godot CLI | Check worker/main-thread/cache/converter source contracts | Project parses and referenced sources exist | Exit 1 with named contract failure |
## Inputs and outputs
| Direction | Contract/data | Producer | Consumer | Ownership | Lifetime |
|---|---|---|---|---|---|
| Input | M00/M03 `report.json` paths | `capture_render_checkpoints.gd` | Comparator | Filesystem-owned immutable evidence | One comparison |
| Input | Renderer/tool GDScript sources | Repository | Contract verifier | Read-only | One verifier run |
| Output | 84 metric comparisons and failures | Comparator | Integrator/CI | Optional JSON plus process output | Evidence retention |
| Output | Repeatable and protocol-local regression inventories | Stability gate | Integrator/CI | Optional JSON plus process output | Evidence retention |
| Output | Structural pass/fail summary | Contract verifier | Integrator/CI | Process output | One run |
## Data flow
```mermaid
flowchart TD
Read[Read every supplied report] --> Compatible{Metadata and inventory match?}
Compatible -->|no| Fail[Exit 1 with exact mismatch]
Compatible -->|yes| Median[Median each metric per checkpoint/pass]
Median --> Pair[Pair 14 result keys]
Pair --> Budget[Compare load, p95, p99, hitch and memory]
Budget --> Json[Optional comparison JSON]
Budget --> Cohorts[Repeated-sample and long-window comparisons]
Cohorts --> Repeated{Same metric fails both?}
Repeated -->|no| Pass[Exit 0 with protocol-local diagnostics]
Repeated -->|yes| Fail
```
## Lifecycle and sequence
```mermaid
sequenceDiagram
participant I as Integrator
participant B as M00 worktree
participant C as M03 worktree
participant R as Checkpoint capture
participant G as Performance comparator
I->>B: capture repeated baseline samples
B->>R: same viewport, driver and cache inventory
I->>C: capture repeated candidate samples
C->>R: same viewport, driver and cache inventory
I->>G: baseline paths plus candidate paths
G->>G: compatibility checks and per-metric medians
G-->>I: short-window and long-window JSON evidence
I->>G: verify stability across both protocols
G-->>I: repeatable-regression pass/fail
```
There is no persistent state machine. Each invocation is read, validate,
aggregate, compare and terminate.
## Ownership, threading and resources
- Reports and source files are borrowed read-only for one process.
- Median aggregation deep-copies the first report and never rewrites inputs.
- Rendering remains owned by the GUI capture process on Godot's main thread.
- The contract verifier is headless and does not instantiate the streaming world.
- The optional comparison report is wholly owned by the caller-selected path.
## Errors, cancellation and recovery
| Failure | Behavior | Recovery |
|---|---|---|
| Missing/malformed report | Terminate with path/shape error | Regenerate that capture |
| Environment/cache mismatch | Fail before accepting metrics | Recapture both commits on the same machine and cache |
| Missing/duplicate checkpoint | Fail result-key validation | Repair manifest/capture completeness |
| Metric over budget | Record baseline, candidate, limit and percentage | Diagnose named checkpoint/lane; rerun only after a code or evidence correction |
| Interrupted GUI capture | No complete report is accepted | Remove/ignore partial output and rerun |
## Configuration and capabilities
The comparator reads thresholds from the baseline report. M03 uses 10% maximum
regression for load time, frame p95, frame p99, maximum hitch, static memory and
video memory. Repeated input paths are optional; when supplied, each side is
reduced independently to the median for every metric.
Closeout uses two independent protocols: repeated three-second captures and a
ten-second measurement window. A regression is accepted as real only when the
same checkpoint/pass/metric exceeds its unchanged 10% budget in both protocols.
Protocol-local failures remain in the JSON as noise diagnostics rather than
being discarded.
## Persistence, cache and migrations
The comparison JSON uses schema version 1 and contains source paths, sample
counts, revisions, all metric pairs and failures. It is evidence, not a runtime
cache. Renderer cache versions are read from the manifest/source contracts; this
module performs no migration or invalidation.
## Diagnostics and observability
- Success reports result pairs, comparison count and budget percentage.
- Failure output names every checkpoint/pass, metric, values and limit.
- The structural verifier reports worker count, frame-step count and cache-version count.
- Capture reports retain PNG hashes, queue snapshots, environment and cache inventory.
## Verification, fidelity and performance
- `verify_renderer_closeout_contracts.gd` covers four worker boundaries, fifteen
frame steps, seven cache versions and the nested M2 GLB output contract.
- `compare_render_performance.ps1` compares 14 cold/warm result pairs and 84 metrics.
- `verify_render_performance_stability.ps1` requires metric-key agreement and
rejects any budget regression reproduced by both sampling protocols.
- M00 and M03 must be captured from their exact commits against the same cache
inventory; old reports with a different inventory are rejected.
- PNG hashes and asset-backed coverage prove that terrain, ADT boundaries, dense
M2, large WMO, liquid, animated M2 and sky were rendered. They do not prove
original-client pixel parity without human/reference-image approval.
## Extension points
CI may retain reports and comparison JSON as artifacts. A future release gate may
add driver-version metadata, long-traversal samples or approved visual-diff
thresholds without changing runtime renderer contracts.
## Known gaps and risks
- Godot reports the rendering API but not the installed NVIDIA driver version.
- A 0.5-second historical M00 measurement window requires repeated median samples.
- Original-client screenshots are not part of the repository evidence set.
- Long-traversal descriptor pressure remains a later quality/release gate.
## Source map
| Path | Responsibility |
|---|---|
| `tools/compare_render_performance.ps1` | Compatibility, median aggregation and metric budgets |
| `tools/verify_render_performance_stability.ps1` | Cross-protocol repeatability acceptance |
| `src/tools/verify_renderer_closeout_contracts.gd` | Structural source/cache/converter contracts |
| `src/tools/capture_render_checkpoints.gd` | Asset-backed GUI capture and report generation |
| `src/tools/render_baseline_manifest.json` | Coverage, viewport, cache contract and budgets |
| `targets/00-render-baseline.md` | Accepted M00 measurement protocol |
| `targets/03-renderer-facade.md` | M03 acceptance and Evidence |
+1 -1
View File
@@ -227,7 +227,7 @@ Existing ADT/native/cache format versions remain unchanged.
| Renderer loaded-mesh diagnostic backend | Implemented | M03 facade typed sample and detached snapshot contract | Not composed into gameplay; triangle Mesh ray is diagnostic only | | Renderer loaded-mesh diagnostic backend | Implemented | M03 facade typed sample and detached snapshot contract | Not composed into gameplay; triangle Mesh ray is diagnostic only |
| Authoritative renderer/physics backend | Planned | Boundary permits replacement | Define holes/slopes/collision/readiness semantics before gameplay composition | | Authoritative renderer/physics backend | Planned | Boundary permits replacement | Define holes/slopes/collision/readiness semantics before gameplay composition |
| Holes/slopes/collision | Planned | Outside height-only contract | Later movement/physics package | | Holes/slopes/collision | Planned | Outside height-only contract | Later movement/physics package |
| Liquid/swim query | Planned | Outside contract | M09/M12 world gameplay | | Liquid/swim query | Planned | Outside contract | M10/M13 world gameplay |
## Known gaps and risks ## Known gaps and risks
+20 -8
View File
@@ -7,7 +7,7 @@
| Status | Implemented | | Status | Implemented |
| Target/work package | M03 / `M03-RND-WMO-PLACEMENT-RESOLVER-001` | | Target/work package | M03 / `M03-RND-WMO-PLACEMENT-RESOLVER-001` |
| Owners | Pure WMO cache-key, placement-identity and world-transform rules | | Owners | Pure WMO cache-key, placement-identity and world-transform rules |
| Last verified | Worktree `work/sindo-main-codex/m03-wmo-placement-resolver`, 2026-07-17 | | Last verified | Worktree `work/sindo-main-codex/m03-wmo-scene-instance-factory`, 2026-08-01 |
| Profiles/capabilities | Existing ADT/WDT WMO placement paths | | Profiles/capabilities | Existing ADT/WDT WMO placement paths |
## Purpose ## Purpose
@@ -31,14 +31,17 @@ live-prototype instance paths.
flowchart LR flowchart LR
Parsed[ADT/WDT WMO placement] --> Loader[StreamingWorldLoader adapter] Parsed[ADT/WDT WMO placement] --> Loader[StreamingWorldLoader adapter]
Loader --> Resolver[WmoPlacementResolver] Loader --> Resolver[WmoPlacementResolver]
Loader --> Factory[WmoSceneInstanceFactory]
Factory --> Resolver
Resolver --> CacheKey[Normalized cache key] Resolver --> CacheKey[Normalized cache key]
Resolver --> Identity[Registry unique key] Resolver --> Identity[Registry unique key]
Resolver --> Transform[World Transform3D] Resolver --> Transform[World Transform3D]
CacheKey --> Cache[Loader WMO caches/requests] CacheKey --> Cache[Loader WMO caches/requests]
Identity --> Registry[WmoPlacementRegistry] Identity --> Registry[WmoPlacementRegistry]
Transform --> RenderRoot[Lightweight render root] Transform --> RenderRoot[Lightweight render root]
Transform --> Scene[Cached scene instance] Transform --> Factory
Transform --> Live[Live prototype instance] Factory --> Scene[Cached scene instance]
Factory --> Live[Live prototype instance]
``` ```
Allowed dependencies are Dictionary/String values and Godot `Vector3`, `Basis` Allowed dependencies are Dictionary/String values and Godot `Vector3`, `Basis`
@@ -62,7 +65,7 @@ WorkerThreadPool, mutexes, files, gameplay, network and editor UI are forbidden.
| Input | Tile key and placement index | Loader build job | Synthetic identity fallback | Copied scalar/String | Registry entry lifetime | | Input | Tile key and placement index | Loader build job | Synthetic identity fallback | Copied scalar/String | Registry entry lifetime |
| Output | Normalized relative path | Resolver | Render/scene cache and load-request maps | New String value | Request/cache lookup | | Output | Normalized relative path | Resolver | Render/scene cache and load-request maps | New String value | Request/cache lookup |
| Output | `uid:*` or `tile:*:*` key | Resolver | `WmoPlacementRegistry` and loader ref arrays | New String value | Until unregister/reset | | Output | `uid:*` or `tile:*:*` key | Resolver | `WmoPlacementRegistry` and loader ref arrays | New String value | Until unregister/reset |
| Output | World `Transform3D` | Resolver | Three WMO instance adapters | Value copy | Instance lifetime after assignment | | Output | World `Transform3D` | Resolver | Lightweight render-root adapter and cached/live instance factory | Value copy | Instance lifetime after assignment |
The resolver retains no source Dictionary, output or engine resource. The resolver retains no source Dictionary, output or engine resource.
@@ -91,6 +94,7 @@ and shutdown require no resolver operation.
```mermaid ```mermaid
sequenceDiagram sequenceDiagram
participant Loader as StreamingWorldLoader participant Loader as StreamingWorldLoader
participant Factory as WmoSceneInstanceFactory
participant Resolver as WmoPlacementResolver participant Resolver as WmoPlacementResolver
participant Registry as WmoPlacementRegistry participant Registry as WmoPlacementRegistry
participant Instance as Render/cached/live instance participant Instance as Render/cached/live instance
@@ -98,9 +102,15 @@ sequenceDiagram
Resolver-->>Loader: cache key Resolver-->>Loader: cache key
Loader->>Resolver: resolve_unique_key(placement, tile, index) Loader->>Resolver: resolve_unique_key(placement, tile, index)
Resolver-->>Registry: identity adopted by loader Resolver-->>Registry: identity adopted by loader
Loader->>Resolver: resolve_world_transform(placement) alt lightweight render root
Resolver-->>Loader: value Transform3D Loader->>Resolver: resolve_world_transform(placement)
Loader->>Instance: assign transform and attach/build Resolver-->>Loader: value Transform3D
else cached/live instance
Loader->>Factory: create with placement
Factory->>Resolver: resolve_world_transform(placement)
Resolver-->>Factory: value Transform3D
end
Loader->>Instance: attach/build prepared instance
``` ```
## Ownership, threading and resources ## Ownership, threading and resources
@@ -109,7 +119,9 @@ sequenceDiagram
- `WmoPlacementRegistry` owns placement-key reference sets. The loader owns its - `WmoPlacementRegistry` owns placement-key reference sets. The loader owns its
key-to-Node map, cache/load-request state, jobs/queues, resource fallback and key-to-Node map, cache/load-request state, jobs/queues, resource fallback and
cancellation. cancellation.
- The loader and builders own every Node/Mesh/MultiMesh/material/RID lifecycle. - `WmoSceneInstanceFactory` owns detached cached/live candidate roots until
rejection or transfer; the loader/builders own attachment and remaining
Node/Mesh/MultiMesh/material/RID lifecycle.
- Pure calls are thread-safe; current consumers execute on the main thread. - Pure calls are thread-safe; current consumers execute on the main thread.
## Errors, cancellation and recovery ## Errors, cancellation and recovery
@@ -0,0 +1,214 @@
# WMO Render Group Materializer
## Metadata
| Field | Value |
|---|---|
| Status | Implemented |
| Target | M03 Renderer Facade and Safe Extraction |
| Work package | `M03-RND-WMO-RENDER-GROUP-MATERIALIZER-001` |
| Owner | Render |
| Last verified | 2026-08-01 |
## Purpose
`WmoRenderGroupMaterializer` creates and attaches one lightweight cached WMO
render group on the renderer main thread. It owns the duplicated
`MeshInstance3D`/`MultiMeshInstance3D` presentation rules that previously lived
inside `StreamingWorldLoader`.
## Non-goals
- select a build step, advance/cancel a job or consume a render permit;
- load, cache, validate or finalize WMO Resources;
- resolve placement transforms or own the placement root;
- choose Editor persistence policy or serialize generated nodes;
- change WMO cache formats, materials, visibility or shadow policy.
## Context and boundaries
The loader obtains a queue-owned WMO root and render Resource, asks
`WmoRenderBuildStepPlanner` for one operation, and finalizes the selected Mesh.
The materializer then performs only indexed node presentation and attachment.
The loader retains scheduler, queue and Editor composition responsibilities.
```mermaid
flowchart LR
Queue[WmoRenderBuildQueue] --> Loader[StreamingWorldLoader]
Planner[WmoRenderBuildStepPlanner] --> Loader
Loader --> Finalizer[WmoRuntimeMeshFinalizer]
Loader --> Materializer[WmoRenderGroupMaterializer]
Materializer --> MeshNode[MeshInstance3D]
Materializer --> MultiMeshNode[MultiMeshInstance3D]
MeshNode --> Root[Queue-owned WMO Node3D root]
MultiMeshNode --> Root
Loader --> EditorOwner[Optional Editor owner assignment]
```
## Public API
| Symbol | Role | Thread/lifetime | Failure behavior |
|---|---|---|---|
| `materialize_mesh_group(...)` | Create, configure and attach one indexed Mesh group | Renderer main thread; stateless after return | Null/invalid parent, null Mesh or negative index returns null |
| `materialize_multimesh_group(...)` | Create, configure and attach one indexed MultiMesh doodad group | Renderer main thread; stateless after return | Null/invalid parent, null MultiMesh or negative index returns null |
Both methods preserve exact Resource identity. `group_index` selects the optional
name and transform; missing names use the historical indexed fallback and a
missing transform leaves `Transform3D.IDENTITY`. Positive visibility range
applies its caller-supplied margin. Shadow mode is always applied explicitly.
## Inputs and outputs
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|---|---|---|---|---|---|
| Input | Queue-owned WMO `Node3D` root | `WmoRenderBuildQueue` via loader | Materializer | Borrowed; not retained | One main-thread call |
| Input | Selected `Mesh` or `MultiMesh` | WMO render Resource via loader | Materializer | Borrowed exact Resource | Parent-node lifetime after attach |
| Input | Names, transforms and selected index | WMO render Resource/planner via loader | Materializer | Borrowed value collections | One call |
| Input | Visibility end/margin and shadow flag | Loader quality profile | Materializer | Scalar values | One call |
| Output | Attached `MeshInstance3D` or `MultiMeshInstance3D` | Materializer | Loader/SceneTree | Parent root owns node and Resource reference | Until placement release/world teardown |
Side effects:
- allocates exactly one Godot geometry node for valid input;
- applies name, optional transform, shadow and optional visibility settings;
- attaches the node exactly once to the supplied WMO root.
It performs no filesystem, ResourceLoader, worker, RenderingServer RID, cache,
queue, permit, logging or direct Editor-owner mutation.
## Data flow
```mermaid
flowchart LR
Input[Root + Resource + indexed metadata + render settings] --> Validate{Valid root/resource/index?}
Validate -->|no| Null[Return null; no attachment]
Validate -->|yes| Kind{Mesh or MultiMesh method}
Kind --> Mesh[Create MeshInstance3D]
Kind --> Multi[Create MultiMeshInstance3D]
Mesh --> Configure[Name + optional transform + render settings]
Multi --> Configure
Configure --> Attach[Attach once to WMO root]
Attach --> Return[Return borrowed attached node]
```
## Main sequence
```mermaid
sequenceDiagram
participant Loader as StreamingWorldLoader
participant Queue as WmoRenderBuildQueue
participant Finalizer as WmoRuntimeMeshFinalizer
participant Materializer as WmoRenderGroupMaterializer
participant Root as WMO Node3D root
Loader->>Queue: read front job and cursors
Loader->>Finalizer: finalize selected Mesh
Loader->>Materializer: materialize selected group
Materializer->>Root: add_child(geometry instance)
Materializer-->>Loader: attached node or null
Loader->>Loader: optional Editor ownership
Loader->>Queue: adopt planned cursors
Loader->>Loader: consume one group permit
```
## Dependency diagram
```mermaid
flowchart TB
Loader[StreamingWorldLoader] --> Materializer[WmoRenderGroupMaterializer]
Materializer --> Engine[Node3D / GeometryInstance3D / Mesh / MultiMesh]
Materializer -. no dependency .-> IO[ResourceLoader / FileAccess]
Materializer -. no dependency .-> Queue[WmoRenderBuildQueue]
Materializer -. no dependency .-> Scheduler[RenderBudgetScheduler]
Materializer -. no dependency .-> Finalizer[WmoRuntimeMeshFinalizer]
```
## Ownership, threading and resources
- Both public methods are main-thread only because they mutate the SceneTree.
- The caller owns the parent root; after attachment the root owns the new node.
- The node retains the exact input Mesh or MultiMesh Resource reference.
- The service retains no Node, Resource, RID, collection or per-group state.
- Loader-owned optional recursive Editor ownership runs after successful return.
## Errors, cancellation and recovery
| Failure/state | Detection | Behavior | Recovery |
|---|---|---|---|
| Null/freed parent | Guard | Return null; allocate/attach nothing | Caller validates current queue job |
| Null Resource | Guard | Return null | Loader advances the historically selected cursor |
| Negative index | Guard | Return null | Planner supplies non-negative selected indices |
| Missing name | Bounds check | Use `Group_N` or `DoodadGroup_N` | Rebuild cache metadata if desired |
| Missing transform | Bounds check | Retain identity transform | Rebuild cache metadata if desired |
| Placement cancellation | Loader/queue | Parent and children released by existing lifecycle | Re-request placement later |
| Shutdown | Loader lifecycle | Service has no retained state to drain | New loader composes a new service |
## Configuration, capabilities and profiles
The service introduces no configuration or capability. It accepts the existing
`wmo_visibility_range`, `CHUNK_SIZE` margin and `wmo_cast_shadows` values chosen
by the loader quality profile. Blizzlike and Enhanced selection remains outside.
## Persistence, cache and migrations
No persisted data or cache format changes. The service neither reads nor writes
WMO cache files and requires no migration or rebake.
## Diagnostics and observability
The service emits no logs or metrics. Existing `wmo_groups` queue depth, build
permits and loader lifecycle diagnostics remain authoritative.
## Verification and fidelity evidence
- `verify_wmo_render_group_materializer.gd` covers exact Mesh/MultiMesh identity,
indexed and fallback names, optional transforms, shadows, positive/disabled
visibility, attachment, invalid input, source boundaries and 1,000 groups.
- Adjacent WMO queue/planner/finalizer and checkpoint regressions protect the
unchanged orchestration and visible output.
- This is an exact code-motion extraction of existing Godot presentation rules;
it adds no original-client 3.3.5a visual parity claim.
## Performance budgets
The synthetic contract requires 1,000 simple Mesh group materializations in
under one second. Production work remains limited to one group per scheduler
permit. Asset-backed CPU/GPU p95/p99 and long traversal remain required evidence.
## Extension points
- Asset-backed WMO traversal can measure group attachment and lifetime without
changing this API.
- Additional render settings belong here only when they apply equally to both
lightweight group-node kinds and have fidelity evidence.
## Capability status
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| Mesh group materialization | Implemented extraction | Identity/name/transform/render/attachment contract | Asset-backed visual/GPU p95/p99 pending |
| MultiMesh doodad group materialization | Implemented extraction | Identity/name/transform/render/attachment contract | Asset-backed traversal/leak evidence pending |
| Build planning/queue progress | Loader-owned | Existing planner/queue regressions | Further orchestration extraction pending |
| Editor persistence ownership | Loader-owned | Source-boundary contract | Editor scene-save integration evidence pending |
## Known gaps and risks
- SceneTree node creation remains synchronous main-thread work by design.
- The synthetic fixture does not measure private WMO assets, GPU upload, portal
visibility, original-client visuals, long traversal or leak behavior.
## Source map
| Path | Responsibility |
|---|---|
| `src/render/wmo/wmo_render_group_materializer.gd` | Indexed geometry-node creation, settings and attachment |
| `src/scenes/streaming/streaming_world_loader.gd` | Composition, finalization, queue, permits, Editor ownership and lifecycle |
| `src/tools/verify_wmo_render_group_materializer.gd` | Synthetic contract, ownership boundary and timing regression |
## Related decisions and references
- [`wmo-render-build-step-planner.md`](wmo-render-build-step-planner.md)
- [`wmo-render-build-queue.md`](wmo-render-build-queue.md)
- [`wmo-runtime-mesh-finalizer.md`](wmo-runtime-mesh-finalizer.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)
+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)
+224
View File
@@ -0,0 +1,224 @@
# WMO Runtime Mesh Finalizer
## Metadata
| Field | Value |
|---|---|
| Status | Implemented extraction |
| Target/work package | M03 / `M03-RND-WMO-RUNTIME-MESH-FINALIZER-001` |
| Owners | Cached WMO runtime Mesh refresh version, surface iteration and material reconstruction |
| Last verified | Worktree `work/sindo-main-codex/m03-wmo-runtime-mesh-finalizer`, 2026-07-18 |
| Profiles/capabilities | Profile-independent cached WMO material refresh |
## Purpose
Finalize stale cached WMO runtime Mesh materials outside
`StreamingWorldLoader`: preserve exact Mesh identity, stamp the historical
refresh version and reconstruct eligible surface materials through WMOBuilder.
## Non-goals
- Traverse scene Nodes or own Mesh/MultiMesh attachment and lifetime.
- Select WMO cache paths, poll ResourceLoader or schedule render build jobs.
- Change WMOBuilder shaders, textures, blend rules or cache serialization.
- Share an abstraction with the behaviorally different M2 finalizer.
## Context and boundaries
```mermaid
flowchart LR
Loader[StreamingWorldLoader render build step] -->|Mesh plus extracted directory| Finalizer[WmoRuntimeMeshFinalizer]
Preparer[WmoRuntimeScenePreparer cached traversal] -->|Mesh plus extracted directory| Finalizer
Finalizer -->|material definition plus compact texture paths| Builder[WMOBuilder material boundary]
Builder -->|rebuilt Material| Finalizer
Finalizer -->|same Mesh identity| Loader
Finalizer -->|same Mesh identity| Preparer
```
The runtime scene preparer owns cached-scene traversal; the loader owns render
build-step selection and composition. The finalizer owns only the in-place
Resource operation and depends on an injected WMO material builder.
## Public API
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|---|---|---|---|---|
| `finalize_mesh(mesh, extracted_directory)` | Command/query | Stamp and refresh a stale Mesh in place | Renderer main thread; stateless between calls | Null returns null; unsupported Mesh returns exact identity |
| `rebuild_cached_material(material, extracted_directory)` | Query/boundary | Reconstruct one metadata-bearing cached WMO Material | Renderer main thread; borrowed input | Null/unmarked/missing builder returns null |
## Inputs and outputs
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|---|---|---|---|---|---|
| Input | `Mesh` and refresh metadata | Cached WMO scene/render group | Finalizer | Borrowed Resource | Call duration; identity retained |
| Input | Extracted content directory | Loader configuration | WMOBuilder | Borrowed String value | Call duration |
| Input | Texture paths, WMO flags/shader/blend and cached colors | Surface Material metadata/parameters | Finalizer | Borrowed values | One surface rebuild |
| Output | Compact texture-path array and material definition | Finalizer | WMOBuilder | Detached values | One builder call |
| Output | Rebuilt Material | WMOBuilder | ArrayMesh surface | Surface adopts exact Resource | Mesh lifetime |
| Output | Exact input Mesh | Finalizer | Runtime scene preparer or loader build step | Caller retains ownership | Existing cache/scene lifetime |
## Data flow
```mermaid
flowchart TD
Mesh[Borrowed Mesh] --> Null{Null?}
Null -->|yes| ReturnNull[Return null]
Null -->|no| Current{Refresh version at least 10?}
Current -->|yes| ReturnSame[Return exact Mesh]
Current -->|no| Stamp[Stamp version 10]
Stamp --> Array{ArrayMesh?}
Array -->|no| ReturnSame
Array -->|yes| Surface[Iterate surfaces]
Surface --> Eligible{Material has texture0 metadata?}
Eligible -->|no| Keep[Keep exact Material]
Eligible -->|yes| Definition[Compact paths and copy metadata/colors]
Definition --> Builder[WMOBuilder build material]
Builder --> Result{Non-null result?}
Result -->|yes| Adopt[Replace surface Material]
Result -->|no| Keep
Keep --> Surface
Adopt --> Surface
Surface -->|done| ReturnSame
```
## Lifecycle/state
```mermaid
stateDiagram-v2
[*] --> Stale: missing/version below 10
[*] --> Current: version at least 10
Stale --> Current: stamp before optional surface refresh
Current --> Current: subsequent calls are identity-only
```
The version stamp is intentionally applied before type/material eligibility, as
in the extracted loader behavior. The finalizer retains no Mesh or Material.
## Main sequence
```mermaid
sequenceDiagram
participant Loader as StreamingWorldLoader
participant Preparer as WmoRuntimeScenePreparer
participant Finalizer as WmoRuntimeMeshFinalizer
participant Mesh as ArrayMesh
participant Builder as WMOBuilder
alt lightweight render build step
Loader->>Finalizer: finalize_mesh(mesh, extracted_directory)
else cached scene traversal
Preparer->>Finalizer: finalize_mesh(mesh, extracted_directory)
end
Finalizer->>Mesh: read/stamp refresh metadata
loop each stale ArrayMesh surface
Finalizer->>Mesh: surface_get_material(index)
alt cached WMO metadata exists
Finalizer->>Builder: _build_material(definition, compact paths, directory)
Builder-->>Finalizer: rebuilt Material or null
Finalizer->>Mesh: surface_set_material when non-null
end
end
Finalizer-->>Loader: exact Mesh identity for build path
Finalizer-->>Preparer: exact Mesh identity for cached traversal path
```
## Dependency diagram
```mermaid
flowchart TB
Loader[StreamingWorldLoader] --> Finalizer[WmoRuntimeMeshFinalizer]
Preparer[WmoRuntimeScenePreparer] --> Finalizer
Finalizer --> Engine[Mesh / ArrayMesh / Material]
Finalizer --> Builder[Injected WMO material builder]
Finalizer -. no dependency .-> Nodes[Node traversal/lifetime]
Finalizer -. no dependency .-> RL[ResourceLoader]
Finalizer -. no dependency .-> Queue[Build queue/scheduler]
```
## Ownership, threading and resources
- The renderer main thread serializes Mesh metadata and surface mutation.
- The caller owns Mesh identity, scene traversal, attachment and destruction.
- ArrayMesh surfaces adopt only non-null builder results; otherwise the exact
cached Material remains attached.
- The service retains no Resource, Node, RID, file, queue or per-path state.
## Errors, cancellation and recovery
| Failure/state | Detection | Behavior | Diagnostic | Recovery |
|---|---|---|---|---|
| Null Mesh | Guard | Return null | Synthetic contract | Correct caller input |
| Current Mesh | Version metadata | Return exact identity, no builder call | Identity/version contract | Version bump makes it stale |
| Unsupported Mesh subtype | Type check after stamp | Return exact identity | PrimitiveMesh contract | No surface work required |
| Null/unmarked Material | Metadata guard | Preserve exact surface value | Surface contract | Rebuild cache with metadata |
| Missing builder/null result | Boundary guard/result | Preserve exact Material | Synthetic contract | Correct composition/retry after version bump |
| Shutdown | Loader lifecycle | No retained work to cancel | Shutdown suite | New loader composes a new service |
## Configuration and capabilities
Refresh version `10` and metadata key `wow_wmo_material_refresh_version` are the
existing runtime compatibility boundary. No project setting, profile, permit or
feature flag is introduced.
## Persistence, cache and migration
The module writes only the existing runtime Mesh metadata stamp and serializes
nothing. It does not change WMO scene/render cache formats or trigger rebakes.
A future material-rule change must deliberately bump the refresh version.
## Diagnostics and observability
The finalizer emits no logs and allocates no metrics. Existing `wmobuild` queue
metrics and loader diagnostics remain the operational correlation surface.
## Verification
- `verify_wmo_runtime_mesh_finalizer.gd`: null/current/non-ArrayMesh identity,
version stamp, null/unmarked surface retention, compact path indices,
flags/shader/blend/default and shader colors, exact builder adoption,
missing-builder behavior, source ownership and 1,000 current calls under one second.
- Adjacent WMO queue/cache/finalizer, shutdown, material and baseline regressions
protect orchestration and visible output.
- Fidelity evidence is exact behavior-preserving extraction; no private asset or
original-client visual-parity claim is added.
## Extension points
- Asset-backed WMO fixtures may compare reconstructed surface parameters without
changing the service contract.
- A deliberate material refresh change may bump the owned version with old/new
fixtures and paired visual evidence.
## Capability status
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| Runtime refresh admission | Implemented extraction | Identity/version/type contracts | Serialized cache fixture pending |
| Cached material definition reconstruction | Implemented extraction | Path/metadata/color contracts | Asset-backed visual comparison pending |
| Scene traversal | Runtime scene preparer-owned | Cached/live traversal regressions | Asset-backed traversal pending |
| Node materialization | Loader/group-materializer-owned | Existing WMO regressions | Further safe extraction pending |
## Known gaps and risks
- Surface mutation and WMOBuilder material construction remain synchronous
main-thread work.
- No proprietary WMO corpus, long leak run, GPU timing, traversal p95/p99 or
paired build-12340 visual capture is included.
## Source map
| Path | Responsibility |
|---|---|
| `src/render/wmo/wmo_runtime_mesh_finalizer.gd` | Refresh admission, surface iteration and material definition reconstruction |
| `addons/mpq_extractor/loaders/wmo_builder.gd` | WMO shader/material construction semantics |
| `src/render/wmo/wmo_runtime_scene_preparer.gd` | Cached scene traversal and finalizer delegation |
| `src/scenes/streaming/streaming_world_loader.gd` | Composition, build jobs, placement and lifetime |
| `src/tools/verify_wmo_runtime_mesh_finalizer.gd` | Identity/version/material/source/timing regression |
## Related decisions and references
- [`wmo-render-build-queue.md`](wmo-render-build-queue.md)
- [`wmo-render-resource-finalizer.md`](wmo-render-resource-finalizer.md)
- [`wmo-scene-resource-finalizer.md`](wmo-scene-resource-finalizer.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)
+211
View File
@@ -0,0 +1,211 @@
# WMO Runtime Scene Preparer
## Metadata
| Field | Value |
|---|---|
| Status | Implemented |
| Target | M03 Renderer Facade and Safe Extraction |
| Work package | `M03-RND-WMO-RUNTIME-SCENE-PREPARER-001` |
| Owner | Render |
| Last verified | 2026-08-01 |
## Purpose
`WmoRuntimeScenePreparer` applies the existing post-instantiation rules to a
borrowed WMO subtree on the renderer main thread. Cached scenes receive recursive
Mesh finalization before render policy; live-built duplicates receive render
policy only.
## Non-goals
- load, validate, instantiate, duplicate, place or attach a WMO scene;
- change `WmoRuntimeMeshFinalizer` material/version rules;
- own the supplied subtree or decide Editor persistence;
- change cache formats, queue progress, permits or shutdown;
- implement portal/room visibility or recursive occluder discovery.
## Context and boundaries
The loader distinguishes validated cached PackedScene instances from duplicated
live-built prototypes. That distinction remains explicit through two public
methods so the live path does not gain cached-Mesh refresh behavior.
```mermaid
flowchart LR
Cached[Validated cached WMO instance] --> Loader[StreamingWorldLoader]
Live[Duplicated live-built WMO instance] --> Loader
Loader -->|prepare_cached_instance| Preparer[WmoRuntimeScenePreparer]
Loader -->|prepare_live_instance| Preparer
Preparer -->|cached only| MeshFinalizer[WmoRuntimeMeshFinalizer]
Preparer --> Policy[Direct Occluders policy + recursive shadow enabling]
Policy --> Borrowed[Borrowed WMO Node3D subtree]
Loader --> Placement[Placement/attachment/registry lifecycle]
```
## Public API
| Symbol | Role | Thread/lifetime | Failure behavior |
|---|---|---|---|
| `prepare_cached_instance(instance, extracted_directory, enable_occlusion_culling, cast_shadows)` | Finalize cached subtree Meshes, then apply render policy | Renderer main thread; stateless after return | Null/freed root returns false |
| `prepare_live_instance(instance, enable_occlusion_culling, cast_shadows)` | Apply render policy without Mesh finalization | Renderer main thread; stateless after return | Null/freed root returns false |
Both methods borrow the root and return a success flag. Disabled occlusion removes
only the direct child named `Occluders`, matching the previous loader lookup.
Enabled shadows set all descendant `GeometryInstance3D` nodes to ON. Disabled
shadows preserve every existing value rather than forcing OFF.
## Inputs and outputs
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|---|---|---|---|---|---|
| Input | Cached or live-built WMO `Node3D` | Loader scene/prototype adapter | Preparer | Borrowed exact subtree | One main-thread call |
| Input | Extracted directory | Loader configuration | Runtime Mesh finalizer via preparer | Borrowed String | Cached preparation call |
| Input | Occlusion/shadow policy | Loader quality configuration | Preparer | Scalar values | One call |
| Internal | Mesh/MultiMesh Mesh reference | Borrowed subtree traversal | `WmoRuntimeMeshFinalizer` | Exact Resource reference; not retained by preparer | One cached call |
| Output | Success flag | Preparer | Loader | Value | Immediate |
| Side effect | Mesh refresh, optional child removal and shadow mutation | Preparer | Borrowed subtree | SceneTree remains loader/placement-owned | Until subtree release |
No filesystem, ResourceLoader, worker, RID, cache, queue, placement, attachment,
Editor-owner or diagnostic side effect is introduced.
## Data flow
```mermaid
flowchart LR
Input[Borrowed root + path kind + policies] --> Valid{Root valid?}
Valid -->|no| False[Return false]
Valid -->|yes cached| Walk[Parent-before-children traversal]
Valid -->|yes live| Policy
Walk --> Mesh{MeshInstance or non-null MultiMesh?}
Mesh -->|yes| Finalize[WmoRuntimeMeshFinalizer.finalize_mesh]
Mesh -->|no| Next[Continue]
Finalize --> Next
Next --> Policy[Apply direct Occluders and shadow policies]
Policy --> True[Return true]
```
## Main sequence
```mermaid
sequenceDiagram
participant Loader as StreamingWorldLoader
participant Preparer as WmoRuntimeScenePreparer
participant Finalizer as WmoRuntimeMeshFinalizer
participant Root as Borrowed WMO subtree
alt cached scene
Loader->>Preparer: prepare_cached_instance(root, directory, policies)
loop parent-before-children Mesh traversal
Preparer->>Finalizer: finalize_mesh(exact Mesh, directory)
end
else live-built duplicate
Loader->>Preparer: prepare_live_instance(root, policies)
end
Preparer->>Root: optional direct Occluders removal
Preparer->>Root: optional recursive shadow ON
Preparer-->>Loader: true/false
Loader->>Loader: place, attach, register and own lifetime
```
## Dependency diagram
```mermaid
flowchart TB
Loader[StreamingWorldLoader] --> Preparer[WmoRuntimeScenePreparer]
Preparer --> Finalizer[WmoRuntimeMeshFinalizer]
Preparer --> Engine[Node3D / GeometryInstance3D / Mesh / MultiMesh]
Preparer -. no dependency .-> IO[ResourceLoader / FileAccess]
Preparer -. no dependency .-> Queue[WMO queues / RenderBudgetScheduler]
Preparer -. no dependency .-> Placement[WmoPlacementResolver / Registry]
```
## Ownership, threading and resources
- Calls are main-thread only because Mesh Resources and SceneTree nodes mutate.
- The loader/placement registry retains ownership of the root and descendants.
- Mesh finalization receives exact borrowed Resource identities.
- A removed direct `Occluders` child is detached and `queue_free()`d as before.
- The service retains no Node, Resource, RID, path, collection or state after return.
## Errors, cancellation and recovery
| Failure/state | Detection | Behavior | Recovery |
|---|---|---|---|
| Null/freed root | Guard | Return false, no traversal | Caller drops/retries stale placement |
| Missing finalizer composition | Null dependency | Skip Mesh refresh but apply render policy | Fix renderer composition before cached use |
| Null Mesh | Exact finalizer call for MeshInstance; finalizer handles null | Continue traversal | Cache rebuild/finalizer diagnostics |
| Null MultiMesh or its Mesh | Guard/finalizer contract | Skip null MultiMesh; finalizer handles null Mesh | Continue safely |
| Occlusion disabled | Direct-child lookup | Detach and queue-free `Occluders` | Reinstantiate to restore subtree |
| Shadows disabled | Policy branch | Preserve current node settings | Re-run with enabled policy if required |
| Placement cancellation/shutdown | Loader lifecycle | Service has no retained work to cancel | Existing subtree release/drain order |
## Configuration and capabilities
The service introduces no setting. It receives existing
`enable_occlusion_culling` and `wmo_cast_shadows` values. The direct-child name
`Occluders` and shadow-ON behavior are compatibility rules, not new capabilities.
## Persistence, cache and migration
No serialized format or cache version changes. Cached Mesh refresh metadata
continues to belong to `WmoRuntimeMeshFinalizer`; no rebake or migration is needed.
## Diagnostics and observability
The preparer emits no log or metric. Loader placement/build metrics and the Mesh
finalizer contracts remain the diagnostic surfaces.
## Verification
- `verify_wmo_runtime_scene_preparer.gd` covers cached parent-before-children
Mesh/MultiMesh identity, directory forwarding, null MultiMesh, direct versus
nested `Occluders`, enabled/preserved shadows, live finalizer suppression,
missing dependency, invalid roots, source boundaries and 1,000 traversals.
- Adjacent WMO finalizer/cache/queue/shutdown and checkpoint checks protect the
unchanged lifecycle and presentation behavior.
- Fidelity evidence is exact behavior-preserving extraction; no asset-backed or
original-client 3.3.5a visual-parity claim is added.
The synthetic budget is 1,000 two-Mesh cached preparations under one second.
Asset-backed CPU/GPU p95/p99, traversal and leak measurements remain pending.
## Extension points
- Asset-backed traversal can validate material identity and subtree lifetime
without expanding this service contract.
- Portal/room visibility requires its own documented WMO service and evidence;
it must not be hidden inside generic subtree preparation.
## Capability status
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| Cached WMO subtree Mesh finalization | Implemented extraction | Exact traversal/resource/directory contract | Asset-backed visual/GPU p95/p99 pending |
| Live/cached render policy | Implemented extraction | Direct occluder and recursive shadow contract | Portal/room and asset-backed traversal pending |
| Placement/attachment/lifetime | Loader-owned | Existing registry/shutdown regressions | Further orchestration extraction pending |
## Known gaps and risks
- Recursive Mesh finalization and shadow mutation remain synchronous main-thread work.
- The missing-finalizer branch degrades safely for isolated tests but production
composition must always inject `WmoRuntimeMeshFinalizer`.
- No private WMO corpus, portal/room behavior, long traversal, leak/GPU timing or
paired original-client capture is included.
## Source map
| Path | Responsibility |
|---|---|
| `src/render/wmo/wmo_runtime_scene_preparer.gd` | Cached/live subtree traversal and render policy |
| `src/render/wmo/wmo_runtime_mesh_finalizer.gd` | Borrowed Mesh refresh/version/material rules |
| `src/scenes/streaming/streaming_world_loader.gd` | Composition, instantiation, placement, attachment and lifetime |
| `src/tools/verify_wmo_runtime_scene_preparer.gd` | Synthetic traversal/policy/boundary/timing regression |
## Related decisions and references
- [`wmo-runtime-mesh-finalizer.md`](wmo-runtime-mesh-finalizer.md)
- [`wmo-scene-resource-finalizer.md`](wmo-scene-resource-finalizer.md)
- [`wmo-render-group-materializer.md`](wmo-render-group-materializer.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)
+219
View File
@@ -0,0 +1,219 @@
# WMO Scene Instance Factory
## Metadata
| Field | Value |
|---|---|
| Status | Implemented |
| Target | M03 Renderer Facade and Safe Extraction |
| Work package | `M03-RND-WMO-SCENE-INSTANCE-FACTORY-001` |
| Owner | Render |
| Last verified | 2026-08-01 |
## Purpose
`WmoSceneInstanceFactory` creates detached WMO `Node3D` roots from validated
cached `PackedScene` resources or live-built prototypes. It owns cache-currentness
validation, basename assignment and canonical placement-resolver delegation.
## Non-goals
- look up/load/cache PackedScenes or build live WMO prototypes;
- apply Mesh/material/occluder/shadow runtime preparation;
- attach nodes, assign Editor ownership or manage placement references;
- own queues, permits, cache versions or world teardown;
- define WMO placement formulas or scene-cache currentness rules.
## Context and boundaries
The loader selects cached versus live sources. The factory creates a detached
instance and applies identity/placement. `WmoRuntimeScenePreparer` then applies
path-specific presentation policy before the loader attaches/registers the root.
```mermaid
flowchart LR
Cache[WMO PackedScene cache] --> Loader[StreamingWorldLoader]
Prototype[Live WMO prototype cache/build] --> Loader
Loader --> Factory[WmoSceneInstanceFactory]
Validator[WMOBuilder scene-cache validator] --> Factory
Resolver[WmoPlacementResolver] --> Factory
Factory --> Detached[Detached WMO Node3D]
Detached --> Preparer[WmoRuntimeScenePreparer]
Preparer --> Loader
Loader --> Scene[Attachment and placement registry]
```
## Public API
| Symbol | Role | Thread/lifetime | Failure behavior |
|---|---|---|---|
| `is_cached_node_current(node)` | Delegate one Node to the injected cache validator | Renderer main thread; no retention | Null/missing validator returns false |
| `instantiate_cached_scene(relative_path, scene, placement)` | Instantiate, type-check, validate, name and place a cached scene | Renderer main thread; detached result caller-owned | Invalid input/root/stale/dependency returns null; created rejected roots freed |
| `duplicate_live_prototype(relative_path, prototype, placement)` | Duplicate, name and place a live prototype | Renderer main thread; detached result caller-owned | Null/missing resolver/unexpected duplicate returns null |
The cached path validates before placement. The live path deliberately skips the
scene-cache validator. Both paths use `get_file().get_basename()` and the exact
`WmoPlacementResolver.resolve_world_transform` result.
## Inputs and outputs
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|---|---|---|---|---|---|
| Input | Cached `PackedScene` or live prototype `Node3D` | Loader cache/build adapters | Factory | Borrowed Resource/Node | One main-thread call |
| Input | Relative WMO path and placement Dictionary | Loader placement job | Factory | Borrowed values | One call |
| Internal | Candidate root | PackedScene instantiate/prototype duplicate | Validator/factory | Factory-owned until accepted | One call |
| Output | Detached named/placed `Node3D` | Factory | Runtime scene preparer/loader | Ownership transfers to caller | Until attachment/release |
| Output | Currentness bool | Validator via factory | Loader cache admission | Value | Immediate |
Side effects are limited to scene instantiation/duplication, candidate name and
transform mutation, and synchronous free of rejected candidates. No attachment,
filesystem, ResourceLoader, worker, RID, queue, cache or Editor-owner mutation.
## Data flow
```mermaid
flowchart LR
Source[PackedScene or live prototype] --> Create{Cached or live?}
Create -->|cached| Instantiate[PackedScene.instantiate]
Create -->|live| Duplicate[prototype.duplicate]
Instantiate --> Type{Node3D?}
Duplicate --> Type
Type -->|no| Free[Free created candidate and return null]
Type -->|yes cached| Current{Cache current?}
Type -->|yes live| Identity[Apply basename]
Current -->|no| Free
Current -->|yes| Identity
Identity --> Resolve[WmoPlacementResolver]
Resolve --> Return[Return detached Node3D]
```
## Main sequence
```mermaid
sequenceDiagram
participant Loader as StreamingWorldLoader
participant Factory as WmoSceneInstanceFactory
participant Validator as WMOBuilder
participant Resolver as WmoPlacementResolver
alt cached source
Loader->>Factory: instantiate_cached_scene(path, scene, placement)
Factory->>Factory: instantiate and require Node3D
Factory->>Validator: is_scene_cache_current(root)
Validator-->>Factory: current/stale
else live source
Loader->>Factory: duplicate_live_prototype(path, prototype, placement)
Factory->>Factory: duplicate and require Node3D
end
Factory->>Factory: assign basename
Factory->>Resolver: resolve_world_transform(placement)
Resolver-->>Factory: exact Transform3D
Factory-->>Loader: detached Node3D or null
```
## Dependency diagram
```mermaid
flowchart TB
Loader[StreamingWorldLoader] --> Factory[WmoSceneInstanceFactory]
Factory --> Validator[Injected WMO scene-cache validator]
Factory --> Resolver[Injected WmoPlacementResolver]
Factory --> Engine[PackedScene / Node3D / Transform3D]
Factory -. no dependency .-> Preparation[WmoRuntimeScenePreparer]
Factory -. no dependency .-> IO[ResourceLoader / FileAccess]
Factory -. no dependency .-> Queue[WMO queues / scheduler]
```
## Ownership, threading and resources
- Calls are renderer-main-thread only because PackedScene/Node APIs mutate.
- The source scene/prototype remains caller/cache-owned.
- The factory owns a newly created root until rejection or successful return.
- Successful return transfers detached-root ownership to the caller.
- Descendant Mesh/Material Resources retain engine duplicate/instantiate identity.
- The factory retains only injected stateless dependencies, never Nodes/Resources.
## Errors, cancellation and recovery
| Failure/state | Detection | Behavior | Recovery |
|---|---|---|---|
| Null scene/prototype | Guard | Return null without allocation | Correct caller source |
| Missing validator | Currentness guard | Cached candidate rejected/freed | Fix composition |
| Missing resolver | Guard before creation | Return null without allocation | Fix composition |
| Non-Node3D root | Runtime type check | Free candidate and return null | Rebuild invalid cache/source |
| Stale cached root | Injected validator | Free candidate; skip placement | Rebuild cache/current metadata |
| Placement cancellation | Loader lifecycle | Detached/attached result released by caller | Existing retry path |
| Shutdown | No retained candidates | Nothing to drain | Existing loader teardown |
The non-Node3D cached rejection now frees the created invalid root synchronously.
Normal admitted caches already enforce Node3D through the scene finalizer, so this
closes an error-path lifetime leak without changing valid rendered output.
## Configuration and capabilities
No new settings. Cache-currentness rules belong to the injected WMOBuilder
boundary; placement formulas belong to `WmoPlacementResolver`.
## Persistence, cache and migration
No format/version change and no rebake. The factory reads no files and writes no
metadata. Existing cache validator version policy remains authoritative.
## Diagnostics and observability
The factory emits no logs or metrics. Loader cache/placement metrics and
synthetic rejection contracts remain the diagnostic surfaces.
## Verification
- `verify_wmo_scene_instance_factory.gd` covers cached validation-before-placement,
exact accepted root/descendant Resource identity, stale-root free, non-Node3D
rejection, live validator suppression, detached ownership, dependencies,
basename/Transform3D application, source boundaries and 1,000 duplicates.
- Adjacent scene finalizer, placement resolver, runtime preparer, shutdown and
checkpoint regressions protect lifecycle and visible output.
- Fidelity evidence is behavior-preserving extraction for valid inputs. The
invalid non-Node3D free is a lifetime fix, not a visual 3.3.5a change.
The synthetic budget requires 1,000 simple live duplicates in under one second.
Asset-backed CPU/GPU p95/p99 and long-traversal evidence remain pending.
## Extension points
- Asset-backed cached/live instances can compare placement and lifetime without
changing the factory API.
- New source kinds should be separate explicit methods only when their validation
and identity semantics differ materially.
## Capability status
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| Cached WMO instantiation | Implemented extraction | Type/currentness/name/placement/lifetime contract | Serialized asset-backed cache corpus pending |
| Live prototype duplication | Implemented extraction | Identity/name/placement/validator-suppression contract | Asset-backed traversal/leak evidence pending |
| Runtime preparation | Separate implemented service | Runtime scene preparer regression | Visual/GPU p95/p99 pending |
| Attachment/registry lifetime | Loader-owned | Existing WMO placement/shutdown regressions | Further orchestration extraction pending |
## Known gaps and risks
- Scene instantiation/duplication remains synchronous main-thread work.
- No private WMO corpus, portal/room behavior, long traversal, leak/GPU timing or
paired original-client capture is included.
## Source map
| Path | Responsibility |
|---|---|
| `src/render/wmo/wmo_scene_instance_factory.gd` | Cached/live creation, validation, identity and placement |
| `src/render/wmo/wmo_placement_resolver.gd` | Canonical WMO placement Transform3D |
| `src/render/wmo/wmo_runtime_scene_preparer.gd` | Post-factory cached/live render preparation |
| `src/scenes/streaming/streaming_world_loader.gd` | Source selection, cache/prototype lookup, attachment and lifetime |
| `src/tools/verify_wmo_scene_instance_factory.gd` | Synthetic type/identity/lifetime/boundary/timing regression |
## Related decisions and references
- [`wmo-placement-resolver.md`](wmo-placement-resolver.md)
- [`wmo-scene-resource-finalizer.md`](wmo-scene-resource-finalizer.md)
- [`wmo-runtime-scene-preparer.md`](wmo-runtime-scene-preparer.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)
+21 -15
View File
@@ -14,8 +14,9 @@
Own mutually exclusive cached, missing and pending states for normalized WMO Own mutually exclusive cached, missing and pending states for normalized WMO
scene paths. The state accepts only caller-validated `PackedScene` references and scene paths. The state accepts only caller-validated `PackedScene` references and
records `.tscn` paths for threaded requests whose I/O, size limit and cache records `.tscn` paths for threaded requests. Size admission remains in
metadata validation remain in `StreamingWorldLoader`. `StreamingWorldLoader`; terminal I/O/probe validation belongs to
`WmoSceneResourceFinalizer`.
## Non-goals ## Non-goals
@@ -33,8 +34,8 @@ flowchart LR
Loader --> State[WmoSceneResourceCacheState] Loader --> State[WmoSceneResourceCacheState]
Loader --> Size[File existence and size limit] Loader --> Size[File existence and size limit]
Size --> ResourceLoader[Godot ResourceLoader] Size --> ResourceLoader[Godot ResourceLoader]
ResourceLoader --> Loader ResourceLoader --> Finalizer[WmoSceneResourceFinalizer]
Loader --> Probe[Instantiate, metadata/version check, free] Finalizer --> Probe[Instantiate, metadata/version check, free]
Probe -->|accepted PackedScene or missing| State Probe -->|accepted PackedScene or missing| State
Loader --> Live[Live-prototype fallback] Loader --> Live[Live-prototype fallback]
``` ```
@@ -82,7 +83,7 @@ flowchart TD
Check -->|allowed| Request[Start threaded request] Check -->|allowed| Request[Start threaded request]
Request -->|error| Mark Request -->|error| Mark
Request -->|accepted| Remember[remember_request] Request -->|accepted| Remember[remember_request]
Remember --> Poll[Loader polls snapshot] Remember --> Poll[Scene Resource finalizer polls snapshot]
Poll -->|failure| CompleteMissing[complete as missing] Poll -->|failure| CompleteMissing[complete as missing]
Poll -->|loaded| Validate[Instantiate and validate cache metadata] Poll -->|loaded| Validate[Instantiate and validate cache metadata]
Validate -->|valid| CompleteScene[complete with scene] Validate -->|valid| CompleteScene[complete with scene]
@@ -114,24 +115,27 @@ stateDiagram-v2
sequenceDiagram sequenceDiagram
participant Loader as StreamingWorldLoader participant Loader as StreamingWorldLoader
participant State as WmoSceneResourceCacheState participant State as WmoSceneResourceCacheState
participant Finalizer as WmoSceneResourceFinalizer
participant RL as ResourceLoader participant RL as ResourceLoader
Loader->>State: cached/missing/pending queries Loader->>State: cached/missing/pending queries
Loader->>Loader: exists and wmo_max_runtime_scene_mb check Loader->>Loader: exists and wmo_max_runtime_scene_mb check
Loader->>RL: load_threaded_request(.tscn) Loader->>RL: load_threaded_request(.tscn)
Loader->>State: remember_request(normalized, path) Loader->>State: remember_request(normalized, path)
loop renderer tick loop renderer tick
Loader->>State: request_paths_snapshot() Loader->>Finalizer: poll_terminal_requests(State)
Loader->>RL: load_threaded_get_status(path) Finalizer->>State: request_paths_snapshot()
Finalizer->>RL: load_threaded_get_status(path)
end end
Loader->>RL: load_threaded_get(path) Finalizer->>RL: load_threaded_get(path)
Loader->>Loader: instantiate, validate WMO metadata, free probe Finalizer->>Finalizer: instantiate, validate WMO metadata, free probe
Loader->>State: complete with scene or as missing Finalizer->>State: complete with scene or as missing
``` ```
## Ownership, threading and resources ## Ownership, threading and resources
- State owns three Dictionaries and strong references to accepted PackedScenes. - State owns three Dictionaries and strong references to accepted PackedScenes.
- Loader owns paths, file measurement, requests, validation and all Node lifetime. - Loader owns paths, file measurement, request admission and placed Nodes;
finalizer owns terminal I/O and call-local validation-probe lifetime.
- All mutation is serialized on the renderer main thread; no mutex is required. - All mutation is serialized on the renderer main thread; no mutex is required.
- Detached request snapshots allow terminal removal while polling. - Detached request snapshots allow terminal removal while polling.
- Scene instantiation borrows the PackedScene and does not transfer cache ownership. - Scene instantiation borrows the PackedScene and does not transfer cache ownership.
@@ -143,7 +147,7 @@ sequenceDiagram
| Missing `.tscn` | Loader ResourceLoader existence check | Direct missing state | Transient reset permits later retry | | Missing `.tscn` | Loader ResourceLoader existence check | Direct missing state | Transient reset permits later retry |
| Oversize `.tscn` | Loader byte limit | Missing plus unchanged debug log | Raise limit/rebuild, then reset | | Oversize `.tscn` | Loader byte limit | Missing plus unchanged debug log | Raise limit/rebuild, then reset |
| Request-start/load failure | Loader error/status | Direct or terminal missing | Reset and retry later | | Request-start/load failure | Loader error/status | Direct or terminal missing | Reset and retry later |
| Wrong type/stale metadata | Loader PackedScene/probe validation | Terminal missing; probe freed | Rebuild cache and reset | | Wrong type/stale metadata | Finalizer PackedScene/probe validation | Terminal missing; probe freed | Rebuild cache and reset |
| Shutdown while pending | Loader drains snapshot | Clear transient, then full cache release | New loader starts absent | | Shutdown while pending | Loader drains snapshot | Clear transient, then full cache release | New loader starts absent |
## Configuration and capabilities ## Configuration and capabilities
@@ -184,8 +188,9 @@ in the loader; the state emits no logs.
| Capability | Status | Evidence | Gap/next step | | Capability | Status | Evidence | Gap/next step |
|---|---|---|---| |---|---|---|---|
| Cached WMO PackedScene state | Implemented extraction | Lifecycle/source/timing and shutdown verifiers | Asset-backed traversal/leak evidence pending | | Cached WMO PackedScene state | Implemented extraction | Lifecycle/source/timing and shutdown verifiers | Asset-backed traversal/leak evidence pending |
| Size and cache metadata validation | Preserved in loader | Source boundary and WMO regressions | Oversize/stale asset fixtures could follow | | Size admission | Preserved in loader | Source boundary and WMO regressions | Oversize asset fixture could follow |
| ResourceLoader I/O and live fallback | Partial/loader-owned | Existing runtime behavior | Separate extraction if justified | | Terminal I/O and cache validation | Implemented extraction | Finalizer type/lifetime/source/timing verifier | Serialized stale fixture pending |
| Live fallback | Loader-owned | Existing runtime behavior | Separate extraction if justified |
## Known gaps and risks ## Known gaps and risks
@@ -199,7 +204,8 @@ in the loader; the state emits no logs.
| Path | Responsibility | | Path | Responsibility |
|---|---| |---|---|
| `src/render/wmo/wmo_scene_resource_cache_state.gd` | Scene/missing/request state and resets | | `src/render/wmo/wmo_scene_resource_cache_state.gd` | Scene/missing/request state and resets |
| `src/scenes/streaming/streaming_world_loader.gd` | Paths, file limit, requests, validation, fallback and Node lifetime | | `src/render/wmo/wmo_scene_resource_finalizer.gd` | Terminal I/O, probe validation/lifetime and publication |
| `src/scenes/streaming/streaming_world_loader.gd` | Paths, file limit, request admission, fallback and placed Nodes |
| `src/tools/verify_wmo_scene_resource_cache_state.gd` | State, boundary and timing regression | | `src/tools/verify_wmo_scene_resource_cache_state.gd` | State, boundary and timing regression |
| `src/tools/verify_render_runtime_cache_shutdown.gd` | Loader final cache ownership regression | | `src/tools/verify_render_runtime_cache_shutdown.gd` | Loader final cache ownership regression |
@@ -0,0 +1,233 @@
# WMO Scene Resource Finalizer
## Metadata
| Field | Value |
|---|---|
| Status | Implemented extraction |
| Target/work package | M03 / `M03-RND-WMO-SCENE-RESOURCE-FINALIZER-001` |
| Owners | Cached WMO PackedScene terminal polling, validation-probe lifetime and scene/missing publication |
| Last verified | Worktree `work/sindo-main-codex/m03-wmo-scene-resource-finalizer`, 2026-07-18 |
| Profiles/capabilities | Profile-independent cached WMO `.tscn` finalization |
## Purpose
Finalize pending cached WMO `.tscn` requests outside `StreamingWorldLoader`:
poll terminal status, retrieve a PackedScene, instantiate one validation probe,
apply existing WMOBuilder cache metadata rules, release the probe and publish
either the exact scene or the historical missing outcome.
## Non-goals
- Select paths, measure file size or start ResourceLoader requests.
- Instantiate placed WMO scenes or execute live WMOBuilder fallback.
- Own cache lifetime, placements, build queues, permits or attached Nodes.
- Change cache metadata/version rules, fallback order or visible behavior.
## Context and boundaries
```mermaid
flowchart LR
Loader[StreamingWorldLoader] -->|compose and tick| Finalizer[WmoSceneResourceFinalizer]
Cache[WmoSceneResourceCacheState] -->|pending snapshot| Finalizer
Finalizer --> ResourceLoader
Finalizer --> Probe[Temporary Node3D probe]
Probe --> Validator[WMOBuilder cache validator]
Finalizer -->|PackedScene or missing| Cache
Cache --> Fallback[Placed cached scene or live fallback]
```
The service may depend on ResourceLoader, PackedScene/Node3D lifetime and the
injected cache validator. FileAccess, size policy, placement, attached SceneTree,
scheduler and application layers remain outside it.
## Public API
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|---|---|---|---|---|
| `poll_terminal_requests(cache_state)` | Command/query | Poll every pending scene path once and publish terminal outcomes | Renderer main thread; stateless between calls | Null state returns zero; non-terminal retained |
| `is_scene_cache_current(scene)` | Command/query | Instantiate, validate and release one probe | Renderer main thread; call-local Node | Null/no validator/wrong root/stale false |
| `load_threaded_get_status(path)` | Boundary query | Read threaded status | Renderer main thread; injectable tests | ResourceLoader semantics |
| `load_threaded_get(path)` | Boundary query | Retrieve terminal Resource | Renderer main thread; injectable tests | Null/wrong type rejected |
## Inputs and outputs
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|---|---|---|---|---|---|
| Input | Detached normalized-path to `.tscn` path Dictionary | Scene cache state | Finalizer polling | Caller-owned copy | One pass |
| Input | Opaque load status/terminal Resource | ResourceLoader | Finalizer | Value/borrowed Resource | One path |
| Input | Cached WMO PackedScene | ResourceLoader | Probe validator | Borrowed reference | One completion |
| Input | Metadata validator | Loader composition | Finalizer | Borrowed Object | Finalizer lifetime |
| Output | Exact validated PackedScene | Finalizer | Scene cache state | Cache adopts strong reference | Until full clear |
| Output | Missing transition | Finalizer | Scene cache state | Path-only state | Until transient clear |
| Side effect | Temporary validation Node | PackedScene/finalizer | Validator | Finalizer-owned | Freed before return |
## Data flow
```mermaid
flowchart TD
Snapshot[Detached pending snapshot] --> Poll[Poll next status in insertion order]
Poll --> Terminal{Loaded or failed?}
Terminal -->|no| Retain[Retain pending]
Terminal -->|failed| Missing[Complete missing]
Terminal -->|loaded| Get[Get Resource]
Get --> Type{PackedScene?}
Type -->|no| Missing
Type -->|yes| Instantiate[Instantiate one probe]
Instantiate --> Root{Node3D root?}
Root -->|no| FreeRejected[Free rejected Node] --> Missing
Root -->|yes| Validate[WMOBuilder metadata validation]
Validate --> Free[Free Node3D probe]
Free --> Current{Current?}
Current -->|yes| Adopt[Adopt exact PackedScene]
Current -->|no| Missing
```
## Lifecycle/state
```mermaid
stateDiagram-v2
[*] --> Polling
Polling --> Pending: non-terminal
Polling --> Probing: loaded PackedScene
Polling --> Missing: failed/null/wrong type
Probing --> Missing: wrong root or stale metadata
Probing --> Cached: current metadata
Probing --> Released: probe freed before outcome
Released --> Missing
Released --> Cached
Pending --> Polling: later tick
```
The finalizer retains no request or Node. Cache state owns Pending/Cached/Missing
lifetime; every instantiated probe is synchronously released before return.
## Main sequence
```mermaid
sequenceDiagram
participant Loader as StreamingWorldLoader
participant Finalizer as WmoSceneResourceFinalizer
participant Cache as WmoSceneResourceCacheState
participant RL as ResourceLoader
participant Validator as WMOBuilder validator
Loader->>Finalizer: poll_terminal_requests(Cache)
Finalizer->>Cache: request_paths_snapshot()
loop insertion-ordered paths
Finalizer->>RL: load_threaded_get_status(path)
alt failed
Finalizer->>Cache: complete_request_as_missing(path)
else loaded
Finalizer->>RL: load_threaded_get(path)
Finalizer->>Finalizer: instantiate Node3D probe
Finalizer->>Validator: is_scene_cache_current(probe)
Finalizer->>Finalizer: free probe
Finalizer->>Cache: complete with exact scene or missing
else non-terminal
Finalizer->>Finalizer: retain request
end
end
```
## Dependency diagram
```mermaid
flowchart TB
Loader[StreamingWorldLoader] --> Finalizer[WmoSceneResourceFinalizer]
Loader --> Cache[WmoSceneResourceCacheState]
Finalizer --> Cache
Finalizer --> ResourceLoader
Finalizer --> PackedScene
Finalizer --> Node3D
Finalizer --> Validator[WMOBuilder cache validator]
Finalizer -. no dependency .-> FileAccess
Finalizer -. no dependency .-> Placement
Finalizer -. no dependency .-> Scheduler
```
## Ownership, threading and resources
- Renderer main thread serializes polling, instantiation and cache publication.
- Finalizer owns every call-local validation probe and frees it before return.
- Non-Node3D rejected roots are also freed, closing the prior leak edge.
- Cache state adopts accepted exact PackedScene references until full clear.
- Loader owns admission/size policy, live fallback, placed Nodes and shutdown order.
## Errors, cancellation and recovery
| Failure/state | Detection | Behavior | Diagnostic | Recovery |
|---|---|---|---|---|
| Null cache state | Guard | Return zero | Contract verifier | Correct composition |
| Non-terminal | Status | Retain pending | Pending count | Poll later |
| Failed/null/wrong Resource | Status/type | Complete missing | Synthetic fixture | Reset/rebuild cache |
| Non-Node3D root | Probe type | Free probe; complete missing | Type/lifetime fixture | Rebuild cache |
| Stale metadata | Injected validator | Free probe; complete missing | Validation fixture | Rebuild/reset |
| Shutdown | Loader lifecycle | Drain then clear cache | Shutdown verifier | New loader starts absent |
## Configuration and capabilities
The loader injects the existing WMOBuilder cache validator. `wmo_cache_dir`,
`wmo_max_runtime_scene_mb`, debug logging, request timing and WMO budgets remain
loader-owned. No setting, profile or cache version is added.
## Persistence, cache and migration
No data is serialized. Existing WMOBuilder metadata/version acceptance is called
unchanged before adoption. The rejected-root lifetime fix needs no migration or
rebake and does not alter accepted cache output.
## Diagnostics and observability
- Poll returns terminal completion count for tests/future metrics.
- Existing pending count and renderer `wmobuild` metrics remain unchanged.
- No logs are emitted; oversize debug logging remains at loader admission.
- Normalized WMO relative path remains the correlation key.
## Verification
- `verify_wmo_scene_resource_finalizer.gd`: null/non-terminal, insertion order,
failed/load boundary, null/wrong/non-Node3D/stale rejection, current exact
identity, accepted/rejected probe release, source ownership and timing.
- Scene cache, render finalizer/cache, WMO queue/planner/registry/resolver,
shutdown, facade, internal-access and baseline regressions cover neighbors.
- Fidelity evidence is exact orchestration extraction plus rejected-root leak
repair; no asset-backed visual or original-client parity claim is made.
## Extension points
- Legal current/stale serialized `.tscn` fixtures can extend evidence unchanged.
- Placed-scene preparation stays separate because it mutates an attached WMO
instance rather than a call-local validation probe.
## Capability status
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| Cached WMO scene terminal polling | Implemented extraction | Status/order/source/timing verifier | Asset-backed traversal pending |
| Probe validation/lifetime | Implemented extraction | Current/stale/type/free fixtures | Serialized fixture pending |
| Scene/missing publication | Implemented extraction | Exact identity/negative fixtures | Asset-backed lifetime/leak run pending |
| File-size admission/live fallback | Loader-owned | Existing scene-cache regressions | Separate extraction if justified |
## Known gaps and risks
- PackedScene instantiation/metadata validation remains synchronous main-thread work.
- No proprietary WMO corpus, serialized stale/oversize fixture, long leak run,
traversal p95/p99 or paired original-client capture is included.
## Source map
| Path | Responsibility |
|---|---|
| `src/render/wmo/wmo_scene_resource_finalizer.gd` | Terminal polling, probe validation/lifetime and publication |
| `src/render/wmo/wmo_scene_resource_cache_state.gd` | Scene/missing/request ownership and resets |
| `src/scenes/streaming/streaming_world_loader.gd` | Admission/size policy, live fallback and placed Node ownership |
| `addons/mpq_extractor/loaders/wmo_builder.gd` | Existing cache metadata/version validation |
| `src/tools/verify_wmo_scene_resource_finalizer.gd` | Terminal/probe/lifetime/source/timing regression |
## Related decisions and references
- [`wmo-scene-resource-cache-state.md`](wmo-scene-resource-cache-state.md)
- [`wmo-render-resource-finalizer.md`](wmo-render-resource-finalizer.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)
+130 -22
View File
@@ -7,7 +7,7 @@
| Status | Partial | | Status | Partial |
| Target/work package | M00 baseline; `M01-RND-STREAMING-FOCUS-001`; `M01-QAR-SERVER-SPAWN-RENDERER-001`; M03 facade/planner/scheduler/internal-access/ground/environment/entity packages; M03 terrain packages; M03 M2 packages; M03 WMO placement package | | Target/work package | M00 baseline; `M01-RND-STREAMING-FOCUS-001`; `M01-QAR-SERVER-SPAWN-RENDERER-001`; M03 facade/planner/scheduler/internal-access/ground/environment/entity packages; M03 terrain packages; M03 M2 packages; M03 WMO placement package |
| Owners | Renderer workstream / milestone integrator | | Owners | Renderer workstream / milestone integrator |
| Last verified | Worktree `work/sindo-main-codex/m03-m2-animation-playback`, 2026-07-18 | | Last verified | Worktree `work/sindo-main-codex/m03-wmo-scene-instance-factory`, 2026-08-01 |
| Profiles/capabilities | `Performance`, `Balanced`, `High`, `Custom`; Blizzlike fidelity incomplete | | Profiles/capabilities | `Performance`, `Balanced`, `High`, `Custom`; Blizzlike fidelity incomplete |
## Purpose ## Purpose
@@ -54,6 +54,10 @@ flowchart LR
M2Transform --> Loader M2Transform --> Loader
Loader --> M2Batch[M2BuildBatchPlanner] Loader --> M2Batch[M2BuildBatchPlanner]
M2Batch --> Loader M2Batch --> Loader
Loader --> M2Dispatch[M2BuildDispatchPlanner]
M2Dispatch --> Loader
Loader --> M2Resources[M2BuildResourceSnapshot]
M2Resources --> M2Dispatch
Loader --> M2Queue[M2BuildQueue] Loader --> M2Queue[M2BuildQueue]
M2Queue --> Loader M2Queue --> Loader
Loader --> M2Static[M2StaticBatchMaterializer] Loader --> M2Static[M2StaticBatchMaterializer]
@@ -66,6 +70,12 @@ flowchart LR
WmoBuildStep --> Loader WmoBuildStep --> Loader
Loader --> WmoBuildQueue[WmoRenderBuildQueue] Loader --> WmoBuildQueue[WmoRenderBuildQueue]
WmoBuildQueue --> Loader WmoBuildQueue --> Loader
Loader --> WmoGroupMaterializer[WmoRenderGroupMaterializer]
WmoGroupMaterializer --> Scene
Loader --> WmoScenePreparer[WmoRuntimeScenePreparer]
WmoScenePreparer --> Scene
Loader --> WmoInstanceFactory[WmoSceneInstanceFactory]
WmoInstanceFactory --> WmoScenePreparer
Native --> Parsed[Parsed tile/model data] Native --> Parsed[Parsed tile/model data]
Parsed --> Loader Parsed --> Loader
Loader --> Scene[SceneTree nodes] Loader --> Scene[SceneTree nodes]
@@ -136,25 +146,37 @@ from externally reading/writing loader-private queue, task, cache and tile-state
| `M2PlacementTransformResolver.resolve_basis/resolve_origin_offset` | Internal pure M2 service | Resolves regular and calibrated model-specific ADT placement transforms | Worker/main thread; stateless | Unknown paths use regular basis and zero offset | | `M2PlacementTransformResolver.resolve_basis/resolve_origin_offset` | Internal pure M2 service | Resolves regular and calibrated model-specific ADT placement transforms | Worker/main thread; stateless | Unknown paths use regular basis and zero offset |
| `M2PlacementGrouper.group_placements` | Internal pure M2 service | Validates and groups ordered tile-local placement transforms by normalized path | Worker/main thread; stateless | Invalid variants/name IDs/empty paths are skipped | | `M2PlacementGrouper.group_placements` | Internal pure M2 service | Validates and groups ordered tile-local placement transforms by normalized path | Worker/main thread; stateless | Invalid variants/name IDs/empty paths are skipped |
| `M2BuildBatchPlanner.plan_batch` | Internal pure M2 service | Selects static/animated batch count and next group cursor | Main/any thread; stateless | Non-positive selected limit clamps to one; empty range completes | | `M2BuildBatchPlanner.plan_batch` | Internal pure M2 service | Selects static/animated batch count and next group cursor | Main/any thread; stateless | Non-positive selected limit clamps to one; empty range completes |
| `M2BuildDispatchPlanner.plan_step` | Internal pure M2 service | Selects wait, animated/static materialization or no-Node advancement from observed resource state | Main/any thread; stateless | Pending animation has priority; unresolved static Mesh waits |
| `M2BuildResourceSnapshot` | Internal M2 value contract | Carries per-step normalized path, animated/static references and pending/missing observations | Renderer main thread; one build operation | Values retained exactly; release never frees resources |
| `M2StaticBuildResourceObserver.observe` | Internal M2 I/O service | Produces cached/pending/requested/missing static snapshot phase | Renderer main thread; stateless | Invalid composition rejected; no candidate marks missing |
| `M2CachedAnimationResourceObserver.observe` | Internal M2 I/O service | Produces cached/pending/static-only animated snapshot phase | Renderer main thread; stateless | Invalid composition returns empty snapshot; no safe candidate marks static-only |
| `M2BuildQueue` / `M2BuildJob` | Internal M2 pending-state service | Own typed root/groups/cursor jobs and FIFO/stale tile keys | Renderer main thread; map session | Invalid enqueue rejected; stale keys drain independently of jobs | | `M2BuildQueue` / `M2BuildJob` | Internal M2 pending-state service | Own typed root/groups/cursor jobs and FIFO/stale tile keys | Renderer main thread; map session | Invalid enqueue rejected; stale keys drain independently of jobs |
| `M2StaticBatchMaterializer.materialize_batch` | Internal M2 scene-materialization service | Builds and attaches one prepared-Mesh MultiMesh transform slice | Renderer main thread; stateless after each call | Invalid/empty input returns null; bounds are caller precondition | | `M2StaticBatchMaterializer.materialize_batch` | Internal M2 scene-materialization service | Builds and attaches one prepared-Mesh MultiMesh transform slice | Renderer main thread; stateless after each call | Invalid/empty input returns null; bounds are caller precondition |
| `M2RuntimeMeshRebuildClassifier` | Internal memoized M2 service | Detects billboard/UV-rotation metadata requiring stale cached-mesh rebuild | Renderer main thread; cached until reset | Invalid variants/indices skipped; first path decision wins | | `M2RuntimeMeshRebuildClassifier` | Internal memoized M2 service | Detects billboard/UV-rotation metadata requiring stale cached-mesh rebuild | Renderer main thread; cached until reset | Invalid variants/indices skipped; first path decision wins |
| `M2AnimationLoadPipelineState` | Internal M2 async-state service | Owns animated scene pending request records and terminal finalize FIFO | Renderer main thread; map/session | Empty/duplicate/unknown transitions rejected | | `M2AnimationLoadPipelineState` | Internal M2 async-state service | Owns animated scene pending request records and terminal finalize FIFO | Renderer main thread; map/session | Empty/duplicate/unknown transitions rejected |
| `M2AnimationResourceFinalizer` | Internal M2 terminal-I/O service | Polls cached animation requests, loads/finalizes scenes and publishes prototype outcome | Renderer main thread; stateless across calls | Invalid/failed candidates mark static-only |
| `M2AnimatedSceneFinalizer` | Internal M2 scene-finalization service | Instantiates candidates, repairs materials and requires AnimationPlayer descendants | Renderer main thread; stateless after each call | Invalid/rejected detached roots are freed | | `M2AnimatedSceneFinalizer` | Internal M2 scene-finalization service | Instantiates candidates, repairs materials and requires AnimationPlayer descendants | Renderer main thread; stateless after each call | Invalid/rejected detached roots are freed |
| `M2AnimationPlaybackController` | Internal M2 playback service | Applies stable phase, default imported animation and native animator startup | Renderer main thread; stateless after each call | Missing inventories/names no-op through historical fallbacks | | `M2AnimationPlaybackController` | Internal M2 playback service | Applies stable phase, default imported animation and native animator startup | Renderer main thread; stateless after each call | Missing inventories/names no-op through historical fallbacks |
| `M2AnimatedInstanceMaterializer` | Internal M2 scene-materialization service | Duplicates ordered animated instances, applies render settings, starts playback and attaches a non-empty batch | Renderer main thread; stateless after each call | Invalid/empty input or all failed duplicates returns empty | | `M2AnimatedInstanceMaterializer` | Internal M2 scene-materialization service | Duplicates ordered animated instances, applies render settings, starts playback and attaches a non-empty batch | Renderer main thread; stateless after each call | Invalid/empty input or all failed duplicates returns empty |
| `M2MeshLoadPipelineState` | Internal M2 async-state service | Owns static Mesh pending request records and terminal finalize FIFO | Renderer main thread; map/session | Empty/duplicate/unknown transitions rejected | | `M2MeshLoadPipelineState` | Internal M2 async-state service | Owns static Mesh pending request records and terminal finalize FIFO | Renderer main thread; map/session | Empty/duplicate/unknown transitions rejected |
| `M2MeshResourceCacheState` | Internal M2 Resource cache | Owns prepared static Mesh references by normalized path | Renderer main thread; final shutdown | Empty path/null Mesh rejected; same path replaces | | `M2MeshResourceCacheState` | Internal M2 Resource cache | Owns prepared static Mesh references by normalized path | Renderer main thread; final shutdown | Empty path/null Mesh rejected; same path replaces |
| `M2MeshResourceExtractor` | Internal M2 scene/resource service | Selects first Mesh from direct Resource, PackedScene or Node subtree | Renderer main thread; stateless except temporary instance | Invalid/no Mesh returns null; temporary PackedScene root freed | | `M2MeshResourceExtractor` | Internal M2 scene/resource service | Selects first Mesh from direct Resource, PackedScene or Node subtree | Renderer main thread; stateless except temporary instance | Invalid/no Mesh returns null; temporary PackedScene root freed |
| `M2MeshResourceFinalizer` | Internal M2 terminal-I/O service | Polls static requests, extracts/prepares terminal Meshes and publishes cache/missing outcomes | Renderer main thread; stateless across calls | Invalid/failed Resources mark missing; one call pops at most one terminal record |
| `M2RuntimeMeshFinalizer` | Internal M2 preparation service | Owns refresh version, rebuild classification, M2Builder rebuild and fallback | Renderer main thread; decisions cached until reset | Null returns null; missing/failed rebuild marks and reuses original Mesh | | `M2RuntimeMeshFinalizer` | Internal M2 preparation service | Owns refresh version, rebuild classification, M2Builder rebuild and fallback | Renderer main thread; decisions cached until reset | Null returns null; missing/failed rebuild marks and reuses original Mesh |
| `M2RawModelRepository` | Internal M2 native repository | Reads static/animated raw Dictionaries through exact M2Loader methods | Synchronous; stateless | Invalid/unavailable/non-Dictionary result returns empty Dictionary | | `M2RawModelRepository` | Internal M2 native repository | Reads static/animated raw Dictionaries through exact M2Loader methods | Synchronous; stateless | Invalid/unavailable/non-Dictionary result returns empty Dictionary |
| `M2PrototypeCacheState` | Internal M2 prototype cache | Owns detached static/animated Nodes and missing/static-only outcomes | Renderer main thread; final shutdown | Invalid admission rejected; first valid prototype wins | | `M2PrototypeCacheState` | Internal M2 prototype cache | Owns detached static/animated Nodes and missing/static-only outcomes | Renderer main thread; final shutdown | Invalid admission rejected; first valid prototype wins |
| `M2NativeAnimationResourceObserver` | Internal native M2 resource observer | Selects GryphonRoost, reads/builds and publishes prototype/static-only outcome | Synchronous renderer main thread; stateless | Invalid/unavailable candidates return null; failures mark static-only |
| `WmoPlacementResolver.normalize_relative_path/resolve_unique_key/resolve_world_transform` | Internal pure WMO service | Resolves cache key, registry identity and world transform | Main/any thread; stateless | Missing UID uses tile/index fallback; transform fields use historical defaults | | `WmoPlacementResolver.normalize_relative_path/resolve_unique_key/resolve_world_transform` | Internal pure WMO service | Resolves cache key, registry identity and world transform | Main/any thread; stateless | Missing UID uses tile/index fallback; transform fields use historical defaults |
| `WmoPlacementRegistry.add_reference/release_reference/contains/active_count/diagnostic_snapshot/clear` | Internal WMO service | Owns placement-key to tile/global reference sets | Renderer main thread; map session | Empty/unknown/non-owner input is rejected without mutation | | `WmoPlacementRegistry.add_reference/release_reference/contains/active_count/diagnostic_snapshot/clear` | Internal WMO service | Owns placement-key to tile/global reference sets | Renderer main thread; map session | Empty/unknown/non-owner input is rejected without mutation |
| `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 |
| `WmoRenderGroupMaterializer.materialize_mesh_group/materialize_multimesh_group` | Internal WMO scene-materialization service | Creates, configures and attaches one indexed lightweight render group | Renderer main thread; stateless after each call | Invalid parent/resource/index returns null without attachment |
| `WmoRuntimeScenePreparer.prepare_cached_instance/prepare_live_instance` | Internal WMO subtree-preparation service | Preserves cached/live Mesh-finalization distinction, direct occluder policy and recursive shadow enabling | Renderer main thread; stateless after each call | Null/freed root returns false |
| `WmoSceneInstanceFactory.instantiate_cached_scene/duplicate_live_prototype` | Internal WMO instance-creation service | Validates/creates detached cached/live roots and applies shared name/placement | Renderer main thread; stateless after each call | Invalid/stale/dependency failure returns null and frees created rejects |
| `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 |
| `WmoSceneResourceFinalizer` | Internal WMO terminal-I/O service | Polls cached scene requests, validates/frees probes and publishes scene/missing outcomes | Renderer main thread; stateless across calls | Failed/wrong/stale scenes complete missing; probes always released |
| `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 |
@@ -185,17 +207,22 @@ loader configuration remains transitional composition data, not a caller API.
| Internal transform | Rotation/path/scale | Loader or grouper / `M2PlacementTransformResolver` | Group/placeholder/instance transforms | Value-only Basis/Vector3 | One placement | | Internal transform | Rotation/path/scale | Loader or grouper / `M2PlacementTransformResolver` | Group/placeholder/instance transforms | Value-only Basis/Vector3 | One placement |
| Internal grouping | Tile origin, M2 names and placements | Loader / `M2PlacementGrouper` | Loader worker result/build job | Fresh Dictionary/Transform3D arrays | One grouping task | | Internal grouping | Tile origin, M2 names and placements | Loader / `M2PlacementGrouper` | Loader worker result/build job | Fresh Dictionary/Transform3D arrays | One grouping task |
| Internal batch plan | Transform count/offset, path kind and limits | Loader / `M2BuildBatchPlanner` | Loader materialization/cursor adapter | Fresh scalar Dictionary | One build operation | | Internal batch plan | Transform count/offset, path kind and limits | Loader / `M2BuildBatchPlanner` | Loader materialization/cursor adapter | Fresh scalar Dictionary | One build operation |
| Internal M2 dispatch plan | Batch count and observed animation/static resource state | Loader / `M2BuildDispatchPlanner` | Loader queue/materializer/progress adapter | Fresh scalar/action Dictionary | One build operation |
| Internal M2 resource observation | Normalized path, optional prototype/Mesh and pending/missing flags | Loader / M2 observers / `M2BuildResourceSnapshot` | Dispatch planner and loader materializer adapter | Snapshot borrows exact engine references | One build operation |
| Internal M2 pending build | Tile key, M2 root, grouped transforms and cursors | Loader / `M2BuildQueue` | Loader readiness, planner and materializer adapters | Queue-owned job/keys and strong references | Until finish/cancel/clear/replacement | | Internal M2 pending build | Tile key, M2 root, grouped transforms and cursors | Loader / `M2BuildQueue` | Loader readiness, planner and materializer adapters | Queue-owned job/keys and strong references | Until finish/cancel/clear/replacement |
| Internal static M2 materialization | Parent, prepared Mesh, ordered transform slice and render settings | Loader / `M2StaticBatchMaterializer` | Attached MultiMeshInstance3D | Parent owns node/MultiMesh; exact Mesh reference retained | One main-thread build batch | | Internal static M2 materialization | Parent, prepared Mesh, ordered transform slice and render settings | Loader / `M2StaticBatchMaterializer` | Attached MultiMeshInstance3D | Parent owns node/MultiMesh; exact Mesh reference retained | One main-thread build batch |
| Internal WMO placement | Path, MODF placement, tile/index | Loader / `WmoPlacementResolver` | WMO caches, registry and three instance adapters | Value-only String/Transform3D | Lookup/placement lifetime | | Internal WMO placement | Path, MODF placement, tile/index | Loader / `WmoPlacementResolver` | WMO caches, registry and three instance adapters | Value-only String/Transform3D | Lookup/placement lifetime |
| Internal WMO ownership | Resolved placement key and tile/global reference key | Loader / `WmoPlacementRegistry` | Loader create/retain/final-free decisions | Registry-owned String sets; detached diagnostics | Map session or final release | | Internal WMO ownership | Resolved placement key and tile/global reference key | Loader / `WmoPlacementRegistry` | Loader create/retain/final-free decisions | Registry-owned String sets; detached diagnostics | Map session or final release |
| Internal WMO build step | Mesh/MultiMesh counts and job cursors | Loader / `WmoRenderBuildStepPlanner` | Loader materialization/cursor adapter | Fresh scalar Dictionary | One group operation | | Internal WMO build step | Mesh/MultiMesh counts and job cursors | Loader / `WmoRenderBuildStepPlanner` | Loader materialization/cursor adapter | Fresh scalar Dictionary | One group operation |
| Internal WMO pending build | Placement key, Node3D root, WMO Resource and cursors | Loader / `WmoRenderBuildQueue` | Loader drain and step planner adapter | Queue-owned job and strong references | Until cancel/clear/replacement | | Internal WMO pending build | Placement key, Node3D root, WMO Resource and cursors | Loader / `WmoRenderBuildQueue` | Loader drain and step planner adapter | Queue-owned job and strong references | Until cancel/clear/replacement |
| Internal WMO group materialization | Parent root, exact Mesh/MultiMesh, indexed metadata and render settings | Loader / `WmoRenderGroupMaterializer` | Attached geometry node | Parent owns node and exact Resource reference | One main-thread group operation |
| Internal WMO subtree preparation | Cached/live root, extracted directory and render policies | Loader / `WmoRuntimeScenePreparer` | Borrowed subtree and runtime Mesh finalizer | Loader/placement owns subtree; preparer retains nothing | One main-thread instance preparation |
| Internal WMO instance creation | Cached PackedScene or live prototype, path and placement | Loader / `WmoSceneInstanceFactory` | Runtime scene preparer and attachment adapter | Factory owns candidate until detached-root transfer | One main-thread creation |
| Internal WMO render cache | Normalized path, cache path and validated Resource | Loader / `WmoRenderResourceCacheState` | Loader lookup, ResourceLoader poll and build queue | State-owned Resource/path references; detached request snapshots | Until transient/full clear | | Internal WMO render cache | Normalized path, cache path and validated Resource | Loader / `WmoRenderResourceCacheState` | Loader lookup, ResourceLoader poll and build queue | State-owned Resource/path references; detached request snapshots | Until transient/full clear |
| Internal WMO scene cache | Normalized path, `.tscn` path and validated PackedScene | Loader / `WmoSceneResourceCacheState` | Loader lookup, request poll and scene instantiation | State-owned PackedScene/path references; detached request snapshots | Until transient/full clear | | Internal WMO scene cache | Normalized path, `.tscn` path and validated PackedScene | Loader / `WmoSceneResourceCacheState` | Loader lookup, request poll and scene instantiation | State-owned PackedScene/path references; detached request snapshots | Until transient/full clear |
| Internal ADT water load | Tile key, ADT path, task ID and parsed Dictionary | Loader/worker / `AdtWaterLoadPipelineState` | Loader task start, budgeted drain and finalization | State-owned records; mutex result mailbox | Request through result completion/reset | | Internal ADT water load | Tile key, ADT path, task ID and parsed Dictionary | Loader/worker / `AdtWaterLoadPipelineState` | Loader task start, budgeted drain and finalization | State-owned records; mutex result mailbox | Request through result completion/reset |
| Internal raw M2 read | Extracted directory and normalized relative path | Loader / `M2RawModelRepository` | Finalizer, static or animated builder adapters | Fresh Dictionary; repository retains nothing | One synchronous native call | | Internal raw M2 read | Extracted directory and normalized relative path | Loader/native observer via `M2RawModelRepository` | Finalizer, static or animated builder adapters | Fresh Dictionary; repository retains nothing | One synchronous native call |
| Internal animated M2 load | Normalized path, cached GLB path and opaque terminal status | Loader / `M2AnimationLoadPipelineState` | Loader poll/finalize adapters | State-owned String/status records; detached pending snapshots | Request through terminal finalize/reset | | Internal animated M2 load | Normalized path, cached GLB path and opaque terminal status | Cached observer / loader / `M2AnimationLoadPipelineState` | Loader poll/finalize adapters | State-owned String/status records; detached pending snapshots | Request through terminal finalize/reset |
| Internal animated M2 candidate | Loaded PackedScene, static material root and detached candidate | Loader / `M2AnimatedSceneFinalizer` | Loader prototype adoption/static fallback | Finalizer owns until rejection or exact-root transfer | One main-thread finalize permit | | Internal animated M2 candidate | Loaded PackedScene, static material root and detached candidate | Loader / `M2AnimatedSceneFinalizer` | Loader prototype adoption/static fallback | Finalizer owns until rejection or exact-root transfer | One main-thread finalize permit |
| Internal animated M2 playback | Prototype/instance native animators, path/index and player inventory | Materializer/finalizer / `M2AnimationPlaybackController` | Native and imported animation state | Borrowed Nodes; detached optional diagnostics | One duplicated instance startup | | Internal animated M2 playback | Prototype/instance native animators, path/index and player inventory | Materializer/finalizer / `M2AnimationPlaybackController` | Native and imported animation state | Borrowed Nodes; detached optional diagnostics | One duplicated instance startup |
| Internal animated M2 materialization | Parent, prototype, ordered transform slice and render settings | Loader / `M2AnimatedInstanceMaterializer` | Attached animated batch and indexed diagnostics | Parent owns batch; caller owns detached diagnostics | One main-thread build batch | | Internal animated M2 materialization | Parent, prototype, ordered transform slice and render settings | Loader / `M2AnimatedInstanceMaterializer` | Attached animated batch and indexed diagnostics | Parent owns batch; caller owns detached diagnostics | One main-thread build batch |
@@ -249,7 +276,9 @@ flowchart TD
M2Grouper --> M2Batch[M2BuildBatchPlanner] M2Grouper --> M2Batch[M2BuildBatchPlanner]
M2Grouper --> M2Queue[M2BuildQueue] M2Grouper --> M2Queue[M2BuildQueue]
M2Queue --> M2Batch M2Queue --> M2Batch
M2Batch --> M2Static[M2StaticBatchMaterializer] M2Batch --> M2Resources[M2BuildResourceSnapshot]
M2Resources --> M2Dispatch[M2BuildDispatchPlanner]
M2Dispatch --> M2Static[M2StaticBatchMaterializer]
M2Static --> M2 M2Static --> M2
R --> WmoPlacement[WmoPlacementResolver] R --> WmoPlacement[WmoPlacementResolver]
WmoPlacement --> WmoRegistry[WmoPlacementRegistry] WmoPlacement --> WmoRegistry[WmoPlacementRegistry]
@@ -377,21 +406,40 @@ sequenceDiagram
The loader retains tasks, mutex/result queues and stale-result checks; accepted The loader retains tasks, mutex/result queues and stale-result checks; accepted
groups enter `M2BuildQueue` as typed pending jobs. groups enter `M2BuildQueue` as typed pending jobs.
- `M2BuildBatchPlanner` is stateless and owns only call-local scalar plans. - `M2BuildBatchPlanner` is stateless and owns only call-local scalar plans.
`M2BuildDispatchPlanner` is stateless and owns only call-local action/transition
plans after the loader observes resource availability. `M2BuildResourceSnapshot`
carries those per-step observations and borrows exact prototype/Mesh references
without controlling engine lifetime.
`M2BuildQueue` owns typed pending jobs, FIFO/stale keys, grouped-transform `M2BuildQueue` owns typed pending jobs, FIFO/stale keys, grouped-transform
references and group/offset/serial cursors without freeing engine objects. references and group/offset/serial cursors without freeing engine objects.
`M2StaticBatchMaterializer` owns static MultiMesh construction/attachment; `M2StaticBuildResourceObserver` owns static Mesh lookup/request admission and
the loader retains resource transitions, cursor-adoption decisions, snapshot adoption. `M2CachedAnimationResourceObserver` owns cached animated
animated/static dispatch, root cleanup, budgets and Editor ownership. prototype lookup, GLB policy/request admission and snapshot production.
`M2NativeAnimationResourceObserver` owns native candidate selection, raw read,
detached build and prototype/static-only cache outcome.
`M2StaticBatchMaterializer` owns static MultiMesh construction/attachment; the
loader retains action execution, cursor adoption, root cleanup, budgets and
Editor ownership.
- `WmoPlacementResolver` is stateless and owns only call-local cache-key, - `WmoPlacementResolver` is stateless and owns only call-local cache-key,
identity and transform values. `WmoPlacementRegistry` owns only placement-key identity and transform values. `WmoPlacementRegistry` owns only placement-key
reference sets. `WmoRenderBuildStepPlanner` owns only a call-local operation reference sets. `WmoRenderBuildStepPlanner` owns only a call-local operation
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.
`WmoRenderGroupMaterializer` owns indexed MeshInstance3D/MultiMeshInstance3D
creation, render settings and attachment without retaining engine objects.
`WmoRuntimeScenePreparer` owns cached-only Mesh traversal/finalization plus the
shared direct-Occluders and recursive shadow policies for cached/live roots.
`WmoSceneInstanceFactory` owns cached/live detached-root creation, cache
validation, basename and placement application; loader retains source lookup,
runtime preparation, attachment and lifetime.
`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; `WmoSceneResourceFinalizer` owns terminal ResourceLoader I/O and
validation-probe lifetime. The loader retains request admission, FileAccess
size checks, live fallback, Mesh finalization, permits, Editor ownership,
validity reactions and every placed-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,
@@ -402,18 +450,27 @@ sequenceDiagram
for billboard/UV-rotation material refresh and is composed by the runtime Mesh for billboard/UV-rotation material refresh and is composed by the runtime Mesh
finalizer. The raw repository loads value data; loader retains Mesh adoption. finalizer. The raw repository loads value data; loader retains Mesh adoption.
- `M2MeshLoadPipelineState` owns static M2 pending Resource paths, opaque - `M2MeshLoadPipelineState` owns static M2 pending Resource paths, opaque
terminal statuses and completion-order finalize FIFO. The loader retains cache terminal statuses and completion-order finalize FIFO. The static observer owns
path selection, ResourceLoader calls, permits and adoption decisions; prototype cache path selection, request admission and initial snapshot adoption. The
cache state owns shared missing outcomes. Mesh resource finalizer owns ResourceLoader polling/finalize and terminal
adoption; loader retains permits and composition, while prototype cache state
owns shared missing outcomes.
- `M2AnimationLoadPipelineState` owns animated M2 pending Resource paths, opaque
terminal statuses and completion-order finalize FIFO. The cached animation
observer owns allow/deny/path/GLB selection, request admission and initial
snapshot production. The native observer owns synchronous native build; the
animation resource finalizer owns cached terminal polling/load/finalize and
adoption. Loader retains permits and material-prototype lookup.
- `M2MeshResourceCacheState` owns prepared static Mesh references and releases - `M2MeshResourceCacheState` owns prepared static Mesh references and releases
them at the existing final-shutdown site. Prototype state and materialization them at the existing final-shutdown site. Prototype state and materialization
belong to the sibling cache service and loader respectively. belong to the sibling cache service and loader respectively.
- `M2MeshResourceExtractor` owns depth-first first-Mesh selection and temporary - `M2MeshResourceExtractor` owns depth-first first-Mesh selection and temporary
PackedScene instance destruction. The loader retains ResourceLoader I/O, PackedScene instance destruction. The static observer admits ResourceLoader
cache/missing adoption and materialization. requests and initial cache/missing adoption; the Mesh resource finalizer owns
terminal extraction/adoption, while the loader retains materialization.
- `M2RuntimeMeshFinalizer` owns refresh version `2`, classifier lifetime, - `M2RuntimeMeshFinalizer` owns refresh version `2`, classifier lifetime,
M2Builder rebuild and original-Mesh fallback. The loader loads raw data only M2Builder rebuild and original-Mesh fallback. `M2MeshResourceFinalizer` loads
after the finalizer reports that a cached Mesh is stale. raw data only after the runtime finalizer reports that a cached Mesh is stale.
- `M2RawModelRepository` owns FileAccess/ClassDB availability and the exact - `M2RawModelRepository` owns FileAccess/ClassDB availability and the exact
`load_m2`/`load_m2_animated` calls. The loader retains normalization, fallback `load_m2`/`load_m2_animated` calls. The loader retains normalization, fallback
order and every result consumer; `M2PrototypeCacheState` retains outcomes. order and every result consumer; `M2PrototypeCacheState` retains outcomes.
@@ -520,6 +577,15 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
transition, completion/raw integer behavior, source ownership and bounded timing. transition, completion/raw integer behavior, source ownership and bounded timing.
- WMO render build queue contract: typed references/cursors, FIFO, duplicate - WMO render build queue contract: typed references/cursors, FIFO, duplicate
replacement, stale-front cleanup, cancel/clear engine lifetime and bounded timing. replacement, stale-front cleanup, cancel/clear engine lifetime and bounded timing.
- WMO render group materializer contract: exact Resource identity, indexed and
fallback names/transforms, render settings, attachment, source ownership and
bounded main-thread timing.
- WMO runtime scene preparer contract: cached/live finalizer distinction,
exact Mesh traversal order, direct occluder removal, recursive shadow policy,
ownership boundaries and bounded main-thread timing.
- WMO scene instance factory contract: cached validation-before-placement,
stale/type rejection lifetime, live validator suppression, exact descendant
Resource identity, naming/placement and bounded main-thread timing.
- WMO render Resource cache contract: invalid/duplicate request rejection, - WMO render Resource cache contract: invalid/duplicate request rejection,
validated/missing terminal transitions, transient/full reset, detached sorted validated/missing terminal transitions, transient/full reset, detached sorted
diagnostics, loader-owned version validation and bounded timing. diagnostics, loader-owned version validation and bounded timing.
@@ -562,7 +628,14 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
| M2 unique placement registry | Implemented extraction | Scene-free ownership/lifecycle/timing contract and historical `uid:11785` smoke | Group/build/tasks/finalization and asset-backed p95/p99 remain pending | | M2 unique placement registry | Implemented extraction | Scene-free ownership/lifecycle/timing contract and historical `uid:11785` smoke | Group/build/tasks/finalization and asset-backed p95/p99 remain pending |
| M2 placement transform resolver | Implemented extraction | Scene-free formula/source/timing contract across three consumers | Asset-backed visual recheck and general placement parity pending | | M2 placement transform resolver | Implemented extraction | Scene-free formula/source/timing contract across three consumers | Asset-backed visual recheck and general placement parity pending |
| M2 placement grouper | Implemented extraction | Scene-free validation/order/transform/source/timing contract | Worker/build state, spatial cells and asset-backed p95/p99 remain pending | | M2 placement grouper | Implemented extraction | Scene-free validation/order/transform/source/timing contract | Worker/build state, spatial cells and asset-backed p95/p99 remain pending |
| M2 build batch planner | Implemented extraction | Scene-free limit/count/cursor/source/timing contract | Queue/resource state, spatial cells and asset-backed p95/p99 remain pending | | M2 build batch planner | Implemented extraction | Scene-free limit/count/cursor/source/timing contract | Spatial cells and asset-backed p95/p99 remain pending |
| M2 build dispatch planner | Implemented extraction | Scene-free priority/action/transition/source/timing contract | Resource observation/orchestration and asset-backed traversal remain pending |
| M2 build resource snapshot | Implemented extraction | Typed identity/adoption/lifetime/source/timing contract | Resource observation service and asset-backed traversal remain pending |
| M2 static build resource observer | Implemented extraction | Cache/pending/path/request/source/timing contract | Asset-backed traversal pending |
| M2 cached animation resource observer | Implemented extraction | Cache/pending/policy/GLB/request/source/timing contract | Asset-backed traversal pending |
| M2 native animation resource observer | Implemented extraction | Candidate/cache/raw/build/adoption/source/timing contract | Asset-backed traversal/leak/p95/p99 pending |
| M2 animation resource finalizer | Implemented extraction | Status/FIFO/load/candidate/repair/adoption/source/timing contract | Asset-backed traversal/leak/p95/p99 pending |
| M2 mesh resource finalizer | Implemented extraction | Status/FIFO/load/extract/prepare/adoption/source/timing contract | Asset-backed traversal/leak/p95/p99 pending |
| M2 build queue | Implemented extraction | Typed lifecycle/FIFO/progress/lifetime/source/timing contract | Resource dispatch and asset-backed traversal remain pending | | M2 build queue | Implemented extraction | Typed lifecycle/FIFO/progress/lifetime/source/timing contract | Resource dispatch and asset-backed traversal remain pending |
| M2 animation load pipeline state | Implemented extraction | Synthetic lifecycle/FIFO/source/timing contract | Asset-backed traversal/leak/animation-fidelity/p95/p99 pending | | M2 animation load pipeline state | Implemented extraction | Synthetic lifecycle/FIFO/source/timing contract | Asset-backed traversal/leak/animation-fidelity/p95/p99 pending |
| M2 animated scene finalizer | Implemented extraction | Synthetic type/lifetime/material/player/source/timing contract | Asset-backed GLB traversal/material/animation comparison pending | | M2 animated scene finalizer | Implemented extraction | Synthetic type/lifetime/material/player/source/timing contract | Asset-backed GLB traversal/material/animation comparison pending |
@@ -572,9 +645,15 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
| WMO placement resolver | Implemented extraction | Scene-free path/identity/transform/source/timing contract | Asset-backed comparison pending | | WMO placement resolver | Implemented extraction | Scene-free path/identity/transform/source/timing contract | Asset-backed comparison pending |
| 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 | 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 group materializer | Implemented extraction | Synthetic Resource/name/transform/render/attachment/source/timing contract | Asset-backed visual/leak/GPU p95/p99 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 runtime scene preparer | Implemented extraction | Synthetic cached/live traversal/occluder/shadow/source/timing contract | Asset-backed visual/leak/GPU p95/p99 pending |
| WMO scene instance factory | Implemented extraction | Synthetic cached/live type/identity/lifetime/name/placement/source/timing contract | Serialized/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 | Asset-backed traversal/leak evidence pending |
| WMO scene Resource finalizer | Implemented extraction | Status/order/type/probe/lifetime/adoption/source/timing contract | Serialized stale/oversize and asset-backed evidence pending |
| WMO runtime Mesh finalizer | Implemented extraction | Identity/version/material-definition/source/timing contract | Asset-backed visual/leak/GPU/p95/p99 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 |
| Liquids | Partial | MH2O/MLIQ paths | LiquidType/depth/shore fidelity | | Liquids | Partial | MH2O/MLIQ paths | LiquidType/depth/shore fidelity |
@@ -630,18 +709,31 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
| `src/render/liquid/adt_water_scene_finalizer.gd` | Main-thread ADT water build, tile attachment and optional editor ownership | | `src/render/liquid/adt_water_scene_finalizer.gd` | Main-thread ADT water build, tile attachment and optional editor ownership |
| `src/render/m2/m2_runtime_mesh_rebuild_classifier.gd` | Billboard/UV-rotation stale cached-mesh rebuild decision and memoization | | `src/render/m2/m2_runtime_mesh_rebuild_classifier.gd` | Billboard/UV-rotation stale cached-mesh rebuild decision and memoization |
| `src/render/m2/m2_animation_load_pipeline_state.gd` | Animated M2 pending Resource paths, terminal statuses and finalize FIFO | | `src/render/m2/m2_animation_load_pipeline_state.gd` | Animated M2 pending Resource paths, terminal statuses and finalize FIFO |
| `src/render/m2/m2_animation_resource_finalizer.gd` | Cached animation terminal polling/load/finalize outcomes |
| `src/render/m2/m2_animated_scene_finalizer.gd` | Animated scene candidate ownership, material repair and player validation | | `src/render/m2/m2_animated_scene_finalizer.gd` | Animated scene candidate ownership, material repair and player validation |
| `src/render/m2/m2_animation_playback_controller.gd` | Per-instance phase, selection, native copy/start and imported playback | | `src/render/m2/m2_animation_playback_controller.gd` | Per-instance phase, selection, native copy/start and imported playback |
| `src/render/m2/m2_animated_instance_materializer.gd` | Animated duplicate/render/playback startup and non-empty batch attachment | | `src/render/m2/m2_animated_instance_materializer.gd` | Animated duplicate/render/playback startup and non-empty batch attachment |
| `src/render/m2/m2_static_batch_materializer.gd` | Static MultiMesh construction, render setup and attachment | | `src/render/m2/m2_static_batch_materializer.gd` | Static MultiMesh construction, render setup and attachment |
| `src/render/m2/m2_build_dispatch_planner.gd` | M2 wait/materializer/advance action priority and transition plan |
| `src/render/m2/m2_build_resource_snapshot.gd` | Typed per-step animated/static observations and borrowed references |
| `src/render/m2/m2_static_build_resource_observer.gd` | Static Mesh lookup/request/missing snapshot production |
| `src/render/m2/m2_cached_animation_resource_observer.gd` | Cached animated GLB policy/request/snapshot production |
| `src/render/m2/m2_build_job.gd` | M2 root/groups references and group/offset/serial progress | | `src/render/m2/m2_build_job.gd` | M2 root/groups references and group/offset/serial progress |
| `src/render/m2/m2_build_queue.gd` | Keyed pending jobs and FIFO/stale tile-key lifecycle | | `src/render/m2/m2_build_queue.gd` | Keyed pending jobs and FIFO/stale tile-key lifecycle |
| `src/render/m2/m2_mesh_load_pipeline_state.gd` | Static M2 pending Resource paths, terminal statuses and finalize FIFO | | `src/render/m2/m2_mesh_load_pipeline_state.gd` | Static M2 pending Resource paths, terminal statuses and finalize FIFO |
| `src/render/m2/m2_mesh_resource_cache_state.gd` | Prepared static M2 Mesh references and final-shutdown release | | `src/render/m2/m2_mesh_resource_cache_state.gd` | Prepared static M2 Mesh references and final-shutdown release |
| `src/render/m2/m2_mesh_resource_extractor.gd` | First-Mesh selection and temporary PackedScene instance lifetime | | `src/render/m2/m2_mesh_resource_extractor.gd` | First-Mesh selection and temporary PackedScene instance lifetime |
| `src/render/m2/m2_mesh_resource_finalizer.gd` | Static terminal polling, Mesh preparation and cache/missing adoption |
| `src/render/m2/m2_runtime_mesh_finalizer.gd` | Runtime refresh version, rebuild classification/build and fallback | | `src/render/m2/m2_runtime_mesh_finalizer.gd` | Runtime refresh version, rebuild classification/build and fallback |
| `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_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/wmo/wmo_scene_resource_finalizer.gd` | Cached WMO terminal polling, probe validation/lifetime and publication |
| `src/render/wmo/wmo_runtime_mesh_finalizer.gd` | Cached WMO runtime refresh admission, surface iteration and material reconstruction |
| `src/render/wmo/wmo_render_group_materializer.gd` | Indexed lightweight WMO geometry-node creation, render setup and attachment |
| `src/render/wmo/wmo_runtime_scene_preparer.gd` | Cached/live WMO subtree Mesh traversal and render policy |
| `src/render/wmo/wmo_scene_instance_factory.gd` | Cached/live detached-root creation, validation, identity and placement |
| `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 |
@@ -658,17 +750,30 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
| `src/tools/verify_adt_water_scene_finalizer.gd` | Synthetic ADT water scene/ownership/boundary/timing regression | | `src/tools/verify_adt_water_scene_finalizer.gd` | Synthetic ADT water scene/ownership/boundary/timing regression |
| `src/tools/verify_m2_runtime_mesh_rebuild_classifier.gd` | M2 rebuild predicate/cache/boundary/timing regression | | `src/tools/verify_m2_runtime_mesh_rebuild_classifier.gd` | M2 rebuild predicate/cache/boundary/timing regression |
| `src/tools/verify_m2_animation_load_pipeline_state.gd` | Animated M2 request/terminal/FIFO/boundary/timing regression | | `src/tools/verify_m2_animation_load_pipeline_state.gd` | Animated M2 request/terminal/FIFO/boundary/timing regression |
| `src/tools/verify_m2_animation_resource_finalizer.gd` | Animated M2 polling/load/finalize/adoption/boundary/timing regression |
| `src/tools/verify_m2_mesh_resource_finalizer.gd` | Static M2 polling/extraction/preparation/adoption/boundary/timing regression |
| `src/tools/verify_m2_animated_scene_finalizer.gd` | Animated M2 candidate/material/player/lifetime/boundary/timing regression | | `src/tools/verify_m2_animated_scene_finalizer.gd` | Animated M2 candidate/material/player/lifetime/boundary/timing regression |
| `src/tools/verify_m2_animation_playback_controller.gd` | Animated M2 phase/selection/playback/native/boundary/timing regression | | `src/tools/verify_m2_animation_playback_controller.gd` | Animated M2 phase/selection/playback/native/boundary/timing regression |
| `src/tools/verify_m2_animated_instance_materializer.gd` | Animated M2 order/render/playback/attachment/boundary/timing regression | | `src/tools/verify_m2_animated_instance_materializer.gd` | Animated M2 order/render/playback/attachment/boundary/timing regression |
| `src/tools/verify_m2_static_batch_materializer.gd` | Static M2 node/Mesh/render/attachment/boundary/timing regression | | `src/tools/verify_m2_static_batch_materializer.gd` | Static M2 node/Mesh/render/attachment/boundary/timing regression |
| `src/tools/verify_m2_build_dispatch_planner.gd` | M2 dispatch priority/action/transition/boundary/timing regression |
| `src/tools/verify_m2_build_resource_snapshot.gd` | M2 observation identity/adoption/lifetime/boundary/timing regression |
| `src/tools/verify_m2_static_build_resource_observer.gd` | Static lookup/request/path/boundary/timing regression |
| `src/tools/verify_m2_cached_animation_resource_observer.gd` | Cached animation lifecycle/policy/GLB/boundary/timing regression |
| `src/tools/verify_m2_build_queue.gd` | M2 job/FIFO/progress/lifetime/boundary/timing regression | | `src/tools/verify_m2_build_queue.gd` | M2 job/FIFO/progress/lifetime/boundary/timing regression |
| `src/tools/verify_m2_mesh_load_pipeline_state.gd` | Static M2 request/terminal/FIFO/boundary/timing regression | | `src/tools/verify_m2_mesh_load_pipeline_state.gd` | Static M2 request/terminal/FIFO/boundary/timing regression |
| `src/tools/verify_m2_mesh_resource_cache_state.gd` | Static M2 Mesh cache ownership/lifetime/boundary/timing regression | | `src/tools/verify_m2_mesh_resource_cache_state.gd` | Static M2 Mesh cache ownership/lifetime/boundary/timing regression |
| `src/tools/verify_m2_mesh_resource_extractor.gd` | M2 direct/PackedScene/subtree order/lifetime/boundary/timing regression | | `src/tools/verify_m2_mesh_resource_extractor.gd` | M2 direct/PackedScene/subtree order/lifetime/boundary/timing regression |
| `src/tools/verify_m2_runtime_mesh_finalizer.gd` | M2 current/stale/rebuild/fallback/boundary/timing regression | | `src/tools/verify_m2_runtime_mesh_finalizer.gd` | M2 current/stale/rebuild/fallback/boundary/timing regression |
| `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_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_wmo_scene_resource_finalizer.gd` | WMO scene status/order/probe/lifetime/adoption/boundary/timing regression |
| `src/tools/verify_wmo_runtime_mesh_finalizer.gd` | WMO Mesh identity/version/material-definition/boundary/timing regression |
| `src/tools/verify_wmo_render_group_materializer.gd` | WMO render-group Resource/name/transform/render/attachment/boundary/timing regression |
| `src/tools/verify_wmo_runtime_scene_preparer.gd` | WMO cached/live traversal/occluder/shadow/boundary/timing regression |
| `src/tools/verify_wmo_scene_instance_factory.gd` | WMO cached/live type/identity/lifetime/name/placement/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 |
@@ -680,6 +785,9 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
| `src/native/src/*_loader.cpp` | Native binary parsing | | `src/native/src/*_loader.cpp` | Native binary parsing |
| `src/tools/build_*cache.gd`, `src/tools/bake_*cache.gd` | Offline cache generation | | `src/tools/build_*cache.gd`, `src/tools/bake_*cache.gd` | Offline cache generation |
| `tools/run_render_baseline.ps1` | Unified M00 baseline runner | | `tools/run_render_baseline.ps1` | Unified M00 baseline runner |
| `tools/compare_render_performance.ps1` | Exact-environment single/repeated report comparator |
| `tools/verify_render_performance_stability.ps1` | Repeated-sample/long-window repeatability gate |
| `src/tools/verify_renderer_closeout_contracts.gd` | Worker, main-thread, cache-version and nested-GLB closeout contracts |
| `src/tools/compare_render_checkpoints.gd` | Offline JPG/PNG paired-image perceptual metrics and JSON pass/fail report | | `src/tools/compare_render_checkpoints.gd` | Offline JPG/PNG paired-image perceptual metrics and JSON pass/fail report |
| `src/tools/verify_render_runtime_cache_shutdown.gd` | Headless ownership regression for detached runtime prototypes, resource caches and empty liquid roots | | `src/tools/verify_render_runtime_cache_shutdown.gd` | Headless ownership regression for detached runtime prototypes, resource caches and empty liquid roots |
| `src/tools/capture_render_checkpoints.gd` | Deterministic no-roll checkpoint camera, performance and visual capture | | `src/tools/capture_render_checkpoints.gd` | Deterministic no-roll checkpoint camera, performance and visual capture |
+23
View File
@@ -6,6 +6,29 @@ Godot project scanning is disabled for this directory by `.gdignore`. Reference
assets remain available to Git, text search and external tooling, but Godot must assets remain available to Git, text search and external tooling, but Godot must
not generate `.import` sidecars inside nested reference repositories. not generate `.import` sidecars inside nested reference repositories.
## Git reference revisions
Remote branches were fetched with pruning and the checked-out default branches
were fast-forwarded on 2026-09-05. The parent repository pins the exact commits
through gitlinks; `.gitmodules` records the branch used for an intentional future
`git submodule update --remote`. Inspect local state before advancing the parent
gitlink pins; checked-out branches in this refresh used `git pull --ff-only`.
| Reference | Canonical branch | Pinned commit |
| --- | --- | --- |
| `open-realm` | `main` | `950a1e8c1343a6e2139cc448f779cb2957a8d3f8` |
| `whoa` | `master` | `ea1345636045635ac601b7d525eb0c38c6c0f6dd` |
| `WoWee` | `master` | `607ea3b8369851014721416293f8e95dfbe64eec` |
| `WowUnreal` | `main` | `c2a4b9827b2e7672e799085c5bf631d37c4ca5d3` |
| `wow.export` | `main` | `c2fd7bde36a712be78a5da896c995b84fbfa2545` |
| `benilla` | `main` | `bc1a2428dd7e00ca8abbfb8f0bf53750dae7b123` |
| `blender-wow-studio/.../pywowlib` | `master` | `55276dc5c2195da7fe136638a2a59716622f8c65` |
`blender-wow-studio-3.4-1.1.0_Experimental` and `noggit-red` are tracked
snapshots, not top-level gitlinks. They do not have a local branch and require a
separate, provenance-checked snapshot refresh instead of a blind pull. The
embedded `pywowlib` gitlink is maintained independently as listed above.
- `blender-wow-studio-3.4-1.1.0_Experimental/` - Blender add-on sources for M2/WMO import/export reference. - `blender-wow-studio-3.4-1.1.0_Experimental/` - Blender add-on sources for M2/WMO import/export reference.
- `wow.export/` - wow.export source tree and Blender OBJ importer reference. - `wow.export/` - wow.export source tree and Blender OBJ importer reference.
- `noggit-red/` - Noggit RED source tree from `https://gitlab.com/dirtbikercj/noggit-red`, used as a reference for WoW 3.3.5 map editing, ADT/WMO/M2 placement behavior, UID handling and editor workflows. - `noggit-red/` - Noggit RED source tree from `https://gitlab.com/dirtbikercj/noggit-red`, used as a reference for WoW 3.3.5 map editing, ADT/WMO/M2 placement behavior, UID handling and editor workflows.
+1
Submodule reference/benilla added at bc1a2428dd
@@ -39,9 +39,12 @@ func start_instance_playback(
var phase := phase_for_instance(relative_path, instance_index) var phase := phase_for_instance(relative_path, instance_index)
var native_diagnostics: Array[Dictionary] = [] var native_diagnostics: Array[Dictionary] = []
for animator in native_animators_in_subtree(root, native_animator_script): for animator in native_animators_in_subtree(root, native_animator_script):
if animator.has_method("prepare_runtime"): if animator.has_method("prepare_runtime_at_phase"):
animator.prepare_runtime() animator.prepare_runtime_at_phase(phase)
animator.set_phase(phase) else:
if animator.has_method("prepare_runtime"):
animator.prepare_runtime()
animator.set_phase(phase)
if collect_native_diagnostics and animator.has_method("runtime_debug_state"): if collect_native_diagnostics and animator.has_method("runtime_debug_state"):
var diagnostic_variant = animator.runtime_debug_state() var diagnostic_variant = animator.runtime_debug_state()
if diagnostic_variant is Dictionary: if diagnostic_variant is Dictionary:
@@ -0,0 +1,187 @@
class_name M2AnimationResourceFinalizer
extends RefCounted
## Polls cached animated M2 ResourceLoader requests and finalizes terminal scenes.
## The caller owns permits, material-source lookup and SceneTree materialization.
const M2_ANIMATED_SCENE_FINALIZER_SCRIPT := preload(
"res://src/render/m2/m2_animated_scene_finalizer.gd"
)
var _animated_scene_finalizer: RefCounted
var _resource_loader_adapter: Object
func _init(
animated_scene_finalizer: RefCounted = null,
resource_loader_adapter: Object = null
) -> void:
_animated_scene_finalizer = animated_scene_finalizer
if _animated_scene_finalizer == null:
_animated_scene_finalizer = M2_ANIMATED_SCENE_FINALIZER_SCRIPT.new()
_resource_loader_adapter = resource_loader_adapter
## Moves terminal threaded requests into the pipeline finalize FIFO.
## Empty resource paths retain the historical immediate static-only outcome.
func poll_terminal_requests(
load_pipeline_state: RefCounted,
prototype_cache_state: RefCounted
) -> int:
if load_pipeline_state == null or prototype_cache_state == null:
return 0
var completed_request_count := 0
var request_records: Array = load_pipeline_state.call(
"request_records_snapshot"
)
for request_variant in request_records:
if not (request_variant is Dictionary):
continue
var request: Dictionary = request_variant
var normalized_relative_path := String(request.get("normalized", ""))
var resource_path := String(request.get("path", ""))
if resource_path.is_empty():
load_pipeline_state.call(
"discard_request",
normalized_relative_path
)
prototype_cache_state.call(
"mark_animation_static",
normalized_relative_path
)
completed_request_count += 1
continue
var load_status := load_threaded_get_status(resource_path)
if (
load_status != ResourceLoader.THREAD_LOAD_LOADED
and load_status != ResourceLoader.THREAD_LOAD_FAILED
):
continue
load_pipeline_state.call(
"complete_request",
normalized_relative_path,
load_status
)
completed_request_count += 1
return completed_request_count
## Pops one terminal record and prepares a detached animated scene candidate.
## An empty result means the record was skipped or resolved as static-only.
func prepare_next_candidate(
load_pipeline_state: RefCounted,
prototype_cache_state: RefCounted
) -> Dictionary:
if load_pipeline_state == null or prototype_cache_state == null:
return {}
var terminal_record: Dictionary = load_pipeline_state.call(
"pop_finalize_record"
)
if terminal_record.is_empty():
return {}
var normalized_relative_path := String(
terminal_record.get("normalized", "")
)
if (
normalized_relative_path.is_empty()
or bool(prototype_cache_state.call(
"has_animated_prototype",
normalized_relative_path
))
or bool(prototype_cache_state.call(
"is_animation_static",
normalized_relative_path
))
):
return {}
if int(terminal_record.get(
"status",
ResourceLoader.THREAD_LOAD_FAILED
)) != ResourceLoader.THREAD_LOAD_LOADED:
prototype_cache_state.call(
"mark_animation_static",
normalized_relative_path
)
return {}
var resource_path := String(terminal_record.get("path", ""))
var loaded_resource := load_threaded_get(resource_path)
var candidate := _animated_scene_finalizer.call(
"instantiate_candidate",
loaded_resource
) as Node3D
if candidate == null:
prototype_cache_state.call(
"mark_animation_static",
normalized_relative_path
)
return {}
return {
"normalized": normalized_relative_path,
"path": resource_path,
"candidate": candidate,
}
## Repairs, validates and adopts one prepared candidate. The preparation must be
## completed synchronously so its detached candidate always receives an owner.
func finalize_prepared_candidate(
preparation: Dictionary,
material_source_root: Node3D,
prototype_cache_state: RefCounted,
debug_logging_enabled: bool = false
) -> Node3D:
if prototype_cache_state == null:
return null
var normalized_relative_path := String(preparation.get("normalized", ""))
var candidate := preparation.get("candidate", null) as Node3D
if normalized_relative_path.is_empty() or candidate == null:
return null
_animated_scene_finalizer.call(
"repair_materials",
candidate,
material_source_root
)
var finalization: Dictionary = _animated_scene_finalizer.call(
"finalize_candidate",
candidate
)
var prototype := finalization.get("prototype", null) as Node3D
if prototype == null:
prototype_cache_state.call(
"mark_animation_static",
normalized_relative_path
)
return null
var canonical_prototype := prototype_cache_state.call(
"adopt_animated_prototype",
normalized_relative_path,
prototype
) as Node3D
if debug_logging_enabled:
print("M2_ANIM_CACHE path=%s cache=%s players=%d" % [
normalized_relative_path,
String(preparation.get("path", "")),
int(finalization.get("animation_player_count", 0)),
])
return canonical_prototype
## Production ResourceLoader status adapter; injectable in 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 terminal-result adapter; injectable in 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://2rnsjucg3dul
@@ -0,0 +1,47 @@
class_name M2BuildDispatchPlanner
extends RefCounted
## Selects the next M2 build action from already observed resource state.
## The planner retains no state or engine-object references.
const ACTION_WAIT_FOR_ANIMATION := &"wait_for_animation"
const ACTION_MATERIALIZE_ANIMATED := &"materialize_animated"
const ACTION_WAIT_FOR_STATIC_MESH := &"wait_for_static_mesh"
const ACTION_MATERIALIZE_STATIC := &"materialize_static"
const ACTION_ADVANCE_WITHOUT_MATERIALIZATION := &"advance_without_materialization"
## Returns a detached action/transition plan for one M2 build operation.
func plan_step(
batch_count: int,
resource_snapshot: RefCounted
) -> Dictionary:
if resource_snapshot == null:
return _wait_plan(ACTION_WAIT_FOR_STATIC_MESH)
if bool(resource_snapshot.call("animation_request_pending")):
return _wait_plan(ACTION_WAIT_FOR_ANIMATION)
if batch_count <= 0:
return _advance_plan(ACTION_ADVANCE_WITHOUT_MATERIALIZATION, false)
if bool(resource_snapshot.call("has_animated_prototype")):
return _advance_plan(ACTION_MATERIALIZE_ANIMATED, true)
if bool(resource_snapshot.call("has_static_mesh")):
return _advance_plan(ACTION_MATERIALIZE_STATIC, true)
if bool(resource_snapshot.call("static_model_missing")):
return _advance_plan(ACTION_ADVANCE_WITHOUT_MATERIALIZATION, true)
return _wait_plan(ACTION_WAIT_FOR_STATIC_MESH)
func _wait_plan(action: StringName) -> Dictionary:
return {
"action": action,
"rotate_queue": true,
"increment_batch_serial": false,
}
func _advance_plan(action: StringName, increment_batch_serial: bool) -> Dictionary:
return {
"action": action,
"rotate_queue": false,
"increment_batch_serial": increment_batch_serial,
}
@@ -0,0 +1 @@
uid://df0bv1x2x4j2x
@@ -0,0 +1,72 @@
class_name M2BuildResourceSnapshot
extends RefCounted
## Holds one M2 build-step resource observation without owning engine lifetime.
var _normalized_relative_path: String
var _animated_prototype: Node3D
var _animation_request_pending: bool
var _static_mesh: Mesh = null
var _static_model_missing: bool = false
func _init(
normalized_relative_path: String,
animated_prototype: Node3D,
animation_request_pending: bool
) -> void:
_normalized_relative_path = normalized_relative_path
_animated_prototype = animated_prototype
_animation_request_pending = animation_request_pending
## Returns the normalized M2 path observed by the loader.
func normalized_relative_path() -> String:
return _normalized_relative_path
## Returns the borrowed animated prototype without transferring ownership.
func animated_prototype() -> Node3D:
return _animated_prototype
## Returns whether an animated prototype was observed.
func has_animated_prototype() -> bool:
return _animated_prototype != null
## Returns whether the animation request remains pending.
func animation_request_pending() -> bool:
return _animation_request_pending
## Adopts the optional static Mesh and terminal missing-model observation.
func adopt_static_observation(static_mesh: Mesh, static_model_missing: bool) -> void:
_static_mesh = static_mesh
_static_model_missing = static_model_missing
## Returns the borrowed static Mesh without transferring ownership.
func static_mesh() -> Mesh:
return _static_mesh
## Returns whether a prepared static Mesh was observed.
func has_static_mesh() -> bool:
return _static_mesh != null
## Returns whether the static model lookup reached a terminal missing outcome.
func static_model_missing() -> bool:
return _static_model_missing
## Returns detached path/availability diagnostics without engine references.
func diagnostic_snapshot() -> Dictionary:
return {
"normalized_relative_path": _normalized_relative_path,
"has_animated_prototype": has_animated_prototype(),
"animation_request_pending": _animation_request_pending,
"has_static_mesh": has_static_mesh(),
"static_model_missing": _static_model_missing,
}
@@ -0,0 +1 @@
uid://cgleoy15rtmby
@@ -0,0 +1,238 @@
class_name M2CachedAnimationResourceObserver
extends RefCounted
## Observes or requests the cached-GLB animated phase of one M2 build step.
const M2_BUILD_RESOURCE_SNAPSHOT_SCRIPT := preload(
"res://src/render/m2/m2_build_resource_snapshot.gd"
)
## Returns a snapshot containing the exact cached prototype or pending state.
## Invalid composition and terminal failures produce a non-pending snapshot.
func observe(
normalized_relative_path: String,
cache_directory: String,
maximum_primitive_count: int,
allowlist_patterns: PackedStringArray,
denylist_patterns: PackedStringArray,
prototype_cache_state: RefCounted,
load_pipeline_state: RefCounted,
debug_logging_enabled: bool = false
) -> RefCounted:
var empty_snapshot: RefCounted = M2_BUILD_RESOURCE_SNAPSHOT_SCRIPT.new(
normalized_relative_path,
null,
false
)
if (
normalized_relative_path.is_empty()
or prototype_cache_state == null
or load_pipeline_state == null
):
return empty_snapshot
var cached_prototype := prototype_cache_state.call(
"find_animated_prototype",
normalized_relative_path
) as Node3D
if cached_prototype != null:
return M2_BUILD_RESOURCE_SNAPSHOT_SCRIPT.new(
normalized_relative_path,
cached_prototype,
false
)
if bool(prototype_cache_state.call(
"is_animation_static",
normalized_relative_path
)):
return empty_snapshot
if bool(load_pipeline_state.call("has_request", normalized_relative_path)):
return M2_BUILD_RESOURCE_SNAPSHOT_SCRIPT.new(
normalized_relative_path,
null,
true
)
var cache_resource_path := find_eligible_glb_cache_path(
normalized_relative_path,
cache_directory,
maximum_primitive_count,
allowlist_patterns,
denylist_patterns,
debug_logging_enabled
)
if cache_resource_path.is_empty():
prototype_cache_state.call(
"mark_animation_static",
normalized_relative_path
)
return empty_snapshot
var request_error := ResourceLoader.load_threaded_request(
cache_resource_path,
"",
false,
ResourceLoader.CACHE_MODE_REUSE
)
if request_error == OK or request_error == ERR_BUSY:
load_pipeline_state.call(
"remember_request",
normalized_relative_path,
cache_resource_path
)
return M2_BUILD_RESOURCE_SNAPSHOT_SCRIPT.new(
normalized_relative_path,
null,
true
)
prototype_cache_state.call("mark_animation_static", normalized_relative_path)
return empty_snapshot
## Returns the first eligible historical `.glb` cache candidate.
func find_eligible_glb_cache_path(
normalized_relative_path: String,
cache_directory: String,
maximum_primitive_count: int,
allowlist_patterns: PackedStringArray,
denylist_patterns: PackedStringArray,
debug_logging_enabled: bool = false
) -> String:
if not is_animation_path_allowed(
normalized_relative_path,
allowlist_patterns,
denylist_patterns
):
return ""
for cache_resource_path in cache_resource_paths(
cache_directory,
normalized_relative_path
):
if not ResourceLoader.exists(cache_resource_path):
continue
if glb_cache_is_safe_for_runtime_animation(
cache_resource_path,
maximum_primitive_count,
debug_logging_enabled
):
return cache_resource_path
return ""
## Applies the historical stripped, case-insensitive allow/deny substring policy.
func is_animation_path_allowed(
normalized_relative_path: String,
allowlist_patterns: PackedStringArray,
denylist_patterns: PackedStringArray
) -> bool:
if normalized_relative_path.is_empty() or allowlist_patterns.is_empty():
return false
var lowercase_path := normalized_relative_path.to_lower()
var allowed := false
for pattern in allowlist_patterns:
var needle := String(pattern).strip_edges().to_lower()
if not needle.is_empty() and lowercase_path.contains(needle):
allowed = true
break
if not allowed:
return false
for pattern in denylist_patterns:
var needle := String(pattern).strip_edges().to_lower()
if not needle.is_empty() and lowercase_path.contains(needle):
return false
return true
## Returns historical nested/lowercase/basename `.glb` candidates without I/O.
func cache_resource_paths(
cache_directory: String,
relative_path: String
) -> PackedStringArray:
var normalized := relative_path.replace("\\", "/")
var lowercase := normalized.to_lower()
var stems := [
normalized.get_basename(),
lowercase.get_basename(),
normalized.get_file().get_basename(),
lowercase.get_file().get_basename(),
]
var result := PackedStringArray()
for stem in stems:
if stem.is_empty():
continue
var cache_resource_path := cache_directory.path_join(stem + ".glb")
if not result.has(cache_resource_path):
result.append(cache_resource_path)
return result
## Checks animation presence, primitive limit and accepted OpenWC schema.
func glb_cache_is_safe_for_runtime_animation(
cache_resource_path: String,
maximum_primitive_count: int,
debug_logging_enabled: bool = false
) -> bool:
var gltf := read_glb_json(cache_resource_path)
if gltf.is_empty():
return false
var animations: Array = gltf.get("animations", [])
if animations.is_empty():
return false
var primitive_count := glb_primitive_count(gltf)
if maximum_primitive_count > 0 and primitive_count > maximum_primitive_count:
return false
var schema := glb_animation_schema(gltf)
if schema == "pivot_prefix_v1" or schema.is_empty():
return true
if debug_logging_enabled:
print(
"M2_ANIM_REJECT schema=%s primitives=%d cache=%s"
% [schema, primitive_count, cache_resource_path]
)
return false
## Reads the JSON chunk of a version-2 GLB or returns an empty Dictionary.
func read_glb_json(cache_resource_path: String) -> Dictionary:
var absolute_path := ProjectSettings.globalize_path(cache_resource_path)
if not FileAccess.file_exists(absolute_path):
return {}
var file := FileAccess.open(absolute_path, FileAccess.READ)
if file == null or file.get_length() < 20:
return {}
var magic := file.get_32()
var version := file.get_32()
file.get_32()
if magic != 0x46546c67 or version != 2:
return {}
var json_length := int(file.get_32())
var chunk_type := file.get_32()
if json_length <= 0 or chunk_type != 0x4e4f534a:
return {}
var parsed: Variant = JSON.parse_string(
file.get_buffer(json_length).get_string_from_utf8()
)
return parsed as Dictionary if parsed is Dictionary else {}
## Counts all glTF Mesh primitives using the historical safety rule.
func glb_primitive_count(gltf: Dictionary) -> int:
var count := 0
var meshes: Array = gltf.get("meshes", [])
for mesh_variant in meshes:
if mesh_variant is Dictionary:
var primitives: Array = (mesh_variant as Dictionary).get(
"primitives",
[]
)
count += primitives.size()
return count
## Returns the OpenWC animation schema marker from parsed glTF metadata.
func glb_animation_schema(gltf: Dictionary) -> String:
var asset: Dictionary = gltf.get("asset", {})
var extras: Dictionary = asset.get("extras", {})
return String(extras.get("openwc_m2_anim_schema", ""))
@@ -0,0 +1 @@
uid://d0ptej41k2smt
+182
View File
@@ -0,0 +1,182 @@
class_name M2MeshResourceFinalizer
extends RefCounted
## Polls static M2 ResourceLoader requests and publishes prepared Mesh outcomes.
## The caller owns permits, pipeline/cache lifetime and MultiMesh materialization.
var _mesh_resource_extractor: RefCounted
var _runtime_mesh_finalizer: RefCounted
var _raw_model_repository: RefCounted
var _resource_loader_adapter: Object
func _init(
mesh_resource_extractor: RefCounted,
runtime_mesh_finalizer: RefCounted,
raw_model_repository: RefCounted,
resource_loader_adapter: Object = null
) -> void:
_mesh_resource_extractor = mesh_resource_extractor
_runtime_mesh_finalizer = runtime_mesh_finalizer
_raw_model_repository = raw_model_repository
_resource_loader_adapter = resource_loader_adapter
## Moves terminal threaded requests into the pipeline finalize FIFO.
## Empty resource paths retain the historical immediate missing-model outcome.
func poll_terminal_requests(
load_pipeline_state: RefCounted,
prototype_cache_state: RefCounted
) -> int:
if load_pipeline_state == null or prototype_cache_state == null:
return 0
var completed_request_count := 0
var request_records: Array = load_pipeline_state.call(
"request_records_snapshot"
)
for request_variant in request_records:
if not (request_variant is Dictionary):
continue
var request: Dictionary = request_variant
var normalized_relative_path := String(request.get("normalized", ""))
var resource_path := String(request.get("path", ""))
if resource_path.is_empty():
load_pipeline_state.call(
"discard_request",
normalized_relative_path
)
prototype_cache_state.call(
"mark_model_missing",
normalized_relative_path
)
completed_request_count += 1
continue
var load_status := load_threaded_get_status(resource_path)
if (
load_status != ResourceLoader.THREAD_LOAD_LOADED
and load_status != ResourceLoader.THREAD_LOAD_FAILED
):
continue
load_pipeline_state.call(
"complete_request",
normalized_relative_path,
load_status
)
completed_request_count += 1
return completed_request_count
## Pops and finalizes one terminal record. A true result means a record was
## consumed, including cached, failed and invalid outcomes.
func finalize_next_resource(
load_pipeline_state: RefCounted,
mesh_resource_cache_state: RefCounted,
prototype_cache_state: RefCounted,
extracted_directory: String
) -> bool:
if (
load_pipeline_state == null
or mesh_resource_cache_state == null
or prototype_cache_state == null
):
return false
var terminal_record: Dictionary = load_pipeline_state.call(
"pop_finalize_record"
)
if terminal_record.is_empty():
return false
var normalized_relative_path := String(
terminal_record.get("normalized", "")
)
if (
normalized_relative_path.is_empty()
or bool(mesh_resource_cache_state.call(
"has_mesh",
normalized_relative_path
))
):
return true
if int(terminal_record.get(
"status",
ResourceLoader.THREAD_LOAD_FAILED
)) != ResourceLoader.THREAD_LOAD_LOADED:
prototype_cache_state.call(
"mark_model_missing",
normalized_relative_path
)
return true
var resource_path := String(terminal_record.get("path", ""))
var loaded_resource := load_threaded_get(resource_path)
var mesh := _mesh_resource_extractor.call(
"extract_first_mesh",
loaded_resource
) as Mesh
if mesh == null:
prototype_cache_state.call(
"mark_model_missing",
normalized_relative_path
)
return true
mesh_resource_cache_state.call(
"store_mesh",
normalized_relative_path,
prepare_mesh_for_runtime(
normalized_relative_path,
mesh,
extracted_directory
)
)
return true
## Applies the existing refresh-version/raw-data/rebuild path to one Mesh.
func prepare_mesh_for_runtime(
normalized_relative_path: String,
mesh: Mesh,
extracted_directory: String
) -> Mesh:
if mesh == null:
return null
if (
_runtime_mesh_finalizer == null
or not bool(_runtime_mesh_finalizer.call(
"requires_raw_data_for_refresh",
mesh
))
):
return mesh
var raw_model_data: Dictionary = {}
if _raw_model_repository != null:
raw_model_data = _raw_model_repository.call(
"load_static_model_data",
extracted_directory,
normalized_relative_path
)
return _runtime_mesh_finalizer.call(
"finalize_mesh",
normalized_relative_path,
mesh,
raw_model_data,
extracted_directory
) as Mesh
## Production ResourceLoader status adapter; injectable in 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 terminal-result adapter; injectable in 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://de4xl1fywvqfw
@@ -0,0 +1,113 @@
class_name M2NativeAnimationResourceObserver
extends RefCounted
## Resolves the existing native GryphonRoost animated M2 prototype path.
const M2_NATIVE_ANIMATED_BUILDER_SCRIPT := preload(
"res://addons/mpq_extractor/loaders/m2_native_animated_builder.gd"
)
var _native_animated_builder: Object
func _init(native_animated_builder: Object = M2_NATIVE_ANIMATED_BUILDER_SCRIPT) -> void:
_native_animated_builder = native_animated_builder
## Returns whether the normalized path uses the historical native animation path.
func is_native_animation_candidate(normalized_relative_path: String) -> bool:
return normalized_relative_path.to_lower().contains("gryphonroost")
## Returns the exact cached or newly adopted native animated prototype.
## Rejected native candidates are marked static-only for the renderer session.
func observe(
normalized_relative_path: String,
extracted_directory: String,
raw_model_repository: RefCounted,
prototype_cache_state: RefCounted,
debug_logging_enabled: bool = false
) -> Node3D:
if (
normalized_relative_path.is_empty()
or not is_native_animation_candidate(normalized_relative_path)
or raw_model_repository == null
or prototype_cache_state == null
):
return null
var cached_prototype := prototype_cache_state.call(
"find_animated_prototype",
normalized_relative_path
) as Node3D
if cached_prototype != null:
return cached_prototype
if bool(prototype_cache_state.call(
"is_animation_static",
normalized_relative_path
)):
return null
var animated_model_data: Dictionary = raw_model_repository.call(
"load_animated_model_data",
extracted_directory,
normalized_relative_path
)
var animated_surfaces: Array = animated_model_data.get(
"animated_surfaces",
[]
)
if animated_model_data.is_empty() or animated_surfaces.is_empty():
prototype_cache_state.call(
"mark_animation_static",
normalized_relative_path
)
return null
var prototype := build_animated_prototype(
animated_model_data,
extracted_directory
)
if prototype == null or prototype.get_child_count() <= 0:
prototype_cache_state.call(
"mark_animation_static",
normalized_relative_path
)
return null
prototype = prototype_cache_state.call(
"adopt_animated_prototype",
normalized_relative_path,
prototype
) as Node3D
if debug_logging_enabled:
print(
(
"M2_NATIVE_ANIM_CACHE path=%s surfaces=%d bones=%d "
+ "anim_id=%d seq=%d score=%d length=%.2f"
)
% [
normalized_relative_path,
animated_surfaces.size(),
(animated_model_data.get("bones", []) as Array).size(),
int(animated_model_data.get("animation_id", -1)),
int(animated_model_data.get("animation_sequence_index", -1)),
int(animated_model_data.get("animation_activity_score", 0)),
float(animated_model_data.get("animation_length", 0.0)),
]
)
return prototype
## Builds one prototype through the exact native animated builder dependency.
func build_animated_prototype(
animated_model_data: Dictionary,
extracted_directory: String
) -> Node3D:
if _native_animated_builder == null:
return null
return _native_animated_builder.call(
"build",
animated_model_data,
extracted_directory
) as Node3D
@@ -0,0 +1 @@
uid://bc3kgi23usncx
@@ -0,0 +1,126 @@
class_name M2StaticBuildResourceObserver
extends RefCounted
## Resolves or requests one static M2 build Mesh and fills a resource snapshot.
const OUTCOME_REJECTED := &"rejected"
const OUTCOME_CACHED := &"cached"
const OUTCOME_PENDING := &"pending"
const OUTCOME_REQUESTED := &"requested"
const OUTCOME_MISSING := &"missing"
## Observes cache/request state and adopts the exact static result into snapshot.
func observe(
resource_snapshot: RefCounted,
normalized_relative_path: String,
cache_directory: String,
mesh_cache_state: RefCounted,
prototype_cache_state: RefCounted,
load_pipeline_state: RefCounted
) -> StringName:
if (
resource_snapshot == null
or normalized_relative_path.is_empty()
or mesh_cache_state == null
or prototype_cache_state == null
or load_pipeline_state == null
):
return OUTCOME_REJECTED
if bool(mesh_cache_state.call("has_mesh", normalized_relative_path)):
resource_snapshot.call(
"adopt_static_observation",
mesh_cache_state.call("find_mesh", normalized_relative_path) as Mesh,
false
)
return OUTCOME_CACHED
if bool(prototype_cache_state.call("is_model_missing", normalized_relative_path)):
resource_snapshot.call("adopt_static_observation", null, true)
return OUTCOME_MISSING
if bool(load_pipeline_state.call("has_request", normalized_relative_path)):
resource_snapshot.call("adopt_static_observation", null, false)
return OUTCOME_PENDING
for cache_resource_path in cache_resource_paths(
cache_directory,
normalized_relative_path,
[".tscn", ".glb"]
):
if not ResourceLoader.exists(cache_resource_path):
continue
if (
cache_resource_path.get_extension().to_lower() == "glb"
and glb_animation_schema(cache_resource_path) == "pivot_prefix_v1"
):
continue
var request_error := ResourceLoader.load_threaded_request(
cache_resource_path,
"",
false,
ResourceLoader.CACHE_MODE_REUSE
)
if request_error == OK or request_error == ERR_BUSY:
load_pipeline_state.call(
"remember_request",
normalized_relative_path,
cache_resource_path
)
resource_snapshot.call("adopt_static_observation", null, false)
return OUTCOME_REQUESTED
break
prototype_cache_state.call("mark_model_missing", normalized_relative_path)
resource_snapshot.call("adopt_static_observation", null, true)
return OUTCOME_MISSING
## Returns historical nested/lowercase/basename cache candidates without I/O.
func cache_resource_paths(
cache_directory: String,
relative_path: String,
extensions: Array[String]
) -> PackedStringArray:
var normalized := relative_path.replace("\\", "/")
var lower := normalized.to_lower()
var stems := [
normalized.get_basename(),
lower.get_basename(),
normalized.get_file().get_basename(),
lower.get_file().get_basename(),
]
var result := PackedStringArray()
for extension in extensions:
for stem in stems:
if stem.is_empty():
continue
var path := cache_directory.path_join(stem + extension)
if not result.has(path):
result.append(path)
return result
## Reads only the OpenWC animation schema marker from a GLB JSON chunk.
func glb_animation_schema(cache_resource_path: String) -> String:
var absolute_path := ProjectSettings.globalize_path(cache_resource_path)
if not FileAccess.file_exists(absolute_path):
return ""
var file := FileAccess.open(absolute_path, FileAccess.READ)
if file == null or file.get_length() < 20:
return ""
var magic := file.get_32()
var version := file.get_32()
file.get_32()
if magic != 0x46546c67 or version != 2:
return ""
var json_length := int(file.get_32())
var chunk_type := file.get_32()
if json_length <= 0 or chunk_type != 0x4e4f534a:
return ""
var parsed: Variant = JSON.parse_string(
file.get_buffer(json_length).get_string_from_utf8()
)
if not (parsed is Dictionary):
return ""
var asset: Dictionary = (parsed as Dictionary).get("asset", {})
var extras: Dictionary = asset.get("extras", {})
return String(extras.get("openwc_m2_anim_schema", ""))
@@ -0,0 +1 @@
uid://c0e3p80ld1dfb
@@ -0,0 +1,101 @@
class_name WmoRenderGroupMaterializer
extends RefCounted
## Creates and attaches one lightweight cached WMO render group. Resource
## finalization, build-step selection and queue progress remain caller-owned.
## All methods mutate SceneTree nodes and must run on the renderer main thread.
## Creates one MeshInstance3D for [param group_index], applies the indexed name
## and optional transform contracts, then attaches it to [param wmo_parent_root].
## The parent owns the returned node; the exact Mesh identity is retained.
## Null/invalid parents, null meshes and negative indices return null.
func materialize_mesh_group(
wmo_parent_root: Node3D,
mesh: Mesh,
group_names: PackedStringArray,
group_transforms: Array,
group_index: int,
visibility_range_end: float,
visibility_range_end_margin: float,
cast_shadows: bool) -> MeshInstance3D:
if (
wmo_parent_root == null
or not is_instance_valid(wmo_parent_root)
or mesh == null
or group_index < 0
):
return null
var mesh_instance := MeshInstance3D.new()
mesh_instance.name = (
group_names[group_index]
if group_index < group_names.size()
else "Group_%d" % group_index
)
mesh_instance.mesh = mesh
if group_index < group_transforms.size():
mesh_instance.transform = group_transforms[group_index]
_apply_render_settings(
mesh_instance,
visibility_range_end,
visibility_range_end_margin,
cast_shadows
)
wmo_parent_root.add_child(mesh_instance)
return mesh_instance
## Creates one MultiMeshInstance3D for [param group_index], applies the indexed
## name and optional transform contracts, then attaches it to the supplied root.
## The parent owns the returned node; the exact MultiMesh identity is retained.
## Null/invalid parents, null MultiMeshes and negative indices return null.
func materialize_multimesh_group(
wmo_parent_root: Node3D,
multimesh: MultiMesh,
group_names: PackedStringArray,
group_transforms: Array,
group_index: int,
visibility_range_end: float,
visibility_range_end_margin: float,
cast_shadows: bool) -> MultiMeshInstance3D:
if (
wmo_parent_root == null
or not is_instance_valid(wmo_parent_root)
or multimesh == null
or group_index < 0
):
return null
var multimesh_instance := MultiMeshInstance3D.new()
multimesh_instance.name = (
group_names[group_index]
if group_index < group_names.size()
else "DoodadGroup_%d" % group_index
)
multimesh_instance.multimesh = multimesh
if group_index < group_transforms.size():
multimesh_instance.transform = group_transforms[group_index]
_apply_render_settings(
multimesh_instance,
visibility_range_end,
visibility_range_end_margin,
cast_shadows
)
wmo_parent_root.add_child(multimesh_instance)
return multimesh_instance
func _apply_render_settings(
geometry_instance: GeometryInstance3D,
visibility_range_end: float,
visibility_range_end_margin: float,
cast_shadows: bool) -> void:
geometry_instance.cast_shadow = (
GeometryInstance3D.SHADOW_CASTING_SETTING_ON
if cast_shadows
else GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
)
if visibility_range_end > 0.0:
geometry_instance.visibility_range_end = visibility_range_end
geometry_instance.visibility_range_end_margin = visibility_range_end_margin
@@ -0,0 +1 @@
uid://cu4tw6868rbkm
@@ -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
@@ -0,0 +1,105 @@
class_name WmoRuntimeMeshFinalizer
extends RefCounted
## Refreshes cached WMO runtime Mesh materials through the configured builder.
## Scene traversal, placement and Node lifetime remain caller-owned.
const MATERIAL_REFRESH_VERSION := 10
const MATERIAL_REFRESH_VERSION_METADATA := "wow_wmo_material_refresh_version"
var _wmo_material_builder: Object
func _init(wmo_material_builder: Object) -> void:
_wmo_material_builder = wmo_material_builder
## Finalizes a stale Mesh in place and returns the exact input Resource identity.
## Null remains null; already-current meshes do not cross the builder boundary.
func finalize_mesh(mesh: Mesh, extracted_directory: String) -> Mesh:
if mesh == null:
return null
if int(mesh.get_meta(MATERIAL_REFRESH_VERSION_METADATA, 0)) >= MATERIAL_REFRESH_VERSION:
return mesh
mesh.set_meta(MATERIAL_REFRESH_VERSION_METADATA, MATERIAL_REFRESH_VERSION)
if not (mesh is ArrayMesh):
return mesh
var array_mesh := mesh as ArrayMesh
for surface_index in range(array_mesh.get_surface_count()):
var rebuilt_material := rebuild_cached_material(
array_mesh.surface_get_material(surface_index),
extracted_directory
)
if rebuilt_material != null:
array_mesh.surface_set_material(surface_index, rebuilt_material)
return mesh
## Rebuilds a material carrying the WMO cache metadata contract. Materials
## without texture0 metadata and failed builder results remain unchanged by the caller.
func rebuild_cached_material(
material: Material,
extracted_directory: String
) -> Material:
if (
material == null
or not material.has_meta("texture0_path")
or _wmo_material_builder == null
):
return null
var texture_paths := PackedStringArray()
var texture0_index := _append_texture_path(
texture_paths,
String(material.get_meta("texture0_path", ""))
)
var texture1_index := _append_texture_path(
texture_paths,
String(material.get_meta("texture1_path", ""))
)
var texture2_index := _append_texture_path(
texture_paths,
String(material.get_meta("texture2_path", ""))
)
var diffuse_color := Color.WHITE
var emissive_color := Color.BLACK
var secondary_color := Color.WHITE
if material is ShaderMaterial:
var shader_material := material as ShaderMaterial
var diffuse_value: Variant = shader_material.get_shader_parameter("diffuse_color")
var emissive_value: Variant = shader_material.get_shader_parameter("emissive_color")
var secondary_value: Variant = shader_material.get_shader_parameter("secondary_color")
if diffuse_value is Color:
diffuse_color = diffuse_value
if emissive_value is Color:
emissive_color = emissive_value
if secondary_value is Color:
secondary_color = secondary_value
var material_definition := {
"texture0": texture0_index,
"texture1": texture1_index,
"texture2": texture2_index,
"flags": int(material.get_meta("wow_flags", 0)),
"shader": int(material.get_meta("wow_shader", 0)),
"blend_mode": int(material.get_meta("wow_blend_mode", 0)),
"diffuse_color": diffuse_color,
"emissive_color": emissive_color,
"color2": secondary_color,
}
return _wmo_material_builder.call(
"_build_material",
material_definition,
texture_paths,
extracted_directory
) as Material
func _append_texture_path(texture_paths: PackedStringArray, texture_path: String) -> int:
if texture_path.is_empty():
return -1
var texture_index := texture_paths.size()
texture_paths.append(texture_path)
return texture_index
@@ -0,0 +1 @@
uid://bsxqkjuk77cgf
@@ -0,0 +1,80 @@
class_name WmoRuntimeScenePreparer
extends RefCounted
## Applies the existing cached/live WMO subtree preparation rules on the
## renderer main thread. Placement, attachment and subtree lifetime stay caller-owned.
var _runtime_mesh_finalizer: Object
func _init(runtime_mesh_finalizer: Object) -> void:
_runtime_mesh_finalizer = runtime_mesh_finalizer
## Prepares an instantiated cached WMO scene by finalizing every borrowed Mesh,
## then applying the historical direct Occluders-child and shadow policies.
## Returns false for a null/freed root without mutating or retaining anything.
func prepare_cached_instance(
instance: Node3D,
extracted_directory: String,
enable_occlusion_culling: bool,
cast_shadows: bool) -> bool:
if instance == null or not is_instance_valid(instance):
return false
_finalize_meshes_in_subtree(instance, extracted_directory)
_apply_runtime_render_policy(instance, enable_occlusion_culling, cast_shadows)
return true
## Prepares a duplicated live-built WMO without re-finalizing its Meshes.
## Returns false for a null/freed root without mutating or retaining anything.
func prepare_live_instance(
instance: Node3D,
enable_occlusion_culling: bool,
cast_shadows: bool) -> bool:
if instance == null or not is_instance_valid(instance):
return false
_apply_runtime_render_policy(instance, enable_occlusion_culling, cast_shadows)
return true
func _finalize_meshes_in_subtree(node: Node, extracted_directory: String) -> void:
if _runtime_mesh_finalizer != null:
if node is MeshInstance3D:
_runtime_mesh_finalizer.call(
"finalize_mesh",
(node as MeshInstance3D).mesh,
extracted_directory
)
elif node is MultiMeshInstance3D:
var multimesh := (node as MultiMeshInstance3D).multimesh
if multimesh != null:
_runtime_mesh_finalizer.call(
"finalize_mesh",
multimesh.mesh,
extracted_directory
)
for child in node.get_children():
_finalize_meshes_in_subtree(child, extracted_directory)
func _apply_runtime_render_policy(
instance: Node3D,
enable_occlusion_culling: bool,
cast_shadows: bool) -> void:
if not enable_occlusion_culling:
var occluders := instance.get_node_or_null("Occluders")
if occluders != null:
instance.remove_child(occluders)
occluders.queue_free()
if cast_shadows:
_enable_shadow_casting_recursive(instance)
func _enable_shadow_casting_recursive(node: Node) -> void:
if node is GeometryInstance3D:
(node as GeometryInstance3D).cast_shadow = (
GeometryInstance3D.SHADOW_CASTING_SETTING_ON
)
for child in node.get_children():
_enable_shadow_casting_recursive(child)
@@ -0,0 +1 @@
uid://d1t0vlkco8kw8
@@ -0,0 +1,73 @@
class_name WmoSceneInstanceFactory
extends RefCounted
## Creates detached cached/live WMO Node3D instances with the existing cache
## validation, naming and world-placement rules. Runtime preparation is separate.
var _scene_cache_validator: Object
var _placement_resolver: Object
func _init(scene_cache_validator: Object, placement_resolver: Object) -> void:
_scene_cache_validator = scene_cache_validator
_placement_resolver = placement_resolver
## Returns whether [param node] satisfies the injected WMO scene-cache contract.
## Null nodes or missing validators return false without mutation.
func is_cached_node_current(node: Node) -> bool:
if node == null or _scene_cache_validator == null:
return false
return bool(_scene_cache_validator.call("is_scene_cache_current", node))
## Instantiates, validates, names and places a cached WMO PackedScene. Rejected
## instantiated roots are freed synchronously. The accepted detached Node3D is
## caller-owned and retains its exact descendant Resource identities.
func instantiate_cached_scene(
relative_path: String,
scene: PackedScene,
placement: Dictionary) -> Node3D:
if scene == null or _placement_resolver == null:
return null
var instantiated_root := scene.instantiate()
if not (instantiated_root is Node3D):
if instantiated_root != null:
instantiated_root.free()
return null
var instance := instantiated_root as Node3D
if not is_cached_node_current(instance):
instance.free()
return null
_apply_identity_and_placement(instance, relative_path, placement)
return instance
## Duplicates, names and places a live-built WMO prototype. The detached result
## is caller-owned. Null inputs, missing placement composition or an unexpected
## non-Node3D duplicate return null; rejected duplicates are freed synchronously.
func duplicate_live_prototype(
relative_path: String,
prototype: Node3D,
placement: Dictionary) -> Node3D:
if prototype == null or _placement_resolver == null:
return null
var duplicated_root := prototype.duplicate()
if not (duplicated_root is Node3D):
if duplicated_root != null:
duplicated_root.free()
return null
var instance := duplicated_root as Node3D
_apply_identity_and_placement(instance, relative_path, placement)
return instance
func _apply_identity_and_placement(
instance: Node3D,
relative_path: String,
placement: Dictionary) -> void:
instance.name = relative_path.get_file().get_basename()
instance.transform = _placement_resolver.call(
"resolve_world_transform",
placement
) as Transform3D
@@ -0,0 +1 @@
uid://c13w66d7uaf2n
@@ -0,0 +1,97 @@
class_name WmoSceneResourceFinalizer
extends RefCounted
## Polls cached WMO PackedScene requests and publishes validated outcomes.
## File-size admission, live fallback and attached Node lifetime remain caller-owned.
var _scene_cache_validator: Object
var _resource_loader_adapter: Object
func _init(
scene_cache_validator: Object,
resource_loader_adapter: Object = null
) -> void:
_scene_cache_validator = scene_cache_validator
_resource_loader_adapter = resource_loader_adapter
## Polls a detached pending snapshot in insertion order. Terminal requests are
## published as the exact validated PackedScene or the historical missing state.
func poll_terminal_requests(scene_resource_cache_state: RefCounted) -> int:
if scene_resource_cache_state == null:
return 0
var completed_request_count := 0
var request_paths: Dictionary = scene_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:
scene_resource_cache_state.call(
"complete_request_as_missing",
normalized_relative_path
)
completed_request_count += 1
continue
var loaded_resource := load_threaded_get(resource_path)
var loaded_scene := loaded_resource as PackedScene
if is_scene_cache_current(loaded_scene):
scene_resource_cache_state.call(
"complete_request_with_scene",
normalized_relative_path,
loaded_scene
)
else:
scene_resource_cache_state.call(
"complete_request_as_missing",
normalized_relative_path
)
completed_request_count += 1
return completed_request_count
## Instantiates one Node3D probe, delegates the existing WMOBuilder metadata
## check and synchronously releases the probe before returning.
func is_scene_cache_current(scene: PackedScene) -> bool:
if scene == null or _scene_cache_validator == null:
return false
var instantiated_node := scene.instantiate()
if not (instantiated_node is Node3D):
if instantiated_node != null:
instantiated_node.free()
return false
var instance := instantiated_node as Node3D
var is_current := bool(_scene_cache_validator.call(
"is_scene_cache_current",
instance
))
instance.free()
return is_current
## 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://cp2r3tadn8l6q
+44 -16
View File
@@ -26,11 +26,13 @@ func setup(target_mesh_instance: MeshInstance3D, bone_data: Array, surface_data:
_capture_materials() _capture_materials()
_make_mesh_unique() _make_mesh_unique()
_rebuild_mesh(0.0) _rebuild_mesh(0.0)
set_process(mesh != null and not bones.is_empty() and not surfaces.is_empty() and animation_length > 0.0) _prepared = _has_runtime_animation_data()
set_process(_prepared)
func _ready() -> void: func _ready() -> void:
prepare_runtime() if not _prepared:
prepare_runtime()
func _process(delta: float) -> void: func _process(delta: float) -> void:
@@ -41,24 +43,49 @@ func _process(delta: float) -> void:
func set_phase(phase: float) -> void: func set_phase(phase: float) -> void:
if animation_length <= 0.0: _set_phase_time(phase)
_time = 0.0
else:
_time = fposmod(animation_length * phase, animation_length)
_rebuild_mesh(_time) _rebuild_mesh(_time)
func prepare_runtime() -> bool: func prepare_runtime() -> bool:
return _prepare_runtime(false)
## Rebinds a duplicated animator to its local mesh and applies its deterministic
## phase with one deformation rebuild. This must happen before attachment so
## _ready() can remain idempotent for already prepared runtime instances.
func prepare_runtime_at_phase(phase: float) -> bool:
_set_phase_time(phase)
return _prepare_runtime(true)
func _prepare_runtime(force_rebuild: bool) -> bool:
if _prepared and not force_rebuild:
set_process(true)
return true
_resolve_mesh_instance() _resolve_mesh_instance()
if force_rebuild:
_materials.clear()
_capture_materials() _capture_materials()
_unique_mesh_ready = false _unique_mesh_ready = false
_make_mesh_unique() _make_mesh_unique()
_rebuild_mesh(_time) _rebuild_mesh(_time)
_prepared = mesh != null and not bones.is_empty() and not surfaces.is_empty() and animation_length > 0.0 _prepared = _has_runtime_animation_data()
set_process(_prepared) set_process(_prepared)
return _prepared return _prepared
func _has_runtime_animation_data() -> bool:
return mesh != null and not bones.is_empty() and not surfaces.is_empty() and animation_length > 0.0
func _set_phase_time(phase: float) -> void:
if animation_length <= 0.0:
_time = 0.0
else:
_time = fposmod(animation_length * phase, animation_length)
func runtime_debug_state() -> Dictionary: func runtime_debug_state() -> Dictionary:
return { return {
"prepared": _prepared, "prepared": _prepared,
@@ -81,11 +108,12 @@ func _resolve_mesh_instance() -> void:
func _make_mesh_unique() -> void: func _make_mesh_unique() -> void:
if _unique_mesh_ready or mesh_instance == null or mesh_instance.mesh == null: if _unique_mesh_ready or mesh_instance == null or mesh_instance.mesh == null:
return return
var duplicated := mesh_instance.mesh.duplicate(true) as ArrayMesh # _rebuild_mesh() replaces every surface from the retained native arrays, so
if duplicated == null: # copying the source ArrayMesh would only duplicate data that is discarded.
return # Materials were captured before this call and are intentionally shared.
mesh_instance.mesh = duplicated var instance_mesh := ArrayMesh.new()
mesh = duplicated mesh_instance.mesh = instance_mesh
mesh = instance_mesh
_unique_mesh_ready = true _unique_mesh_ready = true
@@ -143,20 +171,20 @@ func _rebuild_mesh(time: float) -> void:
continue continue
var transform: Transform3D = bone_matrices[bone_index] var transform: Transform3D = bone_matrices[bone_index]
skinned_pos += transform * base_vertices[vertex_index] * weight skinned_pos += transform * base_vertices[vertex_index] * weight
if normals.size() == base_normals.size(): if not normals.is_empty():
skinned_nrm += (transform.basis * base_normals[vertex_index]) * weight skinned_nrm += (transform.basis * base_normals[vertex_index]) * weight
total_weight += weight total_weight += weight
if total_weight > 0.0: if total_weight > 0.0:
vertices[vertex_index] = skinned_pos / total_weight vertices[vertex_index] = skinned_pos / total_weight
if normals.size() == base_normals.size(): if not normals.is_empty():
normals[vertex_index] = (skinned_nrm / total_weight).normalized() normals[vertex_index] = (skinned_nrm / total_weight).normalized()
else: else:
vertices[vertex_index] = base_vertices[vertex_index] vertices[vertex_index] = base_vertices[vertex_index]
if normals.size() == base_normals.size(): if not normals.is_empty():
normals[vertex_index] = base_normals[vertex_index] normals[vertex_index] = base_normals[vertex_index]
else: else:
vertices[vertex_index] = base_vertices[vertex_index] vertices[vertex_index] = base_vertices[vertex_index]
if normals.size() == base_normals.size(): if not normals.is_empty():
normals[vertex_index] = base_normals[vertex_index] normals[vertex_index] = base_normals[vertex_index]
var arrays := [] var arrays := []
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -176,14 +176,14 @@ func _bake_glb_animation_cache(
if not force and FileAccess.file_exists(abs_out_glb): if not force and FileAccess.file_exists(abs_out_glb):
return true return true
var abs_converter := ProjectSettings.globalize_path(converter) var abs_converter := ProjectSettings.globalize_path(converter)
var abs_output := ProjectSettings.globalize_path(output_dir) var converter_output_directory := abs_out_glb.get_base_dir()
if not FileAccess.file_exists(abs_converter): if not FileAccess.file_exists(abs_converter):
push_warning("M2 GLB converter not found: %s" % converter) push_warning("M2 GLB converter not found: %s" % converter)
return false return false
var stdout := [] var stdout := []
var exit_code := OS.execute( var exit_code := OS.execute(
python_exe, python_exe,
[abs_converter, abs_m2, abs_output], [abs_converter, abs_m2, converter_output_directory],
stdout, stdout,
true, true,
false) false)
+30 -10
View File
@@ -5,6 +5,9 @@ extends SceneTree
const FINALIZER_SCRIPT := preload("res://src/render/m2/m2_animated_scene_finalizer.gd") const FINALIZER_SCRIPT := preload("res://src/render/m2/m2_animated_scene_finalizer.gd")
const FINALIZER_PATH := "res://src/render/m2/m2_animated_scene_finalizer.gd" const FINALIZER_PATH := "res://src/render/m2/m2_animated_scene_finalizer.gd"
const RESOURCE_FINALIZER_PATH := (
"res://src/render/m2/m2_animation_resource_finalizer.gd"
)
const MATERIALIZER_PATH := "res://src/render/m2/m2_animated_instance_materializer.gd" const MATERIALIZER_PATH := "res://src/render/m2/m2_animated_instance_materializer.gd"
const LOADER_PATH := "res://src/scenes/streaming/streaming_world_loader.gd" const LOADER_PATH := "res://src/scenes/streaming/streaming_world_loader.gd"
@@ -116,6 +119,9 @@ func _verify_material_mapping(failures: Array[String]) -> void:
func _verify_ownership_boundaries(failures: Array[String]) -> void: func _verify_ownership_boundaries(failures: Array[String]) -> void:
var finalizer_source := FileAccess.get_file_as_string(FINALIZER_PATH) var finalizer_source := FileAccess.get_file_as_string(FINALIZER_PATH)
var resource_finalizer_source := FileAccess.get_file_as_string(
RESOURCE_FINALIZER_PATH
)
var materializer_source := FileAccess.get_file_as_string(MATERIALIZER_PATH) var materializer_source := FileAccess.get_file_as_string(MATERIALIZER_PATH)
var loader_source := FileAccess.get_file_as_string(LOADER_PATH) var loader_source := FileAccess.get_file_as_string(LOADER_PATH)
_expect_true(loader_source.contains("M2_ANIMATED_SCENE_FINALIZER_SCRIPT.new()"), "loader composes finalizer", failures) _expect_true(loader_source.contains("M2_ANIMATED_SCENE_FINALIZER_SCRIPT.new()"), "loader composes finalizer", failures)
@@ -126,27 +132,41 @@ func _verify_ownership_boundaries(failures: Array[String]) -> void:
]: ]:
_expect_false(loader_source.contains(removed_loader_function), "legacy helper removed: %s" % removed_loader_function, failures) _expect_false(loader_source.contains(removed_loader_function), "legacy helper removed: %s" % removed_loader_function, failures)
for delegated_call in [ for delegated_call in [
"_m2_animated_scene_finalizer.instantiate_candidate(resource)", "\"instantiate_candidate\"",
"_m2_animated_scene_finalizer.repair_materials(candidate, material_source)", "\"repair_materials\"",
"_m2_animated_scene_finalizer.finalize_candidate(candidate)", "\"finalize_candidate\"",
"_m2_animated_scene_finalizer.mesh_instances_in_subtree(root)",
]: ]:
_expect_equal(loader_source.count(delegated_call), 1, "single loader delegation: %s" % delegated_call, failures) _expect_equal(
resource_finalizer_source.count(delegated_call),
1,
"single resource-finalizer delegation: %s" % delegated_call,
failures
)
_expect_equal(
loader_source.count("_m2_animated_scene_finalizer.mesh_instances_in_subtree(root)"),
1,
"single loader mesh traversal delegation",
failures
)
_expect_equal( _expect_equal(
materializer_source.count("_animated_scene_finalizer.animation_players_in_subtree("), materializer_source.count("_animated_scene_finalizer.animation_players_in_subtree("),
1, 1,
"single materializer player-inventory delegation", "single materializer player-inventory delegation",
failures failures
) )
for retained_loader_rule in [ for retained_renderer_rule in [
"ResourceLoader.load_threaded_get(path)", "ResourceLoader.load_threaded_get(resource_path)",
"RENDER_BUDGET_SCHEDULER_SCRIPT.M2_ANIMATION_FINALIZE", "RENDER_BUDGET_SCHEDULER_SCRIPT.M2_ANIMATION_FINALIZE",
"_get_or_load_m2_material_prototype(normalized_rel)", "_get_or_load_m2_material_prototype(normalized_rel)",
"_m2_prototype_cache_state.adopt_animated_prototype(", "\"adopt_animated_prototype\"",
"_m2_prototype_cache_state.mark_animation_static(normalized_rel)", "\"mark_animation_static\"",
"M2_ANIM_CACHE path=%s cache=%s players=%d", "M2_ANIM_CACHE path=%s cache=%s players=%d",
]: ]:
_expect_true(loader_source.contains(retained_loader_rule), "loader retains %s" % retained_loader_rule, failures) _expect_true(
(loader_source + resource_finalizer_source).contains(retained_renderer_rule),
"renderer retains %s" % retained_renderer_rule,
failures
)
for forbidden_dependency in [ for forbidden_dependency in [
"ResourceLoader.", "ResourceLoader.",
"FileAccess.", "FileAccess.",
@@ -5,6 +5,12 @@ extends SceneTree
const PIPELINE_SCRIPT := preload("res://src/render/m2/m2_animation_load_pipeline_state.gd") const PIPELINE_SCRIPT := preload("res://src/render/m2/m2_animation_load_pipeline_state.gd")
const PIPELINE_PATH := "res://src/render/m2/m2_animation_load_pipeline_state.gd" const PIPELINE_PATH := "res://src/render/m2/m2_animation_load_pipeline_state.gd"
const OBSERVER_PATH := (
"res://src/render/m2/m2_cached_animation_resource_observer.gd"
)
const RESOURCE_FINALIZER_PATH := (
"res://src/render/m2/m2_animation_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"
@@ -80,21 +86,33 @@ func _verify_discard_metrics_clear_and_diagnostics(failures: Array[String]) -> v
func _verify_ownership_boundaries(failures: Array[String]) -> void: func _verify_ownership_boundaries(failures: Array[String]) -> void:
var pipeline_source := FileAccess.get_file_as_string(PIPELINE_PATH) var pipeline_source := FileAccess.get_file_as_string(PIPELINE_PATH)
var observer_source := FileAccess.get_file_as_string(OBSERVER_PATH)
var resource_finalizer_source := FileAccess.get_file_as_string(
RESOURCE_FINALIZER_PATH
)
var loader_source := FileAccess.get_file_as_string(LOADER_PATH) var loader_source := FileAccess.get_file_as_string(LOADER_PATH)
_expect_true(loader_source.contains("M2_ANIMATION_LOAD_PIPELINE_STATE_SCRIPT.new()"), "loader composes pipeline state", failures) _expect_true(loader_source.contains("M2_ANIMATION_LOAD_PIPELINE_STATE_SCRIPT.new()"), "loader composes pipeline state", failures)
_expect_false(loader_source.contains("var _m2_animation_load_requests:"), "legacy request field removed", failures) _expect_false(loader_source.contains("var _m2_animation_load_requests:"), "legacy request field removed", failures)
_expect_false(loader_source.contains("var _m2_animation_finalize_queue:"), "legacy finalize field removed", failures) _expect_false(loader_source.contains("var _m2_animation_finalize_queue:"), "legacy finalize field removed", failures)
_expect_equal(loader_source.count("_m2_animation_load_pipeline_state.total_work_count()"), 3, "three existing metrics delegate", failures) _expect_equal(loader_source.count("_m2_animation_load_pipeline_state.total_work_count()"), 3, "three existing metrics delegate", failures)
_expect_equal(loader_source.count("_m2_animation_load_pipeline_state.clear()"), 2, "two existing clear sites delegate", failures) _expect_equal(loader_source.count("_m2_animation_load_pipeline_state.clear()"), 2, "two existing clear sites delegate", failures)
for retained_loader_rule in [ _expect_true(
"ResourceLoader.load_threaded_request(", observer_source.contains("ResourceLoader.load_threaded_request("),
"ResourceLoader.load_threaded_get_status(path)", "cached observer owns request admission",
"ResourceLoader.load_threaded_get(path)", failures
)
for retained_renderer_rule in [
"ResourceLoader.load_threaded_get_status(resource_path)",
"ResourceLoader.load_threaded_get(resource_path)",
"RENDER_BUDGET_SCHEDULER_SCRIPT.M2_ANIMATION_FINALIZE", "RENDER_BUDGET_SCHEDULER_SCRIPT.M2_ANIMATION_FINALIZE",
"_m2_prototype_cache_state.adopt_animated_prototype(", "\"adopt_animated_prototype\"",
"_m2_prototype_cache_state.mark_animation_static(", "\"mark_animation_static\"",
]: ]:
_expect_true(loader_source.contains(retained_loader_rule), "loader retains %s" % retained_loader_rule, failures) _expect_true(
(loader_source + resource_finalizer_source).contains(retained_renderer_rule),
"renderer retains %s" % retained_renderer_rule,
failures
)
for forbidden_dependency in [ for forbidden_dependency in [
"ResourceLoader.", "ResourceLoader.",
"WorkerThreadPool.", "WorkerThreadPool.",

Some files were not shown because too many files have changed in this diff Show More