Compare commits

..

128 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
sindoring c74b90a8ea targets: accept M2 build queue 2026-07-18 02:25:14 +04:00
sindoring 1dc013e5c2 merge: M2 build queue 2026-07-18 02:24:00 +04:00
sindoring 8938350a59 coordination: hand off M2 build queue 2026-07-18 02:23:53 +04:00
sindoring 4a8338f2f0 render: extract M2 build queue 2026-07-18 02:23:40 +04:00
sindoring 3ee9e77422 coordination: claim M2 build queue 2026-07-18 02:13:15 +04:00
sindoring 81e33a9bc4 targets: accept static M2 batch materializer 2026-07-18 02:02:10 +04:00
sindoring 5e82daacb9 merge: static M2 batch materializer 2026-07-18 02:01:15 +04:00
sindoring 8339189907 coordination: hand off static M2 batch materializer 2026-07-18 02:01:09 +04:00
sindoring 252927d90c render: extract static M2 batch materializer 2026-07-18 02:00:55 +04:00
sindoring 0fd052923d coordination: claim static M2 batch materializer 2026-07-18 01:54:26 +04:00
sindoring 86814deca1 targets: accept animated M2 materializer 2026-07-18 01:50:05 +04:00
sindoring c251985256 merge: animated M2 instance materializer 2026-07-18 01:49:17 +04:00
sindoring 899a9ae012 coordination: hand off animated M2 materializer 2026-07-18 01:49:08 +04:00
sindoring 456f3e2334 render: extract animated M2 materializer 2026-07-18 01:48:55 +04:00
sindoring 9ab4c0762d coordination: claim animated M2 materializer 2026-07-18 01:39:44 +04:00
sindoring 5b2d9f23fe docs(M03): accept M2 animation playback 2026-07-18 01:27:33 +04:00
sindoring c0fc191bcd merge M03 M2 animation playback 2026-07-18 01:26:18 +04:00
sindoring 15c24692f0 coord(M03): hand off M2 animation playback 2026-07-18 01:26:10 +04:00
sindoring 9b73571dcb refactor(M03): extract M2 animation playback 2026-07-18 01:25:54 +04:00
sindoring 5be36376c2 coord(M03): claim M2 animation playback 2026-07-18 01:19:13 +04:00
sindoring c0a7bfbd8d docs(M03): accept M2 animated scene finalizer 2026-07-18 01:16:19 +04:00
sindoring e7a7c67f6d merge M03 M2 animated scene finalizer 2026-07-18 01:14:40 +04:00
sindoring bc2bd06abc coord(M03): hand off M2 animated scene finalizer 2026-07-18 01:14:32 +04:00
sindoring 1b450dc580 refactor(M03): extract M2 animated scene finalizer 2026-07-18 01:14:17 +04:00
sindoring 03ba129a52 coord(M03): claim M2 animated scene finalizer 2026-07-18 01:07:09 +04:00
sindoring c20bc62ac0 docs(M03): accept M2 animation load pipeline 2026-07-17 18:25:20 +04:00
sindoring 3dccd3e3ee merge M03 M2 animation load pipeline 2026-07-17 18:24:16 +04:00
sindoring 672e0ce2a5 coord(M03): hand off M2 animation load pipeline 2026-07-17 18:24:10 +04:00
sindoring b8e0f11ed5 refactor(M03): extract M2 animation load pipeline state 2026-07-17 18:23:55 +04:00
sindoring 2695afcdba coord(M03): claim M2 animation load pipeline 2026-07-17 18:16:59 +04:00
sindoring 20a64b5dfc docs(M03): accept M2 prototype cache state 2026-07-17 17:07:27 +04:00
sindoring 1de50f98ce merge M03 M2 prototype cache state 2026-07-17 17:00:59 +04:00
sindoring 5e828aaf01 docs(M03): hand off M2 prototype cache state 2026-07-17 16:29:28 +04:00
sindoring cd664eb4cb refactor(M03): extract M2 prototype cache state 2026-07-17 16:29:11 +04:00
sindoring 87bf067493 coord(M03): claim M2 prototype cache state 2026-07-17 16:18:29 +04:00
sindoring ca77d32be1 docs(M03): accept raw M2 model repository 2026-07-17 15:54:59 +04:00
sindoring 7cd07dda4f merge M03 raw M2 model repository 2026-07-17 15:53:54 +04:00
sindoring 8605b43b1f docs(M03): hand off raw M2 model repository 2026-07-17 15:53:39 +04:00
sindoring b608a81766 refactor(M03): extract raw M2 model repository 2026-07-17 15:53:20 +04:00
sindoring e4bb10cee7 coord(M03): claim raw M2 model repository
Work-Package: M03-RND-M2-RAW-MODEL-REPOSITORY-001
Agent: sindo-main-codex
Tests: coordination gate passed before claim
Fidelity: native-boundary extraction only; parser methods and paths unchanged
2026-07-17 15:44:15 +04:00
sindoring 1d94eace68 docs(M03): accept M2 runtime mesh finalizer
Work-Package: M03-RND-M2-RUNTIME-MESH-FINALIZER-001
Agent: sindo-main-codex
Tests: post-merge finalizer, classifier, extractor, cache, pipeline, materials, shutdown, facade, internal-access, manifest, documentation and coordination gates pass
Fidelity: preserves refresh version 2 and rebuild/fallback transitions; asset-backed visual parity remains unclaimed
2026-07-17 15:03:48 +04:00
sindoring 1fafddecb6 merge M03 M2 runtime mesh finalizer 2026-07-17 15:02:28 +04:00
sindoring 0007a69769 docs(M03): hand off M2 runtime mesh finalizer
Work-Package: M03-RND-M2-RUNTIME-MESH-FINALIZER-001
Agent: sindo-main-codex
Tests: 37 headless regressions, checkpoint dry-run, documentation and coordination gates pass
Fidelity: refresh/rebuild/fallback extraction only; asset-backed visual parity remains unclaimed
2026-07-17 15:02:16 +04:00
sindoring ece7724a28 refactor(M03): extract M2 runtime mesh finalizer
Work-Package: M03-RND-M2-RUNTIME-MESH-FINALIZER-001
Agent: sindo-main-codex
Tests: 37 headless renderer/coordinate contracts pass; checkpoint dry-run 7/7; documentation and coordination gates pass
Fidelity: preserves refresh version 2, classifier, rebuild and fallback transitions; no visual parity claim
2026-07-17 15:01:52 +04:00
sindoring 951bd54fff coord(M03): claim M2 runtime mesh finalizer
Work-Package: M03-RND-M2-RUNTIME-MESH-FINALIZER-001
Agent: sindo-main-codex
Tests: coordination gate passed before claim
Fidelity: extraction-only package; refresh version and visible behavior unchanged
2026-07-17 14:54:16 +04:00
sindoring dd8cfbf79c docs(M03): accept M2 mesh resource extractor
Work-Package: M03-RND-M2-MESH-RESOURCE-EXTRACTOR-001
Agent: sindo-main-codex
Tests: post-merge extractor, cache, pipeline, classifier, materials, shutdown, facade, internal-access, manifest, documentation and coordination gates pass
Fidelity: preserves first-Mesh preorder and temporary PackedScene cleanup; asset-backed visual parity remains unclaimed
2026-07-17 13:04:17 +04:00
sindoring fa85985ec5 merge M03 M2 mesh resource extractor 2026-07-17 13:03:11 +04:00
sindoring f68e971cce docs(M03): hand off M2 mesh resource extractor
Work-Package: M03-RND-M2-MESH-RESOURCE-EXTRACTOR-001
Agent: sindo-main-codex
Tests: 36 headless regressions, checkpoint dry-run, documentation and coordination gates pass
Fidelity: traversal/lifetime extraction only; asset-backed visual parity remains unclaimed
2026-07-17 13:02:58 +04:00
sindoring 7b0e1eac5f refactor(M03): extract M2 mesh resource traversal
Work-Package: M03-RND-M2-MESH-RESOURCE-EXTRACTOR-001
Agent: sindo-main-codex
Tests: 36 headless renderer/coordinate contracts pass; checkpoint dry-run 7/7; documentation and coordination gates pass
Fidelity: preserves first-Mesh preorder and temporary PackedScene cleanup; no visual parity claim
2026-07-17 13:02:40 +04:00
sindoring 6607100686 coord(M03): claim M2 mesh resource extractor
Work-Package: M03-RND-M2-MESH-RESOURCE-EXTRACTOR-001
Agent: sindo-main-codex
Tests: coordination gate passed before claim
Fidelity: extraction-only package; traversal and visible behavior unchanged
2026-07-17 12:57:38 +04:00
208 changed files with 19874 additions and 1260 deletions
+28
View File
@@ -5,3 +5,31 @@
path = third_party/godot-cpp
url = https://github.com/godotengine/godot-cpp
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
+367 -15
View File
@@ -32,12 +32,29 @@ Paired run 2026-07-11 подтвердил крупный coordinate/placement g
- `src/render/wmo/wmo_render_build_step_planner.gd` - mesh-first lightweight WMO group operation and cursor planning without Nodes or Resources.
- `src/render/wmo/wmo_render_build_queue.gd` / `wmo_render_build_job.gd` - typed pending group jobs, FIFO placement keys and strong root/resource references without engine destruction.
- `src/render/wmo/wmo_render_resource_cache_state.gd` - validated lightweight WMO render Resources, negative cache and pending cache paths without ResourceLoader I/O.
- `src/render/wmo/wmo_render_resource_finalizer.gd` - lightweight WMO terminal polling, script/format validation and Resource/missing publication.
- `src/render/wmo/wmo_scene_resource_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/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/m2/m2_runtime_mesh_rebuild_classifier.gd` - memoized billboard/UV-rotation decision for stale cached M2 runtime mesh refresh.
- `src/render/m2/m2_animated_scene_finalizer.gd` - animated PackedScene candidate ownership, historical material repair and AnimationPlayer validation.
- `src/render/m2/m2_animation_playback_controller.gd` - deterministic per-instance phase, default animation selection and native/imported playback mutation.
- `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_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_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_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_runtime_mesh_finalizer.gd` - refresh version, classifier lifetime, M2Builder rebuild and original-Mesh fallback.
- `src/render/m2/m2_prototype_cache_state.gd` - shutdown-lifetime static/animated prototype Nodes and missing/static-only path outcomes.
- `src/scenes/streaming/eastern_kingdoms_streaming.tscn` - текущая сцена для проверки Azeroth/Eastern Kingdoms.
- `addons/mpq_extractor/loaders/adt_builder.gd` - сборка ADT terrain, control splat материалов и MH2O liquids.
- `addons/mpq_extractor/loaders/m2_builder.gd` - сборка статического M2 mesh/material.
@@ -316,7 +333,7 @@ Native M2 animation first pass for composite doodads:
- `M2NativeAnimator` evaluates the selected Stand sequence and applies WoW-style bone matrices:
`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.
- `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.
Причина: композитные doodad вроде `GryphonRoost` снова ломают визуал при GLB-анимации. До M2-native renderer все world placement M2 должны оставаться статическими. GLB-анимация оставлена только как вручную включаемый debug experiment через allowlist.
@@ -669,6 +686,42 @@ SKYBOX_MODEL ...
- полноценный liquid rendering там не реализован;
- 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:
- старый клиент не рендерил все как modern physically based renderer;
@@ -806,7 +859,12 @@ $exe = Join-Path $env:TEMP 'godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe'
- Billboard metadata is resolved per drawn vertex through the skin section bone palette and vertex bone indices. This avoids treating an entire mixed surface as billboard just because the palette contains a billboard bone.
- `M2Builder` stores billboard pivot/mode in `Mesh.ARRAY_CUSTOM0` as RGBA float data. `CUSTOM0.xyz` is the Godot-space bone pivot; `CUSTOM0.w` is the billboard mode: spherical, lock X, lock Y, or lock Z.
- `WowM2Material` applies camera-facing billboarding in the vertex shader for marked vertices. This works with grouped ADT M2 `MultiMesh` batches because the shader uses each instance transform.
- Runtime M2 cache refresh now has `M2_MATERIAL_REFRESH_VERSION`. Old cached `.tscn/.glb` meshes are replaced in memory from raw `.m2` only when the raw model actually needs custom billboard attributes or UV-rotation data. Do not rebuild every stale cached M2 during streaming: creating fresh materials for ordinary props can exhaust the D3D12 resource descriptor heap.
- Runtime M2 cache refresh uses material refresh version `2` (now
`M2RuntimeMeshFinalizer.MATERIAL_REFRESH_VERSION`). Old cached `.tscn/.glb`
meshes are replaced in memory from raw `.m2` only when the raw model actually
needs custom billboard attributes or UV-rotation data. Do not rebuild every
stale cached M2 during streaming: creating fresh materials for ordinary props
can exhaust the D3D12 resource descriptor heap.
- Verified with `ElwynnGrass1.m2`: native data exposes a billboard batch, the built mesh contains `CUSTOM0`, and the M2 shader compiles with the billboard uniform enabled.
- Remaining parity work: many Elwynn tree canopy M2s do not carry billboard bone flags, so their flat-looking leaves are a separate material/geometry parity issue, not this bone-billboard path.
@@ -816,7 +874,8 @@ $exe = Join-Path $env:TEMP 'godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe'
- Texture transform dictionaries now expose static UV rotation matrices and approximate Z-rotation speeds, in addition to existing translation, translation speed and scale.
- `WowM2Material` applies UV rotation and scale around WoW's texture center `(0.5, 0.5)`, then applies translation/scroll. This is closer to the client path shown by `CM2Model::AnimateTextureTransformsMT`.
- All four M2 texture stages receive UV rotation and rotation-speed uniforms, matching the existing four-stage texture binding path.
- `M2_MATERIAL_REFRESH_VERSION` and `M2Builder.MATERIAL_FORMAT_VERSION` are bumped to `2`, so cached M2 meshes are rebuilt in memory when raw `.m2` data is available.
- Runtime M2 material refresh and `M2Builder.MATERIAL_FORMAT_VERSION` are `2`,
so stale cached M2 meshes can be rebuilt in memory when raw `.m2` data is available.
- Verified with `Spells/ArcaneForceShield_Blue.m2`: native data exposes a non-identity UV rotation, the built material receives the stage rotation uniform, and the shader compiles.
## 2026-07-08 Renderer Material Regression Smoke
@@ -1037,11 +1096,45 @@ $exe = Join-Path $env:TEMP 'godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe'
remaining-count capping and next-offset/group-completion calculation.
- Existing animated/static limits still clamp to at least one; resource readiness,
queue rotation, serial progression and `M2_BUILD` permit consumption are unchanged.
- The loader retains build jobs/queues, tile cancellation, animation/mesh caches,
retries and all Node/MultiMesh/Mesh/material/RID finalization.
- `M2BuildQueue` now retains typed pending jobs, FIFO/stale keys and cursors.
The loader retains tile cancellation, resource readiness/retries and all
Node/MultiMesh/Mesh/material/RID finalization.
- Cache formats, quality profiles, batching output and visible rules are unchanged.
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
- `M2RuntimeMeshRebuildClassifier` now owns the memoized decision used when a
@@ -1052,9 +1145,9 @@ $exe = Join-Path $env:TEMP 'godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe'
- Invalid batch variants and combo/transform indices remain skipped. The first
decision for a normalized path remains cached until the same two historical
map/shutdown clear sites.
- `StreamingWorldLoader` still loads raw M2 data, checks
`M2_MATERIAL_REFRESH_VERSION`, calls `M2Builder`, adopts/falls back to Meshes
and owns ResourceLoader/finalization budgets.
- `StreamingWorldLoader` still requests raw M2 data and owns ResourceLoader,
returned-Mesh adoption and finalization budgets. Refresh-version checks,
M2Builder rebuild and fallback now belong to `M2RuntimeMeshFinalizer`.
- No cache version, shader, material, geometry or visible rule changed. Synthetic
predicate timing is not asset-backed descriptor-pressure/p95/p99 evidence.
@@ -1074,6 +1167,108 @@ $exe = Join-Path $env:TEMP 'godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe'
- Cache formats, material refresh, profiles and visible output are unchanged.
Synthetic state timing is not asset-backed I/O, leak or p95/p99 evidence.
## 2026-07-17 M2 Animation Load Pipeline State Extraction
- `M2AnimationLoadPipelineState` now owns animated M2 successful threaded-load
request records and the FIFO populated at loaded/failed terminal transitions.
- Pending snapshots preserve insertion order, while finalization follows completion
order. All three historical `m2_animation` metrics still count both stages.
- Shutdown still drains pending ResourceLoader paths before clear; map reset and
shutdown preserve the same two clear sites.
- At this extraction stage, `StreamingWorldLoader` retained GLB eligibility/cache selection, every
ResourceLoader call, `M2_ANIMATION_FINALIZE` permits, scene instantiation,
material repair and prototype/static-fallback decisions.
- Cache formats, animation behavior and visible output are unchanged. Synthetic
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
- `M2AnimatedSceneFinalizer` now owns terminal animated PackedScene candidate
instantiation, depth-first Mesh/AnimationPlayer traversal, static-prototype
material override mapping and rejected detached-root destruction.
- Material repair retains source/target depth-first ordering, source-index clamp,
per-surface override priority and first discovered material fallback.
- A candidate still requires at least one AnimationPlayer. Accepted exact Node3D
and player count return to the loader; invalid candidates are freed, including
the previously unhandled non-Node3D imported-root case.
- `StreamingWorldLoader` retains ResourceLoader I/O/statuses, finalize permits,
material-prototype selection/loading, prototype adoption/static fallback,
animation playback policy and diagnostic logging.
- Cache formats, profiles and visible rules are unchanged. Synthetic scene and
material fixtures are not asset-backed animation-fidelity or p95/p99 evidence.
## 2026-07-18 M2 Animation Playback Controller Extraction
- `M2AnimationPlaybackController` now owns the stable path/index phase formula,
ordinary/fish/bird animation-name priority, substring/first-name fallbacks and
AnimationPlayer linear-loop/play/seek mutation.
- Exact-script native animators retain depth-first pairing, the same five copied
fields, optional `prepare_runtime`, phase assignment and debug-state sampling.
- Native debug records are detached and sampled only when `debug_streaming` is
enabled; `StreamingWorldLoader` retains path normalization and exact log text.
- Loader still duplicates/transforms/attaches instances, applies visibility,
shadows and Editor ownership, owns build cursors/permits and obtains player inventory.
- Cache formats, playback priorities, phase behavior and visible rules are unchanged.
Synthetic timing is not asset-backed animation fidelity or p95/p99 evidence.
## 2026-07-17 M2 Mesh Resource Cache State Extraction
- `M2MeshResourceCacheState` now owns normalized-path references to prepared
@@ -1082,11 +1277,69 @@ $exe = Join-Path $env:TEMP 'godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe'
- The cache retains map-refresh lifetime and clears only at the existing final
runtime-cache shutdown site after asynchronous work is drained.
- `StreamingWorldLoader` still owns normalization, `.tscn`/`.glb` selection,
ResourceLoader I/O, Mesh extraction, material refresh/rebuild, shared missing
and prototype/animated caches, permits and MultiMesh materialization.
ResourceLoader I/O, Mesh extraction, material refresh/rebuild, cache adoption
decisions, permits and MultiMesh materialization. Prototype/negative state is
now a sibling cache service.
- Cache formats, refresh metadata, profiles and visible output are unchanged.
Synthetic cache timing is not asset-backed memory/leak or p95/p99 evidence.
## 2026-07-17 M2 Mesh Resource Extractor
- `M2MeshResourceExtractor` now selects the first Mesh from direct Mesh
Resources, temporary PackedScene instances and existing prototype/rebuild
Node subtrees using the prior depth-first child order.
- PackedScene extraction still instantiates exactly once and synchronously frees
the temporary root before returning the borrowed Mesh reference.
- `StreamingWorldLoader` still owns ResourceLoader I/O and raw M2 consumers,
material refresh/rebuild, cache/missing adoption, permits and MultiMesh builds.
- Cache formats, traversal rules, profiles and visible output are unchanged.
Synthetic traversal timing is not asset-backed leak or p95/p99 evidence.
## 2026-07-17 M2 Runtime Mesh Finalizer
- `M2RuntimeMeshFinalizer` now owns material refresh version `2`, stale-Mesh
rebuild classification, M2Builder rebuild and original-Mesh fallback.
- Current Meshes still skip raw `.m2` loading. `M2RawModelRepository` owns
FileAccess/ClassDB M2Loader I/O and supplies raw data through
`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
fallback are unchanged. Mesh resource finalization owns cache adoption;
permits and MultiMesh materialization remain loader-owned, while negative
outcomes belong to prototype state.
- Synthetic triangle rebuild/fallback timing is not asset-backed material,
descriptor-pressure/leak or p95/p99 evidence.
## 2026-07-17 M2 Raw Model Repository
- `M2RawModelRepository` now owns the repeated extracted-file and optional
native `M2Loader` boundary for static `load_m2` and animated
`load_m2_animated` raw Dictionaries.
- `StreamingWorldLoader` delegates stale-Mesh refresh and static reads directly;
`M2NativeAnimationResourceObserver` delegates native animated reads. The loader
retains normalization, `.tscn/.glb` fallback order, permits and Node/Mesh use;
prototype/negative state is isolated in `M2PrototypeCacheState`.
- The repository retains no path, native object or parsed data. Empty paths,
absent files/classes/methods and invalid results produce the same empty-value
fallback contract; path join/globalization and native method names are exact.
- No cache format, parser, material, geometry, animation or visible rule changed.
Synthetic missing-input timing is not asset-backed I/O/p95/p99 or fidelity evidence.
## 2026-07-17 M2 Prototype Cache State
- `M2PrototypeCacheState` now owns the exact static and animated prototype Node
references plus missing-model and static-animation negative path outcomes.
- Sync static, native animated, threaded GLB animation, Mesh finalize and
material-prototype paths delegate lookup/adoption/negative transitions to the
service. First valid prototype remains canonical for each normalized path.
- Map refresh still retains all four states. Final shutdown still occurs after
asynchronous drain and now calls one idempotent release operation that frees
detached prototypes, queues defensive in-tree Nodes and clears path outcomes.
- `StreamingWorldLoader` retains normalization, ResourceLoader/native I/O,
builders, animation policy, fallback order, permits and materialization.
- No cache format, visible rule or profile changed. Synthetic ownership/timing
evidence is not an asset-backed traversal/leak/p95/p99 or fidelity claim.
## 2026-07-17 WMO Placement Resolver Extraction
- `WmoPlacementResolver` now owns lowercase/slash cache-key normalization,
@@ -1103,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
Resources, negative entries and normalized-path to pending-cache-path records.
- `StreamingWorldLoader` still constructs cache paths, calls `ResourceLoader`,
polls requests and validates `WMOStreamingResource` script identity plus
`FORMAT_VERSION` before completing cache state.
- `StreamingWorldLoader` still constructs cache paths and starts requests.
`WmoRenderResourceFinalizer` polls terminal requests and validates exact
`WMOStreamingResource` script identity plus `FORMAT_VERSION` before completing
cache state.
- Map reset and orderly request draining clear pending/negative state while
retaining accepted Resources; final runtime cache release clears all state.
- Missing render-cache files still are not negatively cached, preserving retry
@@ -1113,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
remain pending.
## 2026-07-18 WMO Render Resource Finalizer Extraction
- `WmoRenderResourceFinalizer` now owns lightweight WMO render-cache terminal
status polling, loaded Resource retrieval, exact script/current-format
validation and Resource/missing publication.
- Detached pending snapshots retain Dictionary insertion order. Non-terminal
requests remain pending; failed, null, wrong-script and stale-format outcomes
retain the historical negative-cache transition.
- Accepted current-or-newer Resources keep exact identity. Loader retains cache
path selection, request admission, fallback/build orchestration, Node lifetime
and shutdown drain order.
- Cache format, profiles and visible output are unchanged. Synthetic fixtures
are not serialized private assets, leak, p95/p99 or visual-fidelity evidence.
## 2026-07-17 WMO Scene Resource Cache State Extraction
- `WmoSceneResourceCacheState` now owns validated cached-WMO PackedScenes,
negative entries and normalized-path to pending-`.tscn` records.
- `StreamingWorldLoader` still checks file existence and
`wmo_max_runtime_scene_mb`, calls `ResourceLoader`, instantiates a validation
probe, checks WMOBuilder cache metadata and frees the probe before adoption.
`wmo_max_runtime_scene_mb` and starts requests. `WmoSceneResourceFinalizer`
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
retain their prior negative-cache and live-prototype fallback behavior.
- Map reset clears pending/negative state while retaining accepted scenes; final
@@ -1127,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
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
- `AdtWaterLoadPipelineState` now owns ADT water pending FIFO/deduplication,
@@ -1201,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/
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
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,104 @@
# M03-RND-M2-ANIMATED-INSTANCE-MATERIALIZER-001
<!-- OPENWC_CLAIM:M03-RND-M2-ANIMATED-INSTANCE-MATERIALIZER-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-animated-instance-materializer`
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-m2-animated-instance-materializer`
## Outcome
Extract main-thread animated M2 instance duplication, render-property application,
playback startup and batch attachment from `StreamingWorldLoader`.
## Non-goals
- Change build-job cursors, budgets, prototype selection or animation eligibility.
- Change animation selection, phase, native animator behavior or diagnostic text.
- Own Editor scene persistence, static MultiMesh materialization or tile cleanup.
- Change visible transforms, visibility ranges, shadows or placement ordering.
## Paths
- Exclusive: `src/render/m2/m2_animated_instance_materializer.gd`,
`src/tools/verify_m2_animated_instance_materializer.gd`,
`docs/modules/m2-animated-instance-materializer.md`, this claim
- Shared/hotspots: `src/scenes/streaming/streaming_world_loader.gd`, M2 animation
playback/finalizer/build/facade/internal-access/manifest 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
- Duplicate flags, names, transforms and input order remain unchanged.
- Visibility end/margin and shadow mode are applied recursively before attachment.
- Playback data copy/start delegates to the accepted playback controller contract.
- The batch is attached only when at least one duplicate succeeds.
- Loader retains diagnostic formatting/path normalization and Editor ownership.
## Dependencies
- Requires: accepted playback package on master `5b2d9f2`
- Blocks: further animated M2 build orchestration extraction
- External state: Godot Node duplication and SceneTree attachment remain main-thread APIs
## Verification
- Commands: dedicated duplication/order/render/playback/empty/source/timing verifier;
M2 animated pipeline/finalizer/playback/prototype/build/shutdown/material/facade/
internal-access/manifest regressions; docs/coordination/dry-run gates
- Fixtures: synthetic prototype trees with geometry, AnimationPlayer and native animator
- Fidelity evidence: exact transforms, flags, render settings, playback delegation and order
- Performance budget: 1,000 synthetic instances under 1 second
## Documentation deliverables
- Inline API docs
- Module spec with inputs/outputs, ownership and source map
- Data-flow, lifecycle/state, sequence and dependency diagrams
- Adjacent playback/finalizer/build/world-renderer/module-registry/RENDER updates
## Simplicity and naming
- Important name introduced: `M2AnimatedInstanceMaterializer`
- Simplest solution: synchronous main-thread RefCounted returning batch diagnostics
- Rejected complexity: signals, async queue, callbacks and generic scene factory
- Unavoidable complexity: imported AnimationPlayer and native animator paths coexist
- Measured optimization evidence: bounded synthetic batch materialization
## Status
- State: ready for integration
- Done: materializer, loader adapter, verifier and required documentation
- Next: integrator merge and post-merge acceptance
- Blocked by:
## Handoff
- Commit: `456f3e2`
- Results: materializer passes 12 duplication/order/render/playback/diagnostic/
source cases and 1,000 instances in 9.267 ms; all 50 autonomous headless
verifiers pass, while the proprietary ADT placement probe is unavailable
without `data/extracted`; checkpoint dry-run passes 7/7; documentation and
coordination gates pass; loader-private inventory remains 30.
- Remaining risks: private asset traversal/visual comparison, descriptor
pressure, leaks and p95/p99 remain unavailable; materialization is still
synchronous main-thread work and no original-client comparison is claimed.
- Documentation updated: inline API; `m2-animated-instance-materializer.md`
with data-flow, lifecycle, sequence and dependency diagrams; playback,
finalizer, build, world-renderer specs, module registry and `RENDER.md`.
<!-- OPENWC_HANDOFF:READY:M03-RND-M2-ANIMATED-INSTANCE-MATERIALIZER-001:456f3e2 -->
## Integration
- Merge commit: `c251985`
- Post-merge results: materializer `cases=12 instances=1000 elapsed_ms=13.719`;
playback/finalizer, shutdown, facade, internal-access `30`, checkpoint dry-run
`7/7`, documentation and coordination gates passed.
<!-- OPENWC_INTEGRATION:ACCEPTED:M03-RND-M2-ANIMATED-INSTANCE-MATERIALIZER-001:c251985 -->
@@ -0,0 +1,110 @@
# M03-RND-M2-ANIMATED-SCENE-FINALIZER-001
<!-- OPENWC_CLAIM:M03-RND-M2-ANIMATED-SCENE-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-animated-scene-finalizer`
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-m2-animated-scene-finalizer`
## Outcome
Extract animated M2 PackedScene candidate instantiation, material override repair
and AnimationPlayer validation from `StreamingWorldLoader` into an explicit
main-thread finalizer.
## Non-goals
- Call ResourceLoader or interpret threaded-load statuses.
- Choose animation eligibility, cache paths, material prototypes or permits.
- Adopt/cache prototype Nodes or change static/native animation paths.
- Change visible materials, animation selection, cache formats or profiles.
## Paths
- Exclusive: `src/render/m2/m2_animated_scene_finalizer.gd`,
`src/tools/verify_m2_animated_scene_finalizer.gd`,
`docs/modules/m2-animated-scene-finalizer.md`, this claim
- Shared/hotspots: `src/scenes/streaming/streaming_world_loader.gd`, M2
animation pipeline/prototype/material/build/shutdown/facade/internal-access/
manifest verifiers and module specs, `docs/modules/world-renderer.md`,
`docs/modules/README.md`, `RENDER.md`, `targets/03-renderer-facade.md`
- Generated/ignored: `.godot`, native DLL, generated M2 resources, caches and
proprietary renderer corpus
## Contracts and data
- Only a PackedScene whose instantiated root is Node3D becomes a candidate;
unsupported or invalid roots return null and any created invalid root is freed.
- Material repair preserves depth-first MeshInstance3D order, source-index
clamping, per-surface override priority and first-material fallback.
- Finalization accepts a candidate only when at least one descendant
AnimationPlayer exists; rejected detached candidates are freed synchronously.
- Accepted result transfers the exact Node3D and player count to the loader.
- Loader retains ResourceLoader, material-prototype acquisition, permit,
prototype adoption/static fallback and diagnostic-log decisions.
## Dependencies
- Requires: accepted animation load pipeline package on master `c20bc62`
- Blocks: further animated M2 build/materialization decomposition
- External state: imported GLB/PackedScene shape remains engine-owned input
## Verification
- Commands: dedicated invalid/ownership/traversal/material/finalization/source/
timing verifier; animation pipeline/prototype/shutdown/material/M2 build/facade/
internal-access/manifest and adjacent M2 regressions; docs/coordination/dry-run gates
- Fixtures: synthetic PackedScenes, nested AnimationPlayers and ArrayMesh materials
- Fidelity evidence: exact material mapping and accepted/rejected Node transitions
- Performance budget: 10,000 depth-first candidate queries under 1 second
## Documentation deliverables
- Inline public API docs
- Module specification with inputs/outputs, ownership and source map
- Data-flow, lifecycle/state, sequence and dependency diagrams
- Adjacent animation/prototype/world-renderer/module-registry/RENDER updates
## Simplicity and naming
- Important name introduced: `M2AnimatedSceneFinalizer`
- Simplest solution: one stateless main-thread RefCounted over engine scene types
- Rejected complexity: callbacks, generic scene pipeline, signals or cache ownership
- Unavoidable complexity: material repair occurs between candidate creation and acceptance
- Measured optimization evidence: bounded synthetic subtree traversal
## Status
- State: accepted
- Done: finalizer, loader adapters, lifecycle/material verifier and required documentation
- Next: continue animated M2 playback/instance materialization extraction
- Blocked by:
## Handoff
- Commit: `1b450dc`
- Results: animated scene finalizer passes 13 invalid/type/lifetime/traversal/
material/transfer/source cases and 10,000 depth-eight queries in 40.126 ms;
all 48 autonomous headless verifiers pass, while the proprietary ADT placement
probe is unavailable without `data/extracted`; checkpoint dry-run passes 7/7;
documentation and coordination gates pass; loader-private inventory remains 30.
- Remaining risks: proprietary GLB traversal, material/animation comparison,
descriptor pressure, leaks and p95/p99 remain unavailable; no paired original-
client visual comparison is claimed. Positional material mapping is unchanged.
- Documentation updated: inline API; `m2-animated-scene-finalizer.md` with
data-flow, lifecycle, sequence and dependency diagrams; animation pipeline,
prototype/world-renderer specs, module registry and `RENDER.md`.
<!-- OPENWC_HANDOFF:READY:M03-RND-M2-ANIMATED-SCENE-FINALIZER-001:1b450dc -->
## Integration
- Merge commit: `e7a7c67`
- Post-merge results: finalizer `cases=13 iterations=10000
elapsed_ms=40.314`; animation pipeline, prototype state, shutdown, materials,
facade, internal-access `30`, manifest `7/7`, documentation and coordination passed.
<!-- OPENWC_INTEGRATION:ACCEPTED:M03-RND-M2-ANIMATED-SCENE-FINALIZER-001:e7a7c67 -->
@@ -0,0 +1,112 @@
# M03-RND-M2-ANIMATION-LOAD-PIPELINE-001 — M2 animation load pipeline state
<!-- OPENWC_CLAIM:M03-RND-M2-ANIMATION-LOAD-PIPELINE-001:sindo-main-codex:2026-07-20 -->
## Ownership
- Target: M03
- Program: RND
- Owner/Agent ID: sindo-main-codex
- Branch: `work/sindo-main-codex/m03-m2-animation-load-pipeline`
- Lease expires UTC: 2026-07-20
- Integrator: M03 milestone integrator
## Outcome
Extract threaded animated-M2 pending request records and completion-order
finalize FIFO from `StreamingWorldLoader` into an explicit state service.
## Non-goals
- Call ResourceLoader, select/validate GLB paths or interpret status values.
- Instantiate/repair/cache animated Nodes or own prototype outcomes.
- Change animation allow/deny/candidate policy, permits or visible behavior.
- Add cancellation, persistence, retries, dependencies or cache-format changes.
## Paths
- Exclusive: `src/render/m2/m2_animation_load_pipeline_state.gd`,
`src/tools/verify_m2_animation_load_pipeline_state.gd`,
`docs/modules/m2-animation-load-pipeline-state.md`, this claim
- Shared/hotspots: `src/scenes/streaming/streaming_world_loader.gd`, M2
prototype/shutdown/material/build/facade/internal-access/manifest verifiers
and module specs, `docs/modules/world-renderer.md`, `docs/modules/README.md`,
`RENDER.md`, `targets/03-renderer-facade.md`
- Generated/ignored: `.godot`, native DLL, generated M2 resources, caches and
proprietary renderer corpus
## Contracts and data
- One non-empty normalized path has at most one pending cache path.
- Pending snapshots preserve Dictionary insertion order and detach records.
- Completing a known request moves one copied record with opaque status into a
completion-order FIFO; unknown/duplicate completion is rejected.
- Defensive discard removes pending state without creating a finalize record.
- Total work remains pending plus finalize count at all three historical metrics.
- Map reset and shutdown retain the same two clear sites; shutdown still drains
ResourceLoader paths before clear.
## Dependencies
- Requires: accepted prototype cache package on master `20a64b5`
- Blocks: animated M2 finalization/materialization service extraction
- External state: ResourceLoader request statuses remain loader-owned
## Verification
- Commands: dedicated validation/dedupe/order/transition/discard/snapshot/clear/
source/timing verifier; prototype/shutdown/material/M2 build/facade/internal-
access/manifest and adjacent M2 regressions; docs/coordination/dry-run gates
- Fixtures: synthetic normalized/cache paths and opaque integer statuses
- Fidelity evidence: request insertion, terminal FIFO, metrics and clear transitions
- Performance budget: 100 cycles over 256 request completions under 1 second
## Documentation deliverables
- Inline public API docs
- Module specification with inputs/outputs, ownership and source map
- Data-flow, lifecycle/state, sequence and dependency diagrams
- Adjacent M2/world-renderer/module-registry/RENDER updates
## Simplicity and naming
- Important name introduced: `M2AnimationLoadPipelineState`
- Simplest solution: one main-thread RefCounted with pending map and finalize FIFO
- Rejected complexity: generic Resource pipeline, signals, callbacks, typed
ResourceLoader enum wrapper, Node ownership or async abstraction
- Unavoidable complexity: pending insertion order and terminal completion order differ
- Measured optimization evidence: bounded synthetic transition loop
## Status
- State: accepted
- Done: state, loader adapters, lifecycle verifier and required documentation
- Next: continue animated M2 finalization/materialization extraction
- Blocked by:
## Handoff
- Commit: `b8e0f11`
- Results: animation pipeline passes 11 validation/dedupe/order/transition/FIFO/
discard/snapshot/source cases and 100-by-256 cycles in 69.798 ms; all 47
autonomous headless verifiers pass, while the proprietary ADT placement probe
is unavailable without `data/extracted`; checkpoint dry-run passes 7/7;
documentation and coordination gates pass; loader-private inventory decreases
from 31 to 30.
- Remaining risks: successful animated-GLB traversal, descriptor pressure,
leaks, animation fidelity and p95/p99 require proprietary data; no paired
original-client visual comparison is claimed.
- Documentation updated: inline API; `m2-animation-load-pipeline-state.md` with
data-flow, lifecycle, sequence and dependency diagrams; prototype/world-renderer
specs, module registry and `RENDER.md`.
<!-- OPENWC_HANDOFF:READY:M03-RND-M2-ANIMATION-LOAD-PIPELINE-001:b8e0f11 -->
## Integration
- Merge commit: `3dccd3e`
- Post-merge results: animation pipeline `cases=11 iterations=100
elapsed_ms=70.309`; prototype state, shutdown, materials, facade,
internal-access `30`, manifest `7/7`, documentation and coordination gates passed.
<!-- OPENWC_INTEGRATION:ACCEPTED:M03-RND-M2-ANIMATION-LOAD-PIPELINE-001:3dccd3e -->
@@ -0,0 +1,110 @@
# M03-RND-M2-ANIMATION-PLAYBACK-001
<!-- OPENWC_CLAIM:M03-RND-M2-ANIMATION-PLAYBACK-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-playback`
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-m2-animation-playback`
## Outcome
Extract deterministic animated M2 playback selection/phase, AnimationPlayer
mutation and native animator data/start behavior from `StreamingWorldLoader`.
## Non-goals
- Duplicate or attach animated instances/batch roots.
- Own visibility, shadow, Editor owner or build-job cursor rules.
- Load/finalize/cache scenes or change animation eligibility.
- Change candidate priority, phase formula, logging text or visible playback.
## Paths
- Exclusive: `src/render/m2/m2_animation_playback_controller.gd`,
`src/tools/verify_m2_animation_playback_controller.gd`,
`docs/modules/m2-animation-playback-controller.md`, this claim
- Shared/hotspots: `src/scenes/streaming/streaming_world_loader.gd`, M2 animated
finalizer/build/prototype/material/facade/internal-access/manifest 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
- Phase remains `abs(hash("path:index")) % 1000 / 1000.0`.
- Exact animation-name priority remains ordinary Stand/Idle/Run/Walk, fish
Run/Walk/Swim/Stand/Idle/Death, birds Run/Walk/Swim/Stand/Idle, then
case-insensitive substring fallback and first-name fallback.
- Every AnimationPlayer animation becomes linear-looping before selected play;
positive-length selection seeks to deterministic phase.
- Native animators match exact script identity in depth-first order, copy the
same five fields, call prepare_runtime when available and set phase.
- Runtime debug state is sampled only when existing debug mode requests it;
loader retains final log formatting/path normalization.
## Dependencies
- Requires: accepted animated scene finalizer package on master `c0a7bfb`
- Blocks: animated M2 instance materializer extraction
- External state: AnimationPlayer and M2NativeAnimator APIs remain engine/scene contracts
## Verification
- Commands: dedicated phase/selection/loop/play/seek/native-copy/start/diagnostic/
source/timing verifier; animated finalizer/pipeline/prototype/build/shutdown/
material/facade/internal-access/manifest regressions; docs/coordination/dry-run gates
- Fixtures: synthetic AnimationPlayers and real M2NativeAnimator nodes
- Fidelity evidence: exact priority, phase and mutation/reference contracts
- Performance budget: 20,000 selection/phase operations under 1 second
## Documentation deliverables
- Inline API docs
- Module spec with inputs/outputs, ownership and source map
- Data-flow, lifecycle/state, sequence and dependency diagrams
- Adjacent finalizer/build/world-renderer/module-registry/RENDER updates
## Simplicity and naming
- Important name introduced: `M2AnimationPlaybackController`
- Simplest solution: stateless main-thread RefCounted over supplied Nodes/players
- Rejected complexity: signals, playback state machine, generic animation framework
- Unavoidable complexity: native and imported AnimationPlayer paths coexist
- Measured optimization evidence: bounded selection/phase loop
## Status
- State: accepted
- Done: playback controller, loader adapters, verifier and required documentation
- Next: extract animated M2 instance/batch materialization
- Blocked by:
## Handoff
- Commit: `9b73571`
- Results: playback controller passes 15 phase/selection/loop/play/seek/native-
copy/start/diagnostic/source cases and 20,000 phase-selection pairs in
23.415 ms; all 49 autonomous headless verifiers pass, while the proprietary
ADT placement probe is unavailable without `data/extracted`; checkpoint
dry-run passes 7/7; documentation and coordination gates pass; loader-private
inventory remains 30.
- Remaining risks: private animation-name/timing/native visual comparison,
descriptor pressure, leaks and p95/p99 remain unavailable; default selection
is still heuristic and no paired original-client comparison is claimed.
- Documentation updated: inline API; `m2-animation-playback-controller.md` with
data-flow, lifecycle, sequence and dependency diagrams; finalizer/build/world-
renderer specs, module registry and `RENDER.md`.
<!-- OPENWC_HANDOFF:READY:M03-RND-M2-ANIMATION-PLAYBACK-001:9b73571 -->
## Integration
- Merge commit: `c0fc191`
- Post-merge results: playback `cases=15 iterations=20000 elapsed_ms=23.008`;
animated finalizer/pipeline, prototype, build, shutdown, materials, facade,
internal-access `30`, manifest `7/7`, documentation and coordination passed.
<!-- OPENWC_INTEGRATION:ACCEPTED:M03-RND-M2-ANIMATION-PLAYBACK-001:c0fc191 -->
@@ -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-QUEUE-001
<!-- OPENWC_CLAIM:M03-RND-M2-BUILD-QUEUE-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-queue`
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-m2-build-queue`
## Outcome
Replace loader-owned raw M2 build-job Dictionary/FIFO fields with typed job and
queue state while preserving cursor, rotation, stale-key and lifetime behavior.
## Non-goals
- Move tile eligibility, resource readiness/retry or scheduler policy.
- Create/free Nodes, Meshes, MultiMeshes or animated instances inside queue state.
- Change grouping, batch planning, materialization, transform order or visuals.
- Change M2 cache versions, diagnostics text or Editor persistence.
## Paths
- Exclusive: `src/render/m2/m2_build_job.gd`,
`src/render/m2/m2_build_queue.gd`,
`src/tools/verify_m2_build_queue.gd`,
`docs/modules/m2-build-queue.md`, this claim
- Shared/hotspots: `src/scenes/streaming/streaming_world_loader.gd`, M2 build/
materializer/cache/shutdown/facade/internal-access 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
- A job retains tile key, root, exact groups Dictionary, insertion-order
`groups.keys()` snapshot, group index, transform offset and batch serial.
- FIFO keeps duplicate/stale keys; front rotation moves exactly one key to tail.
- Job erase does not erase FIFO entries, preserving external-cancel stale cleanup.
- Progress adopts group index, transform offset and serial atomically.
- Queue release never frees engine objects; loader retains finish/cancel cleanup.
## Dependencies
- Requires: accepted static materializer package on master `81e33a9`
- Blocks: extracting remaining M2 build/resource orchestration
- External state: Node validity and SceneTree destruction remain loader APIs
## Verification
- Commands: dedicated invalid/FIFO/duplicate/stale/rotation/cursor/lifetime/
diagnostics/source/timing verifier; M2 planner/materializer/cache/prototype/
shutdown/facade/internal-access/manifest regressions; docs/coordination/dry-run gates
- Fixtures: synthetic Node3D roots and ordered transform-group Dictionaries
- Fidelity evidence: exact existing keyed state, FIFO and cursor transitions
- Performance budget: 100 cycles of 256 enqueue/rotate/erase operations 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 build/world-renderer/module-registry/RENDER updates
## Simplicity and naming
- Important names: `M2BuildJob`, `M2BuildQueue`
- Simplest solution: two small RefCounted types mirroring accepted WMO queue pattern
- Rejected complexity: signals, generic queue base, callbacks and engine destruction
- Unavoidable complexity: keyed jobs and FIFO keys intentionally permit stale entries
- Measured optimization evidence: bounded synthetic lifecycle loop
## Status
- State: integration accepted
- Done: implementation, verification, documentation and worktree handoff
- Next: extract the next unclaimed M2 resource-dispatch seam
- Blocked by:
## Handoff
- Commit: `4a8338f2f0503c2ba31c495e00a696d359518c06`
- Results: cold editor parse `exit=0`; M2 build queue `cases=13 iterations=100
elapsed_ms=66.247`; all autonomous headless regressions `52/52`; internal
access `private_symbols=30`; baseline manifest/dry-run `7/7`; documentation
`module_specs=39`; coordination passed with 30 historical expired warnings.
- Remaining risks: proprietary ADT visual/p95/p99 evidence is unavailable;
resource readiness/dispatch, permits, materializer choice and root destruction
intentionally remain loader-owned.
- Documentation updated: new `m2-build-queue.md` with API/I/O, data-flow,
lifecycle, sequence, dependency and ownership diagrams; world renderer,
batch planner, module registry, RENDER and M03 Evidence updated.
- Integration: master merge `1dc013e5c2597d5e6f26f44807f5f0a943bf1517`;
post-merge queue `cases=13 iterations=100 elapsed_ms=65.998`, ten adjacent
M2/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,109 @@
# M03-RND-M2-MESH-RESOURCE-EXTRACTOR-001 — M2 mesh resource extractor
<!-- OPENWC_CLAIM:M03-RND-M2-MESH-RESOURCE-EXTRACTOR-001:sindo-main-codex:2026-07-20 -->
## Ownership
- Target: M03
- Program: RND
- Owner/Agent ID: sindo-main-codex
- Branch: `work/sindo-main-codex/m03-m2-mesh-resource-extractor`
- Lease expires UTC: 2026-07-20
- Integrator: M03 milestone integrator
## Outcome
Extract first-Mesh selection from loaded M2 Resources and Node subtrees,
including temporary PackedScene instance cleanup, from `StreamingWorldLoader`.
## Non-goals
- Call ResourceLoader or select/request cache paths.
- Refresh/rebuild materials or load raw M2 data.
- Own the Mesh cache, missing state, prototypes or MultiMesh materialization.
- Change traversal order, cache formats, profiles or visible behavior.
## Paths
- Exclusive: `src/render/m2/m2_mesh_resource_extractor.gd`,
`src/tools/verify_m2_mesh_resource_extractor.gd`,
`docs/modules/m2-mesh-resource-extractor.md`, this claim
- Shared/hotspots: `src/scenes/streaming/streaming_world_loader.gd`,
M2 pipeline/cache/material/shutdown verifiers, `docs/modules/world-renderer.md`,
`docs/modules/README.md`, M2 module specs, `RENDER.md`,
`targets/03-renderer-facade.md`
- Generated/ignored: `.godot`, native DLL, generated M2 resources, caches and
proprietary renderer corpus
## Contracts and data
- A direct Mesh Resource returns the same Mesh reference.
- A PackedScene is instantiated once, searched depth-first in child order and
its temporary root is freed before return.
- A Node subtree search returns the first MeshInstance3D Mesh in historical
depth-first preorder without changing the tree.
- Null, unsupported Resources and subtrees without a Mesh return null.
## Dependencies
- Requires: accepted Mesh resource cache package on master `5e2afbc`
- Blocks: isolated material refresh/rebuild finalizer extraction
- External state: none; verification uses synthetic Mesh/Node/PackedScene fixtures
## Verification
- Commands: dedicated direct/PackedScene/subtree/order/lifetime/source/timing
verifier; M2 pipeline/cache/classifier/material/shutdown/build/facade/internal-
access/manifest, documentation and coordination gates
- Fixtures: ArrayMesh, MeshInstance3D subtrees and synthetic PackedScene
- Fidelity evidence: exact traversal order and temporary-node destruction
- Performance budget: 10,000 synthetic subtree lookups under 1 second
## Documentation deliverables
- Inline public API docs
- Module specification with inputs/outputs, ownership and source map
- Data-flow, lifecycle/state, sequence and dependency diagrams
- M2 cache/pipeline, world-renderer, registry and RENDER updates
## Simplicity and naming
- Important name introduced: `M2MeshResourceExtractor`
- Simplest solution: one stateless RefCounted with two explicit extraction APIs
- Rejected complexity: callback-driven finalizer, generic scene utility,
cache ownership, signals or visitor framework
- Unavoidable complexity: temporary PackedScene instance must be freed while
its referenced Mesh remains borrowed
- Measured optimization evidence: bounded synthetic depth-first traversal
## Status
- State: accepted
- Done: extractor, three loader/test adapters, contract verifier and required documentation
- Next: isolated M2 Mesh material preparation finalizer extraction
- Blocked by:
## Handoff
- Commit: `7b0e1ea`
- Results: extractor passes direct/PackedScene/subtree/order/lifetime contracts
and 10,000 traversals in 39.243 ms; 36 headless renderer/coordinate
regressions pass; checkpoint dry-run passes 7/7; documentation and
coordination gates pass.
- Remaining risks: synchronous PackedScene instantiation remains behind the
existing permit; no private M2 corrupt-scene/traversal/leak/p95/p99 or
original-client comparison; material preparation remains loader-owned.
- Documentation updated: inline API; `m2-mesh-resource-extractor.md` with
data-flow, lifecycle, sequence and dependency diagrams; cache/pipeline specs,
module registry, `world-renderer.md` and `RENDER.md`.
<!-- OPENWC_HANDOFF:READY:M03-RND-M2-MESH-RESOURCE-EXTRACTOR-001:7b0e1ea -->
## Integration
- Merge commit: `fa85985`
- Post-merge results: extractor `cases=10 iterations=10000
elapsed_ms=38.172`; cache, pipeline, classifier, materials, shutdown, facade,
internal-access `34`, manifest `7/7`, documentation and coordination gates passed.
<!-- OPENWC_INTEGRATION:ACCEPTED:M03-RND-M2-MESH-RESOURCE-EXTRACTOR-001:fa85985 -->
@@ -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,111 @@
# M03-RND-M2-PROTOTYPE-CACHE-001 — M2 prototype cache state
<!-- OPENWC_CLAIM:M03-RND-M2-PROTOTYPE-CACHE-001:sindo-main-codex:2026-07-20 -->
## Ownership
- Target: M03
- Program: RND
- Owner/Agent ID: sindo-main-codex
- Branch: `work/sindo-main-codex/m03-m2-prototype-cache`
- Lease expires UTC: 2026-07-20
- Integrator: M03 milestone integrator
## Outcome
Extract static/animated detached M2 prototype references plus missing-model and
static-animation negative outcomes from `StreamingWorldLoader` into one explicit
shutdown-lifetime cache-state service.
## Non-goals
- Choose `.tscn/.glb` paths, call ResourceLoader/native loaders or build Nodes.
- Own animation/Mesh request queues, Mesh resources, materials or MultiMeshes.
- Change candidate/allowlist/denylist rules, fallback order or visible behavior.
- Add persistence, eviction, async work, dependencies or cache-format changes.
## Paths
- Exclusive: `src/render/m2/m2_prototype_cache_state.gd`,
`src/tools/verify_m2_prototype_cache_state.gd`,
`docs/modules/m2-prototype-cache-state.md`, this claim
- Shared/hotspots: `src/scenes/streaming/streaming_world_loader.gd`, runtime
cache shutdown/M2 raw/finalizer/extractor/pipeline/material/animation verifiers
and module specs, `docs/modules/world-renderer.md`, `docs/modules/README.md`,
`RENDER.md`, `targets/03-renderer-facade.md`
- Generated/ignored: `.godot`, native DLL, generated M2 resources, caches and
proprietary renderer corpus
## Contracts and data
- Normalized non-empty paths key static and animated detached `Node3D` prototypes.
- Missing-model and static-animation outcomes are independent path sets.
- Exact cached Node reference is returned; first adoption for an occupied path wins.
- Shutdown frees valid detached nodes synchronously, queues in-tree nodes for free,
clears all four states and remains idempotent.
- Map refresh retains the state exactly as before; only final shutdown clears it.
## Dependencies
- Requires: accepted raw model repository package on master `ca77d32`
- Blocks: remaining M2 animation request/materialization extraction
- External state: none; verification uses synthetic Node3D instances
## Verification
- Commands: dedicated admission/identity/independence/duplicate/free/source/timing
verifier; runtime shutdown/raw/finalizer/extractor/cache/pipeline/material/
animation/M2 build/facade/internal-access/manifest, docs and coordination gates
- Fixtures: detached and in-tree synthetic Node3D prototypes
- Fidelity evidence: all existing lookup/store/negative/clear caller transitions
- Performance budget: 100 cycles over 256 path-state operations under 1 second
## Documentation deliverables
- Inline public API docs
- Module specification with inputs/outputs, ownership and source map
- Data-flow, lifecycle/state, sequence and dependency diagrams
- Adjacent M2/world-renderer/module-registry/RENDER updates
## Simplicity and naming
- Important name introduced: `M2PrototypeCacheState`
- Simplest solution: one main-thread RefCounted with two Node maps and two path sets
- Rejected complexity: generic Node cache, signals, callbacks, eviction policy,
weak references, async cleanup or dependency injection
- Unavoidable complexity: detached Nodes require explicit free at final shutdown
- Measured optimization evidence: bounded synthetic cache-cycle loop
## Status
- State: accepted
- Done: cache state, loader/test adapters, lifecycle verifier and required documentation
- Next: continue M2 animation request/materialization extraction
- Blocked by:
## Handoff
- Commit: `cd664eb`
- Results: prototype state passes 16 admission/identity/negative/lifecycle/source
cases and 100-by-256 cycles in 50.266 ms; 46 autonomous headless verifiers
pass, while the proprietary ADT placement probe is unavailable without
`data/extracted`; checkpoint dry-run passes 7/7; documentation and
coordination gates pass; loader-private inventory decreases from 34 to 31.
- Remaining risks: strong prototypes remain unbounded until shutdown; successful
asset traversal, descriptor pressure, leaks and p95/p99 require proprietary
data; no original-client visual comparison is claimed.
- Documentation updated: inline API; `m2-prototype-cache-state.md` with data-flow,
lifecycle, sequence and dependency diagrams; pipeline/Mesh-cache/extractor/raw
repository/world-renderer specs, module registry and `RENDER.md`.
<!-- OPENWC_HANDOFF:READY:M03-RND-M2-PROTOTYPE-CACHE-001:cd664eb -->
## Integration
- Merge commit: `1de50f9`
- Post-merge results: prototype cache `cases=16 iterations=100
elapsed_ms=37.978`; shutdown, raw repository, finalizer, extractor, Mesh cache,
pipeline, materials, facade, internal-access `31`, manifest `7/7`,
documentation and coordination gates passed.
<!-- OPENWC_INTEGRATION:ACCEPTED:M03-RND-M2-PROTOTYPE-CACHE-001:1de50f9 -->
@@ -0,0 +1,109 @@
# M03-RND-M2-RAW-MODEL-REPOSITORY-001 — raw M2 model repository
<!-- OPENWC_CLAIM:M03-RND-M2-RAW-MODEL-REPOSITORY-001:sindo-main-codex:2026-07-20 -->
## Ownership
- Target: M03
- Program: RND
- Owner/Agent ID: sindo-main-codex
- Branch: `work/sindo-main-codex/m03-m2-raw-model-repository`
- Lease expires UTC: 2026-07-20
- Integrator: M03 milestone integrator
## Outcome
Extract the repeated FileAccess/ClassDB native `M2Loader` boundary for static
and animated raw model Dictionaries from `StreamingWorldLoader`.
## Non-goals
- Cache raw data, Meshes, prototypes, missing paths or animation decisions.
- Build Nodes/Meshes, refresh materials or choose `.tscn/.glb` caches.
- Change static/animated parser methods, path resolution or fallback order.
- Add dependencies, persistence, retries or visible behavior.
## Paths
- Exclusive: `src/render/m2/m2_raw_model_repository.gd`,
`src/tools/verify_m2_raw_model_repository.gd`,
`docs/modules/m2-raw-model-repository.md`, this claim
- Shared/hotspots: `src/scenes/streaming/streaming_world_loader.gd`, M2
finalizer/extractor/cache/pipeline/material/animation/shutdown verifiers and
module specs, `docs/modules/world-renderer.md`, `docs/modules/README.md`,
`RENDER.md`, `targets/03-renderer-facade.md`
- Generated/ignored: `.godot`, native DLL, generated M2 resources, caches and
proprietary renderer corpus
## Contracts and data
- Static load calls native `load_m2`; animated load calls `load_m2_animated`.
- Paths remain `globalize_path(extracted_directory.path_join(normalized_path))`.
- Empty path, missing native class/file/instance/method, parse failure or
non-Dictionary result returns an empty Dictionary without retained state.
- Each successful request creates one native loader instance as before.
## Dependencies
- Requires: accepted runtime Mesh finalizer package on master `1d94eac`
- Blocks: M2 prototype/cache state extraction
- External state: native M2Loader registration and local extracted files
## Verification
- Commands: dedicated invalid/missing/class/source/timing verifier; finalizer/
classifier/extractor/cache/pipeline/material/animation/shutdown/M2 build/facade/
internal-access/manifest, documentation and coordination gates
- Fixtures: empty/missing normalized paths plus source contract; proprietary
success corpus remains external
- Fidelity evidence: exact native method names, path expression and three caller transitions
- Performance budget: 10,000 rejected empty-path calls under 1 second
## Documentation deliverables
- Inline public API docs
- Module specification with inputs/outputs, ownership and source map
- Data-flow, lifecycle/state, sequence and dependency diagrams
- Finalizer/extractor/cache/pipeline, world-renderer, registry and RENDER updates
## Simplicity and naming
- Important name introduced: `M2RawModelRepository`
- Simplest solution: one stateless RefCounted with static and animated load methods
- Rejected complexity: generic native repository, caching, dependency injection,
retries, diagnostics framework or async wrapper
- Unavoidable complexity: animated native method availability is a distinct guard
- Measured optimization evidence: bounded rejected-input loop
## Status
- State: accepted
- Done: repository, three loader adapters, contract verifier and required documentation
- Next: continue M2 prototype/cache state extraction
- Blocked by:
## Handoff
- Commit: `b608a81`
- Results: repository passes invalid/missing/stateless/native-boundary contracts
and 10,000 empty-path rejects in 3.275 ms; 45 repository-independent
headless verifiers pass, while the proprietary ADT placement probe is
unavailable without `data/extracted`; checkpoint dry-run passes 7/7;
documentation and coordination gates pass.
- Remaining risks: successful/corrupt native parsing, descriptor pressure,
leaks and p95/p99 remain asset-backed work; native calls are synchronous and
cannot be cancelled mid-parse; no original-client visual comparison is claimed.
- Documentation updated: inline API; `m2-raw-model-repository.md` with data-flow,
lifecycle, sequence and dependency diagrams; finalizer/classifier/world-renderer
specs, module registry and `RENDER.md`.
<!-- OPENWC_HANDOFF:READY:M03-RND-M2-RAW-MODEL-REPOSITORY-001:b608a81 -->
## Integration
- Merge commit: `7cd07dd`
- Post-merge results: repository `cases=10 iterations=10000
elapsed_ms=3.200`; finalizer, extractor, cache, pipeline, shutdown, facade,
internal-access `34`, manifest `7/7`, documentation and coordination gates passed.
<!-- OPENWC_INTEGRATION:ACCEPTED:M03-RND-M2-RAW-MODEL-REPOSITORY-001:7cd07dd -->
@@ -0,0 +1,110 @@
# M03-RND-M2-RUNTIME-MESH-FINALIZER-001 — M2 runtime mesh finalizer
<!-- OPENWC_CLAIM:M03-RND-M2-RUNTIME-MESH-FINALIZER-001:sindo-main-codex:2026-07-20 -->
## Ownership
- Target: M03
- Program: RND
- Owner/Agent ID: sindo-main-codex
- Branch: `work/sindo-main-codex/m03-m2-runtime-mesh-finalizer`
- Lease expires UTC: 2026-07-20
- Integrator: M03 milestone integrator
## Outcome
Extract stale cached-Mesh refresh classification, metadata adoption,
M2Builder rebuild and fallback from `StreamingWorldLoader` into one runtime M2
Mesh finalizer while retaining raw-file I/O in the loader.
## Non-goals
- Load raw `.m2` files, call FileAccess/ClassDB M2Loader or choose cache paths.
- Own ResourceLoader requests, Mesh cache/missing state or MultiMesh builds.
- Change material refresh version `2`, classifier predicates or rebuild behavior.
- Change cache formats, profiles or visible output.
## Paths
- Exclusive: `src/render/m2/m2_runtime_mesh_finalizer.gd`,
`src/tools/verify_m2_runtime_mesh_finalizer.gd`,
`docs/modules/m2-runtime-mesh-finalizer.md`, this claim
- Shared/hotspots: `src/scenes/streaming/streaming_world_loader.gd`, M2
classifier/extractor/cache/pipeline/material/shutdown verifiers and module
specs, `docs/modules/world-renderer.md`, `docs/modules/README.md`, `RENDER.md`,
`targets/03-renderer-facade.md`
- Generated/ignored: `.godot`, native DLL, generated M2 resources, caches and
proprietary renderer corpus
## Contracts and data
- Refresh version remains `2`; already-current Meshes return unchanged without raw data.
- Stale Meshes require caller-supplied raw data only when refresh is necessary.
- Empty/ordinary raw data marks the original Mesh current and returns it.
- Billboard/UV-rotation data attempts M2Builder rebuild and returns the rebuilt
first Mesh; rebuild failure marks and returns the original Mesh.
- Classifier memoization retains both existing clear sites through finalizer clear.
## Dependencies
- Requires: accepted Mesh resource extractor package on master `dd8cfbf`
- Blocks: further M2 raw-data I/O/prototype state extraction
- External state: none; verification uses synthetic Mesh/raw-data fixtures
## Verification
- Commands: dedicated current/stale/ordinary/rebuild/fallback/clear/source/timing
verifier; classifier/extractor/cache/pipeline/material/shutdown/M2 build/facade/
internal-access/manifest, documentation and coordination gates
- Fixtures: ArrayMesh and one synthetic triangle/billboard M2 Dictionary
- Fidelity evidence: exact refresh version, predicate, rebuild and fallback transitions
- Performance budget: 100 cycles over 256 ordinary finalizations under 1 second
## Documentation deliverables
- Inline public API docs
- Module specification with inputs/outputs, ownership and source map
- Data-flow, lifecycle/state, sequence and dependency diagrams
- Classifier/extractor/cache/pipeline, world-renderer, registry and RENDER updates
## Simplicity and naming
- Important name introduced: `M2RuntimeMeshFinalizer`
- Simplest solution: one service composing existing classifier/extractor and M2Builder
- Rejected complexity: callbacks for loader I/O, generic material pipeline,
cache ownership, signals or strategy framework
- Unavoidable complexity: two-phase raw-data request avoids loading raw files for current Meshes
- Measured optimization evidence: bounded synthetic ordinary finalization loop
## Status
- State: accepted
- Done: finalizer, loader/test adapters, transition/rebuild verifier and required documentation
- Next: further M2 raw-data I/O/prototype state extraction
- Blocked by:
## Handoff
- Commit: `ece7724`
- Results: finalizer passes current/stale/ordinary/synthetic-rebuild/fallback/
clear contracts and 100-by-256 finalizations in 53.719 ms; 37 headless
renderer/coordinate regressions pass; checkpoint dry-run passes 7/7;
documentation and coordination gates pass.
- Remaining risks: rebuild remains synchronous behind the existing permit; no
private M2 material/traversal/descriptor-pressure/leak/p95/p99 or original-
client comparison; raw `.m2` I/O remains loader-owned by design.
- Documentation updated: inline API; `m2-runtime-mesh-finalizer.md` with
data-flow, lifecycle, sequence and dependency diagrams; classifier/extractor/
cache/pipeline specs, module registry, `world-renderer.md` and `RENDER.md`.
<!-- OPENWC_HANDOFF:READY:M03-RND-M2-RUNTIME-MESH-FINALIZER-001:ece7724 -->
## Integration
- Merge commit: `1fafdde`
- Post-merge results: finalizer `cases=11 iterations=100
elapsed_ms=54.349`; classifier, extractor, cache, pipeline, materials,
shutdown, facade, internal-access `34`, manifest `7/7`, documentation and
coordination gates passed.
<!-- OPENWC_INTEGRATION:ACCEPTED:M03-RND-M2-RUNTIME-MESH-FINALIZER-001:1fafdde -->
@@ -0,0 +1,106 @@
# M03-RND-M2-STATIC-BATCH-MATERIALIZER-001
<!-- OPENWC_CLAIM:M03-RND-M2-STATIC-BATCH-MATERIALIZER-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-batch-materializer`
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-m2-static-batch-materializer`
## Outcome
Extract main-thread static M2 `MultiMesh` construction, render-property setup
and batch attachment from `StreamingWorldLoader`.
## Non-goals
- Change M2 grouping, batch sizing, build cursors, budgets or retry behavior.
- Change Mesh loading, refresh, cache ownership or missing-model outcomes.
- Own animated M2 instances, Editor persistence or tile cleanup.
- Introduce spatial cells or change transform order, visibility or shadows.
## Paths
- Exclusive: `src/render/m2/m2_static_batch_materializer.gd`,
`src/tools/verify_m2_static_batch_materializer.gd`,
`docs/modules/m2-static-batch-materializer.md`, this claim
- Shared/hotspots: `src/scenes/streaming/streaming_world_loader.gd`, M2 build/
Mesh-cache/facade/internal-access/manifest 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
- `MultiMesh.TRANSFORM_3D`, exact Mesh identity, instance count and transform
slice order remain unchanged.
- Batch node name remains model basename plus serial.
- Visibility end/margin and shadow setting remain exact renderer inputs.
- A valid batch attaches once to the supplied M2 parent and returns its node.
- Loader retains Editor-owner assignment, build cursor, budgets and retries.
## Dependencies
- Requires: accepted animated materializer package on master `86814de`
- Blocks: further M2 build-job orchestration extraction
- External state: Godot MultiMesh and SceneTree mutation remain main-thread APIs
## Verification
- Commands: dedicated invalid/name/mesh/count/order/render/attachment/source/timing
verifier; M2 build/cache/prototype/animated/shutdown/material/facade/internal-
access/manifest regressions; docs/coordination/dry-run gates
- Fixtures: synthetic ArrayMesh and ordered Transform3D slices
- Fidelity evidence: exact existing MultiMesh construction and render settings
- Performance budget: materialize 10,000 transforms 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 build/cache/world-renderer/module-registry/RENDER updates
## Simplicity and naming
- Important name introduced: `M2StaticBatchMaterializer`
- Simplest solution: synchronous main-thread RefCounted returning attached node
- Rejected complexity: signals, async queue, callbacks and generic materializer base
- Unavoidable complexity: MultiMesh is a Godot Resource owned through its node
- Measured optimization evidence: bounded synthetic transform upload
## Status
- State: ready for integration
- Done: materializer, loader adapter, verifier and required documentation
- Next: integrator merge and post-merge acceptance
- Blocked by:
## Handoff
- Commit: `252927d`
- Results: materializer passes 11 invalid/node/Mesh/render/attachment/source
cases and 10,000 transform API writes in 0.552 ms; all 51 autonomous headless
verifiers pass, while the proprietary ADT placement probe is unavailable
without `data/extracted`; checkpoint dry-run passes 7/7; documentation and
coordination gates pass; loader-private inventory remains 30.
- Remaining risks: headless dummy timing excludes GPU upload cost; private asset
traversal/visual comparison, spatial-cell culling, descriptor pressure, leaks
and p95/p99 remain unavailable; no original-client comparison is claimed.
- Documentation updated: inline API; `m2-static-batch-materializer.md` with
data-flow, lifecycle, sequence and dependency diagrams; build/cache/world-
renderer specs, module registry and `RENDER.md`.
<!-- OPENWC_HANDOFF:READY:M03-RND-M2-STATIC-BATCH-MATERIALIZER-001:252927d -->
## Integration
- Merge commit: `5e82daa`
- Post-merge results: static materializer `cases=11 instances=10000
elapsed_ms=1.327`; build/cache/animated materializer, shutdown, facade,
internal-access `30`, checkpoint dry-run `7/7`, documentation and coordination
gates passed.
<!-- OPENWC_INTEGRATION:ACCEPTED:M03-RND-M2-STATIC-BATCH-MATERIALIZER-001:5e82daa -->
@@ -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 и права на изменение данных.
Планируемый 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. Сервер авторитетен для боя, ресурсов, инвентаря, квестового прогресса и общего мира.
@@ -57,6 +63,11 @@ TrinityCore/AzerothCore ◄── Network Adapter ◄── Runtime Client
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
Владеет 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.
- `ContentTypeDescriptor` — schema, inspector, validator, compiler.
- `WorldRenderer` — streaming focus и entity presentation.
- `GraphicsProfile` — explicit Blizzlike/Enhanced/Racing visual capabilities.
- `GameplaySystem` — commands/events без scene dependency.
- `EditorTool` — selection, command creation и gizmo, без прямой записи.
- `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.
- Branch по capability/profile должен быть локальным и типизированным.
- Не распространять `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`, если таблица остаётся читаемой и валидируемой.
## Comments
+1 -1
View File
@@ -181,7 +181,7 @@ stateDiagram-v2
- Uniform/global parameter: coordinate/color space, range, units и producer.
- Material profile: supported WoW shader/blend modes и approximations.
- Expensive branch/texture dependency имеет cost/fallback note.
- Blizzlike и Enhanced behavior документируются отдельно.
- Blizzlike, Enhanced и Racing behavior документируются отдельно.
### 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 использовать совместно по профилю.
- Unique materials/textures минимизировать; descriptor/resource counts являются budget metric.
- RenderingServer RIDs имеют явного владельца и освобождаются в deterministic shutdown test.
- Shader/material profiles разделяют Blizzlike и Enhanced; runtime не компилирует тяжёлые варианты при пересечении ADT boundary.
- Shader/material profiles разделяют Blizzlike, Enhanced и Racing; runtime не компилирует тяжёлые варианты при пересечении ADT boundary.
## EditorPlugin lifecycle
+15 -1
View File
@@ -2,6 +2,11 @@
Reference-код используется для исследования форматов, поведения и архитектурных вариантов. Он не определяет API OpenWC и не копируется без проверки лицензии, корректности и соответствия Godot.
Актуальные branches и pinned commits локальных Git-референсов зафиксированы в
[`reference/README.md`](../reference/README.md#git-reference-revisions). Gitlinks
точно фиксируют ревизию, а `.gitmodules` задаёт canonical remote и ветку
для контролируемого обновления.
## Основные источники
### OpenWC renderer
@@ -17,12 +22,13 @@ Reference-код используется для исследования фор
### WoWee
- Исследование обновлено до `master` commit `607ea3b8369851014721416293f8e95dfbe64eec` (2026-09-05), относительно прежнего reviewed pin `8456c236b57140e98667d6d8188f5cd1cc226daf`.
- `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/TESTING.md` — единая точка запуска тестов, fixtures, sanitizers и CI discipline.
- `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
@@ -51,6 +57,14 @@ Reference-код используется для исследования фор
Не переносим напрямую старый 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
- [`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.
## 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.
- Content Project schema, stable IDs, save/load/migration.
@@ -28,7 +41,7 @@
Готово, когда небольшой synthetic project можно создать, изменить, undo, перезапустить Editor и получить идентичное состояние.
## M2 — Server inspector и adapters
## M3 — Server inspector и adapters
- TrinityCore/AzerothCore connection profiles.
- Schema detection и capabilities.
@@ -38,7 +51,7 @@
Готово, когда одна сущность round-trip проходит оба поддерживаемых adapter profile без молчаливой потери данных.
## M3 — World Editor MVP
## M4 — World Editor MVP
- Map viewport и coordinate overlays.
- Server spawn visualization.
@@ -48,7 +61,7 @@
Готово, когда NPC размещается в Editor и появляется на тестовом core в ожидаемой позиции.
## M4 — Quest vertical slice
## M5 — Quest vertical slice
- Quest form и chain graph.
- Kill/collect/explore objectives, giver/ender, rewards и localization.
@@ -57,7 +70,7 @@
Готово, когда созданный в Editor квест полностью проходится клиентом.
## M5 — Playable network client
## M6 — Playable network client
- Auth, realm, character selection и world session.
- Entity/update fields и world spawn.
@@ -66,7 +79,7 @@
Готово, когда клиент стабильно входит в мир, перемещается и видит синхронизированные entities.
## M6 — Core gameplay
## M7 — Core gameplay
- Combat/spells/auras/death.
- Inventory/equipment/loot/vendors.
@@ -76,7 +89,7 @@
Готово, когда базовый leveling loop проходит без внешнего клиента.
## M7 — Dungeon authoring
## M8 — Dungeon authoring
- DungeonPackage, encounters, triggers, doors и spawn groups.
- SmartAI/script skeleton generation.
@@ -85,7 +98,7 @@
Готово, когда custom dungeon собирается, разворачивается и проходится группой на test core.
## M8 — Compatibility и completeness
## M9 — Compatibility и completeness
- Feature matrix WoW 3.3.5a.
- Addon compatibility tiers.
@@ -98,17 +111,17 @@
При равной ценности порядок такой:
1. безопасность данных и воспроизводимость;
2. корректность протокола и authoritative state;
3. пользовательский vertical slice;
4. diagnostics и testability;
5. frame pacing;
6. визуальная точность и polish.
2. original-client renderer fidelity и frame pacing текущего M04;
3. корректность протокола и authoritative state;
4. пользовательский vertical slice;
5. diagnostics и testability;
6. opt-in визуальные улучшения после Blizzlike evidence.
## Не делать раньше времени
- прямую запись в production DB;
- универсальный visual scripting для любой C++ механики;
- массовую реализацию Lua API без работающего UI slice;
- большой rewrite существующего renderer;
- полный custom renderer до bounded shader/backend spike и profiler evidence;
- multi-expansion abstraction до устойчивого профиля 3.3.5a;
- proprietary asset packaging в репозитории.
+3 -3
View File
@@ -44,8 +44,8 @@ ID записывается в claim, ветке, PR/MR и handoff. Нельзя
```text
M01-FND-COORDS-001
M03-RND-SCHEDULER-001
M08-NET-SRP-001
M12-UIA-LUA-SPIKE-001
M09-NET-SRP-001
M13-UIA-LUA-SPIKE-001
```
Program codes определены в [`../targets/DEVELOPMENT_ROADMAP.md`](../targets/DEVELOPMENT_ROADMAP.md).
@@ -214,7 +214,7 @@ Merge order:
```text
fnd(M01): add canonical coordinate mapper
net(M08): decode auth challenge safely
net(M09): decode auth challenge safely
rnd(M03): extract streaming target planner
test(M00): add paired checkpoint manifest
```
+36
View File
@@ -54,6 +54,42 @@
- dense WMO/M2, water, character equipment и UI scale matrices.
- 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
- 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 форматов |
| StormLib | ADOPTED | MPQ | Чтение архивов через native extension | Version/license/update audit |
| WowUnreal | REFERENCE | Полный клиент | Coverage, acceptance criteria, networking/UI research | Unreal-specific design |
| WoWee | REFERENCE | Клиент/editor/formats | Architecture, editor workflows, tests, open formats | Заявления требуют независимой проверки |
| Noggit Red | REFERENCE | World editor | Terrain/placement UX, UID workflows | Не Godot architecture |
| 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 |
| 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 |
| whoa | REFERENCE | Client behavior | 3.3.5a runtime semantics и fixtures | Лицензия и переносимость отдельных решений |
| 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 |
| [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 |
| [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 |
@@ -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 |
| [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 — карточка кандидата
- **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.
- **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 — карточка кандидата
- **Problem solved:** Recast navmesh generation, Detour queries, tiled navigation и dynamic obstacles.
+24
View File
@@ -21,18 +21,42 @@
| 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 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 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 animated scene finalizer | Implemented extraction | [`m2-animated-scene-finalizer.md`](m2-animated-scene-finalizer.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 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 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 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 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) |
| WMO placement resolver | Implemented extraction | [`wmo-placement-resolver.md`](wmo-placement-resolver.md) |
| WMO placement registry | Implemented extraction | [`wmo-placement-registry.md`](wmo-placement-registry.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 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 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) |
| 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 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) |
| Streaming target planner | Implemented | [`streaming-target-planner.md`](streaming-target-planner.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 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 |
| 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
+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.
- 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
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 |
| 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 |
| Jump/fall/swim | Planned | M02/M09 roadmap | Requires terrain/liquid and server contracts |
| Prediction/reconciliation | Planned | M08/M09 roadmap | Requires movement snapshot/network contract |
| Jump/fall/swim | Planned | M02/M10 roadmap | Requires terrain/liquid and server contracts |
| Prediction/reconciliation | Planned | M09/M10 roadmap | Requires movement snapshot/network contract |
## Known gaps and risks
@@ -0,0 +1,231 @@
# M2 Animated Instance Materializer
## Metadata
| Field | Value |
|---|---|
| Status | Implemented extraction |
| Target/work package | M03 / `M03-RND-M2-ANIMATED-INSTANCE-MATERIALIZER-001` |
| Owners | Animated M2 duplicate/batch construction, render settings, playback startup and attachment |
| Last verified | Worktree `work/sindo-main-codex/m03-m2-animated-instance-materializer`, 2026-07-18 |
| Profiles/capabilities | Imported GLB and native experimental animated M2 instances |
## Purpose
Materialize one ordered animated M2 build batch on the main thread. The service
duplicates an accepted prototype, preserves historical names and transforms,
applies visibility/shadow settings recursively, starts native/imported playback,
attaches a non-empty batch and returns detached native diagnostic entries.
## Non-goals
- Plan build batches, advance job cursors or consume render budgets.
- Load, finalize, select or cache animated prototypes.
- Select animations or implement native animator deformation.
- Format logs, normalize paths, assign Editor owners or clean up tile roots.
- Materialize static M2 `MultiMesh` batches.
## Context and boundaries
```mermaid
flowchart LR
Loader[StreamingWorldLoader adapter] --> Materializer[M2AnimatedInstanceMaterializer]
Prototype[Accepted animated prototype] --> Materializer
Plan[Transforms and batch slice] --> Materializer
Materializer --> Playback[M2AnimationPlaybackController]
Materializer --> Finalizer[M2AnimatedSceneFinalizer traversal]
Materializer --> Batch[Attached animated batch]
Materializer --> Diagnostics[Detached diagnostic entries]
```
Allowed dependencies are Godot Node/Node3D/GeometryInstance3D APIs and the
accepted animated finalizer and playback services. Resource loading, files,
workers, caches, scheduler policy, Editor ownership and `MultiMesh` are forbidden.
## Public API
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|---|---|---|---|---|
| `materialize_batch(parent, path, prototype, transforms, start, count, serial, visibility_end, visibility_margin, cast_shadows, native_script, collect_diagnostics)` | Command/query | Build, start and attach one ordered animated batch | Main thread; borrowed inputs, parent owns result | Invalid/empty input or all failed duplicates returns `{}` |
## Inputs and outputs
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|---|---|---|---|---|---|
| Input | M2 parent root | Loader build job | Batch attachment | Borrowed Node3D | Tile/job lifetime |
| Input | Relative path, start/count and serial | Batch planner/loader adapter | Names, phase and slice | Copied scalars | One call |
| Input | Accepted animated prototype | Prototype cache | Duplicate source | Borrowed Node3D | Cache-owned |
| Input | Ordered transforms | Placement grouping/build job | Instance transforms | Borrowed Array | Job lifetime |
| Input | Visibility/shadow settings | Renderer configuration | Recursive render mutation | Copied scalars | One call |
| Input | Native animator Script identity | Loader composition | Playback controller | Borrowed Script | Application lifetime |
| Output | `batch_root` | Materializer | Parent SceneTree and loader Editor-owner adapter | Parent-owned Node3D | Tile lifetime |
| Output | `native_diagnostics` entries | Playback controller/materializer | Loader log adapter | Caller-owned Array/Dictionaries | Debug call |
Side effects are Node duplication, instance naming/transform assignment,
recursive geometry mutation, playback mutation and SceneTree attachment. The
service retains no batch, prototype, transform or diagnostic references.
## Data flow
```mermaid
flowchart TD
Slice[Ordered transform slice] --> Duplicate[Duplicate prototype with signals, groups and scripts]
Duplicate --> Identity[Assign historical name and transform]
Identity --> Render[Apply visibility margin and shadow recursively]
Render --> NativeCopy[Copy native animator runtime fields]
NativeCopy --> AttachInstance[Attach duplicate to detached batch]
AttachInstance --> Players[Inventory AnimationPlayers]
Players --> Start[Start deterministic native/imported playback]
Start --> More{More slice entries?}
More -->|yes| Duplicate
More -->|no, non-empty| AttachBatch[Attach batch to supplied parent]
Start --> Diagnostics[Tag detached states with instance index]
```
## Lifecycle/state
```mermaid
stateDiagram-v2
[*] --> Validating
Validating --> Empty: invalid input
Validating --> Building: valid slice
Building --> Building: next successful duplicate
Building --> Empty: no successful duplicates
Building --> Attached: non-empty batch
Empty --> [*]
Attached --> [*]: parent/tile owner releases subtree
```
The `RefCounted` service is stateless; lifecycle labels describe each batch call.
## Main sequence
```mermaid
sequenceDiagram
participant L as StreamingWorldLoader
participant M as M2AnimatedInstanceMaterializer
participant F as M2AnimatedSceneFinalizer
participant P as M2AnimationPlaybackController
participant R as M2 parent root
L->>M: materialize_batch(slice, prototype, render settings)
loop ordered instance
M->>M: duplicate, name, transform, render settings
M->>P: copy native animator fields
M->>F: animation_players_in_subtree(duplicate)
M->>P: start_instance_playback(path, index, players, debug)
end
M->>R: add_child(non-empty batch)
M-->>L: batch root and indexed diagnostics
L->>L: format logs and assign Editor owner
```
## Dependency diagram
```mermaid
flowchart TB
Loader[StreamingWorldLoader] --> Materializer[M2AnimatedInstanceMaterializer]
Materializer --> Playback[M2AnimationPlaybackController]
Materializer --> Finalizer[M2AnimatedSceneFinalizer]
Materializer --> Engine[Node3D / GeometryInstance3D / SceneTree APIs]
Loader --> EditorOwner[Editor ownership adapter]
Loader --> BuildJob[Build cursor and budget]
Materializer -. no dependency .-> Cache[Prototype/cache/load state]
Materializer -. no dependency .-> MultiMesh[Static M2 materialization]
```
## Ownership, threading and resources
- Loader/build state borrows the accepted prototype and ordered transform Array.
- Each successful duplicate becomes a child of the detached batch immediately.
- A non-empty batch transfers to the supplied parent; the parent owns its lifetime.
- A batch with no successful duplicates is queued for deletion and no reference returns.
- Playback/finalizer services borrow each duplicate only during synchronous calls.
- Returned diagnostic entries contain the stable instance index and a detached state.
- All Node duplication, mutation and attachment is main-thread-only.
## Errors, cancellation and recovery
| Failure | Detection | Behavior | Diagnostic | Recovery |
|---|---|---|---|---|
| Null parent/prototype | Entry guard | Return `{}` without mutation | Contract verifier | Correct composition/cache state |
| Empty transforms or non-positive count | Entry guard | Return `{}` | Contract verifier | Planner supplies a non-empty slice |
| Duplicate is not Node3D | Cast result | Skip that entry and preserve order of successes | Child-count contract | Repair prototype scene root |
| Every duplicate fails | Empty batch check | Queue empty batch, return `{}` | Empty-result contract | Rebuild/import prototype |
| Shutdown during/after call | Outside service | Call is synchronous; loader owns cancellation and subtree release | Shutdown regression | Loader cancels job/releases parent |
Transform bounds remain a planner/build-job precondition exactly as before the
extraction; this service does not clamp or silently reorder an invalid slice.
## Configuration and capabilities
| Setting/capability | Default | Profile | Runtime mutable | Effect |
|---|---|---|---|---|
| Duplicate flags | Signals + groups + scripts | All | No | Preserves historical instance behavior |
| Visibility end/margin | Loader renderer settings / chunk size | All | Yes, between calls | Applied to every GeometryInstance3D descendant |
| Shadow mode | Loader `m2_cast_shadows` | All | Yes, between calls | Enables/disables descendant shadow casting |
| Native diagnostics | Loader `debug_streaming` | Debug | Yes | Collects indexed state only when requested |
## Persistence, cache and migration
No data is serialized and no cache/schema version changes. Editor owner
assignment remains a loader concern after attachment, so generated-scene
persistence behavior and migration remain unchanged.
## Diagnostics and observability
- The materializer emits no logs, metrics or debug views.
- Native state remains detached and is tagged with the absolute instance index.
- Loader preserves the existing normalized-path `M2_NATIVE_ANIMATOR` log text.
- No correlation ID is introduced; path/index remains the existing identity.
## Verification
- `verify_m2_animated_instance_materializer.gd` covers invalid input, names,
order, transforms, recursive visibility/margin/shadows, playback, native field
references, indexed diagnostics, prototype immutability and ownership boundaries.
- Playback/finalizer/pipeline/build/prototype/shutdown/material/facade/internal-
access regressions protect adjacent behavior.
- Fidelity evidence is exact extraction of existing flags/order/settings/calls;
no original-client visual comparison or proprietary asset result is claimed.
- Performance budget: 1,000 synthetic Node3D instances under one second.
## Extension points
A later facade-owned animated presentation command may supply the same explicit
batch contract. Async SceneTree mutation, callbacks and a generic scene factory
are intentionally excluded until measurements show a need.
## Capability status
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| Ordered animated instance/batch materialization | Implemented extraction | Synthetic batch contract verifier | Proprietary asset traversal pending |
| Recursive visibility/shadow settings | Implemented extraction | Nested geometry fixture | Visual distance/shadow comparison pending |
| Native/imported playback startup | Delegated to implemented controller | Playback + materializer verifiers | Full WoW animation-state mapping pending |
| Editor persistence | Existing loader-owned | Source boundary and editor helper | Editor integration fixture remains separate |
## Known gaps and risks
- The existing build planner must continue to guarantee valid transform bounds.
- Default animation selection and native deformation retain their documented gaps.
- No private asset, paired original-client, descriptor-pressure, leak or p95/p99 run exists.
- Recursive render mutation remains proportional to duplicate subtree size.
## Source map
| Path | Responsibility |
|---|---|
| `src/render/m2/m2_animated_instance_materializer.gd` | Duplicate, render mutation, playback startup and non-empty batch attachment |
| `src/render/m2/m2_animation_playback_controller.gd` | Phase, selection and native/imported playback mutation |
| `src/render/m2/m2_animated_scene_finalizer.gd` | AnimationPlayer inventory for each duplicate |
| `src/scenes/streaming/streaming_world_loader.gd` | Build adapter, diagnostic formatting and Editor ownership |
| `src/tools/verify_m2_animated_instance_materializer.gd` | Batch/render/playback/boundary/timing regression |
## Related decisions and references
- [`m2-animation-playback-controller.md`](m2-animation-playback-controller.md)
- [`m2-animated-scene-finalizer.md`](m2-animated-scene-finalizer.md)
- [`m2-build-batch-planner.md`](m2-build-batch-planner.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)
+212
View File
@@ -0,0 +1,212 @@
# M2 Animated Scene Finalizer
## Metadata
| Field | Value |
|---|---|
| Status | Implemented extraction |
| Target/work package | M03 / `M03-RND-M2-ANIMATED-SCENE-FINALIZER-001` |
| Owners | Detached animated candidate, material repair and player validation |
| Last verified | Worktree `work/sindo-main-codex/m03-m2-animated-scene-finalizer`, 2026-07-18 |
| Profiles/capabilities | Existing optional cached-GLB animated M2 path |
## Purpose
Finalize one loaded animated M2 scene on the main thread: instantiate a valid
detached Node3D candidate, copy the historical static-prototype material mapping
and accept only candidates containing AnimationPlayer descendants.
## Non-goals
- Call ResourceLoader or interpret threaded-load statuses.
- Choose animation eligibility, cache paths, permits or material prototypes.
- Adopt prototypes, mark static fallback or select/play animations.
- Process native M2 animators or static Mesh rebuilds.
## Context and boundaries
```mermaid
flowchart LR
ResourceFinalizer[M2AnimationResourceFinalizer] -->|loaded Resource and material source| Finalizer[M2AnimatedSceneFinalizer]
Finalizer -->|accepted Node3D and player count| ResourceFinalizer
ResourceFinalizer --> Cache[M2PrototypeCacheState]
```
Allowed dependencies are Godot scene/resource/material types. ResourceLoader,
filesystem, workers, scheduler, builders, raw repositories, prototype cache and
other application layers are forbidden.
## Public API
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|---|---|---|---|---|
| `instantiate_candidate(resource)` | Ownership query | Instantiate PackedScene as detached Node3D | Main thread; caller receives valid candidate | Unsupported/invalid null; created wrong-type root freed |
| `repair_materials(animated_root, material_source_root)` | Command | Apply historical material overrides | Main thread; borrowed roots | Null/empty roots no-op |
| `finalize_candidate(candidate_root)` | Ownership command/query | Require AnimationPlayer and transfer root/count | Main thread; detached candidate | Null empty; rejected candidate freed |
| `mesh_instances_in_subtree(root)` | Query | Depth-first MeshInstance3D inventory | Main thread; borrowed subtree | Null returns empty |
| `animation_players_in_subtree(root)` | Query | Depth-first AnimationPlayer inventory | Main thread; borrowed subtree | Null returns empty |
## Inputs and outputs
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|---|---|---|---|---|---|
| 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 | Detached animated candidate | Instantiator | Repair/final validation | Finalizer then caller/release | One attempt |
| 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 |
Side effects are PackedScene instantiation, surface override assignment and
synchronous destruction of rejected detached roots.
## Data flow
```mermaid
flowchart TD
Resource[Loaded Resource] --> Packed{PackedScene?}
Packed -->|no| Reject[Return null]
Packed -->|yes| Instantiate[Instantiate]
Instantiate --> Root{Node3D?}
Root -->|no| FreeWrong[Free root and reject]
Root -->|yes| Repair[Repair materials]
Repair --> Players{AnimationPlayer exists?}
Players -->|no| FreeCandidate[Free and reject]
Players -->|yes| Transfer[Transfer exact root and count]
```
## Lifecycle/state
```mermaid
stateDiagram-v2
[*] --> Absent
Absent --> Candidate: instantiate valid scene
Candidate --> Candidate: repair materials
Candidate --> Accepted: player found
Candidate --> Released: no player
Accepted --> [*]: ownership transferred
Released --> [*]
```
## Main sequence
```mermaid
sequenceDiagram
participant R as M2AnimationResourceFinalizer
participant F as M2AnimatedSceneFinalizer
participant C as M2PrototypeCacheState
R->>F: instantiate_candidate(Resource)
F-->>R: detached Node3D or null
R->>F: repair_materials(candidate, source)
R->>F: finalize_candidate(candidate)
alt accepted
F-->>R: exact Node3D and player count
R->>C: adopt animated prototype
else rejected
F-->>R: empty; candidate freed
R->>C: mark animation static
end
```
## Dependency diagram
```mermaid
flowchart TB
ResourceFinalizer[M2AnimationResourceFinalizer] --> Finalizer[M2AnimatedSceneFinalizer]
Finalizer --> Engine[PackedScene / Node3D / Mesh / Material / AnimationPlayer]
ResourceFinalizer --> Resource[ResourceLoader]
Loader --> Budget[RenderBudgetScheduler]
ResourceFinalizer --> Prototype[M2PrototypeCacheState]
Finalizer -. no dependency .-> Resource
Finalizer -. no dependency .-> Budget
Finalizer -. no dependency .-> Prototype
```
## Ownership, threading and resources
- Every method runs synchronously on the renderer main thread.
- A valid candidate is detached and finalizer-owned until acceptance.
- Acceptance transfers the exact Node3D to the resource-finalizer/cache path.
- Rejection frees the candidate synchronously, including wrong-type roots.
- Traversal results borrow Nodes; source materials remain Resource-owned.
## Errors, cancellation and recovery
| Failure | Detection | Behavior | Diagnostic | Recovery |
|---|---|---|---|---|
| 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 |
| 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 |
| 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 |
## Configuration and capabilities
| Setting/capability | Default | Profile | Runtime mutable | Effect |
|---|---|---|---|---|
| Finalize permit | `m2_animation_finalize_ops_per_tick = 1` | Existing profiles | Yes | Caller bounds calls |
| Material mapping | Depth-first/index-clamped | All | No | Preserves current appearance |
| Required players | At least one | All | No | Rejects non-animated scenes |
## Persistence, cache and migration
The service retains no state and serializes nothing. GLB/cache formats, material
versions and prototype lifetimes are unchanged; no rebake is required.
## Diagnostics and observability
- Accepted result reports player count for the existing `M2_ANIM_CACHE` log.
- Traversal order is deterministic depth-first preorder.
- The service owns no logging or metrics.
## Verification
- `verify_m2_animated_scene_finalizer.gd` covers invalid Resources, wrong-root
release, exact transfer, player traversal/count, override priority, fallback,
source-index mapping, source boundaries and timing.
- Adjacent animation pipeline/prototype/shutdown/material/build tests protect callers.
- Fidelity evidence is exact mapping/lifecycle extraction; no asset-backed or
original-client visual parity claim is made.
- Performance budget: 10,000 depth-eight player inventories under one second.
## Extension points
Animation playback policy and native animator field copying now belong to
`M2AnimationPlaybackController`; accepted instance/batch materialization is owned
by `M2AnimatedInstanceMaterializer`.
## Capability status
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| PackedScene candidate | Implemented extraction | Type/lifetime fixture | Proprietary corrupt corpus pending |
| Material repair | Implemented extraction | Exact-reference mapping | Asset-backed comparison pending |
| Player validation | Implemented extraction | Nested traversal/transfer | Multi-player GLB corpus pending |
## Known gaps and risks
- Material matching remains positional rather than semantic.
- Accepted prototypes remain unbounded until final loader shutdown.
- No private traversal, descriptor-pressure, p95/p99 or paired-client run exists.
## Source map
| Path | Responsibility |
|---|---|
| `src/render/m2/m2_animated_scene_finalizer.gd` | Candidate ownership, traversal, material repair and validation |
| `src/render/m2/m2_animation_playback_controller.gd` | Accepted-instance playback and native animator startup |
| `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_prototype_cache_state.gd` | Accepted prototype/static-only outcomes |
| `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 |
## Related decisions and references
- [`m2-animation-playback-controller.md`](m2-animation-playback-controller.md)
- [`m2-animation-load-pipeline-state.md`](m2-animation-load-pipeline-state.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)
@@ -0,0 +1,231 @@
# M2 Animation Load Pipeline State
## Metadata
| Field | Value |
|---|---|
| Status | Implemented extraction |
| Target/work package | M03 / `M03-RND-M2-ANIMATION-LOAD-PIPELINE-001` |
| Owners | Animated M2 threaded request records and terminal finalize FIFO |
| Last verified | Worktree `work/sindo-main-codex/m03-m2-animation-load-pipeline`, 2026-07-17 |
| Profiles/capabilities | Existing optional cached-GLB animated M2 path |
## Purpose
Own cross-frame bookkeeping between a successful animated M2 ResourceLoader
request, terminal polling and budgeted main-thread scene finalization. This is
an exact state extraction; cached animation eligibility and request admission
belong to `M2CachedAnimationResourceObserver`; terminal I/O/outcomes belong to
`M2AnimationResourceFinalizer`, while validation and retained Node lifetime
belong to `M2AnimatedSceneFinalizer` and `M2PrototypeCacheState`.
## Non-goals
- Select cache paths, interpret ResourceLoader statuses or perform I/O.
- Decide animated/static fallback or own prototype Nodes.
- Instantiate PackedScenes, repair materials or consume render permits.
- Merge the distinct animated and static-Mesh pipelines.
## Context and boundaries
```mermaid
flowchart LR
Observer[M2CachedAnimationResourceObserver] --> IO[ResourceLoader request]
Observer --> State[M2AnimationLoadPipelineState]
Finalizer[M2AnimationResourceFinalizer] --> IO
Finalizer --> State
State -->|detached pending records| Finalizer
Finalizer -->|opaque terminal status| State
State -->|completion FIFO| Finalizer
Loader[StreamingWorldLoader] --> Finalizer
Loader --> Budget[M2_ANIMATION_FINALIZE permit]
Loader --> Prototype[M2PrototypeCacheState]
```
Allowed dependencies are value containers, Strings and opaque integer statuses.
ResourceLoader, workers, scheduler, Node/Resource/Mesh ownership and other
renderer services are forbidden.
## Public API
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|---|---|---|---|---|
| `remember_request(normalized_relative_path, resource_path)` | Command/query | Insert one pending request | Renderer main thread; until transition/clear | Empty/duplicate false |
| `has_request(path)` | Query | Pending-request dedupe | Main thread | Empty/unknown false |
| `request_records_snapshot()` | Query | Detached pending records in insertion order | Main thread; caller-owned | None |
| `complete_request(path, terminal_status)` | Command/query | Move copied record into completion FIFO | Main thread after poll | Unknown false |
| `discard_request(path)` | Command/query | Remove without finalization | Main thread | Unknown false |
| `has_finalize_record()` / `pop_finalize_record()` | Query/command | Drain completion-order FIFO | Main-thread budget drain | Empty pop returns `{}` |
| `total_work_count()` | Query | Pending plus finalize metric | Main thread | None |
| `pending_request_count()` / `finalize_record_count()` | Query | Stage diagnostics | Main thread | None |
| `clear()` | Command | Drop bookkeeping after caller I/O drain/reset | Main thread | Idempotent; no I/O drain |
| `diagnostic_snapshot()` | Query | Detached paths/status records | Main thread | No Resources exposed |
## Inputs and outputs
| 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 | Opaque terminal status | Resource finalizer | Finalize FIFO | Integer value | Until pop/clear |
| Output | Detached pending records | State | Resource finalizer/shutdown adapter | Caller-owned copies | One pass |
| Output | Oldest completion record | State | Resource finalizer | Transferred Dictionary | One finalize attempt |
| Output | Detached diagnostics | State | Verifier/future metrics | Caller-owned copies | Snapshot lifetime |
Side effects are limited to collection mutation and retaining String/integer values.
## Data flow
```mermaid
flowchart TD
Start[Successful threaded request] --> Remember[Remember request]
Remember --> Poll[Resource finalizer polls detached snapshot]
Poll --> Terminal{Loaded or failed?}
Terminal -->|no| Poll
Terminal -->|yes| Complete[Complete with opaque status]
Complete --> FIFO[Finalize FIFO]
FIFO --> Permit{Permit available?}
Permit -->|no| FIFO
Permit -->|yes| Pop[Pop oldest record]
Pop --> Finalize[Resource finalizer loads/instantiates or marks static]
```
## Lifecycle/state
```mermaid
stateDiagram-v2
[*] --> Absent
Absent --> Pending: remember
Pending --> TerminalQueued: complete
Pending --> Absent: discard or clear
TerminalQueued --> Absent: pop or clear
```
## Main sequence
```mermaid
sequenceDiagram
participant O as CachedAnimationObserver
participant L as StreamingWorldLoader
participant F as AnimationResourceFinalizer
participant R as ResourceLoader
participant S as M2AnimationLoadPipelineState
participant P as M2PrototypeCacheState
O->>R: load_threaded_request(GLB)
O->>S: remember_request(path, GLB)
loop frames
L->>F: poll terminal requests
F->>S: request_records_snapshot()
F->>R: load_threaded_get_status(GLB)
end
F->>S: complete_request(path, status)
L->>F: prepare after permit
F->>S: pop_finalize_record()
F->>R: load_threaded_get(GLB)
F->>P: adopt animated prototype or mark static
```
## Ownership, threading and resources
- Main thread serializes all mutation.
- State owns only request/finalize Dictionaries with copied paths and statuses.
- Loader drains pending ResourceLoader paths before orderly shutdown clear.
- Resource finalizer owns terminal I/O/outcomes and composes scene validation;
prototype state owns accepted detached Nodes and static-only outcomes.
## Dependency diagram
```mermaid
flowchart TB
Observer[M2CachedAnimationResourceObserver] --> State[M2AnimationLoadPipelineState]
Observer --> Resource[ResourceLoader request]
Finalizer[M2AnimationResourceFinalizer] --> State[M2AnimationLoadPipelineState]
Finalizer --> Resource
Loader[StreamingWorldLoader] --> Finalizer
Loader --> Budget[RenderBudgetScheduler]
Loader --> Prototype[M2PrototypeCacheState]
State -. no dependency .-> Resource
State -. no dependency .-> Budget
State -. no dependency .-> Prototype
```
## Errors, cancellation and recovery
| Failure | Detection | Behavior | Diagnostic | Recovery |
|---|---|---|---|---|
| Empty/duplicate request | State guard | Reject unchanged | Contract verifier | Correct caller or request later |
| 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 |
| 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 |
| Shutdown | Loader drains pending paths | Clear state | Source/shutdown regressions | New loader starts empty |
## Configuration and capabilities
| Setting/capability | Default | Profile | Runtime mutable | Effect |
|---|---|---|---|---|
| `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 |
| Animated allow/deny/primitive rules | Existing values | Existing renderer profile | Yes | Observer filters before state insertion |
## Persistence, cache and migration
State is not serialized. Cache formats and prototype lifetimes are unchanged;
no rebake or migration is required.
## Diagnostics and observability
- `total_work_count()` preserves all three historical `m2_animation` metrics.
- Snapshots expose only paths and opaque statuses, never Resources or Nodes.
- Normalized M2 path is the correlation key; existing loader logs are unchanged.
## Verification
- `verify_m2_animation_load_pipeline_state.gd` covers validation, dedupe,
insertion order, completion FIFO, opaque status, discard, detached snapshots,
source boundaries and 100-by-256 timing.
- Adjacent renderer, scheduler, prototype and shutdown regressions cover callers.
- Fidelity evidence is exact state/lifecycle extraction; no visual 3.3.5a parity
or proprietary asset-backed claim is made.
- Performance budget: 25,600 request/complete/pop transitions under one second.
## Extension points
ResourceLoader polling/finalization now belongs to a sibling service without
changing this value-only state contract.
## Capability status
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| Animated request/finalize state | Implemented extraction | Synthetic contract/source/timing verifier | Asset-backed traversal/leak/p95/p99 pending |
| 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 |
## Known gaps and risks
- Dictionary records preserve the existing dynamic ResourceLoader boundary.
- `clear()` does not drain I/O; caller ordering remains mandatory.
- No private asset traversal, leak/descriptor-pressure or paired-client run is included.
## Source map
| Path | Responsibility |
|---|---|
| `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_prototype_cache_state.gd` | Animated prototype/static-only outcomes |
| `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 |
## Related decisions and references
- [`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-mesh-load-pipeline-state.md`](m2-mesh-load-pipeline-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)
@@ -0,0 +1,221 @@
# M2 Animation Playback Controller
## Metadata
| Field | Value |
|---|---|
| Status | Implemented extraction |
| Target/work package | M03 / `M03-RND-M2-ANIMATION-PLAYBACK-001` |
| Owners | Per-instance AnimationPlayer/native animator playback mutation |
| Last verified | Worktree `work/sindo-main-codex-m03-integrator/m03-closeout`, 2026-08-02 |
| Profiles/capabilities | Imported GLB and native experimental animated M2 instances |
## Purpose
Apply deterministic playback to one already duplicated M2 instance: copy native
animator runtime fields, derive a stable phase, prepare/phase native animators,
select an imported animation and configure linear looping/playback/seek.
## Non-goals
- Duplicate, name, transform or attach instances/batch roots.
- Apply visibility, shadows or Editor ownership.
- Load/finalize/cache animated scenes or decide eligibility.
- Change animation priorities, phase formula or native animator implementation.
## Context and boundaries
```mermaid
flowchart LR
Prototype[Animated prototype] --> Materializer[M2AnimatedInstanceMaterializer]
Materializer -->|source and duplicate| Playback[M2AnimationPlaybackController]
Finalizer[M2AnimatedSceneFinalizer player inventory] --> Playback
Playback --> Native[M2NativeAnimator mutation]
Playback --> Imported[AnimationPlayer mutation]
```
Allowed dependencies are supplied Nodes, Script identity, AnimationPlayer and
Animation resources. ResourceLoader, files, workers, caches, scheduler,
MultiMesh, SceneTree attachment and application layers are forbidden.
## Public API
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|---|---|---|---|---|
| `copy_native_animator_data(source_root, target_root, script)` | Command | Copy five runtime fields in depth-first pair order | Main thread; borrowed nodes | Null/script mismatch yields no pairs |
| `start_instance_playback(root, path, index, script, players, collect_diagnostics)` | Command/query | Start native/imported playback and optionally return native state | Main thread; one instance | Null/empty inventories no-op |
| `native_animators_in_subtree(root, script)` | Query | Exact-script depth-first inventory | Main thread; borrowed nodes | Null root/script returns empty |
| `phase_for_instance(path, index)` | Pure query | Stable phase in `[0, 1)` | Any thread; scalar | Historical hash behavior retained |
| `choose_default_animation(player, path)` | Pure engine query | Apply exact ordinary/fish/bird/fallback priority | Main thread; borrowed player | Null/no animations returns empty |
## Inputs and outputs
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|---|---|---|---|---|---|
| Input | Prototype and duplicate subtrees | M2 animated instance materializer | Native field copy | Borrowed Nodes | One duplicate |
| Input | Exact native animator Script | Loader composition | Native inventory | Borrowed Script | Call-local |
| Input | Relative path and instance index | Build batch adapter | Phase/selection | Copied values | One start |
| Input | AnimationPlayer inventory | Animated scene finalizer traversal | Imported playback | Borrowed players | One start |
| 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 |
Side effects are native field assignment, phased preparation calls, animation loop
mutation, play and seek. The service retains no inputs.
## Data flow
```mermaid
flowchart TD
Identity[Path and index] --> Phase[Stable hash phase]
NativeInventory[Exact-script native inventory] --> Prepare[prepare local runtime mesh]
Phase --> Prepare
Players[AnimationPlayers] --> Select[Choose path-specific default]
Select --> Loop[Set every animation LOOP_LINEAR]
Loop --> Play[Play selected name]
Phase --> Seek[Seek positive-length selection]
Prepare --> Diagnostics{Debug requested?}
Diagnostics -->|yes| Snapshot[Detached runtime state]
```
## Lifecycle/state
```mermaid
stateDiagram-v2
[*] --> Unstarted
Unstarted --> Prepared: copy native data and start
Prepared --> Playing: native phase and/or AnimationPlayer play
Playing --> Playing: repeated deterministic start
Playing --> [*]: instance owner releases subtree
```
The controller itself is stateless; lifecycle labels describe borrowed instance state.
## Main sequence
```mermaid
sequenceDiagram
participant M as M2AnimatedInstanceMaterializer
participant F as M2AnimatedSceneFinalizer
participant P as M2AnimationPlaybackController
participant N as M2NativeAnimator
participant A as AnimationPlayer
M->>P: copy_native_animator_data(prototype, duplicate, script)
M->>F: animation_players_in_subtree(duplicate)
F-->>M: ordered players
M->>P: start_instance_playback(path, index, players, debug)
P->>N: prepare_runtime_at_phase
P->>A: choose, loop, play and seek
P-->>M: optional detached native diagnostics
M-->>M: tag states with instance index
```
## Dependency diagram
```mermaid
flowchart TB
Loader[StreamingWorldLoader] --> Materializer[M2AnimatedInstanceMaterializer]
Materializer --> Playback[M2AnimationPlaybackController]
Materializer --> Finalizer[M2AnimatedSceneFinalizer]
Playback --> Engine[Node / Script / AnimationPlayer / Animation]
Materializer --> Batch[SceneTree batch materialization]
Loader --> NativeScript[M2NativeAnimator script]
Playback -. no dependency .-> ResourceLoader
Playback -. no dependency .-> Cache[M2PrototypeCacheState]
Playback -. no dependency .-> Scheduler[RenderBudgetScheduler]
```
## Ownership, threading and resources
- Materializer owns prototype duplication, instance/batch roots and SceneTree attachment.
- Materializer uses finalizer traversal for borrowed AnimationPlayer references.
- Controller borrows Nodes/Script/players only for the synchronous call.
- Native arrays are assigned by reference exactly as before extraction.
- Diagnostic Dictionaries are deep-duplicated before return.
- 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
| Failure | Detection | Behavior | Diagnostic | Recovery |
|---|---|---|---|---|
| Missing roots/script | Inventory guard | No native copy/start | Contract fixture | Correct composition |
| Fewer target animators | Pair-count minimum | Copy available pairs only | Native copy fixture | Rebuild import/prototype |
| No matching name | Ordered fallback | Substring, first name, then empty | Selection fixtures | Add compatible animation |
| Empty animation selection | Empty result | Skip loop/play/seek for that player | Selection fixture | Static/native path continues |
| Zero-length selected animation | Length guard | Play without seek | Playback contract | Imported timing remains engine-owned |
| Cancellation/shutdown | Outside service | No retained state | N/A | Loader owns subtree release |
## Configuration and capabilities
| Setting/capability | Default | Profile | Runtime mutable | Effect |
|---|---|---|---|---|
| Phase buckets | 1000 | All | No | Stable per path/index desynchronization |
| Loop mode | `LOOP_LINEAR` | All | No | Every available imported animation loops |
| Native diagnostics | `debug_streaming` | Debug | Yes | Samples state only when requested |
## Persistence, cache and migration
No state or format is serialized. GLB/native cache formats, prototype lifetime
and material versions are unchanged; no rebake is required.
## Diagnostics and observability
- Optional native states remain detached and contain existing prepared,
processing, mesh, bone, surface and length fields.
- Loader retains `M2_NATIVE_ANIMATOR` log formatting and path normalization.
- Controller emits no logs or metrics.
## Verification
- `verify_m2_animation_playback_controller.gd` covers exact phase, ordinary/
fish/bird priorities, substring/first fallback, loop/play/seek, native exact-
script order, five-field copy, single-rebuild phased preparation, idempotent
ready, detached diagnostics and boundaries.
- Finalizer/build/prototype/material/shutdown regressions protect adjacent behavior.
- Fidelity evidence is exact policy/mutation extraction; no private asset or
original-client animation comparison is claimed.
- Performance budget: 20,000 phase-and-selection pairs under one second.
## Extension points
Server-driven animation state may later replace default selection through a
separate presentation contract; this fallback controller must remain available
for world doodads and compatibility fixtures.
## Capability status
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| Imported default selection/playback | Implemented extraction | Synthetic priority/play/seek verifier | Asset-backed animation-name corpus pending |
| Native data/phase startup | Implemented extraction | Exact-reference/native state verifier | Native visual fidelity remains experimental |
| Instance/batch materialization | Implemented extraction | Materializer contract verifier | Asset-backed traversal pending |
## Known gaps and risks
- Hash phase intentionally depends on existing Godot String hashing behavior.
- Default-name heuristics are not a complete WoW animation-state mapping.
- 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
| Path | Responsibility |
|---|---|
| `src/render/m2/m2_animation_playback_controller.gd` | Phase, selection, native copy/start and imported playback |
| `src/render/m2/m2_animated_scene_finalizer.gd` | Accepted prototype/player inventory |
| `src/scenes/streaming/m2_native_animator.gd` | Experimental native deformation runtime |
| `src/render/m2/m2_animated_instance_materializer.gd` | Instance duplication/attachment and playback composition |
| `src/scenes/streaming/streaming_world_loader.gd` | Build adapter, diagnostic formatting and Editor ownership |
| `src/tools/verify_m2_animation_playback_controller.gd` | Policy/mutation/boundary/timing regression |
## Related decisions and references
- [`m2-animated-scene-finalizer.md`](m2-animated-scene-finalizer.md)
- [`m2-build-batch-planner.md`](m2-build-batch-planner.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,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)
+20 -9
View File
@@ -32,8 +32,8 @@ flowchart LR
Loader --> Planner
Planner --> Plan[Detached batch plan]
Plan --> Loader
Loader --> Ready{Resource ready?}
Ready --> Materialize[Animated or MultiMesh materialization]
Loader --> Dispatch[M2BuildDispatchPlanner]
Dispatch --> Materialize[Animated or MultiMesh materialization]
Loader --> Budget[RenderBudgetScheduler permit]
```
@@ -102,8 +102,10 @@ sequenceDiagram
## Ownership, threading and resources
- The planner owns only call-local scalar values and the returned Dictionary.
- The loader owns build-job Dictionaries, queue ordering, tile checks, resource
readiness/retry, serial numbers and group-index mutation.
- `M2BuildQueue` owns typed jobs, FIFO ordering, serial numbers and group/offset
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.
- The scheduler owns the frame-local `M2_BUILD` counter.
- 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
- A later package may define typed build-job state once queue/resource transitions
are extracted together with explicit cancellation and recovery.
- Remaining native resource observation may be extracted while retaining the
dispatch and typed build-job/FIFO contracts.
- Spatial-cell batching must use measured culling/performance evidence and must
not silently change this model-path batch cursor.
@@ -159,12 +161,15 @@ queue depth, build activity and hitch observability.
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| Static/animated batch cursor planning | Implemented extraction | Contract/source/timing verifier | Asset-backed p95/p99 pending |
| Build queue/resource state machine | Remains in loader | Existing lifecycle regressions | Stateful extraction pending |
| Typed build queue/cursor state | Implemented extraction | M2 build queue lifecycle verifier | Asset-backed traversal 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 |
## Known gaps and risks
- The result remains a Dictionary while loader build jobs are untyped Dictionaries.
- The planner result remains a Dictionary; `M2BuildQueue` adopts its scalar
cursor values through one explicit progress call.
- Negative offsets are preserved rather than rejected to avoid a hidden behavior change.
- Planning timing does not measure resource loading or materialization.
- Private asset-backed p95/p99 and visual comparison remain unavailable.
@@ -174,7 +179,12 @@ queue depth, build activity and hitch observability.
| Path | Responsibility |
|---|---|
| `src/render/m2/m2_build_batch_planner.gd` | Pure limit/count/cursor planning |
| `src/scenes/streaming/streaming_world_loader.gd` | Build job, queue, resources, materialization and budgets |
| `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/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_animation_playback_controller.gd` | Animated-instance phase/selection/native/imported playback |
| `src/render/m2/m2_animated_instance_materializer.gd` | Planned animated-slice duplication, playback startup and attachment |
| `src/render/m2/m2_placement_grouper.gd` | Produces grouped transform arrays |
| `src/tools/verify_m2_build_batch_planner.gd` | Formula, source and timing regression |
@@ -182,6 +192,7 @@ queue depth, build activity and hitch observability.
- [`m2-placement-grouper.md`](m2-placement-grouper.md)
- [`m2-placement-transform-resolver.md`](m2-placement-transform-resolver.md)
- [`m2-static-batch-materializer.md`](m2-static-batch-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)
+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)
+265
View File
@@ -0,0 +1,265 @@
# M2 Build Queue
## Metadata
| Field | Value |
|---|---|
| Status | Implemented |
| Target/work package | M03 / `M03-RND-M2-BUILD-QUEUE-001` |
| Owners | Pending M2 build-job records, FIFO tile keys and build cursors |
| Last verified | Worktree `work/sindo-main-codex/m03-m2-build-queue`, 2026-07-18 |
| Profiles/capabilities | Existing static MultiMesh and animated-instance build paths |
## Purpose
Own typed pending M2 build state outside `StreamingWorldLoader`: a keyed job
retains its M2 root, grouped transforms, insertion-order group keys and three
progress cursors, while a separate FIFO retains scheduling order and stale keys.
## Non-goals
- Decide tile eligibility, detail radius, resource readiness or retries.
- Consume scheduler permits or plan batch sizes.
- Create, attach or free Nodes, Meshes, MultiMeshes or animated instances.
- Load/cache resources, group placements or mutate tile state.
- Change model order, transform order, render settings or visible output.
## Context and boundaries
```mermaid
flowchart LR
Groups[M2PlacementGrouper result] --> Loader[StreamingWorldLoader adapter]
Loader --> Queue[M2BuildQueue]
Queue --> Job[M2BuildJob]
Queue --> Loader
Loader --> Planner[M2BuildBatchPlanner]
Loader --> Dispatch[M2BuildDispatchPlanner]
Loader --> Static[M2StaticBatchMaterializer]
Loader --> Animated[M2AnimatedInstanceMaterializer]
Loader --> Scheduler[RenderBudgetScheduler]
```
Allowed dependencies are String, Dictionary, Array, RefCounted and a strong
Node3D reference. SceneTree destruction, materializers, Mesh/MultiMesh,
RenderingServer/RIDs, ResourceLoader/files, workers/mutexes, scheduler policy,
gameplay, network and Editor APIs are forbidden.
## Public API
### `M2BuildJob`
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|---|---|---|---|---|
| `tile_key()` | Query | Immutable keyed identity | Main thread; job lifetime | None |
| `root()` | Query | Borrow strongly retained M2 root | Main thread; no ownership transfer | May be externally invalidated |
| `groups()` | Query | Borrow exact grouped-transform Dictionary | Main thread; job lifetime | None |
| `group_keys()` | Query | Borrow enqueue-time insertion-order key snapshot | Main thread; job lifetime | None |
| `group_index()` | Query | Current model-path cursor | Main thread | None |
| `transform_offset()` | Query | Current transform cursor within group | Main thread | None |
| `batch_serial()` | Query | Current batch naming serial | Main thread | None |
| `adopt_progress(group_index, offset, serial)` | Command | Atomically replace all progress values | Main thread | Raw integer semantics retained |
| `diagnostic_snapshot()` | Query | Detached scalar/count state | Any serialized caller | Omits engine references |
### `M2BuildQueue`
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|---|---|---|---|---|
| `enqueue(tile_key, root, groups)` | Command | Replace keyed job and append FIFO key | Main thread; until erase/clear | Invalid/empty input returns false |
| `has_pending/front_key/pop_front/rotate_front` | Commands/queries | Inspect, drain or rotate FIFO independently of job validity | Main thread | Empty returns false/empty String |
| `has_job/job_for` | Queries | Inspect keyed record, including stale-key distinction | Main thread | Missing returns false/null |
| `root_for/groups_for/group_keys_for` | Queries | Borrow current job inputs | Main thread | Stale returns null/empty collection |
| `group_index_for/transform_offset_for/batch_serial_for` | Queries | Read current progress | Main thread | Stale returns zero |
| `adopt_progress(tile_key, group_index, offset, serial)` | Command | Atomically update current job progress | Main thread | Stale returns false |
| `erase_job(tile_key)` | Command | Remove keyed job but preserve FIFO keys | Main thread | Unknown returns false |
| `job_keys()` | Query | Detached active-key snapshot for loader cleanup | Main thread | Empty returns empty Array |
| `clear()` | Command | Release all job and FIFO references | Main thread | Idempotent; never frees Nodes |
| `pending_count/active_job_count` | Queries | FIFO and keyed-job metrics | Main thread | None |
| `diagnostic_snapshot()` | Query | Detached FIFO and sorted job diagnostics | Main thread | None |
## Inputs and outputs
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|---|---|---|---|---|---|
| Input | Tile key and M2 root | Loader group-result finalizer | Queue/job | Copied String; strong borrowed Node reference | Until erase/clear |
| Input | Grouped transforms Dictionary | M2PlacementGrouper through loader mailbox | Job | Exact strong reference | Job lifetime |
| Input | `groups.keys()` order | Job constructor | Loader group selection | Job-owned Array snapshot | Job lifetime |
| Input | Next group/offset/serial | Planner/materializer adapter | Job progress | Copied integers | Until next adoption |
| Output | Front/rotated/popped tile key | Queue | Loader scheduling loop | Copied String | One drain step |
| Output | Borrowed root/groups/group keys | Job/queue | Loader readiness/materialization | No ownership transfer | One drain step |
| Output | Detached diagnostics/key snapshots | Queue/job | Tests/metrics/cleanup adapter | Caller-owned collections | Call result |
Side effects are keyed/FIFO collection mutation, strong-reference retention and
cursor mutation. No engine object is created, destroyed or attached.
## Data flow
```mermaid
flowchart TD
Enqueue[Tile key, root and grouped transforms] --> Job[Create or replace M2BuildJob]
Enqueue --> FIFO[Append tile key to FIFO]
FIFO --> Front[Borrow front key]
Front --> Current{Current keyed job exists?}
Current -->|no| Pop[Pop stale key]
Current -->|yes| Loader[Loader eligibility/readiness/materialization]
Loader -->|resource pending| Rotate[Move front key to tail]
Loader -->|operation complete| Progress[Adopt group, offset and serial]
Loader -->|finish/cancel| Erase[Erase job only]
Erase --> Pop
```
## Lifecycle/state
```mermaid
stateDiagram-v2
[*] --> Absent
Absent --> Queued: enqueue valid job and FIFO key
Queued --> Rotated: resource pending
Rotated --> Queued: returns to FIFO front
Queued --> Progressed: adopt progress
Progressed --> Queued: group remains
Queued --> StaleQueued: erase job before FIFO pop
Progressed --> StaleQueued: finish or cancel
StaleQueued --> Absent: pop stale key
Queued --> Absent: clear
StaleQueued --> Absent: clear
```
Duplicate enqueue replaces the current keyed job and adds another FIFO entry;
later erase can therefore leave multiple stale entries, matching historical raw state.
## Main sequence
```mermaid
sequenceDiagram
participant G as M2 group-result drain
participant Q as M2BuildQueue
participant L as StreamingWorldLoader
participant P as M2BuildBatchPlanner
participant M as M2 materializer
G->>Q: enqueue(tile key, root, groups)
L->>Q: front_key and has_job
Q-->>L: borrowed root/groups/keys and cursors
L->>P: plan selected transform slice
alt resource pending
L->>Q: rotate_front
else materializable
L->>M: materialize selected slice
L->>Q: adopt_progress(next index, offset, serial)
else cancelled or complete
L->>L: free/queue-free root if required
L->>Q: erase_job
L->>Q: pop_front
end
```
## Dependency diagram
```mermaid
flowchart TB
Loader[StreamingWorldLoader] --> Queue[M2BuildQueue]
Queue --> Job[M2BuildJob]
Job --> EngineBase[Node3D strong reference]
Loader --> Planner[M2BuildBatchPlanner]
Loader --> Materializers[Static and animated materializers]
Loader --> Scheduler[RenderBudgetScheduler]
Queue -. no dependency .-> SceneMutation[SceneTree destruction/attachment]
Queue -. no dependency .-> Resources[Mesh / MultiMesh / ResourceLoader]
```
## Ownership, threading and resources
- Queue owns keyed job records and FIFO String entries.
- 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.
- 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.
- Group worker results cross their existing mutex mailbox before enqueue.
## Errors, cancellation and recovery
| Failure/state | Detection | Behavior | Diagnostic | Recovery |
|---|---|---|---|---|
| Invalid enqueue input | Entry guard | Reject without mutation | Contract fixture | Correct caller composition |
| Duplicate tile key | Keyed assignment | Replace job and append FIFO key | Duplicate fixture | Stale entries drain normally |
| Stale FIFO key | `has_job == false` | Loader pops and continues without permit | Stale fixture | No action required |
| Resource pending | Loader cache state | Rotate one front key and consume existing permit | Rotation fixture/build diagnostics | Retry when key returns front |
| Root externally invalid | Loader validity check | Erase job and pop key | Loader source contract | Tile may regroup/requeue later |
| Tile cancelled | Loader policy | Queue-free root, erase job; FIFO key drains/pops | Shutdown/lifetime regression | Eligible tile may requeue |
| World clear | Loader job-key snapshot | Cancel roots, then clear queue | Shutdown regression | New map starts empty |
## Configuration and capabilities
The queue introduces no settings. Existing static/animated batch limits,
visibility/shadow settings, resource caches and `M2_BUILD` permits remain loader,
planner, materializer and scheduler contracts.
## 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
- `pending_count` preserves the existing FIFO-backed `m2_build` queue metric.
- `active_job_count` distinguishes keyed jobs from stale/duplicate FIFO entries.
- Diagnostic snapshots retain FIFO order and sort job records by tile key.
- Job diagnostics expose cursors/counts but never root/groups references.
- The module emits no logs.
## Verification
- `verify_m2_build_queue.gd` covers invalid input, exact groups/root retention,
FIFO, duplicate replacement, rotation, stale entries, atomic progress, erase/
clear engine lifetime, detached sorted diagnostics and loader boundaries.
- Planner, static/animated materializer, Mesh pipeline/cache/prototype, shutdown,
facade, internal-access and checkpoint regressions protect adjacent behavior.
- Fidelity evidence is exact state-transition extraction; visual rules are not changed.
- Performance budget: 100 cycles of 256 enqueue/rotate/erase operations under one second.
- No original-client or proprietary asset comparison is claimed for bookkeeping state.
## Extension points
Remaining resource readiness and dispatch logic may later move behind explicit
commands once its cancellation semantics are independently fixed. A generic
queue base, signals and callbacks are intentionally excluded.
## Capability status
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| 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 |
| Root destruction | Existing loader-owned | Lifetime/source/shutdown regressions | Keep outside state service |
| 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
- Grouped transforms remain untyped Dictionary/Array data.
- Queue permits duplicate and stale FIFO keys intentionally for compatibility.
- Job accessors return borrowed mutable groups/key collections; current loader is the sole consumer.
- Private asset traversal, long-flight p95/p99 and leak evidence remain unavailable.
## Source map
| Path | Responsibility |
|---|---|
| `src/render/m2/m2_build_job.gd` | Strong root/groups references, key snapshot and progress cursors |
| `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/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 |
## Related decisions and references
- [`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-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)
+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)
+39 -21
View File
@@ -38,7 +38,7 @@ flowchart LR
State --> FIFO[Finalize FIFO]
FIFO --> Loader
Loader --> Budget[M2_MESH_FINALIZE permit]
Loader --> Cache[M2 Mesh resource cache or shared missing cache]
Loader --> Cache[M2 Mesh resource cache or M2 prototype outcome state]
```
Allowed dependencies are value containers, Strings and opaque integer statuses.
@@ -78,7 +78,7 @@ Side effects are limited to collection mutation and retaining String/integer val
flowchart TD
Start[Successful ResourceLoader request] --> Remember[remember request]
Remember --> Snapshot[request records snapshot]
Snapshot --> Poll[Loader polls status]
Snapshot --> Poll[Mesh resource finalizer polls status]
Poll --> Active{In progress?}
Active -->|yes| Snapshot
Active -->|no| Complete[complete request with terminal status]
@@ -86,7 +86,9 @@ flowchart TD
FIFO --> Permit{Loader permit available?}
Permit -->|no| FIFO
Permit -->|yes| Pop[Pop oldest terminal record]
Pop --> Finalize[Loader gets Resource and adopts Mesh/missing state]
Pop --> Finalize[Mesh resource finalizer gets Resource and extracts first Mesh]
Finalize --> Prepare[M2RuntimeMeshFinalizer prepares Mesh]
Prepare --> Adopt[Loader adopts Mesh or marks prototype outcome state]
```
## Lifecycle/state
@@ -111,6 +113,7 @@ again only if loader cache/missing rules permit it.
```mermaid
sequenceDiagram
participant Loader as StreamingWorldLoader
participant Finalizer as M2MeshResourceFinalizer
participant Resource as ResourceLoader
participant State as M2MeshLoadPipelineState
participant Budget as RenderBudgetScheduler
@@ -118,14 +121,16 @@ sequenceDiagram
Resource-->>Loader: OK or ERR_BUSY
Loader->>State: remember_request(normalized, cache path)
loop frames
Loader->>State: request_records_snapshot()
Loader->>Resource: load_threaded_get_status(path)
Loader->>Finalizer: poll_terminal_requests(state, prototype cache)
Finalizer->>State: request_records_snapshot()
Finalizer->>Resource: load_threaded_get_status(path)
end
Loader->>State: complete_request(normalized, terminal status)
Finalizer->>State: complete_request(normalized, terminal status)
Loader->>Budget: try_consume_permit(M2_MESH_FINALIZE)
Loader->>State: pop_finalize_record()
Loader->>Resource: load_threaded_get(path)
Loader->>Loader: extract/refresh/adopt Mesh or mark missing
Loader->>Finalizer: finalize_next_resource(state, caches, directory)
Finalizer->>State: pop_finalize_record()
Finalizer->>Resource: load_threaded_get(path)
Finalizer->>Finalizer: extract/refresh/adopt Mesh or mark missing
```
## Dependency diagram
@@ -133,10 +138,12 @@ sequenceDiagram
```mermaid
flowchart TB
Loader[StreamingWorldLoader] --> State[M2MeshLoadPipelineState]
Loader --> Resource[ResourceLoader]
Loader --> MeshFinalizer[M2MeshResourceFinalizer]
MeshFinalizer --> State
MeshFinalizer --> Resource[ResourceLoader]
Loader --> Budget[RenderBudgetScheduler]
Loader --> MeshCache[M2 Mesh resource cache and shared missing cache]
Loader --> Classifier[M2RuntimeMeshRebuildClassifier]
Loader --> MeshCache[M2 Mesh resource and prototype outcome cache states]
Loader --> Finalizer[M2RuntimeMeshFinalizer]
State -. no dependency .-> Resource
State -. no dependency .-> Budget
State -. no dependency .-> MeshCache
@@ -146,9 +153,13 @@ flowchart TB
- Main thread serializes all state mutation and snapshots.
- State owns only request/finalize Dictionaries containing Strings and status integers.
- Loader owns ResourceLoader request lifetime and drains active paths before shutdown clear.
- `M2MeshResourceCacheState` owns prepared static Mesh references; the loader
owns shared missing state, material refresh, M2Builder and other engine resources.
- `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
`M2RuntimeMeshFinalizer` owns refresh/rebuild/fallback. The loader owns shared
adoption decisions, raw I/O and remaining engine resources; the prototype
cache service owns missing outcomes.
- Snapshot and diagnostic records are detached; caller mutation cannot affect state.
## Errors, cancellation and recovery
@@ -157,9 +168,9 @@ flowchart TB
|---|---|---|---|---|
| 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 |
| Non-terminal status | Loader 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 |
| Empty defensive path | Loader before poll | Discard and mark missing | Source regression | Correct request producer |
| Non-terminal status | Finalizer poll | Keep pending | Existing queue metric | Poll next frame |
| Terminal load failure | Status in popped record | Finalizer marks missing | Existing missing behavior | World/cache reload |
| 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 |
## Configuration and capabilities
@@ -202,9 +213,12 @@ rebuild policy are unchanged; no migration or rebake is required.
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| 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 materialization | Existing loader-owned | Renderer/material tests | Separate finalizer extraction 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 materialization | Existing loader-owned | Renderer/material tests | Further safe extraction pending |
## Known gaps and risks
@@ -218,8 +232,12 @@ rebuild policy are unchanged; no migration or rebake is required.
| Path | Responsibility |
|---|---|
| `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/scenes/streaming/streaming_world_loader.gd` | Cache path choice, I/O polling, permits and Mesh/missing adoption |
| `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_prototype_cache_state.gd` | Missing-model outcome retention |
| `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/tools/verify_m2_mesh_load_pipeline_state.gd` | Lifecycle/boundary/timing regression |
+33 -22
View File
@@ -13,8 +13,8 @@
## Purpose
Own the runtime cache of prepared static M2 `Mesh` references outside
`StreamingWorldLoader`. The loader remains responsible for finding, loading,
extracting, refreshing and materializing each Mesh.
`StreamingWorldLoader`. Dedicated services extract and prepare each Mesh; the
loader remains responsible for finding, loading, adopting and materializing it.
## Non-goals
@@ -33,9 +33,10 @@ flowchart LR
Cache -->|cached Mesh or null| Loader
Loader --> Pipeline[M2MeshLoadPipelineState]
Pipeline --> IO[ResourceLoader]
IO --> Prepare[Loader extraction and runtime preparation]
IO --> Extract[M2MeshResourceExtractor]
Extract --> Prepare[M2RuntimeMeshFinalizer]
Prepare --> Cache
Loader --> Build[M2 MultiMesh materialization]
Loader --> Build[M2StaticBatchMaterializer]
```
The cache may retain `Mesh` resources and copied normalized-path Strings. It has
@@ -70,13 +71,13 @@ Store and clear mutate only the cache Dictionary and Resource reference counts.
flowchart TD
Lookup[Lookup normalized M2 path] --> Cached{Mesh cached?}
Cached -->|yes| Return[Return retained Mesh]
Cached -->|no| Missing{Shared missing state?}
Cached -->|no| Missing{Prototype outcome says missing?}
Missing -->|yes| Null[Return null]
Missing -->|no| Load[Loader request or synchronous fallback]
Load --> Prepare[Loader extracts and prepares Mesh]
Load --> Prepare[Extractor and runtime finalizer prepare Mesh]
Prepare --> Valid{Prepared Mesh valid?}
Valid -->|yes| Store[Store or replace cache entry]
Valid -->|no| MarkMissing[Loader updates shared missing state]
Valid -->|no| MarkMissing[Loader updates prototype outcome state]
Store --> Return
```
@@ -102,9 +103,9 @@ sequenceDiagram
participant Pipeline as M2MeshLoadPipelineState
Loader->>Cache: find/has normalized path
alt cache miss
Loader->>Pipeline: request/poll/finalize record
Loader->>Loader: ResourceLoader get + extract + prepare
Loader->>Cache: store_mesh(path, prepared Mesh)
Loader->>Pipeline: request record
Loader->>Finalizer: poll/finalize one permitted record
Finalizer->>Cache: store_mesh(path, prepared Mesh)
end
Cache-->>Loader: exact retained Mesh
Loader->>Loader: materialize static M2 batch
@@ -116,8 +117,9 @@ sequenceDiagram
flowchart TB
Loader[StreamingWorldLoader] --> Cache[M2MeshResourceCacheState]
Loader --> Pipeline[M2MeshLoadPipelineState]
Loader --> Classifier[M2RuntimeMeshRebuildClassifier]
Loader --> Builder[M2Builder and MultiMesh]
Loader --> Finalizer[M2RuntimeMeshFinalizer]
Loader --> Builder[M2Builder]
Loader --> Materializer[M2StaticBatchMaterializer]
Cache --> Mesh[Godot Mesh Resource]
Cache -. no dependency .-> Pipeline
Cache -. no dependency .-> Classifier
@@ -129,7 +131,11 @@ flowchart TB
- Renderer main-thread adapters serialize every cache operation.
- The cache owns normalized-path Strings and strong Mesh references.
- Borrowed Mesh lookups do not transfer ownership or duplicate resources.
- The loader owns missing/prototype/animated state and all Nodes/MultiMeshes.
- `M2MeshResourceExtractor` owns first-Mesh selection and temporary PackedScene
instances. `M2PrototypeCacheState` owns missing/prototype/animated state; the
static materializer owns MultiMesh construction/attachment;
`M2MeshResourceFinalizer` owns resource adoption, while the loader owns
build-job and scheduler-permit decisions.
- The loader drains asynchronous work before the final cache clear.
## Errors, cancellation and recovery
@@ -144,8 +150,9 @@ flowchart TB
## Configuration and capabilities
The module adds no setting, budget or profile branch. Cache path order,
`M2_MATERIAL_REFRESH_VERSION`, finalize permits and batching remain loader-owned.
The module adds no setting, budget or profile branch. Cache path order, finalize
permits and batching remain loader-owned; refresh version `2` belongs to
`M2RuntimeMeshFinalizer`.
## Persistence, cache and migration
@@ -171,8 +178,8 @@ the historical Mesh cache had no queue contribution or log site.
## Extension points
- A separate M2 Mesh resource finalizer may later return a prepared Mesh for
`store_mesh` without changing cache ownership.
- `M2RuntimeMeshFinalizer` returns a prepared Mesh for `store_mesh` without
changing cache ownership.
- Eviction requires measured memory pressure and explicit lifetime semantics.
## Capability status
@@ -180,14 +187,14 @@ the historical Mesh cache had no queue contribution or log site.
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| 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 extraction/preparation | Existing loader-owned | Material/classifier regressions | Separate finalizer extraction next |
| 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 preparation | Implemented extraction | Runtime finalizer transition/rebuild verifier | Asset-backed material comparison pending |
## Known gaps and risks
- Strong Mesh references intentionally remain until final shutdown.
- Shared missing state cannot move here without also redesigning prototype and
animated lookup behavior.
- Missing/prototype/animated lookup state is now a separate sibling service.
- No proprietary M2 traversal, leak/descriptor-pressure, p95/p99 or paired
original-client capture is included.
@@ -196,7 +203,11 @@ the historical Mesh cache had no queue contribution or log site.
| Path | Responsibility |
|---|---|
| `src/render/m2/m2_mesh_resource_cache_state.gd` | Prepared Mesh references and final clear |
| `src/scenes/streaming/streaming_world_loader.gd` | Normalization, I/O, preparation, missing state and materialization |
| `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/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_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 |
+211
View File
@@ -0,0 +1,211 @@
# M2 Mesh Resource Extractor
## Metadata
| Field | Value |
|---|---|
| Status | Implemented extraction |
| Target/work package | M03 / `M03-RND-M2-MESH-RESOURCE-EXTRACTOR-001` |
| Owners | First-Mesh selection and temporary PackedScene instance lifetime |
| Last verified | Worktree `work/sindo-main-codex/m03-m2-mesh-resource-extractor`, 2026-07-17 |
| Profiles/capabilities | Static M2 threaded cache and synchronous raw/prototype fallback paths |
## Purpose
Select the first M2 Mesh from a direct Resource, PackedScene or existing Node
subtree while keeping traversal and temporary-node lifetime outside
`StreamingWorldLoader`.
## Non-goals
- Request/load Resources or choose cache paths.
- Load raw M2 data, refresh materials or rebuild Meshes.
- Own Mesh/prototype/missing caches or pending/finalize queues.
- Create MultiMeshes, attach Nodes or consume render permits.
- Change first-Mesh selection order or visible output.
## Context and boundaries
```mermaid
flowchart LR
Pipeline[M2 load pipeline] --> Loader[StreamingWorldLoader]
Loader --> Resource[Loaded Mesh or PackedScene Resource]
Resource --> Extractor[M2MeshResourceExtractor]
Extractor -->|first Mesh or null| Loader
Loader --> Prepare[M2RuntimeMeshFinalizer]
Prepare --> Cache[M2MeshResourceCacheState]
Prototype[Existing M2 Node subtree] --> Extractor
```
The extractor may inspect `Resource`, `PackedScene`, `Node`, `MeshInstance3D`
and `Mesh` engine types. It has no ResourceLoader, filesystem, worker, cache,
scheduler, builder, gameplay, network or editor dependency.
## Public API
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|---|---|---|---|---|
| `extract_first_mesh(resource)` | Query with temporary ownership | Return direct Mesh or first Mesh from one PackedScene instance | Renderer main thread; temporary root freed before return | Null/unsupported/empty returns null |
| `find_first_mesh_in_subtree(root)` | Query | Return first Mesh in historical depth-first preorder | Renderer main thread; caller-owned subtree unchanged | Null/no Mesh returns null |
## Inputs and outputs
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|---|---|---|---|---|---|
| Input | Direct Mesh Resource | Loader threaded finalizer | Extractor | Borrowed Resource | One call |
| Input | PackedScene Resource | Loader threaded finalizer | Extractor | Borrowed scene; extractor owns temporary instance | Temporary root until return |
| Input | Existing prototype/rebuild Node subtree | Loader builder/fallback | Extractor | Borrowed Node tree | One traversal |
| Output | First Mesh reference | Extractor | Loader preparation/cache adapter | Borrowed exact reference | Cache may retain later |
The only side effect is synchronous creation and immediate `free()` of a
temporary PackedScene instance.
## Data flow
```mermaid
flowchart TD
Input[Resource or Node subtree] --> Direct{Direct Mesh?}
Direct -->|yes| Return[Return exact Mesh]
Direct -->|no| Packed{PackedScene?}
Packed -->|yes| Instantiate[Instantiate temporary root]
Packed -->|no| Traverse[Traverse caller-owned subtree]
Instantiate --> TraverseTemp[Depth-first preorder traversal]
TraverseTemp --> Free[Free temporary root]
Free --> ReturnOrNull[Return first Mesh or null]
Traverse --> ReturnOrNull
```
## Lifecycle/state
```mermaid
stateDiagram-v2
[*] --> Stateless
Stateless --> TemporaryInstance: extract PackedScene
TemporaryInstance --> Traversing: instantiate succeeded
Traversing --> Stateless: free temporary root before return
Stateless --> Stateless: direct Mesh/subtree/invalid input
```
No state or Resource reference is retained between calls.
## Main sequence
```mermaid
sequenceDiagram
participant Finalizer as M2MeshResourceFinalizer
participant Extractor as M2MeshResourceExtractor
participant Scene as PackedScene
Finalizer->>Extractor: extract_first_mesh(loaded Resource)
alt direct Mesh
Extractor-->>Finalizer: same Mesh reference
else PackedScene
Extractor->>Scene: instantiate()
Scene-->>Extractor: temporary root
Extractor->>Extractor: depth-first first-Mesh search
Extractor->>Extractor: temporary_root.free()
Extractor-->>Finalizer: Mesh or null
end
Finalizer->>Finalizer: prepare and cache Mesh or mark missing
```
## Dependency diagram
```mermaid
flowchart TB
Finalizer[M2MeshResourceFinalizer] --> Extractor[M2MeshResourceExtractor]
Extractor --> Types[Resource / PackedScene / Node / Mesh]
Loader --> Pipeline[M2MeshLoadPipelineState]
Loader --> Cache[M2MeshResourceCacheState]
Loader --> Finalizer[M2RuntimeMeshFinalizer]
Extractor -. no dependency .-> Pipeline
Extractor -. no dependency .-> Cache
Extractor -. no dependency .-> Classifier
```
## Ownership, threading and resources
- The renderer main thread performs all traversal and PackedScene instantiation.
- Direct and subtree Mesh references are borrowed without duplication.
- A PackedScene temporary root is extractor-owned and synchronously freed.
- Existing prototype/rebuild subtrees remain caller-owned and are never mutated.
- Cache retention remains a cache-state concern; runtime material preparation
belongs to `M2RuntimeMeshFinalizer`.
## Errors, cancellation and recovery
| Failure/state | Detection | Behavior | Diagnostic | Recovery |
|---|---|---|---|---|
| Null/unsupported Resource | Type guard | Return null | Contract verifier | Loader marks missing/falls back |
| PackedScene instantiate failure | Null root | Return null | Existing loader behavior | Rebuild cache/source |
| No Mesh in subtree | Traversal exhaustion | Return null | Contract verifier | Loader marks missing/falls back |
| Null MeshInstance Mesh | Null traversal result | Parent continues with later siblings | Order verifier | Normal sibling search |
| Shutdown/cancel | No retained state | No action | Ownership docs | Caller owns surrounding job |
## Configuration and capabilities
The module adds no settings or profile branches. Cache format order, material
refresh version, render permits and batching remain unchanged outside it.
## Persistence, cache and migration
The extractor is stateless and serializes nothing. Existing `.tscn`/`.glb`
formats, cache versions and rebuild metadata are unchanged; no migration or
rebake is required.
## Diagnostics and observability
The module emits no logs and retains no counters. Loader correlation remains the
normalized M2 path. Its verifier measures traversal time and temporary Node count.
## Verification
- `verify_m2_mesh_resource_extractor.gd`: direct/invalid Resources, depth-first
child order, null-Mesh sibling continuation, subtree ownership, PackedScene
exact Mesh identity, temporary Node release, source boundaries and 10,000
bounded traversals.
- M2 pipeline/cache/classifier/material/shutdown/build regressions cover adjacent
behavior. No asset-backed visual parity claim is made.
## Extension points
- `M2MeshResourceFinalizer` passes the Mesh to `M2RuntimeMeshFinalizer` and
stores its result in `M2MeshResourceCacheState`.
- Broader generic scene traversal is intentionally excluded until another real
consumer requires the same exact contract.
## Capability status
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| Direct/PackedScene first-Mesh extraction | Implemented extraction | Resource/order/lifetime verifier | Asset-backed corrupt-scene fixture pending |
| Prototype/rebuild subtree selection | Implemented extraction | Synthetic preorder verifier | Asset-backed M2 traversal pending |
| Material refresh/rebuild finalization | Implemented extraction | Runtime finalizer transition/rebuild verifier | Asset-backed material comparison pending |
## Known gaps and risks
- PackedScene instantiation remains synchronous main-thread work behind the
existing finalize permit.
- The first Mesh rule intentionally ignores additional MeshInstances.
- No proprietary M2 corpus, corrupt cache, leak/descriptor-pressure, p95/p99 or
paired original-client capture is included.
## Source map
| Path | Responsibility |
|---|---|
| `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/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_mesh_resource_cache_state.gd` | Prepared Mesh retention |
| `src/tools/verify_m2_mesh_resource_extractor.gd` | Resource/order/lifetime/boundary/timing regression |
## Related decisions and references
- [`m2-mesh-resource-cache-state.md`](m2-mesh-resource-cache-state.md)
- [`m2-mesh-load-pipeline-state.md`](m2-mesh-load-pipeline-state.md)
- [`m2-runtime-mesh-rebuild-classifier.md`](m2-runtime-mesh-rebuild-classifier.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)
+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)
+258
View File
@@ -0,0 +1,258 @@
# M2 Prototype Cache State
## Metadata
| Field | Value |
|---|---|
| Status | Implemented extraction |
| Target/work package | M03 / `M03-RND-M2-PROTOTYPE-CACHE-001` |
| Owners | Detached static/animated prototypes and missing/static-animation outcomes |
| Last verified | Worktree `work/sindo-main-codex/m03-m2-prototype-cache`, 2026-07-17 |
| Profiles/capabilities | Static, GLB animated and native animated M2 prototype reuse |
## Purpose
Own the four shutdown-lifetime M2 prototype lookup states previously stored in
`StreamingWorldLoader`: static and animated `Node3D` references, missing model
paths and paths whose animation fallback is static-only.
## Non-goals
- Normalize paths, choose cache files, call ResourceLoader or parse raw M2 data.
- Build, repair or instantiate prototypes, Meshes, materials or MultiMeshes.
- Own animation/Mesh request queues, permits or tile/build state.
- Add eviction, persistence, retries, profile rules or format changes.
## Context and boundaries
```mermaid
flowchart LR
Loader[StreamingWorldLoader] --> State[M2PrototypeCacheState]
Observer[M2 resource observers] --> State
Resource[ResourceLoader / raw builders] --> Loader
Resource --> Observer
State --> Static[Static prototype Node3D]
State --> Animated[Animated prototype Node3D]
State --> Missing[Missing-model path set]
State --> StaticOnly[Static-animation path set]
Loader --> Instances[Duplicate/animated instance materialization]
```
The state may depend on `Node3D` lifetime APIs and value-only path containers.
Filesystem/native/ResourceLoader APIs, builders, worker pools, render scheduling,
Mesh traversal and other application layers are forbidden.
## Public API
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|---|---|---|---|---|
| `has_static_prototype(path)` | Query | Test for one valid retained static prototype | Renderer main thread; shutdown lifetime | Empty/invalid returns false |
| `find_static_prototype(path)` | Query | Return exact retained static Node reference | Renderer main thread; borrowed | Empty/unknown/invalid returns null |
| `adopt_static_prototype(path, prototype)` | Command/query | Retain first valid static prototype and return canonical reference | Renderer main thread; until shutdown | Invalid input returns null; later candidate released |
| `has_animated_prototype(path)` | Query | Test for one valid retained animated prototype | Renderer main thread; shutdown lifetime | Empty/invalid returns false |
| `find_animated_prototype(path)` | Query | Return exact retained animated Node reference | Renderer main thread; borrowed | Empty/unknown/invalid returns null |
| `adopt_animated_prototype(path, prototype)` | Command/query | Retain first valid animated prototype and return canonical reference | Renderer main thread; until shutdown | Invalid input returns null; later candidate released |
| `mark_model_missing(path)` / `is_model_missing(path)` | Command/query | Store/query a missing-model outcome | Renderer main thread; until shutdown | Empty mark rejected |
| `mark_animation_static(path)` / `is_animation_static(path)` | Command/query | Store/query static-only animation outcome | Renderer main thread; until shutdown | Empty mark rejected |
| `diagnostic_snapshot()` | Diagnostic query | Return detached sorted path arrays for all four states | Caller-owned result | Never exposes Nodes |
| `clear_and_release()` | Lifecycle command | Release prototypes and clear all state | Final shutdown after async drain | Idempotent |
## Inputs and outputs
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/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 animated Node3D | GLB/native animation adapter | Cache state | Adopted on success | 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 | Detached path-only snapshot | Cache state | Tests/diagnostics | Fresh caller-owned arrays | One query |
Side effects are strong Node retention, release of rejected duplicate candidates,
final Node destruction/queueing and mutation of runtime-only path state.
## Data flow
```mermaid
flowchart TD
Request[Normalized prototype request] --> Positive{Valid prototype cached?}
Positive -->|yes| Return[Return exact prototype]
Positive -->|no| Negative{Missing or static-only outcome?}
Negative -->|yes| Fallback[Return caller fallback]
Negative -->|no| Load[Loader performs existing I/O/build]
Load --> Success{Valid prototype?}
Success -->|yes| Adopt[Adopt first candidate]
Adopt --> Return
Success -->|no| Mark[Mark relevant negative outcome]
Mark --> Fallback
```
## Lifecycle/state
```mermaid
stateDiagram-v2
[*] --> Empty
Empty --> Positive: adopt static/animated prototype
Empty --> Negative: mark missing/static-only path
Positive --> Positive: lookup or same-reference adoption
Positive --> Positive: duplicate candidate released; first wins
Negative --> Negative: repeated mark/query
Positive --> Released: final shutdown
Negative --> Released: final shutdown
Released --> Released: repeated clear
Released --> [*]
```
Map refresh does not transition or clear this service. Final scene shutdown calls
`clear_and_release` only after worker and ResourceLoader work is drained.
## Main sequence
```mermaid
sequenceDiagram
participant Loader as StreamingWorldLoader
participant State as M2PrototypeCacheState
participant Source as ResourceLoader/raw builder
Loader->>State: find static/animated(path)
alt positive hit
State-->>Loader: exact retained Node3D
else negative hit
Loader->>State: is missing/static-only(path)
State-->>Loader: true; use historical fallback
else uncached
Loader->>Source: existing load/build operation
alt valid prototype
Loader->>State: adopt(path, prototype)
State-->>Loader: canonical Node3D
else failure
Loader->>State: mark missing/static-only(path)
end
end
Loader->>State: shutdown clear_and_release()
State-->>Loader: all paths empty; Nodes released
```
## Dependency diagram
```mermaid
flowchart TB
Loader[StreamingWorldLoader] --> State[M2PrototypeCacheState]
Observer[M2 resource observers] --> State
State --> Node3D
Loader --> Raw[M2RawModelRepository]
Observer --> ResourceLoader
Loader --> StaticBuilder[M2Builder]
Observer --> AnimatedBuilder[M2NativeAnimatedBuilder]
State -. no dependency .-> Raw
State -. no dependency .-> ResourceLoader
State -. no dependency .-> StaticBuilder
State -. no dependency .-> MeshCache[M2MeshResourceCacheState]
```
## Ownership, threading and resources
- Renderer main-thread adapters serialize all operations.
- The state owns strong references to adopted static and animated prototype Nodes.
- A duplicate detached candidate is released immediately; the first valid Node
stays canonical for that path.
- Lookups borrow exact Node references without transferring ownership.
- Detached Nodes are synchronously `free()`d at shutdown. Defensive in-tree
Nodes use `queue_free()` to preserve SceneTree rules.
- Negative entries own only copied normalized-path Strings.
## Errors, cancellation and recovery
| Failure/state | Detection | Behavior | Diagnostic | Recovery |
|---|---|---|---|---|
| Empty path/null/invalid Node | Admission guard | Reject/null/false | Dedicated verifier | Correct caller input |
| 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 |
| Model source/load failure | Loader result | Mark missing outcome | Existing fallback behavior | New loader session/source 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 |
| Cancellation | Not owned | No state transition inside this service | N/A | Loader drains/cancels before shutdown clear |
## Configuration and capabilities
| Setting/capability | Default | Profile | Runtime mutable | Effect |
|---|---|---|---|---|
| Positive cache lifetime | Final loader shutdown | All | No | Reuses detached prototypes across map refresh |
| Negative cache lifetime | Final loader shutdown | All | No | Preserves historical fallback suppression |
| Eviction capacity | Unbounded historical behavior | All | No | No mid-session Node destruction |
Animation enablement, candidate/allow/deny rules and cache paths remain loader
configuration consumed by the cached observer. Per-frame permits remain loader-owned.
## Persistence, cache and migration
This is runtime-only state and serializes nothing. `.tscn/.glb`, material refresh
and native M2 formats are unchanged; no migration or rebake is introduced.
## Diagnostics and observability
- `diagnostic_snapshot` exposes four sorted path arrays without Node references.
- 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
contributed work counts.
- Normalized relative path remains the correlation key.
## Verification
- `verify_m2_prototype_cache_state.gd`: invalid admission, exact static/animated
identity, independent positive/negative states, first-wins duplicate release,
detached/in-tree shutdown ownership, idempotent clear, detached diagnostics,
loader source boundaries and 100-by-256 bounded path-state timing.
- Runtime-cache shutdown regression verifies final release through the real loader.
- Raw repository/finalizer/extractor/Mesh cache/pipeline/material/animation/M2
build regressions protect adjacent behavior.
- Fidelity evidence is exact cache transition and lifetime extraction. No new
proprietary asset or original-client visual comparison is claimed.
## Extension points
Eviction or byte/count budgets require measured memory evidence and explicit
prototype-user lifetime rules. Cached and native animation observers consume
this state without moving ResourceLoader or builder ownership into cache state.
## Capability status
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| Static prototype retention | Implemented extraction | Identity/source/shutdown verifier | Asset-backed traversal/leak run pending |
| Animated prototype retention | Implemented extraction | Independent identity/shutdown verifier | Legal animated success fixture pending |
| Missing/static-only outcomes | Implemented extraction | Independent negative-state verifier | Recovery still requires new loader session |
| Bounded eviction | Historical unbounded behavior | Documented lifetime | Add only after memory profiling |
## Known gaps and risks
- Strong Node references intentionally live until final shutdown.
- Negative outcomes do not retain reason codes and cannot be individually retried,
preserving historical behavior.
- No proprietary long traversal, descriptor pressure, leak, p95/p99 or paired
original-client evidence is included.
## Source map
| Path | Responsibility |
|---|---|
| `src/render/m2/m2_prototype_cache_state.gd` | Positive Node ownership, negative path state and shutdown release |
| `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_render_runtime_cache_shutdown.gd` | Integrated final-shutdown release regression |
## Related decisions and references
- [`m2-raw-model-repository.md`](m2-raw-model-repository.md)
- [`m2-animated-scene-finalizer.md`](m2-animated-scene-finalizer.md)
- [`m2-animation-load-pipeline-state.md`](m2-animation-load-pipeline-state.md)
- [`m2-mesh-resource-cache-state.md`](m2-mesh-resource-cache-state.md)
- [`m2-mesh-load-pipeline-state.md`](m2-mesh-load-pipeline-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)
+235
View File
@@ -0,0 +1,235 @@
# M2 Raw Model Repository
## Metadata
| Field | Value |
|---|---|
| Status | Implemented extraction |
| Target/work package | M03 / `M03-RND-M2-RAW-MODEL-REPOSITORY-001` |
| Owners | Native M2Loader availability, file existence and raw Dictionary loading |
| Last verified | Worktree `work/sindo-main-codex/m03-m2-raw-model-repository`, 2026-07-17 |
| Profiles/capabilities | Static and native-animated raw M2 reads |
## Purpose
Provide one stateless boundary between renderer orchestration and the optional
native `M2Loader`. The repository resolves an extracted path and returns the raw
static or animated Dictionary consumed by existing builders and classifiers.
## Non-goals
- Normalize relative paths or choose `.tscn`/`.glb` cache fallbacks.
- Cache successful data, missing paths, Meshes, Nodes or animation decisions.
- Build geometry/materials, own queues or attach renderer objects.
- Change native parsing, cache formats, retry policy or visible behavior.
## Context and boundaries
```mermaid
flowchart LR
MeshFinalizer[M2MeshResourceFinalizer] --> Repository[M2RawModelRepository]
Loader[StreamingWorldLoader] --> Repository
Observer[M2NativeAnimationResourceObserver] --> Repository
Repository --> File[Extracted M2 file]
Repository --> Native[ClassDB M2Loader]
Native --> Raw[Raw Dictionary]
Raw --> MeshFinalizer
Raw --> Loader
Raw --> Observer
Loader --> Builder[Existing M2 builders/finalizer]
```
Allowed dependencies are `ProjectSettings`, `FileAccess` and `ClassDB` for the
native file boundary. Resource caches, builders, SceneTree types, worker pools,
renderer policy and other application layers are forbidden.
## Public API
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|---|---|---|---|---|
| `load_static_model_data(extracted_directory, normalized_relative_path)` | Query | Invoke native `load_m2` for one existing extracted file | Synchronous; stateless | Returns `{}` for rejected/unavailable/invalid result |
| `load_animated_model_data(extracted_directory, normalized_relative_path)` | Query | Invoke native `load_m2_animated` for one existing extracted file | Synchronous; stateless | Returns `{}` for rejected/unavailable/invalid result |
## Inputs and outputs
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|---|---|---|---|---|---|
| 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 | Extracted `.m2` bytes | Local legal extraction | Native M2Loader | File-owned | Native call |
| 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` | 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 |
Side effects are limited to file-existence inspection, synchronous native file
parsing and creation of one temporary native loader instance per accepted call.
## Data flow
```mermaid
flowchart TD
Request[Directory plus normalized path] --> Empty{Path empty?}
Empty -->|yes| Reject[Return empty Dictionary]
Empty -->|no| Class{M2Loader registered?}
Class -->|no| Reject
Class -->|yes| Resolve[globalize directory/path join]
Resolve --> Exists{File exists?}
Exists -->|no| Reject
Exists -->|yes| Instance[Instantiate M2Loader]
Instance --> Method{Instance and method available?}
Method -->|no| Reject
Method -->|yes| Parse[Call static or animated method]
Parse --> Type{Dictionary result?}
Type -->|no| Reject
Type -->|yes| Return[Return raw Dictionary]
```
## Lifecycle/state
```mermaid
stateDiagram-v2
[*] --> Ready: construct repository
Ready --> Resolving: load request
Resolving --> Ready: rejected input/unavailable dependency
Resolving --> Parsing: file and native method available
Parsing --> Ready: Dictionary or empty result returned
Ready --> [*]: caller releases reference
```
No request, result or failure state survives a call.
## Main sequence
```mermaid
sequenceDiagram
participant Caller as Loader or NativeAnimationObserver
participant Repo as M2RawModelRepository
participant File as FileAccess
participant Native as M2Loader
Caller->>Repo: load static/animated(directory, path)
Repo->>File: file_exists(globalized joined path)
alt dependency or file unavailable
Repo-->>Caller: empty Dictionary
else available
Repo->>Native: instantiate and call exact native method
Native-->>Repo: Variant
Repo-->>Caller: Dictionary or empty Dictionary
end
```
## Dependency diagram
```mermaid
flowchart TB
Loader[StreamingWorldLoader] --> Repository[M2RawModelRepository]
Observer[M2NativeAnimationResourceObserver] --> Repository
Repository --> ProjectSettings
Repository --> FileAccess
Repository --> ClassDB
ClassDB --> Native[M2Loader extension]
Loader --> Finalizer[M2RuntimeMeshFinalizer]
Loader --> StaticBuilder[M2Builder]
Observer --> AnimatedBuilder[M2NativeAnimatedBuilder]
Repository -. no dependency .-> Finalizer
Repository -. no dependency .-> StaticBuilder
Repository -. no dependency .-> Cache[Renderer caches/queues]
```
## Ownership, threading and resources
- The repository owns only call-local path, native instance and result values.
- 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.
- Native `M2Loader` owns parsing behavior and returns a new Dictionary value.
- Calls are synchronous on the caller's thread; current renderer callers use the
main thread. The module starts no tasks and touches no SceneTree objects.
- No file handle, native instance, Dictionary or engine Resource is retained.
## Errors, cancellation and recovery
| Failure | Detection | Behavior | Diagnostic | Recovery |
|---|---|---|---|---|
| Empty normalized path | String guard | Return `{}` | Dedicated verifier | Correct caller normalization |
| Native extension absent | `ClassDB.class_exists` | Return `{}` | Existing renderer fallback | Install/load extension and retry |
| Extracted file absent | `FileAccess.file_exists` | Return `{}` | Prototype cache records historical missing state through loader | Restore extraction and start a new loader session |
| Instance/method absent | Null/`has_method` guard | Return `{}` | Source contract verifier | Repair extension registration |
| Parse/non-Dictionary result | Result type check | Return `{}` | Existing loader fallback | Repair asset/parser and retry |
| Cancellation requested | Not supported inside synchronous native call | Call completes | None | Caller controls whether to start call |
## Configuration and capabilities
| Setting/capability | Default | Profile | Runtime mutable | Effect |
|---|---|---|---|---|
| Native class name | `M2Loader` | All | No | Exact GDExtension registration |
| Static method | `load_m2` | All | No | Existing static parser path |
| Animated method | `load_m2_animated` | Experimental animated path | No | Existing native animation parser |
| Extracted directory | Loader `extracted_dir` | Scene configuration | Yes, caller-owned | Root used for path join |
## Persistence, cache and migration
The module adds no persistence or cache. Native methods, normalized relative
paths and `.tscn/.glb`/material versions are unchanged, so no migration or rebake
is required.
## Diagnostics and observability
- Logs: none; loader retains existing fallback and debug output.
- Metrics: none; rejected-input timing is verifier-only.
- Debug views: none.
- Correlation ID: normalized relative M2 path in the caller.
## Verification
- `verify_m2_raw_model_repository.gd` covers empty/missing/repeated failures,
exact static/animated method ownership, three loader delegations, forbidden
dependencies and 10,000 empty-path rejects under one second.
- Adjacent finalizer/extractor/cache/pipeline/animation/material/shutdown tests
protect caller behavior and ownership.
- Fidelity evidence is structural: exact path join/globalization and native
method names are preserved. No proprietary M2 or original-client visual claim
is made by this extraction.
## Extension points
Future asset-backed verification may add a legal fixture around these value
contracts. Caching, asynchronous I/O and parser replacement require separate
measured work packages rather than expansion of this repository.
## Capability status
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| Static raw M2 read | Implemented extraction | Source/invalid/missing verifier | Legal success fixture pending |
| Native animated raw M2 read | Implemented extraction | Exact method/source contract | Legal animated fixture pending |
| Stateless failure behavior | Verified synthetic | Repeated missing call and source boundary | Asset-backed corrupt input pending |
## Known gaps and risks
- Native parsing remains synchronous and has no mid-call cancellation.
- The repository deliberately does not distinguish missing dependency, missing
file and parser failure; loader fallback behavior historically treats them alike.
- Proprietary asset traversal, descriptor pressure, p95/p99 and visual comparison
remain outside this structural extraction.
## Source map
| Path | Responsibility |
|---|---|
| `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/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/tools/verify_m2_raw_model_repository.gd` | Rejected-input/source/dependency/timing regression |
## Related decisions and references
- [`m2-runtime-mesh-finalizer.md`](m2-runtime-mesh-finalizer.md)
- [`m2-mesh-resource-cache-state.md`](m2-mesh-resource-cache-state.md)
- [`m2-mesh-load-pipeline-state.md`](m2-mesh-load-pipeline-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)
+225
View File
@@ -0,0 +1,225 @@
# M2 Runtime Mesh Finalizer
## Metadata
| Field | Value |
|---|---|
| Status | Implemented extraction |
| Target/work package | M03 / `M03-RND-M2-RUNTIME-MESH-FINALIZER-001` |
| Owners | Stale Mesh refresh version, rebuild classification, M2Builder rebuild and fallback |
| Last verified | Worktree `work/sindo-main-codex/m03-m2-runtime-mesh-finalizer`, 2026-07-17 |
| Profiles/capabilities | Static cached and prototype M2 runtime material preparation |
## Purpose
Finalize an extracted static M2 Mesh for runtime use by accepting already-loaded
raw M2 data, preserving material refresh version `2`, rebuilding only billboard/
UV-rotation cases and returning the historical original-Mesh fallback.
## Non-goals
- Read raw `.m2` files or call ClassDB/FileAccess/ResourceLoader.
- Own request/finalize queues, Mesh/missing/prototype caches or MultiMeshes.
- Select cache paths or extract a Mesh from the originally loaded Resource.
- Change classifier predicates, builder materials, refresh version or profiles.
## Context and boundaries
```mermaid
flowchart LR
ResourceFinalizer[M2MeshResourceFinalizer] -->|is raw data required?| Finalizer[M2RuntimeMeshFinalizer]
ResourceFinalizer --> Raw[M2RawModelRepository]
Raw --> Finalizer
Finalizer --> Classifier[M2RuntimeMeshRebuildClassifier]
Finalizer --> Builder[M2Builder]
Builder --> Subtree[M2MeshResourceExtractor]
Finalizer -->|prepared Mesh| Cache[M2MeshResourceCacheState]
```
The finalizer may use Mesh metadata, raw value Dictionaries, M2Builder and the
two existing scene-free M2 services. Raw-file I/O, cache state, render permits,
Nodes outside temporary rebuild roots and other application layers are forbidden.
## Public API
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|---|---|---|---|---|
| `requires_raw_data_for_refresh(mesh)` | Query | Tell loader whether stale Mesh needs raw-file lookup | Renderer main thread | Null/current false |
| `finalize_mesh(path, mesh, raw_data, extracted_directory)` | Command/query | Return current, marked, rebuilt or fallback Mesh | Renderer main thread; one finalize operation | Null Mesh returns null; invalid rebuild falls back |
| `clear()` | Command | Clear memoized classifier decisions | Map reset/final shutdown | Idempotent |
| `cached_rebuild_decision_count()` | Diagnostic query | Expose memoized path-decision count | Tests/diagnostics | None |
## Inputs and outputs
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|---|---|---|---|---|---|
| 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 resource finalizer | Classifier/M2Builder | Caller-owned value container | One call |
| 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 |
Side effects are Mesh refresh metadata, classifier memoization and temporary
M2Builder prototype creation/destruction during required rebuilds.
## Data flow
```mermaid
flowchart TD
Mesh[Extracted Mesh] --> Current{Refresh version >= 2?}
Current -->|yes| ReturnOriginal[Return original unchanged]
Current -->|no| NeedRaw[Repository supplies raw M2 data through loader]
NeedRaw --> HasData{Raw data available?}
HasData -->|no| Mark[Mark original version 2]
HasData -->|yes| Classify{Billboard or UV rotation?}
Classify -->|no| Mark
Classify -->|yes| Build[M2Builder temporary prototype]
Build --> Extract[M2MeshResourceExtractor first Mesh]
Extract --> Rebuilt{Rebuilt Mesh exists?}
Rebuilt -->|yes| ReturnRebuilt[Return rebuilt Mesh]
Rebuilt -->|no| Mark
Mark --> ReturnOriginal
```
## Lifecycle/state
```mermaid
stateDiagram-v2
[*] --> Current: Mesh metadata >= 2
[*] --> Stale: Mesh metadata < 2
Stale --> Current: empty/ordinary data marks original
Stale --> Rebuilding: classifier requires rebuild
Rebuilding --> Current: rebuilt Mesh returned
Rebuilding --> Current: failure marks original fallback
Current --> Current: later preparation is no-op
```
Classifier decisions are memoized per normalized path until either existing
map/reset clear site calls `clear()`.
## Main sequence
```mermaid
sequenceDiagram
participant ResourceFinalizer as M2MeshResourceFinalizer
participant Finalizer as M2RuntimeMeshFinalizer
participant Raw as M2Loader boundary
participant Classifier as RebuildClassifier
participant Builder as M2Builder
ResourceFinalizer->>Finalizer: requires_raw_data_for_refresh(mesh)
alt current Mesh
Finalizer-->>ResourceFinalizer: false; reuse Mesh
else stale Mesh
Finalizer-->>ResourceFinalizer: true
ResourceFinalizer->>Raw: load static raw Dictionary
ResourceFinalizer->>Finalizer: finalize_mesh(path, mesh, raw, extracted dir)
Finalizer->>Classifier: needs_runtime_mesh_rebuild(path, raw)
opt rebuild required
Finalizer->>Builder: build(raw, extracted dir)
Finalizer->>Finalizer: extract first Mesh; free prototype
end
Finalizer-->>ResourceFinalizer: rebuilt or marked fallback Mesh
end
```
## Dependency diagram
```mermaid
flowchart TB
ResourceFinalizer[M2MeshResourceFinalizer] --> Finalizer[M2RuntimeMeshFinalizer]
Finalizer --> Classifier[M2RuntimeMeshRebuildClassifier]
Finalizer --> Extractor[M2MeshResourceExtractor]
Finalizer --> Builder[M2Builder]
ResourceFinalizer --> Raw[M2RawModelRepository]
ResourceFinalizer --> Cache[M2MeshResourceCacheState]
Finalizer -. no dependency .-> Raw
Finalizer -. no dependency .-> Cache
```
## Ownership, threading and resources
- Renderer main thread executes metadata changes and M2Builder work.
- `M2RawModelRepository` owns raw M2 file/native calls; the Mesh resource
finalizer supplies its value result.
- Finalizer owns classifier memoization and temporary rebuild prototype lifetime.
- M2Builder owns construction rules; extractor selects the first rebuilt Mesh.
- Cache/prototype adapters decide where the returned Mesh reference is retained.
## Errors, cancellation and recovery
| Failure/state | Detection | Behavior | Diagnostic | Recovery |
|---|---|---|---|---|
| Null Mesh | Guard | Return null | Contract verifier | Caller marks missing |
| Current Mesh | Metadata query | Return exact reference; no raw load | Contract verifier | None needed |
| Raw file/parse unavailable | Empty Dictionary from loader | Mark/reuse original | Existing renderer behavior | Source/cache repair |
| Ordinary stale Mesh | Classifier false | Mark/reuse original | Classifier verifier | None needed |
| Builder produces no Mesh | Extractor null | Mark/reuse original | Finalizer fallback fixture | Source/builder repair |
| Reset/shutdown | Loader lifecycle | Clear classifier decisions | Source/clear verifier | Recompute next session |
## Configuration and capabilities
`MATERIAL_REFRESH_VERSION` remains `2` and is now owned by this finalizer.
Finalize permits, cache paths, extracted directory configuration and quality
profiles remain unchanged in the loader.
## Persistence, cache and migration
No new format is serialized. Existing Mesh metadata key
`wow_m2_material_refresh_version`, M2Builder `MATERIAL_FORMAT_VERSION == 2` and
`.tscn/.glb` caches are unchanged; no migration or rebake is introduced.
## Diagnostics and observability
`cached_rebuild_decision_count` exposes only a scalar count. Existing renderer
logs/queue metrics are unchanged. Normalized M2 path remains the correlation key.
## Verification
- `verify_m2_runtime_mesh_finalizer.gd`: null/current/raw requirement, empty and
ordinary marking, synthetic triangle billboard rebuild, rebuild failure
fallback, refresh metadata, classifier clear, source boundary and 100-by-256
bounded finalization.
- Classifier/extractor/cache/pipeline/material/shutdown/M2 build regressions cover
adjacent behavior. No asset-backed or original-client visual claim is made.
## Extension points
- Asset-backed fixture coverage can be added without changing the finalizer's
value-data contract.
- Refresh versions above `2` require explicit cache/fidelity evidence and docs.
## Capability status
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| Runtime Mesh refresh/fallback | Implemented extraction | Synthetic transition/rebuild verifier | Asset-backed corrupt/stale cache pending |
| Rebuild predicate | Implemented extraction dependency | Classifier fixtures | Original-client material comparison pending |
| Raw M2 refresh I/O | Implemented repository dependency | Repository source/invalid verifier | Legal success fixture pending |
## Known gaps and risks
- Required rebuild remains synchronous main-thread work behind existing permits.
- Synthetic geometry proves transition/build behavior, not full asset materials.
- No proprietary M2 traversal, descriptor-pressure/leak, p95/p99 or paired
original-client capture is included.
## Source map
| Path | Responsibility |
|---|---|
| `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_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_mesh_resource_extractor.gd` | First rebuilt-Mesh selection |
| `src/tools/verify_m2_runtime_mesh_finalizer.gd` | Transition/rebuild/boundary/timing regression |
## Related decisions and references
- [`m2-runtime-mesh-rebuild-classifier.md`](m2-runtime-mesh-rebuild-classifier.md)
- [`m2-mesh-resource-extractor.md`](m2-mesh-resource-extractor.md)
- [`m2-mesh-resource-cache-state.md`](m2-mesh-resource-cache-state.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)
@@ -29,13 +29,13 @@ runtime-cache lifetime.
```mermaid
flowchart LR
Cache[Stale cached M2 Mesh] --> Loader[StreamingWorldLoader]
Loader --> Raw[M2Loader raw Dictionary]
Cache[Stale cached M2 Mesh] --> Finalizer[M2RuntimeMeshFinalizer]
Loader[StreamingWorldLoader] --> Raw[M2Loader raw Dictionary]
Raw --> Classifier[M2RuntimeMeshRebuildClassifier]
Classifier --> Decision[Memoized rebuild boolean]
Decision --> Loader
Loader -->|true| Builder[M2Builder rebuild]
Loader -->|false| Refresh[Mark material refresh version]
Decision --> Finalizer
Finalizer -->|true| Builder[M2Builder rebuild]
Finalizer -->|false| Refresh[Mark material refresh version]
```
Allowed dependencies are Dictionary/Array, `PackedInt32Array`, `Vector4` and
@@ -56,8 +56,8 @@ WorkerThreadPool, Mesh/Node/RID ownership and other layers are forbidden.
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|---|---|---|---|---|---|
| Input | Normalized M2 relative path | Loader cache lookup | Classifier cache key | String value copied | Until clear |
| Input | Raw M2 batches/transforms/combos Dictionary | Native `M2Loader` adapter | Classifier | Borrowed; not mutated | First classification call per path |
| Output | Rebuild-required boolean | Classifier | Loader material-refresh adapter | Scalar | Memoized until clear |
| Input | Raw M2 batches/transforms/combos Dictionary | `M2RawModelRepository` through loader adapter | Classifier | Borrowed; not mutated | First classification call per path |
| Output | Rebuild-required boolean | Classifier | Runtime Mesh finalizer | Scalar | Memoized until clear |
| Output | Detached decision Dictionary | Classifier | Verifier/future diagnostics | Caller-owned copy | Snapshot lifetime |
Side effects are limited to inserting and clearing boolean memoization entries.
@@ -103,16 +103,18 @@ stateDiagram-v2
sequenceDiagram
participant Loader as StreamingWorldLoader
participant Native as M2Loader
participant Finalizer as M2RuntimeMeshFinalizer
participant Classifier as M2RuntimeMeshRebuildClassifier
participant Builder as M2Builder
Loader->>Native: load_m2(normalized path)
Native-->>Loader: raw Dictionary
Loader->>Classifier: needs_runtime_mesh_rebuild(path, data)
Classifier-->>Loader: memoized boolean
Loader->>Finalizer: finalize_mesh(path, mesh, data, dir)
Finalizer->>Classifier: needs_runtime_mesh_rebuild(path, data)
Classifier-->>Finalizer: memoized boolean
alt rebuild required
Loader->>Builder: rebuild mesh from raw data
Finalizer->>Builder: rebuild mesh from raw data
else no rebuild required
Loader->>Loader: stamp material refresh version
Finalizer->>Finalizer: stamp material refresh version
end
```
@@ -120,7 +122,8 @@ sequenceDiagram
```mermaid
flowchart TB
Loader[StreamingWorldLoader] --> Classifier[M2RuntimeMeshRebuildClassifier]
Loader[StreamingWorldLoader] --> Finalizer[M2RuntimeMeshFinalizer]
Finalizer --> Classifier[M2RuntimeMeshRebuildClassifier]
Loader --> Native[M2Loader]
Loader --> Builder[M2Builder]
Loader --> Cache[M2 Mesh/Scene caches]
@@ -132,8 +135,8 @@ flowchart TB
## Ownership, threading and resources
- The classifier owns only normalized-path boolean entries.
- The loader owns raw loading, cache/resource selection, Mesh replacement and
material refresh metadata.
- The finalizer owns classifier lifetime, rebuild decisions and refresh metadata.
- The loader owns raw loading, cache/resource selection and Mesh adoption.
- Raw Dictionaries are borrowed and never retained; only the computed boolean is cached.
- Current calls and clears are serialized on the renderer main thread.
- No Node, Resource, Mesh, material or RID reference crosses into the classifier.
@@ -154,7 +157,7 @@ flowchart TB
|---|---|---|---|---|
| Maximum texture stages inspected | `4` | Existing M2 material path | No | Preserves historical four-stage shader boundary |
| UV rotation-speed epsilon | `0.000001` exclusive | Existing M2 material path | No | Larger absolute speed requires rebuild |
| `M2_MATERIAL_REFRESH_VERSION` | Loader constant `2` | All | Code version | Gates whether classifier is called |
| `MATERIAL_REFRESH_VERSION` | Finalizer constant `2` | All | Code version | Gates whether classifier is called |
## Persistence, cache and migration
@@ -193,7 +196,7 @@ reset/shutdown sites. No persisted cache version or rebuild instruction changes.
| Billboard rebuild classification | Implemented extraction | Flag/count fixtures | Asset-backed cache refresh timing pending |
| UV-rotation rebuild classification | Implemented extraction | Combo/stage/threshold fixtures | More build-12340 material fixtures pending |
| Runtime memoization lifetime | Implemented extraction | First-decision/clear/source fixtures | Byte/count runtime metric not exposed |
| M2 mesh loading/finalization | Existing loader-owned | Material/shutdown regressions | Separate state extraction pending |
| Runtime Mesh finalization | Implemented extraction | Finalizer transition/rebuild fixtures | Asset-backed material comparison pending |
## Known gaps and risks
@@ -0,0 +1,224 @@
# M2 Static Batch Materializer
## Metadata
| Field | Value |
|---|---|
| Status | Implemented |
| Target/work package | M03 / `M03-RND-M2-STATIC-BATCH-MATERIALIZER-001` |
| Owners | Static M2 MultiMesh construction, render settings and attachment |
| Last verified | Worktree `work/sindo-main-codex/m03-m2-static-batch-materializer`, 2026-07-18 |
| Profiles/capabilities | Existing static M2 placement path |
## Purpose
Build and attach one static M2 `MultiMeshInstance3D` from an already prepared
Mesh and an ordered transform slice. The service preserves the existing
transform format, Mesh identity, batch name, visibility and shadow behavior.
## Non-goals
- Group placements, size batches or advance build-job cursors.
- Load, refresh, build or cache Mesh resources.
- Consume render permits, rotate queues or decide retries/cancellation.
- Assign Editor owners or release tile roots.
- Materialize animated instances or introduce spatial-cell batching.
## Context and boundaries
```mermaid
flowchart LR
Groups[Ordered transform group] --> Loader[StreamingWorldLoader build adapter]
MeshCache[Prepared Mesh cache] --> Loader
Planner[M2BuildBatchPlanner] --> Slice[Start and count]
Slice --> Loader
Loader --> Materializer[M2StaticBatchMaterializer]
Materializer --> Batch[Attached MultiMeshInstance3D]
Loader --> EditorOwner[Editor owner adapter]
```
Allowed dependencies are Godot `Mesh`, `MultiMesh`, `MultiMeshInstance3D`,
`GeometryInstance3D` and main-thread SceneTree APIs. Files, ResourceLoader,
workers, RenderingServer calls, caches, build jobs, scheduler policy, gameplay,
network and Editor ownership are forbidden.
## Public API
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|---|---|---|---|---|
| `materialize_batch(parent, path, mesh, transforms, start, count, serial, visibility_end, visibility_margin, cast_shadows)` | Command/query | Create, configure and attach one static MultiMesh batch | Main thread; borrowed inputs, parent owns result | Invalid/empty input returns null; transform bounds are a caller precondition |
## Inputs and outputs
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|---|---|---|---|---|---|
| Input | M2 parent root | Loader build job | Batch attachment | Borrowed Node3D | Tile/job lifetime |
| Input | Relative model path and serial | Loader build adapter | Batch node name | Copied values | One call |
| Input | Prepared static Mesh | M2 Mesh resource cache/finalizer | MultiMesh | Borrowed Resource; exact reference retained by MultiMesh | Cache and batch lifetimes |
| Input | Ordered transforms, start and count | Placement group and batch plan | MultiMesh transform buffer | Borrowed Array/scalars | One call |
| Input | Visibility end/margin and shadow flag | Renderer configuration | MultiMeshInstance3D properties | Copied scalars | One call |
| Output | Attached `MultiMeshInstance3D` | Materializer | Parent SceneTree and loader Editor-owner adapter | Parent-owned node/resource | Tile lifetime |
Side effects are MultiMesh allocation, transform-buffer mutation,
MultiMeshInstance3D allocation/configuration and SceneTree attachment. The
service retains no direct reference after return.
## Data flow
```mermaid
flowchart TD
Validate[Validate parent, Mesh, transforms and count] --> MultiMesh[Allocate TRANSFORM_3D MultiMesh]
MultiMesh --> MeshIdentity[Assign exact prepared Mesh]
MeshIdentity --> Count[Set instance count]
Count --> Upload[Upload transforms start plus ordered batch offset]
Upload --> Node[Create named MultiMeshInstance3D]
Node --> Render[Apply shadow and optional visibility end/margin]
Render --> Attach[Attach once to supplied parent]
Attach --> Return[Return attached node]
Validate -->|invalid| Null[Return null without attachment]
```
## Lifecycle/state
```mermaid
stateDiagram-v2
[*] --> Validating
Validating --> Rejected: invalid or empty input
Validating --> Building: valid slice
Building --> Configured: transforms and render properties set
Configured --> Attached: parent add_child
Rejected --> [*]
Attached --> [*]: parent/tile owner releases subtree
```
The `RefCounted` service is stateless; states describe a single call.
## Main sequence
```mermaid
sequenceDiagram
participant L as StreamingWorldLoader
participant P as M2BuildBatchPlanner
participant M as M2StaticBatchMaterializer
participant R as M2 parent root
L->>P: plan_batch(transform count, offset, static limits)
P-->>L: start/count and cursor transition
L->>L: resolve prepared Mesh or rotate pending job
L->>M: materialize_batch(parent, Mesh, transform slice, render settings)
M->>M: allocate MultiMesh and upload ordered transforms
M->>R: add_child(MultiMeshInstance3D)
M-->>L: attached node
L->>L: assign optional Editor owner
```
## Dependency diagram
```mermaid
flowchart TB
Loader[StreamingWorldLoader] --> Materializer[M2StaticBatchMaterializer]
Planner[M2BuildBatchPlanner] --> Loader
MeshState[M2MeshResourceCacheState] --> Loader
Materializer --> Engine[Mesh / MultiMesh / MultiMeshInstance3D]
Loader --> EditorOwner[Editor ownership]
Loader --> Scheduler[RenderBudgetScheduler]
Materializer -. no dependency .-> ResourceIO[ResourceLoader / files]
Materializer -. no dependency .-> BuildState[Build jobs / queues / caches]
```
## Ownership, threading and resources
- The caller borrows a prepared Mesh and transform Array for the synchronous call.
- The new MultiMesh retains the exact Mesh Resource reference.
- The new MultiMeshInstance3D retains the MultiMesh and transfers to the parent.
- Parent/tile teardown owns node and Resource release through the SceneTree.
- Loader assigns an Editor owner after successful attachment when configured.
- All Resource and SceneTree mutation is main-thread-only.
- No RID is acquired or released directly by this service.
## Errors, cancellation and recovery
| Failure | Detection | Behavior | Diagnostic | Recovery |
|---|---|---|---|---|
| Null parent or Mesh | Entry guard | Return null without allocation/attachment | Synthetic contract fixture | Correct build composition/cache state |
| Empty transforms or non-positive count | Entry guard | Return null | Synthetic contract fixture | Planner supplies non-empty slice |
| Invalid transform bounds | Caller precondition | Historical indexed access behavior remains | Build planner/source regression | Repair job cursor/group state |
| Mesh pending | Outside service | Loader rotates job without calling service | Existing build/cache diagnostics | Async completion retries job |
| Tile cancelled | Outside service | Loader cancels before materialization or releases parent later | Shutdown regression | Eligible tile may requeue |
The service does not swallow invalid bounds or silently clamp/reorder transforms,
because doing so would change the accepted build-planner contract.
## Configuration and capabilities
| Setting/capability | Default | Profile | Runtime mutable | Effect |
|---|---|---|---|---|
| Transform format | `MultiMesh.TRANSFORM_3D` | All | No | Preserves full 3D M2 placements |
| Static batch count | Loader `m2_multimesh_batch_size` through planner | Quality preset | Yes, between plans | Controls per-batch transform count |
| Visibility end/margin | Loader M2 range / chunk size | Quality preset | Yes, between calls | Applied only when end is positive |
| Shadow mode | Loader `m2_cast_shadows` | Quality preset | Yes, between calls | Selects ON/OFF casting setting |
## Persistence, cache and migration
No state is serialized and no cache/schema version changes. The service consumes
the accepted prepared Mesh cache contract; no rebake or migration is required.
## Diagnostics and observability
- The service emits no logs, metrics or debug views.
- Existing loader queue/hitch metrics continue to account for `m2build` work.
- Path/serial remain visible through the unchanged batch node name.
- Editor ownership remains observable through the existing loader adapter.
## Verification
- `verify_m2_static_batch_materializer.gd` covers invalid inputs, node naming,
exact Mesh identity, transform format/count, ordered slice source contract,
visibility, shadows, single attachment, ownership boundaries and timing.
- Build planner, Mesh cache/extractor/finalizer, prototype, animated materializer,
shutdown, materials, facade and internal-access regressions protect neighbors.
- The headless dummy renderer does not round-trip per-instance transforms through
`MultiMesh.get_instance_transform`; exact ordered upload is protected by the
source contract and render checkpoints remain the observable regression gate.
- Fidelity evidence is exact extraction only; no new build-12340 parity claim.
- Performance budget: 10,000 transform API writes under one second in headless.
## Extension points
Measured spatial-cell batching may later provide smaller transform slices while
reusing this materializer. A generic static/animated base class, async mutation
and callback framework are intentionally excluded.
## Capability status
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| Static M2 MultiMesh materialization | Implemented extraction | Synthetic node/resource/render/source/timing verifier | Asset-backed traversal pending |
| Transform ordering | Implemented extraction | Exact source contract and existing checkpoints | Headless per-instance getter unavailable |
| Editor persistence | Existing loader-owned | Source boundary and editor helper | Editor integration fixture remains separate |
| Spatial-cell batching | Planned | Renderer roadmap | Culling/performance design and evidence pending |
## Known gaps and risks
- Materialization remains synchronous main-thread work under the existing permit.
- MultiMesh transform upload timing in a headless dummy renderer does not measure GPU cost.
- No private asset traversal, visual comparison, descriptor-pressure, leak or p95/p99 run exists.
- Current model-path batching is not culling-driven spatial-cell batching.
## Source map
| Path | Responsibility |
|---|---|
| `src/render/m2/m2_static_batch_materializer.gd` | Static MultiMesh allocation, transform upload, render setup and attachment |
| `src/render/m2/m2_build_batch_planner.gd` | Static/animated batch count and cursor planning |
| `src/render/m2/m2_mesh_resource_cache_state.gd` | Prepared static Mesh reference ownership |
| `src/scenes/streaming/streaming_world_loader.gd` | Resource readiness, job/budget adapter and Editor ownership |
| `src/tools/verify_m2_static_batch_materializer.gd` | Node/resource/render/source/timing regression |
## Related decisions and references
- [`m2-build-batch-planner.md`](m2-build-batch-planner.md)
- [`m2-mesh-resource-cache-state.md`](m2-mesh-resource-cache-state.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)
@@ -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 |
| 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 |
| Liquid/swim query | Planned | Outside contract | M09/M12 world gameplay |
| Liquid/swim query | Planned | Outside contract | M10/M13 world gameplay |
## Known gaps and risks
+20 -8
View File
@@ -7,7 +7,7 @@
| Status | Implemented |
| Target/work package | M03 / `M03-RND-WMO-PLACEMENT-RESOLVER-001` |
| 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 |
## Purpose
@@ -31,14 +31,17 @@ live-prototype instance paths.
flowchart LR
Parsed[ADT/WDT WMO placement] --> Loader[StreamingWorldLoader adapter]
Loader --> Resolver[WmoPlacementResolver]
Loader --> Factory[WmoSceneInstanceFactory]
Factory --> Resolver
Resolver --> CacheKey[Normalized cache key]
Resolver --> Identity[Registry unique key]
Resolver --> Transform[World Transform3D]
CacheKey --> Cache[Loader WMO caches/requests]
Identity --> Registry[WmoPlacementRegistry]
Transform --> RenderRoot[Lightweight render root]
Transform --> Scene[Cached scene instance]
Transform --> Live[Live prototype instance]
Transform --> Factory
Factory --> Scene[Cached scene instance]
Factory --> Live[Live prototype instance]
```
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 |
| 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 | 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.
@@ -91,6 +94,7 @@ and shutdown require no resolver operation.
```mermaid
sequenceDiagram
participant Loader as StreamingWorldLoader
participant Factory as WmoSceneInstanceFactory
participant Resolver as WmoPlacementResolver
participant Registry as WmoPlacementRegistry
participant Instance as Render/cached/live instance
@@ -98,9 +102,15 @@ sequenceDiagram
Resolver-->>Loader: cache key
Loader->>Resolver: resolve_unique_key(placement, tile, index)
Resolver-->>Registry: identity adopted by loader
Loader->>Resolver: resolve_world_transform(placement)
Resolver-->>Loader: value Transform3D
Loader->>Instance: assign transform and attach/build
alt lightweight render root
Loader->>Resolver: resolve_world_transform(placement)
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
@@ -109,7 +119,9 @@ sequenceDiagram
- `WmoPlacementRegistry` owns placement-key reference sets. The loader owns its
key-to-Node map, cache/load-request state, jobs/queues, resource fallback and
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.
## 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
lightweight-WMO render paths outside the monolithic streamer. The state holder
accepts only caller-validated `Resource` references and records cache paths for
threaded requests whose I/O lifecycle remains in `StreamingWorldLoader`.
threaded requests whose terminal I/O lifecycle belongs to
`WmoRenderResourceFinalizer`.
## Non-goals
@@ -34,7 +35,8 @@ flowchart LR
State -->|cached Resource| Loader
Loader -->|cache path| ResourceLoader[Godot ResourceLoader]
ResourceLoader -->|status and loaded Resource| Loader
Loader --> Validate[Script and FORMAT_VERSION validation]
Loader --> Finalizer[WmoRenderResourceFinalizer]
Finalizer --> Validate[Script and FORMAT_VERSION validation]
Validate -->|accepted Resource or missing| State
Loader --> Fallback[Cached scene or live-prototype fallback]
```
@@ -53,8 +55,8 @@ or editor dependency. Cache validation stays at the I/O boundary in the loader.
| `has_request(path)` | Query | Test pending threaded-request state | Renderer main thread | Empty returns false |
| `remember_request(path, cache_path)` | Command/query | Record one loader-started request | Renderer main thread; until terminal/reset | Invalid or occupied state returns false |
| `request_paths_snapshot()` | Query | Copy pending normalized/cache-path mapping | Poll or shutdown drain | Detached Dictionary |
| `complete_request_with_resource(path, resource)` | Command/query | Remove pending request and adopt caller-validated Resource | Terminal loader poll | Unknown/null returns false |
| `complete_request_as_missing(path)` | Command/query | Remove pending request and adopt negative state | Terminal loader poll | Unknown returns false |
| `complete_request_with_resource(path, resource)` | Command/query | Remove pending request and adopt caller-validated Resource | Terminal finalizer poll | Unknown/null returns false |
| `complete_request_as_missing(path)` | Command/query | Remove pending request and adopt negative state | Terminal finalizer poll | Unknown returns false |
| `clear_transient_state()` | Command | Clear pending and missing while retaining accepted Resources | Map reset/request drain | Idempotent |
| `clear_all()` | Command | Release Resources, pending and missing | Final runtime cache release | Idempotent |
| `pending_request_count()` | Query | Preserve renderer queue metric contribution | Renderer diagnostics | None |
@@ -81,11 +83,11 @@ flowchart TD
Blocked -->|yes| Null[Return null; loader waits/falls back]
Blocked -->|no| Start[Loader starts threaded request]
Start --> Remember[remember_request]
Remember --> Poll[Loader polls detached request snapshot]
Remember --> Poll[Finalizer polls detached request snapshot]
Poll --> Terminal{Loaded or failed?}
Terminal -->|no| Poll
Terminal -->|failed| Missing[complete as missing]
Terminal -->|loaded| Validate[Loader validates script/version]
Terminal -->|loaded| Validate[Finalizer validates script/version]
Validate -->|valid| Adopt[complete with Resource]
Validate -->|invalid| Missing
```
@@ -113,28 +115,31 @@ stateDiagram-v2
```mermaid
sequenceDiagram
participant Loader as StreamingWorldLoader
participant Finalizer as WmoRenderResourceFinalizer
participant State as WmoRenderResourceCacheState
participant RL as ResourceLoader
Loader->>State: cached/missing/pending queries
Loader->>RL: exists + load_threaded_request(cache path)
Loader->>State: remember_request(normalized, cache path)
loop renderer tick
Loader->>State: request_paths_snapshot()
Loader->>RL: load_threaded_get_status(cache path)
Loader->>Finalizer: poll_terminal_requests(State)
Finalizer->>State: request_paths_snapshot()
Finalizer->>RL: load_threaded_get_status(cache path)
end
alt load failed
Loader->>State: complete_request_as_missing(normalized)
Finalizer->>State: complete_request_as_missing(normalized)
else loaded
Loader->>RL: load_threaded_get(cache path)
Loader->>Loader: validate script and FORMAT_VERSION
Loader->>State: complete with Resource or as missing
Finalizer->>RL: load_threaded_get(cache path)
Finalizer->>Finalizer: validate script and FORMAT_VERSION
Finalizer->>State: complete with Resource or as missing
end
```
## Ownership, threading and resources
- The state owns three Dictionaries and strong references to accepted Resources.
- The loader owns normalization, cache paths, ResourceLoader calls and validation.
- The loader owns normalization, cache paths and request admission; the finalizer
owns terminal ResourceLoader calls and validation.
- All mutation is serialized by the renderer main-thread lookup/drain lifecycle.
- No mutex or callback is needed; detached request snapshots permit safe removal.
- The loader/build queue borrow Resources without transferring ownership.
@@ -158,8 +163,8 @@ format version, request scheduling and WMO build budgets remain loader-owned.
## Persistence, cache and migration
The state is runtime-only and serializes nothing. `WMOStreamingResource` script
identity and `FORMAT_VERSION` validation remain unchanged in the loader; no cache
migration or rebuild is introduced by this extraction.
identity and `FORMAT_VERSION` validation remain unchanged in the finalizer; no
cache migration or rebuild is introduced by this extraction.
## Diagnostics and observability
@@ -189,7 +194,7 @@ states; Resource references and cache file paths are not exposed. No logs emit.
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| Lightweight WMO render Resource state | Implemented extraction | Lifecycle/source/timing and shutdown verifiers | Asset-backed traversal/leak evidence pending |
| Cache script/version validation | Preserved in loader | Source boundary and WMO regressions | Dedicated corrupt-cache fixture could follow |
| Cache script/version validation | Implemented extraction | Finalizer source and synthetic validation verifier | Serialized corrupt-cache fixture could follow |
| Cached WMO scene state | Implemented extraction | Scene-cache lifecycle/source/timing verifier | Asset-backed traversal/leak evidence pending |
| WMO materialization | Partial/loader-owned | Queue/planner regressions | Further safe extraction |
@@ -206,7 +211,8 @@ states; Resource references and cache file paths are not exposed. No logs emit.
| Path | Responsibility |
|---|---|
| `src/render/wmo/wmo_render_resource_cache_state.gd` | Resource/missing/request state and resets |
| `src/scenes/streaming/streaming_world_loader.gd` | Normalize, request, poll, validate, fallback and shutdown I/O |
| `src/render/wmo/wmo_render_resource_finalizer.gd` | Terminal polling, script/format validation and publication |
| `src/scenes/streaming/streaming_world_loader.gd` | Normalize, request admission, fallback and shutdown order |
| `src/tools/verify_wmo_render_resource_cache_state.gd` | State, boundary and timing regression |
| `src/tools/verify_render_runtime_cache_shutdown.gd` | Loader final Resource ownership regression |
@@ -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
scene paths. The state accepts only caller-validated `PackedScene` references and
records `.tscn` paths for threaded requests whose I/O, size limit and cache
metadata validation remain in `StreamingWorldLoader`.
records `.tscn` paths for threaded requests. Size admission remains in
`StreamingWorldLoader`; terminal I/O/probe validation belongs to
`WmoSceneResourceFinalizer`.
## Non-goals
@@ -33,8 +34,8 @@ flowchart LR
Loader --> State[WmoSceneResourceCacheState]
Loader --> Size[File existence and size limit]
Size --> ResourceLoader[Godot ResourceLoader]
ResourceLoader --> Loader
Loader --> Probe[Instantiate, metadata/version check, free]
ResourceLoader --> Finalizer[WmoSceneResourceFinalizer]
Finalizer --> Probe[Instantiate, metadata/version check, free]
Probe -->|accepted PackedScene or missing| State
Loader --> Live[Live-prototype fallback]
```
@@ -82,7 +83,7 @@ flowchart TD
Check -->|allowed| Request[Start threaded request]
Request -->|error| Mark
Request -->|accepted| Remember[remember_request]
Remember --> Poll[Loader polls snapshot]
Remember --> Poll[Scene Resource finalizer polls snapshot]
Poll -->|failure| CompleteMissing[complete as missing]
Poll -->|loaded| Validate[Instantiate and validate cache metadata]
Validate -->|valid| CompleteScene[complete with scene]
@@ -114,24 +115,27 @@ stateDiagram-v2
sequenceDiagram
participant Loader as StreamingWorldLoader
participant State as WmoSceneResourceCacheState
participant Finalizer as WmoSceneResourceFinalizer
participant RL as ResourceLoader
Loader->>State: cached/missing/pending queries
Loader->>Loader: exists and wmo_max_runtime_scene_mb check
Loader->>RL: load_threaded_request(.tscn)
Loader->>State: remember_request(normalized, path)
loop renderer tick
Loader->>State: request_paths_snapshot()
Loader->>RL: load_threaded_get_status(path)
Loader->>Finalizer: poll_terminal_requests(State)
Finalizer->>State: request_paths_snapshot()
Finalizer->>RL: load_threaded_get_status(path)
end
Loader->>RL: load_threaded_get(path)
Loader->>Loader: instantiate, validate WMO metadata, free probe
Loader->>State: complete with scene or as missing
Finalizer->>RL: load_threaded_get(path)
Finalizer->>Finalizer: instantiate, validate WMO metadata, free probe
Finalizer->>State: complete with scene or as missing
```
## Ownership, threading and resources
- 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.
- Detached request snapshots allow terminal removal while polling.
- 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 |
| 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 |
| 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 |
## Configuration and capabilities
@@ -184,8 +188,9 @@ in the loader; the state emits no logs.
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| 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 |
| ResourceLoader I/O and live fallback | Partial/loader-owned | Existing runtime behavior | Separate extraction if justified |
| Size admission | Preserved in loader | Source boundary and WMO regressions | Oversize asset fixture could follow |
| 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
@@ -199,7 +204,8 @@ in the loader; the state emits no logs.
| Path | Responsibility |
|---|---|
| `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_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)
+199 -19
View File
@@ -7,7 +7,7 @@
| 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 |
| Owners | Renderer workstream / milestone integrator |
| Last verified | Worktree `work/sindo-main-codex/m03-m2-mesh-resource-cache`, 2026-07-17 |
| Last verified | Worktree `work/sindo-main-codex/m03-wmo-scene-instance-factory`, 2026-08-01 |
| Profiles/capabilities | `Performance`, `Balanced`, `High`, `Custom`; Blizzlike fidelity incomplete |
## Purpose
@@ -54,6 +54,14 @@ flowchart LR
M2Transform --> Loader
Loader --> M2Batch[M2BuildBatchPlanner]
M2Batch --> Loader
Loader --> M2Dispatch[M2BuildDispatchPlanner]
M2Dispatch --> Loader
Loader --> M2Resources[M2BuildResourceSnapshot]
M2Resources --> M2Dispatch
Loader --> M2Queue[M2BuildQueue]
M2Queue --> Loader
Loader --> M2Static[M2StaticBatchMaterializer]
M2Static --> Scene
Loader --> WmoPlacement[WmoPlacementResolver]
WmoPlacement --> Loader
Loader --> WmoRegistry[WmoPlacementRegistry]
@@ -62,6 +70,12 @@ flowchart LR
WmoBuildStep --> Loader
Loader --> WmoBuildQueue[WmoRenderBuildQueue]
WmoBuildQueue --> Loader
Loader --> WmoGroupMaterializer[WmoRenderGroupMaterializer]
WmoGroupMaterializer --> Scene
Loader --> WmoScenePreparer[WmoRuntimeScenePreparer]
WmoScenePreparer --> Scene
Loader --> WmoInstanceFactory[WmoSceneInstanceFactory]
WmoInstanceFactory --> WmoScenePreparer
Native --> Parsed[Parsed tile/model data]
Parsed --> Loader
Loader --> Scene[SceneTree nodes]
@@ -132,15 +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 |
| `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 |
| `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 |
| `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 |
| `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 |
| `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 |
| `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 |
| `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 |
| `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 |
| `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 |
| `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 |
| `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 |
| `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 |
| `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 |
| `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 |
@@ -171,13 +207,26 @@ 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 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 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 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 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 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 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 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 | 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 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 M2 prototype state | Normalized path, adopted static/animated Node or negative outcome | Loader / `M2PrototypeCacheState` | Loader reuse/fallback adapters | State-owned strong Node refs and Strings; detached diagnostics | Until final shutdown |
| Output | Desired tile/detail operations | Streamer plan application | Finalize queues | Loader-owned | Cross-frame |
| Output | Terrain/M2/WMO/liquid instances | Loader/builders | Godot world/renderer | Loader/world owner | Main-thread attach |
| Output | Detached renderer metrics | `WorldRenderFacade` | Capture/baseline tools | Caller-owned deep copy | On demand |
@@ -225,7 +274,12 @@ flowchart TD
M2Registry --> M2Grouper[M2PlacementGrouper]
M2Transform[M2PlacementTransformResolver] --> M2Grouper
M2Grouper --> M2Batch[M2BuildBatchPlanner]
M2Batch --> M2
M2Grouper --> M2Queue[M2BuildQueue]
M2Queue --> M2Batch
M2Batch --> M2Resources[M2BuildResourceSnapshot]
M2Resources --> M2Dispatch[M2BuildDispatchPlanner]
M2Dispatch --> M2Static[M2StaticBatchMaterializer]
M2Static --> M2
R --> WmoPlacement[WmoPlacementResolver]
WmoPlacement --> WmoRegistry[WmoPlacementRegistry]
WmoRegistry --> WmoBuildQueue[WmoRenderBuildQueue]
@@ -349,19 +403,43 @@ sequenceDiagram
grouper owns worker-path final transforms; direct placeholder/instance
transforms and every build/render side effect remain loader-owned.
- `M2PlacementGrouper` is stateless and owns only call-local grouped transforms.
The loader retains tasks, mutex/result queues, stale checks and build state.
- `M2BuildBatchPlanner` is stateless and owns only call-local scalar plans. The
loader retains queue/resource transitions, cursor adoption and materialization.
The loader retains tasks, mutex/result queues and stale-result checks; accepted
groups enter `M2BuildQueue` as typed pending jobs.
- `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
references and group/offset/serial cursors without freeing engine objects.
`M2StaticBuildResourceObserver` owns static Mesh lookup/request admission and
snapshot adoption. `M2CachedAnimationResourceObserver` owns cached animated
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,
identity and transform values. `WmoPlacementRegistry` owns only placement-key
reference sets. `WmoRenderBuildStepPlanner` owns only a call-local operation
and cursor plan. `WmoRenderBuildQueue` owns typed pending jobs, FIFO keys and
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
and pending cache paths. `WmoSceneResourceCacheState` similarly owns validated
PackedScenes, negative entries and pending `.tscn` paths. The loader retains
ResourceLoader/FileAccess I/O, size and cache-version validation, live fallback,
materialization, permits, validity reactions and every Node lifecycle action.
and pending cache paths; `WmoRenderResourceFinalizer` owns its terminal
ResourceLoader polling and script/format validation. `WmoSceneResourceCacheState`
similarly owns validated PackedScenes, negative entries and pending `.tscn`
paths; `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
active task IDs and the worker-safe parsed-result mailbox. The loader retains
WorkerThreadPool start/wait, ADTLoader parsing, concurrency/finalize permits,
@@ -369,14 +447,37 @@ sequenceDiagram
performs the existing ADTBuilder call, tile attach and optional recursive
Editor ownership on the main thread without retaining the generated subtree.
- `M2RuntimeMeshRebuildClassifier` owns only normalized-path boolean decisions
for billboard/UV-rotation material refresh. The loader retains raw M2 loading,
refresh-version checks, Mesh rebuild/adoption and both historical clear sites.
for billboard/UV-rotation material refresh and is composed by the runtime Mesh
finalizer. The raw repository loads value data; loader retains Mesh adoption.
- `M2MeshLoadPipelineState` owns static M2 pending Resource paths, opaque
terminal statuses and completion-order finalize FIFO. The loader retains cache
path selection, ResourceLoader calls, permits, shared missing state and adoption.
terminal statuses and completion-order finalize FIFO. The static observer owns
cache path selection, request admission and initial snapshot adoption. The
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
them at the existing final-shutdown site. The loader retains extraction,
material refresh/rebuild, missing/prototype state and materialization.
them at the existing final-shutdown site. Prototype state and materialization
belong to the sibling cache service and loader respectively.
- `M2MeshResourceExtractor` owns depth-first first-Mesh selection and temporary
PackedScene instance destruction. The static observer admits ResourceLoader
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,
M2Builder rebuild and original-Mesh fallback. `M2MeshResourceFinalizer` loads
raw data only after the runtime finalizer reports that a cached Mesh is stale.
- `M2RawModelRepository` owns FileAccess/ClassDB availability and the exact
`load_m2`/`load_m2_animated` calls. The loader retains normalization, fallback
order and every result consumer; `M2PrototypeCacheState` retains outcomes.
- `M2PrototypeCacheState` owns exact static/animated prototype references,
missing-model and static-animation path sets, duplicate-candidate release and
final-shutdown Node cleanup. ResourceLoader, builders and fallback decisions
remain loader-owned.
- Rendered-ground query results and diagnostic snapshots are caller-owned values;
the facade never returns Mesh, Node, tile-state or queue references.
- Loaded-mesh ground sampling is renderer diagnostics, not authoritative gameplay
@@ -476,6 +577,15 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
transition, completion/raw integer behavior, source ownership and bounded timing.
- WMO render build queue contract: typed references/cursors, FIFO, duplicate
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,
validated/missing terminal transitions, transient/full reset, detached sorted
diagnostics, loader-owned version validation and bounded timing.
@@ -514,16 +624,36 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
|---|---|---|---|
| ADT streaming/terrain | Partial | M00 checkpoints and current scenes | Fidelity/performance gaps remain |
| Static M2 placement | Partial | MultiMesh/dedupe probes | Full materials/animation/effects |
| M2 static batch materializer | Implemented extraction | Synthetic node/Mesh/render/source/timing contract | Asset-backed traversal/visual/GPU p95/p99 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 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 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 animation playback controller | Implemented extraction | Synthetic phase/selection/loop/native-copy/source/timing contract | Asset-backed animation timing/name/native comparison pending |
| M2 animated instance materializer | Implemented extraction | Synthetic order/transform/render/playback/attachment/source/timing contract | Asset-backed traversal/visual/leak/p95/p99 pending |
| M2 prototype cache state | Implemented extraction | Synthetic identity/negative/lifecycle/source/timing plus shutdown contract | Asset-backed traversal/leak/p95/p99 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 render build step planner | Implemented extraction | Scene-free order/cursor/source/timing contract | Asset-backed traversal p95/p99 pending |
| WMO render build queue | Implemented extraction | Typed lifecycle/order/ownership/source/timing contract | Materialization and asset-backed traversal/leak evidence pending |
| WMO render Resource cache state | Implemented extraction | Scene-free lifecycle/exclusivity/source/timing plus shutdown contract | ResourceLoader I/O and asset-backed traversal/leak evidence pending |
| WMO 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 render build queue | Implemented extraction | Typed lifecycle/order/ownership/source/timing contract | 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 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 |
| 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 |
@@ -578,8 +708,32 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
| `src/render/liquid/adt_water_load_pipeline_state.gd` | ADT water request/task/result bookkeeping and worker-safe mailbox |
| `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_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_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_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_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_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_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_raw_model_repository.gd` | Stateless static/animated native M2 file boundary |
| `src/render/m2/m2_native_animation_resource_observer.gd` | Native candidate/read/build/cache observation |
| `src/render/m2/m2_prototype_cache_state.gd` | Detached prototype ownership and negative lookup outcomes |
| `src/render/wmo/wmo_render_resource_finalizer.gd` | Lightweight WMO terminal polling, validation and publication |
| `src/render/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_policy.gd` | Immutable renderer radius/prefetch policy |
| `src/render/streaming/streaming_target_plan.gd` | Immutable planner result with read-only tile-key sets |
@@ -595,8 +749,31 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
| `src/tools/verify_adt_water_load_pipeline_state.gd` | ADT water FIFO/task/thread/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_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_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_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_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_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_raw_model_repository.gd` | M2 invalid/missing/native-boundary/dependency/timing regression |
| `src/tools/verify_m2_native_animation_resource_observer.gd` | Native candidate/cache/raw/build/adoption/boundary/timing regression |
| `src/tools/verify_m2_prototype_cache_state.gd` | M2 prototype identity/negative/lifecycle/boundary/timing regression |
| `src/tools/verify_wmo_render_resource_finalizer.gd` | WMO render status/order/validation/adoption/boundary/timing regression |
| `src/tools/verify_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_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 |
@@ -608,6 +785,9 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
| `src/native/src/*_loader.cpp` | Native binary parsing |
| `src/tools/build_*cache.gd`, `src/tools/bake_*cache.gd` | Offline cache generation |
| `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/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 |
+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
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.
- `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.
+1
Submodule reference/benilla added at bc1a2428dd
@@ -0,0 +1,120 @@
class_name M2AnimatedInstanceMaterializer
extends RefCounted
## Duplicates one ordered animated M2 batch, applies instance render settings,
## starts playback and attaches a non-empty batch to the supplied parent.
## All calls mutate Godot scene objects and therefore belong on the main thread.
const M2_ANIMATED_SCENE_FINALIZER_SCRIPT := preload(
"res://src/render/m2/m2_animated_scene_finalizer.gd"
)
const M2_ANIMATION_PLAYBACK_CONTROLLER_SCRIPT := preload(
"res://src/render/m2/m2_animation_playback_controller.gd"
)
var _animated_scene_finalizer := M2_ANIMATED_SCENE_FINALIZER_SCRIPT.new()
var _animation_playback_controller := M2_ANIMATION_PLAYBACK_CONTROLLER_SCRIPT.new()
## Returns the attached batch root and detached diagnostic entries. Invalid or
## empty input, and a batch whose duplicates all fail, returns an empty result.
## The caller owns the returned/attached batch through `m2_parent_root`.
func materialize_batch(
m2_parent_root: Node3D,
relative_path: String,
prototype: Node3D,
transforms: Array,
start_index: int,
instance_count: int,
batch_serial: int,
visibility_range_end: float,
visibility_range_margin: float,
cast_shadows: bool,
native_animator_script: Script,
collect_native_diagnostics: bool
) -> Dictionary:
if (
m2_parent_root == null
or transforms.is_empty()
or instance_count <= 0
or prototype == null
):
return {}
var model_name := relative_path.get_file().get_basename()
var batch_root := Node3D.new()
batch_root.name = "%s_anim_%d" % [model_name, batch_serial]
var diagnostic_entries: Array[Dictionary] = []
for batch_offset in instance_count:
var instance_index := start_index + batch_offset
var instance := prototype.duplicate(
Node.DUPLICATE_SIGNALS | Node.DUPLICATE_GROUPS | Node.DUPLICATE_SCRIPTS
) as Node3D
if instance == null:
continue
instance.name = "%s_%d" % [model_name, instance_index]
instance.transform = transforms[instance_index]
_apply_visibility_range_recursive(
instance,
visibility_range_end,
visibility_range_margin
)
_apply_shadow_cast_recursive(instance, cast_shadows)
_animation_playback_controller.copy_native_animator_data(
prototype,
instance,
native_animator_script
)
batch_root.add_child(instance)
var animation_players := _animated_scene_finalizer.animation_players_in_subtree(
instance
)
var native_diagnostics := _animation_playback_controller.start_instance_playback(
instance,
relative_path,
instance_index,
native_animator_script,
animation_players,
collect_native_diagnostics
)
for native_diagnostic in native_diagnostics:
diagnostic_entries.append({
"instance_index": instance_index,
"state": native_diagnostic,
})
if batch_root.get_child_count() <= 0:
batch_root.queue_free()
return {}
m2_parent_root.add_child(batch_root)
return {
"batch_root": batch_root,
"native_diagnostics": diagnostic_entries,
}
func _apply_visibility_range_recursive(
node: Node,
range_end: float,
range_end_margin: float
) -> void:
if range_end <= 0.0:
return
if node is GeometryInstance3D:
var geometry := node as GeometryInstance3D
geometry.visibility_range_end = range_end
geometry.visibility_range_end_margin = range_end_margin
for child in node.get_children():
_apply_visibility_range_recursive(child, range_end, range_end_margin)
func _apply_shadow_cast_recursive(node: Node, cast_shadows: bool) -> void:
if node is GeometryInstance3D:
var geometry := node as GeometryInstance3D
geometry.cast_shadow = (
GeometryInstance3D.SHADOW_CASTING_SETTING_ON
if cast_shadows
else GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
)
for child in node.get_children():
_apply_shadow_cast_recursive(child, cast_shadows)
@@ -0,0 +1 @@
uid://dkgy3rvsmlkvm
@@ -0,0 +1,115 @@
class_name M2AnimatedSceneFinalizer
extends RefCounted
## Instantiates and validates detached animated M2 scene candidates and repairs
## their surface materials from an existing static material prototype.
## The caller owns ResourceLoader I/O, permits and prototype cache adoption.
## Instantiates one PackedScene as a detached Node3D candidate. Unsupported
## Resources return null; a created non-Node3D root is freed before return.
func instantiate_candidate(resource: Resource) -> Node3D:
if not (resource is PackedScene):
return null
var candidate_root := (resource as PackedScene).instantiate()
if candidate_root is Node3D:
return candidate_root as Node3D
if candidate_root != null:
candidate_root.free()
return null
## Applies the historical depth-first source-to-target material mapping. A null
## source or target leaves the candidate unchanged.
func repair_materials(animated_root: Node3D, material_source_root: Node3D) -> void:
if animated_root == null or material_source_root == null:
return
var source_meshes := mesh_instances_in_subtree(material_source_root)
var target_meshes := mesh_instances_in_subtree(animated_root)
if source_meshes.is_empty() or target_meshes.is_empty():
return
var fallback_material := _first_mesh_material(source_meshes)
for target_index in target_meshes.size():
var target := target_meshes[target_index]
if target == null or target.mesh == null:
continue
var source := source_meshes[mini(target_index, source_meshes.size() - 1)]
for surface_index in target.mesh.get_surface_count():
var material := _mesh_instance_surface_material(source, surface_index)
if material == null:
material = fallback_material
if material != null:
target.set_surface_override_material(surface_index, material)
## Accepts a detached candidate only when it contains an AnimationPlayer.
## Rejected candidates are freed synchronously. A successful result transfers
## the exact prototype reference and its AnimationPlayer count to the caller.
func finalize_candidate(candidate_root: Node3D) -> Dictionary:
if candidate_root == null:
return {}
var animation_players := animation_players_in_subtree(candidate_root)
if animation_players.is_empty():
candidate_root.free()
return {}
return {
"prototype": candidate_root,
"animation_player_count": animation_players.size(),
}
## Returns MeshInstance3D descendants in depth-first preorder, including root.
func mesh_instances_in_subtree(root: Node) -> Array[MeshInstance3D]:
var mesh_instances: Array[MeshInstance3D] = []
if root != null:
_collect_mesh_instances(root, mesh_instances)
return mesh_instances
## Returns AnimationPlayer descendants in depth-first preorder, including root.
func animation_players_in_subtree(root: Node) -> Array[AnimationPlayer]:
var animation_players: Array[AnimationPlayer] = []
if root != null:
_collect_animation_players(root, animation_players)
return animation_players
func _collect_mesh_instances(node: Node, result: Array[MeshInstance3D]) -> void:
if node is MeshInstance3D:
result.append(node as MeshInstance3D)
for child in node.get_children():
_collect_mesh_instances(child, result)
func _collect_animation_players(node: Node, result: Array[AnimationPlayer]) -> void:
if node is AnimationPlayer:
result.append(node as AnimationPlayer)
for child in node.get_children():
_collect_animation_players(child, result)
func _first_mesh_material(meshes: Array[MeshInstance3D]) -> Material:
for mesh_instance in meshes:
if mesh_instance == null or mesh_instance.mesh == null:
continue
for surface_index in mesh_instance.mesh.get_surface_count():
var material := _mesh_instance_surface_material(mesh_instance, surface_index)
if material != null:
return material
return null
func _mesh_instance_surface_material(
mesh_instance: MeshInstance3D,
surface_index: int
) -> Material:
if mesh_instance == null or mesh_instance.mesh == null:
return null
if surface_index < mesh_instance.get_surface_override_material_count():
var override_material := mesh_instance.get_surface_override_material(surface_index)
if override_material != null:
return override_material
if surface_index < mesh_instance.mesh.get_surface_count():
return mesh_instance.mesh.surface_get_material(surface_index)
return null
@@ -0,0 +1 @@
uid://cjjim3v675w66
@@ -0,0 +1,102 @@
class_name M2AnimationLoadPipelineState
extends RefCounted
## Owns animated M2 threaded-load request records and the terminal finalize FIFO.
## The caller owns ResourceLoader I/O, permits, Nodes and prototype outcomes.
var _request_by_normalized_path: Dictionary = {}
var _finalize_records: Array[Dictionary] = []
## Records one normalized M2 path and ResourceLoader path. Empty or duplicate
## values are rejected without mutation.
func remember_request(normalized_relative_path: String, resource_path: String) -> bool:
if normalized_relative_path.is_empty() or resource_path.is_empty():
return false
if _request_by_normalized_path.has(normalized_relative_path):
return false
_request_by_normalized_path[normalized_relative_path] = {
"normalized": normalized_relative_path,
"path": resource_path,
}
return true
## Returns whether one normalized path currently has a pending threaded request.
func has_request(normalized_relative_path: String) -> bool:
return (
not normalized_relative_path.is_empty()
and _request_by_normalized_path.has(normalized_relative_path)
)
## Returns detached pending records in their existing Dictionary insertion order.
func request_records_snapshot() -> Array[Dictionary]:
var records: Array[Dictionary] = []
for request_variant in _request_by_normalized_path.values():
var request: Dictionary = request_variant
records.append(request.duplicate())
return records
## Removes one pending request and appends its terminal status to the finalize
## FIFO. Unknown paths are rejected without mutation.
func complete_request(normalized_relative_path: String, terminal_status: int) -> bool:
if not _request_by_normalized_path.has(normalized_relative_path):
return false
var request: Dictionary = _request_by_normalized_path[normalized_relative_path]
_request_by_normalized_path.erase(normalized_relative_path)
var finalize_record := request.duplicate()
finalize_record["status"] = terminal_status
_finalize_records.append(finalize_record)
return true
## Removes one pending request without enqueuing finalization.
func discard_request(normalized_relative_path: String) -> bool:
return _request_by_normalized_path.erase(normalized_relative_path)
## Returns whether a terminal record is waiting for budgeted finalization.
func has_finalize_record() -> bool:
return not _finalize_records.is_empty()
## Removes and returns the oldest terminal record. Empty means none is ready.
func pop_finalize_record() -> Dictionary:
if _finalize_records.is_empty():
return {}
return _finalize_records.pop_front()
## Returns pending plus terminal-finalize work for existing renderer metrics.
func total_work_count() -> int:
return _request_by_normalized_path.size() + _finalize_records.size()
## Returns the pending request count.
func pending_request_count() -> int:
return _request_by_normalized_path.size()
## Returns the terminal finalize FIFO count.
func finalize_record_count() -> int:
return _finalize_records.size()
## Clears request/finalize bookkeeping. The caller must drain ResourceLoader
## requests before orderly shutdown clear.
func clear() -> void:
_request_by_normalized_path.clear()
_finalize_records.clear()
## Returns detached request and finalize records without loaded Resources/Nodes.
func diagnostic_snapshot() -> Dictionary:
var finalize_records: Array[Dictionary] = []
for record in _finalize_records:
finalize_records.append(record.duplicate())
return {
"requests": request_records_snapshot(),
"finalize_records": finalize_records,
}
@@ -0,0 +1 @@
uid://h5rk6njgaujd
@@ -0,0 +1,117 @@
class_name M2AnimationPlaybackController
extends RefCounted
## Applies deterministic playback to one animated M2 instance and copies the
## runtime fields of exact-script native animators. The materializer owns
## instance duplication/attachment; its caller owns diagnostic log formatting.
## Copies the five historical runtime fields between matching native animators
## in depth-first order. References are intentionally retained exactly.
func copy_native_animator_data(
source_root: Node,
target_root: Node,
native_animator_script: Script
) -> void:
var source_animators := native_animators_in_subtree(source_root, native_animator_script)
var target_animators := native_animators_in_subtree(target_root, native_animator_script)
for animator_index in range(mini(source_animators.size(), target_animators.size())):
var source := source_animators[animator_index]
var target := target_animators[animator_index]
target.mesh_instance_path = source.mesh_instance_path
target.bones = source.bones
target.surfaces = source.surfaces
target.animation_length = source.animation_length
target.playback_speed = source.playback_speed
## Prepares native animators, applies deterministic phase, and starts supplied
## AnimationPlayers with the historical loop/name/seek rules. Runtime debug
## state is sampled only when requested and returned as detached Dictionaries.
func start_instance_playback(
root: Node,
relative_path: String,
instance_index: int,
native_animator_script: Script,
animation_players: Array[AnimationPlayer],
collect_native_diagnostics: bool
) -> Array[Dictionary]:
var phase := phase_for_instance(relative_path, instance_index)
var native_diagnostics: Array[Dictionary] = []
for animator in native_animators_in_subtree(root, native_animator_script):
if animator.has_method("prepare_runtime_at_phase"):
animator.prepare_runtime_at_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"):
var diagnostic_variant = animator.runtime_debug_state()
if diagnostic_variant is Dictionary:
native_diagnostics.append((diagnostic_variant as Dictionary).duplicate(true))
for player in animation_players:
if player == null:
continue
var animation_name := choose_default_animation(player, relative_path)
if animation_name.is_empty():
continue
for available_name in player.get_animation_list():
var available_animation := player.get_animation(available_name)
if available_animation != null:
available_animation.loop_mode = Animation.LOOP_LINEAR
player.play(animation_name)
var selected_animation := player.get_animation(animation_name)
if selected_animation != null and selected_animation.length > 0.0:
player.seek(selected_animation.length * phase, true)
return native_diagnostics
## Returns exact-script native animators in depth-first preorder.
func native_animators_in_subtree(
root: Node,
native_animator_script: Script
) -> Array[Node]:
var native_animators: Array[Node] = []
if root != null and native_animator_script != null:
_collect_native_animators(root, native_animator_script, native_animators)
return native_animators
## Returns the existing stable path/index phase in the inclusive-lower,
## exclusive-upper range [0, 1).
func phase_for_instance(relative_path: String, instance_index: int) -> float:
return float(abs(("%s:%d" % [relative_path, instance_index]).hash()) % 1000) / 1000.0
## Chooses the historical default animation name for ordinary, fish and bird
## paths, then case-insensitive substring and first-name fallbacks.
func choose_default_animation(player: AnimationPlayer, relative_path: String = "") -> String:
if player == null:
return ""
var lower_path := relative_path.to_lower()
var candidates: Array[String] = ["Stand", "Idle", "Run", "Walk"]
if lower_path.contains("fish"):
candidates = ["Run", "Walk", "Swim", "Stand", "Idle", "Death"]
elif lower_path.contains("eagle") or lower_path.contains("bird") or lower_path.contains("gull"):
candidates = ["Run", "Walk", "Swim", "Stand", "Idle"]
for candidate in candidates:
if player.has_animation(candidate):
return candidate
var available_names := player.get_animation_list()
for candidate in ["run", "walk", "swim", "stand", "idle", "loop"]:
for available_name in available_names:
if String(available_name).to_lower().contains(candidate):
return String(available_name)
return String(available_names[0]) if not available_names.is_empty() else ""
func _collect_native_animators(
node: Node,
native_animator_script: Script,
result: Array[Node]
) -> void:
if node.get_script() == native_animator_script:
result.append(node)
for child in node.get_children():
_collect_native_animators(child, native_animator_script, result)
@@ -0,0 +1 @@
uid://c6oln217sml80
@@ -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
+78
View File
@@ -0,0 +1,78 @@
class_name M2BuildJob
extends RefCounted
## Holds one M2 build cursor, grouped transforms and a strong root reference.
## Releasing this record never frees the referenced Node.
var _tile_key: String
var _root: Node3D
var _groups: Dictionary
var _group_keys: Array
var _group_index: int = 0
var _transform_offset: int = 0
var _batch_serial: int = 0
func _init(tile_key: String, root: Node3D, groups: Dictionary) -> void:
_tile_key = tile_key
_root = root
_groups = groups
_group_keys = groups.keys()
## Returns the immutable tile identity used by the queue.
func tile_key() -> String:
return _tile_key
## Returns the strongly referenced M2 scene root without transferring ownership.
func root() -> Node3D:
return _root
## Returns the exact grouped-transform Dictionary supplied at enqueue time.
func groups() -> Dictionary:
return _groups
## Returns the insertion-order group-key snapshot created at enqueue time.
func group_keys() -> Array:
return _group_keys
## Returns the current model-path group cursor.
func group_index() -> int:
return _group_index
## Returns the current transform offset within the selected model-path group.
func transform_offset() -> int:
return _transform_offset
## Returns the next static/animated batch serial used for node naming.
func batch_serial() -> int:
return _batch_serial
## Atomically adopts all progress values after one planned build operation.
func adopt_progress(
next_group_index: int,
next_transform_offset: int,
next_batch_serial: int
) -> void:
_group_index = next_group_index
_transform_offset = next_transform_offset
_batch_serial = next_batch_serial
## Returns detached scalar/count diagnostics without engine-object references.
func diagnostic_snapshot() -> Dictionary:
return {
"tile_key": _tile_key,
"group_index": _group_index,
"transform_offset": _transform_offset,
"batch_serial": _batch_serial,
"group_count": _group_keys.size(),
"has_root": _root != null,
}
+1
View File
@@ -0,0 +1 @@
uid://drawyd5qmel7j
+169
View File
@@ -0,0 +1,169 @@
class_name M2BuildQueue
extends RefCounted
## Owns pending M2 build-job records and FIFO tile keys. Stale FIFO keys are
## intentional: erasing a job never removes its queued key.
const JOB_SCRIPT := preload("res://src/render/m2/m2_build_job.gd")
var _jobs_by_tile_key: Dictionary = {}
var _queued_tile_keys: Array[String] = []
## Enqueues a typed job. Duplicate tile keys replace the keyed job and append
## another FIFO entry, matching raw Dictionary assignment plus Array append.
func enqueue(tile_key: String, root: Node3D, groups: Dictionary) -> bool:
if tile_key.is_empty() or root == null or groups.is_empty():
return false
_jobs_by_tile_key[tile_key] = JOB_SCRIPT.new(tile_key, root, groups)
_queued_tile_keys.append(tile_key)
return true
## Returns whether at least one FIFO key, including a stale key, is pending.
func has_pending() -> bool:
return not _queued_tile_keys.is_empty()
## Returns the front FIFO tile key, or an empty String when the queue is empty.
func front_key() -> String:
if _queued_tile_keys.is_empty():
return ""
return _queued_tile_keys.front()
## Pops and returns the front FIFO key, or an empty String when already empty.
func pop_front() -> String:
if _queued_tile_keys.is_empty():
return ""
return _queued_tile_keys.pop_front()
## Moves the front FIFO key to the tail and reports whether a key existed.
func rotate_front() -> bool:
if _queued_tile_keys.is_empty():
return false
_queued_tile_keys.append(_queued_tile_keys.pop_front())
return true
## Returns whether a tile key still has a current job record.
func has_job(tile_key: String) -> bool:
return _jobs_by_tile_key.has(tile_key)
## Returns the current job as its engine base type, or null for a stale key.
func job_for(tile_key: String) -> RefCounted:
return _jobs_by_tile_key.get(tile_key, null) as RefCounted
## Returns the borrowed root for a current job, or null for a stale key.
func root_for(tile_key: String) -> Node3D:
var job := job_for(tile_key)
if job == null:
return null
return job.call("root") as Node3D
## Returns the exact grouped-transform Dictionary, or an empty Dictionary.
func groups_for(tile_key: String) -> Dictionary:
var job := job_for(tile_key)
if job == null:
return {}
return job.call("groups") as Dictionary
## Returns the borrowed group-key snapshot, or an empty Array for a stale key.
func group_keys_for(tile_key: String) -> Array:
var job := job_for(tile_key)
if job == null:
return []
return job.call("group_keys") as Array
## Returns the current model-path group cursor, or zero for a stale key.
func group_index_for(tile_key: String) -> int:
var job := job_for(tile_key)
if job == null:
return 0
return int(job.call("group_index"))
## Returns the current in-group transform offset, or zero for a stale key.
func transform_offset_for(tile_key: String) -> int:
var job := job_for(tile_key)
if job == null:
return 0
return int(job.call("transform_offset"))
## Returns the current batch serial, or zero for a stale key.
func batch_serial_for(tile_key: String) -> int:
var job := job_for(tile_key)
if job == null:
return 0
return int(job.call("batch_serial"))
## Atomically adopts all progress values for a current job.
func adopt_progress(
tile_key: String,
next_group_index: int,
next_transform_offset: int,
next_batch_serial: int
) -> bool:
var job := job_for(tile_key)
if job == null:
return false
job.call(
"adopt_progress",
next_group_index,
next_transform_offset,
next_batch_serial
)
return true
## Erases only the keyed job and intentionally leaves every FIFO entry stale.
## Returns whether an active record existed. Engine objects are never freed.
func erase_job(tile_key: String) -> bool:
var had_job := _jobs_by_tile_key.has(tile_key)
_jobs_by_tile_key.erase(tile_key)
return had_job
## Returns a detached snapshot of active job keys for loader-owned cleanup.
func job_keys() -> Array:
return _jobs_by_tile_key.keys()
## Releases all job/key references without freeing referenced engine objects.
func clear() -> void:
_jobs_by_tile_key.clear()
_queued_tile_keys.clear()
## Returns FIFO entry count, including duplicate and stale keys.
func pending_count() -> int:
return _queued_tile_keys.size()
## Returns active keyed job count, excluding stale FIFO keys.
func active_job_count() -> int:
return _jobs_by_tile_key.size()
## Returns detached FIFO order and tile-key-sorted scalar job diagnostics.
func diagnostic_snapshot() -> Dictionary:
var tile_keys: Array = _jobs_by_tile_key.keys()
tile_keys.sort()
var jobs: Array[Dictionary] = []
for tile_key_variant in tile_keys:
var tile_key := String(tile_key_variant)
var job := job_for(tile_key)
if job != null:
jobs.append(job.call("diagnostic_snapshot") as Dictionary)
return {
"queued_tile_keys": _queued_tile_keys.duplicate(),
"jobs": jobs,
}
+1
View File
@@ -0,0 +1 @@
uid://57qrpiqqgpr4
@@ -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", ""))

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