Compare commits

..

78 Commits

Author SHA1 Message Date
sindoring 1fd87b3132 обновление 2026-09-05 13:02:03 +04:00
sindoring bc4440562d обновление документации и сабмодулей 2026-08-12 15:53:15 +04:00
sindoring 5b73ddadc9 обновление аудита 2026-08-12 14:15:22 +04:00
sindoring a11eb50219 актуализация референсов 2026-08-12 14:02:08 +04:00
sindoring adc441ffca coord(M04): accept renderer roadmap migration 2026-08-02 17:42:18 +04:00
sindoring afbc4faab3 coord(M04): hand off renderer roadmap migration 2026-08-02 17:41:41 +04:00
sindoring f77f50c0f9 docs(M04): prioritize renderer fidelity milestone
Work-Package: M04-RND-FIDELITY-ROADMAP-001
Agent: sindo-main-codex-renderer-roadmap
Tests: coordination, documentation, target sequence, old-link and diff gates pass
Fidelity: original build-12340 captures are authoritative; Noggit is secondary reference
2026-08-02 17:41:18 +04:00
sindoring 4369dd3c91 coord(M04): claim renderer fidelity roadmap migration 2026-08-02 15:44:52 +04:00
sindoring fe6d0deab9 Record M03 closeout integration 2026-08-02 15:29:34 +04:00
sindoring 203d40bd0e Mark M03 renderer milestone complete 2026-08-02 15:28:40 +04:00
sindoring 3bf1c2c657 Complete M03 renderer closeout validation 2026-08-02 15:28:10 +04:00
sindoring d9d20cb53f qar: add M03 renderer closeout gates 2026-08-01 16:49:43 +04:00
sindoring ef6d324b4f coordination: claim M03 integrator closeout 2026-08-01 14:54:00 +04:00
sindoring f978a16806 targets: accept WMO scene instance factory
Work-Package: M03-RND-WMO-SCENE-INSTANCE-FACTORY-001
Agent: sindo-main-codex
Tests: post-merge WMO/placement/facade/internal-access/manifest/docs/coordination smoke passed
Fidelity: valid cached/live behavior preserved; invalid-root lifetime leak fixed
2026-08-01 10:51:35 +04:00
sindoring 541279ed45 merge: WMO scene instance factory
Work-Package: M03-RND-WMO-SCENE-INSTANCE-FACTORY-001
Agent: sindo-main-codex
Tests: 65/66 headless verifiers; proprietary ADT probe unavailable; checkpoint dry-run 7/7
Fidelity: valid cached/live behavior preserved; invalid-root leak fixed
2026-08-01 10:50:26 +04:00
sindoring 8da41dc17c coordination: hand off WMO scene instance factory
Work-Package: M03-RND-WMO-SCENE-INSTANCE-FACTORY-001
Agent: sindo-main-codex
Tests: documented in handoff
Fidelity: valid cached/live behavior retained; invalid-root leak fixed
2026-08-01 10:50:16 +04:00
sindoring 7e97b19095 render: extract WMO scene instance factory
Work-Package: M03-RND-WMO-SCENE-INSTANCE-FACTORY-001
Agent: sindo-main-codex
Tests: 65/66 headless verifiers; proprietary ADT probe unavailable; checkpoint dry-run 7/7; docs and coordination passed
Fidelity: valid cached/live naming, placement, validation and Resource identity preserved; invalid-root leak fixed
2026-08-01 10:49:56 +04:00
sindoring 07521ee6a4 coordination: claim WMO scene instance factory
Work-Package: M03-RND-WMO-SCENE-INSTANCE-FACTORY-001
Agent: sindo-main-codex
Tests: not run (coordination only)
Fidelity: behavior-preserving extraction planned
2026-08-01 10:42:14 +04:00
sindoring f8348cf0cb targets: accept WMO runtime scene preparer
Work-Package: M03-RND-WMO-RUNTIME-SCENE-PREPARER-001
Agent: sindo-main-codex
Tests: post-merge WMO/facade/internal-access/manifest/docs/coordination smoke passed
Fidelity: cached/live, occluder and shadow behavior preserved; asset-backed evidence pending
2026-08-01 10:38:59 +04:00
sindoring 57d0a9f8bd merge: WMO runtime scene preparer
Work-Package: M03-RND-WMO-RUNTIME-SCENE-PREPARER-001
Agent: sindo-main-codex
Tests: 64/65 headless verifiers; proprietary ADT probe unavailable; checkpoint dry-run 7/7
Fidelity: preserves cached/live preparation, occluder and shadow semantics
2026-08-01 10:37:53 +04:00
sindoring 73b30ca699 coordination: hand off WMO runtime scene preparer
Work-Package: M03-RND-WMO-RUNTIME-SCENE-PREPARER-001
Agent: sindo-main-codex
Tests: documented in handoff
Fidelity: exact cached/live preparation behavior retained
2026-08-01 10:37:45 +04:00
sindoring ffed91c364 render: extract WMO runtime scene preparer
Work-Package: M03-RND-WMO-RUNTIME-SCENE-PREPARER-001
Agent: sindo-main-codex
Tests: 64/65 headless verifiers passed; proprietary ADT probe unavailable; checkpoint dry-run 7/7; docs and coordination passed
Fidelity: preserves cached/live preparation distinction, direct occluder policy and shadow semantics
2026-08-01 10:37:23 +04:00
sindoring cfa3dc1009 coordination: claim WMO runtime scene preparer
Work-Package: M03-RND-WMO-RUNTIME-SCENE-PREPARER-001
Agent: sindo-main-codex
Tests: not run (coordination only)
Fidelity: behavior-preserving extraction planned
2026-08-01 10:28:50 +04:00
sindoring 57d3330944 targets: accept WMO render group materializer
Work-Package: M03-RND-WMO-RENDER-GROUP-MATERIALIZER-001
Agent: sindo-main-codex
Tests: post-merge WMO/facade/internal-access/manifest/docs/coordination smoke passed
Fidelity: behavior-preserving extraction; asset-backed visual evidence remains pending
2026-08-01 10:25:15 +04:00
sindoring 705354dc14 merge: WMO render group materializer
Work-Package: M03-RND-WMO-RENDER-GROUP-MATERIALIZER-001
Agent: sindo-main-codex
Tests: 63/64 autonomous headless verifiers; proprietary ADT probe unavailable; checkpoint dry-run 7/7
Fidelity: behavior-preserving WMO group materialization extraction
2026-08-01 10:22:58 +04:00
sindoring 6deb80c0dd coordination: hand off WMO render group materializer
Work-Package: M03-RND-WMO-RENDER-GROUP-MATERIALIZER-001
Agent: sindo-main-codex
Tests: documented in handoff
Fidelity: exact behavior-preserving extraction
2026-08-01 10:22:44 +04:00
sindoring f4d5e29cc9 render: extract WMO render group materializer
Work-Package: M03-RND-WMO-RENDER-GROUP-MATERIALIZER-001
Agent: sindo-main-codex
Tests: 63/64 autonomous verifiers passed; proprietary ADT probe unavailable; baseline dry-run 7/7; documentation and coordination gates passed
Fidelity: exact behavior-preserving node materialization extraction; no new 3.3.5a parity claim
2026-08-01 10:22:21 +04:00
sindoring 543ee1572b coordination: claim WMO render group materializer
Work-Package: M03-RND-WMO-RENDER-GROUP-MATERIALIZER-001
Agent: sindo-main-codex
Tests: not run (coordination only)
Fidelity: behavior-preserving extraction planned
2026-08-01 10:11:29 +04:00
sindoring f35dcf3a09 targets: accept WMO runtime Mesh finalizer 2026-07-18 14:35:39 +04:00
sindoring d65ebee57f merge: WMO runtime Mesh finalizer 2026-07-18 14:34:02 +04:00
sindoring a59086fdf0 coordination: hand off WMO runtime Mesh finalizer 2026-07-18 14:33:57 +04:00
sindoring ae40e3f1d4 render: extract WMO runtime Mesh finalizer 2026-07-18 14:33:48 +04:00
sindoring 49e70260ed coordination: claim WMO runtime Mesh finalizer 2026-07-18 14:27:17 +04:00
sindoring 406dc464c3 targets: accept WMO scene resource finalizer 2026-07-18 14:18:29 +04:00
sindoring 6a0f9bd3ba merge: WMO scene resource finalizer 2026-07-18 14:17:09 +04:00
sindoring 3525cc0ead coordination: hand off WMO scene resource finalizer 2026-07-18 14:17:04 +04:00
sindoring 7779e9e75f render: extract WMO scene resource finalizer 2026-07-18 14:16:49 +04:00
sindoring 9ba7ca3263 coordination: claim WMO scene resource finalizer 2026-07-18 14:08:02 +04:00
sindoring 8c54313f7d targets: accept WMO render resource finalizer 2026-07-18 14:04:51 +04:00
sindoring 1acddab8a5 merge: WMO render resource finalizer 2026-07-18 14:03:48 +04:00
sindoring 5a1cd9d7c8 coordination: hand off WMO render resource finalizer 2026-07-18 14:03:41 +04:00
sindoring a86f8f2212 render: extract WMO render resource finalizer 2026-07-18 14:03:27 +04:00
sindoring fb7c9f174e coordination: claim WMO render resource finalizer 2026-07-18 13:55:50 +04:00
sindoring f1a7400ed1 targets: accept M2 Mesh resource finalizer 2026-07-18 13:52:55 +04:00
sindoring 34b700051f merge: M2 Mesh resource finalizer 2026-07-18 13:51:25 +04:00
sindoring 71a1012779 coordination: hand off M2 Mesh resource finalizer 2026-07-18 13:51:21 +04:00
sindoring ddeb708c08 render: extract M2 Mesh resource finalizer 2026-07-18 13:51:08 +04:00
sindoring c328d86554 coordination: claim M2 Mesh resource finalizer 2026-07-18 13:38:02 +04:00
sindoring 75ddf6a2ed targets: accept M2 animation resource finalizer 2026-07-18 13:31:34 +04:00
sindoring 06f6394043 merge: M2 animation resource finalizer 2026-07-18 13:30:43 +04:00
sindoring b20f0d7f6f coordination: hand off M2 animation resource finalizer 2026-07-18 13:30:39 +04:00
sindoring e7cd967dce render: extract M2 animation resource finalizer 2026-07-18 13:30:24 +04:00
sindoring ff952da7d8 coordination: claim M2 animation resource finalizer 2026-07-18 13:18:53 +04:00
sindoring 70729bb341 targets: accept native M2 animation resource observer 2026-07-18 10:57:18 +04:00
sindoring d37c799850 merge: native M2 animation resource observer 2026-07-18 10:56:08 +04:00
sindoring c24c3f159c coordination: hand off native M2 animation resource observer 2026-07-18 10:56:04 +04:00
sindoring 1cb0101a73 render: extract native M2 animation resource observer 2026-07-18 10:55:47 +04:00
sindoring d22a9cd743 coordination: claim native M2 animation resource observer 2026-07-18 10:42:13 +04:00
sindoring 6f385ed261 targets: accept cached M2 animation resource observer 2026-07-18 10:24:30 +04:00
sindoring f79e064d25 merge: cached M2 animation resource observer 2026-07-18 10:23:29 +04:00
sindoring 6a23c1b996 coordination: hand off cached M2 animation resource observer 2026-07-18 10:23:22 +04:00
sindoring df87619220 render: extract cached M2 animation resource observer 2026-07-18 10:23:03 +04:00
sindoring 3519f183bb coordination: claim cached M2 animation resource observer 2026-07-18 10:10:19 +04:00
sindoring 5ebf4de2ff targets: accept static M2 build resource observer 2026-07-18 03:04:01 +04:00
sindoring a043c79654 merge: static M2 build resource observer 2026-07-18 03:02:06 +04:00
sindoring 194b64d030 coordination: hand off static M2 build resource observer 2026-07-18 03:02:02 +04:00
sindoring 7cb3e3412f render: extract static M2 build resource observer 2026-07-18 03:01:43 +04:00
sindoring f062ea91cd coordination: claim static M2 build resource observer 2026-07-18 02:51:46 +04:00
sindoring 032a256e70 targets: accept M2 build resource snapshot 2026-07-18 02:48:54 +04:00
sindoring 4354834c50 merge: M2 build resource snapshot 2026-07-18 02:47:35 +04:00
sindoring 41b3b63215 coordination: hand off M2 build resource snapshot 2026-07-18 02:47:26 +04:00
sindoring ec1b90f1e4 render: add M2 build resource snapshot 2026-07-18 02:47:10 +04:00
sindoring 0decd10e09 coordination: claim M2 build resource snapshot 2026-07-18 02:38:46 +04:00
sindoring b113db01cd targets: accept M2 build dispatch planner 2026-07-18 02:35:49 +04:00
sindoring 99a90ddfb3 merge: M2 build dispatch planner 2026-07-18 02:34:36 +04:00
sindoring ed71b36aec coordination: hand off M2 build dispatch planner 2026-07-18 02:34:31 +04:00
sindoring 17f5cc0faa render: extract M2 build dispatch planner 2026-07-18 02:34:18 +04:00
sindoring 1fb566f9bc coordination: claim M2 build dispatch planner 2026-07-18 02:27:16 +04:00
158 changed files with 12893 additions and 1002 deletions
+28
View File
@@ -5,3 +5,31 @@
path = third_party/godot-cpp
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
+252 -16
View File
@@ -32,6 +32,8 @@ Paired run 2026-07-11 подтвердил крупный coordinate/placement g
- `src/render/wmo/wmo_render_build_step_planner.gd` - mesh-first lightweight WMO group operation and cursor planning without Nodes or Resources.
- `src/render/wmo/wmo_render_build_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.
@@ -41,7 +43,13 @@ Paired run 2026-07-11 подтвердил крупный coordinate/placement g
- `src/render/m2/m2_animated_instance_materializer.gd` - main-thread animated instance duplication, render settings, playback startup and non-empty batch attachment.
- `src/render/m2/m2_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.
@@ -325,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.
@@ -678,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;
@@ -1058,6 +1102,39 @@ $exe = Join-Path $env:TEMP 'godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe'
- Cache formats, quality profiles, batching output and visible rules are unchanged.
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
@@ -1098,12 +1175,70 @@ $exe = Join-Path $env:TEMP 'godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe'
order. All three historical `m2_animation` metrics still count both stages.
- Shutdown still drains pending ResourceLoader paths before clear; map reset and
shutdown preserve the same two clear sites.
- `StreamingWorldLoader` retains GLB eligibility/cache selection, every
- At this extraction stage, `StreamingWorldLoader` retained GLB eligibility/cache selection, every
ResourceLoader call, `M2_ANIMATION_FINALIZE` permits, scene instantiation,
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
@@ -1164,12 +1299,14 @@ $exe = Join-Path $env:TEMP 'godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe'
- `M2RuntimeMeshFinalizer` now owns material refresh version `2`, stale-Mesh
rebuild classification, M2Builder rebuild and original-Mesh fallback.
- Current Meshes still skip raw `.m2` loading. `M2RawModelRepository` now owns
FileAccess/ClassDB M2Loader I/O and supplies raw data through the loader only
when the finalizer reports a stale Mesh; both historical clear sites persist.
- 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. Cache adoption decisions, permits and MultiMesh
materialization remain loader-owned; negative outcomes belong to prototype state.
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.
@@ -1178,10 +1315,10 @@ $exe = Join-Path $env:TEMP 'godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe'
- `M2RawModelRepository` now owns the repeated extracted-file and optional
native `M2Loader` boundary for static `load_m2` and animated
`load_m2_animated` raw Dictionaries.
- `StreamingWorldLoader` delegates the stale-Mesh refresh, static prototype and
native animated prototype reads. It retains normalization, `.tscn/.glb`
fallback order, builders, permits and Node/Mesh use; prototype/negative state
is now isolated in `M2PrototypeCacheState`.
- `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.
@@ -1219,9 +1356,10 @@ $exe = Join-Path $env:TEMP 'godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe'
- `WmoRenderResourceCacheState` now owns validated lightweight-WMO render
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
@@ -1229,13 +1367,28 @@ $exe = Join-Path $env:TEMP 'godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe'
- Asset-backed corrupt-cache, traversal/leak p95/p99 and paired fidelity evidence
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
@@ -1243,6 +1396,21 @@ $exe = Join-Path $env:TEMP 'godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe'
- Asset-backed oversize/stale fixtures, traversal/leak p95/p99 and paired fidelity
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,
@@ -1317,6 +1485,74 @@ $exe = Join-Path $env:TEMP 'godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe'
- ADT parsing, quality tasks/results, tile state, cache format versions, material/
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,84 @@
# M03-RND-M2-ANIMATION-RESOURCE-FINALIZER-001
<!-- OPENWC_CLAIM:M03-RND-M2-ANIMATION-RESOURCE-FINALIZER-001:sindo-main-codex:2026-07-20 -->
## Owner
- Agent ID: `sindo-main-codex`
- Target: M03 Renderer Facade and Safe Extraction
- Branch: `work/sindo-main-codex/m03-m2-animation-resource-finalizer`
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-m2-animation-resource-finalizer`
## Outcome
Move cached animated M2 ResourceLoader status polling, terminal Resource load,
candidate finalization, prototype/static-only adoption and success diagnostics
from the loader into a dedicated two-phase finalization service.
## Non-goals
- Change cached animation eligibility, request admission or native-first order.
- Move material-prototype lookup, permit ownership, playback or materialization.
- Change ResourceLoader status order, cache formats, profiles or visible output.
- Generalize static and animated ResourceLoader paths behind callbacks/frameworks.
## Paths
- Exclusive: `src/render/m2/m2_animation_resource_finalizer.gd`,
`src/tools/verify_m2_animation_resource_finalizer.gd`,
`docs/modules/m2-animation-resource-finalizer.md`, this claim
- Shared: loader, animation pipeline/scene finalizer/prototype cache/cached
observer/world-renderer specs and verifiers, module registry, `RENDER.md`, M03 Evidence
## Contracts and data
- Pending request snapshots are polled in existing Dictionary insertion order.
- Empty Resource paths are discarded and marked static-only immediately.
- Only LOADED/FAILED statuses move into completion-order finalize FIFO.
- One permit processes one popped terminal record, including skipped/stale records.
- Existing prototype/static-only state suppresses terminal Resource loading.
- LOADED Resource is instantiated before material-prototype lookup is requested.
- Candidate repair/finalize/adoption and exact `M2_ANIM_CACHE` debug fields remain.
- Rejected candidates are released by the existing animated scene finalizer.
## Dependencies
- Requires: accepted animation pipeline, scene finalizer, cached/native observers
- Blocks: remaining loader-owned M2 material-prototype lookup and action execution
## Verification
- Synthetic polling/status/FIFO/skip/load/candidate/repair/adoption/log/source and
timing contracts; adjacent M2/renderer gates; full headless suite and checkpoint dry-run.
## Documentation deliverables
- Inline API docs; module API/I/O/ownership; data-flow/state/sequence/dependency
diagrams; adjacent renderer docs and M03 Evidence.
## Status
- State: accepted
- Done: implementation, verification and documentation
- Next: static M2 Resource terminal polling/finalize extraction
- Blocked by:
## Handoff
- Commit: `e7cd967`
- Results: finalizer `28` cases / `1,000` timing iterations; all `58/58`
autonomous headless regressions; facade/internal-access/shutdown/manifest;
documentation `45`; coordination; checkpoint dry-run `7/7`.
- Remaining risks: private asset animation/visual/leak/p95/p99 evidence is absent;
terminal Resource get/instantiation/repair stays synchronous; immediate prepared-
candidate completion is a composition precondition; material lookup stays loader-owned.
- Documentation updated: new finalizer API/I/O/data-flow/state/sequence/dependency/
ownership spec; pipeline, scene finalizer, cached observer, prototype cache,
world renderer, module registry, `RENDER.md` and M03 Evidence.
## Integration
- Merge: `06f6394`
- Post-merge: finalizer, pipeline, scene finalizer, cached observer, prototype
cache, snapshot, dispatch, shutdown, facade, internal-access `30`, manifest
`7/7`, documentation `45`, coordination and checkpoint dry-run `7/7` passed.
@@ -0,0 +1,97 @@
# M03-RND-M2-BUILD-DISPATCH-PLANNER-001
<!-- OPENWC_CLAIM:M03-RND-M2-BUILD-DISPATCH-PLANNER-001:sindo-main-codex:2026-07-20 -->
## Owner
- Agent ID: `sindo-main-codex`
- Target: M03 Renderer Facade and Safe Extraction
- Branch: `work/sindo-main-codex/m03-m2-build-dispatch-planner`
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-m2-build-dispatch-planner`
## Outcome
Extract the pure M2 build-step dispatch decision from the loader while preserving
animation-wait priority, static-resource retry, missing-model advancement and
exact permit/cursor behavior.
## Non-goals
- Move ResourceLoader I/O, path lookup, cache state or retry ownership.
- Create/free Nodes, Meshes, MultiMeshes or animated instances in the planner.
- Change batch sizing, queue ordering, transforms, visuals or cache versions.
- Add spatial-cell batching or new diagnostics/profile settings.
## Paths
- Exclusive: `src/render/m2/m2_build_dispatch_planner.gd`,
`src/tools/verify_m2_build_dispatch_planner.gd`,
`docs/modules/m2-build-dispatch-planner.md`, this claim
- Shared/hotspots: `src/scenes/streaming/streaming_world_loader.gd`, M2 build/
queue/materializer/cache verifiers and specs, `docs/modules/world-renderer.md`,
`docs/modules/README.md`, `RENDER.md`, `targets/03-renderer-facade.md`
- Generated/ignored: `.godot`, native DLL, generated resources/caches and proprietary corpus
## Contracts and data
- Pending animation request wins before empty/animated/static decisions.
- Positive batch with animated prototype selects animated materialization.
- Positive static batch selects prepared Mesh, waits when unresolved, and advances
without materialization only when the model is terminally missing.
- Zero/non-positive batch advances without materialization.
- Planner returns a fresh action plan and owns no state or engine reference.
## Dependencies
- Requires: accepted typed M2 build queue on master `c74b90a`
- Blocks: extracting stateful M2 resource readiness/dispatch orchestration
- External state: cache lookups, requests and engine lifetime remain loader APIs
## Verification
- Commands: dedicated action-priority/matrix/fresh-result/source/timing verifier;
M2 queue/planner/materializer/cache/prototype/shutdown/facade/internal-access/
manifest regressions; documentation, coordination and checkpoint dry-run gates
- Fixtures: scalar availability and terminal-state combinations only
- Fidelity evidence: exact existing branch priority and retry/advance behavior
- Performance budget: 20,000 pure dispatch plans under one second
## Documentation deliverables
- Inline API docs
- Module spec with inputs/outputs, ownership and source map
- Data-flow, lifecycle/state, sequence and dependency diagrams
- Adjacent M2 build/world-renderer/module-registry/RENDER updates
## Simplicity and naming
- Important name: `M2BuildDispatchPlanner`
- Simplest solution: one stateless RefCounted with explicit action constants
- Rejected complexity: callbacks, signals, cache references and generic state machine
- Unavoidable complexity: animation-pending priority precedes every other action
- Measured optimization evidence: bounded synthetic decision loop
## Status
- State: integration accepted
- Done: implementation, verification, documentation and worktree handoff
- Next: extract typed M2 resource observation/request orchestration
- Blocked by:
## Handoff
- Commit: `17f5cc0faa376c8ac84a7ac72d5b8c0366866980`
- Results: dispatch planner `cases=10 iterations=20000 elapsed_ms=11.933`;
all autonomous headless regressions `53/53`; internal access
`private_symbols=30`; baseline manifest/dry-run `7/7`; documentation
`module_specs=40`; coordination passed with 30 historical expired warnings.
- Remaining risks: proprietary ADT visual/p95/p99 evidence is unavailable;
resource observation/requests, action execution, permits and engine lifetime
intentionally remain loader-owned.
- Documentation updated: new `m2-build-dispatch-planner.md` with API/I/O,
data-flow, lifecycle, sequence, dependency and ownership diagrams; batch/queue/
world renderer specs, module registry, RENDER and M03 Evidence updated.
- Integration: master merge `99a90ddfb3364d6672198887198602b84dd19e58`;
post-merge dispatch planner `cases=10 iterations=20000 elapsed_ms=12.291`,
twelve adjacent M2/resource/lifetime/facade/internal-access/manifest checks,
documentation and coordination all passed; checkpoint dry-run retained `7/7` plans.
@@ -0,0 +1,98 @@
# M03-RND-M2-BUILD-RESOURCE-SNAPSHOT-001
<!-- OPENWC_CLAIM:M03-RND-M2-BUILD-RESOURCE-SNAPSHOT-001:sindo-main-codex:2026-07-20 -->
## Owner
- Agent ID: `sindo-main-codex`
- Target: M03 Renderer Facade and Safe Extraction
- Branch: `work/sindo-main-codex/m03-m2-build-resource-snapshot`
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-m2-build-resource-snapshot`
## Outcome
Replace loose per-step M2 animated/static resource variables with a typed
observation snapshot consumed by the dispatch planner and materializer adapter.
## Non-goals
- Move ResourceLoader I/O, cache lookup/request execution or retry ownership.
- Own or free prototype Nodes or Mesh Resources.
- Change dispatch priority, batch sizing, queue ordering, permits or visuals.
- Change M2 cache paths, formats, allowlists or native animation selection.
## Paths
- Exclusive: `src/render/m2/m2_build_resource_snapshot.gd`,
`src/tools/verify_m2_build_resource_snapshot.gd`,
`docs/modules/m2-build-resource-snapshot.md`, this claim
- Shared/hotspots: `src/render/m2/m2_build_dispatch_planner.gd`, its verifier/spec,
`src/scenes/streaming/streaming_world_loader.gd`, M2 queue/materializer/cache
specs and tests, `docs/modules/world-renderer.md`, `docs/modules/README.md`,
`RENDER.md`, `targets/03-renderer-facade.md`
- Generated/ignored: `.godot`, native DLL, generated resources/caches and proprietary corpus
## Contracts and data
- Snapshot retains normalized path, optional animated prototype, animation
request state, optional static Mesh and terminal missing-model state.
- Static observation is adopted only after animation is not pending, the batch
is positive and no animated prototype exists.
- Accessors borrow exact engine references without transferring/freeing ownership.
- Diagnostics expose only path and scalar availability flags.
## Dependencies
- Requires: accepted M2 dispatch planner on master `b113db0`
- Blocks: extracting M2 resource observation/request service ownership
- External state: prototype/cache/pipeline/resource lifetime remains loader-owned
## Verification
- Commands: dedicated identity/adoption/diagnostic/lifetime/source/timing verifier;
dispatch/queue/batch/materializer/cache/pipeline/shutdown/facade/internal-access/
manifest regressions; documentation, coordination and checkpoint dry-run gates
- Fixtures: synthetic Node3D prototype and ArrayMesh resources
- Fidelity evidence: exact resource presence/pending/missing values and branch order
- Performance budget: 20,000 snapshot construct/adopt/read cycles under one second
## Documentation deliverables
- Inline API docs
- Module spec with inputs/outputs, ownership and source map
- Data-flow, lifecycle/state, sequence and dependency diagrams
- Adjacent dispatch/queue/world-renderer/module-registry/RENDER updates
## Simplicity and naming
- Important name: `M2BuildResourceSnapshot`
- Simplest solution: one small RefCounted value holder with explicit accessors
- Rejected complexity: generic resource union, signals, callbacks and cache ownership
- Unavoidable complexity: two-phase animation then optional static observation
- Measured optimization evidence: bounded synthetic lifecycle loop
## Status
- State: integration accepted
- Done: implementation, verification, documentation and worktree handoff
- Next: extract the M2 resource observation/request producer service
- Blocked by:
## Handoff
- Commit: `ec1b90f1e44037572e3e5052e6dedc016c29859a`
- Results: resource snapshot `cases=12 iterations=20000 elapsed_ms=42.995`;
dispatch planner remained green; all autonomous headless regressions `54/54`;
internal access `private_symbols=30`; baseline manifest/dry-run `7/7`;
documentation `module_specs=41`; coordination passed with 30 historical warnings.
- Remaining risks: proprietary ADT visual/p95/p99 evidence is unavailable;
resource lookup/request execution, action execution, permits and engine lifetime
intentionally remain loader-owned.
- Documentation updated: new `m2-build-resource-snapshot.md` with API/I/O,
data-flow, lifecycle, sequence, dependency and ownership diagrams; dispatch/
queue/world renderer specs, module registry, RENDER and M03 Evidence updated.
- Integration: master merge `4354834c50ae426640c721041af67d5265e82205`;
post-merge snapshot `cases=12 iterations=20000 elapsed_ms=42.211`, dispatch
planner and twelve adjacent resource/materializer/lifetime/facade/internal-
access/manifest checks, documentation and coordination all passed; checkpoint
dry-run retained `7/7` plans.
@@ -0,0 +1,91 @@
# M03-RND-M2-CACHED-ANIMATION-RESOURCE-OBSERVER-001
<!-- OPENWC_CLAIM:M03-RND-M2-CACHED-ANIMATION-RESOURCE-OBSERVER-001:sindo-main-codex:2026-07-20 -->
## Owner
- Agent ID: `sindo-main-codex`
- Target: M03 Renderer Facade and Safe Extraction
- Branch: `work/sindo-main-codex/m03-m2-cached-animation-resource-observer`
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-m2-cached-animation-resource-observer`
## Outcome
Move cached-GLB animated M2 lookup, allow/deny and primitive/schema safety
selection, threaded request admission and static-only transition from the loader
into a service that produces `M2BuildResourceSnapshot`.
## Non-goals
- Move native GryphonRoost raw-data build, debug logging or finalize drains.
- Own/free prototype Nodes or change cache/pipeline ownership.
- Change allow/deny defaults, primitive limits, candidate order or GLB rules.
- Change batching, permits, cache formats, profiles or visible output.
## Paths
- Exclusive: `src/render/m2/m2_cached_animation_resource_observer.gd`,
`src/tools/verify_m2_cached_animation_resource_observer.gd`,
`docs/modules/m2-cached-animation-resource-observer.md`, this claim
- Shared: loader, M2 snapshot/animation pipeline/prototype specs and verifiers,
world renderer, module registry, `RENDER.md` and M03 Evidence
## Contracts and data
- Cached animated prototype wins; static-only and pending states do not request.
- Empty allowlist rejects; denylist wins; matching is stripped/case-insensitive.
- Historical nested/lowercase/basename `.glb` order remains exact.
- Candidate requires animations, primitive limit and accepted empty or
`pivot_prefix_v1` schema; other schemas are rejected.
- Successful/`ERR_BUSY` request is remembered and reported pending; no usable
candidate or request failure marks animation static.
- Service produces a snapshot borrowing the exact prototype and frees nothing.
## Dependencies
- Requires: accepted static observer `5ebf4de`, snapshot and animation pipeline
- Blocks: native animation observation and remaining finalize extraction
## Verification
- Synthetic cache/pipeline/snapshot lifecycle, policy/path/GLB metadata fixtures,
source boundaries and timing; adjacent M2/renderer gates; full headless suite
and checkpoint dry-run.
## Documentation deliverables
- Inline API docs; module API/I/O/ownership; data-flow/state/sequence/dependency
diagrams; adjacent renderer docs and M03 Evidence.
## Status
- State: accepted
- Done: implementation, integration and post-merge acceptance
- Next: native M2 animation observation extraction
- Blocked by:
## Handoff
- Commit: `df87619` (`render: extract cached M2 animation resource observer`)
- Results: observer PASS (`33` cases, `20,000` iterations, `61.745ms`);
full autonomous suite PASS (`56/56`); renderer checkpoint dry-run PASS (`7/7`);
internal-access PASS (`30` private symbols); documentation PASS (`43` module
specs); coordination PASS with `34` historical warnings.
- Remaining risks: successful asynchronous admission is covered by source and
existing pipeline regressions because an undrained unit request leaks; generated
GLB metadata does not prove Godot import fidelity; private traversal, p95/p99,
leak and paired visual evidence remain unavailable; native observation and
terminal ResourceLoader polling/finalize remain loader-owned.
- Documentation updated: new cached observer API/I/O/data-flow/state/sequence/
dependency/ownership specification; M2 snapshot, batch/dispatch, animation
pipeline/prototype, world-renderer, module registry, `RENDER.md` and M03 Evidence.
## Integration
- Merge: `f79e064` (`merge: cached M2 animation resource observer`)
- Post-merge: observer `33` cases / `20,000` iterations / `61.514ms`;
animation pipeline `11` / `66.049ms`; snapshot `12` / `39.947ms`;
dispatch `10` / `17.915ms`; prototype cache `16` / `35.053ms`; shutdown,
facade, internal-access `30`, manifest `7/7`, documentation `43` and
coordination passed. Checkpoint dry-run completed `7/7`; expected private
Azeroth/character assets remained unavailable.
@@ -0,0 +1,84 @@
# M03-RND-M2-MESH-RESOURCE-FINALIZER-001
<!-- OPENWC_CLAIM:M03-RND-M2-MESH-RESOURCE-FINALIZER-001:sindo-main-codex:2026-07-20 -->
## Owner
- Agent ID: `sindo-main-codex`
- Target: M03 Renderer Facade and Safe Extraction
- Branch: `work/sindo-main-codex/m03-m2-mesh-resource-finalizer`
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-m2-mesh-resource-finalizer`
## Outcome
Move static M2 ResourceLoader status polling, terminal Resource/Mesh extraction,
runtime Mesh preparation, Mesh-cache adoption and missing outcome from the
loader into one dedicated finalization service.
## Non-goals
- Change static cache path selection or threaded request admission.
- Merge static and animated terminal services behind a generic callback layer.
- Own permits, MultiMesh materialization, prototype Nodes or SceneTree roots.
- Change material refresh version, rebuild rules, cache formats or visible output.
## Paths
- Exclusive: `src/render/m2/m2_mesh_resource_finalizer.gd`,
`src/tools/verify_m2_mesh_resource_finalizer.gd`,
`docs/modules/m2-mesh-resource-finalizer.md`, this claim
- Shared: loader, Mesh pipeline/cache/extractor/runtime-finalizer/raw-repository/
prototype specs and verifiers, world renderer, registry, `RENDER.md`, M03 Evidence
## Contracts and data
- Pending requests are polled in existing Dictionary insertion order.
- Empty Resource paths are discarded and marked missing immediately.
- Only LOADED/FAILED statuses enter completion-order finalize FIFO.
- One permit pops exactly one terminal record, including skipped/cached records.
- Existing cached Mesh suppresses terminal Resource retrieval.
- Loaded Resource uses exact first-Mesh extraction and current refresh-version path.
- Raw M2 data loads only when the extracted Mesh is stale.
- Prepared Mesh is stored by existing replacement semantics; failures mark missing.
## Dependencies
- Requires: accepted Mesh pipeline/cache/extractor/runtime finalizer/raw repository
- Blocks: remaining loader-owned M2 materialization and orchestration cleanup
## Verification
- Synthetic polling/FIFO/skip/load/extract/refresh/store/missing/source/timing
contracts; adjacent M2/renderer gates; full headless suite and checkpoint dry-run.
## Documentation deliverables
- Inline API docs; module API/I/O/ownership; data-flow/state/sequence/dependency
diagrams; adjacent renderer docs and M03 Evidence.
## Status
- State: accepted
- Done: implementation, verification and documentation
- Next: next unclaimed M03 renderer extraction package
- Blocked by:
## Handoff
- Commit: `ddeb708` (`render: extract M2 Mesh resource finalizer`)
- Results: finalizer PASS `cases=31 iterations=1000 elapsed_ms=0.767`;
adjacent Mesh pipeline/cache/extractor/runtime-finalizer/raw-repository/
prototype/shutdown checks passed; autonomous headless suite `59/59`;
documentation `46`; coordination passed with `34` historical warnings;
checkpoint dry-run `7/7`.
- Remaining risks: terminal instantiation/raw parsing/rebuild remain synchronous
main-thread work behind existing permits; no private-asset visual, leak,
descriptor-pressure or p95/p99 evidence was added.
- Documentation updated: new full module specification with API/I/O and
data-flow/state/sequence/dependency diagrams; adjacent M2 module specs,
world-renderer registry/spec, `RENDER.md` and M03 Evidence.
- Merge: `34b7000` (`merge: M2 Mesh resource finalizer`)
- Post-merge: finalizer PASS `cases=31 iterations=1000 elapsed_ms=0.826`;
adjacent M2 pipeline/cache/extractor/runtime/repository/prototype/shutdown,
facade, internal-access `30`, manifest `7/7`, documentation `46` and
coordination gates passed.
@@ -0,0 +1,84 @@
# M03-RND-M2-NATIVE-ANIMATION-RESOURCE-OBSERVER-001
<!-- OPENWC_CLAIM:M03-RND-M2-NATIVE-ANIMATION-RESOURCE-OBSERVER-001:sindo-main-codex:2026-07-20 -->
## Owner
- Agent ID: `sindo-main-codex`
- Target: M03 Renderer Facade and Safe Extraction
- Branch: `work/sindo-main-codex/m03-m2-native-animation-resource-observer`
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-m2-native-animation-resource-observer`
## Outcome
Move native GryphonRoost M2 animation candidate selection, synchronous raw read,
native animated build, prototype/static-only adoption and debug diagnostics from
the loader into a dedicated observer.
## Non-goals
- Change the exact native candidate predicate or add more animated models.
- Move cached-GLB observation, ResourceLoader polling/finalize or playback.
- Own attached instances, consume permits or change queue/batch behavior.
- Change native parsing/building, cache formats, profiles or visible output.
## Paths
- Exclusive: `src/render/m2/m2_native_animation_resource_observer.gd`,
`src/tools/verify_m2_native_animation_resource_observer.gd`,
`docs/modules/m2-native-animation-resource-observer.md`, this claim
- Shared: loader, M2 cached observer/snapshot/raw repository/prototype specs and
verifiers, world renderer, module registry, `RENDER.md` and M03 Evidence
## Contracts and data
- Only case-insensitive paths containing `gryphonroost` are native candidates.
- Cached animated prototype wins; static-only state suppresses repeated reads.
- Empty raw data or animated surfaces marks animation static.
- Builder result must be non-null with at least one child; rejection marks static.
- Successful result is adopted through first-wins prototype cache semantics.
- Existing `M2_NATIVE_ANIM_CACHE` fields and debug gating remain exact.
- Observer attaches/frees no Node and starts no async work.
## Dependencies
- Requires: accepted cached observer `6f385ed`, raw repository and prototype state
- Blocks: remaining ResourceLoader polling/finalize extraction
## Verification
- Synthetic candidate/cache/static/raw/build/adoption/lifetime/logging/source and
timing contracts; adjacent M2/renderer gates; full headless suite and checkpoint
dry-run.
## Documentation deliverables
- Inline API docs; module API/I/O/ownership; data-flow/state/sequence/dependency
diagrams; adjacent renderer docs and M03 Evidence.
## Status
- State: accepted
- Done: implementation, verification and documentation
- Next: remaining cached ResourceLoader terminal polling/finalize extraction
- Blocked by:
## Handoff
- Commit: `1cb0101`
- Results: observer `33` cases / `20,000` timing iterations; all `57/57`
autonomous headless regressions; facade/internal-access/shutdown/manifest;
documentation `44`; coordination; checkpoint dry-run `7/7`.
- Remaining risks: private asset animation/visual/leak/p95/p99 evidence is absent;
native parse/build stays synchronous; historical childless rejected Node remains
unfreed; cached ResourceLoader terminal polling/finalize stays loader-owned.
- Documentation updated: new native observer API/I/O/data-flow/state/sequence/
dependency/ownership spec; cached observer, snapshot, dispatch, repository,
prototype cache, world renderer, module registry, `RENDER.md` and M03 Evidence.
## Integration
- Merge: `d37c799`
- Post-merge: native/cached observers, repository, cache, snapshot, dispatch,
shutdown, facade, internal-access `30`, manifest `7/7`, documentation `44`,
coordination and checkpoint dry-run `7/7` passed on master.
@@ -0,0 +1,86 @@
# M03-RND-M2-STATIC-BUILD-RESOURCE-OBSERVER-001
<!-- OPENWC_CLAIM:M03-RND-M2-STATIC-BUILD-RESOURCE-OBSERVER-001:sindo-main-codex:2026-07-20 -->
## Owner
- Agent ID: `sindo-main-codex`
- Target: M03 Renderer Facade and Safe Extraction
- Branch: `work/sindo-main-codex/m03-m2-static-build-resource-observer`
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-m2-static-build-resource-observer`
## Outcome
Move static M2 build cache lookup, threaded request selection and terminal
missing transition from the loader into a service that fills
`M2BuildResourceSnapshot`.
## Non-goals
- Move animation/native prototype observation or finalize drains.
- Own/free Meshes, Nodes or cache entries.
- Change path candidate order, GLB filtering, retry behavior, permits or visuals.
- Change cache formats, profiles or batching.
## Paths
- Exclusive: `src/render/m2/m2_static_build_resource_observer.gd`,
`src/tools/verify_m2_static_build_resource_observer.gd`,
`docs/modules/m2-static-build-resource-observer.md`, this claim
- Shared: loader, M2 snapshot/dispatch/pipeline/cache specs and verifiers,
world renderer, module registry, RENDER and M03 Evidence
## Contracts and data
- Cached Mesh wins; terminal missing returns missing; existing pending request waits.
- Candidate order remains normalized/lowercase nested then basename per extension.
- `.tscn` precedes `.glb`; pivot-prefix GLB is skipped for static build.
- Successful/`ERR_BUSY` request is remembered; no usable candidate marks missing.
- Snapshot adopts exact Mesh/missing result; service never frees engine objects.
## Dependencies
- Requires: accepted resource snapshot `032a256`
- Blocks: remaining animated resource observer extraction
## Verification
- Synthetic cache/pipeline/snapshot lifecycle, path order, source boundaries and timing;
adjacent M2 and renderer gates; full headless suite and checkpoint dry-run.
## Documentation deliverables
- Inline API docs; module API/I/O/ownership; data-flow/state/sequence/dependency
diagrams; adjacent renderer docs and M03 Evidence.
## Status
- State: accepted
- Done: implementation, integration and post-merge acceptance
- Next: remaining animated resource observer extraction
- Blocked by:
## Handoff
- Commit: `7cb3e34` (`render: extract static M2 build resource observer`)
- Results: observer PASS (`11` cases, `20,000` iterations, `64.654ms`);
full autonomous suite PASS (`55/55`); renderer checkpoint dry-run PASS (`7/7`);
internal-access PASS (`30` private symbols); documentation PASS (`42` module
specs); coordination PASS with `30` historical warnings.
- Remaining risks: successful asynchronous admission is covered by source and
existing pipeline regressions because an undrained unit request leaks; private
asset traversal, p95/p99, leak and visual evidence remain unavailable;
animated observation and static finalize polling remain loader-owned.
- Documentation updated: new observer module API/I/O/data-flow/state/sequence/
dependency/ownership specification; M2 snapshot, pipeline/cache, world-renderer,
module registry, `RENDER.md` and M03 Evidence updated.
## Integration
- Merge: `a043c79` (`merge: static M2 build resource observer`)
- Post-merge: observer `11` cases / `20,000` iterations / `64.202ms`;
snapshot `12` / `42.895ms`; dispatch `10` / `18.096ms`; Mesh pipeline
`11` / `73.615ms`; Mesh cache `9` / `40.229ms`; prototype cache `16` /
`35.947ms`; shutdown, facade, internal-access `30`, manifest `7/7`,
documentation `42` and coordination passed. Checkpoint dry-run completed
`7/7`; expected private Azeroth/character assets remained unavailable.
@@ -0,0 +1,88 @@
# M03-RND-WMO-RENDER-GROUP-MATERIALIZER-001
<!-- OPENWC_CLAIM:M03-RND-WMO-RENDER-GROUP-MATERIALIZER-001:sindo-main-codex:2026-08-03 -->
## Owner
- Agent ID: `sindo-main-codex`
- Target: M03 Renderer Facade and Safe Extraction
- Branch: `work/sindo-main-codex/m03-wmo-render-group-materializer`
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-wmo-render-group-materializer`
## Outcome
Move one-step WMO MeshInstance3D and MultiMeshInstance3D creation, render
settings and attachment from the loader into one main-thread materializer.
## Non-goals
- Change WMO placement, resource loading, build-step planning or queue state.
- Change runtime Mesh finalization, WMOBuilder material behavior or cache formats.
- Change permits, visibility distances, shadow policy or visible output.
- Generalize M2 and WMO materialization behind a shared abstraction.
## Paths
- Exclusive: `src/render/wmo/wmo_render_group_materializer.gd`,
`src/tools/verify_wmo_render_group_materializer.gd`,
`docs/modules/wmo-render-group-materializer.md`, this claim
- Shared: loader, renderer module registry/specification, `RENDER.md`, M03 Evidence
## Contracts and data
- Mesh and MultiMesh resources retain exact identity.
- Names and optional transforms preserve existing indexed fallback behavior.
- Shadow and positive visibility-range settings are applied unchanged.
- The supplied WMO root becomes the sole SceneTree owner of the created node.
- Editor persisted ownership remains an explicit loader composition policy.
## Dependencies
- Requires: accepted WMO render build queue/planner and runtime Mesh finalizer
- Blocks: remaining loader-owned WMO runtime traversal cleanup
## Verification
- Synthetic Mesh/MultiMesh identity, naming, transform, rendering, attachment,
invalid-input, ownership, source and bounded-timing contracts; adjacent WMO and
renderer gates; full autonomous suite and checkpoint dry-run.
## Documentation deliverables
- Inline API docs; module API/I/O/ownership; data-flow/sequence/dependency
diagrams; adjacent renderer docs and M03 Evidence.
## Status
- State: ready
- Done: implementation, verification and documentation
- Next: integrator review and merge
- Blocked by:
## Handoff
- Commit: `f4d5e29` (`render: extract WMO render group materializer`)
- Results: materializer PASS `cases=37 iterations=1000 elapsed_ms=2.771`;
autonomous headless suite `63/64`, with only the proprietary
`verify_adt_m2_placements.gd` probe unavailable because `data/extracted` is
absent; cold/editor parse completed without script diagnostics after restoring
ignored generated/native worktree artifacts; checkpoint dry-run retained
`7/7`; documentation passed with `50` module specifications; coordination
passed with `77` historical expired-claim warnings.
- Fidelity: exact Mesh/MultiMesh identity, indexed/fallback presentation,
render settings, attachment and scheduler/queue boundaries are unchanged.
No private-asset or original-client visual parity claim is added.
- Remaining risks: synchronous main-thread Node creation remains; no private WMO
visual comparison, long traversal, leak/GPU or p95/p99 evidence.
- Documentation: new full module specification with API/I/O and data-flow,
sequence/dependency diagrams; renderer registry/source map and `RENDER.md`
updated.
<!-- OPENWC_HANDOFF:READY:M03-RND-WMO-RENDER-GROUP-MATERIALIZER-001:f4d5e29 -->
<!-- OPENWC_INTEGRATION:ACCEPTED:M03-RND-WMO-RENDER-GROUP-MATERIALIZER-001:705354d -->
- Merge: `705354d` (`merge: WMO render group materializer`)
- Post-merge: materializer PASS `cases=37 iterations=1000 elapsed_ms=3.236`;
all nine adjacent WMO services, shutdown, materials, facade, internal-access
`30`, manifest `7/7`, documentation `50` and coordination passed.
@@ -0,0 +1,79 @@
# M03-RND-WMO-RENDER-RESOURCE-FINALIZER-001
<!-- OPENWC_CLAIM:M03-RND-WMO-RENDER-RESOURCE-FINALIZER-001:sindo-main-codex:2026-07-20 -->
## Owner
- Agent ID: `sindo-main-codex`
- Target: M03 Renderer Facade and Safe Extraction
- Branch: `work/sindo-main-codex/m03-wmo-render-resource-finalizer`
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-wmo-render-resource-finalizer`
## Outcome
Move lightweight WMO render-cache terminal ResourceLoader polling, script/format
validation and Resource/missing publication from the loader into one service.
## Non-goals
- Change `.res` cache path selection or threaded request admission.
- Own WMO build permits, queues, placement state, Nodes or materialization.
- Change `WMOStreamingResource.FORMAT_VERSION`, fallback order or visible output.
- Combine render Resource and PackedScene finalization behind a generic callback.
## Paths
- Exclusive: `src/render/wmo/wmo_render_resource_finalizer.gd`,
`src/tools/verify_wmo_render_resource_finalizer.gd`,
`docs/modules/wmo-render-resource-finalizer.md`, this claim
- Shared: loader, WMO render cache state spec/verifier, world renderer registry/spec,
`RENDER.md`, M03 Evidence
## Contracts and data
- Pending requests are polled in detached Dictionary insertion order.
- Only LOADED/FAILED statuses complete requests.
- Failed, null, wrong-script and stale-format Resources publish missing outcome.
- Current exact-script Resources are adopted without duplication.
- Request admission, reset/shutdown drain order and cache lifetime are unchanged.
## Dependencies
- Requires: accepted WMO render Resource cache state
- Blocks: remaining loader-owned WMO orchestration/materialization cleanup
## Verification
- Synthetic status/order/load/validation/adoption/source/timing contracts;
adjacent WMO/renderer gates; full headless suite and checkpoint dry-run.
## Documentation deliverables
- Inline API docs; module API/I/O/ownership; data-flow/state/sequence/dependency
diagrams; adjacent renderer docs and M03 Evidence.
## Status
- State: accepted
- Done: implementation, verification and documentation
- Next: next unclaimed M03 renderer extraction package
- Blocked by:
## Handoff
- Commit: `a86f8f2` (`render: extract WMO render resource finalizer`)
- Results: finalizer PASS `cases=24 iterations=1000 elapsed_ms=3.938`;
adjacent WMO cache/queue/planner/registry/resolver/scene-cache/shutdown checks
passed; autonomous headless suite `60/60`; documentation `47`; coordination
passed with `34` historical warnings; checkpoint dry-run `7/7`.
- Remaining risks: terminal ResourceLoader polling remains a synchronous
main-thread boundary; no serialized private WMO, corrupt-cache, visual, leak,
descriptor-pressure or p95/p99 evidence was added.
- Documentation updated: new full module specification with API/I/O and
data-flow/state/sequence/dependency diagrams; adjacent WMO cache/world-renderer
specs, module registry, `RENDER.md` and M03 Evidence.
- Merge: `1acddab` (`merge: WMO render resource finalizer`)
- Post-merge: finalizer PASS `cases=24 iterations=1000 elapsed_ms=3.777`;
WMO cache/queue/planner/registry/resolver/scene-cache/shutdown, facade,
internal-access `30`, manifest and checkpoint dry-run `7/7`, documentation
`47` and coordination gates passed.
@@ -0,0 +1,78 @@
# M03-RND-WMO-RUNTIME-MESH-FINALIZER-001
<!-- OPENWC_CLAIM:M03-RND-WMO-RUNTIME-MESH-FINALIZER-001:sindo-main-codex:2026-07-20 -->
## Owner
- Agent ID: `sindo-main-codex`
- Target: M03 Renderer Facade and Safe Extraction
- Branch: `work/sindo-main-codex/m03-wmo-runtime-mesh-finalizer`
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-wmo-runtime-mesh-finalizer`
## Outcome
Move cached WMO runtime Mesh material refresh versioning, surface iteration and
material-definition reconstruction from the loader into one service.
## Non-goals
- Change WMO traversal, placement, build-job scheduling or Node materialization.
- Change WMOBuilder shader/material behavior or cache formats.
- Change texture path ordering, metadata names or visible output.
- Generalize M2 and WMO finalization behind a shared abstraction.
## Paths
- Exclusive: `src/render/wmo/wmo_runtime_mesh_finalizer.gd`,
`src/tools/verify_wmo_runtime_mesh_finalizer.gd`,
`docs/modules/wmo-runtime-mesh-finalizer.md`, this claim
- Shared: loader, WMO build/cache and world renderer specs/verifiers,
`RENDER.md`, M03 Evidence
## Contracts and data
- Null meshes remain null; accepted meshes retain exact Resource identity.
- Refresh metadata version `10` remains the admission boundary.
- Only stale `ArrayMesh` surfaces with cached WMO texture metadata are rebuilt.
- Texture indices remain compact and ordered texture0, texture1, texture2.
- WMO flags, shader, blend mode and cached colors are forwarded unchanged.
- Scene traversal, builder material semantics and shutdown order are unchanged.
## Dependencies
- Requires: accepted WMO render and scene Resource finalizers
- Blocks: remaining loader-owned WMO runtime materialization cleanup
## Verification
- Synthetic identity/version/type/surface/metadata/color/definition/source/timing
contracts; adjacent WMO/renderer gates; full suite and checkpoint dry-run.
## Documentation deliverables
- Inline API docs; module API/I/O/ownership; data-flow/state/sequence/dependency
diagrams; adjacent renderer docs and M03 Evidence.
## Status
- State: accepted
- Done: implementation, verification and documentation
- Next: next unclaimed M03 renderer extraction package
- Blocked by:
## Handoff
- Commit: `ae40e3f` (`render: extract WMO runtime Mesh finalizer`)
- Results: finalizer PASS `cases=27 iterations=1000 elapsed_ms=0.402` in the
full run; autonomous headless suite `63/63`; documentation `49`; coordination
passed with `34` historical warnings; checkpoint dry-run `7/7`.
- Remaining risks: WMOBuilder surface mutation remains synchronous main-thread
work; no private WMO corpus, visual comparison, leak/GPU or p95/p99 evidence.
- Documentation updated: new full module specification with API/I/O and
data-flow/state/sequence/dependency diagrams; renderer registry/source map,
`RENDER.md` and M03 Evidence.
- Merge: `d65ebee` (`merge: WMO runtime Mesh finalizer`)
- Post-merge: finalizer PASS `cases=27 iterations=1000 elapsed_ms=0.202`;
WMO scene/render cache/finalizer/queue/planner/registry/resolver, shutdown,
materials, facade, internal-access `30`, manifest and checkpoint dry-run
`7/7`, documentation `49` and coordination gates passed.
@@ -0,0 +1,93 @@
# M03-RND-WMO-RUNTIME-SCENE-PREPARER-001
<!-- OPENWC_CLAIM:M03-RND-WMO-RUNTIME-SCENE-PREPARER-001:sindo-main-codex:2026-08-03 -->
## Owner
- Agent ID: `sindo-main-codex`
- Target: M03 Renderer Facade and Safe Extraction
- Branch: `work/sindo-main-codex/m03-wmo-runtime-scene-preparer`
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-wmo-runtime-scene-preparer`
## Outcome
Move cached/live WMO subtree preparation—recursive Mesh finalization, direct
Occluders-child policy and recursive shadow enabling—from the loader into one
stateless main-thread service.
## Non-goals
- Change scene/prototype loading, validation, placement or attachment.
- Change WMO material refresh rules, cache versions or builder behavior.
- Change occlusion/shadow defaults or add visibility/portal behavior.
- Change queue, scheduler, ownership or shutdown lifecycle.
## Paths
- Exclusive: `src/render/wmo/wmo_runtime_scene_preparer.gd`,
`src/tools/verify_wmo_runtime_scene_preparer.gd`,
`docs/modules/wmo-runtime-scene-preparer.md`, this claim
- Shared: loader, renderer module registry/specification, `RENDER.md`, M03 Evidence
## Contracts and data
- Cached preparation finalizes every MeshInstance3D Mesh and non-null
MultiMeshInstance3D Mesh in depth-first traversal order.
- Live preparation does not cross the runtime Mesh finalizer boundary.
- Disabled occlusion removes only the direct child named `Occluders` and queues
it for deletion; enabled occlusion retains it.
- Enabled shadows recursively set every GeometryInstance3D to ON; disabled
shadows preserve existing per-node values.
- The service borrows the subtree and retains no Node or Resource.
## Dependencies
- Requires: accepted WMO runtime Mesh finalizer
- Blocks: remaining loader-owned WMO scene/prototype orchestration cleanup
## Verification
- Synthetic cached/live traversal, exact resource/order, occluder, shadow,
invalid-input, ownership, source and bounded-timing contracts; adjacent WMO,
renderer, documentation and coordination gates; checkpoint dry-run.
## Documentation deliverables
- Inline API docs; module API/I/O/ownership; data-flow/sequence/dependency
diagrams; adjacent renderer docs and M03 Evidence.
## Status
- State: ready
- Done: implementation, verification and documentation
- Next: integrator review and merge
- Blocked by:
## Handoff
- Commit: `ffed91c` (`render: extract WMO runtime scene preparer`)
- Results: preparer PASS `cases=31 iterations=1000 elapsed_ms=3.669`;
updated Mesh-finalizer boundary PASS `cases=28 iterations=1000
elapsed_ms=0.204`; headless suite `64/65` with no unexpected failures and
only the proprietary ADT placement probe unavailable without `data/extracted`;
editor parse had zero script diagnostics; checkpoint dry-run retained `7/7`;
documentation passed with `51` module specifications; coordination passed
with `77` historical expired-claim warnings.
- Fidelity: cached/live preparation distinction, exact Mesh traversal order,
direct `Occluders` lookup/removal and enabled/preserved shadow semantics are
unchanged. No private-asset or original-client parity claim is added.
- Remaining risks: recursive Resource/SceneTree mutation remains synchronous;
no asset-backed WMO portal/room, visual, leak/GPU or p95/p99 evidence.
- Documentation: new full module specification with API/I/O and data-flow,
sequence/dependency diagrams; Mesh-finalizer ownership spec, renderer registry,
world-renderer source map and `RENDER.md` updated.
<!-- OPENWC_HANDOFF:READY:M03-RND-WMO-RUNTIME-SCENE-PREPARER-001:ffed91c -->
<!-- OPENWC_INTEGRATION:ACCEPTED:M03-RND-WMO-RUNTIME-SCENE-PREPARER-001:57d0a9f -->
- Merge: `57d0a9f` (`merge: WMO runtime scene preparer`)
- Post-merge: preparer PASS `cases=31 iterations=1000 elapsed_ms=3.486`;
Mesh finalizer `cases=28`/`0.199ms`; all nine adjacent WMO services, shutdown,
materials, facade, internal-access `30`, manifest `7/7`, documentation `51`
and coordination passed.
@@ -0,0 +1,90 @@
# M03-RND-WMO-SCENE-INSTANCE-FACTORY-001
<!-- OPENWC_CLAIM:M03-RND-WMO-SCENE-INSTANCE-FACTORY-001:sindo-main-codex:2026-08-03 -->
## Owner
- Agent ID: `sindo-main-codex`
- Target: M03 Renderer Facade and Safe Extraction
- Branch: `work/sindo-main-codex/m03-wmo-scene-instance-factory`
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-wmo-scene-instance-factory`
## Outcome
Move WMO cached-scene instantiation/currentness validation and live-prototype
duplication with shared name/placement application into one main-thread factory.
## Non-goals
- Change cache lookup/admission, ResourceLoader or prototype construction.
- Change runtime Mesh/material/occluder/shadow preparation.
- Change attachment, placement registry, queues, permits or lifetime.
- Change scene cache version rules or placement formulas.
## Paths
- Exclusive: `src/render/wmo/wmo_scene_instance_factory.gd`,
`src/tools/verify_wmo_scene_instance_factory.gd`,
`docs/modules/wmo-scene-instance-factory.md`, this claim
- Shared: loader, adjacent WMO verifier/spec, renderer registry/specification,
`RENDER.md`, M03 Evidence
## Contracts and data
- Null scenes/prototypes and non-Node3D instantiation/duplication return null.
- Cached instances failing the injected currentness validator are synchronously freed.
- Accepted instances retain exact descendant Resource identities.
- Both paths apply `relative_path.get_file().get_basename()` and the exact
placement-resolver Transform3D.
- The factory returns detached roots and retains no Node or Resource.
## Dependencies
- Requires: accepted WMO placement resolver and runtime scene preparer
- Blocks: remaining loader-owned WMO cache/prototype orchestration cleanup
## Verification
- Synthetic cached/live type, validation, freeing, exact identity, name,
transform, dependency/order, ownership, source and bounded-timing contracts;
adjacent WMO/renderer gates and checkpoint dry-run.
## Documentation deliverables
- Inline API docs; module API/I/O/ownership; data-flow/sequence/dependency
diagrams; adjacent renderer docs and M03 Evidence.
## Status
- State: ready
- Done: implementation, verification and documentation
- Next: integrator review and merge
- Blocked by:
## Handoff
- Commit: `7e97b19` (`render: extract WMO scene instance factory`)
- Results: factory PASS `cases=41 iterations=1000 elapsed_ms=5.297`;
placement resolver dependency PASS `cases=10 iterations=20000
elapsed_ms=28.740`; suite `65/66` with no unexpected failures and only the
proprietary ADT placement probe unavailable; editor parse had zero script
diagnostics; checkpoint dry-run retained `7/7`; documentation passed with
`52` module specifications; coordination passed with `77` historical warnings.
- Fidelity: valid cached/live cache-validation distinction, basename, exact
Transform3D and descendant Resource identity are unchanged. Invalid non-Node3D
cached roots are now freed synchronously, fixing an error-path leak without
changing valid visible output.
- Remaining risks: instantiation/duplication remains synchronous; no private WMO
portal/room, visual, long-traversal, leak/GPU or p95/p99 evidence.
- Documentation: new full factory module specification; placement resolver
consumers/sequence, renderer registry/source map and `RENDER.md` updated.
<!-- OPENWC_HANDOFF:READY:M03-RND-WMO-SCENE-INSTANCE-FACTORY-001:7e97b19 -->
<!-- OPENWC_INTEGRATION:ACCEPTED:M03-RND-WMO-SCENE-INSTANCE-FACTORY-001:541279e -->
- Merge: `541279e` (`merge: WMO scene instance factory`)
- Post-merge: factory PASS `cases=41 iterations=1000 elapsed_ms=5.308`;
placement resolver `cases=10`/`31.207ms`; runtime preparation/finalization,
adjacent WMO services, shutdown, materials, facade, internal-access `30`,
manifest `7/7`, documentation `52` and coordination passed.
@@ -0,0 +1,80 @@
# M03-RND-WMO-SCENE-RESOURCE-FINALIZER-001
<!-- OPENWC_CLAIM:M03-RND-WMO-SCENE-RESOURCE-FINALIZER-001:sindo-main-codex:2026-07-20 -->
## Owner
- Agent ID: `sindo-main-codex`
- Target: M03 Renderer Facade and Safe Extraction
- Branch: `work/sindo-main-codex/m03-wmo-scene-resource-finalizer`
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-wmo-scene-resource-finalizer`
## Outcome
Move cached WMO PackedScene terminal ResourceLoader polling, probe validation,
probe lifetime and scene/missing publication from the loader into one service.
## Non-goals
- Change `.tscn` path selection, file-size admission or request start behavior.
- Own live fallback, placement/build jobs, attached Nodes or scheduler permits.
- Change WMOBuilder cache metadata/version rules or visible output.
- Merge PackedScene and lightweight render Resource finalization generically.
## Paths
- Exclusive: `src/render/wmo/wmo_scene_resource_finalizer.gd`,
`src/tools/verify_wmo_scene_resource_finalizer.gd`,
`docs/modules/wmo-scene-resource-finalizer.md`, this claim
- Shared: loader, WMO scene cache state spec/verifier, world renderer registry/spec,
`RENDER.md`, M03 Evidence
## Contracts and data
- Pending requests are polled in detached Dictionary insertion order.
- Only LOADED/FAILED statuses complete requests.
- Failed, null, non-PackedScene, non-Node3D and stale scenes publish missing.
- Accepted PackedScenes retain exact Resource identity.
- Validation instantiates once and releases accepted/rejected Node3D probes once.
- Admission, oversize logging, live fallback and shutdown order are unchanged.
## Dependencies
- Requires: accepted WMO scene Resource cache state
- Blocks: remaining loader-owned WMO live fallback/materialization cleanup
## Verification
- Synthetic status/order/type/probe/validation/lifetime/adoption/source/timing
contracts; adjacent WMO/renderer gates; full suite and checkpoint dry-run.
## Documentation deliverables
- Inline API docs; module API/I/O/ownership; data-flow/state/sequence/dependency
diagrams; adjacent renderer docs and M03 Evidence.
## Status
- State: accepted
- Done: implementation, verification and documentation
- Next: next unclaimed M03 renderer extraction package
- Blocked by:
## Handoff
- Commit: `7779e9e` (`render: extract WMO scene resource finalizer`)
- Results: finalizer PASS `cases=26 iterations=1000 elapsed_ms=4.186`;
adjacent WMO scene/render cache/finalizer/queue/planner/registry/resolver and
shutdown checks passed; autonomous headless suite `61/61`; documentation `48`;
coordination passed with `34` historical warnings; checkpoint dry-run `7/7`.
- Remaining risks: PackedScene probe validation remains synchronous main-thread
work; no serialized private WMO stale/oversize, long leak, visual,
descriptor-pressure or p95/p99 evidence was added.
- Documentation updated: new full module specification with API/I/O and
data-flow/state/sequence/dependency diagrams; adjacent WMO cache/world-renderer
specs, module registry, `RENDER.md` and M03 Evidence.
- Merge: `6a0f9bd` (`merge: WMO scene resource finalizer`)
- Post-merge: finalizer PASS `cases=26 iterations=1000 elapsed_ms=3.970`;
WMO scene/render cache/finalizer/queue/planner/registry/resolver/shutdown,
facade, internal-access `30`, manifest and checkpoint dry-run `7/7`,
documentation `48` and coordination gates passed.
@@ -0,0 +1,95 @@
# M04-RND-FIDELITY-ROADMAP-001
<!-- OPENWC_CLAIM:M04-RND-FIDELITY-ROADMAP-001:sindo-main-codex-renderer-roadmap:2026-08-04 -->
<!-- OPENWC_INTEGRATION:ACCEPTED:M04-RND-FIDELITY-ROADMAP-001:afbc4fa -->
## Owner
- Agent ID: `sindo-main-codex-renderer-roadmap`
- Target: M04 Renderer Fidelity and Graphics Foundation
- Branch: `work/sindo-main-codex-renderer-roadmap/m04-renderer-fidelity-plan`
- Worktree: `C:\Users\sindo\open-wc-worktrees\m04-renderer-fidelity-plan`
## Outcome
Insert an evidence-driven renderer-fidelity milestone immediately after M03 and
shift the existing M04–M13 executable targets to M05–M14 without losing their
content, dependencies or status semantics.
## Non-goals
- Implement renderer behavior or claim visual parity in this package.
- Capture or commit proprietary original-client screenshots.
- Begin the shifted Editor, content, network or gameplay milestones.
## Paths
- Exclusive: `targets/04-renderer-fidelity.md`, this claim
- Shared/hotspot: all numbered target filenames and cross-target dependencies,
`targets/README.md`, `targets/DEVELOPMENT_ROADMAP.md`, `targets/roadmap/`,
`docs/ROADMAP.md`, renderer/testing/feature documentation where milestone IDs
or fidelity gates are normative
## Contracts and data
- M03 remains `DONE`; new M04 becomes the only `ACTIVE` target.
- Existing executable targets shift monotonically: old M04–M13 become M05–M14.
- The new renderer gate uses original build-12340 captures as the authoritative
oracle and treats Noggit as a secondary composition/reference tool.
- Proprietary images remain outside Git; manifests, metadata, hashes, metrics and
human approval records are repository evidence.
- `Blizzlike335` and opt-in enhanced/racing graphics remain explicit profiles.
## Dependencies
- Requires: completed M03 renderer facade and closeout contracts.
- Blocks: renderer-fidelity implementation and all shifted milestones.
## Verification
- Coordination and documentation gates; target marker/index consistency; link
and milestone-reference audit; `git diff --check`.
## Fidelity evidence
- Planning evidence only: exact local client build `3.3.5.12340`, seven existing
paired checkpoints and the user-proposed two-viewpoint-per-location corpus.
No new parity claim is made.
## Documentation deliverables
- Executable M04 target with reference, static, temporal, subsystem, performance
and acceptance gates; shifted target dependencies; development/subsystem
roadmap and testing/feature-map alignment.
## Status
- State: integrated
- Done: inserted M04 renderer fidelity, shifted old M04–M13 to M05–M14,
updated dependencies, normative roadmaps, profile boundaries and test policy
- Next: begin the M04 reference manifest/capture contract work package when the
user supplies or approves the original-client CSV/capture corpus
- Blocked by:
<!-- OPENWC_HANDOFF:READY:M04-RND-FIDELITY-ROADMAP-001:f77f50c -->
## Handoff
- Commit: `f77f50c` on
`work/sindo-main-codex-renderer-roadmap/m04-renderer-fidelity-plan`.
- Outcome: M03 remains `DONE`; new M04 is the only `ACTIVE` target; Editor,
content, server, world editor, network, gameplay, playable client, quest,
completeness and dungeon targets moved intact to M05–M14.
- Contracts: original build-12340 captures are authoritative; Noggit is a
secondary composition reference; proprietary pixels remain outside Git;
Blizzlike/Enhanced/Racing graphics profiles are explicit boundaries.
- Verification: coordination passed `targets=15 active=1`; documentation passed
`module_specs=53 required_files=7`; target sequence passed `M00–M14`; old
executable-target links `0`; `git diff --check` passed.
- Fidelity: planning and corpus provenance only; no renderer implementation or
new parity claim is included.
- Documentation: executable M04, target index/dependency graph, renderer
subsystem plan, high-level roadmap, architecture, testing/tooling/coding/
documentation policies and affected future milestone references updated.
- Cache/migration: no runtime/cache/data format changes. The exact CSV schema is
intentionally deferred to the first M04 contract work package.
+12
View File
@@ -9,6 +9,12 @@ OpenWC состоит из двух продуктов на общей плат
Оба продукта используют общие канонические модели, импорт данных и renderer preview, но не разделяют UI, lifecycle и права на изменение данных.
Планируемый 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.
+14
View File
@@ -21,6 +21,11 @@
| 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) |
@@ -28,9 +33,11 @@
| 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) |
@@ -39,10 +46,17 @@
| WMO render build step planner | Implemented extraction | [`wmo-render-build-step-planner.md`](wmo-render-build-step-planner.md) |
| WMO render build 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
+22 -22
View File
@@ -27,9 +27,9 @@ and accept only candidates containing AnimationPlayer descendants.
```mermaid
flowchart LR
Loader[StreamingWorldLoader] -->|loaded Resource and material source| Finalizer[M2AnimatedSceneFinalizer]
Finalizer -->|accepted Node3D and player count| Loader
Loader --> Cache[M2PrototypeCacheState]
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,
@@ -50,10 +50,10 @@ other application layers are forbidden.
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|---|---|---|---|---|---|
| Input | Loaded Resource | Loader I/O adapter | Candidate instantiator | Borrowed Resource | One finalize permit |
| Input | Loaded Resource | Animation resource finalizer | Candidate instantiator | Borrowed Resource | One finalize permit |
| Input | Static material prototype Node3D | Loader cache/build adapter | Material repair | Borrowed Node | One repair call |
| Input | Detached animated candidate | Instantiator | Repair/final validation | Finalizer then caller/release | One attempt |
| Output | Accepted prototype and player count | Finalizer | Loader adoption/log adapter | Exact Node transferred | Shutdown cache lifetime |
| Output | Accepted prototype and player count | Finalizer | Resource-finalizer adoption/log adapter | Exact Node transferred | Shutdown cache lifetime |
| Output | Depth-first engine-node arrays | Traversal | Loader preparation/playback | Borrowed references | One call |
Side effects are PackedScene instantiation, surface override assignment and
@@ -91,20 +91,19 @@ stateDiagram-v2
```mermaid
sequenceDiagram
participant L as StreamingWorldLoader
participant R as M2AnimationResourceFinalizer
participant F as M2AnimatedSceneFinalizer
participant C as M2PrototypeCacheState
L->>F: instantiate_candidate(Resource)
F-->>L: detached Node3D or null
L->>L: get static material prototype
L->>F: repair_materials(candidate, source)
L->>F: finalize_candidate(candidate)
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-->>L: exact Node3D and player count
L->>C: adopt animated prototype
F-->>R: exact Node3D and player count
R->>C: adopt animated prototype
else rejected
F-->>L: empty; candidate freed
L->>C: mark animation static
F-->>R: empty; candidate freed
R->>C: mark animation static
end
```
@@ -112,11 +111,11 @@ sequenceDiagram
```mermaid
flowchart TB
Loader[StreamingWorldLoader] --> Finalizer[M2AnimatedSceneFinalizer]
ResourceFinalizer[M2AnimationResourceFinalizer] --> Finalizer[M2AnimatedSceneFinalizer]
Finalizer --> Engine[PackedScene / Node3D / Mesh / Material / AnimationPlayer]
Loader --> Resource[ResourceLoader]
ResourceFinalizer --> Resource[ResourceLoader]
Loader --> Budget[RenderBudgetScheduler]
Loader --> Prototype[M2PrototypeCacheState]
ResourceFinalizer --> Prototype[M2PrototypeCacheState]
Finalizer -. no dependency .-> Resource
Finalizer -. no dependency .-> Budget
Finalizer -. no dependency .-> Prototype
@@ -126,7 +125,7 @@ flowchart TB
- 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 loader/prototype cache path.
- 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.
@@ -134,11 +133,11 @@ flowchart TB
| Failure | Detection | Behavior | Diagnostic | Recovery |
|---|---|---|---|---|
| Null/unsupported Resource | Type guard | Return null | Dedicated verifier | Loader marks static |
| Null/unsupported Resource | Type guard | Return null | Dedicated verifier | Resource finalizer marks static |
| Wrong root type | Instantiated type guard | Free and return null | Node-count regression | Rebuild cache |
| 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 | Loader marks static |
| No AnimationPlayer | Descendant inventory | Free and return empty | Lifetime regression | Resource finalizer marks static |
| Shutdown/cancellation | Not owned | No retained state | N/A | Loader drains first |
## Configuration and capabilities
@@ -199,7 +198,8 @@ by `M2AnimatedInstanceMaterializer`.
| `src/render/m2/m2_animated_instance_materializer.gd` | Per-duplicate player inventory consumer and batch owner |
| `src/render/m2/m2_animation_load_pipeline_state.gd` | Pending/terminal records before finalization |
| `src/render/m2/m2_prototype_cache_state.gd` | Accepted prototype/static-only outcomes |
| `src/scenes/streaming/streaming_world_loader.gd` | I/O, permits, material source, adoption and logs |
| `src/render/m2/m2_animation_resource_finalizer.gd` | I/O, candidate composition, adoption and logs |
| `src/scenes/streaming/streaming_world_loader.gd` | Permits and material-source lookup |
| `src/tools/verify_m2_animated_scene_finalizer.gd` | Scene/material/lifetime/boundary/timing regression |
## Related decisions and references
@@ -14,8 +14,9 @@
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; animation eligibility and loading remain in
`StreamingWorldLoader`, while scene finalization and retained Node lifecycle
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
@@ -29,11 +30,14 @@ belong to `M2AnimatedSceneFinalizer` and `M2PrototypeCacheState`.
```mermaid
flowchart LR
Loader[StreamingWorldLoader] --> IO[ResourceLoader]
Loader --> State[M2AnimationLoadPipelineState]
State -->|detached pending records| Loader
Loader -->|opaque terminal status| State
State -->|completion FIFO| Loader
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]
```
@@ -62,9 +66,9 @@ renderer services are forbidden.
| 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 | Loader polling adapter | Finalize FIFO | Integer value | Until pop/clear |
| Output | Detached pending records | State | Loader poll/shutdown adapter | Caller-owned copies | One pass |
| Output | Oldest completion record | State | Loader finalizer | Transferred Dictionary | One finalize attempt |
| 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.
@@ -74,7 +78,7 @@ Side effects are limited to collection mutation and retaining String/integer val
```mermaid
flowchart TD
Start[Successful threaded request] --> Remember[Remember request]
Remember --> Poll[Loader polls detached snapshot]
Remember --> Poll[Resource finalizer polls detached snapshot]
Poll --> Terminal{Loaded or failed?}
Terminal -->|no| Poll
Terminal -->|yes| Complete[Complete with opaque status]
@@ -82,7 +86,7 @@ flowchart TD
FIFO --> Permit{Permit available?}
Permit -->|no| FIFO
Permit -->|yes| Pop[Pop oldest record]
Pop --> Finalize[Loader loads/instantiates or marks static]
Pop --> Finalize[Resource finalizer loads/instantiates or marks static]
```
## Lifecycle/state
@@ -100,20 +104,24 @@ stateDiagram-v2
```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
L->>R: load_threaded_request(GLB)
L->>S: remember_request(path, GLB)
O->>R: load_threaded_request(GLB)
O->>S: remember_request(path, GLB)
loop frames
L->>S: request_records_snapshot()
L->>R: load_threaded_get_status(GLB)
L->>F: poll terminal requests
F->>S: request_records_snapshot()
F->>R: load_threaded_get_status(GLB)
end
L->>S: complete_request(path, status)
L->>S: pop_finalize_record() after permit
L->>R: load_threaded_get(GLB)
L->>P: adopt animated prototype or mark static
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
@@ -121,15 +129,18 @@ sequenceDiagram
- 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.
- Loader owns PackedScene instantiation and material repair; prototype state owns
accepted detached Node references and static-only outcomes.
- 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
Loader[StreamingWorldLoader] --> State[M2AnimationLoadPipelineState]
Loader --> Resource[ResourceLoader]
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
@@ -142,7 +153,7 @@ flowchart TB
| Failure | Detection | Behavior | Diagnostic | Recovery |
|---|---|---|---|---|
| Empty/duplicate request | State guard | Reject unchanged | Contract verifier | Correct caller or request later |
| Request start/cache miss | Loader | No insertion; mark static | Existing loader path | Cache correction/reload |
| Request start/cache miss | Cached observer | No insertion; mark static | Observer contract | Cache correction/reload |
| Non-terminal status | Loader | Keep pending | Existing metric | Poll next frame |
| 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 |
@@ -154,7 +165,7 @@ flowchart TB
|---|---|---|---|---|
| `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 | Filter before state insertion |
| Animated allow/deny/primitive rules | Existing values | Existing renderer profile | Yes | Observer filters before state insertion |
## Persistence, cache and migration
@@ -179,16 +190,16 @@ no rebake or migration is required.
## Extension points
ResourceLoader polling may later move behind a separate adapter without
changing this value-only state contract. Animated-scene finalization is now a
sibling service.
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 |
| ResourceLoader and GLB selection | Existing loader-owned | Adjacent renderer tests | I/O adapter extraction optional |
| Cached request admission and GLB selection | Implemented in observer | Policy/GLB/source verifier | Asset-backed traversal pending |
| Terminal ResourceLoader polling | Implemented finalizer extraction | Status/order/source regressions | Asset-backed traversal pending |
| Animated prototype outcomes | Implemented extraction | Prototype cache verifier | Asset-backed animation fidelity pending |
## Known gaps and risks
@@ -202,14 +213,17 @@ sibling service.
| 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` | Eligibility, I/O, permits, instantiation and adoption |
| `src/scenes/streaming/streaming_world_loader.gd` | Permit loop, material lookup and composition |
| `src/tools/verify_m2_animation_load_pipeline_state.gd` | Lifecycle/boundary/timing regression |
## 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)
@@ -7,7 +7,7 @@
| 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-m2-animation-playback`, 2026-07-18 |
| Last verified | Worktree `work/sindo-main-codex-m03-integrator/m03-closeout`, 2026-08-02 |
| Profiles/capabilities | Imported GLB and native experimental animated M2 instances |
## Purpose
@@ -59,7 +59,7 @@ MultiMesh, SceneTree attachment and application layers are forbidden.
| Output | Mutated native/imported playback | Controller | Rendered instance | Nodes retain state/resources | Instance lifetime |
| Output | Detached native diagnostic records | Controller | Loader log adapter | Caller-owned Dictionaries | Debug call |
Side effects are native field assignment, prepare/phase calls, animation loop
Side effects are native field assignment, phased preparation calls, animation loop
mutation, play and seek. The service retains no inputs.
## Data flow
@@ -67,13 +67,13 @@ mutation, play and seek. The service retains no inputs.
```mermaid
flowchart TD
Identity[Path and index] --> Phase[Stable hash phase]
NativeInventory[Exact-script native inventory] --> Prepare[prepare runtime if available]
Phase --> NativePhase[set native 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]
NativePhase --> Diagnostics{Debug requested?}
Prepare --> Diagnostics{Debug requested?}
Diagnostics -->|yes| Snapshot[Detached runtime state]
```
@@ -103,7 +103,7 @@ sequenceDiagram
M->>F: animation_players_in_subtree(duplicate)
F-->>M: ordered players
M->>P: start_instance_playback(path, index, players, debug)
P->>N: prepare_runtime and set_phase
P->>N: prepare_runtime_at_phase
P->>A: choose, loop, play and seek
P-->>M: optional detached native diagnostics
M-->>M: tag states with instance index
@@ -132,6 +132,11 @@ flowchart TB
- 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
@@ -168,7 +173,8 @@ and material versions are unchanged; no rebake is required.
- `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, phase, detached diagnostics and boundaries.
script order, five-field copy, single-rebuild phased preparation, idempotent
ready, detached diagnostics and boundaries.
- Finalizer/build/prototype/material/shutdown regressions protect adjacent behavior.
- Fidelity evidence is exact policy/mutation extraction; no private asset or
original-client animation comparison is claimed.
@@ -192,7 +198,8 @@ for world doodads and compatibility fixtures.
- Hash phase intentionally depends on existing Godot String hashing behavior.
- Default-name heuristics are not a complete WoW animation-state mapping.
- No proprietary traversal, animation timing comparison, p95/p99 or paired-client run exists.
- Native CPU deformation remains proportional to vertex count and is unsuitable
for large numbers of independently animated instances without a future GPU path.
## Source map
@@ -0,0 +1,249 @@
# M2 Animation Resource Finalizer
## Metadata
| Field | Value |
|---|---|
| Status | Implemented extraction |
| Target/work package | M03 / `M03-RND-M2-ANIMATION-RESOURCE-FINALIZER-001` |
| Owners | Cached animated Resource polling, terminal load and prototype outcome |
| Last verified | Worktree `work/sindo-main-codex/m03-m2-animation-resource-finalizer`, 2026-07-18 |
| Profiles/capabilities | Existing optional cached-GLB animated M2 path |
## Purpose
Drain cached animated M2 ResourceLoader work without keeping terminal I/O and
prototype outcome logic in `StreamingWorldLoader`. The service polls pending
requests, prepares a detached scene candidate and completes material repair,
validation, cache adoption or static-only fallback.
## Non-goals
- Select cached GLB paths, apply allow/deny policy or admit threaded requests.
- Select/build native animated M2 prototypes.
- Look up the static material prototype used for repair.
- Own render permits, build jobs, playback, instances or SceneTree roots.
- Change ResourceLoader ordering, cache formats, profiles or visible output.
## Context and boundaries
```mermaid
flowchart LR
Observer[M2CachedAnimationResourceObserver] --> Pipeline[M2AnimationLoadPipelineState]
Loader[StreamingWorldLoader] --> Finalizer[M2AnimationResourceFinalizer]
Pipeline --> Finalizer
ResourceLoader --> Finalizer
Finalizer --> SceneFinalizer[M2AnimatedSceneFinalizer]
Finalizer --> Cache[M2PrototypeCacheState]
Loader --> Material[Material prototype lookup]
Material --> Finalizer
```
The two-phase API preserves the historical ordering: a loaded Resource must
instantiate a candidate before the loader performs material-prototype lookup.
## Public API
| Symbol | Kind | Purpose | Failure behavior |
|---|---|---|---|
| `poll_terminal_requests(pipeline, cache)` | I/O command | Move LOADED/FAILED requests to completion FIFO | Invalid composition returns zero |
| `prepare_next_candidate(pipeline, cache)` | I/O command/query | Pop one record, load Resource and instantiate candidate | Skip/failed/invalid outcome returns empty Dictionary |
| `finalize_prepared_candidate(preparation, material_source, cache, debug)` | Command/query | Repair, validate, adopt/log or mark static-only | Invalid/finalization failure returns null |
| `load_threaded_get_status(path)` | I/O adapter | Production status query with injectable test seam | Returns ResourceLoader status |
| `load_threaded_get(path)` | I/O adapter | Production terminal Resource retrieval with injectable test seam | May return null |
## Inputs and outputs
| Direction | Data | Producer | Consumer | Ownership/lifetime |
|---|---|---|---|---|
| Input | Pending/finalize records | Animation pipeline | Finalizer | Pipeline-owned copied Dictionaries |
| Input | ResourceLoader status/Resource | Godot ResourceLoader | Finalizer | Resource reference for one operation |
| Input | Material source root | Loader lookup | Scene finalizer | Borrowed exact Node3D |
| Input | Prototype/static-only state | Prototype cache | Finalizer | Borrowed service; loader session |
| Output | Prepared candidate Dictionary | Finalizer | Loader/finalizer completion | Caller-owned, immediate operation |
| Output | Canonical animated prototype | Prototype cache | Loader build path | Borrowed exact Node3D |
| Output | Static-only outcome | Finalizer | Prototype cache | Copied path; loader session |
| Output | Success diagnostic | Finalizer | Runtime log | Debug-gated one-line record |
## Data flow
```mermaid
flowchart TD
Poll[Poll pending records in insertion order] --> Path{Resource path empty?}
Path -->|yes| Discard[Discard and mark static-only]
Path -->|no| Status{LOADED or FAILED?}
Status -->|no| Pending[Keep pending]
Status -->|yes| Complete[Append completion FIFO]
Complete --> Permit[Loader consumes one finalize permit]
Permit --> Pop[Pop oldest terminal record]
Pop --> Existing{Empty/cached/static-only?}
Existing -->|yes| Skip[Finish permit operation]
Existing -->|no| Loaded{Status LOADED?}
Loaded -->|no| Mark[Mark static-only]
Loaded -->|yes| Get[Get terminal Resource]
Get --> Instantiate[Instantiate detached candidate]
Instantiate --> Valid{Candidate exists?}
Valid -->|no| Mark
Valid -->|yes| Material[Loader resolves material source]
Material --> Repair[Repair and validate]
Repair --> Prototype{Prototype accepted?}
Prototype -->|no| Mark
Prototype -->|yes| Adopt[Adopt and optionally log]
```
## Lifecycle/state
```mermaid
stateDiagram-v2
[*] --> Polling
Polling --> Pending: nonterminal status
Polling --> TerminalQueued: LOADED or FAILED
Polling --> StaticOnly: empty Resource path
TerminalQueued --> Skipped: cached/static/invalid record
TerminalQueued --> StaticOnly: failed load or invalid candidate
TerminalQueued --> Prepared: detached candidate
Prepared --> Adopted: repair/finalize succeeds
Prepared --> StaticOnly: validation fails
Pending --> [*]
Skipped --> [*]
StaticOnly --> [*]
Adopted --> [*]
```
## Main sequence
```mermaid
sequenceDiagram
participant L as StreamingWorldLoader
participant F as AnimationResourceFinalizer
participant P as AnimationLoadPipelineState
participant R as ResourceLoader
participant S as AnimatedSceneFinalizer
participant C as PrototypeCacheState
L->>F: poll_terminal_requests(P, C)
F->>R: load_threaded_get_status(path)
F->>P: complete terminal request
L->>L: consume M2_ANIMATION_FINALIZE permit
L->>F: prepare_next_candidate(P, C)
F->>P: pop oldest finalize record
F->>R: load_threaded_get(path)
F->>S: instantiate_candidate(Resource)
alt candidate available
F-->>L: normalized/path/candidate
L->>L: resolve material prototype
L->>F: finalize_prepared_candidate(...)
F->>S: repair_materials + finalize_candidate
F->>C: adopt prototype or mark static-only
else rejected
F->>C: mark static-only
end
```
## Dependency diagram
```mermaid
flowchart TB
Finalizer[M2AnimationResourceFinalizer] --> ResourceLoader
Finalizer --> Pipeline[M2AnimationLoadPipelineState]
Finalizer --> SceneFinalizer[M2AnimatedSceneFinalizer]
Finalizer --> Cache[M2PrototypeCacheState]
Loader[StreamingWorldLoader] --> Finalizer
Loader --> Scheduler[RenderBudgetScheduler]
Loader --> MaterialLookup[Material prototype lookup]
Finalizer -. no dependency .-> Scheduler
Finalizer -. no dependency .-> BuildQueue[M2BuildQueue]
Finalizer -. no dependency .-> Playback[M2AnimationPlaybackController]
```
## Ownership, threading and resources
- All methods run synchronously on the renderer main thread.
- Pipeline owns pending and completion records; one loader permit pops one record.
- The prepared Dictionary temporarily owns the detached candidate reference. The
loader must call completion synchronously after material lookup.
- Existing scene finalizer frees rejected candidate roots. Prototype cache owns
accepted detached prototypes until final shutdown.
- Service retains only scene-finalizer and optional test-adapter references. The
production ResourceLoader path creates no self-reference or retained Resource.
- Loader retains scheduler permits, material lookup and all instance/SceneTree work.
## Errors, cancellation and recovery
| Failure/state | Behavior | Recovery |
|---|---|---|
| Missing service dependency | Return zero/empty/null | Correct renderer composition |
| Empty Resource path | Discard pending request and mark static-only | Repair cache and start new session |
| Nonterminal status | Keep request pending | Poll next tick |
| FAILED terminal status | Pop one record and mark static-only | Repair cache and start new session |
| Existing cached/static state | Pop and skip without terminal Resource get | None |
| Null/unsupported Resource | Mark static-only | Repair imported cache |
| Candidate validation failure | Scene finalizer frees candidate; mark static-only | Repair animation/cache |
| Tile cancellation | No direct transition; shared cache work continues | Loader queue remains authoritative |
| Shutdown | Loader drains ResourceLoader before pipeline/cache clear | New loader starts empty |
## Configuration and capabilities
The module adds no setting. Existing cached-animation allow/deny policy,
`m2_animation_finalize_ops_per_tick`, debug flag and scheduler lane remain exact.
## Persistence, cache and migration
No persistence, schema or cache path changes are introduced. Existing `.glb`
imports and `pivot_prefix_v1` eligibility remain owned by the cached observer.
## Diagnostics and observability
Successful debug-enabled adoption emits the unchanged `M2_ANIM_CACHE` record
with normalized path, Resource cache path and AnimationPlayer count. Pipeline
work metrics remain pending plus completion FIFO size.
## Verification
- Dedicated verifier covers pending/loaded/failed polling order, empty path,
completion FIFO, cached/static skips, terminal Resource get, candidate identity,
material repair arguments, adoption, rejection, source ownership and timing.
- Pipeline, scene-finalizer, prototype-cache, cached-observer, shutdown, facade,
internal-access and baseline regressions protect adjacent behavior.
- Fidelity evidence is exact lifecycle/I/O extraction only; no private asset,
original-client animation, visual, leak-pressure or p95/p99 claim is made.
## Extension points
Material-prototype lookup may later become a dedicated resource observer, which
would allow the entire two-phase operation to compose outside the loader. Static
Mesh Resource finalization remains separate to avoid premature generalization.
## Capability status
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| Animated request terminal polling | Implemented extraction | Status/order/source verifier | Asset-backed long traversal pending |
| Animated candidate finalization/outcome | Implemented extraction | Identity/repair/adoption verifier | Private visual/leak/p95/p99 pending |
| Material prototype lookup | Loader-owned | Existing renderer regressions | Dedicated observer optional |
## Known gaps and risks
- Terminal Resource get, instantiation and repair remain synchronous main-thread work.
- Prepared candidate ownership relies on immediate completion by the loader adapter.
- Private asset traversal, descriptor pressure, leak and p95/p99 evidence is absent.
## Source map
| Path | Responsibility |
|---|---|
| `src/render/m2/m2_animation_resource_finalizer.gd` | Poll/load/prepare/finalize/cache/log service |
| `src/render/m2/m2_animation_load_pipeline_state.gd` | Pending and completion FIFO ownership |
| `src/render/m2/m2_animated_scene_finalizer.gd` | Candidate instantiation/repair/validation/rejection cleanup |
| `src/render/m2/m2_prototype_cache_state.gd` | Accepted prototype/static-only ownership |
| `src/scenes/streaming/streaming_world_loader.gd` | Permits, material lookup and synchronous composition |
| `src/tools/verify_m2_animation_resource_finalizer.gd` | Polling/finalization/boundary/timing regression |
## Related decisions and references
- [`m2-animation-load-pipeline-state.md`](m2-animation-load-pipeline-state.md)
- [`m2-animated-scene-finalizer.md`](m2-animated-scene-finalizer.md)
- [`m2-cached-animation-resource-observer.md`](m2-cached-animation-resource-observer.md)
- [`m2-prototype-cache-state.md`](m2-prototype-cache-state.md)
- [`world-renderer.md`](world-renderer.md)
- [`../../RENDER.md`](../../RENDER.md)
- [`../../targets/roadmap/02-rendering-and-graphics.md`](../../targets/roadmap/02-rendering-and-graphics.md)
+10 -6
View File
@@ -32,8 +32,8 @@ flowchart LR
Loader --> Planner
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]
```
@@ -103,7 +103,9 @@ sequenceDiagram
- The planner owns only call-local scalar values and the returned Dictionary.
- `M2BuildQueue` owns typed jobs, FIFO ordering, serial numbers and group/offset
cursors. The loader owns tile checks, resource readiness/retry and adoption calls.
cursors. `M2BuildDispatchPlanner` owns the pure resource-state action decision;
the loader owns tile checks and native-first orchestration, while static and
cached animation observers own their observation/retry phases.
- Materializers own main-thread Node/MultiMesh construction under loader roots.
- 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 extract resource readiness/dispatch while retaining the
typed build-job and FIFO contracts defined by `M2BuildQueue`.
- 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.
@@ -160,7 +162,8 @@ queue depth, build activity and hitch observability.
|---|---|---|---|
| Static/animated batch cursor planning | Implemented extraction | Contract/source/timing verifier | Asset-backed p95/p99 pending |
| Typed build queue/cursor state | Implemented extraction | M2 build queue lifecycle verifier | Asset-backed traversal pending |
| Resource readiness/dispatch | Remains in loader | Existing lifecycle regressions | Stateful extraction pending |
| Resource dispatch decision | Implemented extraction | M2 build dispatch planner verifier | Asset-backed traversal pending |
| Resource observation/requests | Remains in loader | Existing lifecycle regressions | Stateful extraction pending |
| Spatial-cell batching | Planned | Renderer roadmap | Culling evidence/design pending |
## Known gaps and risks
@@ -176,6 +179,7 @@ queue depth, build activity and hitch observability.
| Path | Responsibility |
|---|---|
| `src/render/m2/m2_build_batch_planner.gd` | Pure limit/count/cursor planning |
| `src/render/m2/m2_build_dispatch_planner.gd` | Pure observed-state action/transition planning |
| `src/render/m2/m2_build_queue.gd` | Typed pending jobs, FIFO order and cursor ownership |
| `src/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 |
+237
View File
@@ -0,0 +1,237 @@
# M2 Build Dispatch Planner
## Metadata
| Field | Value |
|---|---|
| Status | Implemented extraction |
| Target/work package | M03 / `M03-RND-M2-BUILD-DISPATCH-PLANNER-001` |
| Owners | Pure M2 resource-state to build-action decision |
| Last verified | Worktree `work/sindo-main-codex/m03-m2-build-dispatch-planner`, 2026-07-18 |
| Profiles/capabilities | Existing static MultiMesh and animated-instance build paths |
## Purpose
Select one M2 build action after renderer observers produce animation/static
resource state. The planner makes wait priority, materializer selection and the
historical missing-model serial transition explicit without owning resources or
engine work.
## Non-goals
- Locate, request, load, cache or validate M2 resources.
- Size batches, own build jobs/queues or consume renderer permits.
- Create, attach or free Nodes, Meshes, MultiMeshes or animated instances.
- Change transforms, ordering, render settings, profiles or visible output.
- Add spatial-cell batching or M2 format/fidelity behavior.
## Context and boundaries
```mermaid
flowchart LR
Loader[StreamingWorldLoader resource observations] --> Planner[M2BuildDispatchPlanner]
Planner --> Action[Detached action and transition plan]
Action --> Loader
Loader --> Queue[M2BuildQueue rotate/progress]
Loader --> Animated[M2AnimatedInstanceMaterializer]
Loader --> Static[M2StaticBatchMaterializer]
Loader --> Budget[RenderBudgetScheduler]
```
The planner depends only on `RefCounted`, scalar values, `StringName` constants
and a fresh Dictionary. ResourceLoader, caches, Nodes, Meshes, RenderingServer,
SceneTree, files, workers, mutexes, gameplay, network and Editor APIs are forbidden.
## Public API
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|---|---|---|---|---|
| `ACTION_WAIT_FOR_ANIMATION` | Constant | Pending animation request blocks all other actions | Immutable | None |
| `ACTION_MATERIALIZE_ANIMATED` | Constant | Build the selected slice from animated prototype | Immutable | None |
| `ACTION_WAIT_FOR_STATIC_MESH` | Constant | Static resource is unresolved and queue must rotate | Immutable | None |
| `ACTION_MATERIALIZE_STATIC` | Constant | Build the selected slice from prepared static Mesh | Immutable | None |
| `ACTION_ADVANCE_WITHOUT_MATERIALIZATION` | Constant | Empty or terminally missing slice advances without a Node | Immutable | None |
| `plan_step(batch_count, resource_snapshot)` | Pure query | Return action, queue-rotation and serial-transition values | Any thread; call-local result | Null snapshot waits for static Mesh; non-positive count advances |
## Inputs and outputs
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|---|---|---|---|---|---|
| Input | Planned batch count | `M2BuildBatchPlanner` through loader | Dispatch planner | Integer copy | One call |
| Input | Typed animated/static availability, pending and missing observations | Loader / `M2BuildResourceSnapshot` | Dispatch planner | Borrowed snapshot; engine references are not read | One call |
| Output | Action | Dispatch planner | Loader materializer/wait branch | Immutable StringName | One operation |
| Output | `rotate_queue` | Dispatch planner | Loader queue adapter | Detached boolean | One operation |
| Output | `increment_batch_serial` | Dispatch planner | Loader progress adapter | Detached boolean | One operation |
The output Dictionary is newly allocated and caller-owned. The planner retains
no state, resource or engine-object reference.
## Data flow
```mermaid
flowchart TD
Input[Observed state] --> AnimationPending{Animation request pending?}
AnimationPending -->|yes| WaitAnimation[Wait animation; rotate]
AnimationPending -->|no| Positive{Batch count positive?}
Positive -->|no| AdvanceEmpty[Advance; keep serial]
Positive -->|yes| Animated{Animated prototype?}
Animated -->|yes| BuildAnimated[Materialize animated; increment serial]
Animated -->|no| StaticReady{Static Mesh ready?}
StaticReady -->|yes| BuildStatic[Materialize static; increment serial]
StaticReady -->|no| Missing{Model terminally missing?}
Missing -->|yes| AdvanceMissing[Advance without Node; increment serial]
Missing -->|no| WaitStatic[Wait static Mesh; rotate]
```
## Lifecycle/state
```mermaid
stateDiagram-v2
[*] --> Observe
Observe --> Waiting: animation/static unresolved
Observe --> Materializing: animated/static resource ready
Observe --> Advancing: empty or terminally missing batch
Waiting --> [*]: detached wait plan
Materializing --> [*]: detached materialization plan
Advancing --> [*]: detached advance plan
```
The planner itself is stateless. Queue rotation, cursor adoption, cancellation,
map reset and shutdown remain external lifecycle transitions.
## Main sequence
```mermaid
sequenceDiagram
participant L as StreamingWorldLoader
participant B as M2BuildBatchPlanner
participant D as M2BuildDispatchPlanner
participant Q as M2BuildQueue
participant M as M2 materializer
participant S as RenderBudgetScheduler
L->>L: observe animation request/prototype
alt animation not pending
L->>B: plan_batch
L->>L: lookup/request static Mesh when required
end
L->>D: plan_step(batch count, resource snapshot)
D-->>L: action, rotate flag, serial flag
alt wait action
L->>Q: rotate_front
else materialize action
L->>M: materialize selected slice
L->>Q: adopt progress and serial
else advance action
L->>Q: adopt progress and optional serial
end
L->>S: consume one M2_BUILD permit
```
## Dependency diagram
```mermaid
flowchart TB
Loader[StreamingWorldLoader] --> Dispatch[M2BuildDispatchPlanner]
Loader --> Batch[M2BuildBatchPlanner]
Loader --> Queue[M2BuildQueue]
Loader --> Caches[M2 cache/pipeline state]
Loader --> Materializers[M2 materializers]
Dispatch --> Snapshot[M2BuildResourceSnapshot accessors]
Dispatch --> Values[Integers + booleans + StringName]
Dispatch -. no dependency .-> Engine[Node / Mesh / ResourceLoader]
Dispatch -. no dependency .-> State[Queue / cache / scheduler state]
```
## Ownership, threading and resources
- Planner owns call-local comparisons and the returned Dictionary only.
- `M2BuildResourceSnapshot` owns the typed call-local observation contract;
dispatch reads availability/pending/missing accessors but no engine references.
- Loader owns native-first observation order, action execution and permit use;
native/static/cached-animation observers own their resource phases.
- `M2BuildQueue` owns pending jobs, FIFO keys and progress cursors.
- Materializers own main-thread scene construction under loader-owned roots.
- Pure calls are thread-safe; the current loader adapter calls on main thread.
## Errors, cancellation and recovery
| Failure/state | Detection | Behavior | Diagnostic | Recovery |
|---|---|---|---|---|
| Animation request pending | Boolean observation | Wait wins even if other flags are true | Priority fixture | Retry when queue returns front |
| Non-positive batch | Count comparison | Advance without serial increment | Zero/negative fixtures | Next group/cursor operation |
| Static Mesh unresolved | No Mesh and not missing | Rotate without serial increment | Retry fixture | Loader request/cache completes |
| Static model missing | Terminal missing flag | Advance without Node and increment serial | Missing fixture | Later group continues |
| Contradictory static ready/missing | Both true | Ready Mesh wins | Priority fixture | Loader normally prevents contradiction |
| Tile cancellation/root invalid | Outside planner | Loader cancels before dispatch | Queue/shutdown regressions | Eligible tile may requeue |
## Configuration and capabilities
The planner adds no setting. Existing animated/static batch limits, animation
enable flag, visibility/shadow settings and `M2_BUILD` permits remain external.
## Persistence, cache and migration
No state is serialized. No ADT/M2 cache format or version changes; no rebake or
migration is required.
## Diagnostics and observability
The planner emits no logs. Its verifier reports the action matrix, source
boundaries and bounded 20,000-call timing. Existing loader queue/hitch metrics
remain unchanged.
## Verification
- `verify_m2_build_dispatch_planner.gd` covers wait priority, zero/negative
counts, animated/static priority, unresolved retry, terminal missing, detached
results, loader/dependency boundaries and bounded timing.
- Queue, batch planner, materializers, caches, shutdown, facade, internal-access
and checkpoint tests protect adjacent behavior.
- Fidelity evidence is exact branch/transition extraction only. No private asset
or original-client visual comparison is claimed.
## Extension points
Static, cached animated and native animated observation now live behind sibling
services that produce `M2BuildResourceSnapshot`. Generic callbacks, signals and a shared state-machine
framework remain intentionally excluded.
## Capability status
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| M2 build action selection | Implemented extraction | Priority/matrix/source/timing verifier | Asset-backed traversal pending |
| Static/cached/native animation observation | Implemented separately | Observer/cache/pipeline regressions | Asset-backed traversal pending |
| Queue/cursor state | Implemented separately | M2 build queue verifier | Asset-backed traversal pending |
| Materialization | Implemented separately | Static/animated materializer verifiers | GPU/p95/p99 evidence pending |
## Known gaps and risks
- Native prototype attempts remain synchronous through the observer; loader still
owns static request I/O and terminal animated ResourceLoader polling.
- Synthetic timing does not measure ResourceLoader, Node or GPU work.
- Private traversal, leak, visual and p95/p99 evidence remains unavailable.
## Source map
| Path | Responsibility |
|---|---|
| `src/render/m2/m2_build_dispatch_planner.gd` | Pure action priority and transition plan |
| `src/render/m2/m2_build_resource_snapshot.gd` | Typed per-step resource observation contract |
| `src/render/m2/m2_cached_animation_resource_observer.gd` | Cached animation observation/request phase |
| `src/render/m2/m2_native_animation_resource_observer.gd` | Native candidate/read/build/cache observation |
| `src/scenes/streaming/streaming_world_loader.gd` | Observation order, action execution, permits and engine lifetime |
| `src/render/m2/m2_build_batch_planner.gd` | Batch count and cursor plan |
| `src/render/m2/m2_build_queue.gd` | Pending jobs, FIFO and cursor ownership |
| `src/tools/verify_m2_build_dispatch_planner.gd` | Matrix, boundary and timing regression |
## Related decisions and references
- [`m2-build-batch-planner.md`](m2-build-batch-planner.md)
- [`m2-build-resource-snapshot.md`](m2-build-resource-snapshot.md)
- [`m2-build-queue.md`](m2-build-queue.md)
- [`m2-static-batch-materializer.md`](m2-static-batch-materializer.md)
- [`m2-animated-instance-materializer.md`](m2-animated-instance-materializer.md)
- [`world-renderer.md`](world-renderer.md)
- [`../../RENDER.md`](../../RENDER.md)
- [`../../targets/roadmap/02-rendering-and-graphics.md`](../../targets/roadmap/02-rendering-and-graphics.md)
+8 -1
View File
@@ -33,6 +33,7 @@ flowchart LR
Queue --> Job[M2BuildJob]
Queue --> Loader
Loader --> Planner[M2BuildBatchPlanner]
Loader --> Dispatch[M2BuildDispatchPlanner]
Loader --> Static[M2StaticBatchMaterializer]
Loader --> Animated[M2AnimatedInstanceMaterializer]
Loader --> Scheduler[RenderBudgetScheduler]
@@ -171,6 +172,8 @@ flowchart TB
- 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.
@@ -229,7 +232,8 @@ queue base, signals and callbacks are intentionally excluded.
| Typed keyed M2 jobs and FIFO | Implemented extraction | Lifecycle/order/source/timing verifier | Asset-backed traversal pending |
| 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 readiness/dispatch | Existing loader-owned | Adjacent cache/build tests | Safe extraction remains |
| Resource dispatch decision | Implemented separately | Dispatch planner verifier | Asset-backed traversal pending |
| Resource observation/requests | Existing loader-owned | Adjacent cache/build tests | Safe extraction remains |
## Known gaps and risks
@@ -246,11 +250,14 @@ queue base, signals and callbacks are intentionally excluded.
| `src/render/m2/m2_build_queue.gd` | Keyed job ownership, FIFO/stale/rotation lifecycle and diagnostics |
| `src/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)
+239
View File
@@ -0,0 +1,239 @@
# M2 Build Resource Snapshot
## Metadata
| Field | Value |
|---|---|
| Status | Implemented extraction |
| Target/work package | M03 / `M03-RND-M2-BUILD-RESOURCE-SNAPSHOT-001` |
| Owners | Per-build-step observed M2 resource references and outcome flags |
| Last verified | Worktree `work/sindo-main-codex/m03-m2-build-resource-snapshot`, 2026-07-18 |
| Profiles/capabilities | Existing static MultiMesh and animated-instance build paths |
## Purpose
Carry the resource observations for one M2 build operation as a typed contract:
normalized path, optional animated prototype, pending-animation state, optional
static Mesh and terminal missing-model state. The snapshot never owns engine lifetime.
## Non-goals
- Locate, request, load, validate or cache M2 resources.
- Decide dispatch action, batch count, queue order or permit consumption.
- Create, attach or free Nodes, Meshes, MultiMeshes or animated instances.
- Change animation allowlists, native candidate rules or cache paths/formats.
- Persist observations beyond one build operation.
## Context and boundaries
```mermaid
flowchart LR
Native[M2NativeAnimationResourceObserver] --> Snapshot[M2BuildResourceSnapshot]
Cached[M2CachedAnimationResourceObserver] --> Snapshot
Static[M2StaticBuildResourceObserver] --> Snapshot
Snapshot --> Dispatch[M2BuildDispatchPlanner]
Snapshot --> Loader
Loader --> Animated[M2AnimatedInstanceMaterializer]
Loader --> Static[M2StaticBatchMaterializer]
```
The snapshot depends only on `RefCounted`, String, booleans and borrowed Node3D/
Mesh references. ResourceLoader, cache/pipeline state, SceneTree mutation,
RenderingServer/RIDs, files, workers, mutexes, gameplay, network and Editor APIs
are forbidden.
## Public API
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|---|---|---|---|---|
| Constructor `(normalized_path, animated_prototype, animation_pending)` | Command | Capture first-phase animation observation | Main thread; one build operation | Values retained exactly |
| `normalized_relative_path()` | Query | Return observed normalized path | Snapshot lifetime | None |
| `animated_prototype()` | Query | Borrow exact animated prototype | Main thread; no ownership transfer | May be null |
| `has_animated_prototype()` | Query | Report prototype availability | Any serialized caller | None |
| `animation_request_pending()` | Query | Report pending animation request | Any serialized caller | None |
| `adopt_static_observation(mesh, missing)` | Command | Replace second-phase static observation | Main thread | Values retained exactly, including contradictory inputs |
| `static_mesh()` | Query | Borrow exact prepared Mesh | Main thread; no ownership transfer | May be null |
| `has_static_mesh()` | Query | Report static Mesh availability | Any serialized caller | None |
| `static_model_missing()` | Query | Report terminal missing outcome | Any serialized caller | None |
| `diagnostic_snapshot()` | Query | Return detached path/availability flags | Any serialized caller | Omits engine references |
## Inputs and outputs
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|---|---|---|---|---|---|
| Input | Normalized relative path | Loader path adapter | Snapshot/diagnostics | Copied String | Snapshot lifetime |
| Input | Optional animated prototype | Native/cached animation observer | Snapshot/materializer adapter | Borrowed exact Node3D reference | One build operation |
| Input | Animation request pending | Cached animation observer | Snapshot/dispatch planner | Boolean copy | One build operation |
| Input | Optional prepared static Mesh | Mesh cache/request path | Snapshot/materializer adapter | Borrowed exact Mesh reference | One build operation |
| Input | Static model terminally missing | Prototype cache state | Snapshot/dispatch planner | Boolean copy | One build operation |
| Output | Availability/pending/missing values | Snapshot | Dispatch planner | Scalar reads | One dispatch plan |
| Output | Borrowed prototype or Mesh | Snapshot | Loader materializer adapter | No ownership transfer | One materialization call |
| Output | Detached diagnostics | Snapshot | Tests/future metrics | Caller-owned Dictionary | Call result |
The snapshot is released after the build-loop operation. Releasing or replacing
it never frees the borrowed Node3D or Mesh.
## Data flow
```mermaid
flowchart TD
Path[Normalized M2 path] --> Construct[Create snapshot]
Animated[Animated prototype or null] --> Construct
Pending[Animation request pending] --> Construct
Construct --> PendingCheck{Animation pending?}
PendingCheck -->|yes| Dispatch[Dispatch planner]
PendingCheck -->|no| Batch[Batch planner]
Batch --> StaticNeeded{Positive static batch?}
StaticNeeded -->|yes| Adopt[Adopt Mesh and missing observation]
StaticNeeded -->|no| Dispatch
Adopt --> Dispatch
Dispatch --> Borrow[Loader borrows selected resource]
```
## Lifecycle/state
```mermaid
stateDiagram-v2
[*] --> AnimationObserved: construct
AnimationObserved --> AnimationObserved: animation pending or animated path
AnimationObserved --> StaticObserved: adopt static result
StaticObserved --> StaticObserved: replace static result
AnimationObserved --> Released: operation ends
StaticObserved --> Released: operation ends
Released --> [*]
```
## Main sequence
```mermaid
sequenceDiagram
participant L as StreamingWorldLoader
participant S as M2BuildResourceSnapshot
participant B as M2BuildBatchPlanner
participant D as M2BuildDispatchPlanner
participant M as M2 materializer
L->>L: delegate native animated prototype observation
L->>S: construct native result
opt no native prototype
L->>L: delegate cached animation observer
L->>S: receive cached/pending snapshot
end
alt animation not pending
L->>B: plan batch using snapshot availability
opt positive static batch
L->>L: lookup/request static Mesh
L->>S: adopt_static_observation
end
end
L->>D: plan_step(batch count, snapshot)
D-->>L: action plan
opt materialization action
L->>S: borrow prototype or Mesh
L->>M: materialize selected slice
end
```
## Dependency diagram
```mermaid
flowchart TB
Loader[StreamingWorldLoader] --> Snapshot[M2BuildResourceSnapshot]
Dispatch[M2BuildDispatchPlanner] --> Snapshot
Snapshot --> Values[String and booleans]
Snapshot --> Borrowed[Borrowed Node3D and Mesh]
Snapshot -. no dependency .-> IO[ResourceLoader / FileAccess]
Snapshot -. no dependency .-> State[Cache / pipeline / queue / scheduler]
Snapshot -. no dependency .-> Mutation[SceneTree / RenderingServer]
```
## Ownership, threading and resources
- Snapshot owns only copied scalar state and temporary references.
- Prototype/cache services retain resource state; the native observer owns its
phase while loader owns native-first ordering among resource observers.
- Scene roots remain loader/tile-owned; Mesh lifetime follows cache/resource refs.
- Dispatch planner reads only snapshot accessors and never mutates the snapshot.
- Current construction/adoption occurs on renderer main thread.
## Errors, cancellation and recovery
| Failure/state | Detection | Behavior | Diagnostic | Recovery |
|---|---|---|---|---|
| Empty path | Exact String retained | No validation or hidden normalization | Contract fixture | Loader already normalizes input |
| Null prototype/Mesh | Availability accessor false | Dispatch uses pending/missing state | Contract fixture | Loader request/cache path retries |
| Mesh and missing both true | Exact adoption | Both retained; dispatch gives ready Mesh priority | Contradiction fixture | Loader normally prevents combination |
| Static observation replaced | Explicit adoption | Latest Mesh/missing values win | Replacement fixture | None required |
| Borrowed Node externally freed | Outside snapshot | Loader validity/lifecycle gates remain authoritative | Shutdown regressions | Tile may cancel/requeue |
| Tile cancellation | Outside snapshot | Ephemeral snapshot releases references | Queue/shutdown regressions | Eligible tile may rebuild |
## Configuration and capabilities
The snapshot adds no setting. Existing animation enable/allow/deny rules, batch
limits, cache paths, render settings and scheduler permits remain external.
## Persistence, cache and migration
No observation is serialized. No ADT/M2 cache version changes and no rebake or
migration are required.
## Diagnostics and observability
`diagnostic_snapshot()` exposes normalized path and four availability/outcome
flags without Node/Mesh references. The module emits no logs. Existing loader
queue/hitch metrics remain unchanged.
## Verification
- `verify_m2_build_resource_snapshot.gd` covers exact path/reference identity,
defaults, static adoption/replacement/clear, contradictory flags, detached
diagnostics, engine lifetime, loader/dispatch boundaries and bounded timing.
- Dispatch, queue, batch, materializer, cache/pipeline, shutdown, facade,
internal-access and checkpoint regressions protect adjacent behavior.
- Fidelity evidence is exact observation transfer only; no private asset or
original-client visual claim is made.
## Extension points
`M2CachedAnimationResourceObserver` produces the cached animated phase and
`M2StaticBuildResourceObserver` produces the static phase. Native animation is
produced by `M2NativeAnimationResourceObserver`. The value stays independent of all
producers and of generic callback frameworks.
## Capability status
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| Typed per-step M2 resource observation | Implemented extraction | Identity/adoption/lifetime/source/timing verifier | Asset-backed traversal pending |
| Dispatch decision | Implemented separately | Dispatch planner verifier | Asset-backed traversal pending |
| Static lookup/request execution | Implemented separately | Static observer verifier | Asset-backed traversal pending |
| Cached animated lookup/request execution | Implemented separately | Cached observer verifier | Asset-backed traversal pending |
| Native animated lookup/build execution | Implemented observer extraction | Native observer lifecycle/source verifier | Asset-backed traversal pending |
| Engine lifetime/materialization | Loader/materializer-owned | Lifetime/materializer regressions | GPU/p95/p99 evidence pending |
## Known gaps and risks
- Static observation is mutable during its short two-phase construction.
- Raw Node3D/Mesh references remain necessary for current materializer APIs.
- Native parsing/build remains synchronous; loader still owns terminal polling.
- Synthetic timing does not measure resource, Node or GPU work.
## Source map
| Path | Responsibility |
|---|---|
| `src/render/m2/m2_build_resource_snapshot.gd` | Typed per-step resource observations and diagnostics |
| `src/render/m2/m2_build_dispatch_planner.gd` | Snapshot-to-action planning |
| `src/render/m2/m2_static_build_resource_observer.gd` | Static snapshot production and requests |
| `src/render/m2/m2_cached_animation_resource_observer.gd` | Cached animated snapshot production and requests |
| `src/render/m2/m2_native_animation_resource_observer.gd` | Native prototype observation producer |
| `src/scenes/streaming/streaming_world_loader.gd` | Observer ordering and materializer borrowing |
| `src/tools/verify_m2_build_resource_snapshot.gd` | Identity/adoption/lifetime/boundary/timing regression |
## Related decisions and references
- [`m2-build-dispatch-planner.md`](m2-build-dispatch-planner.md)
- [`m2-build-batch-planner.md`](m2-build-batch-planner.md)
- [`m2-build-queue.md`](m2-build-queue.md)
- [`world-renderer.md`](world-renderer.md)
- [`../../RENDER.md`](../../RENDER.md)
- [`../../targets/roadmap/02-rendering-and-graphics.md`](../../targets/roadmap/02-rendering-and-graphics.md)
@@ -0,0 +1,242 @@
# M2 Cached Animation Resource Observer
## Metadata
| Field | Value |
|---|---|
| Status | Implemented extraction |
| Target/work package | M03 / `M03-RND-M2-CACHED-ANIMATION-RESOURCE-OBSERVER-001` |
| Owners | Cached animated M2 eligibility, request admission and initial snapshot |
| Last verified | Worktree `work/sindo-main-codex/m03-m2-cached-animation-resource-observer`, 2026-07-18 |
| Profiles/capabilities | Existing optional cached-GLB animated M2 path |
## Purpose
Produce the cached-animation phase of `M2BuildResourceSnapshot`: reuse an exact
animated prototype, wait for a pending request, admit the first safe GLB request,
or record a terminal static-only animation outcome without changing behavior.
## Non-goals
- Load/build native GryphonRoost animation or emit its diagnostics.
- Poll/finalize threaded Resources or instantiate/fix imported scenes.
- Own/free prototype Nodes, snapshots, pipeline entries or cache state.
- Materialize instances, consume permits or change animation policy defaults.
- Change GLB layout, cache format, batching, profiles or visible output.
## Context and boundaries
```mermaid
flowchart LR
Loader[StreamingWorldLoader] --> Native[M2NativeAnimationResourceObserver]
Native -->|no prototype| Observer[M2CachedAnimationResourceObserver]
Prototype[M2PrototypeCacheState] --> Observer
Pipeline[M2AnimationLoadPipelineState] --> Observer
Resource[ResourceLoader and GLB JSON] --> Observer
Observer --> Snapshot[M2BuildResourceSnapshot]
Snapshot --> Dispatch[M2BuildDispatchPlanner]
```
## Public API
| Symbol | Kind | Purpose | Failure behavior |
|---|---|---|---|
| `observe(path, cache_dir, max_primitives, allow, deny, prototype_state, pipeline_state, debug)` | Command/query | Produce cached prototype/pending/static-only snapshot | Invalid input returns empty non-pending snapshot |
| `find_eligible_glb_cache_path(...)` | I/O query | Select first existing safe historical candidate | Returns empty String |
| `is_animation_path_allowed(path, allow, deny)` | Pure query | Apply stripped case-insensitive substring policy | Empty path/allowlist rejects; deny wins |
| `cache_resource_paths(cache_dir, path)` | Pure query | Return nested/lowercase/basename GLB candidates | Empty values yield fewer candidates |
| `glb_cache_is_safe_for_runtime_animation(path, max, debug)` | I/O query | Validate animations, primitive limit and schema | Invalid/missing GLB rejects |
| `read_glb_json(path)` | I/O query | Read version-2 GLB JSON chunk | Returns empty Dictionary |
| `glb_primitive_count(gltf)` | Pure query | Count primitives across meshes | Invalid entries contribute zero |
| `glb_animation_schema(gltf)` | Pure query | Read OpenWC schema marker | Missing marker returns empty String |
## Inputs and outputs
| Direction | Data | Producer | Consumer | Ownership/lifetime |
|---|---|---|---|---|
| Input | Normalized M2 path/cache directory | Loader | Observer | Copied Strings; one observation |
| Input | Primitive cap and allow/deny patterns | Loader exports | Observer | Copied/read-only call values |
| Input | Prototype and request states | M2 state services | Observer | Borrowed services; map/session |
| Input | Existing GLB cache file | Offline cache pipeline | Observer | Read-only file access |
| Output | Resource snapshot | Observer | Loader/dispatch/materializer | Caller-owned snapshot; one build step |
| Output | Borrowed exact prototype | Prototype state | Snapshot/materializer | No ownership transfer |
| Output | Threaded request record | Observer | Animation pipeline | Pipeline-owned copied paths |
| Output | Static-only transition | Observer | Prototype state | State-owned copied path |
## Data flow
```mermaid
flowchart TD
Start[Observe normalized path] --> Valid{Valid composition?}
Valid -->|no| Empty[Empty non-pending snapshot]
Valid -->|yes| Cached{Prototype cached?}
Cached -->|yes| Ready[Snapshot with exact prototype]
Cached -->|no| Static{Already static-only?}
Static -->|yes| Empty
Static -->|no| Pending{Request exists?}
Pending -->|yes| Wait[Pending snapshot]
Pending -->|no| Policy{Allow and not deny?}
Policy -->|no| Mark[Mark static-only]
Policy -->|yes| Candidate[Historical GLB candidates]
Candidate --> Safe{Animations, primitive cap, schema safe?}
Safe -->|no usable| Mark
Safe -->|yes| Request[Threaded request]
Request -->|OK or busy| Remember[Remember and return pending snapshot]
Request -->|error| Mark
Mark --> Empty
```
## Lifecycle/state
```mermaid
stateDiagram-v2
[*] --> Observing
Observing --> Cached
Observing --> Pending
Observing --> Requested
Observing --> StaticOnly
Observing --> Rejected
Cached --> [*]
Pending --> [*]
Requested --> [*]
StaticOnly --> [*]
Rejected --> [*]
```
## Main sequence
```mermaid
sequenceDiagram
participant L as StreamingWorldLoader
participant O as CachedAnimationObserver
participant C as PrototypeCacheState
participant P as AnimationLoadPipelineState
participant R as ResourceLoader
participant S as M2BuildResourceSnapshot
L->>L: attempt native candidate
alt no native prototype
L->>O: observe(path, cache/policy/state)
O->>C: find animated / static-only
O->>P: has request
opt eligible cache candidate
O->>R: exists + GLB JSON + threaded request
O->>P: remember request
end
O-->>L: snapshot
else native prototype
L->>S: construct native snapshot
end
```
## Dependency diagram
```mermaid
flowchart TB
Observer[M2CachedAnimationResourceObserver] --> Snapshot[M2BuildResourceSnapshot]
Observer --> Prototype[M2PrototypeCacheState]
Observer --> Pipeline[M2AnimationLoadPipelineState]
Observer --> ResourceLoader
Observer --> FileAccess[GLB header and JSON]
Observer -. no dependency .-> Native[M2 native builder/repository]
Observer -. no dependency .-> Finalizer[M2AnimatedSceneFinalizer]
Observer -. no dependency .-> Scheduler[RenderBudgetScheduler]
Observer -. no dependency .-> SceneTree
```
## Ownership, threading and resources
- Observer retains no service, request, snapshot, Resource or Node reference.
- Prototype state retains accepted Nodes and static-only paths until shutdown.
- Pipeline state retains copied request paths until terminal polling/finalize.
- Snapshot borrows the exact cached prototype; observer never frees it.
- Resource existence/request and synchronous GLB JSON inspection run on the
renderer main thread, matching the previous loader behavior.
- The sibling native observer owns the native build attempt and the animation
resource finalizer owns terminal polling/finalize. Loader retains permits,
material lookup, materialization and SceneTree mutation.
## Errors, cancellation and recovery
| Failure/state | Behavior | Recovery |
|---|---|---|
| Empty path/dependency | Return empty non-pending snapshot without mutation | Correct composition |
| Cached prototype | Return exact reference immediately | None |
| Existing request | Return pending without duplicate admission | Retry after terminal drain |
| Disallowed/denied path | Mark static-only | New loader session/configuration |
| Missing/invalid/unsafe GLB | Continue candidates, then mark static-only | Repair/rebake cache and restart session |
| Request error | Mark static-only | New loader session/cache repair |
| Tile cancellation | Observer retains nothing | Loader queue remains authoritative |
| Shutdown | Loader drains requests before state clear | New loader starts empty |
## Configuration and capabilities
The service consumes existing `m2_cache_dir`, `m2_animated_max_primitives`,
`m2_animated_allowlist_patterns`, `m2_animated_denylist_patterns` and debug flag.
It adds no setting and preserves runtime mutability and defaults.
## Persistence, cache and migration
No persistence or cache format changes are introduced. Existing `.glb` candidate
layout and OpenWC schema markers are read unchanged; no rebake is required.
## Diagnostics and observability
Unsupported non-empty schemas preserve the historical optional
`M2_ANIM_REJECT` debug line. Snapshot and pipeline diagnostics remain detached;
queue and hitch metrics are unchanged.
## Verification
- Dedicated verifier covers invalid/cached identity/static/pending/missing
lifecycle, allow/deny semantics, exact path order, generated GLB header/JSON,
animation presence, primitive cap, accepted/rejected schemas, source boundaries
and bounded timing.
- Snapshot, dispatch, pipeline, prototype, shutdown, facade, internal-access and
checkpoint regressions protect adjacent behavior.
- Fidelity evidence is exact policy/I/O extraction. No original-client visual or
animation parity claim is made.
## Extension points
Native and cached observation remain sibling services. Terminal ResourceLoader
polling/finalization uses the dedicated finalizer while retaining pipeline state.
## Capability status
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| Cached animation observation/request | Implemented extraction | Lifecycle/policy/GLB/source/timing verifier | Asset-backed traversal pending |
| Native animation observation | Implemented sibling extraction | Native observer lifecycle/source verifier | Asset-backed traversal pending |
| Animated finalize/preparation | Implemented finalizer extraction | Pipeline/finalizer regressions | Asset-backed traversal pending |
## Known gaps and risks
- A successful asynchronous request is not started by the unit fixture because
leaving it undrained leaks process work; source and pipeline tests cover admission.
- Generated GLB metadata fixtures validate policy, not Godot import fidelity.
- Private traversal, leak/descriptor pressure, p95/p99 and paired visuals remain
unavailable without private assets.
## Source map
| Path | Responsibility |
|---|---|
| `src/render/m2/m2_cached_animation_resource_observer.gd` | Cached GLB policy, request and snapshot production |
| `src/render/m2/m2_build_resource_snapshot.gd` | Typed per-step observation value |
| `src/render/m2/m2_animation_load_pipeline_state.gd` | Pending request/finalize state |
| `src/render/m2/m2_animation_resource_finalizer.gd` | Terminal polling/load/finalize outcomes |
| `src/render/m2/m2_prototype_cache_state.gd` | Prototype and static-only outcomes |
| `src/render/m2/m2_native_animation_resource_observer.gd` | Native-first candidate/read/build/cache observation |
| `src/scenes/streaming/streaming_world_loader.gd` | Composition, fallback order, finalize and execution |
| `src/tools/verify_m2_cached_animation_resource_observer.gd` | Lifecycle/policy/GLB/boundary/timing regression |
## Related decisions and references
- [`m2-build-resource-snapshot.md`](m2-build-resource-snapshot.md)
- [`m2-native-animation-resource-observer.md`](m2-native-animation-resource-observer.md)
- [`m2-animation-load-pipeline-state.md`](m2-animation-load-pipeline-state.md)
- [`m2-prototype-cache-state.md`](m2-prototype-cache-state.md)
- [`m2-animated-scene-finalizer.md`](m2-animated-scene-finalizer.md)
- [`world-renderer.md`](world-renderer.md)
- [`../../RENDER.md`](../../RENDER.md)
- [`../../targets/roadmap/02-rendering-and-graphics.md`](../../targets/roadmap/02-rendering-and-graphics.md)
+24 -15
View File
@@ -78,7 +78,7 @@ Side effects are limited to collection mutation and retaining String/integer val
flowchart TD
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,7 @@ flowchart TD
FIFO --> Permit{Loader permit available?}
Permit -->|no| FIFO
Permit -->|yes| Pop[Pop oldest terminal record]
Pop --> Finalize[Loader gets Resource and delegates first-Mesh extraction]
Pop --> Finalize[Mesh resource finalizer gets Resource and extracts first Mesh]
Finalize --> Prepare[M2RuntimeMeshFinalizer prepares Mesh]
Prepare --> Adopt[Loader adopts Mesh or marks prototype outcome state]
```
@@ -113,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
@@ -120,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
@@ -135,7 +138,9 @@ 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 and prototype outcome cache states]
Loader --> Finalizer[M2RuntimeMeshFinalizer]
@@ -148,7 +153,9 @@ 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.
- `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
@@ -161,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
@@ -206,7 +213,8 @@ 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 |
| 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 |
@@ -224,11 +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/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, I/O polling, permits and adoption decisions |
| `src/scenes/streaming/streaming_world_loader.gd` | Cache path choice, request admission, permits and composition |
| `src/render/m2/m2_runtime_mesh_rebuild_classifier.gd` | Downstream stale Mesh rebuild decision |
| `src/tools/verify_m2_mesh_load_pipeline_state.gd` | Lifecycle/boundary/timing regression |
+9 -7
View File
@@ -103,9 +103,9 @@ sequenceDiagram
participant Pipeline as M2MeshLoadPipelineState
Loader->>Cache: find/has normalized path
alt cache miss
Loader->>Pipeline: request/poll/finalize record
Loader->>Loader: ResourceLoader get; delegate 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
@@ -133,8 +133,9 @@ flowchart TB
- Borrowed Mesh lookups do not transfer ownership or duplicate resources.
- `M2MeshResourceExtractor` owns first-Mesh selection and temporary PackedScene
instances. `M2PrototypeCacheState` owns missing/prototype/animated state; the
static materializer owns MultiMesh construction/attachment; the loader owns
resource adoption and build-job decisions.
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
@@ -186,7 +187,7 @@ 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 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 |
@@ -203,8 +204,9 @@ the historical Mesh cache had no queue contribution or log site.
|---|---|
| `src/render/m2/m2_mesh_resource_cache_state.gd` | Prepared Mesh references and final clear |
| `src/render/m2/m2_mesh_resource_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, raw/resource I/O and materialization |
| `src/scenes/streaming/streaming_world_loader.gd` | Normalization, request admission, permits and materialization |
| `src/render/m2/m2_runtime_mesh_finalizer.gd` | Refresh/rebuild/fallback preparation |
| `src/render/m2/m2_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 |
+10 -9
View File
@@ -92,27 +92,27 @@ No state or Resource reference is retained between calls.
```mermaid
sequenceDiagram
participant Loader as StreamingWorldLoader
participant Finalizer as M2MeshResourceFinalizer
participant Extractor as M2MeshResourceExtractor
participant Scene as PackedScene
Loader->>Extractor: extract_first_mesh(loaded Resource)
Finalizer->>Extractor: extract_first_mesh(loaded Resource)
alt direct Mesh
Extractor-->>Loader: same Mesh reference
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-->>Loader: Mesh or null
Extractor-->>Finalizer: Mesh or null
end
Loader->>Loader: prepare and cache Mesh or mark missing
Finalizer->>Finalizer: prepare and cache Mesh or mark missing
```
## Dependency diagram
```mermaid
flowchart TB
Loader[StreamingWorldLoader] --> Extractor[M2MeshResourceExtractor]
Finalizer[M2MeshResourceFinalizer] --> Extractor[M2MeshResourceExtractor]
Extractor --> Types[Resource / PackedScene / Node / Mesh]
Loader --> Pipeline[M2MeshLoadPipelineState]
Loader --> Cache[M2MeshResourceCacheState]
@@ -168,8 +168,8 @@ normalized M2 path. Its verifier measures traversal time and temporary Node coun
## Extension points
- `M2RuntimeMeshFinalizer` consumes the Mesh returned here and the loader stores
its result in `M2MeshResourceCacheState`.
- `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.
@@ -194,8 +194,9 @@ normalized M2 path. Its verifier measures traversal time and temporary Node coun
| 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` | ResourceLoader/raw I/O, cache decisions and materialization |
| `src/scenes/streaming/streaming_world_loader.gd` | Request admission, permits and materialization |
| `src/render/m2/m2_runtime_mesh_finalizer.gd` | Refresh/rebuild/fallback preparation |
| `src/render/m2/m2_mesh_resource_cache_state.gd` | Prepared Mesh retention |
| `src/tools/verify_m2_mesh_resource_extractor.gd` | Resource/order/lifetime/boundary/timing regression |
+251
View File
@@ -0,0 +1,251 @@
# M2 Mesh Resource Finalizer
## Metadata
| Field | Value |
|---|---|
| Status | Implemented extraction |
| Target/work package | M03 / `M03-RND-M2-MESH-RESOURCE-FINALIZER-001` |
| Owners | Static M2 terminal Resource I/O, Mesh preparation and cache outcome |
| Last verified | Worktree `work/sindo-main-codex/m03-m2-mesh-resource-finalizer`, 2026-07-18 |
| Profiles/capabilities | Existing cached/static MultiMesh M2 path |
## Purpose
Drain static M2 ResourceLoader work and publish a prepared Mesh or missing-model
outcome without keeping terminal I/O and refresh logic in `StreamingWorldLoader`.
The same preparation method serves threaded and synchronous Mesh consumers.
## Non-goals
- Select cache candidates or admit threaded ResourceLoader requests.
- Own scheduler permits, prototype Nodes, MultiMeshes or SceneTree roots.
- Change first-Mesh traversal, refresh version, rebuild rules or raw parser.
- Merge static and animated terminal services through callbacks/frameworks.
- Change cache formats, profiles or visible output.
## Context and boundaries
```mermaid
flowchart LR
Observer[M2StaticBuildResourceObserver] --> Pipeline[M2MeshLoadPipelineState]
Loader[StreamingWorldLoader] --> Finalizer[M2MeshResourceFinalizer]
Pipeline --> Finalizer
ResourceLoader --> Finalizer
Finalizer --> Extractor[M2MeshResourceExtractor]
Finalizer --> Runtime[M2RuntimeMeshFinalizer]
Finalizer --> Raw[M2RawModelRepository]
Finalizer --> MeshCache[M2MeshResourceCacheState]
Finalizer --> Prototype[M2PrototypeCacheState]
```
## Public API
| Symbol | Kind | Purpose | Failure behavior |
|---|---|---|---|
| `poll_terminal_requests(pipeline, prototype_cache)` | I/O command | Move LOADED/FAILED records into completion FIFO | Invalid composition returns zero |
| `finalize_next_resource(pipeline, mesh_cache, prototype_cache, extracted_dir)` | I/O command | Pop one record and publish Mesh/missing outcome | Empty/invalid composition returns false |
| `prepare_mesh_for_runtime(path, mesh, extracted_dir)` | I/O preparation | Preserve current Mesh or load raw data and refresh stale Mesh | Null returns null |
| `load_threaded_get_status(path)` | I/O adapter | Production status query with injectable test seam | Returns ResourceLoader status |
| `load_threaded_get(path)` | I/O adapter | Production terminal Resource retrieval with injectable test seam | May return null |
## Inputs and outputs
| Direction | Data | Producer | Consumer | Ownership/lifetime |
|---|---|---|---|---|
| Input | Pending/finalize records | Mesh pipeline | Finalizer | Pipeline-owned copied Dictionaries |
| Input | Terminal Resource | ResourceLoader | Mesh extractor | Borrowed one operation |
| Input | Extracted/cached Mesh | Mesh extractor/loader consumer | Runtime preparation | Borrowed Resource reference |
| Input | Raw M2 Dictionary | Raw repository | Runtime finalizer | Call-local value |
| Output | Prepared Mesh | Runtime finalizer | Mesh cache/loader consumer | Resource reference |
| Output | Missing-model outcome | Finalizer | Prototype cache | Copied path; loader session |
| Output | Processed-record flag | Finalizer | Loader permit loop/tests | Boolean value |
## Data flow
```mermaid
flowchart TD
Poll[Poll pending records in insertion order] --> Path{Resource path empty?}
Path -->|yes| Discard[Discard and mark missing]
Path -->|no| Status{LOADED or FAILED?}
Status -->|no| Pending[Keep pending]
Status -->|yes| Complete[Append completion FIFO]
Complete --> Permit[Loader consumes M2_MESH_FINALIZE permit]
Permit --> Pop[Pop oldest terminal record]
Pop --> Cached{Empty path or Mesh cached?}
Cached -->|yes| Skip[Finish one permit operation]
Cached -->|no| Loaded{Status LOADED?}
Loaded -->|no| Missing[Mark model missing]
Loaded -->|yes| Get[Get terminal Resource]
Get --> Extract[Extract first Mesh]
Extract --> Found{Mesh found?}
Found -->|no| Missing
Found -->|yes| Current{Refresh version current?}
Current -->|yes| Store[Store exact Mesh]
Current -->|no| Raw[Read raw M2 data]
Raw --> Refresh[Finalize/rebuild/fallback Mesh]
Refresh --> Store
```
## Lifecycle/state
```mermaid
stateDiagram-v2
[*] --> Polling
Polling --> Pending: nonterminal status
Polling --> TerminalQueued: LOADED or FAILED
Polling --> Missing: empty Resource path
TerminalQueued --> Skipped: empty/cached record
TerminalQueued --> Missing: failed load or no Mesh
TerminalQueued --> Extracted: Mesh found
Extracted --> Current: refresh version current
Extracted --> Refreshing: stale Mesh
Refreshing --> Prepared: rebuild or fallback
Current --> Cached
Prepared --> Cached
Pending --> [*]
Skipped --> [*]
Missing --> [*]
Cached --> [*]
```
## Main sequence
```mermaid
sequenceDiagram
participant L as StreamingWorldLoader
participant F as M2MeshResourceFinalizer
participant P as M2MeshLoadPipelineState
participant R as ResourceLoader
participant E as M2MeshResourceExtractor
participant N as M2RuntimeMeshFinalizer
participant C as M2MeshResourceCacheState
L->>F: poll_terminal_requests(P, prototype cache)
F->>R: load_threaded_get_status(path)
F->>P: complete terminal request
L->>L: consume M2_MESH_FINALIZE permit
L->>F: finalize_next_resource(...)
F->>P: pop oldest record
F->>R: load_threaded_get(path)
F->>E: extract_first_mesh(Resource)
F->>N: requires_raw_data_for_refresh(Mesh)
opt stale Mesh
F->>F: read raw M2 data
F->>N: finalize_mesh(...)
end
F->>C: store prepared Mesh
```
## Dependency diagram
```mermaid
flowchart TB
Finalizer[M2MeshResourceFinalizer] --> ResourceLoader
Finalizer --> Pipeline[M2MeshLoadPipelineState]
Finalizer --> Extractor[M2MeshResourceExtractor]
Finalizer --> Runtime[M2RuntimeMeshFinalizer]
Finalizer --> Raw[M2RawModelRepository]
Finalizer --> MeshCache[M2MeshResourceCacheState]
Finalizer --> Prototype[M2PrototypeCacheState]
Loader[StreamingWorldLoader] --> Finalizer
Loader --> Scheduler[RenderBudgetScheduler]
Finalizer -. no dependency .-> Scheduler
Finalizer -. no dependency .-> MultiMesh
Finalizer -. no dependency .-> SceneTree
```
## Ownership, threading and resources
- All operations run synchronously on the renderer main thread.
- Pipeline owns pending/completion records; one permit pops one terminal record.
- Extractor owns temporary PackedScene instance cleanup and returns a Mesh reference.
- Runtime finalizer owns stale/current/rebuild/fallback rules; raw repository owns
only call-local native I/O.
- Mesh cache retains prepared Mesh references until final shutdown; prototype
cache retains missing-model paths.
- Service retains dependency references but no pending record, Resource, Mesh or
raw Dictionary between calls. Loader retains permits and materialization.
## Errors, cancellation and recovery
| Failure/state | Behavior | Recovery |
|---|---|---|
| Missing composition dependency | Return zero/false; preparation keeps Mesh where possible | Correct composition |
| Empty Resource path | Discard request and mark missing | Repair cache and restart session |
| Nonterminal status | Keep pending | Poll next tick |
| FAILED status | Pop and mark missing | Repair cache and restart session |
| Existing cached Mesh | Pop and skip terminal Resource get | None |
| Null/unsupported/no-Mesh Resource | Mark missing | Repair imported cache |
| Raw read/rebuild unavailable | Runtime finalizer marks/reuses original Mesh | Existing fallback |
| Tile cancellation | Shared cache work continues | Loader queue remains authoritative |
| Shutdown | Loader drains ResourceLoader before pipeline/cache clear | New loader starts empty |
## Configuration and capabilities
The service consumes the existing extracted directory. The scheduler retains
`m2_mesh_finalize_ops_per_tick`; material refresh version remains `2`.
## Persistence, cache and migration
No cache path, schema or version changes are introduced. Existing `.tscn/.glb`
Resources and in-memory refresh metadata remain compatible; no rebake is required.
## Diagnostics and observability
The service adds no log. Existing pending-plus-finalize metrics and refresh
metadata remain unchanged. Normalized relative path remains the correlation key.
## Verification
- Dedicated verifier covers status insertion order, empty-path discard, FIFO,
failed/cached/null outcomes, terminal get, exact Mesh identity, stale-only raw
read, exact finalizer arguments, cache adoption, source ownership and timing.
- Pipeline/cache/extractor/runtime-finalizer/raw-repository/prototype/shutdown,
facade, internal-access and baseline regressions protect adjacent behavior.
- Fidelity evidence is exact lifecycle/I/O extraction only; no private asset,
original-client visual, leak-pressure or p95/p99 claim is made.
## Extension points
Further extraction can move static prototype/material-source construction without
changing this terminal Resource contract. Animated finalization remains separate
because its candidate-before-material ordering and Node ownership differ.
## Capability status
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| Static terminal polling/load | Implemented extraction | Status/FIFO/load/source verifier | Asset-backed traversal pending |
| Mesh extraction/preparation/cache outcome | Implemented extraction | Identity/raw/finalizer/store verifier | Private leak/p95/p99 pending |
| Static MultiMesh materialization | Separate implemented service | Materializer verifier | GPU/asset-backed evidence pending |
## Known gaps and risks
- Terminal Resource get, extraction and stale rebuild remain synchronous main-thread work.
- Prepared Mesh cache remains unbounded until final shutdown.
- Private asset traversal, descriptor pressure, leak and p95/p99 evidence is absent.
## Source map
| Path | Responsibility |
|---|---|
| `src/render/m2/m2_mesh_resource_finalizer.gd` | Poll/load/extract/prepare/cache/missing service |
| `src/render/m2/m2_mesh_load_pipeline_state.gd` | Pending and completion FIFO ownership |
| `src/render/m2/m2_mesh_resource_extractor.gd` | First-Mesh and temporary-instance ownership |
| `src/render/m2/m2_runtime_mesh_finalizer.gd` | Refresh/rebuild/fallback rules |
| `src/render/m2/m2_raw_model_repository.gd` | Optional native raw M2 boundary |
| `src/render/m2/m2_mesh_resource_cache_state.gd` | Prepared Mesh ownership |
| `src/render/m2/m2_prototype_cache_state.gd` | Missing-model outcome ownership |
| `src/scenes/streaming/streaming_world_loader.gd` | Permit loop and materialization composition |
| `src/tools/verify_m2_mesh_resource_finalizer.gd` | Polling/preparation/boundary/timing regression |
## Related decisions and references
- [`m2-mesh-load-pipeline-state.md`](m2-mesh-load-pipeline-state.md)
- [`m2-mesh-resource-cache-state.md`](m2-mesh-resource-cache-state.md)
- [`m2-mesh-resource-extractor.md`](m2-mesh-resource-extractor.md)
- [`m2-runtime-mesh-finalizer.md`](m2-runtime-mesh-finalizer.md)
- [`m2-raw-model-repository.md`](m2-raw-model-repository.md)
- [`world-renderer.md`](world-renderer.md)
- [`../../RENDER.md`](../../RENDER.md)
- [`../../targets/roadmap/02-rendering-and-graphics.md`](../../targets/roadmap/02-rendering-and-graphics.md)
@@ -0,0 +1,245 @@
# M2 Native Animation Resource Observer
## Metadata
| Field | Value |
|---|---|
| Status | Implemented extraction |
| Target/work package | M03 / `M03-RND-M2-NATIVE-ANIMATION-RESOURCE-OBSERVER-001` |
| Owners | Native animation candidate policy, raw read, build and cache outcome |
| Last verified | Worktree `work/sindo-main-codex/m03-m2-native-animation-resource-observer`, 2026-07-18 |
| Profiles/capabilities | Existing native GryphonRoost animated M2 path |
## Purpose
Resolve the historical native animated M2 path as one synchronous observation:
select a GryphonRoost candidate, reuse cached state, read animated raw data,
build a detached prototype and publish either the prototype or static-only result.
## Non-goals
- Add native animation candidates or change the `gryphonroost` predicate.
- Observe/request cached GLB animation or poll/finalize ResourceLoader work.
- Duplicate, attach, animate or free accepted instances in the SceneTree.
- Consume render permits or change build queue, batching and dispatch policy.
- Change native parsing/building, cache formats, profiles or visible output.
## Context and boundaries
```mermaid
flowchart LR
Loader[StreamingWorldLoader] --> Observer[M2NativeAnimationResourceObserver]
Observer --> Repository[M2RawModelRepository]
Observer --> Builder[M2NativeAnimatedBuilder]
Observer --> Cache[M2PrototypeCacheState]
Observer --> Prototype[Detached animated prototype]
Prototype --> Snapshot[M2BuildResourceSnapshot]
Observer -->|no prototype| Cached[M2CachedAnimationResourceObserver]
```
The observer is the native-first resource boundary. The loader supplies an
already-normalized path and remains responsible for fallback ordering and for
constructing the snapshot consumed by dispatch.
## Public API
| Symbol | Kind | Purpose | Failure behavior |
|---|---|---|---|
| `is_native_animation_candidate(path)` | Pure query | Apply the historical case-insensitive GryphonRoost substring policy | Empty/unmatched path returns false |
| `observe(path, extracted_dir, repository, cache, debug)` | Command/query | Return an exact cached/new native prototype or record static-only fallback | Invalid/non-candidate input returns null without mutation |
| `build_animated_prototype(data, extracted_dir)` | Adapter command | Call the retained native animated builder dependency | Missing builder or invalid result returns null |
The optional constructor dependency exists for deterministic tests; production
uses `M2NativeAnimatedBuilder` without a new abstraction or runtime setting.
## Inputs and outputs
| Direction | Data | Producer | Consumer | Ownership/lifetime |
|---|---|---|---|---|
| Input | Normalized relative M2 path | Loader path adapter | Observer | Copied String; one call |
| Input | Extracted directory | Loader configuration | Repository/builder | Copied String; one call |
| Input | Raw animated Dictionary | Repository | Observer/builder | Call-local value |
| Input | Prototype cache state | Loader composition | Observer | Borrowed service; loader session |
| Output | Cached or adopted prototype | Cache/observer | Loader snapshot | Borrowed exact Node3D reference |
| Output | Static-only transition | Observer | Prototype cache | Copied path; loader session |
| Output | Debug line | Observer | Runtime log | Emitted only when enabled and build succeeds |
## Data flow
```mermaid
flowchart TD
Start[Observe normalized path] --> Candidate{Valid native candidate?}
Candidate -->|no| None[Return null]
Candidate -->|yes| Cached{Cached prototype?}
Cached -->|yes| Return[Return exact prototype]
Cached -->|no| Static{Marked static-only?}
Static -->|yes| None
Static -->|no| Read[Read animated raw data]
Read --> Surfaces{Data and animated surfaces?}
Surfaces -->|no| Mark[Mark static-only]
Surfaces -->|yes| Build[Build detached prototype]
Build --> Children{Prototype has children?}
Children -->|no| Mark
Children -->|yes| Adopt[Adopt first cache prototype]
Adopt --> Log{Debug enabled?}
Log -->|yes| Emit[Emit exact native cache fields]
Log -->|no| Return
Emit --> Return
Mark --> None
```
## Lifecycle/state
```mermaid
stateDiagram-v2
[*] --> Observing
Observing --> Rejected: invalid or non-candidate
Observing --> Cached: positive cache hit
Observing --> StaticOnly: negative cache hit
Observing --> Reading: uncached candidate
Reading --> StaticOnly: no animated surfaces
Reading --> Building: usable raw data
Building --> StaticOnly: null or childless result
Building --> Adopted: valid result
Adopted --> [*]
Cached --> [*]
StaticOnly --> [*]
Rejected --> [*]
```
## Main sequence
```mermaid
sequenceDiagram
participant L as StreamingWorldLoader
participant O as NativeAnimationObserver
participant C as M2PrototypeCacheState
participant R as M2RawModelRepository
participant B as M2NativeAnimatedBuilder
L->>O: observe(path, directory, repository, cache, debug)
O->>C: find animated / is static-only
alt uncached native candidate
O->>R: load_animated_model_data(directory, path)
R-->>O: raw Dictionary
O->>B: build(raw, directory)
alt valid prototype
O->>C: adopt_animated_prototype(path, prototype)
O-->>L: canonical prototype
else unavailable
O->>C: mark_animation_static(path)
O-->>L: null
end
else cached result
O-->>L: prototype or null
end
```
## Dependency diagram
```mermaid
flowchart TB
Observer[M2NativeAnimationResourceObserver] --> Repository[M2RawModelRepository]
Observer --> Builder[M2NativeAnimatedBuilder]
Observer --> Cache[M2PrototypeCacheState]
Loader[StreamingWorldLoader] --> Observer
Observer -. no dependency .-> ResourceLoader
Observer -. no dependency .-> Pipeline[M2AnimationLoadPipelineState]
Observer -. no dependency .-> Scheduler[RenderBudgetScheduler]
Observer -. no dependency .-> SceneTree
```
## Ownership, threading and resources
- Observation and native parsing/building remain synchronous on the renderer
main thread, exactly as before extraction; there is no mid-call cancellation.
- The observer retains no path, raw Dictionary, Node or service reference.
- Prototype cache owns an accepted detached Node until final shutdown; callers
only borrow the canonical reference.
- The historical childless-builder rejection does not free its detached Node.
This deliberately preserved lifetime edge is documented as a remaining leak
risk rather than silently changed during architectural extraction.
- Loader owns snapshots, fallback sequencing, permits, materialization and every
SceneTree mutation.
## Errors, cancellation and recovery
| Failure/state | Behavior | Recovery |
|---|---|---|
| Empty/non-candidate path or missing dependency | Return null without state mutation | Correct composition/path |
| Cached prototype | Return exact canonical Node immediately | None |
| Existing static-only outcome | Return null without repeated I/O | New loader session after source repair |
| Empty raw data or animated surfaces | Mark static-only and return null | Repair source/parser and start new session |
| Null/childless build | Mark static-only and return null | Repair builder/data and start new session |
| Duplicate successful build | Cache releases later candidate and returns first | None |
| Cancellation during native call | Not supported; call completes | Loader controls whether the call starts |
| Shutdown | Observer retains nothing | Cache releases adopted Nodes after drains |
## Configuration and capabilities
The observer consumes the existing extracted directory and `debug_streaming`
flag. Candidate policy remains the case-insensitive substring `gryphonroost`.
No profile, cache, budget or animation setting is added.
## Persistence, cache and migration
No persistence or format changes are introduced. Positive and static-only state
continues to use the shutdown-lifetime prototype cache; no asset rebake is needed.
## Diagnostics and observability
A successful debug-enabled build emits the unchanged `M2_NATIVE_ANIM_CACHE`
record with path, surface count, bone count, animation id, sequence index,
activity score and length. Rejections remain silent as before.
## Verification
- Dedicated synthetic verifier covers candidate policy, invalid dependencies,
cache identity, static suppression, raw/surface/build failures, preserved
childless lifetime, exact repository/builder arguments, adoption, source
ownership and bounded candidate checks.
- Cached observer, raw repository, prototype cache, snapshot, dispatch, shutdown,
facade, internal-access and render-baseline checks protect adjacent behavior.
- Fidelity evidence is exact control-flow and diagnostic extraction. It is not
original-client animation, visual, memory or asset-backed performance evidence.
## Extension points
Terminal cached-GLB polling/finalization can move behind a separate adapter
without changing this native-first contract. Candidate expansion requires its
own fidelity evidence and must not silently change `Blizzlike335` behavior.
## Capability status
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| Native candidate selection | Implemented extraction | Policy/source/timing verifier | Additional candidates require fidelity work |
| Raw read/build/cache outcome | Implemented extraction | Failure/identity/adoption verifier | Asset-backed traversal pending |
| Terminal cached ResourceLoader polling | Loader-owned | Existing pipeline regressions | Dedicated finalize adapter |
## Known gaps and risks
- Native parsing/building is synchronous and not bounded by a render permit.
- Rejected childless builder Nodes retain the historical unowned lifetime edge.
- Private asset traversal, descriptor/leak pressure, p95/p99 and paired visual/
animation evidence remain unavailable in this package.
## Source map
| Path | Responsibility |
|---|---|
| `src/render/m2/m2_native_animation_resource_observer.gd` | Candidate/cache/raw/build/outcome/logging observation |
| `src/render/m2/m2_raw_model_repository.gd` | Optional native file/parser boundary |
| `src/render/m2/m2_prototype_cache_state.gd` | Positive prototype and static-only ownership |
| `addons/mpq_extractor/loaders/m2_native_animated_builder.gd` | Existing animated prototype construction |
| `src/scenes/streaming/streaming_world_loader.gd` | Composition, native-first fallback order and execution |
| `src/tools/verify_m2_native_animation_resource_observer.gd` | Lifecycle/identity/boundary/timing regression |
## Related decisions and references
- [`m2-cached-animation-resource-observer.md`](m2-cached-animation-resource-observer.md)
- [`m2-build-resource-snapshot.md`](m2-build-resource-snapshot.md)
- [`m2-raw-model-repository.md`](m2-raw-model-repository.md)
- [`m2-prototype-cache-state.md`](m2-prototype-cache-state.md)
- [`world-renderer.md`](world-renderer.md)
- [`../../RENDER.md`](../../RENDER.md)
- [`../../targets/roadmap/02-rendering-and-graphics.md`](../../targets/roadmap/02-rendering-and-graphics.md)
+19 -10
View File
@@ -28,7 +28,9 @@ paths and paths whose animation fallback is static-only.
```mermaid
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]
@@ -62,7 +64,7 @@ Mesh traversal and other application layers are forbidden.
| Input | Normalized non-empty M2 path | Loader normalization | Cache state | Copied String key | Shutdown lifetime |
| Input | 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 | Loader failure/fallback adapter | Cache state | Boolean set entry | Until final shutdown |
| Input | Missing/static-only outcome | Resource observers/finalizers | Cache state | Boolean set entry | Until final shutdown |
| Output | Canonical prototype Node3D | Cache state | Loader instance/material adapter | Borrowed exact reference | One lookup/use |
| Output | Detached path-only snapshot | Cache state | Tests/diagnostics | Fresh caller-owned arrays | One query |
@@ -135,11 +137,12 @@ sequenceDiagram
```mermaid
flowchart TB
Loader[StreamingWorldLoader] --> State[M2PrototypeCacheState]
Observer[M2 resource observers] --> State
State --> Node3D
Loader --> Raw[M2RawModelRepository]
Loader --> ResourceLoader
Observer --> ResourceLoader
Loader --> StaticBuilder[M2Builder]
Loader --> AnimatedBuilder[M2NativeAnimatedBuilder]
Observer --> AnimatedBuilder[M2NativeAnimatedBuilder]
State -. no dependency .-> Raw
State -. no dependency .-> ResourceLoader
State -. no dependency .-> StaticBuilder
@@ -165,7 +168,7 @@ flowchart TB
| 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 | Loader policy/result | Mark static-only outcome | Existing fallback behavior | New loader session/cache repair |
| Animation unavailable/unsafe | Observer/native result | Mark static-only outcome | Existing fallback behavior | New loader session/cache repair |
| Shutdown | Loader lifecycle | Release positive and clear all state | Shutdown verifier | New loader begins empty |
| Cancellation | Not owned | No state transition inside this service | N/A | Loader drains/cancels before shutdown clear |
@@ -177,8 +180,8 @@ flowchart TB
| Negative cache lifetime | Final loader shutdown | All | No | Preserves historical fallback suppression |
| Eviction capacity | Unbounded historical behavior | All | No | No mid-session Node destruction |
Animation enablement, candidate/allow/deny rules, cache paths and per-frame
permits remain loader configuration.
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
@@ -188,7 +191,10 @@ 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.
- Existing `M2_ANIM_CACHE` and native animation logs remain loader-owned.
- Cached eligibility rejection logging belongs to the cached observer; native
success logging belongs to the native observer and cached terminal success
logging belongs to the animation resource finalizer. Static terminal missing
outcomes are produced by `M2MeshResourceFinalizer`.
- Existing renderer queue metrics remain unchanged because these tables never
contributed work counts.
- Normalized relative path remains the correlation key.
@@ -208,8 +214,8 @@ and native M2 formats are unchanged; no migration or rebake is introduced.
## Extension points
Eviction or byte/count budgets require measured memory evidence and explicit
prototype-user lifetime rules. Animation request-state extraction can consume
this service without moving ResourceLoader or builder ownership into it.
prototype-user lifetime rules. Cached and native animation observers consume
this state without moving ResourceLoader or builder ownership into cache state.
## Capability status
@@ -233,7 +239,10 @@ this service without moving ResourceLoader or builder ownership into it.
| Path | Responsibility |
|---|---|
| `src/render/m2/m2_prototype_cache_state.gd` | Positive Node ownership, negative path state and shutdown release |
| `src/scenes/streaming/streaming_world_loader.gd` | Normalization, I/O/build/fallback decisions and cache adapters |
| `src/render/m2/m2_native_animation_resource_observer.gd` | Animated raw/build and positive/static-only transitions |
| `src/render/m2/m2_animation_resource_finalizer.gd` | Cached terminal positive/static-only transitions |
| `src/render/m2/m2_mesh_resource_finalizer.gd` | Static terminal missing transitions |
| `src/scenes/streaming/streaming_world_loader.gd` | Normalization, observer order, remaining I/O/fallback adapters |
| `src/tools/verify_m2_prototype_cache_state.gd` | Admission/identity/lifecycle/source/timing regression |
| `src/tools/verify_render_runtime_cache_shutdown.gd` | Integrated final-shutdown release regression |
+18 -9
View File
@@ -27,11 +27,15 @@ static or animated Dictionary consumed by existing builders and classifiers.
```mermaid
flowchart LR
Loader[StreamingWorldLoader] --> Repository[M2RawModelRepository]
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]
```
@@ -53,8 +57,8 @@ renderer policy and other application layers are forbidden.
| Input | Extracted directory String | Loader configuration | Repository path resolution | Copied value | One call |
| Input | 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` | Loader/finalizer/M2Builder | Fresh native result | One caller operation |
| Output | Animated raw M2 Dictionary | Native `load_m2_animated` | Loader/animated builder | Fresh native result | One caller operation |
| Output | 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
@@ -98,18 +102,18 @@ No request, result or failure state survives a call.
```mermaid
sequenceDiagram
participant Loader as StreamingWorldLoader
participant Caller as Loader or NativeAnimationObserver
participant Repo as M2RawModelRepository
participant File as FileAccess
participant Native as M2Loader
Loader->>Repo: load static/animated(directory, path)
Caller->>Repo: load static/animated(directory, path)
Repo->>File: file_exists(globalized joined path)
alt dependency or file unavailable
Repo-->>Loader: empty Dictionary
Repo-->>Caller: empty Dictionary
else available
Repo->>Native: instantiate and call exact native method
Native-->>Repo: Variant
Repo-->>Loader: Dictionary or empty Dictionary
Repo-->>Caller: Dictionary or empty Dictionary
end
```
@@ -118,13 +122,14 @@ sequenceDiagram
```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]
Loader --> AnimatedBuilder[M2NativeAnimatedBuilder]
Observer --> AnimatedBuilder[M2NativeAnimatedBuilder]
Repository -. no dependency .-> Finalizer
Repository -. no dependency .-> StaticBuilder
Repository -. no dependency .-> Cache[Renderer caches/queues]
@@ -133,7 +138,9 @@ flowchart TB
## 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 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
@@ -211,6 +218,8 @@ measured work packages rather than expansion of this repository.
| 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 |
+18 -17
View File
@@ -27,9 +27,8 @@ UV-rotation cases and returning the historical original-Mesh fallback.
```mermaid
flowchart LR
Extractor[M2MeshResourceExtractor] --> Loader[StreamingWorldLoader]
Loader -->|is raw data required?| Finalizer[M2RuntimeMeshFinalizer]
Loader --> Raw[M2RawModelRepository]
ResourceFinalizer[M2MeshResourceFinalizer] -->|is raw data required?| Finalizer[M2RuntimeMeshFinalizer]
ResourceFinalizer --> Raw[M2RawModelRepository]
Raw --> Finalizer
Finalizer --> Classifier[M2RuntimeMeshRebuildClassifier]
Finalizer --> Builder[M2Builder]
@@ -54,8 +53,8 @@ Nodes outside temporary rebuild roots and other application layers are forbidden
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|---|---|---|---|---|---|
| Input | Extracted Mesh and normalized M2 path | Loader/extractor adapter | Finalizer | Borrowed Mesh reference/String copy | One call |
| Input | Already-loaded raw M2 Dictionary | `M2RawModelRepository` through loader adapter | Classifier/M2Builder | Caller-owned value container | One call |
| Input | 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 |
@@ -101,24 +100,24 @@ map/reset clear site calls `clear()`.
```mermaid
sequenceDiagram
participant Loader as StreamingWorldLoader
participant ResourceFinalizer as M2MeshResourceFinalizer
participant Finalizer as M2RuntimeMeshFinalizer
participant Raw as M2Loader boundary
participant Classifier as RebuildClassifier
participant Builder as M2Builder
Loader->>Finalizer: requires_raw_data_for_refresh(mesh)
ResourceFinalizer->>Finalizer: requires_raw_data_for_refresh(mesh)
alt current Mesh
Finalizer-->>Loader: false; reuse Mesh
Finalizer-->>ResourceFinalizer: false; reuse Mesh
else stale Mesh
Finalizer-->>Loader: true
Loader->>Raw: load static raw Dictionary
Loader->>Finalizer: finalize_mesh(path, mesh, raw, extracted dir)
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-->>Loader: rebuilt or marked fallback Mesh
Finalizer-->>ResourceFinalizer: rebuilt or marked fallback Mesh
end
```
@@ -126,12 +125,12 @@ sequenceDiagram
```mermaid
flowchart TB
Loader[StreamingWorldLoader] --> Finalizer[M2RuntimeMeshFinalizer]
ResourceFinalizer[M2MeshResourceFinalizer] --> Finalizer[M2RuntimeMeshFinalizer]
Finalizer --> Classifier[M2RuntimeMeshRebuildClassifier]
Finalizer --> Extractor[M2MeshResourceExtractor]
Finalizer --> Builder[M2Builder]
Loader --> Raw[M2RawModelRepository]
Loader --> Cache[M2MeshResourceCacheState]
ResourceFinalizer --> Raw[M2RawModelRepository]
ResourceFinalizer --> Cache[M2MeshResourceCacheState]
Finalizer -. no dependency .-> Raw
Finalizer -. no dependency .-> Cache
```
@@ -139,7 +138,8 @@ flowchart TB
## Ownership, threading and resources
- Renderer main thread executes metadata changes and M2Builder work.
- `M2RawModelRepository` owns raw M2 file/native calls; loader supplies its value result.
- `M2RawModelRepository` owns raw M2 file/native calls; the Mesh resource
finalizer supplies its value result.
- Finalizer owns classifier memoization and temporary rebuild prototype lifetime.
- M2Builder owns construction rules; extractor selects the first rebuilt Mesh.
- Cache/prototype adapters decide where the returned Mesh reference is retained.
@@ -208,7 +208,8 @@ logs/queue metrics are unchanged. Normalized M2 path remains the correlation key
|---|---|
| `src/render/m2/m2_runtime_mesh_finalizer.gd` | Refresh version, classification, rebuild and fallback |
| `src/render/m2/m2_raw_model_repository.gd` | Raw-file/native I/O and Dictionary result |
| `src/scenes/streaming/streaming_world_loader.gd` | Repository call and returned-Mesh adoption |
| `src/render/m2/m2_mesh_resource_finalizer.gd` | Repository call and returned-Mesh adoption |
| `src/scenes/streaming/streaming_world_loader.gd` | Service composition, permits and materialization |
| `src/render/m2/m2_runtime_mesh_rebuild_classifier.gd` | Memoized billboard/UV-rotation predicate |
| `src/render/m2/m2_mesh_resource_extractor.gd` | First rebuilt-Mesh selection |
| `src/tools/verify_m2_runtime_mesh_finalizer.gd` | Transition/rebuild/boundary/timing regression |
@@ -0,0 +1,211 @@
# M2 Static Build Resource Observer
## Metadata
| Field | Value |
|---|---|
| Status | Implemented extraction |
| Target/work package | M03 / `M03-RND-M2-STATIC-BUILD-RESOURCE-OBSERVER-001` |
| Owners | Static build Mesh lookup, request selection and missing transition |
| Last verified | Worktree `work/sindo-main-codex/m03-m2-static-build-resource-observer`, 2026-07-18 |
| Profiles/capabilities | Existing static MultiMesh M2 build path |
## Purpose
Produce the static phase of `M2BuildResourceSnapshot`: reuse a prepared Mesh,
wait for an existing request, start the first eligible cache request, or record
a terminal missing model while preserving historical path and GLB rules.
## Non-goals
- Observe animated/native prototypes or finalize threaded loads.
- Own/free Meshes, Nodes, cache entries or snapshot lifetime.
- Materialize instances, plan batches, rotate queues or consume permits.
- Change cache format, candidate order, profiles or visible output.
## Context and boundaries
```mermaid
flowchart LR
Loader[StreamingWorldLoader] --> Observer[M2StaticBuildResourceObserver]
MeshCache[M2MeshResourceCacheState] --> Observer
Missing[M2PrototypeCacheState] --> Observer
Pipeline[M2MeshLoadPipelineState] --> Observer
Observer --> Snapshot[M2BuildResourceSnapshot]
Snapshot --> Dispatch[M2BuildDispatchPlanner]
```
## Public API
| Symbol | Kind | Purpose | Errors |
|---|---|---|---|
| `observe(snapshot, path, cache_dir, mesh_cache, prototype_cache, pipeline)` | Command/query | Adopt cached/pending/requested/missing static result | Invalid inputs return `rejected` |
| `cache_resource_paths(cache_dir, path, extensions)` | Pure query | Return unique historical nested/lowercase/basename candidates | Empty values yield fewer/empty candidates |
| `glb_animation_schema(path)` | Read query | Read OpenWC schema marker from GLB JSON | Invalid/missing GLB returns empty String |
| `OUTCOME_*` | Constants | Stable diagnostic transition identifiers | None |
## Inputs and outputs
| Direction | Data | Producer | Consumer | Ownership/lifetime |
|---|---|---|---|---|
| Input | Normalized M2 path/cache directory | Loader | Observer | Copied Strings; one observation |
| Input | Mesh/missing/request states | M2 state services | Observer | Borrowed services; map/session |
| Input/output | Resource snapshot | Loader | Observer/dispatch/materializer | Borrowed; one build operation |
| Output | Cached Mesh or missing/pending values | Observer | Snapshot | Exact borrowed Mesh/scalars |
| Output | Threaded request record | Observer | Mesh pipeline | Pipeline-owned path record |
| Output | Outcome StringName | Observer | Tests/future diagnostics | Immutable scalar |
## Data flow
```mermaid
flowchart TD
Start[Observe static path] --> Cached{Mesh cached?}
Cached -->|yes| AdoptMesh[Adopt exact Mesh; cached]
Cached -->|no| Missing{Already missing?}
Missing -->|yes| AdoptMissing[Adopt missing]
Missing -->|no| Pending{Request pending?}
Pending -->|yes| AdoptPending[Adopt unresolved; pending]
Pending -->|no| Candidates[Generate tscn then glb candidates]
Candidates --> Eligible{Exists and not pivot-prefix GLB?}
Eligible -->|yes| Request[Threaded request]
Request -->|OK or busy| Remember[Remember request; requested]
Eligible -->|none/error| MarkMissing[Mark/adopt missing]
```
## Lifecycle/state
```mermaid
stateDiagram-v2
[*] --> Observing
Observing --> Cached
Observing --> Pending
Observing --> Requested
Observing --> Missing
Observing --> Rejected
Cached --> [*]
Pending --> [*]
Requested --> [*]
Missing --> [*]
Rejected --> [*]
```
## Main sequence
```mermaid
sequenceDiagram
participant L as StreamingWorldLoader
participant O as StaticBuildResourceObserver
participant C as Mesh/prototype cache state
participant P as MeshLoadPipelineState
participant R as ResourceLoader
participant S as M2BuildResourceSnapshot
L->>O: observe(snapshot, path, cache_dir, states)
O->>C: cached/missing lookup
alt cache hit or missing
O->>S: adopt static observation
else pending
O->>P: has_request
O->>S: adopt unresolved observation
else request candidate
O->>R: exists + load_threaded_request
O->>P: remember_request
O->>S: adopt unresolved observation
end
```
## Dependency diagram
```mermaid
flowchart TB
Observer[M2StaticBuildResourceObserver] --> ResourceLoader
Observer --> FileAccess[GLB header/JSON read]
Observer --> MeshCache[M2MeshResourceCacheState]
Observer --> PrototypeCache[M2PrototypeCacheState]
Observer --> Pipeline[M2MeshLoadPipelineState]
Observer --> Snapshot[M2BuildResourceSnapshot]
Observer -. no dependency .-> SceneTree
Observer -. no dependency .-> Materializers
Observer -. no dependency .-> Scheduler
```
## Ownership, threading and resources
- Observer retains no request, cache, snapshot or engine reference.
- Cache/pipeline services retain their existing state and resource ownership.
- Snapshot borrows the exact cached Mesh; observer never frees it.
- Resource existence/request and GLB inspection run on renderer main thread.
- `M2MeshResourceFinalizer` performs terminal polling, Resource extraction,
Mesh preparation and cache/missing adoption from the loader permit loop.
## Errors, cancellation and recovery
| State | Behavior | Recovery |
|---|---|---|
| Invalid dependency/path | Return `rejected`; no mutation | Correct composition |
| Existing request | Return `pending`; no duplicate request | Retry when queue rotates |
| Pivot-prefix GLB | Skip as static candidate | Continue next candidate |
| Request error | Stop historical search and mark missing | Later reset/new session |
| No candidate | Mark/adopt terminal missing | Cache content becomes available in new session |
| Tile cancellation | Observer retains nothing | Loader/queue cancellation remains authoritative |
## Configuration and capabilities
The observer consumes existing `m2_cache_dir`. It adds no profile, cache version,
batch, visibility, shadow or scheduler setting.
## Persistence, cache and migration
No new persistence is introduced. Existing `.tscn/.glb` layout, pipeline records
and missing-cache lifetime are unchanged; no rebake is required.
## Diagnostics and observability
Returned outcomes distinguish rejected/cached/pending/requested/missing without
logging. Existing queue and hitch metrics remain unchanged.
## Verification
- Dedicated verifier covers invalid, cached identity, missing, pending, path
order/deduplication, absent GLB schema, source ownership and bounded timing.
- Snapshot/dispatch/pipeline/cache/shutdown/facade/internal-access/checkpoint
regressions protect adjacent behavior.
- No original-client visual claim is made; this is bookkeeping/I/O extraction.
## Extension points
Animated/native observation may become a separate producer of the animation
phase. Shared cache candidate/GLB inspection can later be reused without adding
a generic resource framework.
## Capability status
| Capability | Status | Evidence | Gap |
|---|---|---|---|
| Static build lookup/request production | Implemented extraction | Lifecycle/path/source/timing verifier | Asset-backed traversal pending |
| Static finalize/preparation | Implemented extraction | Mesh resource finalizer verifier | Asset-backed traversal pending |
| Animated observation | Existing loader-owned | Animation regressions | Separate producer pending |
## Known gaps and risks
- Successful request behavior is protected by source plus existing pipeline
tests; starting an undrained asynchronous request in the unit fixture would leak.
- GLB schema parser reads synchronously, matching the previous loader behavior.
- Private traversal, p95/p99, leak and visual evidence remain unavailable.
## Source map
| Path | Responsibility |
|---|---|
| `src/render/m2/m2_static_build_resource_observer.gd` | Static cache/request/missing observation |
| `src/render/m2/m2_build_resource_snapshot.gd` | Typed observation result |
| `src/render/m2/m2_mesh_resource_finalizer.gd` | Terminal polling, preparation and adoption |
| `src/scenes/streaming/streaming_world_loader.gd` | Composition, permits and action execution |
| `src/tools/verify_m2_static_build_resource_observer.gd` | Lifecycle/path/boundary/timing regression |
## Related decisions and references
- [`m2-build-resource-snapshot.md`](m2-build-resource-snapshot.md)
- [`m2-mesh-load-pipeline-state.md`](m2-mesh-load-pipeline-state.md)
- [`m2-mesh-resource-cache-state.md`](m2-mesh-resource-cache-state.md)
- [`world-renderer.md`](world-renderer.md)
- [`../../RENDER.md`](../../RENDER.md)
@@ -0,0 +1,183 @@
# Renderer Closeout Verification
## Metadata
| Field | Value |
|---|---|
| Status | Implemented |
| Target/work package | M03 / `M03-QAR-INTEGRATOR-CLOSEOUT-001` |
| Owner | Renderer structural, cache and performance acceptance gates |
| Last verified | Worktree `work/sindo-main-codex-m03-integrator/m03-closeout`, 2026-08-02 |
| Profile | `Blizzlike335`; quality preset `High` for checkpoint evidence |
## Purpose
Provide reproducible closeout checks for the M03 renderer decomposition. The
checks prove that worker boundaries remain CPU-only, main-thread frame steps are
budgeted, cache versions remain explicit, and paired M00/M03 reports stay within
the agreed 10% performance budgets.
## Non-goals
- Claim pixel-level parity with the original build-12340 client.
- Replace subsystem unit verifiers or long-traversal release tests.
- Generate, mutate or migrate production cache payloads.
- Hide incompatible environments or cache inventories by normalizing reports.
## Context and boundaries
```mermaid
flowchart LR
M00[M00 commit capture] --> Reports[Render checkpoint reports]
M03[M03 commit capture] --> Reports
Reports --> Comparator[compare_render_performance.ps1]
Sources[Renderer source and cache versions] --> Contracts[verify_renderer_closeout_contracts.gd]
Comparator --> Stability[repeatability gate across short and long windows]
Stability --> Evidence[JSON comparison and exit code]
Contracts --> Evidence
Evidence --> Target[M03 Evidence / DONE decision]
```
The capture command owns SceneTree execution and PNG/report writes. The
PowerShell comparator is read-only except for its requested JSON output. The
GDScript contract verifier reads source files and creates no renderer resources.
## Public API
| Symbol | Kind | Purpose | Preconditions | Failure |
|---|---|---|---|---|
| `compare_render_performance.ps1 -BaselineReport <paths> -CandidateReport <paths> [-OutputReport <path>]` | CLI | Compare one report or median of repeated reports | Same schema, profile, environment, cache contract/inventory and result keys | Exit 1 and enumerate incompatible fields or budget regressions |
| `verify_render_performance_stability.ps1 -RepeatedSampleComparison <path> -LongWindowComparison <path>` | CLI | Reject only a metric regression reproduced by both independent protocols | Both comparator reports contain the same 84 metrics | Exit 1 with every repeatable result/metric key |
| `verify_renderer_closeout_contracts.gd` | Godot CLI | Check worker/main-thread/cache/converter source contracts | Project parses and referenced sources exist | Exit 1 with named contract failure |
## Inputs and outputs
| Direction | Contract/data | Producer | Consumer | Ownership | Lifetime |
|---|---|---|---|---|---|
| Input | M00/M03 `report.json` paths | `capture_render_checkpoints.gd` | Comparator | Filesystem-owned immutable evidence | One comparison |
| Input | Renderer/tool GDScript sources | Repository | Contract verifier | Read-only | One verifier run |
| Output | 84 metric comparisons and failures | Comparator | Integrator/CI | Optional JSON plus process output | Evidence retention |
| Output | Repeatable and protocol-local regression inventories | Stability gate | Integrator/CI | Optional JSON plus process output | Evidence retention |
| Output | Structural pass/fail summary | Contract verifier | Integrator/CI | Process output | One run |
## Data flow
```mermaid
flowchart TD
Read[Read every supplied report] --> Compatible{Metadata and inventory match?}
Compatible -->|no| Fail[Exit 1 with exact mismatch]
Compatible -->|yes| Median[Median each metric per checkpoint/pass]
Median --> Pair[Pair 14 result keys]
Pair --> Budget[Compare load, p95, p99, hitch and memory]
Budget --> Json[Optional comparison JSON]
Budget --> Cohorts[Repeated-sample and long-window comparisons]
Cohorts --> Repeated{Same metric fails both?}
Repeated -->|no| Pass[Exit 0 with protocol-local diagnostics]
Repeated -->|yes| Fail
```
## Lifecycle and sequence
```mermaid
sequenceDiagram
participant I as Integrator
participant B as M00 worktree
participant C as M03 worktree
participant R as Checkpoint capture
participant G as Performance comparator
I->>B: capture repeated baseline samples
B->>R: same viewport, driver and cache inventory
I->>C: capture repeated candidate samples
C->>R: same viewport, driver and cache inventory
I->>G: baseline paths plus candidate paths
G->>G: compatibility checks and per-metric medians
G-->>I: short-window and long-window JSON evidence
I->>G: verify stability across both protocols
G-->>I: repeatable-regression pass/fail
```
There is no persistent state machine. Each invocation is read, validate,
aggregate, compare and terminate.
## Ownership, threading and resources
- Reports and source files are borrowed read-only for one process.
- Median aggregation deep-copies the first report and never rewrites inputs.
- Rendering remains owned by the GUI capture process on Godot's main thread.
- The contract verifier is headless and does not instantiate the streaming world.
- The optional comparison report is wholly owned by the caller-selected path.
## Errors, cancellation and recovery
| Failure | Behavior | Recovery |
|---|---|---|
| Missing/malformed report | Terminate with path/shape error | Regenerate that capture |
| Environment/cache mismatch | Fail before accepting metrics | Recapture both commits on the same machine and cache |
| Missing/duplicate checkpoint | Fail result-key validation | Repair manifest/capture completeness |
| Metric over budget | Record baseline, candidate, limit and percentage | Diagnose named checkpoint/lane; rerun only after a code or evidence correction |
| Interrupted GUI capture | No complete report is accepted | Remove/ignore partial output and rerun |
## Configuration and capabilities
The comparator reads thresholds from the baseline report. M03 uses 10% maximum
regression for load time, frame p95, frame p99, maximum hitch, static memory and
video memory. Repeated input paths are optional; when supplied, each side is
reduced independently to the median for every metric.
Closeout uses two independent protocols: repeated three-second captures and a
ten-second measurement window. A regression is accepted as real only when the
same checkpoint/pass/metric exceeds its unchanged 10% budget in both protocols.
Protocol-local failures remain in the JSON as noise diagnostics rather than
being discarded.
## Persistence, cache and migrations
The comparison JSON uses schema version 1 and contains source paths, sample
counts, revisions, all metric pairs and failures. It is evidence, not a runtime
cache. Renderer cache versions are read from the manifest/source contracts; this
module performs no migration or invalidation.
## Diagnostics and observability
- Success reports result pairs, comparison count and budget percentage.
- Failure output names every checkpoint/pass, metric, values and limit.
- The structural verifier reports worker count, frame-step count and cache-version count.
- Capture reports retain PNG hashes, queue snapshots, environment and cache inventory.
## Verification, fidelity and performance
- `verify_renderer_closeout_contracts.gd` covers four worker boundaries, fifteen
frame steps, seven cache versions and the nested M2 GLB output contract.
- `compare_render_performance.ps1` compares 14 cold/warm result pairs and 84 metrics.
- `verify_render_performance_stability.ps1` requires metric-key agreement and
rejects any budget regression reproduced by both sampling protocols.
- M00 and M03 must be captured from their exact commits against the same cache
inventory; old reports with a different inventory are rejected.
- PNG hashes and asset-backed coverage prove that terrain, ADT boundaries, dense
M2, large WMO, liquid, animated M2 and sky were rendered. They do not prove
original-client pixel parity without human/reference-image approval.
## Extension points
CI may retain reports and comparison JSON as artifacts. A future release gate may
add driver-version metadata, long-traversal samples or approved visual-diff
thresholds without changing runtime renderer contracts.
## Known gaps and risks
- Godot reports the rendering API but not the installed NVIDIA driver version.
- A 0.5-second historical M00 measurement window requires repeated median samples.
- Original-client screenshots are not part of the repository evidence set.
- Long-traversal descriptor pressure remains a later quality/release gate.
## Source map
| Path | Responsibility |
|---|---|
| `tools/compare_render_performance.ps1` | Compatibility, median aggregation and metric budgets |
| `tools/verify_render_performance_stability.ps1` | Cross-protocol repeatability acceptance |
| `src/tools/verify_renderer_closeout_contracts.gd` | Structural source/cache/converter contracts |
| `src/tools/capture_render_checkpoints.gd` | Asset-backed GUI capture and report generation |
| `src/tools/render_baseline_manifest.json` | Coverage, viewport, cache contract and budgets |
| `targets/00-render-baseline.md` | Accepted M00 measurement protocol |
| `targets/03-renderer-facade.md` | M03 acceptance and Evidence |
+1 -1
View File
@@ -227,7 +227,7 @@ Existing ADT/native/cache format versions remain unchanged.
| Renderer loaded-mesh diagnostic backend | Implemented | M03 facade typed sample and detached snapshot contract | Not composed into gameplay; triangle Mesh ray is diagnostic only |
| 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)
+130 -22
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-animation-playback`, 2026-07-18 |
| Last verified | Worktree `work/sindo-main-codex/m03-wmo-scene-instance-factory`, 2026-08-01 |
| Profiles/capabilities | `Performance`, `Balanced`, `High`, `Custom`; Blizzlike fidelity incomplete |
## Purpose
@@ -54,6 +54,10 @@ 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]
@@ -66,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]
@@ -136,25 +146,37 @@ from externally reading/writing loader-private queue, task, cache and tile-state
| `M2PlacementTransformResolver.resolve_basis/resolve_origin_offset` | Internal pure M2 service | Resolves regular and calibrated model-specific ADT placement transforms | Worker/main thread; stateless | Unknown paths use regular basis and zero offset |
| `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 |
@@ -185,17 +207,22 @@ loader configuration remains transitional composition data, not a caller API.
| Internal transform | Rotation/path/scale | Loader or grouper / `M2PlacementTransformResolver` | Group/placeholder/instance transforms | Value-only Basis/Vector3 | One placement |
| Internal 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 / `M2RawModelRepository` | Finalizer, static or animated builder adapters | Fresh Dictionary; repository retains nothing | One synchronous native call |
| Internal animated M2 load | Normalized path, cached GLB path and opaque terminal status | Loader / `M2AnimationLoadPipelineState` | Loader poll/finalize adapters | State-owned String/status records; detached pending snapshots | Request through terminal finalize/reset |
| Internal 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 |
@@ -249,7 +276,9 @@ flowchart TD
M2Grouper --> M2Batch[M2BuildBatchPlanner]
M2Grouper --> M2Queue[M2BuildQueue]
M2Queue --> M2Batch
M2Batch --> M2Static[M2StaticBatchMaterializer]
M2Batch --> M2Resources[M2BuildResourceSnapshot]
M2Resources --> M2Dispatch[M2BuildDispatchPlanner]
M2Dispatch --> M2Static[M2StaticBatchMaterializer]
M2Static --> M2
R --> WmoPlacement[WmoPlacementResolver]
WmoPlacement --> WmoRegistry[WmoPlacementRegistry]
@@ -377,21 +406,40 @@ sequenceDiagram
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.
`M2StaticBatchMaterializer` owns static MultiMesh construction/attachment;
the loader retains resource transitions, cursor-adoption decisions,
animated/static dispatch, root cleanup, budgets and Editor ownership.
`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,
@@ -402,18 +450,27 @@ sequenceDiagram
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 and adoption decisions; prototype
cache state owns shared missing outcomes.
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. 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 loader retains ResourceLoader I/O,
cache/missing adoption and materialization.
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. The loader loads raw data only
after the finalizer reports that a cached Mesh is stale.
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.
@@ -520,6 +577,15 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
transition, completion/raw integer behavior, source ownership and bounded timing.
- 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.
@@ -562,7 +628,14 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
| M2 unique placement registry | Implemented extraction | Scene-free ownership/lifecycle/timing contract and historical `uid:11785` smoke | Group/build/tasks/finalization and asset-backed p95/p99 remain pending |
| M2 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 |
@@ -572,9 +645,15 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
| WMO placement resolver | Implemented extraction | Scene-free path/identity/transform/source/timing contract | Asset-backed comparison pending |
| WMO placement 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 |
@@ -630,18 +709,31 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
| `src/render/liquid/adt_water_scene_finalizer.gd` | Main-thread ADT water build, tile attachment and optional editor ownership |
| `src/render/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 |
@@ -658,17 +750,30 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
| `src/tools/verify_adt_water_scene_finalizer.gd` | Synthetic ADT water scene/ownership/boundary/timing regression |
| `src/tools/verify_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 |
@@ -680,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
@@ -39,9 +39,12 @@ func start_instance_playback(
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"):
animator.prepare_runtime()
animator.set_phase(phase)
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:
@@ -0,0 +1,187 @@
class_name M2AnimationResourceFinalizer
extends RefCounted
## Polls cached animated M2 ResourceLoader requests and finalizes terminal scenes.
## The caller owns permits, material-source lookup and SceneTree materialization.
const M2_ANIMATED_SCENE_FINALIZER_SCRIPT := preload(
"res://src/render/m2/m2_animated_scene_finalizer.gd"
)
var _animated_scene_finalizer: RefCounted
var _resource_loader_adapter: Object
func _init(
animated_scene_finalizer: RefCounted = null,
resource_loader_adapter: Object = null
) -> void:
_animated_scene_finalizer = animated_scene_finalizer
if _animated_scene_finalizer == null:
_animated_scene_finalizer = M2_ANIMATED_SCENE_FINALIZER_SCRIPT.new()
_resource_loader_adapter = resource_loader_adapter
## Moves terminal threaded requests into the pipeline finalize FIFO.
## Empty resource paths retain the historical immediate static-only outcome.
func poll_terminal_requests(
load_pipeline_state: RefCounted,
prototype_cache_state: RefCounted
) -> int:
if load_pipeline_state == null or prototype_cache_state == null:
return 0
var completed_request_count := 0
var request_records: Array = load_pipeline_state.call(
"request_records_snapshot"
)
for request_variant in request_records:
if not (request_variant is Dictionary):
continue
var request: Dictionary = request_variant
var normalized_relative_path := String(request.get("normalized", ""))
var resource_path := String(request.get("path", ""))
if resource_path.is_empty():
load_pipeline_state.call(
"discard_request",
normalized_relative_path
)
prototype_cache_state.call(
"mark_animation_static",
normalized_relative_path
)
completed_request_count += 1
continue
var load_status := load_threaded_get_status(resource_path)
if (
load_status != ResourceLoader.THREAD_LOAD_LOADED
and load_status != ResourceLoader.THREAD_LOAD_FAILED
):
continue
load_pipeline_state.call(
"complete_request",
normalized_relative_path,
load_status
)
completed_request_count += 1
return completed_request_count
## Pops one terminal record and prepares a detached animated scene candidate.
## An empty result means the record was skipped or resolved as static-only.
func prepare_next_candidate(
load_pipeline_state: RefCounted,
prototype_cache_state: RefCounted
) -> Dictionary:
if load_pipeline_state == null or prototype_cache_state == null:
return {}
var terminal_record: Dictionary = load_pipeline_state.call(
"pop_finalize_record"
)
if terminal_record.is_empty():
return {}
var normalized_relative_path := String(
terminal_record.get("normalized", "")
)
if (
normalized_relative_path.is_empty()
or bool(prototype_cache_state.call(
"has_animated_prototype",
normalized_relative_path
))
or bool(prototype_cache_state.call(
"is_animation_static",
normalized_relative_path
))
):
return {}
if int(terminal_record.get(
"status",
ResourceLoader.THREAD_LOAD_FAILED
)) != ResourceLoader.THREAD_LOAD_LOADED:
prototype_cache_state.call(
"mark_animation_static",
normalized_relative_path
)
return {}
var resource_path := String(terminal_record.get("path", ""))
var loaded_resource := load_threaded_get(resource_path)
var candidate := _animated_scene_finalizer.call(
"instantiate_candidate",
loaded_resource
) as Node3D
if candidate == null:
prototype_cache_state.call(
"mark_animation_static",
normalized_relative_path
)
return {}
return {
"normalized": normalized_relative_path,
"path": resource_path,
"candidate": candidate,
}
## Repairs, validates and adopts one prepared candidate. The preparation must be
## completed synchronously so its detached candidate always receives an owner.
func finalize_prepared_candidate(
preparation: Dictionary,
material_source_root: Node3D,
prototype_cache_state: RefCounted,
debug_logging_enabled: bool = false
) -> Node3D:
if prototype_cache_state == null:
return null
var normalized_relative_path := String(preparation.get("normalized", ""))
var candidate := preparation.get("candidate", null) as Node3D
if normalized_relative_path.is_empty() or candidate == null:
return null
_animated_scene_finalizer.call(
"repair_materials",
candidate,
material_source_root
)
var finalization: Dictionary = _animated_scene_finalizer.call(
"finalize_candidate",
candidate
)
var prototype := finalization.get("prototype", null) as Node3D
if prototype == null:
prototype_cache_state.call(
"mark_animation_static",
normalized_relative_path
)
return null
var canonical_prototype := prototype_cache_state.call(
"adopt_animated_prototype",
normalized_relative_path,
prototype
) as Node3D
if debug_logging_enabled:
print("M2_ANIM_CACHE path=%s cache=%s players=%d" % [
normalized_relative_path,
String(preparation.get("path", "")),
int(finalization.get("animation_player_count", 0)),
])
return canonical_prototype
## Production ResourceLoader status adapter; injectable in synthetic tests.
func load_threaded_get_status(resource_path: String) -> int:
if _resource_loader_adapter != null:
return int(_resource_loader_adapter.call(
"load_threaded_get_status",
resource_path
))
return ResourceLoader.load_threaded_get_status(resource_path)
## Production ResourceLoader terminal-result adapter; injectable in tests.
func load_threaded_get(resource_path: String) -> Resource:
if _resource_loader_adapter != null:
return _resource_loader_adapter.call(
"load_threaded_get",
resource_path
) as Resource
return ResourceLoader.load_threaded_get(resource_path)
@@ -0,0 +1 @@
uid://2rnsjucg3dul
@@ -0,0 +1,47 @@
class_name M2BuildDispatchPlanner
extends RefCounted
## Selects the next M2 build action from already observed resource state.
## The planner retains no state or engine-object references.
const ACTION_WAIT_FOR_ANIMATION := &"wait_for_animation"
const ACTION_MATERIALIZE_ANIMATED := &"materialize_animated"
const ACTION_WAIT_FOR_STATIC_MESH := &"wait_for_static_mesh"
const ACTION_MATERIALIZE_STATIC := &"materialize_static"
const ACTION_ADVANCE_WITHOUT_MATERIALIZATION := &"advance_without_materialization"
## Returns a detached action/transition plan for one M2 build operation.
func plan_step(
batch_count: int,
resource_snapshot: RefCounted
) -> Dictionary:
if resource_snapshot == null:
return _wait_plan(ACTION_WAIT_FOR_STATIC_MESH)
if bool(resource_snapshot.call("animation_request_pending")):
return _wait_plan(ACTION_WAIT_FOR_ANIMATION)
if batch_count <= 0:
return _advance_plan(ACTION_ADVANCE_WITHOUT_MATERIALIZATION, false)
if bool(resource_snapshot.call("has_animated_prototype")):
return _advance_plan(ACTION_MATERIALIZE_ANIMATED, true)
if bool(resource_snapshot.call("has_static_mesh")):
return _advance_plan(ACTION_MATERIALIZE_STATIC, true)
if bool(resource_snapshot.call("static_model_missing")):
return _advance_plan(ACTION_ADVANCE_WITHOUT_MATERIALIZATION, true)
return _wait_plan(ACTION_WAIT_FOR_STATIC_MESH)
func _wait_plan(action: StringName) -> Dictionary:
return {
"action": action,
"rotate_queue": true,
"increment_batch_serial": false,
}
func _advance_plan(action: StringName, increment_batch_serial: bool) -> Dictionary:
return {
"action": action,
"rotate_queue": false,
"increment_batch_serial": increment_batch_serial,
}
@@ -0,0 +1 @@
uid://df0bv1x2x4j2x
@@ -0,0 +1,72 @@
class_name M2BuildResourceSnapshot
extends RefCounted
## Holds one M2 build-step resource observation without owning engine lifetime.
var _normalized_relative_path: String
var _animated_prototype: Node3D
var _animation_request_pending: bool
var _static_mesh: Mesh = null
var _static_model_missing: bool = false
func _init(
normalized_relative_path: String,
animated_prototype: Node3D,
animation_request_pending: bool
) -> void:
_normalized_relative_path = normalized_relative_path
_animated_prototype = animated_prototype
_animation_request_pending = animation_request_pending
## Returns the normalized M2 path observed by the loader.
func normalized_relative_path() -> String:
return _normalized_relative_path
## Returns the borrowed animated prototype without transferring ownership.
func animated_prototype() -> Node3D:
return _animated_prototype
## Returns whether an animated prototype was observed.
func has_animated_prototype() -> bool:
return _animated_prototype != null
## Returns whether the animation request remains pending.
func animation_request_pending() -> bool:
return _animation_request_pending
## Adopts the optional static Mesh and terminal missing-model observation.
func adopt_static_observation(static_mesh: Mesh, static_model_missing: bool) -> void:
_static_mesh = static_mesh
_static_model_missing = static_model_missing
## Returns the borrowed static Mesh without transferring ownership.
func static_mesh() -> Mesh:
return _static_mesh
## Returns whether a prepared static Mesh was observed.
func has_static_mesh() -> bool:
return _static_mesh != null
## Returns whether the static model lookup reached a terminal missing outcome.
func static_model_missing() -> bool:
return _static_model_missing
## Returns detached path/availability diagnostics without engine references.
func diagnostic_snapshot() -> Dictionary:
return {
"normalized_relative_path": _normalized_relative_path,
"has_animated_prototype": has_animated_prototype(),
"animation_request_pending": _animation_request_pending,
"has_static_mesh": has_static_mesh(),
"static_model_missing": _static_model_missing,
}
@@ -0,0 +1 @@
uid://cgleoy15rtmby
@@ -0,0 +1,238 @@
class_name M2CachedAnimationResourceObserver
extends RefCounted
## Observes or requests the cached-GLB animated phase of one M2 build step.
const M2_BUILD_RESOURCE_SNAPSHOT_SCRIPT := preload(
"res://src/render/m2/m2_build_resource_snapshot.gd"
)
## Returns a snapshot containing the exact cached prototype or pending state.
## Invalid composition and terminal failures produce a non-pending snapshot.
func observe(
normalized_relative_path: String,
cache_directory: String,
maximum_primitive_count: int,
allowlist_patterns: PackedStringArray,
denylist_patterns: PackedStringArray,
prototype_cache_state: RefCounted,
load_pipeline_state: RefCounted,
debug_logging_enabled: bool = false
) -> RefCounted:
var empty_snapshot: RefCounted = M2_BUILD_RESOURCE_SNAPSHOT_SCRIPT.new(
normalized_relative_path,
null,
false
)
if (
normalized_relative_path.is_empty()
or prototype_cache_state == null
or load_pipeline_state == null
):
return empty_snapshot
var cached_prototype := prototype_cache_state.call(
"find_animated_prototype",
normalized_relative_path
) as Node3D
if cached_prototype != null:
return M2_BUILD_RESOURCE_SNAPSHOT_SCRIPT.new(
normalized_relative_path,
cached_prototype,
false
)
if bool(prototype_cache_state.call(
"is_animation_static",
normalized_relative_path
)):
return empty_snapshot
if bool(load_pipeline_state.call("has_request", normalized_relative_path)):
return M2_BUILD_RESOURCE_SNAPSHOT_SCRIPT.new(
normalized_relative_path,
null,
true
)
var cache_resource_path := find_eligible_glb_cache_path(
normalized_relative_path,
cache_directory,
maximum_primitive_count,
allowlist_patterns,
denylist_patterns,
debug_logging_enabled
)
if cache_resource_path.is_empty():
prototype_cache_state.call(
"mark_animation_static",
normalized_relative_path
)
return empty_snapshot
var request_error := ResourceLoader.load_threaded_request(
cache_resource_path,
"",
false,
ResourceLoader.CACHE_MODE_REUSE
)
if request_error == OK or request_error == ERR_BUSY:
load_pipeline_state.call(
"remember_request",
normalized_relative_path,
cache_resource_path
)
return M2_BUILD_RESOURCE_SNAPSHOT_SCRIPT.new(
normalized_relative_path,
null,
true
)
prototype_cache_state.call("mark_animation_static", normalized_relative_path)
return empty_snapshot
## Returns the first eligible historical `.glb` cache candidate.
func find_eligible_glb_cache_path(
normalized_relative_path: String,
cache_directory: String,
maximum_primitive_count: int,
allowlist_patterns: PackedStringArray,
denylist_patterns: PackedStringArray,
debug_logging_enabled: bool = false
) -> String:
if not is_animation_path_allowed(
normalized_relative_path,
allowlist_patterns,
denylist_patterns
):
return ""
for cache_resource_path in cache_resource_paths(
cache_directory,
normalized_relative_path
):
if not ResourceLoader.exists(cache_resource_path):
continue
if glb_cache_is_safe_for_runtime_animation(
cache_resource_path,
maximum_primitive_count,
debug_logging_enabled
):
return cache_resource_path
return ""
## Applies the historical stripped, case-insensitive allow/deny substring policy.
func is_animation_path_allowed(
normalized_relative_path: String,
allowlist_patterns: PackedStringArray,
denylist_patterns: PackedStringArray
) -> bool:
if normalized_relative_path.is_empty() or allowlist_patterns.is_empty():
return false
var lowercase_path := normalized_relative_path.to_lower()
var allowed := false
for pattern in allowlist_patterns:
var needle := String(pattern).strip_edges().to_lower()
if not needle.is_empty() and lowercase_path.contains(needle):
allowed = true
break
if not allowed:
return false
for pattern in denylist_patterns:
var needle := String(pattern).strip_edges().to_lower()
if not needle.is_empty() and lowercase_path.contains(needle):
return false
return true
## Returns historical nested/lowercase/basename `.glb` candidates without I/O.
func cache_resource_paths(
cache_directory: String,
relative_path: String
) -> PackedStringArray:
var normalized := relative_path.replace("\\", "/")
var lowercase := normalized.to_lower()
var stems := [
normalized.get_basename(),
lowercase.get_basename(),
normalized.get_file().get_basename(),
lowercase.get_file().get_basename(),
]
var result := PackedStringArray()
for stem in stems:
if stem.is_empty():
continue
var cache_resource_path := cache_directory.path_join(stem + ".glb")
if not result.has(cache_resource_path):
result.append(cache_resource_path)
return result
## Checks animation presence, primitive limit and accepted OpenWC schema.
func glb_cache_is_safe_for_runtime_animation(
cache_resource_path: String,
maximum_primitive_count: int,
debug_logging_enabled: bool = false
) -> bool:
var gltf := read_glb_json(cache_resource_path)
if gltf.is_empty():
return false
var animations: Array = gltf.get("animations", [])
if animations.is_empty():
return false
var primitive_count := glb_primitive_count(gltf)
if maximum_primitive_count > 0 and primitive_count > maximum_primitive_count:
return false
var schema := glb_animation_schema(gltf)
if schema == "pivot_prefix_v1" or schema.is_empty():
return true
if debug_logging_enabled:
print(
"M2_ANIM_REJECT schema=%s primitives=%d cache=%s"
% [schema, primitive_count, cache_resource_path]
)
return false
## Reads the JSON chunk of a version-2 GLB or returns an empty Dictionary.
func read_glb_json(cache_resource_path: String) -> Dictionary:
var absolute_path := ProjectSettings.globalize_path(cache_resource_path)
if not FileAccess.file_exists(absolute_path):
return {}
var file := FileAccess.open(absolute_path, FileAccess.READ)
if file == null or file.get_length() < 20:
return {}
var magic := file.get_32()
var version := file.get_32()
file.get_32()
if magic != 0x46546c67 or version != 2:
return {}
var json_length := int(file.get_32())
var chunk_type := file.get_32()
if json_length <= 0 or chunk_type != 0x4e4f534a:
return {}
var parsed: Variant = JSON.parse_string(
file.get_buffer(json_length).get_string_from_utf8()
)
return parsed as Dictionary if parsed is Dictionary else {}
## Counts all glTF Mesh primitives using the historical safety rule.
func glb_primitive_count(gltf: Dictionary) -> int:
var count := 0
var meshes: Array = gltf.get("meshes", [])
for mesh_variant in meshes:
if mesh_variant is Dictionary:
var primitives: Array = (mesh_variant as Dictionary).get(
"primitives",
[]
)
count += primitives.size()
return count
## Returns the OpenWC animation schema marker from parsed glTF metadata.
func glb_animation_schema(gltf: Dictionary) -> String:
var asset: Dictionary = gltf.get("asset", {})
var extras: Dictionary = asset.get("extras", {})
return String(extras.get("openwc_m2_anim_schema", ""))
@@ -0,0 +1 @@
uid://d0ptej41k2smt
+182
View File
@@ -0,0 +1,182 @@
class_name M2MeshResourceFinalizer
extends RefCounted
## Polls static M2 ResourceLoader requests and publishes prepared Mesh outcomes.
## The caller owns permits, pipeline/cache lifetime and MultiMesh materialization.
var _mesh_resource_extractor: RefCounted
var _runtime_mesh_finalizer: RefCounted
var _raw_model_repository: RefCounted
var _resource_loader_adapter: Object
func _init(
mesh_resource_extractor: RefCounted,
runtime_mesh_finalizer: RefCounted,
raw_model_repository: RefCounted,
resource_loader_adapter: Object = null
) -> void:
_mesh_resource_extractor = mesh_resource_extractor
_runtime_mesh_finalizer = runtime_mesh_finalizer
_raw_model_repository = raw_model_repository
_resource_loader_adapter = resource_loader_adapter
## Moves terminal threaded requests into the pipeline finalize FIFO.
## Empty resource paths retain the historical immediate missing-model outcome.
func poll_terminal_requests(
load_pipeline_state: RefCounted,
prototype_cache_state: RefCounted
) -> int:
if load_pipeline_state == null or prototype_cache_state == null:
return 0
var completed_request_count := 0
var request_records: Array = load_pipeline_state.call(
"request_records_snapshot"
)
for request_variant in request_records:
if not (request_variant is Dictionary):
continue
var request: Dictionary = request_variant
var normalized_relative_path := String(request.get("normalized", ""))
var resource_path := String(request.get("path", ""))
if resource_path.is_empty():
load_pipeline_state.call(
"discard_request",
normalized_relative_path
)
prototype_cache_state.call(
"mark_model_missing",
normalized_relative_path
)
completed_request_count += 1
continue
var load_status := load_threaded_get_status(resource_path)
if (
load_status != ResourceLoader.THREAD_LOAD_LOADED
and load_status != ResourceLoader.THREAD_LOAD_FAILED
):
continue
load_pipeline_state.call(
"complete_request",
normalized_relative_path,
load_status
)
completed_request_count += 1
return completed_request_count
## Pops and finalizes one terminal record. A true result means a record was
## consumed, including cached, failed and invalid outcomes.
func finalize_next_resource(
load_pipeline_state: RefCounted,
mesh_resource_cache_state: RefCounted,
prototype_cache_state: RefCounted,
extracted_directory: String
) -> bool:
if (
load_pipeline_state == null
or mesh_resource_cache_state == null
or prototype_cache_state == null
):
return false
var terminal_record: Dictionary = load_pipeline_state.call(
"pop_finalize_record"
)
if terminal_record.is_empty():
return false
var normalized_relative_path := String(
terminal_record.get("normalized", "")
)
if (
normalized_relative_path.is_empty()
or bool(mesh_resource_cache_state.call(
"has_mesh",
normalized_relative_path
))
):
return true
if int(terminal_record.get(
"status",
ResourceLoader.THREAD_LOAD_FAILED
)) != ResourceLoader.THREAD_LOAD_LOADED:
prototype_cache_state.call(
"mark_model_missing",
normalized_relative_path
)
return true
var resource_path := String(terminal_record.get("path", ""))
var loaded_resource := load_threaded_get(resource_path)
var mesh := _mesh_resource_extractor.call(
"extract_first_mesh",
loaded_resource
) as Mesh
if mesh == null:
prototype_cache_state.call(
"mark_model_missing",
normalized_relative_path
)
return true
mesh_resource_cache_state.call(
"store_mesh",
normalized_relative_path,
prepare_mesh_for_runtime(
normalized_relative_path,
mesh,
extracted_directory
)
)
return true
## Applies the existing refresh-version/raw-data/rebuild path to one Mesh.
func prepare_mesh_for_runtime(
normalized_relative_path: String,
mesh: Mesh,
extracted_directory: String
) -> Mesh:
if mesh == null:
return null
if (
_runtime_mesh_finalizer == null
or not bool(_runtime_mesh_finalizer.call(
"requires_raw_data_for_refresh",
mesh
))
):
return mesh
var raw_model_data: Dictionary = {}
if _raw_model_repository != null:
raw_model_data = _raw_model_repository.call(
"load_static_model_data",
extracted_directory,
normalized_relative_path
)
return _runtime_mesh_finalizer.call(
"finalize_mesh",
normalized_relative_path,
mesh,
raw_model_data,
extracted_directory
) as Mesh
## Production ResourceLoader status adapter; injectable in synthetic tests.
func load_threaded_get_status(resource_path: String) -> int:
if _resource_loader_adapter != null:
return int(_resource_loader_adapter.call(
"load_threaded_get_status",
resource_path
))
return ResourceLoader.load_threaded_get_status(resource_path)
## Production ResourceLoader terminal-result adapter; injectable in tests.
func load_threaded_get(resource_path: String) -> Resource:
if _resource_loader_adapter != null:
return _resource_loader_adapter.call(
"load_threaded_get",
resource_path
) as Resource
return ResourceLoader.load_threaded_get(resource_path)
@@ -0,0 +1 @@
uid://de4xl1fywvqfw
@@ -0,0 +1,113 @@
class_name M2NativeAnimationResourceObserver
extends RefCounted
## Resolves the existing native GryphonRoost animated M2 prototype path.
const M2_NATIVE_ANIMATED_BUILDER_SCRIPT := preload(
"res://addons/mpq_extractor/loaders/m2_native_animated_builder.gd"
)
var _native_animated_builder: Object
func _init(native_animated_builder: Object = M2_NATIVE_ANIMATED_BUILDER_SCRIPT) -> void:
_native_animated_builder = native_animated_builder
## Returns whether the normalized path uses the historical native animation path.
func is_native_animation_candidate(normalized_relative_path: String) -> bool:
return normalized_relative_path.to_lower().contains("gryphonroost")
## Returns the exact cached or newly adopted native animated prototype.
## Rejected native candidates are marked static-only for the renderer session.
func observe(
normalized_relative_path: String,
extracted_directory: String,
raw_model_repository: RefCounted,
prototype_cache_state: RefCounted,
debug_logging_enabled: bool = false
) -> Node3D:
if (
normalized_relative_path.is_empty()
or not is_native_animation_candidate(normalized_relative_path)
or raw_model_repository == null
or prototype_cache_state == null
):
return null
var cached_prototype := prototype_cache_state.call(
"find_animated_prototype",
normalized_relative_path
) as Node3D
if cached_prototype != null:
return cached_prototype
if bool(prototype_cache_state.call(
"is_animation_static",
normalized_relative_path
)):
return null
var animated_model_data: Dictionary = raw_model_repository.call(
"load_animated_model_data",
extracted_directory,
normalized_relative_path
)
var animated_surfaces: Array = animated_model_data.get(
"animated_surfaces",
[]
)
if animated_model_data.is_empty() or animated_surfaces.is_empty():
prototype_cache_state.call(
"mark_animation_static",
normalized_relative_path
)
return null
var prototype := build_animated_prototype(
animated_model_data,
extracted_directory
)
if prototype == null or prototype.get_child_count() <= 0:
prototype_cache_state.call(
"mark_animation_static",
normalized_relative_path
)
return null
prototype = prototype_cache_state.call(
"adopt_animated_prototype",
normalized_relative_path,
prototype
) as Node3D
if debug_logging_enabled:
print(
(
"M2_NATIVE_ANIM_CACHE path=%s surfaces=%d bones=%d "
+ "anim_id=%d seq=%d score=%d length=%.2f"
)
% [
normalized_relative_path,
animated_surfaces.size(),
(animated_model_data.get("bones", []) as Array).size(),
int(animated_model_data.get("animation_id", -1)),
int(animated_model_data.get("animation_sequence_index", -1)),
int(animated_model_data.get("animation_activity_score", 0)),
float(animated_model_data.get("animation_length", 0.0)),
]
)
return prototype
## Builds one prototype through the exact native animated builder dependency.
func build_animated_prototype(
animated_model_data: Dictionary,
extracted_directory: String
) -> Node3D:
if _native_animated_builder == null:
return null
return _native_animated_builder.call(
"build",
animated_model_data,
extracted_directory
) as Node3D
@@ -0,0 +1 @@
uid://bc3kgi23usncx
@@ -0,0 +1,126 @@
class_name M2StaticBuildResourceObserver
extends RefCounted
## Resolves or requests one static M2 build Mesh and fills a resource snapshot.
const OUTCOME_REJECTED := &"rejected"
const OUTCOME_CACHED := &"cached"
const OUTCOME_PENDING := &"pending"
const OUTCOME_REQUESTED := &"requested"
const OUTCOME_MISSING := &"missing"
## Observes cache/request state and adopts the exact static result into snapshot.
func observe(
resource_snapshot: RefCounted,
normalized_relative_path: String,
cache_directory: String,
mesh_cache_state: RefCounted,
prototype_cache_state: RefCounted,
load_pipeline_state: RefCounted
) -> StringName:
if (
resource_snapshot == null
or normalized_relative_path.is_empty()
or mesh_cache_state == null
or prototype_cache_state == null
or load_pipeline_state == null
):
return OUTCOME_REJECTED
if bool(mesh_cache_state.call("has_mesh", normalized_relative_path)):
resource_snapshot.call(
"adopt_static_observation",
mesh_cache_state.call("find_mesh", normalized_relative_path) as Mesh,
false
)
return OUTCOME_CACHED
if bool(prototype_cache_state.call("is_model_missing", normalized_relative_path)):
resource_snapshot.call("adopt_static_observation", null, true)
return OUTCOME_MISSING
if bool(load_pipeline_state.call("has_request", normalized_relative_path)):
resource_snapshot.call("adopt_static_observation", null, false)
return OUTCOME_PENDING
for cache_resource_path in cache_resource_paths(
cache_directory,
normalized_relative_path,
[".tscn", ".glb"]
):
if not ResourceLoader.exists(cache_resource_path):
continue
if (
cache_resource_path.get_extension().to_lower() == "glb"
and glb_animation_schema(cache_resource_path) == "pivot_prefix_v1"
):
continue
var request_error := ResourceLoader.load_threaded_request(
cache_resource_path,
"",
false,
ResourceLoader.CACHE_MODE_REUSE
)
if request_error == OK or request_error == ERR_BUSY:
load_pipeline_state.call(
"remember_request",
normalized_relative_path,
cache_resource_path
)
resource_snapshot.call("adopt_static_observation", null, false)
return OUTCOME_REQUESTED
break
prototype_cache_state.call("mark_model_missing", normalized_relative_path)
resource_snapshot.call("adopt_static_observation", null, true)
return OUTCOME_MISSING
## Returns historical nested/lowercase/basename cache candidates without I/O.
func cache_resource_paths(
cache_directory: String,
relative_path: String,
extensions: Array[String]
) -> PackedStringArray:
var normalized := relative_path.replace("\\", "/")
var lower := normalized.to_lower()
var stems := [
normalized.get_basename(),
lower.get_basename(),
normalized.get_file().get_basename(),
lower.get_file().get_basename(),
]
var result := PackedStringArray()
for extension in extensions:
for stem in stems:
if stem.is_empty():
continue
var path := cache_directory.path_join(stem + extension)
if not result.has(path):
result.append(path)
return result
## Reads only the OpenWC animation schema marker from a GLB JSON chunk.
func glb_animation_schema(cache_resource_path: String) -> String:
var absolute_path := ProjectSettings.globalize_path(cache_resource_path)
if not FileAccess.file_exists(absolute_path):
return ""
var file := FileAccess.open(absolute_path, FileAccess.READ)
if file == null or file.get_length() < 20:
return ""
var magic := file.get_32()
var version := file.get_32()
file.get_32()
if magic != 0x46546c67 or version != 2:
return ""
var json_length := int(file.get_32())
var chunk_type := file.get_32()
if json_length <= 0 or chunk_type != 0x4e4f534a:
return ""
var parsed: Variant = JSON.parse_string(
file.get_buffer(json_length).get_string_from_utf8()
)
if not (parsed is Dictionary):
return ""
var asset: Dictionary = (parsed as Dictionary).get("asset", {})
var extras: Dictionary = asset.get("extras", {})
return String(extras.get("openwc_m2_anim_schema", ""))
@@ -0,0 +1 @@
uid://c0e3p80ld1dfb
@@ -0,0 +1,101 @@
class_name WmoRenderGroupMaterializer
extends RefCounted
## Creates and attaches one lightweight cached WMO render group. Resource
## finalization, build-step selection and queue progress remain caller-owned.
## All methods mutate SceneTree nodes and must run on the renderer main thread.
## Creates one MeshInstance3D for [param group_index], applies the indexed name
## and optional transform contracts, then attaches it to [param wmo_parent_root].
## The parent owns the returned node; the exact Mesh identity is retained.
## Null/invalid parents, null meshes and negative indices return null.
func materialize_mesh_group(
wmo_parent_root: Node3D,
mesh: Mesh,
group_names: PackedStringArray,
group_transforms: Array,
group_index: int,
visibility_range_end: float,
visibility_range_end_margin: float,
cast_shadows: bool) -> MeshInstance3D:
if (
wmo_parent_root == null
or not is_instance_valid(wmo_parent_root)
or mesh == null
or group_index < 0
):
return null
var mesh_instance := MeshInstance3D.new()
mesh_instance.name = (
group_names[group_index]
if group_index < group_names.size()
else "Group_%d" % group_index
)
mesh_instance.mesh = mesh
if group_index < group_transforms.size():
mesh_instance.transform = group_transforms[group_index]
_apply_render_settings(
mesh_instance,
visibility_range_end,
visibility_range_end_margin,
cast_shadows
)
wmo_parent_root.add_child(mesh_instance)
return mesh_instance
## Creates one MultiMeshInstance3D for [param group_index], applies the indexed
## name and optional transform contracts, then attaches it to the supplied root.
## The parent owns the returned node; the exact MultiMesh identity is retained.
## Null/invalid parents, null MultiMeshes and negative indices return null.
func materialize_multimesh_group(
wmo_parent_root: Node3D,
multimesh: MultiMesh,
group_names: PackedStringArray,
group_transforms: Array,
group_index: int,
visibility_range_end: float,
visibility_range_end_margin: float,
cast_shadows: bool) -> MultiMeshInstance3D:
if (
wmo_parent_root == null
or not is_instance_valid(wmo_parent_root)
or multimesh == null
or group_index < 0
):
return null
var multimesh_instance := MultiMeshInstance3D.new()
multimesh_instance.name = (
group_names[group_index]
if group_index < group_names.size()
else "DoodadGroup_%d" % group_index
)
multimesh_instance.multimesh = multimesh
if group_index < group_transforms.size():
multimesh_instance.transform = group_transforms[group_index]
_apply_render_settings(
multimesh_instance,
visibility_range_end,
visibility_range_end_margin,
cast_shadows
)
wmo_parent_root.add_child(multimesh_instance)
return multimesh_instance
func _apply_render_settings(
geometry_instance: GeometryInstance3D,
visibility_range_end: float,
visibility_range_end_margin: float,
cast_shadows: bool) -> void:
geometry_instance.cast_shadow = (
GeometryInstance3D.SHADOW_CASTING_SETTING_ON
if cast_shadows
else GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
)
if visibility_range_end > 0.0:
geometry_instance.visibility_range_end = visibility_range_end
geometry_instance.visibility_range_end_margin = visibility_range_end_margin
@@ -0,0 +1 @@
uid://cu4tw6868rbkm
@@ -0,0 +1,90 @@
class_name WmoRenderResourceFinalizer
extends RefCounted
## Polls lightweight WMO render-cache requests and publishes terminal outcomes.
## Request admission, cache lifetime and WMO materialization remain caller-owned.
var _expected_resource_script: Script
var _minimum_format_version: int
var _resource_loader_adapter: Object
func _init(
expected_resource_script: Script,
minimum_format_version: int,
resource_loader_adapter: Object = null
) -> void:
_expected_resource_script = expected_resource_script
_minimum_format_version = minimum_format_version
_resource_loader_adapter = resource_loader_adapter
## Polls a detached pending snapshot in insertion order. Only terminal requests
## are removed and published as an exact Resource reference or missing outcome.
func poll_terminal_requests(render_resource_cache_state: RefCounted) -> int:
if render_resource_cache_state == null:
return 0
var completed_request_count := 0
var request_paths: Dictionary = render_resource_cache_state.call(
"request_paths_snapshot"
)
for normalized_relative_path_variant in request_paths.keys():
var normalized_relative_path := String(normalized_relative_path_variant)
var resource_path := String(request_paths[normalized_relative_path_variant])
var load_status := load_threaded_get_status(resource_path)
if (
load_status != ResourceLoader.THREAD_LOAD_LOADED
and load_status != ResourceLoader.THREAD_LOAD_FAILED
):
continue
if load_status != ResourceLoader.THREAD_LOAD_LOADED:
render_resource_cache_state.call(
"complete_request_as_missing",
normalized_relative_path
)
completed_request_count += 1
continue
var loaded_resource := load_threaded_get(resource_path)
if is_current_render_resource(loaded_resource):
render_resource_cache_state.call(
"complete_request_with_resource",
normalized_relative_path,
loaded_resource
)
else:
render_resource_cache_state.call(
"complete_request_as_missing",
normalized_relative_path
)
completed_request_count += 1
return completed_request_count
## Accepts only the exact configured script and its current-or-newer format.
func is_current_render_resource(resource: Resource) -> bool:
return (
resource != null
and _expected_resource_script != null
and resource.get_script() == _expected_resource_script
and int(resource.get("format_version")) >= _minimum_format_version
)
## Production ResourceLoader status boundary; injectable for synthetic tests.
func load_threaded_get_status(resource_path: String) -> int:
if _resource_loader_adapter != null:
return int(_resource_loader_adapter.call(
"load_threaded_get_status",
resource_path
))
return ResourceLoader.load_threaded_get_status(resource_path)
## Production ResourceLoader result boundary; injectable for synthetic tests.
func load_threaded_get(resource_path: String) -> Resource:
if _resource_loader_adapter != null:
return _resource_loader_adapter.call(
"load_threaded_get",
resource_path
) as Resource
return ResourceLoader.load_threaded_get(resource_path)
@@ -0,0 +1 @@
uid://dtvba6g1grttk
@@ -0,0 +1,105 @@
class_name WmoRuntimeMeshFinalizer
extends RefCounted
## Refreshes cached WMO runtime Mesh materials through the configured builder.
## Scene traversal, placement and Node lifetime remain caller-owned.
const MATERIAL_REFRESH_VERSION := 10
const MATERIAL_REFRESH_VERSION_METADATA := "wow_wmo_material_refresh_version"
var _wmo_material_builder: Object
func _init(wmo_material_builder: Object) -> void:
_wmo_material_builder = wmo_material_builder
## Finalizes a stale Mesh in place and returns the exact input Resource identity.
## Null remains null; already-current meshes do not cross the builder boundary.
func finalize_mesh(mesh: Mesh, extracted_directory: String) -> Mesh:
if mesh == null:
return null
if int(mesh.get_meta(MATERIAL_REFRESH_VERSION_METADATA, 0)) >= MATERIAL_REFRESH_VERSION:
return mesh
mesh.set_meta(MATERIAL_REFRESH_VERSION_METADATA, MATERIAL_REFRESH_VERSION)
if not (mesh is ArrayMesh):
return mesh
var array_mesh := mesh as ArrayMesh
for surface_index in range(array_mesh.get_surface_count()):
var rebuilt_material := rebuild_cached_material(
array_mesh.surface_get_material(surface_index),
extracted_directory
)
if rebuilt_material != null:
array_mesh.surface_set_material(surface_index, rebuilt_material)
return mesh
## Rebuilds a material carrying the WMO cache metadata contract. Materials
## without texture0 metadata and failed builder results remain unchanged by the caller.
func rebuild_cached_material(
material: Material,
extracted_directory: String
) -> Material:
if (
material == null
or not material.has_meta("texture0_path")
or _wmo_material_builder == null
):
return null
var texture_paths := PackedStringArray()
var texture0_index := _append_texture_path(
texture_paths,
String(material.get_meta("texture0_path", ""))
)
var texture1_index := _append_texture_path(
texture_paths,
String(material.get_meta("texture1_path", ""))
)
var texture2_index := _append_texture_path(
texture_paths,
String(material.get_meta("texture2_path", ""))
)
var diffuse_color := Color.WHITE
var emissive_color := Color.BLACK
var secondary_color := Color.WHITE
if material is ShaderMaterial:
var shader_material := material as ShaderMaterial
var diffuse_value: Variant = shader_material.get_shader_parameter("diffuse_color")
var emissive_value: Variant = shader_material.get_shader_parameter("emissive_color")
var secondary_value: Variant = shader_material.get_shader_parameter("secondary_color")
if diffuse_value is Color:
diffuse_color = diffuse_value
if emissive_value is Color:
emissive_color = emissive_value
if secondary_value is Color:
secondary_color = secondary_value
var material_definition := {
"texture0": texture0_index,
"texture1": texture1_index,
"texture2": texture2_index,
"flags": int(material.get_meta("wow_flags", 0)),
"shader": int(material.get_meta("wow_shader", 0)),
"blend_mode": int(material.get_meta("wow_blend_mode", 0)),
"diffuse_color": diffuse_color,
"emissive_color": emissive_color,
"color2": secondary_color,
}
return _wmo_material_builder.call(
"_build_material",
material_definition,
texture_paths,
extracted_directory
) as Material
func _append_texture_path(texture_paths: PackedStringArray, texture_path: String) -> int:
if texture_path.is_empty():
return -1
var texture_index := texture_paths.size()
texture_paths.append(texture_path)
return texture_index
@@ -0,0 +1 @@
uid://bsxqkjuk77cgf
@@ -0,0 +1,80 @@
class_name WmoRuntimeScenePreparer
extends RefCounted
## Applies the existing cached/live WMO subtree preparation rules on the
## renderer main thread. Placement, attachment and subtree lifetime stay caller-owned.
var _runtime_mesh_finalizer: Object
func _init(runtime_mesh_finalizer: Object) -> void:
_runtime_mesh_finalizer = runtime_mesh_finalizer
## Prepares an instantiated cached WMO scene by finalizing every borrowed Mesh,
## then applying the historical direct Occluders-child and shadow policies.
## Returns false for a null/freed root without mutating or retaining anything.
func prepare_cached_instance(
instance: Node3D,
extracted_directory: String,
enable_occlusion_culling: bool,
cast_shadows: bool) -> bool:
if instance == null or not is_instance_valid(instance):
return false
_finalize_meshes_in_subtree(instance, extracted_directory)
_apply_runtime_render_policy(instance, enable_occlusion_culling, cast_shadows)
return true
## Prepares a duplicated live-built WMO without re-finalizing its Meshes.
## Returns false for a null/freed root without mutating or retaining anything.
func prepare_live_instance(
instance: Node3D,
enable_occlusion_culling: bool,
cast_shadows: bool) -> bool:
if instance == null or not is_instance_valid(instance):
return false
_apply_runtime_render_policy(instance, enable_occlusion_culling, cast_shadows)
return true
func _finalize_meshes_in_subtree(node: Node, extracted_directory: String) -> void:
if _runtime_mesh_finalizer != null:
if node is MeshInstance3D:
_runtime_mesh_finalizer.call(
"finalize_mesh",
(node as MeshInstance3D).mesh,
extracted_directory
)
elif node is MultiMeshInstance3D:
var multimesh := (node as MultiMeshInstance3D).multimesh
if multimesh != null:
_runtime_mesh_finalizer.call(
"finalize_mesh",
multimesh.mesh,
extracted_directory
)
for child in node.get_children():
_finalize_meshes_in_subtree(child, extracted_directory)
func _apply_runtime_render_policy(
instance: Node3D,
enable_occlusion_culling: bool,
cast_shadows: bool) -> void:
if not enable_occlusion_culling:
var occluders := instance.get_node_or_null("Occluders")
if occluders != null:
instance.remove_child(occluders)
occluders.queue_free()
if cast_shadows:
_enable_shadow_casting_recursive(instance)
func _enable_shadow_casting_recursive(node: Node) -> void:
if node is GeometryInstance3D:
(node as GeometryInstance3D).cast_shadow = (
GeometryInstance3D.SHADOW_CASTING_SETTING_ON
)
for child in node.get_children():
_enable_shadow_casting_recursive(child)
@@ -0,0 +1 @@
uid://d1t0vlkco8kw8
@@ -0,0 +1,73 @@
class_name WmoSceneInstanceFactory
extends RefCounted
## Creates detached cached/live WMO Node3D instances with the existing cache
## validation, naming and world-placement rules. Runtime preparation is separate.
var _scene_cache_validator: Object
var _placement_resolver: Object
func _init(scene_cache_validator: Object, placement_resolver: Object) -> void:
_scene_cache_validator = scene_cache_validator
_placement_resolver = placement_resolver
## Returns whether [param node] satisfies the injected WMO scene-cache contract.
## Null nodes or missing validators return false without mutation.
func is_cached_node_current(node: Node) -> bool:
if node == null or _scene_cache_validator == null:
return false
return bool(_scene_cache_validator.call("is_scene_cache_current", node))
## Instantiates, validates, names and places a cached WMO PackedScene. Rejected
## instantiated roots are freed synchronously. The accepted detached Node3D is
## caller-owned and retains its exact descendant Resource identities.
func instantiate_cached_scene(
relative_path: String,
scene: PackedScene,
placement: Dictionary) -> Node3D:
if scene == null or _placement_resolver == null:
return null
var instantiated_root := scene.instantiate()
if not (instantiated_root is Node3D):
if instantiated_root != null:
instantiated_root.free()
return null
var instance := instantiated_root as Node3D
if not is_cached_node_current(instance):
instance.free()
return null
_apply_identity_and_placement(instance, relative_path, placement)
return instance
## Duplicates, names and places a live-built WMO prototype. The detached result
## is caller-owned. Null inputs, missing placement composition or an unexpected
## non-Node3D duplicate return null; rejected duplicates are freed synchronously.
func duplicate_live_prototype(
relative_path: String,
prototype: Node3D,
placement: Dictionary) -> Node3D:
if prototype == null or _placement_resolver == null:
return null
var duplicated_root := prototype.duplicate()
if not (duplicated_root is Node3D):
if duplicated_root != null:
duplicated_root.free()
return null
var instance := duplicated_root as Node3D
_apply_identity_and_placement(instance, relative_path, placement)
return instance
func _apply_identity_and_placement(
instance: Node3D,
relative_path: String,
placement: Dictionary) -> void:
instance.name = relative_path.get_file().get_basename()
instance.transform = _placement_resolver.call(
"resolve_world_transform",
placement
) as Transform3D
@@ -0,0 +1 @@
uid://c13w66d7uaf2n
@@ -0,0 +1,97 @@
class_name WmoSceneResourceFinalizer
extends RefCounted
## Polls cached WMO PackedScene requests and publishes validated outcomes.
## File-size admission, live fallback and attached Node lifetime remain caller-owned.
var _scene_cache_validator: Object
var _resource_loader_adapter: Object
func _init(
scene_cache_validator: Object,
resource_loader_adapter: Object = null
) -> void:
_scene_cache_validator = scene_cache_validator
_resource_loader_adapter = resource_loader_adapter
## Polls a detached pending snapshot in insertion order. Terminal requests are
## published as the exact validated PackedScene or the historical missing state.
func poll_terminal_requests(scene_resource_cache_state: RefCounted) -> int:
if scene_resource_cache_state == null:
return 0
var completed_request_count := 0
var request_paths: Dictionary = scene_resource_cache_state.call(
"request_paths_snapshot"
)
for normalized_relative_path_variant in request_paths.keys():
var normalized_relative_path := String(normalized_relative_path_variant)
var resource_path := String(request_paths[normalized_relative_path_variant])
var load_status := load_threaded_get_status(resource_path)
if (
load_status != ResourceLoader.THREAD_LOAD_LOADED
and load_status != ResourceLoader.THREAD_LOAD_FAILED
):
continue
if load_status != ResourceLoader.THREAD_LOAD_LOADED:
scene_resource_cache_state.call(
"complete_request_as_missing",
normalized_relative_path
)
completed_request_count += 1
continue
var loaded_resource := load_threaded_get(resource_path)
var loaded_scene := loaded_resource as PackedScene
if is_scene_cache_current(loaded_scene):
scene_resource_cache_state.call(
"complete_request_with_scene",
normalized_relative_path,
loaded_scene
)
else:
scene_resource_cache_state.call(
"complete_request_as_missing",
normalized_relative_path
)
completed_request_count += 1
return completed_request_count
## Instantiates one Node3D probe, delegates the existing WMOBuilder metadata
## check and synchronously releases the probe before returning.
func is_scene_cache_current(scene: PackedScene) -> bool:
if scene == null or _scene_cache_validator == null:
return false
var instantiated_node := scene.instantiate()
if not (instantiated_node is Node3D):
if instantiated_node != null:
instantiated_node.free()
return false
var instance := instantiated_node as Node3D
var is_current := bool(_scene_cache_validator.call(
"is_scene_cache_current",
instance
))
instance.free()
return is_current
## Production ResourceLoader status boundary; injectable for synthetic tests.
func load_threaded_get_status(resource_path: String) -> int:
if _resource_loader_adapter != null:
return int(_resource_loader_adapter.call(
"load_threaded_get_status",
resource_path
))
return ResourceLoader.load_threaded_get_status(resource_path)
## Production ResourceLoader result boundary; injectable for synthetic tests.
func load_threaded_get(resource_path: String) -> Resource:
if _resource_loader_adapter != null:
return _resource_loader_adapter.call(
"load_threaded_get",
resource_path
) as Resource
return ResourceLoader.load_threaded_get(resource_path)
@@ -0,0 +1 @@
uid://cp2r3tadn8l6q
+44 -16
View File
@@ -26,11 +26,13 @@ func setup(target_mesh_instance: MeshInstance3D, bone_data: Array, surface_data:
_capture_materials()
_make_mesh_unique()
_rebuild_mesh(0.0)
set_process(mesh != null and not bones.is_empty() and not surfaces.is_empty() and animation_length > 0.0)
_prepared = _has_runtime_animation_data()
set_process(_prepared)
func _ready() -> void:
prepare_runtime()
if not _prepared:
prepare_runtime()
func _process(delta: float) -> void:
@@ -41,24 +43,49 @@ func _process(delta: float) -> void:
func set_phase(phase: float) -> void:
if animation_length <= 0.0:
_time = 0.0
else:
_time = fposmod(animation_length * phase, animation_length)
_set_phase_time(phase)
_rebuild_mesh(_time)
func prepare_runtime() -> bool:
return _prepare_runtime(false)
## Rebinds a duplicated animator to its local mesh and applies its deterministic
## phase with one deformation rebuild. This must happen before attachment so
## _ready() can remain idempotent for already prepared runtime instances.
func prepare_runtime_at_phase(phase: float) -> bool:
_set_phase_time(phase)
return _prepare_runtime(true)
func _prepare_runtime(force_rebuild: bool) -> bool:
if _prepared and not force_rebuild:
set_process(true)
return true
_resolve_mesh_instance()
if force_rebuild:
_materials.clear()
_capture_materials()
_unique_mesh_ready = false
_make_mesh_unique()
_rebuild_mesh(_time)
_prepared = mesh != null and not bones.is_empty() and not surfaces.is_empty() and animation_length > 0.0
_prepared = _has_runtime_animation_data()
set_process(_prepared)
return _prepared
func _has_runtime_animation_data() -> bool:
return mesh != null and not bones.is_empty() and not surfaces.is_empty() and animation_length > 0.0
func _set_phase_time(phase: float) -> void:
if animation_length <= 0.0:
_time = 0.0
else:
_time = fposmod(animation_length * phase, animation_length)
func runtime_debug_state() -> Dictionary:
return {
"prepared": _prepared,
@@ -81,11 +108,12 @@ func _resolve_mesh_instance() -> void:
func _make_mesh_unique() -> void:
if _unique_mesh_ready or mesh_instance == null or mesh_instance.mesh == null:
return
var duplicated := mesh_instance.mesh.duplicate(true) as ArrayMesh
if duplicated == null:
return
mesh_instance.mesh = duplicated
mesh = duplicated
# _rebuild_mesh() replaces every surface from the retained native arrays, so
# copying the source ArrayMesh would only duplicate data that is discarded.
# Materials were captured before this call and are intentionally shared.
var instance_mesh := ArrayMesh.new()
mesh_instance.mesh = instance_mesh
mesh = instance_mesh
_unique_mesh_ready = true
@@ -143,20 +171,20 @@ func _rebuild_mesh(time: float) -> void:
continue
var transform: Transform3D = bone_matrices[bone_index]
skinned_pos += transform * base_vertices[vertex_index] * weight
if normals.size() == base_normals.size():
if not normals.is_empty():
skinned_nrm += (transform.basis * base_normals[vertex_index]) * weight
total_weight += weight
if total_weight > 0.0:
vertices[vertex_index] = skinned_pos / total_weight
if normals.size() == base_normals.size():
if not normals.is_empty():
normals[vertex_index] = (skinned_nrm / total_weight).normalized()
else:
vertices[vertex_index] = base_vertices[vertex_index]
if normals.size() == base_normals.size():
if not normals.is_empty():
normals[vertex_index] = base_normals[vertex_index]
else:
vertices[vertex_index] = base_vertices[vertex_index]
if normals.size() == base_normals.size():
if not normals.is_empty():
normals[vertex_index] = base_normals[vertex_index]
var arrays := []
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -176,14 +176,14 @@ func _bake_glb_animation_cache(
if not force and FileAccess.file_exists(abs_out_glb):
return true
var abs_converter := ProjectSettings.globalize_path(converter)
var abs_output := ProjectSettings.globalize_path(output_dir)
var converter_output_directory := abs_out_glb.get_base_dir()
if not FileAccess.file_exists(abs_converter):
push_warning("M2 GLB converter not found: %s" % converter)
return false
var stdout := []
var exit_code := OS.execute(
python_exe,
[abs_converter, abs_m2, abs_output],
[abs_converter, abs_m2, converter_output_directory],
stdout,
true,
false)
+30 -10
View File
@@ -5,6 +5,9 @@ extends SceneTree
const FINALIZER_SCRIPT := preload("res://src/render/m2/m2_animated_scene_finalizer.gd")
const FINALIZER_PATH := "res://src/render/m2/m2_animated_scene_finalizer.gd"
const RESOURCE_FINALIZER_PATH := (
"res://src/render/m2/m2_animation_resource_finalizer.gd"
)
const MATERIALIZER_PATH := "res://src/render/m2/m2_animated_instance_materializer.gd"
const LOADER_PATH := "res://src/scenes/streaming/streaming_world_loader.gd"
@@ -116,6 +119,9 @@ func _verify_material_mapping(failures: Array[String]) -> void:
func _verify_ownership_boundaries(failures: Array[String]) -> void:
var finalizer_source := FileAccess.get_file_as_string(FINALIZER_PATH)
var resource_finalizer_source := FileAccess.get_file_as_string(
RESOURCE_FINALIZER_PATH
)
var materializer_source := FileAccess.get_file_as_string(MATERIALIZER_PATH)
var loader_source := FileAccess.get_file_as_string(LOADER_PATH)
_expect_true(loader_source.contains("M2_ANIMATED_SCENE_FINALIZER_SCRIPT.new()"), "loader composes finalizer", failures)
@@ -126,27 +132,41 @@ func _verify_ownership_boundaries(failures: Array[String]) -> void:
]:
_expect_false(loader_source.contains(removed_loader_function), "legacy helper removed: %s" % removed_loader_function, failures)
for delegated_call in [
"_m2_animated_scene_finalizer.instantiate_candidate(resource)",
"_m2_animated_scene_finalizer.repair_materials(candidate, material_source)",
"_m2_animated_scene_finalizer.finalize_candidate(candidate)",
"_m2_animated_scene_finalizer.mesh_instances_in_subtree(root)",
"\"instantiate_candidate\"",
"\"repair_materials\"",
"\"finalize_candidate\"",
]:
_expect_equal(loader_source.count(delegated_call), 1, "single loader delegation: %s" % delegated_call, failures)
_expect_equal(
resource_finalizer_source.count(delegated_call),
1,
"single resource-finalizer delegation: %s" % delegated_call,
failures
)
_expect_equal(
loader_source.count("_m2_animated_scene_finalizer.mesh_instances_in_subtree(root)"),
1,
"single loader mesh traversal delegation",
failures
)
_expect_equal(
materializer_source.count("_animated_scene_finalizer.animation_players_in_subtree("),
1,
"single materializer player-inventory delegation",
failures
)
for retained_loader_rule in [
"ResourceLoader.load_threaded_get(path)",
for retained_renderer_rule in [
"ResourceLoader.load_threaded_get(resource_path)",
"RENDER_BUDGET_SCHEDULER_SCRIPT.M2_ANIMATION_FINALIZE",
"_get_or_load_m2_material_prototype(normalized_rel)",
"_m2_prototype_cache_state.adopt_animated_prototype(",
"_m2_prototype_cache_state.mark_animation_static(normalized_rel)",
"\"adopt_animated_prototype\"",
"\"mark_animation_static\"",
"M2_ANIM_CACHE path=%s cache=%s players=%d",
]:
_expect_true(loader_source.contains(retained_loader_rule), "loader retains %s" % retained_loader_rule, failures)
_expect_true(
(loader_source + resource_finalizer_source).contains(retained_renderer_rule),
"renderer retains %s" % retained_renderer_rule,
failures
)
for forbidden_dependency in [
"ResourceLoader.",
"FileAccess.",
@@ -5,6 +5,12 @@ extends SceneTree
const PIPELINE_SCRIPT := preload("res://src/render/m2/m2_animation_load_pipeline_state.gd")
const PIPELINE_PATH := "res://src/render/m2/m2_animation_load_pipeline_state.gd"
const OBSERVER_PATH := (
"res://src/render/m2/m2_cached_animation_resource_observer.gd"
)
const RESOURCE_FINALIZER_PATH := (
"res://src/render/m2/m2_animation_resource_finalizer.gd"
)
const LOADER_PATH := "res://src/scenes/streaming/streaming_world_loader.gd"
@@ -80,21 +86,33 @@ func _verify_discard_metrics_clear_and_diagnostics(failures: Array[String]) -> v
func _verify_ownership_boundaries(failures: Array[String]) -> void:
var pipeline_source := FileAccess.get_file_as_string(PIPELINE_PATH)
var observer_source := FileAccess.get_file_as_string(OBSERVER_PATH)
var resource_finalizer_source := FileAccess.get_file_as_string(
RESOURCE_FINALIZER_PATH
)
var loader_source := FileAccess.get_file_as_string(LOADER_PATH)
_expect_true(loader_source.contains("M2_ANIMATION_LOAD_PIPELINE_STATE_SCRIPT.new()"), "loader composes pipeline state", failures)
_expect_false(loader_source.contains("var _m2_animation_load_requests:"), "legacy request field removed", failures)
_expect_false(loader_source.contains("var _m2_animation_finalize_queue:"), "legacy finalize field removed", failures)
_expect_equal(loader_source.count("_m2_animation_load_pipeline_state.total_work_count()"), 3, "three existing metrics delegate", failures)
_expect_equal(loader_source.count("_m2_animation_load_pipeline_state.clear()"), 2, "two existing clear sites delegate", failures)
for retained_loader_rule in [
"ResourceLoader.load_threaded_request(",
"ResourceLoader.load_threaded_get_status(path)",
"ResourceLoader.load_threaded_get(path)",
_expect_true(
observer_source.contains("ResourceLoader.load_threaded_request("),
"cached observer owns request admission",
failures
)
for retained_renderer_rule in [
"ResourceLoader.load_threaded_get_status(resource_path)",
"ResourceLoader.load_threaded_get(resource_path)",
"RENDER_BUDGET_SCHEDULER_SCRIPT.M2_ANIMATION_FINALIZE",
"_m2_prototype_cache_state.adopt_animated_prototype(",
"_m2_prototype_cache_state.mark_animation_static(",
"\"adopt_animated_prototype\"",
"\"mark_animation_static\"",
]:
_expect_true(loader_source.contains(retained_loader_rule), "loader retains %s" % retained_loader_rule, failures)
_expect_true(
(loader_source + resource_finalizer_source).contains(retained_renderer_rule),
"renderer retains %s" % retained_renderer_rule,
failures
)
for forbidden_dependency in [
"ResourceLoader.",
"WorkerThreadPool.",

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