Compare commits
153 Commits
5aa40d6afb
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
| f35dcf3a09 | |||
| d65ebee57f | |||
| a59086fdf0 | |||
| ae40e3f1d4 | |||
| 49e70260ed | |||
| 406dc464c3 | |||
| 6a0f9bd3ba | |||
| 3525cc0ead | |||
| 7779e9e75f | |||
| 9ba7ca3263 | |||
| 8c54313f7d | |||
| 1acddab8a5 | |||
| 5a1cd9d7c8 | |||
| a86f8f2212 | |||
| fb7c9f174e | |||
| f1a7400ed1 | |||
| 34b700051f | |||
| 71a1012779 | |||
| ddeb708c08 | |||
| c328d86554 | |||
| 75ddf6a2ed | |||
| 06f6394043 | |||
| b20f0d7f6f | |||
| e7cd967dce | |||
| ff952da7d8 | |||
| 70729bb341 | |||
| d37c799850 | |||
| c24c3f159c | |||
| 1cb0101a73 | |||
| d22a9cd743 | |||
| 6f385ed261 | |||
| f79e064d25 | |||
| 6a23c1b996 | |||
| df87619220 | |||
| 3519f183bb | |||
| 5ebf4de2ff | |||
| a043c79654 | |||
| 194b64d030 | |||
| 7cb3e3412f | |||
| f062ea91cd | |||
| 032a256e70 | |||
| 4354834c50 | |||
| 41b3b63215 | |||
| ec1b90f1e4 | |||
| 0decd10e09 | |||
| b113db01cd | |||
| 99a90ddfb3 | |||
| ed71b36aec | |||
| 17f5cc0faa | |||
| 1fb566f9bc | |||
| c74b90a8ea | |||
| 1dc013e5c2 | |||
| 8938350a59 | |||
| 4a8338f2f0 | |||
| 3ee9e77422 | |||
| 81e33a9bc4 | |||
| 5e82daacb9 | |||
| 8339189907 | |||
| 252927d90c | |||
| 0fd052923d | |||
| 86814deca1 | |||
| c251985256 | |||
| 899a9ae012 | |||
| 456f3e2334 | |||
| 9ab4c0762d | |||
| 5b2d9f23fe | |||
| c0fc191bcd | |||
| 15c24692f0 | |||
| 9b73571dcb | |||
| 5be36376c2 | |||
| c0a7bfbd8d | |||
| e7a7c67f6d | |||
| bc2bd06abc | |||
| 1b450dc580 | |||
| 03ba129a52 | |||
| c20bc62ac0 | |||
| 3dccd3e3ee | |||
| 672e0ce2a5 | |||
| b8e0f11ed5 | |||
| 2695afcdba | |||
| 20a64b5dfc | |||
| 1de50f98ce | |||
| 5e828aaf01 | |||
| cd664eb4cb | |||
| 87bf067493 | |||
| ca77d32be1 | |||
| 7cd07dda4f | |||
| 8605b43b1f | |||
| b608a81766 | |||
| e4bb10cee7 | |||
| 1d94eace68 | |||
| 1fafddecb6 | |||
| 0007a69769 | |||
| ece7724a28 | |||
| 951bd54fff | |||
| dd8cfbf79c | |||
| fa85985ec5 | |||
| f68e971cce | |||
| 7b0e1eac5f | |||
| 6607100686 | |||
| 5e2afbc400 | |||
| 30945615b9 | |||
| c5ecaba0ab | |||
| e990a6503d | |||
| e2bb501695 | |||
| ef782d1f2b | |||
| 83b3618c50 | |||
| ccbb27a9fd | |||
| 82df7a555d | |||
| 5f6287905c | |||
| 8ec13d6018 | |||
| 38f831ea2f | |||
| 0544cf22df | |||
| 19ca8f48e8 | |||
| 324e64dd6e | |||
| 90b1dac56e | |||
| 874fd0f3f2 | |||
| 9cb8bdb137 | |||
| 235be3a36c | |||
| 4f8fc3b3b8 | |||
| e1b2886657 | |||
| f4707750b7 | |||
| 78adb54cfe | |||
| b7c036f14d | |||
| 7973945582 | |||
| 4387c66c0a | |||
| ada0fd9467 | |||
| ff4f320412 | |||
| c83929428d | |||
| 71fa093118 | |||
| e690756301 | |||
| b4955d6162 | |||
| b4b94b3cbb | |||
| d39f591e50 | |||
| 4314f961f6 | |||
| c9547041da | |||
| 60603e18c8 | |||
| 3411156f8f | |||
| e576ae2cad | |||
| a878e7c4e7 | |||
| ab40a76b62 | |||
| e1bb105fff | |||
| 040b635ec6 | |||
| 1e740aaa65 | |||
| c06aed568b | |||
| 01bad4ca26 | |||
| 3a6b1d306d | |||
| e420a8724f | |||
| 1e73e597f8 | |||
| f9e5a4f2e0 | |||
| 16053b46ca | |||
| c5900368d4 | |||
| 79bc4e6ffc |
@@ -27,6 +27,34 @@ Paired run 2026-07-11 подтвердил крупный coordinate/placement g
|
||||
|
||||
- `src/render/world_render_facade.gd` - стабильная граница runtime/tools для focus и metrics.
|
||||
- `src/scenes/streaming/streaming_world_loader.gd` - внутренний runtime streamer мира.
|
||||
- `src/render/wmo/wmo_placement_resolver.gd` - value-only WMO path, identity and transform rules.
|
||||
- `src/render/wmo/wmo_placement_registry.gd` - WMO placement-key to tile/global reference sets; Nodes and render jobs remain in the streamer.
|
||||
- `src/render/wmo/wmo_render_build_step_planner.gd` - mesh-first lightweight WMO group operation and cursor planning without Nodes or Resources.
|
||||
- `src/render/wmo/wmo_render_build_queue.gd` / `wmo_render_build_job.gd` - typed pending group jobs, FIFO placement keys and strong root/resource references without engine destruction.
|
||||
- `src/render/wmo/wmo_render_resource_cache_state.gd` - validated lightweight WMO render Resources, negative cache and pending cache paths without ResourceLoader I/O.
|
||||
- `src/render/wmo/wmo_render_resource_finalizer.gd` - lightweight WMO terminal polling, script/format validation and Resource/missing publication.
|
||||
- `src/render/wmo/wmo_scene_resource_finalizer.gd` - cached WMO terminal polling, PackedScene probe validation/lifetime and scene/missing publication.
|
||||
- `src/render/wmo/wmo_scene_resource_cache_state.gd` - validated cached-WMO PackedScenes, negative cache and pending `.tscn` paths without file/I/O/Node ownership.
|
||||
- `src/render/liquid/adt_water_load_pipeline_state.gd` - ADT water pending FIFO/dedupe, active task IDs and worker-safe parsed-result mailbox without parser or Node ownership.
|
||||
- `src/render/liquid/adt_water_scene_finalizer.gd` - stateless main-thread ADT water build/attach and optional persisted Editor ownership through the existing ADTBuilder.
|
||||
- `src/render/m2/m2_runtime_mesh_rebuild_classifier.gd` - memoized billboard/UV-rotation decision for stale cached M2 runtime mesh refresh.
|
||||
- `src/render/m2/m2_animated_scene_finalizer.gd` - animated PackedScene candidate ownership, historical material repair and AnimationPlayer validation.
|
||||
- `src/render/m2/m2_animation_playback_controller.gd` - deterministic per-instance phase, default animation selection and native/imported playback mutation.
|
||||
- `src/render/m2/m2_animated_instance_materializer.gd` - main-thread animated instance duplication, render settings, playback startup and non-empty batch attachment.
|
||||
- `src/render/m2/m2_static_batch_materializer.gd` - main-thread static M2 MultiMesh construction, render settings and attachment.
|
||||
- `src/render/m2/m2_build_queue.gd` / `m2_build_job.gd` - typed pending M2 jobs, FIFO/stale tile keys, grouped-transform references and progress cursors without engine destruction.
|
||||
- `src/render/m2/m2_build_dispatch_planner.gd` - pure animation/static wait, materialization and missing-model advance decision.
|
||||
- `src/render/m2/m2_build_resource_snapshot.gd` - typed per-step animated/static resource observations without engine destruction.
|
||||
- `src/render/m2/m2_static_build_resource_observer.gd` - static Mesh cache lookup, threaded request selection and missing transition.
|
||||
- `src/render/m2/m2_cached_animation_resource_observer.gd` - cached animated GLB eligibility, threaded request admission and snapshot production.
|
||||
- `src/render/m2/m2_animation_load_pipeline_state.gd` - animated M2 threaded-load request records and completion-order finalize FIFO without I/O or Node ownership.
|
||||
- `src/render/m2/m2_animation_resource_finalizer.gd` - cached animated M2 terminal status polling, Resource load, scene finalization and prototype/static-only outcome.
|
||||
- `src/render/m2/m2_mesh_resource_finalizer.gd` - static M2 terminal status polling, Mesh extraction/preparation and cache/missing outcome.
|
||||
- `src/render/m2/m2_mesh_load_pipeline_state.gd` - static M2 threaded-load request records, terminal statuses and completion-order finalize FIFO without I/O or Mesh ownership.
|
||||
- `src/render/m2/m2_mesh_resource_cache_state.gd` - normalized-path prepared static M2 Mesh references with final-shutdown lifetime.
|
||||
- `src/render/m2/m2_mesh_resource_extractor.gd` - first-Mesh selection from direct/PackedScene/Node inputs with temporary instance cleanup.
|
||||
- `src/render/m2/m2_runtime_mesh_finalizer.gd` - refresh version, classifier lifetime, M2Builder rebuild and original-Mesh fallback.
|
||||
- `src/render/m2/m2_prototype_cache_state.gd` - shutdown-lifetime static/animated prototype Nodes and missing/static-only path outcomes.
|
||||
- `src/scenes/streaming/eastern_kingdoms_streaming.tscn` - текущая сцена для проверки Azeroth/Eastern Kingdoms.
|
||||
- `addons/mpq_extractor/loaders/adt_builder.gd` - сборка ADT terrain, control splat материалов и MH2O liquids.
|
||||
- `addons/mpq_extractor/loaders/m2_builder.gd` - сборка статического M2 mesh/material.
|
||||
@@ -305,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.
|
||||
@@ -394,6 +422,12 @@ Known limits:
|
||||
- `WMOBuilder` строит group meshes;
|
||||
- runtime registry дедуплицирует WMO по `unique_id`;
|
||||
- WMO на границе ADT не дублируются;
|
||||
- `WmoPlacementRegistry` owns only placement reference sets; the streamer keeps
|
||||
the parallel key-to-Node map and performs final-release cancellation/free;
|
||||
- `WmoRenderBuildStepPlanner` selects one mesh or MultiMesh group and its next
|
||||
cursors; the streamer still materializes Nodes and consumes the frame permit;
|
||||
- `WmoRenderBuildQueue` owns pending typed jobs/FIFO keys and releases references
|
||||
on cancel/reset; the streamer still validates and frees engine objects;
|
||||
- большие WMO не должны инстанцироваться как тяжелые `.tscn` во время движения;
|
||||
- добавлен lightweight render-cache path для WMO groups;
|
||||
- `wmo_render_group_ops_per_tick` ограничивает подключение WMO groups по кадрам;
|
||||
@@ -789,7 +823,12 @@ $exe = Join-Path $env:TEMP 'godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe'
|
||||
- Billboard metadata is resolved per drawn vertex through the skin section bone palette and vertex bone indices. This avoids treating an entire mixed surface as billboard just because the palette contains a billboard bone.
|
||||
- `M2Builder` stores billboard pivot/mode in `Mesh.ARRAY_CUSTOM0` as RGBA float data. `CUSTOM0.xyz` is the Godot-space bone pivot; `CUSTOM0.w` is the billboard mode: spherical, lock X, lock Y, or lock Z.
|
||||
- `WowM2Material` applies camera-facing billboarding in the vertex shader for marked vertices. This works with grouped ADT M2 `MultiMesh` batches because the shader uses each instance transform.
|
||||
- Runtime M2 cache refresh now has `M2_MATERIAL_REFRESH_VERSION`. Old cached `.tscn/.glb` meshes are replaced in memory from raw `.m2` only when the raw model actually needs custom billboard attributes or UV-rotation data. Do not rebuild every stale cached M2 during streaming: creating fresh materials for ordinary props can exhaust the D3D12 resource descriptor heap.
|
||||
- Runtime M2 cache refresh uses material refresh version `2` (now
|
||||
`M2RuntimeMeshFinalizer.MATERIAL_REFRESH_VERSION`). Old cached `.tscn/.glb`
|
||||
meshes are replaced in memory from raw `.m2` only when the raw model actually
|
||||
needs custom billboard attributes or UV-rotation data. Do not rebuild every
|
||||
stale cached M2 during streaming: creating fresh materials for ordinary props
|
||||
can exhaust the D3D12 resource descriptor heap.
|
||||
- Verified with `ElwynnGrass1.m2`: native data exposes a billboard batch, the built mesh contains `CUSTOM0`, and the M2 shader compiles with the billboard uniform enabled.
|
||||
- Remaining parity work: many Elwynn tree canopy M2s do not carry billboard bone flags, so their flat-looking leaves are a separate material/geometry parity issue, not this bone-billboard path.
|
||||
|
||||
@@ -799,7 +838,8 @@ $exe = Join-Path $env:TEMP 'godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe'
|
||||
- Texture transform dictionaries now expose static UV rotation matrices and approximate Z-rotation speeds, in addition to existing translation, translation speed and scale.
|
||||
- `WowM2Material` applies UV rotation and scale around WoW's texture center `(0.5, 0.5)`, then applies translation/scroll. This is closer to the client path shown by `CM2Model::AnimateTextureTransformsMT`.
|
||||
- All four M2 texture stages receive UV rotation and rotation-speed uniforms, matching the existing four-stage texture binding path.
|
||||
- `M2_MATERIAL_REFRESH_VERSION` and `M2Builder.MATERIAL_FORMAT_VERSION` are bumped to `2`, so cached M2 meshes are rebuilt in memory when raw `.m2` data is available.
|
||||
- Runtime M2 material refresh and `M2Builder.MATERIAL_FORMAT_VERSION` are `2`,
|
||||
so stale cached M2 meshes can be rebuilt in memory when raw `.m2` data is available.
|
||||
- Verified with `Spells/ArcaneForceShield_Blue.m2`: native data exposes a non-identity UV rotation, the built material receives the stage rotation uniform, and the shader compiles.
|
||||
|
||||
## 2026-07-08 Renderer Material Regression Smoke
|
||||
@@ -1020,11 +1060,250 @@ $exe = Join-Path $env:TEMP 'godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe'
|
||||
remaining-count capping and next-offset/group-completion calculation.
|
||||
- Existing animated/static limits still clamp to at least one; resource readiness,
|
||||
queue rotation, serial progression and `M2_BUILD` permit consumption are unchanged.
|
||||
- The loader retains build jobs/queues, tile cancellation, animation/mesh caches,
|
||||
retries and all Node/MultiMesh/Mesh/material/RID finalization.
|
||||
- `M2BuildQueue` now retains typed pending jobs, FIFO/stale keys and cursors.
|
||||
The loader retains tile cancellation, resource readiness/retries and all
|
||||
Node/MultiMesh/Mesh/material/RID finalization.
|
||||
- Cache formats, quality profiles, batching output and visible rules are unchanged.
|
||||
Asset-backed p95/p99 and spatial-cell batching evidence remain pending.
|
||||
|
||||
## 2026-07-18 M2 Build Dispatch Planner Extraction
|
||||
|
||||
- `M2BuildDispatchPlanner` now owns the pure action priority between pending
|
||||
animation, animated materialization, pending/ready static Mesh and terminally
|
||||
missing-model advancement.
|
||||
- Pending animation still wins before batch planning. Unresolved static Meshes
|
||||
still rotate and consume one `M2_BUILD` permit without advancing progress.
|
||||
- Empty batches advance without serial change; animated, static and terminally
|
||||
missing positive batches retain the historical serial increment.
|
||||
- Resource lookup/request order, queue/cursor adoption, materialization, permits,
|
||||
Node lifetime, cache formats, profiles and visible rules remain unchanged.
|
||||
|
||||
## 2026-07-18 M2 Build Resource Snapshot Extraction
|
||||
|
||||
- `M2BuildResourceSnapshot` now carries one build-step normalized path, optional
|
||||
animated prototype, pending-animation state, optional static Mesh and terminal
|
||||
missing-model state through typed accessors.
|
||||
- Animation observation is captured first. Static observation is adopted only
|
||||
for a positive non-animated batch after animation is no longer pending.
|
||||
- The dispatch planner consumes the snapshot; the loader borrows the selected
|
||||
prototype/Mesh for the existing materializer calls.
|
||||
- ResourceLoader/cache requests, permit/cursor transitions, engine lifetime,
|
||||
cache formats, profiles and visible rules remain unchanged.
|
||||
|
||||
## 2026-07-18 M2 Static Build Resource Observer Extraction
|
||||
|
||||
- `M2StaticBuildResourceObserver` now owns prepared-Mesh lookup, existing-request
|
||||
detection, `.tscn`-before-`.glb` candidate selection, pivot-prefix GLB rejection,
|
||||
threaded request admission and terminal missing transition for build jobs.
|
||||
- The observer fills `M2BuildResourceSnapshot`; the loader retains animation
|
||||
observation, finalize drains, materialization, permits and engine lifetime.
|
||||
- Candidate order, request errors, cache formats, profiles and visuals are unchanged.
|
||||
|
||||
## 2026-07-17 M2 Runtime Mesh Rebuild Classifier Extraction
|
||||
|
||||
- `M2RuntimeMeshRebuildClassifier` now owns the memoized decision used when a
|
||||
stale cached M2 mesh may need an in-memory rebuild from raw data.
|
||||
- Existing triggers are unchanged: any billboard flag/positive billboard vertex
|
||||
count, or a referenced non-identity UV rotation/absolute rotation speed above
|
||||
`0.000001` across at most four texture stages.
|
||||
- Invalid batch variants and combo/transform indices remain skipped. The first
|
||||
decision for a normalized path remains cached until the same two historical
|
||||
map/shutdown clear sites.
|
||||
- `StreamingWorldLoader` still requests raw M2 data and owns ResourceLoader,
|
||||
returned-Mesh adoption and finalization budgets. Refresh-version checks,
|
||||
M2Builder rebuild and fallback now belong to `M2RuntimeMeshFinalizer`.
|
||||
- No cache version, shader, material, geometry or visible rule changed. Synthetic
|
||||
predicate timing is not asset-backed descriptor-pressure/p95/p99 evidence.
|
||||
|
||||
## 2026-07-17 M2 Mesh Load Pipeline State Extraction
|
||||
|
||||
- `M2MeshLoadPipelineState` now owns static M2 successful threaded-load request
|
||||
records and the FIFO populated when ResourceLoader polling reaches loaded or
|
||||
failed terminal status.
|
||||
- Pending polling snapshots preserve Dictionary insertion order; terminal FIFO
|
||||
order follows completion transition order. Existing `m2_mesh` metrics still
|
||||
count pending plus finalize records at all three sites.
|
||||
- Shutdown still drains every pending ResourceLoader path before state clear;
|
||||
map reset and shutdown retain the same two clear sites.
|
||||
- `StreamingWorldLoader` still selects `.tscn`/`.glb` paths, excludes animated
|
||||
pivot-prefix GLBs, performs all ResourceLoader calls, consumes
|
||||
`M2_MESH_FINALIZE`, extracts/prepares Meshes and updates Mesh/missing caches.
|
||||
- Cache formats, material refresh, profiles and visible output are unchanged.
|
||||
Synthetic state timing is not asset-backed I/O, leak or p95/p99 evidence.
|
||||
|
||||
## 2026-07-17 M2 Animation Load Pipeline State Extraction
|
||||
|
||||
- `M2AnimationLoadPipelineState` now owns animated M2 successful threaded-load
|
||||
request records and the FIFO populated at loaded/failed terminal transitions.
|
||||
- Pending snapshots preserve insertion order, while finalization follows completion
|
||||
order. All three historical `m2_animation` metrics still count both stages.
|
||||
- Shutdown still drains pending ResourceLoader paths before clear; map reset and
|
||||
shutdown preserve the same two clear sites.
|
||||
- At this extraction stage, `StreamingWorldLoader` retained GLB eligibility/cache selection, every
|
||||
ResourceLoader call, `M2_ANIMATION_FINALIZE` permits, scene instantiation,
|
||||
material repair and prototype/static-fallback decisions.
|
||||
- Cache formats, animation behavior and visible output are unchanged. Synthetic
|
||||
timing is not asset-backed I/O, leak, animation-fidelity or p95/p99 evidence.
|
||||
|
||||
## 2026-07-18 M2 Cached Animation Resource Observer Extraction
|
||||
|
||||
- `M2CachedAnimationResourceObserver` now owns cached animated-prototype lookup,
|
||||
allow/deny matching, historical `.glb` candidate selection, GLB animation/
|
||||
primitive/schema safety validation, request admission and static-only fallback.
|
||||
- The observer produces `M2BuildResourceSnapshot` with the exact borrowed cached
|
||||
prototype or pending state and retains/frees no engine object.
|
||||
- Native GryphonRoost raw-data build/debug logging belongs to the sibling native
|
||||
observer; terminal polling, finalization, permits and SceneTree mutation remain
|
||||
loader-owned.
|
||||
- Defaults, path order, accepted empty/`pivot_prefix_v1` schemas, cache format,
|
||||
metrics and visible behavior are unchanged. Generated GLB metadata fixtures
|
||||
are not private asset, leak, p95/p99 or original-client animation evidence.
|
||||
|
||||
## 2026-07-18 M2 Animation Resource Finalizer Extraction
|
||||
|
||||
- `M2AnimationResourceFinalizer` now owns cached animated M2 terminal status
|
||||
polling, completion-FIFO pops, terminal Resource retrieval, candidate
|
||||
instantiation, repair/validation, prototype/static-only adoption and success log.
|
||||
- The two-phase preparation/completion API preserves the prior ordering: the
|
||||
loader resolves a material prototype only after a loaded PackedScene produces
|
||||
a detached Node3D candidate. One scheduler permit still pops one record.
|
||||
- `StreamingWorldLoader` retains the `M2_ANIMATION_FINALIZE` permit loop,
|
||||
material-prototype lookup, build dispatch, materialization and SceneTree lifetime.
|
||||
- No request order, status rule, cache format, profile or visible behavior changed.
|
||||
Synthetic fixtures are not asset-backed animation/leak/p95/p99 evidence.
|
||||
|
||||
## 2026-07-18 M2 Mesh Resource Finalizer Extraction
|
||||
|
||||
- `M2MeshResourceFinalizer` now owns static M2 terminal status polling,
|
||||
completion-FIFO pops, terminal Resource retrieval, first-Mesh extraction,
|
||||
stale/current runtime preparation and Mesh/missing cache adoption.
|
||||
- Pending paths are still polled in insertion order. One
|
||||
`M2_MESH_FINALIZE` permit still pops at most one terminal record; cached Mesh
|
||||
outcomes skip terminal retrieval exactly as before.
|
||||
- Current Meshes retain exact identity and skip raw reads. Stale Meshes still
|
||||
request raw data only when `M2RuntimeMeshFinalizer` requires it, preserving
|
||||
refresh-version, rebuild and original-Mesh fallback rules.
|
||||
- `StreamingWorldLoader` retains request admission, scheduler permits,
|
||||
composition, static materialization and shutdown drain ordering. Cache paths,
|
||||
profiles and visible behavior are unchanged; synthetic fixtures are not
|
||||
asset-backed visual/leak/p95/p99 evidence.
|
||||
|
||||
## 2026-07-18 M2 Native Animation Resource Observer Extraction
|
||||
|
||||
- `M2NativeAnimationResourceObserver` now owns the exact case-insensitive
|
||||
`gryphonroost` candidate rule, animated prototype/static-only cache checks,
|
||||
synchronous raw animated read, native builder call, adoption and success log.
|
||||
- The native attempt remains first. Empty raw data/surfaces and null/childless
|
||||
builds retain the same static-only fallback; accepted Nodes remain owned by
|
||||
`M2PrototypeCacheState` until final shutdown.
|
||||
- `StreamingWorldLoader` retains observer order, typed snapshot construction,
|
||||
cached-GLB fallback, terminal ResourceLoader polling/finalize, permits,
|
||||
materialization and SceneTree lifetime.
|
||||
- The historical unfreed childless builder result is deliberately preserved and
|
||||
documented as a leak risk. No parser, cache, profile or visible rule changed;
|
||||
synthetic fixtures are not asset-backed animation or performance evidence.
|
||||
|
||||
## 2026-07-18 M2 Animated Scene Finalizer Extraction
|
||||
|
||||
- `M2AnimatedSceneFinalizer` now owns terminal animated PackedScene candidate
|
||||
instantiation, depth-first Mesh/AnimationPlayer traversal, static-prototype
|
||||
material override mapping and rejected detached-root destruction.
|
||||
- Material repair retains source/target depth-first ordering, source-index clamp,
|
||||
per-surface override priority and first discovered material fallback.
|
||||
- A candidate still requires at least one AnimationPlayer. Accepted exact Node3D
|
||||
and player count return to the loader; invalid candidates are freed, including
|
||||
the previously unhandled non-Node3D imported-root case.
|
||||
- `StreamingWorldLoader` retains ResourceLoader I/O/statuses, finalize permits,
|
||||
material-prototype selection/loading, prototype adoption/static fallback,
|
||||
animation playback policy and diagnostic logging.
|
||||
- Cache formats, profiles and visible rules are unchanged. Synthetic scene and
|
||||
material fixtures are not asset-backed animation-fidelity or p95/p99 evidence.
|
||||
|
||||
## 2026-07-18 M2 Animation Playback Controller Extraction
|
||||
|
||||
- `M2AnimationPlaybackController` now owns the stable path/index phase formula,
|
||||
ordinary/fish/bird animation-name priority, substring/first-name fallbacks and
|
||||
AnimationPlayer linear-loop/play/seek mutation.
|
||||
- Exact-script native animators retain depth-first pairing, the same five copied
|
||||
fields, optional `prepare_runtime`, phase assignment and debug-state sampling.
|
||||
- Native debug records are detached and sampled only when `debug_streaming` is
|
||||
enabled; `StreamingWorldLoader` retains path normalization and exact log text.
|
||||
- Loader still duplicates/transforms/attaches instances, applies visibility,
|
||||
shadows and Editor ownership, owns build cursors/permits and obtains player inventory.
|
||||
- Cache formats, playback priorities, phase behavior and visible rules are unchanged.
|
||||
Synthetic timing is not asset-backed animation fidelity or p95/p99 evidence.
|
||||
|
||||
## 2026-07-17 M2 Mesh Resource Cache State Extraction
|
||||
|
||||
- `M2MeshResourceCacheState` now owns normalized-path references to prepared
|
||||
static M2 Mesh resources. Lookup returns the exact retained reference and a
|
||||
later store for one path keeps the historical replacement behavior.
|
||||
- The cache retains map-refresh lifetime and clears only at the existing final
|
||||
runtime-cache shutdown site after asynchronous work is drained.
|
||||
- `StreamingWorldLoader` still owns normalization, `.tscn`/`.glb` selection,
|
||||
ResourceLoader I/O, Mesh extraction, material refresh/rebuild, cache adoption
|
||||
decisions, permits and MultiMesh materialization. Prototype/negative state is
|
||||
now a sibling cache service.
|
||||
- Cache formats, refresh metadata, profiles and visible output are unchanged.
|
||||
Synthetic cache timing is not asset-backed memory/leak or p95/p99 evidence.
|
||||
|
||||
## 2026-07-17 M2 Mesh Resource Extractor
|
||||
|
||||
- `M2MeshResourceExtractor` now selects the first Mesh from direct Mesh
|
||||
Resources, temporary PackedScene instances and existing prototype/rebuild
|
||||
Node subtrees using the prior depth-first child order.
|
||||
- PackedScene extraction still instantiates exactly once and synchronously frees
|
||||
the temporary root before returning the borrowed Mesh reference.
|
||||
- `StreamingWorldLoader` still owns ResourceLoader I/O and raw M2 consumers,
|
||||
material refresh/rebuild, cache/missing adoption, permits and MultiMesh builds.
|
||||
- Cache formats, traversal rules, profiles and visible output are unchanged.
|
||||
Synthetic traversal timing is not asset-backed leak or p95/p99 evidence.
|
||||
|
||||
## 2026-07-17 M2 Runtime Mesh Finalizer
|
||||
|
||||
- `M2RuntimeMeshFinalizer` now owns material refresh version `2`, stale-Mesh
|
||||
rebuild classification, M2Builder rebuild and original-Mesh fallback.
|
||||
- Current Meshes still skip raw `.m2` loading. `M2RawModelRepository` owns
|
||||
FileAccess/ClassDB M2Loader I/O and supplies raw data through
|
||||
`M2MeshResourceFinalizer` only when the runtime finalizer reports a stale Mesh;
|
||||
both historical clear sites persist.
|
||||
- Billboard/UV-rotation predicates, rebuild extraction, metadata key and failure
|
||||
fallback are unchanged. Mesh resource finalization owns cache adoption;
|
||||
permits and MultiMesh materialization remain loader-owned, while negative
|
||||
outcomes belong to prototype state.
|
||||
- Synthetic triangle rebuild/fallback timing is not asset-backed material,
|
||||
descriptor-pressure/leak or p95/p99 evidence.
|
||||
|
||||
## 2026-07-17 M2 Raw Model Repository
|
||||
|
||||
- `M2RawModelRepository` now owns the repeated extracted-file and optional
|
||||
native `M2Loader` boundary for static `load_m2` and animated
|
||||
`load_m2_animated` raw Dictionaries.
|
||||
- `StreamingWorldLoader` delegates stale-Mesh refresh and static reads directly;
|
||||
`M2NativeAnimationResourceObserver` delegates native animated reads. The loader
|
||||
retains normalization, `.tscn/.glb` fallback order, permits and Node/Mesh use;
|
||||
prototype/negative state is isolated in `M2PrototypeCacheState`.
|
||||
- The repository retains no path, native object or parsed data. Empty paths,
|
||||
absent files/classes/methods and invalid results produce the same empty-value
|
||||
fallback contract; path join/globalization and native method names are exact.
|
||||
- No cache format, parser, material, geometry, animation or visible rule changed.
|
||||
Synthetic missing-input timing is not asset-backed I/O/p95/p99 or fidelity evidence.
|
||||
|
||||
## 2026-07-17 M2 Prototype Cache State
|
||||
|
||||
- `M2PrototypeCacheState` now owns the exact static and animated prototype Node
|
||||
references plus missing-model and static-animation negative path outcomes.
|
||||
- Sync static, native animated, threaded GLB animation, Mesh finalize and
|
||||
material-prototype paths delegate lookup/adoption/negative transitions to the
|
||||
service. First valid prototype remains canonical for each normalized path.
|
||||
- Map refresh still retains all four states. Final shutdown still occurs after
|
||||
asynchronous drain and now calls one idempotent release operation that frees
|
||||
detached prototypes, queues defensive in-tree Nodes and clears path outcomes.
|
||||
- `StreamingWorldLoader` retains normalization, ResourceLoader/native I/O,
|
||||
builders, animation policy, fallback order, permits and materialization.
|
||||
- No cache format, visible rule or profile changed. Synthetic ownership/timing
|
||||
evidence is not an asset-backed traversal/leak/p95/p99 or fidelity claim.
|
||||
|
||||
## 2026-07-17 WMO Placement Resolver Extraction
|
||||
|
||||
- `WmoPlacementResolver` now owns lowercase/slash cache-key normalization,
|
||||
@@ -1037,6 +1316,98 @@ $exe = Join-Path $env:TEMP 'godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe'
|
||||
- Cache formats, placement values, profiles and visible rules are unchanged.
|
||||
Asset-backed placement/p95/p99 and general WMO parity remain pending.
|
||||
|
||||
## 2026-07-17 WMO Render Resource Cache State Extraction
|
||||
|
||||
- `WmoRenderResourceCacheState` now owns validated lightweight-WMO render
|
||||
Resources, negative entries and normalized-path to pending-cache-path records.
|
||||
- `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
|
||||
and cached-scene/live fallback behavior. Formats, profiles and visuals are unchanged.
|
||||
- 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` 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
|
||||
shutdown releases all scene state. Formats, profiles and visuals are unchanged.
|
||||
- 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,
|
||||
active WorkerThreadPool task IDs and the mutex-protected parsed-result mailbox.
|
||||
- `StreamingWorldLoader` still selects concurrency, starts/waits tasks, invokes
|
||||
`ADTLoader`, consumes `WATER_FINALIZE` permits, rejects stale tile/path/root
|
||||
results and owns `water_loaded` timing. Main-thread Water subtree construction
|
||||
and attachment are delegated separately as described below.
|
||||
- Tile release still cancels only pending water requests; active work completes
|
||||
and its result is discarded when tile ownership is stale. Shutdown waits task
|
||||
IDs before clearing state; reset does not interrupt workers.
|
||||
- Pending-plus-active `water` metrics, parsing, formats, materials, profiles and
|
||||
visible behavior are unchanged.
|
||||
- Asset-backed MH2O/MCLQ fidelity, long-traversal leak/p95/p99 and paired capture
|
||||
evidence remain pending.
|
||||
|
||||
## 2026-07-17 ADT Water Scene Finalizer Extraction
|
||||
|
||||
- `AdtWaterSceneFinalizer` now owns the two main-thread operations that turn
|
||||
validated parsed ADT data into a Water subtree: the existing
|
||||
`ADTBuilder.build_tile_water_scene` call and attachment to the tile root.
|
||||
- Both raw tile finalization and delayed cached-tile water results use the same
|
||||
service. Optional persisted Editor ownership is assigned recursively only
|
||||
when the loader supplies its existing edited-scene owner.
|
||||
- The service is stateless and retains no Node, Mesh, material or RID. The tile
|
||||
SceneTree subtree remains the lifetime owner; dry data still causes ADTBuilder
|
||||
to free its temporary empty Water root.
|
||||
- Parsing, task/result state, concurrency, stale validation, `WATER_FINALIZE`
|
||||
permits, `enable_water` and `water_loaded` timing remain loader-owned.
|
||||
- Geometry, materials, coordinates, cache formats, profiles and visible output
|
||||
are unchanged. Synthetic geometry/ownership evidence is not an MH2O/MCLQ or
|
||||
original-client parity claim; asset-backed traversal/p95/p99 remains pending.
|
||||
|
||||
## 2026-07-16 World Environment Snapshot Facade
|
||||
|
||||
- `WorldEnvironmentSnapshot` carries one immutable finite time-of-day value,
|
||||
@@ -1078,6 +1449,22 @@ $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.
|
||||
|
||||
## 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,115 @@
|
||||
# M03-RND-ADT-WATER-LOAD-PIPELINE-001 — ADT water load pipeline state
|
||||
|
||||
<!-- OPENWC_CLAIM:M03-RND-ADT-WATER-LOAD-PIPELINE-001:sindo-main-codex:2026-07-20 -->
|
||||
|
||||
## Ownership
|
||||
|
||||
- Target: M03
|
||||
- Program: RND
|
||||
- Owner/Agent ID: sindo-main-codex
|
||||
- Branch: `work/sindo-main-codex/m03-adt-water-load-pipeline`
|
||||
- Lease expires UTC: 2026-07-20
|
||||
- Integrator: M03 milestone integrator
|
||||
|
||||
## Outcome
|
||||
|
||||
Extract ADT water pending-request FIFO/deduplication, active worker task IDs and
|
||||
worker-safe result mailbox from the loader while preserving loader-owned parse,
|
||||
budget, stale-result and scene finalization behavior.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Instantiate `ADTLoader`, start/wait WorkerThreadPool tasks or parse ADT bytes.
|
||||
- Consume `WATER_FINALIZE` permits or choose concurrency/profile limits.
|
||||
- Build/attach/free water Nodes, materials, Meshes or RIDs.
|
||||
- Change tile/path stale checks, empty-data behavior or `water_loaded` timing.
|
||||
- Change MH2O/MCLQ parsing or claim liquid fidelity/performance parity.
|
||||
|
||||
## Paths
|
||||
|
||||
- Exclusive: `src/render/liquid/adt_water_load_pipeline_state.gd`,
|
||||
`src/tools/verify_adt_water_load_pipeline_state.gd`,
|
||||
`docs/modules/adt-water-load-pipeline-state.md`, this claim
|
||||
- Shared/hotspots: `src/scenes/streaming/streaming_world_loader.gd`,
|
||||
`src/tools/verify_render_runtime_cache_shutdown.gd`,
|
||||
`docs/modules/world-renderer.md`, `docs/modules/README.md`, `RENDER.md`,
|
||||
`targets/03-renderer-facade.md`
|
||||
- Generated/ignored: `.godot`, native DLL, generated ADT resources, caches and
|
||||
proprietary renderer corpus
|
||||
|
||||
## Contracts and data
|
||||
|
||||
- Pending requests preserve FIFO and tile-key deduplication.
|
||||
- Tile release removes all matching pending entries but does not cancel active work.
|
||||
- Active task IDs are loader-created opaque integers keyed by tile.
|
||||
- Worker publication and main-thread result pop/clear are mutex protected.
|
||||
- Result order and Dictionary payload identity remain unchanged.
|
||||
- Clear releases bookkeeping only; loader waits tasks at orderly shutdown.
|
||||
- Water parsing, coordinate, material, profile and visual rules are unchanged.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Requires: current master `4387c66`; accepted scheduler and renderer lifecycle
|
||||
- Blocks: further liquid parse/finalization decomposition
|
||||
- External state: none; verification uses synthetic task IDs and payloads
|
||||
|
||||
## Verification
|
||||
|
||||
- Commands: dedicated FIFO/dedupe/task/result/cancel/clear/thread/source/timing
|
||||
verifier; shutdown, water/material and adjacent renderer/streaming/coordinate/
|
||||
documentation/coordination gates
|
||||
- Fixtures: synthetic tile/path requests, task IDs and water dictionaries
|
||||
- Fidelity evidence: exact current queue/task/result transitions preserved
|
||||
- Performance budget: 100 cycles over 256 requests/results under 1 second
|
||||
|
||||
## Documentation deliverables
|
||||
|
||||
- Inline public API docs
|
||||
- Module specification with inputs/outputs, ownership and source map
|
||||
- Data-flow, lifecycle/state, worker sequence and dependency diagrams
|
||||
- World-renderer, module registry and RENDER status updates
|
||||
|
||||
## Simplicity and naming
|
||||
|
||||
- Important names introduced: `AdtWaterLoadPipelineState`
|
||||
- Simplest considered solution: one RefCounted with FIFO/dedupe/task maps and one mutex mailbox
|
||||
- Rejected complexity/abstractions: generic async scheduler, signals, callbacks,
|
||||
WorkerThreadPool wrapper, parser/material ownership or multi-lane framework
|
||||
- Unavoidable complexity and justification: result mailbox crosses worker/main threads
|
||||
- Measured optimization evidence: bounded synthetic lifecycle loop
|
||||
|
||||
## Status
|
||||
|
||||
- State: accepted
|
||||
- Done: pipeline state implemented; loader enqueue/start/publish/drain/release/
|
||||
reset/shutdown/metrics adapters migrated; required documentation added
|
||||
- Next: continue liquid parse/finalization extraction or another remaining M03
|
||||
service boundary
|
||||
- Blocked by:
|
||||
|
||||
<!-- OPENWC_INTEGRATION:ACCEPTED:M03-RND-ADT-WATER-LOAD-PIPELINE-001:f470775 -->
|
||||
|
||||
## Handoff
|
||||
|
||||
- Commit: `b7c036f`
|
||||
- Results: water pipeline PASS (`cases=12`, `iterations=100`,
|
||||
`elapsed_ms=86.170`; initial direct cold run `94.576ms`); real Thread result
|
||||
publication, FIFO/dedupe/task/cancel/clear/source contracts PASS; WMO caches/
|
||||
queue/planner/registry/resolver, materials, shutdown and 18 adjacent M2/terrain/
|
||||
facade/scheduler/streaming/coordinate regressions PASS; documentation and
|
||||
coordination gates PASS; checkpoint dry-run PASS `7/7`.
|
||||
- Remaining risks: clear does not interrupt active WorkerThreadPool work and a
|
||||
post-reset publication remains stale until drained/cleared, matching prior
|
||||
behavior; payload Dictionaries require no mutation after publication; parser,
|
||||
main-thread water materialization and asset-backed MH2O/MCLQ traversal/leak/
|
||||
p95/p99/original-client evidence remain pending.
|
||||
- Documentation updated: inline public API; new
|
||||
`docs/modules/adt-water-load-pipeline-state.md`; module registry, world renderer
|
||||
and `RENDER.md`; data-flow/state/sequence/dependency diagrams, ownership,
|
||||
threading, failure/recovery, profiles, performance limit and source map included.
|
||||
- Integration: merged as `f470775`; post-merge water pipeline (`cases=12`,
|
||||
`iterations=100`, `elapsed_ms=86.935`), shutdown, renderer materials, facade,
|
||||
internal-access (`private_symbols=36`), `7/7` manifest, documentation and
|
||||
coordination gates passed.
|
||||
|
||||
<!-- OPENWC_HANDOFF:READY:M03-RND-ADT-WATER-LOAD-PIPELINE-001:b7c036f -->
|
||||
@@ -0,0 +1,116 @@
|
||||
# M03-RND-ADT-WATER-SCENE-FINALIZER-001 — ADT water scene finalizer
|
||||
|
||||
<!-- OPENWC_CLAIM:M03-RND-ADT-WATER-SCENE-FINALIZER-001:sindo-main-codex:2026-07-20 -->
|
||||
|
||||
## Ownership
|
||||
|
||||
- Target: M03
|
||||
- Program: RND
|
||||
- Owner/Agent ID: sindo-main-codex
|
||||
- Branch: `work/sindo-main-codex/m03-adt-water-scene-finalizer`
|
||||
- Lease expires UTC: 2026-07-20
|
||||
- Integrator: M03 milestone integrator
|
||||
|
||||
## Outcome
|
||||
|
||||
Extract main-thread ADT water subtree construction, tile-root attachment and
|
||||
optional recursive editor ownership from `StreamingWorldLoader` while retaining
|
||||
the existing `ADTBuilder` geometry/material implementation.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Parse or reinterpret MH2O/MCLQ data.
|
||||
- Own pending requests, WorkerThreadPool tasks or parsed-result delivery.
|
||||
- Consume `WATER_FINALIZE` permits or validate tile/path/result staleness.
|
||||
- Mark tile state `water_loaded` or choose whether water is enabled.
|
||||
- Change liquid geometry, materials, coordinates, cache formats or profiles.
|
||||
|
||||
## Paths
|
||||
|
||||
- Exclusive: `src/render/liquid/adt_water_scene_finalizer.gd`,
|
||||
`src/tools/verify_adt_water_scene_finalizer.gd`,
|
||||
`docs/modules/adt-water-scene-finalizer.md`, this claim
|
||||
- Shared/hotspots: `src/scenes/streaming/streaming_world_loader.gd`,
|
||||
`src/tools/verify_adt_water_load_pipeline_state.gd`,
|
||||
`docs/modules/adt-water-load-pipeline-state.md`,
|
||||
`docs/modules/world-renderer.md`, `docs/modules/README.md`, `RENDER.md`,
|
||||
`targets/03-renderer-facade.md`
|
||||
- Generated/ignored: `.godot`, native DLL, generated ADT resources, caches and
|
||||
proprietary renderer corpus
|
||||
|
||||
## Contracts and data
|
||||
|
||||
- Empty water data or invalid tile root produces no Node and no scene mutation.
|
||||
- Existing `ADTBuilder.build_tile_water_scene` remains the only geometry/material source.
|
||||
- A non-empty built Water root is attached exactly once to the supplied tile root.
|
||||
- Optional editor ownership is assigned recursively only when explicitly enabled
|
||||
and an edited scene root is available.
|
||||
- The returned Node is borrowed and remains owned by the tile SceneTree subtree.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Requires: accepted water pipeline package on current master `e1b2886`
|
||||
- Blocks: further liquid finalization/material responsibility extraction
|
||||
- External state: none; verification uses synthetic liquid dictionaries
|
||||
|
||||
## Verification
|
||||
|
||||
- Commands: dedicated empty/invalid/build/attach/editor-owner/source/timing
|
||||
verifier; water pipeline, materials, shutdown, facade, internal-access,
|
||||
manifest, documentation and coordination gates
|
||||
- Fixtures: synthetic one-cell liquid mask/heights and temporary Node roots
|
||||
- Fidelity evidence: exact existing ADTBuilder output and two attachment paths preserved
|
||||
- Performance budget: 100 empty finalization attempts under 1 second
|
||||
|
||||
## Documentation deliverables
|
||||
|
||||
- Inline public API docs
|
||||
- Module specification with inputs/outputs, ownership and source map
|
||||
- Data-flow, lifecycle/state, main-thread sequence and dependency diagrams
|
||||
- Water-pipeline, world-renderer, module registry and RENDER status updates
|
||||
|
||||
## Simplicity and naming
|
||||
|
||||
- Important names introduced: `AdtWaterSceneFinalizer`
|
||||
- Simplest considered solution: one RefCounted with one explicit attach operation
|
||||
- Rejected complexity/abstractions: generic scene finalizer, signals, callbacks,
|
||||
builder interface, queue ownership or alternate liquid renderer
|
||||
- Unavoidable complexity and justification: editor-persisted generated subtrees
|
||||
require recursive owner assignment after SceneTree attachment
|
||||
- Measured optimization evidence: bounded synthetic no-water loop
|
||||
|
||||
## Status
|
||||
|
||||
- State: accepted
|
||||
- Done: stateless finalizer implemented; both loader water paths migrated;
|
||||
synthetic scene/owner regression and required documentation added
|
||||
- Next: continue liquid parsing/task execution extraction or another remaining M03 boundary
|
||||
- Blocked by:
|
||||
|
||||
<!-- OPENWC_INTEGRATION:ACCEPTED:M03-RND-ADT-WATER-SCENE-FINALIZER-001:874fd0f -->
|
||||
|
||||
## Handoff
|
||||
|
||||
- Commit: `235be3a`
|
||||
- Results: scene finalizer PASS (`cases=8`, `iterations=100`,
|
||||
`elapsed_ms=0.042`; initial `0.024ms`); synthetic liquid produced one
|
||||
four-vertex/six-index surface with tile attachment and recursive owner; water
|
||||
pipeline (`119.648ms`), WMO/M2/terrain/facade/scheduler/streaming/coordinate,
|
||||
materials, shutdown, internal-access `36` and 32 headless contract regressions
|
||||
PASS; documentation and coordination gates PASS; checkpoint dry-run PASS `7/7`.
|
||||
- Remaining risks: main-thread mesh/material construction remains synchronous
|
||||
behind one existing permit; the Dictionary builder boundary remains untyped;
|
||||
proprietary MH2O/MCLQ traversal/leak/p95/p99/original-client evidence is absent.
|
||||
A verifier launched concurrently with the initial cold editor import observed
|
||||
the pre-existing global-class cache race; serialized editor parse and shutdown
|
||||
rerun passed, with only expected missing private asset diagnostics.
|
||||
- Documentation updated: inline public API; new
|
||||
`docs/modules/adt-water-scene-finalizer.md`; water-pipeline spec, module registry,
|
||||
world renderer and `RENDER.md`; data-flow/state/sequence/dependency diagrams,
|
||||
ownership, main-thread effects, failure/recovery, migration and source map included.
|
||||
- Integration: merged as `874fd0f`; post-merge finalizer (`cases=8`,
|
||||
`iterations=100`, `elapsed_ms=0.025`), water pipeline (`98.398ms`), shutdown,
|
||||
renderer materials, facade, internal-access (`private_symbols=36`), `7/7`
|
||||
manifest, documentation and coordination gates passed.
|
||||
|
||||
<!-- OPENWC_HANDOFF:READY:M03-RND-ADT-WATER-SCENE-FINALIZER-001:235be3a -->
|
||||
@@ -0,0 +1,104 @@
|
||||
# M03-RND-M2-ANIMATED-INSTANCE-MATERIALIZER-001
|
||||
|
||||
<!-- OPENWC_CLAIM:M03-RND-M2-ANIMATED-INSTANCE-MATERIALIZER-001:sindo-main-codex:2026-07-20 -->
|
||||
|
||||
## Owner
|
||||
|
||||
- Agent ID: `sindo-main-codex`
|
||||
- Target: M03 Renderer Facade and Safe Extraction
|
||||
- Branch: `work/sindo-main-codex/m03-m2-animated-instance-materializer`
|
||||
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-m2-animated-instance-materializer`
|
||||
|
||||
## Outcome
|
||||
|
||||
Extract main-thread animated M2 instance duplication, render-property application,
|
||||
playback startup and batch attachment from `StreamingWorldLoader`.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Change build-job cursors, budgets, prototype selection or animation eligibility.
|
||||
- Change animation selection, phase, native animator behavior or diagnostic text.
|
||||
- Own Editor scene persistence, static MultiMesh materialization or tile cleanup.
|
||||
- Change visible transforms, visibility ranges, shadows or placement ordering.
|
||||
|
||||
## Paths
|
||||
|
||||
- Exclusive: `src/render/m2/m2_animated_instance_materializer.gd`,
|
||||
`src/tools/verify_m2_animated_instance_materializer.gd`,
|
||||
`docs/modules/m2-animated-instance-materializer.md`, this claim
|
||||
- Shared/hotspots: `src/scenes/streaming/streaming_world_loader.gd`, M2 animation
|
||||
playback/finalizer/build/facade/internal-access/manifest verifiers and specs,
|
||||
`docs/modules/world-renderer.md`, `docs/modules/README.md`, `RENDER.md`,
|
||||
`targets/03-renderer-facade.md`
|
||||
- Generated/ignored: `.godot`, native DLL, generated resources/caches and proprietary corpus
|
||||
|
||||
## Contracts and data
|
||||
|
||||
- Duplicate flags, names, transforms and input order remain unchanged.
|
||||
- Visibility end/margin and shadow mode are applied recursively before attachment.
|
||||
- Playback data copy/start delegates to the accepted playback controller contract.
|
||||
- The batch is attached only when at least one duplicate succeeds.
|
||||
- Loader retains diagnostic formatting/path normalization and Editor ownership.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Requires: accepted playback package on master `5b2d9f2`
|
||||
- Blocks: further animated M2 build orchestration extraction
|
||||
- External state: Godot Node duplication and SceneTree attachment remain main-thread APIs
|
||||
|
||||
## Verification
|
||||
|
||||
- Commands: dedicated duplication/order/render/playback/empty/source/timing verifier;
|
||||
M2 animated pipeline/finalizer/playback/prototype/build/shutdown/material/facade/
|
||||
internal-access/manifest regressions; docs/coordination/dry-run gates
|
||||
- Fixtures: synthetic prototype trees with geometry, AnimationPlayer and native animator
|
||||
- Fidelity evidence: exact transforms, flags, render settings, playback delegation and order
|
||||
- Performance budget: 1,000 synthetic instances under 1 second
|
||||
|
||||
## Documentation deliverables
|
||||
|
||||
- Inline API docs
|
||||
- Module spec with inputs/outputs, ownership and source map
|
||||
- Data-flow, lifecycle/state, sequence and dependency diagrams
|
||||
- Adjacent playback/finalizer/build/world-renderer/module-registry/RENDER updates
|
||||
|
||||
## Simplicity and naming
|
||||
|
||||
- Important name introduced: `M2AnimatedInstanceMaterializer`
|
||||
- Simplest solution: synchronous main-thread RefCounted returning batch diagnostics
|
||||
- Rejected complexity: signals, async queue, callbacks and generic scene factory
|
||||
- Unavoidable complexity: imported AnimationPlayer and native animator paths coexist
|
||||
- Measured optimization evidence: bounded synthetic batch materialization
|
||||
|
||||
## Status
|
||||
|
||||
- State: ready for integration
|
||||
- Done: materializer, loader adapter, verifier and required documentation
|
||||
- Next: integrator merge and post-merge acceptance
|
||||
- Blocked by:
|
||||
|
||||
## Handoff
|
||||
|
||||
- Commit: `456f3e2`
|
||||
- Results: materializer passes 12 duplication/order/render/playback/diagnostic/
|
||||
source cases and 1,000 instances in 9.267 ms; all 50 autonomous headless
|
||||
verifiers pass, while the proprietary ADT placement probe is unavailable
|
||||
without `data/extracted`; checkpoint dry-run passes 7/7; documentation and
|
||||
coordination gates pass; loader-private inventory remains 30.
|
||||
- Remaining risks: private asset traversal/visual comparison, descriptor
|
||||
pressure, leaks and p95/p99 remain unavailable; materialization is still
|
||||
synchronous main-thread work and no original-client comparison is claimed.
|
||||
- Documentation updated: inline API; `m2-animated-instance-materializer.md`
|
||||
with data-flow, lifecycle, sequence and dependency diagrams; playback,
|
||||
finalizer, build, world-renderer specs, module registry and `RENDER.md`.
|
||||
|
||||
<!-- OPENWC_HANDOFF:READY:M03-RND-M2-ANIMATED-INSTANCE-MATERIALIZER-001:456f3e2 -->
|
||||
|
||||
## Integration
|
||||
|
||||
- Merge commit: `c251985`
|
||||
- Post-merge results: materializer `cases=12 instances=1000 elapsed_ms=13.719`;
|
||||
playback/finalizer, shutdown, facade, internal-access `30`, checkpoint dry-run
|
||||
`7/7`, documentation and coordination gates passed.
|
||||
|
||||
<!-- OPENWC_INTEGRATION:ACCEPTED:M03-RND-M2-ANIMATED-INSTANCE-MATERIALIZER-001:c251985 -->
|
||||
@@ -0,0 +1,110 @@
|
||||
# M03-RND-M2-ANIMATED-SCENE-FINALIZER-001
|
||||
|
||||
<!-- OPENWC_CLAIM:M03-RND-M2-ANIMATED-SCENE-FINALIZER-001:sindo-main-codex:2026-07-20 -->
|
||||
|
||||
## Owner
|
||||
|
||||
- Agent ID: `sindo-main-codex`
|
||||
- Target: M03 Renderer Facade and Safe Extraction
|
||||
- Branch: `work/sindo-main-codex/m03-m2-animated-scene-finalizer`
|
||||
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-m2-animated-scene-finalizer`
|
||||
|
||||
## Outcome
|
||||
|
||||
Extract animated M2 PackedScene candidate instantiation, material override repair
|
||||
and AnimationPlayer validation from `StreamingWorldLoader` into an explicit
|
||||
main-thread finalizer.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Call ResourceLoader or interpret threaded-load statuses.
|
||||
- Choose animation eligibility, cache paths, material prototypes or permits.
|
||||
- Adopt/cache prototype Nodes or change static/native animation paths.
|
||||
- Change visible materials, animation selection, cache formats or profiles.
|
||||
|
||||
## Paths
|
||||
|
||||
- Exclusive: `src/render/m2/m2_animated_scene_finalizer.gd`,
|
||||
`src/tools/verify_m2_animated_scene_finalizer.gd`,
|
||||
`docs/modules/m2-animated-scene-finalizer.md`, this claim
|
||||
- Shared/hotspots: `src/scenes/streaming/streaming_world_loader.gd`, M2
|
||||
animation pipeline/prototype/material/build/shutdown/facade/internal-access/
|
||||
manifest verifiers and module specs, `docs/modules/world-renderer.md`,
|
||||
`docs/modules/README.md`, `RENDER.md`, `targets/03-renderer-facade.md`
|
||||
- Generated/ignored: `.godot`, native DLL, generated M2 resources, caches and
|
||||
proprietary renderer corpus
|
||||
|
||||
## Contracts and data
|
||||
|
||||
- Only a PackedScene whose instantiated root is Node3D becomes a candidate;
|
||||
unsupported or invalid roots return null and any created invalid root is freed.
|
||||
- Material repair preserves depth-first MeshInstance3D order, source-index
|
||||
clamping, per-surface override priority and first-material fallback.
|
||||
- Finalization accepts a candidate only when at least one descendant
|
||||
AnimationPlayer exists; rejected detached candidates are freed synchronously.
|
||||
- Accepted result transfers the exact Node3D and player count to the loader.
|
||||
- Loader retains ResourceLoader, material-prototype acquisition, permit,
|
||||
prototype adoption/static fallback and diagnostic-log decisions.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Requires: accepted animation load pipeline package on master `c20bc62`
|
||||
- Blocks: further animated M2 build/materialization decomposition
|
||||
- External state: imported GLB/PackedScene shape remains engine-owned input
|
||||
|
||||
## Verification
|
||||
|
||||
- Commands: dedicated invalid/ownership/traversal/material/finalization/source/
|
||||
timing verifier; animation pipeline/prototype/shutdown/material/M2 build/facade/
|
||||
internal-access/manifest and adjacent M2 regressions; docs/coordination/dry-run gates
|
||||
- Fixtures: synthetic PackedScenes, nested AnimationPlayers and ArrayMesh materials
|
||||
- Fidelity evidence: exact material mapping and accepted/rejected Node transitions
|
||||
- Performance budget: 10,000 depth-first candidate queries under 1 second
|
||||
|
||||
## Documentation deliverables
|
||||
|
||||
- Inline public API docs
|
||||
- Module specification with inputs/outputs, ownership and source map
|
||||
- Data-flow, lifecycle/state, sequence and dependency diagrams
|
||||
- Adjacent animation/prototype/world-renderer/module-registry/RENDER updates
|
||||
|
||||
## Simplicity and naming
|
||||
|
||||
- Important name introduced: `M2AnimatedSceneFinalizer`
|
||||
- Simplest solution: one stateless main-thread RefCounted over engine scene types
|
||||
- Rejected complexity: callbacks, generic scene pipeline, signals or cache ownership
|
||||
- Unavoidable complexity: material repair occurs between candidate creation and acceptance
|
||||
- Measured optimization evidence: bounded synthetic subtree traversal
|
||||
|
||||
## Status
|
||||
|
||||
- State: accepted
|
||||
- Done: finalizer, loader adapters, lifecycle/material verifier and required documentation
|
||||
- Next: continue animated M2 playback/instance materialization extraction
|
||||
- Blocked by:
|
||||
|
||||
## Handoff
|
||||
|
||||
- Commit: `1b450dc`
|
||||
- Results: animated scene finalizer passes 13 invalid/type/lifetime/traversal/
|
||||
material/transfer/source cases and 10,000 depth-eight queries in 40.126 ms;
|
||||
all 48 autonomous headless verifiers pass, while the proprietary ADT placement
|
||||
probe is unavailable without `data/extracted`; checkpoint dry-run passes 7/7;
|
||||
documentation and coordination gates pass; loader-private inventory remains 30.
|
||||
- Remaining risks: proprietary GLB traversal, material/animation comparison,
|
||||
descriptor pressure, leaks and p95/p99 remain unavailable; no paired original-
|
||||
client visual comparison is claimed. Positional material mapping is unchanged.
|
||||
- Documentation updated: inline API; `m2-animated-scene-finalizer.md` with
|
||||
data-flow, lifecycle, sequence and dependency diagrams; animation pipeline,
|
||||
prototype/world-renderer specs, module registry and `RENDER.md`.
|
||||
|
||||
<!-- OPENWC_HANDOFF:READY:M03-RND-M2-ANIMATED-SCENE-FINALIZER-001:1b450dc -->
|
||||
|
||||
## Integration
|
||||
|
||||
- Merge commit: `e7a7c67`
|
||||
- Post-merge results: finalizer `cases=13 iterations=10000
|
||||
elapsed_ms=40.314`; animation pipeline, prototype state, shutdown, materials,
|
||||
facade, internal-access `30`, manifest `7/7`, documentation and coordination passed.
|
||||
|
||||
<!-- OPENWC_INTEGRATION:ACCEPTED:M03-RND-M2-ANIMATED-SCENE-FINALIZER-001:e7a7c67 -->
|
||||
@@ -0,0 +1,112 @@
|
||||
# M03-RND-M2-ANIMATION-LOAD-PIPELINE-001 — M2 animation load pipeline state
|
||||
|
||||
<!-- OPENWC_CLAIM:M03-RND-M2-ANIMATION-LOAD-PIPELINE-001:sindo-main-codex:2026-07-20 -->
|
||||
|
||||
## Ownership
|
||||
|
||||
- Target: M03
|
||||
- Program: RND
|
||||
- Owner/Agent ID: sindo-main-codex
|
||||
- Branch: `work/sindo-main-codex/m03-m2-animation-load-pipeline`
|
||||
- Lease expires UTC: 2026-07-20
|
||||
- Integrator: M03 milestone integrator
|
||||
|
||||
## Outcome
|
||||
|
||||
Extract threaded animated-M2 pending request records and completion-order
|
||||
finalize FIFO from `StreamingWorldLoader` into an explicit state service.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Call ResourceLoader, select/validate GLB paths or interpret status values.
|
||||
- Instantiate/repair/cache animated Nodes or own prototype outcomes.
|
||||
- Change animation allow/deny/candidate policy, permits or visible behavior.
|
||||
- Add cancellation, persistence, retries, dependencies or cache-format changes.
|
||||
|
||||
## Paths
|
||||
|
||||
- Exclusive: `src/render/m2/m2_animation_load_pipeline_state.gd`,
|
||||
`src/tools/verify_m2_animation_load_pipeline_state.gd`,
|
||||
`docs/modules/m2-animation-load-pipeline-state.md`, this claim
|
||||
- Shared/hotspots: `src/scenes/streaming/streaming_world_loader.gd`, M2
|
||||
prototype/shutdown/material/build/facade/internal-access/manifest verifiers
|
||||
and module specs, `docs/modules/world-renderer.md`, `docs/modules/README.md`,
|
||||
`RENDER.md`, `targets/03-renderer-facade.md`
|
||||
- Generated/ignored: `.godot`, native DLL, generated M2 resources, caches and
|
||||
proprietary renderer corpus
|
||||
|
||||
## Contracts and data
|
||||
|
||||
- One non-empty normalized path has at most one pending cache path.
|
||||
- Pending snapshots preserve Dictionary insertion order and detach records.
|
||||
- Completing a known request moves one copied record with opaque status into a
|
||||
completion-order FIFO; unknown/duplicate completion is rejected.
|
||||
- Defensive discard removes pending state without creating a finalize record.
|
||||
- Total work remains pending plus finalize count at all three historical metrics.
|
||||
- Map reset and shutdown retain the same two clear sites; shutdown still drains
|
||||
ResourceLoader paths before clear.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Requires: accepted prototype cache package on master `20a64b5`
|
||||
- Blocks: animated M2 finalization/materialization service extraction
|
||||
- External state: ResourceLoader request statuses remain loader-owned
|
||||
|
||||
## Verification
|
||||
|
||||
- Commands: dedicated validation/dedupe/order/transition/discard/snapshot/clear/
|
||||
source/timing verifier; prototype/shutdown/material/M2 build/facade/internal-
|
||||
access/manifest and adjacent M2 regressions; docs/coordination/dry-run gates
|
||||
- Fixtures: synthetic normalized/cache paths and opaque integer statuses
|
||||
- Fidelity evidence: request insertion, terminal FIFO, metrics and clear transitions
|
||||
- Performance budget: 100 cycles over 256 request completions under 1 second
|
||||
|
||||
## Documentation deliverables
|
||||
|
||||
- Inline public API docs
|
||||
- Module specification with inputs/outputs, ownership and source map
|
||||
- Data-flow, lifecycle/state, sequence and dependency diagrams
|
||||
- Adjacent M2/world-renderer/module-registry/RENDER updates
|
||||
|
||||
## Simplicity and naming
|
||||
|
||||
- Important name introduced: `M2AnimationLoadPipelineState`
|
||||
- Simplest solution: one main-thread RefCounted with pending map and finalize FIFO
|
||||
- Rejected complexity: generic Resource pipeline, signals, callbacks, typed
|
||||
ResourceLoader enum wrapper, Node ownership or async abstraction
|
||||
- Unavoidable complexity: pending insertion order and terminal completion order differ
|
||||
- Measured optimization evidence: bounded synthetic transition loop
|
||||
|
||||
## Status
|
||||
|
||||
- State: accepted
|
||||
- Done: state, loader adapters, lifecycle verifier and required documentation
|
||||
- Next: continue animated M2 finalization/materialization extraction
|
||||
- Blocked by:
|
||||
|
||||
## Handoff
|
||||
|
||||
- Commit: `b8e0f11`
|
||||
- Results: animation pipeline passes 11 validation/dedupe/order/transition/FIFO/
|
||||
discard/snapshot/source cases and 100-by-256 cycles in 69.798 ms; all 47
|
||||
autonomous headless verifiers pass, while the proprietary ADT placement probe
|
||||
is unavailable without `data/extracted`; checkpoint dry-run passes 7/7;
|
||||
documentation and coordination gates pass; loader-private inventory decreases
|
||||
from 31 to 30.
|
||||
- Remaining risks: successful animated-GLB traversal, descriptor pressure,
|
||||
leaks, animation fidelity and p95/p99 require proprietary data; no paired
|
||||
original-client visual comparison is claimed.
|
||||
- Documentation updated: inline API; `m2-animation-load-pipeline-state.md` with
|
||||
data-flow, lifecycle, sequence and dependency diagrams; prototype/world-renderer
|
||||
specs, module registry and `RENDER.md`.
|
||||
|
||||
<!-- OPENWC_HANDOFF:READY:M03-RND-M2-ANIMATION-LOAD-PIPELINE-001:b8e0f11 -->
|
||||
|
||||
## Integration
|
||||
|
||||
- Merge commit: `3dccd3e`
|
||||
- Post-merge results: animation pipeline `cases=11 iterations=100
|
||||
elapsed_ms=70.309`; prototype state, shutdown, materials, facade,
|
||||
internal-access `30`, manifest `7/7`, documentation and coordination gates passed.
|
||||
|
||||
<!-- OPENWC_INTEGRATION:ACCEPTED:M03-RND-M2-ANIMATION-LOAD-PIPELINE-001:3dccd3e -->
|
||||
@@ -0,0 +1,110 @@
|
||||
# M03-RND-M2-ANIMATION-PLAYBACK-001
|
||||
|
||||
<!-- OPENWC_CLAIM:M03-RND-M2-ANIMATION-PLAYBACK-001:sindo-main-codex:2026-07-20 -->
|
||||
|
||||
## Owner
|
||||
|
||||
- Agent ID: `sindo-main-codex`
|
||||
- Target: M03 Renderer Facade and Safe Extraction
|
||||
- Branch: `work/sindo-main-codex/m03-m2-animation-playback`
|
||||
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-m2-animation-playback`
|
||||
|
||||
## Outcome
|
||||
|
||||
Extract deterministic animated M2 playback selection/phase, AnimationPlayer
|
||||
mutation and native animator data/start behavior from `StreamingWorldLoader`.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Duplicate or attach animated instances/batch roots.
|
||||
- Own visibility, shadow, Editor owner or build-job cursor rules.
|
||||
- Load/finalize/cache scenes or change animation eligibility.
|
||||
- Change candidate priority, phase formula, logging text or visible playback.
|
||||
|
||||
## Paths
|
||||
|
||||
- Exclusive: `src/render/m2/m2_animation_playback_controller.gd`,
|
||||
`src/tools/verify_m2_animation_playback_controller.gd`,
|
||||
`docs/modules/m2-animation-playback-controller.md`, this claim
|
||||
- Shared/hotspots: `src/scenes/streaming/streaming_world_loader.gd`, M2 animated
|
||||
finalizer/build/prototype/material/facade/internal-access/manifest verifiers and
|
||||
specs, `docs/modules/world-renderer.md`, `docs/modules/README.md`, `RENDER.md`,
|
||||
`targets/03-renderer-facade.md`
|
||||
- Generated/ignored: `.godot`, native DLL, generated resources/caches and proprietary corpus
|
||||
|
||||
## Contracts and data
|
||||
|
||||
- Phase remains `abs(hash("path:index")) % 1000 / 1000.0`.
|
||||
- Exact animation-name priority remains ordinary Stand/Idle/Run/Walk, fish
|
||||
Run/Walk/Swim/Stand/Idle/Death, birds Run/Walk/Swim/Stand/Idle, then
|
||||
case-insensitive substring fallback and first-name fallback.
|
||||
- Every AnimationPlayer animation becomes linear-looping before selected play;
|
||||
positive-length selection seeks to deterministic phase.
|
||||
- Native animators match exact script identity in depth-first order, copy the
|
||||
same five fields, call prepare_runtime when available and set phase.
|
||||
- Runtime debug state is sampled only when existing debug mode requests it;
|
||||
loader retains final log formatting/path normalization.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Requires: accepted animated scene finalizer package on master `c0a7bfb`
|
||||
- Blocks: animated M2 instance materializer extraction
|
||||
- External state: AnimationPlayer and M2NativeAnimator APIs remain engine/scene contracts
|
||||
|
||||
## Verification
|
||||
|
||||
- Commands: dedicated phase/selection/loop/play/seek/native-copy/start/diagnostic/
|
||||
source/timing verifier; animated finalizer/pipeline/prototype/build/shutdown/
|
||||
material/facade/internal-access/manifest regressions; docs/coordination/dry-run gates
|
||||
- Fixtures: synthetic AnimationPlayers and real M2NativeAnimator nodes
|
||||
- Fidelity evidence: exact priority, phase and mutation/reference contracts
|
||||
- Performance budget: 20,000 selection/phase operations under 1 second
|
||||
|
||||
## Documentation deliverables
|
||||
|
||||
- Inline API docs
|
||||
- Module spec with inputs/outputs, ownership and source map
|
||||
- Data-flow, lifecycle/state, sequence and dependency diagrams
|
||||
- Adjacent finalizer/build/world-renderer/module-registry/RENDER updates
|
||||
|
||||
## Simplicity and naming
|
||||
|
||||
- Important name introduced: `M2AnimationPlaybackController`
|
||||
- Simplest solution: stateless main-thread RefCounted over supplied Nodes/players
|
||||
- Rejected complexity: signals, playback state machine, generic animation framework
|
||||
- Unavoidable complexity: native and imported AnimationPlayer paths coexist
|
||||
- Measured optimization evidence: bounded selection/phase loop
|
||||
|
||||
## Status
|
||||
|
||||
- State: accepted
|
||||
- Done: playback controller, loader adapters, verifier and required documentation
|
||||
- Next: extract animated M2 instance/batch materialization
|
||||
- Blocked by:
|
||||
|
||||
## Handoff
|
||||
|
||||
- Commit: `9b73571`
|
||||
- Results: playback controller passes 15 phase/selection/loop/play/seek/native-
|
||||
copy/start/diagnostic/source cases and 20,000 phase-selection pairs in
|
||||
23.415 ms; all 49 autonomous headless verifiers pass, while the proprietary
|
||||
ADT placement probe is unavailable without `data/extracted`; checkpoint
|
||||
dry-run passes 7/7; documentation and coordination gates pass; loader-private
|
||||
inventory remains 30.
|
||||
- Remaining risks: private animation-name/timing/native visual comparison,
|
||||
descriptor pressure, leaks and p95/p99 remain unavailable; default selection
|
||||
is still heuristic and no paired original-client comparison is claimed.
|
||||
- Documentation updated: inline API; `m2-animation-playback-controller.md` with
|
||||
data-flow, lifecycle, sequence and dependency diagrams; finalizer/build/world-
|
||||
renderer specs, module registry and `RENDER.md`.
|
||||
|
||||
<!-- OPENWC_HANDOFF:READY:M03-RND-M2-ANIMATION-PLAYBACK-001:9b73571 -->
|
||||
|
||||
## Integration
|
||||
|
||||
- Merge commit: `c0fc191`
|
||||
- Post-merge results: playback `cases=15 iterations=20000 elapsed_ms=23.008`;
|
||||
animated finalizer/pipeline, prototype, build, shutdown, materials, facade,
|
||||
internal-access `30`, manifest `7/7`, documentation and coordination passed.
|
||||
|
||||
<!-- OPENWC_INTEGRATION:ACCEPTED:M03-RND-M2-ANIMATION-PLAYBACK-001:c0fc191 -->
|
||||
@@ -0,0 +1,84 @@
|
||||
# M03-RND-M2-ANIMATION-RESOURCE-FINALIZER-001
|
||||
|
||||
<!-- OPENWC_CLAIM:M03-RND-M2-ANIMATION-RESOURCE-FINALIZER-001:sindo-main-codex:2026-07-20 -->
|
||||
|
||||
## Owner
|
||||
|
||||
- Agent ID: `sindo-main-codex`
|
||||
- Target: M03 Renderer Facade and Safe Extraction
|
||||
- Branch: `work/sindo-main-codex/m03-m2-animation-resource-finalizer`
|
||||
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-m2-animation-resource-finalizer`
|
||||
|
||||
## Outcome
|
||||
|
||||
Move cached animated M2 ResourceLoader status polling, terminal Resource load,
|
||||
candidate finalization, prototype/static-only adoption and success diagnostics
|
||||
from the loader into a dedicated two-phase finalization service.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Change cached animation eligibility, request admission or native-first order.
|
||||
- Move material-prototype lookup, permit ownership, playback or materialization.
|
||||
- Change ResourceLoader status order, cache formats, profiles or visible output.
|
||||
- Generalize static and animated ResourceLoader paths behind callbacks/frameworks.
|
||||
|
||||
## Paths
|
||||
|
||||
- Exclusive: `src/render/m2/m2_animation_resource_finalizer.gd`,
|
||||
`src/tools/verify_m2_animation_resource_finalizer.gd`,
|
||||
`docs/modules/m2-animation-resource-finalizer.md`, this claim
|
||||
- Shared: loader, animation pipeline/scene finalizer/prototype cache/cached
|
||||
observer/world-renderer specs and verifiers, module registry, `RENDER.md`, M03 Evidence
|
||||
|
||||
## Contracts and data
|
||||
|
||||
- Pending request snapshots are polled in existing Dictionary insertion order.
|
||||
- Empty Resource paths are discarded and marked static-only immediately.
|
||||
- Only LOADED/FAILED statuses move into completion-order finalize FIFO.
|
||||
- One permit processes one popped terminal record, including skipped/stale records.
|
||||
- Existing prototype/static-only state suppresses terminal Resource loading.
|
||||
- LOADED Resource is instantiated before material-prototype lookup is requested.
|
||||
- Candidate repair/finalize/adoption and exact `M2_ANIM_CACHE` debug fields remain.
|
||||
- Rejected candidates are released by the existing animated scene finalizer.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Requires: accepted animation pipeline, scene finalizer, cached/native observers
|
||||
- Blocks: remaining loader-owned M2 material-prototype lookup and action execution
|
||||
|
||||
## Verification
|
||||
|
||||
- Synthetic polling/status/FIFO/skip/load/candidate/repair/adoption/log/source and
|
||||
timing contracts; adjacent M2/renderer gates; full headless suite and checkpoint dry-run.
|
||||
|
||||
## Documentation deliverables
|
||||
|
||||
- Inline API docs; module API/I/O/ownership; data-flow/state/sequence/dependency
|
||||
diagrams; adjacent renderer docs and M03 Evidence.
|
||||
|
||||
## Status
|
||||
|
||||
- State: accepted
|
||||
- Done: implementation, verification and documentation
|
||||
- Next: static M2 Resource terminal polling/finalize extraction
|
||||
- Blocked by:
|
||||
|
||||
## Handoff
|
||||
|
||||
- Commit: `e7cd967`
|
||||
- Results: finalizer `28` cases / `1,000` timing iterations; all `58/58`
|
||||
autonomous headless regressions; facade/internal-access/shutdown/manifest;
|
||||
documentation `45`; coordination; checkpoint dry-run `7/7`.
|
||||
- Remaining risks: private asset animation/visual/leak/p95/p99 evidence is absent;
|
||||
terminal Resource get/instantiation/repair stays synchronous; immediate prepared-
|
||||
candidate completion is a composition precondition; material lookup stays loader-owned.
|
||||
- Documentation updated: new finalizer API/I/O/data-flow/state/sequence/dependency/
|
||||
ownership spec; pipeline, scene finalizer, cached observer, prototype cache,
|
||||
world renderer, module registry, `RENDER.md` and M03 Evidence.
|
||||
|
||||
## Integration
|
||||
|
||||
- Merge: `06f6394`
|
||||
- Post-merge: finalizer, pipeline, scene finalizer, cached observer, prototype
|
||||
cache, snapshot, dispatch, shutdown, facade, internal-access `30`, manifest
|
||||
`7/7`, documentation `45`, coordination and checkpoint dry-run `7/7` passed.
|
||||
@@ -0,0 +1,97 @@
|
||||
# M03-RND-M2-BUILD-DISPATCH-PLANNER-001
|
||||
|
||||
<!-- OPENWC_CLAIM:M03-RND-M2-BUILD-DISPATCH-PLANNER-001:sindo-main-codex:2026-07-20 -->
|
||||
|
||||
## Owner
|
||||
|
||||
- Agent ID: `sindo-main-codex`
|
||||
- Target: M03 Renderer Facade and Safe Extraction
|
||||
- Branch: `work/sindo-main-codex/m03-m2-build-dispatch-planner`
|
||||
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-m2-build-dispatch-planner`
|
||||
|
||||
## Outcome
|
||||
|
||||
Extract the pure M2 build-step dispatch decision from the loader while preserving
|
||||
animation-wait priority, static-resource retry, missing-model advancement and
|
||||
exact permit/cursor behavior.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Move ResourceLoader I/O, path lookup, cache state or retry ownership.
|
||||
- Create/free Nodes, Meshes, MultiMeshes or animated instances in the planner.
|
||||
- Change batch sizing, queue ordering, transforms, visuals or cache versions.
|
||||
- Add spatial-cell batching or new diagnostics/profile settings.
|
||||
|
||||
## Paths
|
||||
|
||||
- Exclusive: `src/render/m2/m2_build_dispatch_planner.gd`,
|
||||
`src/tools/verify_m2_build_dispatch_planner.gd`,
|
||||
`docs/modules/m2-build-dispatch-planner.md`, this claim
|
||||
- Shared/hotspots: `src/scenes/streaming/streaming_world_loader.gd`, M2 build/
|
||||
queue/materializer/cache verifiers and specs, `docs/modules/world-renderer.md`,
|
||||
`docs/modules/README.md`, `RENDER.md`, `targets/03-renderer-facade.md`
|
||||
- Generated/ignored: `.godot`, native DLL, generated resources/caches and proprietary corpus
|
||||
|
||||
## Contracts and data
|
||||
|
||||
- Pending animation request wins before empty/animated/static decisions.
|
||||
- Positive batch with animated prototype selects animated materialization.
|
||||
- Positive static batch selects prepared Mesh, waits when unresolved, and advances
|
||||
without materialization only when the model is terminally missing.
|
||||
- Zero/non-positive batch advances without materialization.
|
||||
- Planner returns a fresh action plan and owns no state or engine reference.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Requires: accepted typed M2 build queue on master `c74b90a`
|
||||
- Blocks: extracting stateful M2 resource readiness/dispatch orchestration
|
||||
- External state: cache lookups, requests and engine lifetime remain loader APIs
|
||||
|
||||
## Verification
|
||||
|
||||
- Commands: dedicated action-priority/matrix/fresh-result/source/timing verifier;
|
||||
M2 queue/planner/materializer/cache/prototype/shutdown/facade/internal-access/
|
||||
manifest regressions; documentation, coordination and checkpoint dry-run gates
|
||||
- Fixtures: scalar availability and terminal-state combinations only
|
||||
- Fidelity evidence: exact existing branch priority and retry/advance behavior
|
||||
- Performance budget: 20,000 pure dispatch plans under one second
|
||||
|
||||
## Documentation deliverables
|
||||
|
||||
- Inline API docs
|
||||
- Module spec with inputs/outputs, ownership and source map
|
||||
- Data-flow, lifecycle/state, sequence and dependency diagrams
|
||||
- Adjacent M2 build/world-renderer/module-registry/RENDER updates
|
||||
|
||||
## Simplicity and naming
|
||||
|
||||
- Important name: `M2BuildDispatchPlanner`
|
||||
- Simplest solution: one stateless RefCounted with explicit action constants
|
||||
- Rejected complexity: callbacks, signals, cache references and generic state machine
|
||||
- Unavoidable complexity: animation-pending priority precedes every other action
|
||||
- Measured optimization evidence: bounded synthetic decision loop
|
||||
|
||||
## Status
|
||||
|
||||
- State: integration accepted
|
||||
- Done: implementation, verification, documentation and worktree handoff
|
||||
- Next: extract typed M2 resource observation/request orchestration
|
||||
- Blocked by:
|
||||
|
||||
## Handoff
|
||||
|
||||
- Commit: `17f5cc0faa376c8ac84a7ac72d5b8c0366866980`
|
||||
- Results: dispatch planner `cases=10 iterations=20000 elapsed_ms=11.933`;
|
||||
all autonomous headless regressions `53/53`; internal access
|
||||
`private_symbols=30`; baseline manifest/dry-run `7/7`; documentation
|
||||
`module_specs=40`; coordination passed with 30 historical expired warnings.
|
||||
- Remaining risks: proprietary ADT visual/p95/p99 evidence is unavailable;
|
||||
resource observation/requests, action execution, permits and engine lifetime
|
||||
intentionally remain loader-owned.
|
||||
- Documentation updated: new `m2-build-dispatch-planner.md` with API/I/O,
|
||||
data-flow, lifecycle, sequence, dependency and ownership diagrams; batch/queue/
|
||||
world renderer specs, module registry, RENDER and M03 Evidence updated.
|
||||
- Integration: master merge `99a90ddfb3364d6672198887198602b84dd19e58`;
|
||||
post-merge dispatch planner `cases=10 iterations=20000 elapsed_ms=12.291`,
|
||||
twelve adjacent M2/resource/lifetime/facade/internal-access/manifest checks,
|
||||
documentation and coordination all passed; checkpoint dry-run retained `7/7` plans.
|
||||
@@ -0,0 +1,98 @@
|
||||
# M03-RND-M2-BUILD-QUEUE-001
|
||||
|
||||
<!-- OPENWC_CLAIM:M03-RND-M2-BUILD-QUEUE-001:sindo-main-codex:2026-07-20 -->
|
||||
|
||||
## Owner
|
||||
|
||||
- Agent ID: `sindo-main-codex`
|
||||
- Target: M03 Renderer Facade and Safe Extraction
|
||||
- Branch: `work/sindo-main-codex/m03-m2-build-queue`
|
||||
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-m2-build-queue`
|
||||
|
||||
## Outcome
|
||||
|
||||
Replace loader-owned raw M2 build-job Dictionary/FIFO fields with typed job and
|
||||
queue state while preserving cursor, rotation, stale-key and lifetime behavior.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Move tile eligibility, resource readiness/retry or scheduler policy.
|
||||
- Create/free Nodes, Meshes, MultiMeshes or animated instances inside queue state.
|
||||
- Change grouping, batch planning, materialization, transform order or visuals.
|
||||
- Change M2 cache versions, diagnostics text or Editor persistence.
|
||||
|
||||
## Paths
|
||||
|
||||
- Exclusive: `src/render/m2/m2_build_job.gd`,
|
||||
`src/render/m2/m2_build_queue.gd`,
|
||||
`src/tools/verify_m2_build_queue.gd`,
|
||||
`docs/modules/m2-build-queue.md`, this claim
|
||||
- Shared/hotspots: `src/scenes/streaming/streaming_world_loader.gd`, M2 build/
|
||||
materializer/cache/shutdown/facade/internal-access verifiers and specs,
|
||||
`docs/modules/world-renderer.md`, `docs/modules/README.md`, `RENDER.md`,
|
||||
`targets/03-renderer-facade.md`
|
||||
- Generated/ignored: `.godot`, native DLL, generated resources/caches and proprietary corpus
|
||||
|
||||
## Contracts and data
|
||||
|
||||
- A job retains tile key, root, exact groups Dictionary, insertion-order
|
||||
`groups.keys()` snapshot, group index, transform offset and batch serial.
|
||||
- FIFO keeps duplicate/stale keys; front rotation moves exactly one key to tail.
|
||||
- Job erase does not erase FIFO entries, preserving external-cancel stale cleanup.
|
||||
- Progress adopts group index, transform offset and serial atomically.
|
||||
- Queue release never frees engine objects; loader retains finish/cancel cleanup.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Requires: accepted static materializer package on master `81e33a9`
|
||||
- Blocks: extracting remaining M2 build/resource orchestration
|
||||
- External state: Node validity and SceneTree destruction remain loader APIs
|
||||
|
||||
## Verification
|
||||
|
||||
- Commands: dedicated invalid/FIFO/duplicate/stale/rotation/cursor/lifetime/
|
||||
diagnostics/source/timing verifier; M2 planner/materializer/cache/prototype/
|
||||
shutdown/facade/internal-access/manifest regressions; docs/coordination/dry-run gates
|
||||
- Fixtures: synthetic Node3D roots and ordered transform-group Dictionaries
|
||||
- Fidelity evidence: exact existing keyed state, FIFO and cursor transitions
|
||||
- Performance budget: 100 cycles of 256 enqueue/rotate/erase operations under one second
|
||||
|
||||
## Documentation deliverables
|
||||
|
||||
- Inline API docs
|
||||
- Module spec with inputs/outputs, ownership and source map
|
||||
- Data-flow, lifecycle/state, sequence and dependency diagrams
|
||||
- Adjacent build/world-renderer/module-registry/RENDER updates
|
||||
|
||||
## Simplicity and naming
|
||||
|
||||
- Important names: `M2BuildJob`, `M2BuildQueue`
|
||||
- Simplest solution: two small RefCounted types mirroring accepted WMO queue pattern
|
||||
- Rejected complexity: signals, generic queue base, callbacks and engine destruction
|
||||
- Unavoidable complexity: keyed jobs and FIFO keys intentionally permit stale entries
|
||||
- Measured optimization evidence: bounded synthetic lifecycle loop
|
||||
|
||||
## Status
|
||||
|
||||
- State: integration accepted
|
||||
- Done: implementation, verification, documentation and worktree handoff
|
||||
- Next: extract the next unclaimed M2 resource-dispatch seam
|
||||
- Blocked by:
|
||||
|
||||
## Handoff
|
||||
|
||||
- Commit: `4a8338f2f0503c2ba31c495e00a696d359518c06`
|
||||
- Results: cold editor parse `exit=0`; M2 build queue `cases=13 iterations=100
|
||||
elapsed_ms=66.247`; all autonomous headless regressions `52/52`; internal
|
||||
access `private_symbols=30`; baseline manifest/dry-run `7/7`; documentation
|
||||
`module_specs=39`; coordination passed with 30 historical expired warnings.
|
||||
- Remaining risks: proprietary ADT visual/p95/p99 evidence is unavailable;
|
||||
resource readiness/dispatch, permits, materializer choice and root destruction
|
||||
intentionally remain loader-owned.
|
||||
- Documentation updated: new `m2-build-queue.md` with API/I/O, data-flow,
|
||||
lifecycle, sequence, dependency and ownership diagrams; world renderer,
|
||||
batch planner, module registry, RENDER and M03 Evidence updated.
|
||||
- Integration: master merge `1dc013e5c2597d5e6f26f44807f5f0a943bf1517`;
|
||||
post-merge queue `cases=13 iterations=100 elapsed_ms=65.998`, ten adjacent
|
||||
M2/lifetime/facade/internal-access/manifest checks, documentation and
|
||||
coordination all passed; checkpoint dry-run retained `7/7` plans.
|
||||
@@ -0,0 +1,98 @@
|
||||
# M03-RND-M2-BUILD-RESOURCE-SNAPSHOT-001
|
||||
|
||||
<!-- OPENWC_CLAIM:M03-RND-M2-BUILD-RESOURCE-SNAPSHOT-001:sindo-main-codex:2026-07-20 -->
|
||||
|
||||
## Owner
|
||||
|
||||
- Agent ID: `sindo-main-codex`
|
||||
- Target: M03 Renderer Facade and Safe Extraction
|
||||
- Branch: `work/sindo-main-codex/m03-m2-build-resource-snapshot`
|
||||
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-m2-build-resource-snapshot`
|
||||
|
||||
## Outcome
|
||||
|
||||
Replace loose per-step M2 animated/static resource variables with a typed
|
||||
observation snapshot consumed by the dispatch planner and materializer adapter.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Move ResourceLoader I/O, cache lookup/request execution or retry ownership.
|
||||
- Own or free prototype Nodes or Mesh Resources.
|
||||
- Change dispatch priority, batch sizing, queue ordering, permits or visuals.
|
||||
- Change M2 cache paths, formats, allowlists or native animation selection.
|
||||
|
||||
## Paths
|
||||
|
||||
- Exclusive: `src/render/m2/m2_build_resource_snapshot.gd`,
|
||||
`src/tools/verify_m2_build_resource_snapshot.gd`,
|
||||
`docs/modules/m2-build-resource-snapshot.md`, this claim
|
||||
- Shared/hotspots: `src/render/m2/m2_build_dispatch_planner.gd`, its verifier/spec,
|
||||
`src/scenes/streaming/streaming_world_loader.gd`, M2 queue/materializer/cache
|
||||
specs and tests, `docs/modules/world-renderer.md`, `docs/modules/README.md`,
|
||||
`RENDER.md`, `targets/03-renderer-facade.md`
|
||||
- Generated/ignored: `.godot`, native DLL, generated resources/caches and proprietary corpus
|
||||
|
||||
## Contracts and data
|
||||
|
||||
- Snapshot retains normalized path, optional animated prototype, animation
|
||||
request state, optional static Mesh and terminal missing-model state.
|
||||
- Static observation is adopted only after animation is not pending, the batch
|
||||
is positive and no animated prototype exists.
|
||||
- Accessors borrow exact engine references without transferring/freeing ownership.
|
||||
- Diagnostics expose only path and scalar availability flags.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Requires: accepted M2 dispatch planner on master `b113db0`
|
||||
- Blocks: extracting M2 resource observation/request service ownership
|
||||
- External state: prototype/cache/pipeline/resource lifetime remains loader-owned
|
||||
|
||||
## Verification
|
||||
|
||||
- Commands: dedicated identity/adoption/diagnostic/lifetime/source/timing verifier;
|
||||
dispatch/queue/batch/materializer/cache/pipeline/shutdown/facade/internal-access/
|
||||
manifest regressions; documentation, coordination and checkpoint dry-run gates
|
||||
- Fixtures: synthetic Node3D prototype and ArrayMesh resources
|
||||
- Fidelity evidence: exact resource presence/pending/missing values and branch order
|
||||
- Performance budget: 20,000 snapshot construct/adopt/read cycles under one second
|
||||
|
||||
## Documentation deliverables
|
||||
|
||||
- Inline API docs
|
||||
- Module spec with inputs/outputs, ownership and source map
|
||||
- Data-flow, lifecycle/state, sequence and dependency diagrams
|
||||
- Adjacent dispatch/queue/world-renderer/module-registry/RENDER updates
|
||||
|
||||
## Simplicity and naming
|
||||
|
||||
- Important name: `M2BuildResourceSnapshot`
|
||||
- Simplest solution: one small RefCounted value holder with explicit accessors
|
||||
- Rejected complexity: generic resource union, signals, callbacks and cache ownership
|
||||
- Unavoidable complexity: two-phase animation then optional static observation
|
||||
- Measured optimization evidence: bounded synthetic lifecycle loop
|
||||
|
||||
## Status
|
||||
|
||||
- State: integration accepted
|
||||
- Done: implementation, verification, documentation and worktree handoff
|
||||
- Next: extract the M2 resource observation/request producer service
|
||||
- Blocked by:
|
||||
|
||||
## Handoff
|
||||
|
||||
- Commit: `ec1b90f1e44037572e3e5052e6dedc016c29859a`
|
||||
- Results: resource snapshot `cases=12 iterations=20000 elapsed_ms=42.995`;
|
||||
dispatch planner remained green; all autonomous headless regressions `54/54`;
|
||||
internal access `private_symbols=30`; baseline manifest/dry-run `7/7`;
|
||||
documentation `module_specs=41`; coordination passed with 30 historical warnings.
|
||||
- Remaining risks: proprietary ADT visual/p95/p99 evidence is unavailable;
|
||||
resource lookup/request execution, action execution, permits and engine lifetime
|
||||
intentionally remain loader-owned.
|
||||
- Documentation updated: new `m2-build-resource-snapshot.md` with API/I/O,
|
||||
data-flow, lifecycle, sequence, dependency and ownership diagrams; dispatch/
|
||||
queue/world renderer specs, module registry, RENDER and M03 Evidence updated.
|
||||
- Integration: master merge `4354834c50ae426640c721041af67d5265e82205`;
|
||||
post-merge snapshot `cases=12 iterations=20000 elapsed_ms=42.211`, dispatch
|
||||
planner and twelve adjacent resource/materializer/lifetime/facade/internal-
|
||||
access/manifest checks, documentation and coordination all passed; checkpoint
|
||||
dry-run retained `7/7` plans.
|
||||
@@ -0,0 +1,91 @@
|
||||
# M03-RND-M2-CACHED-ANIMATION-RESOURCE-OBSERVER-001
|
||||
|
||||
<!-- OPENWC_CLAIM:M03-RND-M2-CACHED-ANIMATION-RESOURCE-OBSERVER-001:sindo-main-codex:2026-07-20 -->
|
||||
|
||||
## Owner
|
||||
|
||||
- Agent ID: `sindo-main-codex`
|
||||
- Target: M03 Renderer Facade and Safe Extraction
|
||||
- Branch: `work/sindo-main-codex/m03-m2-cached-animation-resource-observer`
|
||||
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-m2-cached-animation-resource-observer`
|
||||
|
||||
## Outcome
|
||||
|
||||
Move cached-GLB animated M2 lookup, allow/deny and primitive/schema safety
|
||||
selection, threaded request admission and static-only transition from the loader
|
||||
into a service that produces `M2BuildResourceSnapshot`.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Move native GryphonRoost raw-data build, debug logging or finalize drains.
|
||||
- Own/free prototype Nodes or change cache/pipeline ownership.
|
||||
- Change allow/deny defaults, primitive limits, candidate order or GLB rules.
|
||||
- Change batching, permits, cache formats, profiles or visible output.
|
||||
|
||||
## Paths
|
||||
|
||||
- Exclusive: `src/render/m2/m2_cached_animation_resource_observer.gd`,
|
||||
`src/tools/verify_m2_cached_animation_resource_observer.gd`,
|
||||
`docs/modules/m2-cached-animation-resource-observer.md`, this claim
|
||||
- Shared: loader, M2 snapshot/animation pipeline/prototype specs and verifiers,
|
||||
world renderer, module registry, `RENDER.md` and M03 Evidence
|
||||
|
||||
## Contracts and data
|
||||
|
||||
- Cached animated prototype wins; static-only and pending states do not request.
|
||||
- Empty allowlist rejects; denylist wins; matching is stripped/case-insensitive.
|
||||
- Historical nested/lowercase/basename `.glb` order remains exact.
|
||||
- Candidate requires animations, primitive limit and accepted empty or
|
||||
`pivot_prefix_v1` schema; other schemas are rejected.
|
||||
- Successful/`ERR_BUSY` request is remembered and reported pending; no usable
|
||||
candidate or request failure marks animation static.
|
||||
- Service produces a snapshot borrowing the exact prototype and frees nothing.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Requires: accepted static observer `5ebf4de`, snapshot and animation pipeline
|
||||
- Blocks: native animation observation and remaining finalize extraction
|
||||
|
||||
## Verification
|
||||
|
||||
- Synthetic cache/pipeline/snapshot lifecycle, policy/path/GLB metadata fixtures,
|
||||
source boundaries and timing; adjacent M2/renderer gates; full headless suite
|
||||
and checkpoint dry-run.
|
||||
|
||||
## Documentation deliverables
|
||||
|
||||
- Inline API docs; module API/I/O/ownership; data-flow/state/sequence/dependency
|
||||
diagrams; adjacent renderer docs and M03 Evidence.
|
||||
|
||||
## Status
|
||||
|
||||
- State: accepted
|
||||
- Done: implementation, integration and post-merge acceptance
|
||||
- Next: native M2 animation observation extraction
|
||||
- Blocked by:
|
||||
|
||||
## Handoff
|
||||
|
||||
- Commit: `df87619` (`render: extract cached M2 animation resource observer`)
|
||||
- Results: observer PASS (`33` cases, `20,000` iterations, `61.745ms`);
|
||||
full autonomous suite PASS (`56/56`); renderer checkpoint dry-run PASS (`7/7`);
|
||||
internal-access PASS (`30` private symbols); documentation PASS (`43` module
|
||||
specs); coordination PASS with `34` historical warnings.
|
||||
- Remaining risks: successful asynchronous admission is covered by source and
|
||||
existing pipeline regressions because an undrained unit request leaks; generated
|
||||
GLB metadata does not prove Godot import fidelity; private traversal, p95/p99,
|
||||
leak and paired visual evidence remain unavailable; native observation and
|
||||
terminal ResourceLoader polling/finalize remain loader-owned.
|
||||
- Documentation updated: new cached observer API/I/O/data-flow/state/sequence/
|
||||
dependency/ownership specification; M2 snapshot, batch/dispatch, animation
|
||||
pipeline/prototype, world-renderer, module registry, `RENDER.md` and M03 Evidence.
|
||||
|
||||
## Integration
|
||||
|
||||
- Merge: `f79e064` (`merge: cached M2 animation resource observer`)
|
||||
- Post-merge: observer `33` cases / `20,000` iterations / `61.514ms`;
|
||||
animation pipeline `11` / `66.049ms`; snapshot `12` / `39.947ms`;
|
||||
dispatch `10` / `17.915ms`; prototype cache `16` / `35.053ms`; shutdown,
|
||||
facade, internal-access `30`, manifest `7/7`, documentation `43` and
|
||||
coordination passed. Checkpoint dry-run completed `7/7`; expected private
|
||||
Azeroth/character assets remained unavailable.
|
||||
@@ -0,0 +1,109 @@
|
||||
# M03-RND-M2-MESH-LOAD-PIPELINE-001 — M2 mesh load pipeline state
|
||||
|
||||
<!-- OPENWC_CLAIM:M03-RND-M2-MESH-LOAD-PIPELINE-001:sindo-main-codex:2026-07-20 -->
|
||||
|
||||
## Ownership
|
||||
|
||||
- Target: M03
|
||||
- Program: RND
|
||||
- Owner/Agent ID: sindo-main-codex
|
||||
- Branch: `work/sindo-main-codex/m03-m2-mesh-load-pipeline`
|
||||
- Lease expires UTC: 2026-07-20
|
||||
- Integrator: M03 milestone integrator
|
||||
|
||||
## Outcome
|
||||
|
||||
Extract static M2 threaded ResourceLoader request records and their terminal
|
||||
finalize FIFO from `StreamingWorldLoader`, preserving request order, status
|
||||
transition, metrics and reset/shutdown behavior.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Call ResourceLoader or choose `.tscn`/`.glb` cache paths.
|
||||
- Own M2 Mesh, scene, missing or material-refresh caches.
|
||||
- Extract Mesh from Resources or rebuild/adopt runtime Meshes.
|
||||
- Consume `M2_MESH_FINALIZE` permits.
|
||||
- Change cache formats, animation loading or visible M2 behavior.
|
||||
|
||||
## Paths
|
||||
|
||||
- Exclusive: `src/render/m2/m2_mesh_load_pipeline_state.gd`,
|
||||
`src/tools/verify_m2_mesh_load_pipeline_state.gd`,
|
||||
`docs/modules/m2-mesh-load-pipeline-state.md`, this claim
|
||||
- Shared/hotspots: `src/scenes/streaming/streaming_world_loader.gd`,
|
||||
`docs/modules/world-renderer.md`, `docs/modules/README.md`, `RENDER.md`,
|
||||
`targets/03-renderer-facade.md`
|
||||
- Generated/ignored: `.godot`, native DLL, generated M2 resources, caches and
|
||||
proprietary renderer corpus
|
||||
|
||||
## Contracts and data
|
||||
|
||||
- One normalized path owns at most one pending request.
|
||||
- Pending records preserve Dictionary insertion order for polling snapshots.
|
||||
- Terminal completion removes the request and appends its record/status to FIFO.
|
||||
- Finalize pop remains oldest-first; duplicate/unknown transitions are rejected.
|
||||
- Total work count remains pending plus finalize records.
|
||||
- Clear drops bookkeeping only; loader drains ResourceLoader requests first.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Requires: accepted classifier package on current master `8ec13d6`
|
||||
- Blocks: further M2 Mesh cache/finalization service extraction
|
||||
- External state: none; verification uses synthetic paths and opaque statuses
|
||||
|
||||
## Verification
|
||||
|
||||
- Commands: dedicated validation/order/transition/FIFO/clear/source/timing
|
||||
verifier; M2 classifier/builder/grouper/transform/registry, materials, shutdown,
|
||||
facade, internal-access, manifest, documentation and coordination gates
|
||||
- Fixtures: synthetic normalized paths, resource paths and integer terminal statuses
|
||||
- Fidelity evidence: exact request/finalize transitions and metric/reset sites preserved
|
||||
- Performance budget: 100 cycles over 256 request/completion/pop records under 1 second
|
||||
|
||||
## Documentation deliverables
|
||||
|
||||
- Inline public API docs
|
||||
- Module specification with inputs/outputs, ownership and source map
|
||||
- Data-flow, lifecycle/state, sequence and dependency diagrams
|
||||
- World-renderer, module registry and RENDER status updates
|
||||
|
||||
## Simplicity and naming
|
||||
|
||||
- Important names introduced: `M2MeshLoadPipelineState`
|
||||
- Simplest considered solution: one RefCounted with request map and finalize FIFO
|
||||
- Rejected complexity/abstractions: generic ResourceLoader queue, signals,
|
||||
callbacks, Mesh cache ownership or animation/static pipeline merger
|
||||
- Unavoidable complexity and justification: polling and budgeted finalization are
|
||||
distinct lifecycle stages across frames
|
||||
- Measured optimization evidence: bounded synthetic transition loop
|
||||
|
||||
## Status
|
||||
|
||||
- State: accepted
|
||||
- Done: state holder, loader adapters, contract verifier and required documentation
|
||||
- Next: further M2 Mesh cache/finalizer extraction
|
||||
- Blocked by:
|
||||
|
||||
## Handoff
|
||||
|
||||
- Commit: `82df7a5`
|
||||
- Results: 34 headless contract regressions pass; dedicated pipeline state
|
||||
verifier passes 100-by-256 transitions in 100.273 ms; documentation and
|
||||
coordination gates pass; checkpoint dry-run passes 7/7.
|
||||
- Remaining risks: no private asset traversal, leak/descriptor-pressure or
|
||||
original-client visual comparison; ResourceLoader I/O and Mesh cache/finalizer
|
||||
ownership remain in `StreamingWorldLoader` by design.
|
||||
- Documentation updated: inline API; `m2-mesh-load-pipeline-state.md` with
|
||||
data-flow, state, sequence and dependency diagrams; module registry,
|
||||
`world-renderer.md` and `RENDER.md`.
|
||||
|
||||
<!-- OPENWC_HANDOFF:READY:M03-RND-M2-MESH-LOAD-PIPELINE-001:82df7a5 -->
|
||||
|
||||
## Integration
|
||||
|
||||
- Merge commit: `83b3618`
|
||||
- Post-merge results: pipeline state `cases=11 iterations=100
|
||||
elapsed_ms=69.196`; classifier, materials, shutdown, facade, internal-access
|
||||
`34`, manifest `7/7`, documentation and coordination gates passed.
|
||||
|
||||
<!-- OPENWC_INTEGRATION:ACCEPTED:M03-RND-M2-MESH-LOAD-PIPELINE-001:83b3618 -->
|
||||
@@ -0,0 +1,108 @@
|
||||
# M03-RND-M2-MESH-RESOURCE-CACHE-001 — M2 mesh resource cache state
|
||||
|
||||
<!-- OPENWC_CLAIM:M03-RND-M2-MESH-RESOURCE-CACHE-001:sindo-main-codex:2026-07-20 -->
|
||||
|
||||
## Ownership
|
||||
|
||||
- Target: M03
|
||||
- Program: RND
|
||||
- Owner/Agent ID: sindo-main-codex
|
||||
- Branch: `work/sindo-main-codex/m03-m2-mesh-resource-cache`
|
||||
- Lease expires UTC: 2026-07-20
|
||||
- Integrator: M03 milestone integrator
|
||||
|
||||
## Outcome
|
||||
|
||||
Extract the normalized-path to static M2 Mesh resource cache from
|
||||
`StreamingWorldLoader`, preserving lookup, replacement, shutdown lifetime and
|
||||
all existing cache consumers.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Own the shared M2 missing-path cache or animated/prototype node caches.
|
||||
- Call ResourceLoader, extract Meshes from Resources or select cache paths.
|
||||
- Prepare, rebuild or materialize M2 Meshes.
|
||||
- Change cache formats, material refresh, animation or visible behavior.
|
||||
|
||||
## Paths
|
||||
|
||||
- Exclusive: `src/render/m2/m2_mesh_resource_cache_state.gd`,
|
||||
`src/tools/verify_m2_mesh_resource_cache_state.gd`,
|
||||
`docs/modules/m2-mesh-resource-cache-state.md`, this claim
|
||||
- Shared/hotspots: `src/scenes/streaming/streaming_world_loader.gd`,
|
||||
`src/tools/verify_m2_mesh_load_pipeline_state.gd`,
|
||||
`src/tools/verify_render_runtime_cache_shutdown.gd`,
|
||||
`docs/modules/world-renderer.md`, `docs/modules/README.md`, `RENDER.md`,
|
||||
`targets/03-renderer-facade.md`
|
||||
- Generated/ignored: `.godot`, native DLL, generated M2 resources, caches and
|
||||
proprietary renderer corpus
|
||||
|
||||
## Contracts and data
|
||||
|
||||
- Non-empty normalized M2 paths map to at most one Mesh resource reference.
|
||||
- Store replaces the previous reference for the same path.
|
||||
- Lookup returns the exact retained Mesh reference; unknown/empty paths return null.
|
||||
- Clear releases all cache references only at the existing final-shutdown site.
|
||||
- Shared missing/prototype/animated caches remain loader-owned and unchanged.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Requires: accepted mesh load pipeline package on master `ef782d1`
|
||||
- Blocks: isolated M2 Mesh resource finalizer extraction
|
||||
- External state: none; verification uses synthetic Mesh resources
|
||||
|
||||
## Verification
|
||||
|
||||
- Commands: dedicated validation/store/replace/lookup/clear/source/timing
|
||||
verifier; mesh pipeline/classifier/material/shutdown/M2 build/facade/internal-
|
||||
access/manifest, documentation and coordination gates
|
||||
- Fixtures: synthetic ArrayMesh resources and normalized paths
|
||||
- Fidelity evidence: exact cache call-site and shutdown lifetime preservation
|
||||
- Performance budget: 100 cycles over 256 store/lookup entries under 1 second
|
||||
|
||||
## Documentation deliverables
|
||||
|
||||
- Inline public API docs
|
||||
- Module specification with inputs/outputs, ownership and source map
|
||||
- Data-flow, lifecycle/state and dependency diagrams
|
||||
- World-renderer, module registry and RENDER status updates
|
||||
|
||||
## Simplicity and naming
|
||||
|
||||
- Important name introduced: `M2MeshResourceCacheState`
|
||||
- Simplest solution: one RefCounted with one typed-purpose Dictionary
|
||||
- Rejected complexity: generic resource cache, missing-state merger, eviction,
|
||||
signals, callbacks or materialization ownership
|
||||
- Unavoidable complexity: none beyond retaining engine Resource references
|
||||
- Measured optimization evidence: bounded synthetic store/lookup loop
|
||||
|
||||
## Status
|
||||
|
||||
- State: accepted
|
||||
- Done: cache state, loader/test adapters, contract verifier and required documentation
|
||||
- Next: isolated M2 Mesh resource finalizer extraction
|
||||
- Blocked by:
|
||||
|
||||
## Handoff
|
||||
|
||||
- Commit: `e990a65`
|
||||
- Results: dedicated cache verifier passes 100-by-256 store/lookups in
|
||||
38.974 ms; 35 headless renderer/coordinate regressions pass; checkpoint
|
||||
dry-run passes 7/7; documentation and coordination gates pass.
|
||||
- Remaining risks: no private M2 traversal, Mesh memory/leak/descriptor-pressure
|
||||
run or original-client visual comparison; extraction/preparation, shared
|
||||
missing state and materialization remain loader-owned by design.
|
||||
- Documentation updated: inline API; `m2-mesh-resource-cache-state.md` with
|
||||
data-flow, lifecycle, sequence and dependency diagrams; pipeline spec, module
|
||||
registry, `world-renderer.md` and `RENDER.md`.
|
||||
|
||||
<!-- OPENWC_HANDOFF:READY:M03-RND-M2-MESH-RESOURCE-CACHE-001:e990a65 -->
|
||||
|
||||
## Integration
|
||||
|
||||
- Merge commit: `3094561`
|
||||
- Post-merge results: cache state `cases=9 iterations=100
|
||||
elapsed_ms=38.686`; pipeline, classifier, materials, shutdown, facade,
|
||||
internal-access `34`, manifest `7/7`, documentation and coordination gates passed.
|
||||
|
||||
<!-- OPENWC_INTEGRATION:ACCEPTED:M03-RND-M2-MESH-RESOURCE-CACHE-001:3094561 -->
|
||||
@@ -0,0 +1,109 @@
|
||||
# M03-RND-M2-MESH-RESOURCE-EXTRACTOR-001 — M2 mesh resource extractor
|
||||
|
||||
<!-- OPENWC_CLAIM:M03-RND-M2-MESH-RESOURCE-EXTRACTOR-001:sindo-main-codex:2026-07-20 -->
|
||||
|
||||
## Ownership
|
||||
|
||||
- Target: M03
|
||||
- Program: RND
|
||||
- Owner/Agent ID: sindo-main-codex
|
||||
- Branch: `work/sindo-main-codex/m03-m2-mesh-resource-extractor`
|
||||
- Lease expires UTC: 2026-07-20
|
||||
- Integrator: M03 milestone integrator
|
||||
|
||||
## Outcome
|
||||
|
||||
Extract first-Mesh selection from loaded M2 Resources and Node subtrees,
|
||||
including temporary PackedScene instance cleanup, from `StreamingWorldLoader`.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Call ResourceLoader or select/request cache paths.
|
||||
- Refresh/rebuild materials or load raw M2 data.
|
||||
- Own the Mesh cache, missing state, prototypes or MultiMesh materialization.
|
||||
- Change traversal order, cache formats, profiles or visible behavior.
|
||||
|
||||
## Paths
|
||||
|
||||
- Exclusive: `src/render/m2/m2_mesh_resource_extractor.gd`,
|
||||
`src/tools/verify_m2_mesh_resource_extractor.gd`,
|
||||
`docs/modules/m2-mesh-resource-extractor.md`, this claim
|
||||
- Shared/hotspots: `src/scenes/streaming/streaming_world_loader.gd`,
|
||||
M2 pipeline/cache/material/shutdown verifiers, `docs/modules/world-renderer.md`,
|
||||
`docs/modules/README.md`, M2 module specs, `RENDER.md`,
|
||||
`targets/03-renderer-facade.md`
|
||||
- Generated/ignored: `.godot`, native DLL, generated M2 resources, caches and
|
||||
proprietary renderer corpus
|
||||
|
||||
## Contracts and data
|
||||
|
||||
- A direct Mesh Resource returns the same Mesh reference.
|
||||
- A PackedScene is instantiated once, searched depth-first in child order and
|
||||
its temporary root is freed before return.
|
||||
- A Node subtree search returns the first MeshInstance3D Mesh in historical
|
||||
depth-first preorder without changing the tree.
|
||||
- Null, unsupported Resources and subtrees without a Mesh return null.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Requires: accepted Mesh resource cache package on master `5e2afbc`
|
||||
- Blocks: isolated material refresh/rebuild finalizer extraction
|
||||
- External state: none; verification uses synthetic Mesh/Node/PackedScene fixtures
|
||||
|
||||
## Verification
|
||||
|
||||
- Commands: dedicated direct/PackedScene/subtree/order/lifetime/source/timing
|
||||
verifier; M2 pipeline/cache/classifier/material/shutdown/build/facade/internal-
|
||||
access/manifest, documentation and coordination gates
|
||||
- Fixtures: ArrayMesh, MeshInstance3D subtrees and synthetic PackedScene
|
||||
- Fidelity evidence: exact traversal order and temporary-node destruction
|
||||
- Performance budget: 10,000 synthetic subtree lookups under 1 second
|
||||
|
||||
## Documentation deliverables
|
||||
|
||||
- Inline public API docs
|
||||
- Module specification with inputs/outputs, ownership and source map
|
||||
- Data-flow, lifecycle/state, sequence and dependency diagrams
|
||||
- M2 cache/pipeline, world-renderer, registry and RENDER updates
|
||||
|
||||
## Simplicity and naming
|
||||
|
||||
- Important name introduced: `M2MeshResourceExtractor`
|
||||
- Simplest solution: one stateless RefCounted with two explicit extraction APIs
|
||||
- Rejected complexity: callback-driven finalizer, generic scene utility,
|
||||
cache ownership, signals or visitor framework
|
||||
- Unavoidable complexity: temporary PackedScene instance must be freed while
|
||||
its referenced Mesh remains borrowed
|
||||
- Measured optimization evidence: bounded synthetic depth-first traversal
|
||||
|
||||
## Status
|
||||
|
||||
- State: accepted
|
||||
- Done: extractor, three loader/test adapters, contract verifier and required documentation
|
||||
- Next: isolated M2 Mesh material preparation finalizer extraction
|
||||
- Blocked by:
|
||||
|
||||
## Handoff
|
||||
|
||||
- Commit: `7b0e1ea`
|
||||
- Results: extractor passes direct/PackedScene/subtree/order/lifetime contracts
|
||||
and 10,000 traversals in 39.243 ms; 36 headless renderer/coordinate
|
||||
regressions pass; checkpoint dry-run passes 7/7; documentation and
|
||||
coordination gates pass.
|
||||
- Remaining risks: synchronous PackedScene instantiation remains behind the
|
||||
existing permit; no private M2 corrupt-scene/traversal/leak/p95/p99 or
|
||||
original-client comparison; material preparation remains loader-owned.
|
||||
- Documentation updated: inline API; `m2-mesh-resource-extractor.md` with
|
||||
data-flow, lifecycle, sequence and dependency diagrams; cache/pipeline specs,
|
||||
module registry, `world-renderer.md` and `RENDER.md`.
|
||||
|
||||
<!-- OPENWC_HANDOFF:READY:M03-RND-M2-MESH-RESOURCE-EXTRACTOR-001:7b0e1ea -->
|
||||
|
||||
## Integration
|
||||
|
||||
- Merge commit: `fa85985`
|
||||
- Post-merge results: extractor `cases=10 iterations=10000
|
||||
elapsed_ms=38.172`; cache, pipeline, classifier, materials, shutdown, facade,
|
||||
internal-access `34`, manifest `7/7`, documentation and coordination gates passed.
|
||||
|
||||
<!-- OPENWC_INTEGRATION:ACCEPTED:M03-RND-M2-MESH-RESOURCE-EXTRACTOR-001:fa85985 -->
|
||||
@@ -0,0 +1,84 @@
|
||||
# M03-RND-M2-MESH-RESOURCE-FINALIZER-001
|
||||
|
||||
<!-- OPENWC_CLAIM:M03-RND-M2-MESH-RESOURCE-FINALIZER-001:sindo-main-codex:2026-07-20 -->
|
||||
|
||||
## Owner
|
||||
|
||||
- Agent ID: `sindo-main-codex`
|
||||
- Target: M03 Renderer Facade and Safe Extraction
|
||||
- Branch: `work/sindo-main-codex/m03-m2-mesh-resource-finalizer`
|
||||
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-m2-mesh-resource-finalizer`
|
||||
|
||||
## Outcome
|
||||
|
||||
Move static M2 ResourceLoader status polling, terminal Resource/Mesh extraction,
|
||||
runtime Mesh preparation, Mesh-cache adoption and missing outcome from the
|
||||
loader into one dedicated finalization service.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Change static cache path selection or threaded request admission.
|
||||
- Merge static and animated terminal services behind a generic callback layer.
|
||||
- Own permits, MultiMesh materialization, prototype Nodes or SceneTree roots.
|
||||
- Change material refresh version, rebuild rules, cache formats or visible output.
|
||||
|
||||
## Paths
|
||||
|
||||
- Exclusive: `src/render/m2/m2_mesh_resource_finalizer.gd`,
|
||||
`src/tools/verify_m2_mesh_resource_finalizer.gd`,
|
||||
`docs/modules/m2-mesh-resource-finalizer.md`, this claim
|
||||
- Shared: loader, Mesh pipeline/cache/extractor/runtime-finalizer/raw-repository/
|
||||
prototype specs and verifiers, world renderer, registry, `RENDER.md`, M03 Evidence
|
||||
|
||||
## Contracts and data
|
||||
|
||||
- Pending requests are polled in existing Dictionary insertion order.
|
||||
- Empty Resource paths are discarded and marked missing immediately.
|
||||
- Only LOADED/FAILED statuses enter completion-order finalize FIFO.
|
||||
- One permit pops exactly one terminal record, including skipped/cached records.
|
||||
- Existing cached Mesh suppresses terminal Resource retrieval.
|
||||
- Loaded Resource uses exact first-Mesh extraction and current refresh-version path.
|
||||
- Raw M2 data loads only when the extracted Mesh is stale.
|
||||
- Prepared Mesh is stored by existing replacement semantics; failures mark missing.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Requires: accepted Mesh pipeline/cache/extractor/runtime finalizer/raw repository
|
||||
- Blocks: remaining loader-owned M2 materialization and orchestration cleanup
|
||||
|
||||
## Verification
|
||||
|
||||
- Synthetic polling/FIFO/skip/load/extract/refresh/store/missing/source/timing
|
||||
contracts; adjacent M2/renderer gates; full headless suite and checkpoint dry-run.
|
||||
|
||||
## Documentation deliverables
|
||||
|
||||
- Inline API docs; module API/I/O/ownership; data-flow/state/sequence/dependency
|
||||
diagrams; adjacent renderer docs and M03 Evidence.
|
||||
|
||||
## Status
|
||||
|
||||
- State: accepted
|
||||
- Done: implementation, verification and documentation
|
||||
- Next: next unclaimed M03 renderer extraction package
|
||||
- Blocked by:
|
||||
|
||||
## Handoff
|
||||
|
||||
- Commit: `ddeb708` (`render: extract M2 Mesh resource finalizer`)
|
||||
- Results: finalizer PASS `cases=31 iterations=1000 elapsed_ms=0.767`;
|
||||
adjacent Mesh pipeline/cache/extractor/runtime-finalizer/raw-repository/
|
||||
prototype/shutdown checks passed; autonomous headless suite `59/59`;
|
||||
documentation `46`; coordination passed with `34` historical warnings;
|
||||
checkpoint dry-run `7/7`.
|
||||
- Remaining risks: terminal instantiation/raw parsing/rebuild remain synchronous
|
||||
main-thread work behind existing permits; no private-asset visual, leak,
|
||||
descriptor-pressure or p95/p99 evidence was added.
|
||||
- Documentation updated: new full module specification with API/I/O and
|
||||
data-flow/state/sequence/dependency diagrams; adjacent M2 module specs,
|
||||
world-renderer registry/spec, `RENDER.md` and M03 Evidence.
|
||||
- Merge: `34b7000` (`merge: M2 Mesh resource finalizer`)
|
||||
- Post-merge: finalizer PASS `cases=31 iterations=1000 elapsed_ms=0.826`;
|
||||
adjacent M2 pipeline/cache/extractor/runtime/repository/prototype/shutdown,
|
||||
facade, internal-access `30`, manifest `7/7`, documentation `46` and
|
||||
coordination gates passed.
|
||||
@@ -0,0 +1,84 @@
|
||||
# M03-RND-M2-NATIVE-ANIMATION-RESOURCE-OBSERVER-001
|
||||
|
||||
<!-- OPENWC_CLAIM:M03-RND-M2-NATIVE-ANIMATION-RESOURCE-OBSERVER-001:sindo-main-codex:2026-07-20 -->
|
||||
|
||||
## Owner
|
||||
|
||||
- Agent ID: `sindo-main-codex`
|
||||
- Target: M03 Renderer Facade and Safe Extraction
|
||||
- Branch: `work/sindo-main-codex/m03-m2-native-animation-resource-observer`
|
||||
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-m2-native-animation-resource-observer`
|
||||
|
||||
## Outcome
|
||||
|
||||
Move native GryphonRoost M2 animation candidate selection, synchronous raw read,
|
||||
native animated build, prototype/static-only adoption and debug diagnostics from
|
||||
the loader into a dedicated observer.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Change the exact native candidate predicate or add more animated models.
|
||||
- Move cached-GLB observation, ResourceLoader polling/finalize or playback.
|
||||
- Own attached instances, consume permits or change queue/batch behavior.
|
||||
- Change native parsing/building, cache formats, profiles or visible output.
|
||||
|
||||
## Paths
|
||||
|
||||
- Exclusive: `src/render/m2/m2_native_animation_resource_observer.gd`,
|
||||
`src/tools/verify_m2_native_animation_resource_observer.gd`,
|
||||
`docs/modules/m2-native-animation-resource-observer.md`, this claim
|
||||
- Shared: loader, M2 cached observer/snapshot/raw repository/prototype specs and
|
||||
verifiers, world renderer, module registry, `RENDER.md` and M03 Evidence
|
||||
|
||||
## Contracts and data
|
||||
|
||||
- Only case-insensitive paths containing `gryphonroost` are native candidates.
|
||||
- Cached animated prototype wins; static-only state suppresses repeated reads.
|
||||
- Empty raw data or animated surfaces marks animation static.
|
||||
- Builder result must be non-null with at least one child; rejection marks static.
|
||||
- Successful result is adopted through first-wins prototype cache semantics.
|
||||
- Existing `M2_NATIVE_ANIM_CACHE` fields and debug gating remain exact.
|
||||
- Observer attaches/frees no Node and starts no async work.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Requires: accepted cached observer `6f385ed`, raw repository and prototype state
|
||||
- Blocks: remaining ResourceLoader polling/finalize extraction
|
||||
|
||||
## Verification
|
||||
|
||||
- Synthetic candidate/cache/static/raw/build/adoption/lifetime/logging/source and
|
||||
timing contracts; adjacent M2/renderer gates; full headless suite and checkpoint
|
||||
dry-run.
|
||||
|
||||
## Documentation deliverables
|
||||
|
||||
- Inline API docs; module API/I/O/ownership; data-flow/state/sequence/dependency
|
||||
diagrams; adjacent renderer docs and M03 Evidence.
|
||||
|
||||
## Status
|
||||
|
||||
- State: accepted
|
||||
- Done: implementation, verification and documentation
|
||||
- Next: remaining cached ResourceLoader terminal polling/finalize extraction
|
||||
- Blocked by:
|
||||
|
||||
## Handoff
|
||||
|
||||
- Commit: `1cb0101`
|
||||
- Results: observer `33` cases / `20,000` timing iterations; all `57/57`
|
||||
autonomous headless regressions; facade/internal-access/shutdown/manifest;
|
||||
documentation `44`; coordination; checkpoint dry-run `7/7`.
|
||||
- Remaining risks: private asset animation/visual/leak/p95/p99 evidence is absent;
|
||||
native parse/build stays synchronous; historical childless rejected Node remains
|
||||
unfreed; cached ResourceLoader terminal polling/finalize stays loader-owned.
|
||||
- Documentation updated: new native observer API/I/O/data-flow/state/sequence/
|
||||
dependency/ownership spec; cached observer, snapshot, dispatch, repository,
|
||||
prototype cache, world renderer, module registry, `RENDER.md` and M03 Evidence.
|
||||
|
||||
## Integration
|
||||
|
||||
- Merge: `d37c799`
|
||||
- Post-merge: native/cached observers, repository, cache, snapshot, dispatch,
|
||||
shutdown, facade, internal-access `30`, manifest `7/7`, documentation `44`,
|
||||
coordination and checkpoint dry-run `7/7` passed on master.
|
||||
@@ -0,0 +1,111 @@
|
||||
# M03-RND-M2-PROTOTYPE-CACHE-001 — M2 prototype cache state
|
||||
|
||||
<!-- OPENWC_CLAIM:M03-RND-M2-PROTOTYPE-CACHE-001:sindo-main-codex:2026-07-20 -->
|
||||
|
||||
## Ownership
|
||||
|
||||
- Target: M03
|
||||
- Program: RND
|
||||
- Owner/Agent ID: sindo-main-codex
|
||||
- Branch: `work/sindo-main-codex/m03-m2-prototype-cache`
|
||||
- Lease expires UTC: 2026-07-20
|
||||
- Integrator: M03 milestone integrator
|
||||
|
||||
## Outcome
|
||||
|
||||
Extract static/animated detached M2 prototype references plus missing-model and
|
||||
static-animation negative outcomes from `StreamingWorldLoader` into one explicit
|
||||
shutdown-lifetime cache-state service.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Choose `.tscn/.glb` paths, call ResourceLoader/native loaders or build Nodes.
|
||||
- Own animation/Mesh request queues, Mesh resources, materials or MultiMeshes.
|
||||
- Change candidate/allowlist/denylist rules, fallback order or visible behavior.
|
||||
- Add persistence, eviction, async work, dependencies or cache-format changes.
|
||||
|
||||
## Paths
|
||||
|
||||
- Exclusive: `src/render/m2/m2_prototype_cache_state.gd`,
|
||||
`src/tools/verify_m2_prototype_cache_state.gd`,
|
||||
`docs/modules/m2-prototype-cache-state.md`, this claim
|
||||
- Shared/hotspots: `src/scenes/streaming/streaming_world_loader.gd`, runtime
|
||||
cache shutdown/M2 raw/finalizer/extractor/pipeline/material/animation verifiers
|
||||
and module specs, `docs/modules/world-renderer.md`, `docs/modules/README.md`,
|
||||
`RENDER.md`, `targets/03-renderer-facade.md`
|
||||
- Generated/ignored: `.godot`, native DLL, generated M2 resources, caches and
|
||||
proprietary renderer corpus
|
||||
|
||||
## Contracts and data
|
||||
|
||||
- Normalized non-empty paths key static and animated detached `Node3D` prototypes.
|
||||
- Missing-model and static-animation outcomes are independent path sets.
|
||||
- Exact cached Node reference is returned; first adoption for an occupied path wins.
|
||||
- Shutdown frees valid detached nodes synchronously, queues in-tree nodes for free,
|
||||
clears all four states and remains idempotent.
|
||||
- Map refresh retains the state exactly as before; only final shutdown clears it.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Requires: accepted raw model repository package on master `ca77d32`
|
||||
- Blocks: remaining M2 animation request/materialization extraction
|
||||
- External state: none; verification uses synthetic Node3D instances
|
||||
|
||||
## Verification
|
||||
|
||||
- Commands: dedicated admission/identity/independence/duplicate/free/source/timing
|
||||
verifier; runtime shutdown/raw/finalizer/extractor/cache/pipeline/material/
|
||||
animation/M2 build/facade/internal-access/manifest, docs and coordination gates
|
||||
- Fixtures: detached and in-tree synthetic Node3D prototypes
|
||||
- Fidelity evidence: all existing lookup/store/negative/clear caller transitions
|
||||
- Performance budget: 100 cycles over 256 path-state operations under 1 second
|
||||
|
||||
## Documentation deliverables
|
||||
|
||||
- Inline public API docs
|
||||
- Module specification with inputs/outputs, ownership and source map
|
||||
- Data-flow, lifecycle/state, sequence and dependency diagrams
|
||||
- Adjacent M2/world-renderer/module-registry/RENDER updates
|
||||
|
||||
## Simplicity and naming
|
||||
|
||||
- Important name introduced: `M2PrototypeCacheState`
|
||||
- Simplest solution: one main-thread RefCounted with two Node maps and two path sets
|
||||
- Rejected complexity: generic Node cache, signals, callbacks, eviction policy,
|
||||
weak references, async cleanup or dependency injection
|
||||
- Unavoidable complexity: detached Nodes require explicit free at final shutdown
|
||||
- Measured optimization evidence: bounded synthetic cache-cycle loop
|
||||
|
||||
## Status
|
||||
|
||||
- State: accepted
|
||||
- Done: cache state, loader/test adapters, lifecycle verifier and required documentation
|
||||
- Next: continue M2 animation request/materialization extraction
|
||||
- Blocked by:
|
||||
|
||||
## Handoff
|
||||
|
||||
- Commit: `cd664eb`
|
||||
- Results: prototype state passes 16 admission/identity/negative/lifecycle/source
|
||||
cases and 100-by-256 cycles in 50.266 ms; 46 autonomous headless verifiers
|
||||
pass, while the proprietary ADT placement probe is unavailable without
|
||||
`data/extracted`; checkpoint dry-run passes 7/7; documentation and
|
||||
coordination gates pass; loader-private inventory decreases from 34 to 31.
|
||||
- Remaining risks: strong prototypes remain unbounded until shutdown; successful
|
||||
asset traversal, descriptor pressure, leaks and p95/p99 require proprietary
|
||||
data; no original-client visual comparison is claimed.
|
||||
- Documentation updated: inline API; `m2-prototype-cache-state.md` with data-flow,
|
||||
lifecycle, sequence and dependency diagrams; pipeline/Mesh-cache/extractor/raw
|
||||
repository/world-renderer specs, module registry and `RENDER.md`.
|
||||
|
||||
<!-- OPENWC_HANDOFF:READY:M03-RND-M2-PROTOTYPE-CACHE-001:cd664eb -->
|
||||
|
||||
## Integration
|
||||
|
||||
- Merge commit: `1de50f9`
|
||||
- Post-merge results: prototype cache `cases=16 iterations=100
|
||||
elapsed_ms=37.978`; shutdown, raw repository, finalizer, extractor, Mesh cache,
|
||||
pipeline, materials, facade, internal-access `31`, manifest `7/7`,
|
||||
documentation and coordination gates passed.
|
||||
|
||||
<!-- OPENWC_INTEGRATION:ACCEPTED:M03-RND-M2-PROTOTYPE-CACHE-001:1de50f9 -->
|
||||
@@ -0,0 +1,109 @@
|
||||
# M03-RND-M2-RAW-MODEL-REPOSITORY-001 — raw M2 model repository
|
||||
|
||||
<!-- OPENWC_CLAIM:M03-RND-M2-RAW-MODEL-REPOSITORY-001:sindo-main-codex:2026-07-20 -->
|
||||
|
||||
## Ownership
|
||||
|
||||
- Target: M03
|
||||
- Program: RND
|
||||
- Owner/Agent ID: sindo-main-codex
|
||||
- Branch: `work/sindo-main-codex/m03-m2-raw-model-repository`
|
||||
- Lease expires UTC: 2026-07-20
|
||||
- Integrator: M03 milestone integrator
|
||||
|
||||
## Outcome
|
||||
|
||||
Extract the repeated FileAccess/ClassDB native `M2Loader` boundary for static
|
||||
and animated raw model Dictionaries from `StreamingWorldLoader`.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Cache raw data, Meshes, prototypes, missing paths or animation decisions.
|
||||
- Build Nodes/Meshes, refresh materials or choose `.tscn/.glb` caches.
|
||||
- Change static/animated parser methods, path resolution or fallback order.
|
||||
- Add dependencies, persistence, retries or visible behavior.
|
||||
|
||||
## Paths
|
||||
|
||||
- Exclusive: `src/render/m2/m2_raw_model_repository.gd`,
|
||||
`src/tools/verify_m2_raw_model_repository.gd`,
|
||||
`docs/modules/m2-raw-model-repository.md`, this claim
|
||||
- Shared/hotspots: `src/scenes/streaming/streaming_world_loader.gd`, M2
|
||||
finalizer/extractor/cache/pipeline/material/animation/shutdown verifiers and
|
||||
module specs, `docs/modules/world-renderer.md`, `docs/modules/README.md`,
|
||||
`RENDER.md`, `targets/03-renderer-facade.md`
|
||||
- Generated/ignored: `.godot`, native DLL, generated M2 resources, caches and
|
||||
proprietary renderer corpus
|
||||
|
||||
## Contracts and data
|
||||
|
||||
- Static load calls native `load_m2`; animated load calls `load_m2_animated`.
|
||||
- Paths remain `globalize_path(extracted_directory.path_join(normalized_path))`.
|
||||
- Empty path, missing native class/file/instance/method, parse failure or
|
||||
non-Dictionary result returns an empty Dictionary without retained state.
|
||||
- Each successful request creates one native loader instance as before.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Requires: accepted runtime Mesh finalizer package on master `1d94eac`
|
||||
- Blocks: M2 prototype/cache state extraction
|
||||
- External state: native M2Loader registration and local extracted files
|
||||
|
||||
## Verification
|
||||
|
||||
- Commands: dedicated invalid/missing/class/source/timing verifier; finalizer/
|
||||
classifier/extractor/cache/pipeline/material/animation/shutdown/M2 build/facade/
|
||||
internal-access/manifest, documentation and coordination gates
|
||||
- Fixtures: empty/missing normalized paths plus source contract; proprietary
|
||||
success corpus remains external
|
||||
- Fidelity evidence: exact native method names, path expression and three caller transitions
|
||||
- Performance budget: 10,000 rejected empty-path calls under 1 second
|
||||
|
||||
## Documentation deliverables
|
||||
|
||||
- Inline public API docs
|
||||
- Module specification with inputs/outputs, ownership and source map
|
||||
- Data-flow, lifecycle/state, sequence and dependency diagrams
|
||||
- Finalizer/extractor/cache/pipeline, world-renderer, registry and RENDER updates
|
||||
|
||||
## Simplicity and naming
|
||||
|
||||
- Important name introduced: `M2RawModelRepository`
|
||||
- Simplest solution: one stateless RefCounted with static and animated load methods
|
||||
- Rejected complexity: generic native repository, caching, dependency injection,
|
||||
retries, diagnostics framework or async wrapper
|
||||
- Unavoidable complexity: animated native method availability is a distinct guard
|
||||
- Measured optimization evidence: bounded rejected-input loop
|
||||
|
||||
## Status
|
||||
|
||||
- State: accepted
|
||||
- Done: repository, three loader adapters, contract verifier and required documentation
|
||||
- Next: continue M2 prototype/cache state extraction
|
||||
- Blocked by:
|
||||
|
||||
## Handoff
|
||||
|
||||
- Commit: `b608a81`
|
||||
- Results: repository passes invalid/missing/stateless/native-boundary contracts
|
||||
and 10,000 empty-path rejects in 3.275 ms; 45 repository-independent
|
||||
headless verifiers pass, while the proprietary ADT placement probe is
|
||||
unavailable without `data/extracted`; checkpoint dry-run passes 7/7;
|
||||
documentation and coordination gates pass.
|
||||
- Remaining risks: successful/corrupt native parsing, descriptor pressure,
|
||||
leaks and p95/p99 remain asset-backed work; native calls are synchronous and
|
||||
cannot be cancelled mid-parse; no original-client visual comparison is claimed.
|
||||
- Documentation updated: inline API; `m2-raw-model-repository.md` with data-flow,
|
||||
lifecycle, sequence and dependency diagrams; finalizer/classifier/world-renderer
|
||||
specs, module registry and `RENDER.md`.
|
||||
|
||||
<!-- OPENWC_HANDOFF:READY:M03-RND-M2-RAW-MODEL-REPOSITORY-001:b608a81 -->
|
||||
|
||||
## Integration
|
||||
|
||||
- Merge commit: `7cd07dd`
|
||||
- Post-merge results: repository `cases=10 iterations=10000
|
||||
elapsed_ms=3.200`; finalizer, extractor, cache, pipeline, shutdown, facade,
|
||||
internal-access `34`, manifest `7/7`, documentation and coordination gates passed.
|
||||
|
||||
<!-- OPENWC_INTEGRATION:ACCEPTED:M03-RND-M2-RAW-MODEL-REPOSITORY-001:7cd07dd -->
|
||||
@@ -0,0 +1,110 @@
|
||||
# M03-RND-M2-RUNTIME-MESH-FINALIZER-001 — M2 runtime mesh finalizer
|
||||
|
||||
<!-- OPENWC_CLAIM:M03-RND-M2-RUNTIME-MESH-FINALIZER-001:sindo-main-codex:2026-07-20 -->
|
||||
|
||||
## Ownership
|
||||
|
||||
- Target: M03
|
||||
- Program: RND
|
||||
- Owner/Agent ID: sindo-main-codex
|
||||
- Branch: `work/sindo-main-codex/m03-m2-runtime-mesh-finalizer`
|
||||
- Lease expires UTC: 2026-07-20
|
||||
- Integrator: M03 milestone integrator
|
||||
|
||||
## Outcome
|
||||
|
||||
Extract stale cached-Mesh refresh classification, metadata adoption,
|
||||
M2Builder rebuild and fallback from `StreamingWorldLoader` into one runtime M2
|
||||
Mesh finalizer while retaining raw-file I/O in the loader.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Load raw `.m2` files, call FileAccess/ClassDB M2Loader or choose cache paths.
|
||||
- Own ResourceLoader requests, Mesh cache/missing state or MultiMesh builds.
|
||||
- Change material refresh version `2`, classifier predicates or rebuild behavior.
|
||||
- Change cache formats, profiles or visible output.
|
||||
|
||||
## Paths
|
||||
|
||||
- Exclusive: `src/render/m2/m2_runtime_mesh_finalizer.gd`,
|
||||
`src/tools/verify_m2_runtime_mesh_finalizer.gd`,
|
||||
`docs/modules/m2-runtime-mesh-finalizer.md`, this claim
|
||||
- Shared/hotspots: `src/scenes/streaming/streaming_world_loader.gd`, M2
|
||||
classifier/extractor/cache/pipeline/material/shutdown verifiers and module
|
||||
specs, `docs/modules/world-renderer.md`, `docs/modules/README.md`, `RENDER.md`,
|
||||
`targets/03-renderer-facade.md`
|
||||
- Generated/ignored: `.godot`, native DLL, generated M2 resources, caches and
|
||||
proprietary renderer corpus
|
||||
|
||||
## Contracts and data
|
||||
|
||||
- Refresh version remains `2`; already-current Meshes return unchanged without raw data.
|
||||
- Stale Meshes require caller-supplied raw data only when refresh is necessary.
|
||||
- Empty/ordinary raw data marks the original Mesh current and returns it.
|
||||
- Billboard/UV-rotation data attempts M2Builder rebuild and returns the rebuilt
|
||||
first Mesh; rebuild failure marks and returns the original Mesh.
|
||||
- Classifier memoization retains both existing clear sites through finalizer clear.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Requires: accepted Mesh resource extractor package on master `dd8cfbf`
|
||||
- Blocks: further M2 raw-data I/O/prototype state extraction
|
||||
- External state: none; verification uses synthetic Mesh/raw-data fixtures
|
||||
|
||||
## Verification
|
||||
|
||||
- Commands: dedicated current/stale/ordinary/rebuild/fallback/clear/source/timing
|
||||
verifier; classifier/extractor/cache/pipeline/material/shutdown/M2 build/facade/
|
||||
internal-access/manifest, documentation and coordination gates
|
||||
- Fixtures: ArrayMesh and one synthetic triangle/billboard M2 Dictionary
|
||||
- Fidelity evidence: exact refresh version, predicate, rebuild and fallback transitions
|
||||
- Performance budget: 100 cycles over 256 ordinary finalizations under 1 second
|
||||
|
||||
## Documentation deliverables
|
||||
|
||||
- Inline public API docs
|
||||
- Module specification with inputs/outputs, ownership and source map
|
||||
- Data-flow, lifecycle/state, sequence and dependency diagrams
|
||||
- Classifier/extractor/cache/pipeline, world-renderer, registry and RENDER updates
|
||||
|
||||
## Simplicity and naming
|
||||
|
||||
- Important name introduced: `M2RuntimeMeshFinalizer`
|
||||
- Simplest solution: one service composing existing classifier/extractor and M2Builder
|
||||
- Rejected complexity: callbacks for loader I/O, generic material pipeline,
|
||||
cache ownership, signals or strategy framework
|
||||
- Unavoidable complexity: two-phase raw-data request avoids loading raw files for current Meshes
|
||||
- Measured optimization evidence: bounded synthetic ordinary finalization loop
|
||||
|
||||
## Status
|
||||
|
||||
- State: accepted
|
||||
- Done: finalizer, loader/test adapters, transition/rebuild verifier and required documentation
|
||||
- Next: further M2 raw-data I/O/prototype state extraction
|
||||
- Blocked by:
|
||||
|
||||
## Handoff
|
||||
|
||||
- Commit: `ece7724`
|
||||
- Results: finalizer passes current/stale/ordinary/synthetic-rebuild/fallback/
|
||||
clear contracts and 100-by-256 finalizations in 53.719 ms; 37 headless
|
||||
renderer/coordinate regressions pass; checkpoint dry-run passes 7/7;
|
||||
documentation and coordination gates pass.
|
||||
- Remaining risks: rebuild remains synchronous behind the existing permit; no
|
||||
private M2 material/traversal/descriptor-pressure/leak/p95/p99 or original-
|
||||
client comparison; raw `.m2` I/O remains loader-owned by design.
|
||||
- Documentation updated: inline API; `m2-runtime-mesh-finalizer.md` with
|
||||
data-flow, lifecycle, sequence and dependency diagrams; classifier/extractor/
|
||||
cache/pipeline specs, module registry, `world-renderer.md` and `RENDER.md`.
|
||||
|
||||
<!-- OPENWC_HANDOFF:READY:M03-RND-M2-RUNTIME-MESH-FINALIZER-001:ece7724 -->
|
||||
|
||||
## Integration
|
||||
|
||||
- Merge commit: `1fafdde`
|
||||
- Post-merge results: finalizer `cases=11 iterations=100
|
||||
elapsed_ms=54.349`; classifier, extractor, cache, pipeline, materials,
|
||||
shutdown, facade, internal-access `34`, manifest `7/7`, documentation and
|
||||
coordination gates passed.
|
||||
|
||||
<!-- OPENWC_INTEGRATION:ACCEPTED:M03-RND-M2-RUNTIME-MESH-FINALIZER-001:1fafdde -->
|
||||
@@ -0,0 +1,115 @@
|
||||
# M03-RND-M2-RUNTIME-REBUILD-CLASSIFIER-001 — M2 runtime rebuild classifier
|
||||
|
||||
<!-- OPENWC_CLAIM:M03-RND-M2-RUNTIME-REBUILD-CLASSIFIER-001:sindo-main-codex:2026-07-20 -->
|
||||
|
||||
## Ownership
|
||||
|
||||
- Target: M03
|
||||
- Program: RND
|
||||
- Owner/Agent ID: sindo-main-codex
|
||||
- Branch: `work/sindo-main-codex/m03-m2-runtime-rebuild-classifier`
|
||||
- Lease expires UTC: 2026-07-20
|
||||
- Integrator: M03 milestone integrator
|
||||
|
||||
## Outcome
|
||||
|
||||
Extract the memoized decision that stale cached M2 meshes require an in-memory
|
||||
runtime rebuild because raw batches contain billboard vertices or referenced UV
|
||||
rotation transforms.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Load raw M2 files or cache Resources.
|
||||
- Build, replace or mutate Meshes, materials, Nodes or RIDs.
|
||||
- Change `M2_MATERIAL_REFRESH_VERSION` or cache formats.
|
||||
- Interpret animation, particles, ribbons or general M2 shader fidelity.
|
||||
- Change path normalization, ResourceLoader transitions or finalize budgets.
|
||||
|
||||
## Paths
|
||||
|
||||
- Exclusive: `src/render/m2/m2_runtime_mesh_rebuild_classifier.gd`,
|
||||
`src/tools/verify_m2_runtime_mesh_rebuild_classifier.gd`,
|
||||
`docs/modules/m2-runtime-mesh-rebuild-classifier.md`, this claim
|
||||
- Shared/hotspots: `src/scenes/streaming/streaming_world_loader.gd`,
|
||||
`docs/modules/world-renderer.md`, `docs/modules/README.md`, `RENDER.md`,
|
||||
`targets/03-renderer-facade.md`
|
||||
- Generated/ignored: `.godot`, native DLL, generated ADT/M2 resources, caches
|
||||
and proprietary renderer corpus
|
||||
|
||||
## Contracts and data
|
||||
|
||||
- Billboard metadata requires rebuild when `has_billboard` is true or
|
||||
`billboard_vertex_count` is positive in any Dictionary batch.
|
||||
- UV rotation requires non-empty transforms and combos plus a referenced
|
||||
non-identity `Vector4` rotation or absolute rotation speed above `0.000001`.
|
||||
- Only up to four texture stages per batch are inspected; invalid variants and
|
||||
out-of-range combo/transform indices are skipped.
|
||||
- The first decision for a normalized path is memoized until clear, matching the
|
||||
existing loader cache lifetime.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Requires: current accepted master `90b1dac`
|
||||
- Blocks: further M2 mesh request/cache/finalization extraction
|
||||
- External state: none; verification uses synthetic raw M2 Dictionaries
|
||||
|
||||
## Verification
|
||||
|
||||
- Commands: dedicated billboard/UV/index/stage/cache/clear/source/timing verifier;
|
||||
M2 builder/grouper/transform/registry, materials, shutdown, facade,
|
||||
internal-access, manifest, documentation and coordination gates
|
||||
- Fixtures: synthetic batches, transform combos and rotation dictionaries
|
||||
- Fidelity evidence: exact existing predicate thresholds, stage cap and cache lifetime preserved
|
||||
- Performance budget: 100 classifications over 256 paths under 1 second
|
||||
|
||||
## Documentation deliverables
|
||||
|
||||
- Inline public API docs
|
||||
- Module specification with inputs/outputs, ownership and source map
|
||||
- Data-flow, lifecycle/state, sequence and dependency diagrams
|
||||
- World-renderer, module registry and RENDER status updates
|
||||
|
||||
## Simplicity and naming
|
||||
|
||||
- Important names introduced: `M2RuntimeMeshRebuildClassifier`
|
||||
- Simplest considered solution: one RefCounted with one memoized query and clear
|
||||
- Rejected complexity/abstractions: generic predicate cache, strategy interface,
|
||||
ResourceLoader wrapper, signals or typed rewrite of raw parser Dictionaries
|
||||
- Unavoidable complexity and justification: four-stage combo indirection is the
|
||||
existing build-12340 M2 material boundary
|
||||
- Measured optimization evidence: bounded synthetic classification loop
|
||||
|
||||
## Status
|
||||
|
||||
- State: accepted
|
||||
- Done: classifier/cache implemented; loader query and both clear sites migrated;
|
||||
synthetic predicate/cache verifier and required documentation added
|
||||
- Next: continue M2 mesh request/cache/finalization extraction
|
||||
- Blocked by:
|
||||
|
||||
<!-- OPENWC_INTEGRATION:ACCEPTED:M03-RND-M2-RUNTIME-REBUILD-CLASSIFIER-001:38f831e -->
|
||||
|
||||
## Handoff
|
||||
|
||||
- Commit: `19ca8f4`
|
||||
- Results: classifier PASS (`cases=12`, `iterations=100`,
|
||||
`elapsed_ms=64.585`; initial `35.461ms`); billboard flag/count, invalid
|
||||
variants/indices, UV rotation/speed/epsilon, four-stage cap, memoization,
|
||||
detached diagnostics and clear contracts PASS; 33 M2/WMO/liquid/terrain/
|
||||
facade/scheduler/streaming/coordinate/material/shutdown regressions PASS;
|
||||
internal-access decreased to `35`; documentation and coordination gates PASS;
|
||||
checkpoint dry-run PASS `7/7`.
|
||||
- Remaining risks: raw parser data remains Dictionary-based; the first decision
|
||||
for a path remains authoritative until clear; classification covers the current
|
||||
four-stage material implementation only; private asset descriptor-pressure,
|
||||
traversal, p95/p99 and paired original-client evidence remain absent.
|
||||
- Documentation updated: inline public API; new
|
||||
`docs/modules/m2-runtime-mesh-rebuild-classifier.md`; module registry, world
|
||||
renderer and `RENDER.md`; data-flow/state/sequence/dependency diagrams,
|
||||
ownership, failure/recovery, thresholds, cache lifetime and source map included.
|
||||
- Integration: merged as `38f831e`; post-merge classifier (`cases=12`,
|
||||
`iterations=100`, `elapsed_ms=45.439`), materials, shutdown, M2 batch planner,
|
||||
facade, internal-access (`private_symbols=35`), `7/7` manifest, documentation
|
||||
and coordination gates passed.
|
||||
|
||||
<!-- OPENWC_HANDOFF:READY:M03-RND-M2-RUNTIME-REBUILD-CLASSIFIER-001:19ca8f4 -->
|
||||
@@ -0,0 +1,106 @@
|
||||
# M03-RND-M2-STATIC-BATCH-MATERIALIZER-001
|
||||
|
||||
<!-- OPENWC_CLAIM:M03-RND-M2-STATIC-BATCH-MATERIALIZER-001:sindo-main-codex:2026-07-20 -->
|
||||
|
||||
## Owner
|
||||
|
||||
- Agent ID: `sindo-main-codex`
|
||||
- Target: M03 Renderer Facade and Safe Extraction
|
||||
- Branch: `work/sindo-main-codex/m03-m2-static-batch-materializer`
|
||||
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-m2-static-batch-materializer`
|
||||
|
||||
## Outcome
|
||||
|
||||
Extract main-thread static M2 `MultiMesh` construction, render-property setup
|
||||
and batch attachment from `StreamingWorldLoader`.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Change M2 grouping, batch sizing, build cursors, budgets or retry behavior.
|
||||
- Change Mesh loading, refresh, cache ownership or missing-model outcomes.
|
||||
- Own animated M2 instances, Editor persistence or tile cleanup.
|
||||
- Introduce spatial cells or change transform order, visibility or shadows.
|
||||
|
||||
## Paths
|
||||
|
||||
- Exclusive: `src/render/m2/m2_static_batch_materializer.gd`,
|
||||
`src/tools/verify_m2_static_batch_materializer.gd`,
|
||||
`docs/modules/m2-static-batch-materializer.md`, this claim
|
||||
- Shared/hotspots: `src/scenes/streaming/streaming_world_loader.gd`, M2 build/
|
||||
Mesh-cache/facade/internal-access/manifest verifiers and specs,
|
||||
`docs/modules/world-renderer.md`, `docs/modules/README.md`, `RENDER.md`,
|
||||
`targets/03-renderer-facade.md`
|
||||
- Generated/ignored: `.godot`, native DLL, generated resources/caches and proprietary corpus
|
||||
|
||||
## Contracts and data
|
||||
|
||||
- `MultiMesh.TRANSFORM_3D`, exact Mesh identity, instance count and transform
|
||||
slice order remain unchanged.
|
||||
- Batch node name remains model basename plus serial.
|
||||
- Visibility end/margin and shadow setting remain exact renderer inputs.
|
||||
- A valid batch attaches once to the supplied M2 parent and returns its node.
|
||||
- Loader retains Editor-owner assignment, build cursor, budgets and retries.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Requires: accepted animated materializer package on master `86814de`
|
||||
- Blocks: further M2 build-job orchestration extraction
|
||||
- External state: Godot MultiMesh and SceneTree mutation remain main-thread APIs
|
||||
|
||||
## Verification
|
||||
|
||||
- Commands: dedicated invalid/name/mesh/count/order/render/attachment/source/timing
|
||||
verifier; M2 build/cache/prototype/animated/shutdown/material/facade/internal-
|
||||
access/manifest regressions; docs/coordination/dry-run gates
|
||||
- Fixtures: synthetic ArrayMesh and ordered Transform3D slices
|
||||
- Fidelity evidence: exact existing MultiMesh construction and render settings
|
||||
- Performance budget: materialize 10,000 transforms under one second
|
||||
|
||||
## Documentation deliverables
|
||||
|
||||
- Inline API docs
|
||||
- Module spec with inputs/outputs, ownership and source map
|
||||
- Data-flow, lifecycle/state, sequence and dependency diagrams
|
||||
- Adjacent build/cache/world-renderer/module-registry/RENDER updates
|
||||
|
||||
## Simplicity and naming
|
||||
|
||||
- Important name introduced: `M2StaticBatchMaterializer`
|
||||
- Simplest solution: synchronous main-thread RefCounted returning attached node
|
||||
- Rejected complexity: signals, async queue, callbacks and generic materializer base
|
||||
- Unavoidable complexity: MultiMesh is a Godot Resource owned through its node
|
||||
- Measured optimization evidence: bounded synthetic transform upload
|
||||
|
||||
## Status
|
||||
|
||||
- State: ready for integration
|
||||
- Done: materializer, loader adapter, verifier and required documentation
|
||||
- Next: integrator merge and post-merge acceptance
|
||||
- Blocked by:
|
||||
|
||||
## Handoff
|
||||
|
||||
- Commit: `252927d`
|
||||
- Results: materializer passes 11 invalid/node/Mesh/render/attachment/source
|
||||
cases and 10,000 transform API writes in 0.552 ms; all 51 autonomous headless
|
||||
verifiers pass, while the proprietary ADT placement probe is unavailable
|
||||
without `data/extracted`; checkpoint dry-run passes 7/7; documentation and
|
||||
coordination gates pass; loader-private inventory remains 30.
|
||||
- Remaining risks: headless dummy timing excludes GPU upload cost; private asset
|
||||
traversal/visual comparison, spatial-cell culling, descriptor pressure, leaks
|
||||
and p95/p99 remain unavailable; no original-client comparison is claimed.
|
||||
- Documentation updated: inline API; `m2-static-batch-materializer.md` with
|
||||
data-flow, lifecycle, sequence and dependency diagrams; build/cache/world-
|
||||
renderer specs, module registry and `RENDER.md`.
|
||||
|
||||
<!-- OPENWC_HANDOFF:READY:M03-RND-M2-STATIC-BATCH-MATERIALIZER-001:252927d -->
|
||||
|
||||
## Integration
|
||||
|
||||
- Merge commit: `5e82daa`
|
||||
- Post-merge results: static materializer `cases=11 instances=10000
|
||||
elapsed_ms=1.327`; build/cache/animated materializer, shutdown, facade,
|
||||
internal-access `30`, checkpoint dry-run `7/7`, documentation and coordination
|
||||
gates passed.
|
||||
|
||||
<!-- OPENWC_INTEGRATION:ACCEPTED:M03-RND-M2-STATIC-BATCH-MATERIALIZER-001:5e82daa -->
|
||||
@@ -0,0 +1,86 @@
|
||||
# M03-RND-M2-STATIC-BUILD-RESOURCE-OBSERVER-001
|
||||
|
||||
<!-- OPENWC_CLAIM:M03-RND-M2-STATIC-BUILD-RESOURCE-OBSERVER-001:sindo-main-codex:2026-07-20 -->
|
||||
|
||||
## Owner
|
||||
|
||||
- Agent ID: `sindo-main-codex`
|
||||
- Target: M03 Renderer Facade and Safe Extraction
|
||||
- Branch: `work/sindo-main-codex/m03-m2-static-build-resource-observer`
|
||||
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-m2-static-build-resource-observer`
|
||||
|
||||
## Outcome
|
||||
|
||||
Move static M2 build cache lookup, threaded request selection and terminal
|
||||
missing transition from the loader into a service that fills
|
||||
`M2BuildResourceSnapshot`.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Move animation/native prototype observation or finalize drains.
|
||||
- Own/free Meshes, Nodes or cache entries.
|
||||
- Change path candidate order, GLB filtering, retry behavior, permits or visuals.
|
||||
- Change cache formats, profiles or batching.
|
||||
|
||||
## Paths
|
||||
|
||||
- Exclusive: `src/render/m2/m2_static_build_resource_observer.gd`,
|
||||
`src/tools/verify_m2_static_build_resource_observer.gd`,
|
||||
`docs/modules/m2-static-build-resource-observer.md`, this claim
|
||||
- Shared: loader, M2 snapshot/dispatch/pipeline/cache specs and verifiers,
|
||||
world renderer, module registry, RENDER and M03 Evidence
|
||||
|
||||
## Contracts and data
|
||||
|
||||
- Cached Mesh wins; terminal missing returns missing; existing pending request waits.
|
||||
- Candidate order remains normalized/lowercase nested then basename per extension.
|
||||
- `.tscn` precedes `.glb`; pivot-prefix GLB is skipped for static build.
|
||||
- Successful/`ERR_BUSY` request is remembered; no usable candidate marks missing.
|
||||
- Snapshot adopts exact Mesh/missing result; service never frees engine objects.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Requires: accepted resource snapshot `032a256`
|
||||
- Blocks: remaining animated resource observer extraction
|
||||
|
||||
## Verification
|
||||
|
||||
- Synthetic cache/pipeline/snapshot lifecycle, path order, source boundaries and timing;
|
||||
adjacent M2 and renderer gates; full headless suite and checkpoint dry-run.
|
||||
|
||||
## Documentation deliverables
|
||||
|
||||
- Inline API docs; module API/I/O/ownership; data-flow/state/sequence/dependency
|
||||
diagrams; adjacent renderer docs and M03 Evidence.
|
||||
|
||||
## Status
|
||||
|
||||
- State: accepted
|
||||
- Done: implementation, integration and post-merge acceptance
|
||||
- Next: remaining animated resource observer extraction
|
||||
- Blocked by:
|
||||
|
||||
## Handoff
|
||||
|
||||
- Commit: `7cb3e34` (`render: extract static M2 build resource observer`)
|
||||
- Results: observer PASS (`11` cases, `20,000` iterations, `64.654ms`);
|
||||
full autonomous suite PASS (`55/55`); renderer checkpoint dry-run PASS (`7/7`);
|
||||
internal-access PASS (`30` private symbols); documentation PASS (`42` module
|
||||
specs); coordination PASS with `30` historical warnings.
|
||||
- Remaining risks: successful asynchronous admission is covered by source and
|
||||
existing pipeline regressions because an undrained unit request leaks; private
|
||||
asset traversal, p95/p99, leak and visual evidence remain unavailable;
|
||||
animated observation and static finalize polling remain loader-owned.
|
||||
- Documentation updated: new observer module API/I/O/data-flow/state/sequence/
|
||||
dependency/ownership specification; M2 snapshot, pipeline/cache, world-renderer,
|
||||
module registry, `RENDER.md` and M03 Evidence updated.
|
||||
|
||||
## Integration
|
||||
|
||||
- Merge: `a043c79` (`merge: static M2 build resource observer`)
|
||||
- Post-merge: observer `11` cases / `20,000` iterations / `64.202ms`;
|
||||
snapshot `12` / `42.895ms`; dispatch `10` / `18.096ms`; Mesh pipeline
|
||||
`11` / `73.615ms`; Mesh cache `9` / `40.229ms`; prototype cache `16` /
|
||||
`35.947ms`; shutdown, facade, internal-access `30`, manifest `7/7`,
|
||||
documentation `42` and coordination passed. Checkpoint dry-run completed
|
||||
`7/7`; expected private Azeroth/character assets remained unavailable.
|
||||
@@ -0,0 +1,106 @@
|
||||
# M03-RND-WMO-PLACEMENT-REGISTRY-001 — WMO placement registry
|
||||
|
||||
<!-- OPENWC_CLAIM:M03-RND-WMO-PLACEMENT-REGISTRY-001:sindo-main-codex:2026-07-20 -->
|
||||
|
||||
## Ownership
|
||||
|
||||
- Target: M03
|
||||
- Program: RND
|
||||
- Owner/Agent ID: sindo-main-codex
|
||||
- Branch: `work/sindo-main-codex/m03-wmo-placement-registry`
|
||||
- Lease expires UTC: 2026-07-20
|
||||
- Integrator: M03 milestone integrator
|
||||
|
||||
## Outcome
|
||||
|
||||
Extract WMO placement-key to tile-reference ownership from the loader while
|
||||
leaving every Node and renderer-resource lifecycle in the loader.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Change placement identity rules or WMO transforms.
|
||||
- Move Node maps, world roots, tile state, jobs, queues or cancellation.
|
||||
- Move cache/load requests, material refresh or render-group building.
|
||||
- Change duplicate ordering, global-WMO lifetime or build budgets.
|
||||
- Claim asset-backed WMO fidelity or performance acceptance.
|
||||
|
||||
## Paths
|
||||
|
||||
- Exclusive: `src/render/wmo/wmo_placement_registry.gd`,
|
||||
`src/tools/verify_wmo_placement_registry.gd`,
|
||||
`docs/modules/wmo-placement-registry.md`, this claim
|
||||
- Shared/hotspots: `src/scenes/streaming/streaming_world_loader.gd`,
|
||||
`docs/modules/wmo-placement-resolver.md`, `docs/modules/world-renderer.md`,
|
||||
`docs/modules/README.md`, `RENDER.md`, `targets/03-renderer-facade.md`
|
||||
- Generated/ignored: `.godot`, native DLL, generated ADT/WMO resources, caches
|
||||
and proprietary renderer corpus
|
||||
|
||||
## Contracts and data
|
||||
|
||||
- Input: resolved WMO placement key and tile/global reference key
|
||||
- `add_reference` creates a key/ref set or idempotently retains an existing ref
|
||||
- `release_reference` erases only the supplied ref and returns true only when the
|
||||
final ref removes the placement key
|
||||
- `contains`, `active_count`, detached sorted diagnostics and idempotent `clear`
|
||||
- Loader retains key→Node mapping and reacts to final release by cancelling the
|
||||
render build and queue-freeing the corresponding Node
|
||||
- Cache/schema/coordinate/profile versions: unchanged
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Requires: current master `5aa40d6`, `WmoPlacementResolver` identity contract
|
||||
- Blocks: WMO build/resource state decomposition
|
||||
- External state: none; tests use synthetic placement/tile keys
|
||||
|
||||
## Verification
|
||||
|
||||
- Commands: dedicated add/shared/idempotent/non-owner/final/global/clear/
|
||||
diagnostics/source/timing verifier; resolver/material/shutdown plus M2/terrain/
|
||||
facade/internal-access/manifest/scheduler/streaming/coordinate/docs/coord gates
|
||||
- Fidelity evidence: exact shared-ref and final-release behavior preserved
|
||||
- Performance budget: dictionary membership over active WMO keys/refs; no Node,
|
||||
Resource, RID, task, queue, mutex, cache or file access
|
||||
|
||||
## Documentation deliverables
|
||||
|
||||
- Inline registry API documentation
|
||||
- New module spec with inputs/outputs, data-flow, lifecycle/state, ownership,
|
||||
failure/recovery, performance, fidelity limitations and source map
|
||||
- Placement resolver, renderer/module registry and `RENDER.md` updates
|
||||
|
||||
## Simplicity and naming
|
||||
|
||||
- Important name: `WmoPlacementRegistry`
|
||||
- Simplest solution: one private key-to-reference-set Dictionary
|
||||
- Rejected complexity: generic ref-count registry, signals, callbacks, Resources,
|
||||
weak refs or Node ownership inside the service
|
||||
- Unavoidable complexity: loader keeps a parallel key-to-Node map
|
||||
- Measured optimization evidence: bounded synthetic add/release loop
|
||||
|
||||
## Status
|
||||
|
||||
- State: accepted
|
||||
- Done: registry extracted; global/tile adapters migrated; Node/resource/job
|
||||
ownership retained in loader; contract, regressions, dry-run and docs passed
|
||||
- Next: WMO build/resource state extraction and asset-backed placement evidence
|
||||
- Blocked by:
|
||||
|
||||
<!-- OPENWC_HANDOFF:READY:M03-RND-WMO-PLACEMENT-REGISTRY-001:c590036 -->
|
||||
<!-- OPENWC_INTEGRATION:ACCEPTED:M03-RND-WMO-PLACEMENT-REGISTRY-001:f9e5a4f -->
|
||||
|
||||
## Handoff
|
||||
|
||||
- Commit: `c590036`
|
||||
- Results: `verify_wmo_placement_registry.gd` PASS cases=10,
|
||||
iterations=100, elapsed_ms=53.941; 21 adjacent WMO/M2/terrain/facade/
|
||||
scheduler/streaming/coordinate regressions PASS; documentation and
|
||||
coordination gates PASS; render dry-run PASS 7/7.
|
||||
- Remaining risks: no proprietary asset corpus or original-client paired WMO
|
||||
capture; dry-run reports expected missing ADT/DBC/character assets; WMO
|
||||
build/resource state remains loader-owned.
|
||||
- Documentation updated: `docs/modules/wmo-placement-registry.md`,
|
||||
`docs/modules/wmo-placement-resolver.md`, `docs/modules/world-renderer.md`,
|
||||
`docs/modules/README.md`, `RENDER.md`; inline public API comments added.
|
||||
- Integration: accepted in master merge `f9e5a4f`; post-merge registry
|
||||
(`cases=10`, `iterations=100`, `elapsed_ms=54.178`), resolver, runtime-cache
|
||||
shutdown, facade, documentation and coordination gates passed.
|
||||
@@ -0,0 +1,108 @@
|
||||
# M03-RND-WMO-RENDER-BUILD-PLANNER-001 — WMO render build step planner
|
||||
|
||||
<!-- OPENWC_CLAIM:M03-RND-WMO-RENDER-BUILD-PLANNER-001:sindo-main-codex:2026-07-20 -->
|
||||
|
||||
## Ownership
|
||||
|
||||
- Target: M03
|
||||
- Program: RND
|
||||
- Owner/Agent ID: sindo-main-codex
|
||||
- Branch: `work/sindo-main-codex/m03-wmo-render-build-planner`
|
||||
- Lease expires UTC: 2026-07-20
|
||||
- Integrator: M03 milestone integrator
|
||||
|
||||
## Outcome
|
||||
|
||||
Extract the pure one-operation WMO render build cursor decision from the loader
|
||||
while preserving mesh-before-MultiMesh ordering and one budget permit per group.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Move Nodes, Resources, materials, jobs, queues, cancellation or permits.
|
||||
- Change WMO placement, cache loading, transforms, visibility or shadows.
|
||||
- Change build ordering, group names, editor ownership or finalization thread.
|
||||
- Change cache formats, settings, presets or renderer diagnostics.
|
||||
- Claim asset-backed WMO fidelity or p95/p99 acceptance.
|
||||
|
||||
## Paths
|
||||
|
||||
- Exclusive: `src/render/wmo/wmo_render_build_step_planner.gd`,
|
||||
`src/tools/verify_wmo_render_build_step_planner.gd`,
|
||||
`docs/modules/wmo-render-build-step-planner.md`, this claim
|
||||
- Shared/hotspots: `src/scenes/streaming/streaming_world_loader.gd`,
|
||||
`docs/modules/world-renderer.md`, `docs/modules/README.md`, `RENDER.md`,
|
||||
`targets/03-renderer-facade.md`
|
||||
- Generated/ignored: `.godot`, native DLL, generated ADT/WMO resources, caches
|
||||
and proprietary renderer corpus
|
||||
|
||||
## Contracts and data
|
||||
|
||||
- Input: mesh count/index and MultiMesh count/index
|
||||
- Output: `mesh`, `multimesh` or `complete` operation, selected index and next
|
||||
cursor values
|
||||
- Mesh is selected while its cursor is below count; MultiMesh is selected only
|
||||
after mesh exhaustion; complete follows both exhausted ranges
|
||||
- Loader retains cursor adoption, Resource array access, Node creation, material
|
||||
refresh, ownership, queue cancellation and budget permit consumption
|
||||
- Cache/schema/coordinate/profile versions: unchanged
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Requires: current master `1e73e59`, accepted WMO placement registry/resolver
|
||||
- Blocks: further WMO render build state decomposition
|
||||
- External state: none; verification uses scalar synthetic fixtures
|
||||
|
||||
## Verification
|
||||
|
||||
- Commands: dedicated ordering/cursor/empty/raw-edge/source/timing verifier;
|
||||
WMO registry/resolver/material/shutdown plus M2/terrain/facade/internal-access/
|
||||
manifest/scheduler/streaming/coordinate/docs/coord gates
|
||||
- Fidelity evidence: exact mesh-first, one-step cursor behavior preserved
|
||||
- Performance budget: 20,000 pure plans under 1 second; no Node, Resource, RID,
|
||||
task, queue, mutex, cache or file access
|
||||
|
||||
## Documentation deliverables
|
||||
|
||||
- Inline public planner API documentation
|
||||
- New module spec with inputs/outputs, data flow, sequence, ownership,
|
||||
failure/recovery, performance/fidelity limits and source map
|
||||
- Renderer module registry, world renderer and `RENDER.md` updates
|
||||
|
||||
## Simplicity and naming
|
||||
|
||||
- Important name: `WmoRenderBuildStepPlanner`
|
||||
- Simplest solution: one stateless function and three operation constants
|
||||
- Rejected complexity: job class, iterator, generic cursor framework, callbacks,
|
||||
signals or ownership of engine objects
|
||||
- Unavoidable complexity: loader still has separate mesh/MultiMesh materializers
|
||||
- Measured optimization evidence: bounded synthetic planning loop
|
||||
|
||||
## Status
|
||||
|
||||
- State: accepted
|
||||
- Done: planner and loader adapter implemented; Node/Resource/material/job/queue/
|
||||
permit ownership retained in loader; regressions, dry-run and docs passed
|
||||
- Next: WMO build job/resource ownership or liquid service extraction
|
||||
- Blocked by:
|
||||
|
||||
<!-- OPENWC_INTEGRATION:ACCEPTED:M03-RND-WMO-RENDER-BUILD-PLANNER-001:c06aed5 -->
|
||||
|
||||
## Handoff
|
||||
|
||||
- Commit: `3a6b1d3`
|
||||
- Results: dedicated planner PASS (`cases=9`, `iterations=20000`,
|
||||
`elapsed_ms=11.914`); WMO registry/resolver/material/shutdown and 18 adjacent
|
||||
M2/terrain/facade/scheduler/streaming/coordinate regressions PASS;
|
||||
documentation and coordination gates PASS; checkpoint dry-run PASS `7/7`.
|
||||
- Remaining risks: no proprietary WMO corpus, original-client paired capture or
|
||||
traversal p95/p99; dry-run reports expected missing ADT/DBC/character assets;
|
||||
WMO Resource arrays and untyped build jobs/queues remain loader-owned.
|
||||
- Documentation updated: inline planner API;
|
||||
`docs/modules/wmo-render-build-step-planner.md`, module registry,
|
||||
`docs/modules/world-renderer.md`, `RENDER.md` with data-flow and sequence
|
||||
diagrams, ownership, failure/recovery and source map.
|
||||
- Integration: accepted in master merge `c06aed5`; post-merge planner
|
||||
(`cases=9`, `iterations=20000`, `elapsed_ms=11.059`), WMO registry/resolver,
|
||||
runtime-cache shutdown, facade, documentation and coordination gates passed.
|
||||
|
||||
<!-- OPENWC_HANDOFF:READY:M03-RND-WMO-RENDER-BUILD-PLANNER-001:3a6b1d3 -->
|
||||
@@ -0,0 +1,117 @@
|
||||
# M03-RND-WMO-RENDER-BUILD-QUEUE-001 — WMO render build queue
|
||||
|
||||
<!-- OPENWC_CLAIM:M03-RND-WMO-RENDER-BUILD-QUEUE-001:sindo-main-codex:2026-07-20 -->
|
||||
|
||||
## Ownership
|
||||
|
||||
- Target: M03
|
||||
- Program: RND
|
||||
- Owner/Agent ID: sindo-main-codex
|
||||
- Branch: `work/sindo-main-codex/m03-wmo-render-build-queue`
|
||||
- Lease expires UTC: 2026-07-20
|
||||
- Integrator: M03 milestone integrator
|
||||
|
||||
## Outcome
|
||||
|
||||
Extract typed lightweight-WMO render build jobs and their FIFO key queue from
|
||||
the loader while leaving all engine-object destruction and materialization in
|
||||
the loader.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Queue-free Nodes or free Resources/RIDs.
|
||||
- Move material refresh, Node creation, editor ownership or budget permits.
|
||||
- Change duplicate enqueue, stale-key cleanup, cancel or clear ordering.
|
||||
- Change placement, cache loading/versioning, transforms, shadows or visibility.
|
||||
- Claim asset-backed WMO fidelity, leak acceptance or traversal p95/p99.
|
||||
|
||||
## Paths
|
||||
|
||||
- Exclusive: `src/render/wmo/wmo_render_build_job.gd`,
|
||||
`src/render/wmo/wmo_render_build_queue.gd`,
|
||||
`src/tools/verify_wmo_render_build_queue.gd`,
|
||||
`docs/modules/wmo-render-build-queue.md`, this claim
|
||||
- Shared/hotspots: `src/scenes/streaming/streaming_world_loader.gd`,
|
||||
`docs/modules/wmo-render-build-step-planner.md`,
|
||||
`docs/modules/world-renderer.md`, `docs/modules/README.md`, `RENDER.md`,
|
||||
`targets/03-renderer-facade.md`
|
||||
- Generated/ignored: `.godot`, native DLL, generated ADT/WMO resources, caches
|
||||
and proprietary renderer corpus
|
||||
|
||||
## Contracts and data
|
||||
|
||||
- Typed job: placement key, strong Node3D/Resource references and two cursors
|
||||
- FIFO queue preserves duplicate key append and first-occurrence erase behavior
|
||||
- Stale front keys remain observable for loader-compatible pop-and-continue
|
||||
- Cancel releases the keyed job reference and first queued key only
|
||||
- Clear releases every job/key reference idempotently; never frees engine objects
|
||||
- Loader retains validity checks, planner invocation, cursor adoption,
|
||||
materialization, permits, cancellation reactions and Node destruction
|
||||
- Cache/schema/coordinate/profile versions: unchanged
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Requires: current master `1e740aa`, accepted WMO build step planner
|
||||
- Blocks: further WMO cache/load and materialization decomposition
|
||||
- External state: none; verification uses synthetic Node3D/Resource instances
|
||||
|
||||
## Verification
|
||||
|
||||
- Commands: dedicated invalid/FIFO/duplicate/stale/cursor/cancel/clear/
|
||||
diagnostics/source/timing verifier; WMO planner/registry/resolver/material/
|
||||
shutdown plus M2/terrain/facade/internal-access/manifest/scheduler/streaming/
|
||||
coordinate/docs/coord gates
|
||||
- Fidelity evidence: exact current queue/dictionary behavior preserved
|
||||
- Performance budget: 100 cycles over 256 jobs under 1 second
|
||||
|
||||
## Documentation deliverables
|
||||
|
||||
- Inline job/queue API documentation
|
||||
- New module spec with inputs/outputs, data flow, state/sequence, ownership,
|
||||
threading, failure/recovery, performance/fidelity limits and source map
|
||||
- Build-step planner, renderer module registry, world renderer and RENDER updates
|
||||
|
||||
## Simplicity and naming
|
||||
|
||||
- Important names: `WmoRenderBuildJob`, `WmoRenderBuildQueue`
|
||||
- Simplest solution: one typed record plus Dictionary/Array queue composition
|
||||
- Rejected complexity: generic job scheduler, signals, callbacks, mutexes,
|
||||
Resources as job records or engine lifetime callbacks
|
||||
- Unavoidable complexity: duplicate keys can intentionally leave stale entries
|
||||
- Measured optimization evidence: bounded synthetic enqueue/cancel/clear loop
|
||||
|
||||
## Status
|
||||
|
||||
- State: accepted
|
||||
- Done: typed job/queue implemented; three clear sites, metrics, enqueue, drain and
|
||||
cancel migrated; engine destruction/materialization retained in loader
|
||||
- Next: WMO cache/load state or liquid service extraction
|
||||
- Blocked by:
|
||||
|
||||
<!-- OPENWC_INTEGRATION:ACCEPTED:M03-RND-WMO-RENDER-BUILD-QUEUE-001:60603e1 -->
|
||||
|
||||
## Handoff
|
||||
|
||||
- Commit: `e576ae2` (implementation `e1bb105` plus cold-load fix)
|
||||
- Results: queue PASS (`cases=11`, `iterations=100`, `elapsed_ms=59.874`);
|
||||
WMO planner/registry/resolver/material/shutdown and 18 adjacent M2/terrain/
|
||||
facade/scheduler/streaming/coordinate regressions PASS; documentation and
|
||||
coordination gates PASS; checkpoint dry-run PASS `7/7`.
|
||||
- Cold-load recovery: post-merge smoke exposed an editor class-cache dependency;
|
||||
typed queue accessors removed the loader/global-class dependency and a fresh
|
||||
run without `.godot` passed (`cases=11`, `elapsed_ms=57.835`).
|
||||
- Remaining risks: strong Node/Resource references require every loader cancel/
|
||||
clear path; duplicate enqueue intentionally preserves stale FIFO keys; no
|
||||
proprietary WMO corpus, original-client paired capture, leak run or traversal
|
||||
p95/p99; dry-run reports expected missing ADT/DBC/character assets.
|
||||
- Documentation updated: inline job/queue API;
|
||||
`docs/modules/wmo-render-build-queue.md`, build-step planner spec, module
|
||||
registry, world renderer and `RENDER.md`; data-flow/state/sequence diagrams,
|
||||
ownership, threading, failure/recovery and source map included.
|
||||
- Integration: implementation merged as `a878e7c`; its first post-merge smoke
|
||||
exposed and rejected a cold global-class registration dependency. Fix merged
|
||||
as `60603e1`; post-fix master queue (`cases=11`, `iterations=100`,
|
||||
`elapsed_ms=56.454`), planner, registry, resolver, shutdown, facade,
|
||||
documentation and coordination gates passed without parse diagnostics.
|
||||
|
||||
<!-- OPENWC_HANDOFF:READY:M03-RND-WMO-RENDER-BUILD-QUEUE-001:e576ae2 -->
|
||||
@@ -0,0 +1,115 @@
|
||||
# M03-RND-WMO-RENDER-RESOURCE-CACHE-001 — WMO render resource cache state
|
||||
|
||||
<!-- OPENWC_CLAIM:M03-RND-WMO-RENDER-RESOURCE-CACHE-001:sindo-main-codex:2026-07-20 -->
|
||||
|
||||
## Ownership
|
||||
|
||||
- Target: M03
|
||||
- Program: RND
|
||||
- Owner/Agent ID: sindo-main-codex
|
||||
- Branch: `work/sindo-main-codex/m03-wmo-render-resource-cache`
|
||||
- Lease expires UTC: 2026-07-20
|
||||
- Integrator: M03 milestone integrator
|
||||
|
||||
## Outcome
|
||||
|
||||
Extract lightweight-WMO render Resource cache, negative cache and asynchronous
|
||||
request bookkeeping from the loader while preserving loader-owned I/O and
|
||||
cache-format validation.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Call `ResourceLoader`, construct cache paths or inspect files.
|
||||
- Validate `WMOStreamingResource` script identity or format version.
|
||||
- Own cached WMO scenes, live prototypes, placement/build jobs or Nodes/RIDs.
|
||||
- Change fallback order, retry behavior, quality profiles or visual output.
|
||||
- Claim asset-backed WMO fidelity, leak acceptance or traversal p95/p99.
|
||||
|
||||
## Paths
|
||||
|
||||
- Exclusive: `src/render/wmo/wmo_render_resource_cache_state.gd`,
|
||||
`src/tools/verify_wmo_render_resource_cache_state.gd`,
|
||||
`docs/modules/wmo-render-resource-cache-state.md`, this claim
|
||||
- Shared/hotspots: `src/scenes/streaming/streaming_world_loader.gd`,
|
||||
`src/tools/verify_render_runtime_cache_shutdown.gd`,
|
||||
`docs/modules/wmo-render-build-queue.md`, `docs/modules/world-renderer.md`,
|
||||
`docs/modules/README.md`, `RENDER.md`, `targets/03-renderer-facade.md`
|
||||
- Generated/ignored: `.godot`, native DLL, generated ADT/WMO resources, caches
|
||||
and proprietary renderer corpus
|
||||
|
||||
## Contracts and data
|
||||
|
||||
- State keys are loader-normalized relative WMO paths copied as `String`.
|
||||
- Cached values are already validated `Resource` references.
|
||||
- Pending values are cache paths for loader-owned `ResourceLoader` requests.
|
||||
- Terminal request removal precedes validated-resource or missing-state adoption.
|
||||
- Request/missing reset preserves accepted resources; full clear releases all.
|
||||
- Cache/schema/coordinate/profile versions and validation rules remain unchanged.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Requires: current master `c954704`, accepted WMO render build queue
|
||||
- Blocks: further WMO cached-scene/load and materialization decomposition
|
||||
- External state: none; verification uses synthetic Resources and paths
|
||||
|
||||
## Verification
|
||||
|
||||
- Commands: dedicated lifecycle/request/cache/reset/diagnostics/source/timing
|
||||
verifier; WMO queue/planner/registry/resolver/material/shutdown plus adjacent
|
||||
renderer/streaming/coordinate/documentation/coordination gates
|
||||
- Fixtures: synthetic Resource instances and normalized relative/cache paths
|
||||
- Fidelity evidence: exact current cache/missing/request transitions preserved
|
||||
- Performance budget: 100 cycles over 256 resource/request keys under 1 second
|
||||
|
||||
## Documentation deliverables
|
||||
|
||||
- Inline public API docs
|
||||
- Module specification with inputs/outputs, ownership and source map
|
||||
- Data-flow, lifecycle/state and loader interaction sequence diagrams
|
||||
- World-renderer, module registry, WMO queue and RENDER status updates
|
||||
|
||||
## Simplicity and naming
|
||||
|
||||
- Important names introduced: `WmoRenderResourceCacheState`
|
||||
- Simplest considered solution: one RefCounted around three Dictionaries
|
||||
- Rejected complexity/abstractions: generic cache, callbacks, signals, mutexes,
|
||||
ResourceLoader wrapper, persistence layer or shared WMO/M2 cache hierarchy
|
||||
- Unavoidable complexity and justification: partial reset must retain validated
|
||||
Resources while discarding pending/negative state at map reset and shutdown
|
||||
- Measured optimization evidence: bounded synthetic store/request/reset loop
|
||||
|
||||
## Status
|
||||
|
||||
- State: accepted
|
||||
- Done: state holder implemented; loader cache/request/reset/metrics adapters
|
||||
migrated; shutdown regression and required documentation updated
|
||||
- Next: cached WMO scene/load state or liquid service extraction
|
||||
- Blocked by:
|
||||
|
||||
<!-- OPENWC_INTEGRATION:ACCEPTED:M03-RND-WMO-RENDER-RESOURCE-CACHE-001:b4955d6 -->
|
||||
|
||||
## Handoff
|
||||
|
||||
- Commit: `d39f591`
|
||||
- Results: cache-state PASS (`cases=10`, `iterations=100`,
|
||||
`elapsed_ms=58.727`); direct no-`.godot` cache-state run PASS (`45.361ms`);
|
||||
WMO queue/planner/registry/resolver/material/shutdown plus 18 adjacent
|
||||
M2/terrain/facade/scheduler/streaming/coordinate regressions PASS;
|
||||
documentation and coordination gates PASS; checkpoint dry-run PASS `7/7`.
|
||||
- Remaining risks: accepted Resources remain strongly referenced until full
|
||||
shutdown release; cached-scene and ResourceLoader I/O seams remain in loader;
|
||||
no proprietary corrupt-cache fixture, asset-backed WMO traversal/leak run,
|
||||
p95/p99 or original-client paired evidence. Full loader cannot be used as a
|
||||
direct empty-class-cache smoke because pre-existing typed global coordinate/
|
||||
focus dependencies require editor class registration; the extracted service
|
||||
itself passed cold and normal editor parse passed.
|
||||
- Documentation updated: inline public API; new
|
||||
`docs/modules/wmo-render-resource-cache-state.md`; module registry, WMO queue,
|
||||
world renderer and `RENDER.md`; data-flow/state/sequence diagrams, ownership,
|
||||
threading, cache-version boundary, failure/recovery and source map included.
|
||||
- Integration: merged as `b4955d6`; post-merge cache state (`cases=10`,
|
||||
`iterations=100`, `elapsed_ms=51.949`), shutdown, WMO queue/resolver, facade,
|
||||
internal-access (`private_symbols=41`), `7/7` manifest, documentation and
|
||||
coordination gates passed.
|
||||
|
||||
<!-- OPENWC_HANDOFF:READY:M03-RND-WMO-RENDER-RESOURCE-CACHE-001:d39f591 -->
|
||||
@@ -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,117 @@
|
||||
# M03-RND-WMO-SCENE-RESOURCE-CACHE-001 — WMO cached-scene resource state
|
||||
|
||||
<!-- OPENWC_CLAIM:M03-RND-WMO-SCENE-RESOURCE-CACHE-001:sindo-main-codex:2026-07-20 -->
|
||||
|
||||
## Ownership
|
||||
|
||||
- Target: M03
|
||||
- Program: RND
|
||||
- Owner/Agent ID: sindo-main-codex
|
||||
- Branch: `work/sindo-main-codex/m03-wmo-scene-resource-cache`
|
||||
- Lease expires UTC: 2026-07-20
|
||||
- Integrator: M03 milestone integrator
|
||||
|
||||
## Outcome
|
||||
|
||||
Extract cached-WMO `PackedScene`, negative-cache and asynchronous request state
|
||||
from the loader while preserving loader-owned file limits, I/O and scene-cache
|
||||
version validation.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Call `ResourceLoader`/`FileAccess`, construct `.tscn` paths or measure files.
|
||||
- Instantiate scenes or validate WMO builder cache metadata/version.
|
||||
- Own live WMO prototypes, placement/build jobs, instantiated Nodes or RIDs.
|
||||
- Change missing/oversize/request-error fallback behavior or debug output.
|
||||
- Claim asset-backed WMO fidelity, leak acceptance or traversal p95/p99.
|
||||
|
||||
## Paths
|
||||
|
||||
- Exclusive: `src/render/wmo/wmo_scene_resource_cache_state.gd`,
|
||||
`src/tools/verify_wmo_scene_resource_cache_state.gd`,
|
||||
`docs/modules/wmo-scene-resource-cache-state.md`, this claim
|
||||
- Shared/hotspots: `src/scenes/streaming/streaming_world_loader.gd`,
|
||||
`src/tools/verify_render_runtime_cache_shutdown.gd`,
|
||||
`docs/modules/wmo-render-resource-cache-state.md`,
|
||||
`docs/modules/world-renderer.md`, `docs/modules/README.md`, `RENDER.md`,
|
||||
`targets/03-renderer-facade.md`
|
||||
- Generated/ignored: `.godot`, native DLL, generated ADT/WMO resources, caches
|
||||
and proprietary renderer corpus
|
||||
|
||||
## Contracts and data
|
||||
|
||||
- State keys are loader-normalized relative WMO paths copied as `String`.
|
||||
- Cached values are loader-validated `PackedScene` references.
|
||||
- Pending values are `.tscn` paths for loader-owned threaded requests.
|
||||
- Missing state accepts absent file, oversize, request-start, load and validation failures.
|
||||
- Terminal pending removal precedes validated-scene or missing adoption.
|
||||
- Transient clear retains accepted scenes; full clear releases all state.
|
||||
- Cache metadata/version, file-size limit, profile and fallback rules are unchanged.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Requires: current master `e690756`; accepted WMO render Resource cache state
|
||||
- Blocks: further WMO I/O/fallback and materialization decomposition
|
||||
- External state: none; verification uses synthetic PackedScenes and paths
|
||||
|
||||
## Verification
|
||||
|
||||
- Commands: dedicated lifecycle/request/missing/reset/diagnostics/source/timing
|
||||
verifier; WMO render-cache/queue/planner/registry/resolver/material/shutdown plus
|
||||
adjacent renderer/streaming/coordinate/documentation/coordination gates
|
||||
- Fixtures: synthetic PackedScene instances and normalized relative/cache paths
|
||||
- Fidelity evidence: exact current cached-scene/missing/request transitions preserved
|
||||
- Performance budget: 100 cycles over 256 scene/request keys under 1 second
|
||||
|
||||
## Documentation deliverables
|
||||
|
||||
- Inline public API docs
|
||||
- Module specification with inputs/outputs, ownership and source map
|
||||
- Data-flow, lifecycle/state and loader interaction sequence diagrams
|
||||
- World-renderer, module registry, render-cache and RENDER status updates
|
||||
|
||||
## Simplicity and naming
|
||||
|
||||
- Important names introduced: `WmoSceneResourceCacheState`
|
||||
- Simplest considered solution: one RefCounted around three Dictionaries
|
||||
- Rejected complexity/abstractions: generic cache hierarchy, callbacks, signals,
|
||||
mutexes, ResourceLoader wrapper, persistence layer or render/scene cache merger
|
||||
- Unavoidable complexity and justification: missing can be recorded without a
|
||||
pending request, and partial reset must retain validated PackedScenes
|
||||
- Measured optimization evidence: bounded synthetic store/request/reset loop
|
||||
|
||||
## Status
|
||||
|
||||
- State: accepted
|
||||
- Done: state holder implemented; loader lookup/request/drain/reset/metrics
|
||||
adapters migrated; shutdown regression and required documentation updated
|
||||
- Next: WMO I/O/live fallback or liquid service extraction
|
||||
- Blocked by:
|
||||
|
||||
<!-- OPENWC_INTEGRATION:ACCEPTED:M03-RND-WMO-SCENE-RESOURCE-CACHE-001:ada0fd9 -->
|
||||
|
||||
## Handoff
|
||||
|
||||
- Commit: `c839294`
|
||||
- Results: scene cache-state PASS (`cases=11`, `iterations=100`,
|
||||
`elapsed_ms=40.768`; initial direct cold run `43.636ms`); render cache-state,
|
||||
WMO queue/planner/registry/resolver/material/shutdown plus 18 adjacent
|
||||
M2/terrain/facade/scheduler/streaming/coordinate regressions PASS;
|
||||
documentation and coordination gates PASS; checkpoint dry-run PASS `7/7`.
|
||||
- Remaining risks: accepted PackedScenes remain strongly referenced until full
|
||||
shutdown release; ResourceLoader/FileAccess I/O, cache-validation probes and
|
||||
live fallback remain in loader; no proprietary stale/oversize fixture,
|
||||
asset-backed traversal/leak run, p95/p99 or original-client paired evidence.
|
||||
The extracted scene state passed cold; full loader cold execution still needs
|
||||
editor class registration for pre-existing focus/coordinate global classes.
|
||||
- Documentation updated: inline public API; new
|
||||
`docs/modules/wmo-scene-resource-cache-state.md`; render-cache spec, module
|
||||
registry, world renderer and `RENDER.md`; data-flow/state/sequence diagrams,
|
||||
ownership, threading, file-size/cache-version boundary, failure/recovery and
|
||||
source map included.
|
||||
- Integration: merged as `ada0fd9`; post-merge scene state (`cases=11`,
|
||||
`iterations=100`, `elapsed_ms=40.113`), render state (`43.434ms`), shutdown,
|
||||
WMO queue/resolver, facade, internal-access (`private_symbols=40`), `7/7`
|
||||
manifest, documentation and coordination gates passed.
|
||||
|
||||
<!-- OPENWC_HANDOFF:READY:M03-RND-WMO-SCENE-RESOURCE-CACHE-001:c839294 -->
|
||||
@@ -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.
|
||||
@@ -15,11 +15,41 @@
|
||||
| Terrain quality Mesh cache | Implemented extraction | [`terrain-quality-mesh-cache.md`](terrain-quality-mesh-cache.md) |
|
||||
| Terrain chunk LOD planner | Implemented extraction | [`terrain-chunk-lod-planner.md`](terrain-chunk-lod-planner.md) |
|
||||
| Terrain chunk geometry queue planner | Implemented extraction | [`terrain-chunk-geometry-queue-planner.md`](terrain-chunk-geometry-queue-planner.md) |
|
||||
| ADT water load pipeline state | Implemented extraction | [`adt-water-load-pipeline-state.md`](adt-water-load-pipeline-state.md) |
|
||||
| ADT water scene finalizer | Implemented extraction | [`adt-water-scene-finalizer.md`](adt-water-scene-finalizer.md) |
|
||||
| M2 unique placement registry | Implemented extraction | [`m2-unique-placement-registry.md`](m2-unique-placement-registry.md) |
|
||||
| M2 placement transform resolver | Implemented | [`m2-placement-transform-resolver.md`](m2-placement-transform-resolver.md) |
|
||||
| M2 placement grouper | Implemented extraction | [`m2-placement-grouper.md`](m2-placement-grouper.md) |
|
||||
| M2 build batch planner | Implemented extraction | [`m2-build-batch-planner.md`](m2-build-batch-planner.md) |
|
||||
| M2 build dispatch planner | Implemented extraction | [`m2-build-dispatch-planner.md`](m2-build-dispatch-planner.md) |
|
||||
| M2 build resource snapshot | Implemented extraction | [`m2-build-resource-snapshot.md`](m2-build-resource-snapshot.md) |
|
||||
| M2 static build resource observer | Implemented extraction | [`m2-static-build-resource-observer.md`](m2-static-build-resource-observer.md) |
|
||||
| M2 cached animation resource observer | Implemented extraction | [`m2-cached-animation-resource-observer.md`](m2-cached-animation-resource-observer.md) |
|
||||
| M2 native animation resource observer | Implemented extraction | [`m2-native-animation-resource-observer.md`](m2-native-animation-resource-observer.md) |
|
||||
| M2 build queue | Implemented extraction | [`m2-build-queue.md`](m2-build-queue.md) |
|
||||
| M2 static batch materializer | Implemented extraction | [`m2-static-batch-materializer.md`](m2-static-batch-materializer.md) |
|
||||
| M2 runtime mesh rebuild classifier | Implemented extraction | [`m2-runtime-mesh-rebuild-classifier.md`](m2-runtime-mesh-rebuild-classifier.md) |
|
||||
| M2 animated scene finalizer | Implemented extraction | [`m2-animated-scene-finalizer.md`](m2-animated-scene-finalizer.md) |
|
||||
| M2 animation playback controller | Implemented extraction | [`m2-animation-playback-controller.md`](m2-animation-playback-controller.md) |
|
||||
| M2 animated instance materializer | Implemented extraction | [`m2-animated-instance-materializer.md`](m2-animated-instance-materializer.md) |
|
||||
| M2 animation load pipeline state | Implemented extraction | [`m2-animation-load-pipeline-state.md`](m2-animation-load-pipeline-state.md) |
|
||||
| M2 animation resource finalizer | Implemented extraction | [`m2-animation-resource-finalizer.md`](m2-animation-resource-finalizer.md) |
|
||||
| M2 mesh load pipeline state | Implemented extraction | [`m2-mesh-load-pipeline-state.md`](m2-mesh-load-pipeline-state.md) |
|
||||
| M2 mesh resource cache state | Implemented extraction | [`m2-mesh-resource-cache-state.md`](m2-mesh-resource-cache-state.md) |
|
||||
| M2 mesh resource extractor | Implemented extraction | [`m2-mesh-resource-extractor.md`](m2-mesh-resource-extractor.md) |
|
||||
| M2 mesh resource finalizer | Implemented extraction | [`m2-mesh-resource-finalizer.md`](m2-mesh-resource-finalizer.md) |
|
||||
| M2 runtime mesh finalizer | Implemented extraction | [`m2-runtime-mesh-finalizer.md`](m2-runtime-mesh-finalizer.md) |
|
||||
| M2 raw model repository | Implemented extraction | [`m2-raw-model-repository.md`](m2-raw-model-repository.md) |
|
||||
| M2 prototype cache state | Implemented extraction | [`m2-prototype-cache-state.md`](m2-prototype-cache-state.md) |
|
||||
| WMO placement resolver | Implemented extraction | [`wmo-placement-resolver.md`](wmo-placement-resolver.md) |
|
||||
| WMO placement registry | Implemented extraction | [`wmo-placement-registry.md`](wmo-placement-registry.md) |
|
||||
| WMO render build step planner | Implemented extraction | [`wmo-render-build-step-planner.md`](wmo-render-build-step-planner.md) |
|
||||
| WMO render build queue | Implemented extraction | [`wmo-render-build-queue.md`](wmo-render-build-queue.md) |
|
||||
| WMO render Resource cache state | Implemented extraction | [`wmo-render-resource-cache-state.md`](wmo-render-resource-cache-state.md) |
|
||||
| WMO render Resource finalizer | Implemented extraction | [`wmo-render-resource-finalizer.md`](wmo-render-resource-finalizer.md) |
|
||||
| WMO scene Resource cache state | Implemented extraction | [`wmo-scene-resource-cache-state.md`](wmo-scene-resource-cache-state.md) |
|
||||
| WMO scene Resource finalizer | Implemented extraction | [`wmo-scene-resource-finalizer.md`](wmo-scene-resource-finalizer.md) |
|
||||
| WMO runtime Mesh finalizer | Implemented extraction | [`wmo-runtime-mesh-finalizer.md`](wmo-runtime-mesh-finalizer.md) |
|
||||
| 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) |
|
||||
|
||||
@@ -0,0 +1,252 @@
|
||||
# ADT Water Load Pipeline State
|
||||
|
||||
## Metadata
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Status | Implemented |
|
||||
| Target/work package | M03 / `M03-RND-ADT-WATER-LOAD-PIPELINE-001` |
|
||||
| Owners | ADT water pending FIFO/dedupe, active task IDs and worker-safe result mailbox |
|
||||
| Last verified | Worktree `work/sindo-main-codex/m03-adt-water-load-pipeline`, 2026-07-17 |
|
||||
| Profiles/capabilities | ADT MH2O/MCLQ water loading; profile-independent state |
|
||||
|
||||
## Purpose
|
||||
|
||||
Own the cross-frame and cross-thread bookkeeping for asynchronous ADT water
|
||||
loads outside `StreamingWorldLoader`: FIFO requests, queued-key deduplication,
|
||||
opaque active task IDs and parsed-result delivery. The loader retains task
|
||||
execution, parsing, budgets, stale-result decisions and scene finalization.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Start, cancel or wait WorkerThreadPool tasks.
|
||||
- Instantiate `ADTLoader` or interpret MH2O/MCLQ data.
|
||||
- Select concurrency limits or consume render permits.
|
||||
- Create, attach or free Nodes, Meshes, materials or RIDs.
|
||||
- Change liquid visuals, coordinates, tile acceptance or loaded-state timing.
|
||||
|
||||
## Context and boundaries
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Tile[Loaded tile needing water] --> Loader[StreamingWorldLoader]
|
||||
Loader --> State[AdtWaterLoadPipelineState]
|
||||
State --> Request[Oldest tile/path request]
|
||||
Request --> Loader
|
||||
Loader --> Worker[WorkerThreadPool ADTLoader task]
|
||||
Worker --> State
|
||||
State --> Result[Parsed ADT result]
|
||||
Result --> Loader
|
||||
Loader --> Budget[WATER_FINALIZE permit]
|
||||
Loader --> Finalizer[AdtWaterSceneFinalizer]
|
||||
Finalizer --> Builder[ADTBuilder water scene]
|
||||
Finalizer --> Scene[Main-thread tile Node attach]
|
||||
```
|
||||
|
||||
Allowed dependencies are RefCounted, Dictionary/Array/String and one Godot
|
||||
`Mutex` around results. WorkerThreadPool, ADTLoader, scheduler, tile state,
|
||||
scene-tree/render resources and gameplay/editor/network are forbidden.
|
||||
|
||||
## Public API
|
||||
|
||||
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|
||||
|---|---|---|---|---|
|
||||
| `enqueue_request(tile_key, adt_path)` | Command/query | Append deduplicated FIFO request | Renderer main thread; until dequeue/cancel/clear | Empty/queued/active returns false |
|
||||
| `take_next_request()` | Command/query | Pop oldest request and release queued marker | Renderer main thread | Empty returns `{}` |
|
||||
| `cancel_pending_requests(tile_key)` | Command/query | Remove all matching pending entries | Tile release/main thread | Empty/unknown removes zero; active unaffected |
|
||||
| `remember_active_task(tile_key, task_id)` | Command/query | Retain loader-created opaque task ID | Main thread; until completion/clear | Empty/duplicate returns false |
|
||||
| `has_active_task` / `active_task_id_for` | Query | Test/borrow active task identity | Main thread | Unknown ID is `-1` |
|
||||
| `complete_active_task(tile_key)` | Command/query | Remove and return completed task ID | Main thread after wait | Unknown returns `-1` |
|
||||
| `active_task_ids_snapshot()` | Query | Copy IDs for orderly shutdown waiting | Main thread | Detached Array |
|
||||
| `publish_result(tile_key, path, data)` | Command/query | Append parsed result under mutex | Worker or main thread | Empty key/path returns false |
|
||||
| `has_result()` / `pop_result()` | Query/command | Observe/pop oldest result under mutex | Main-thread drain | Empty pop returns `{}` |
|
||||
| `total_load_count()` | Query | Pending plus active metric | Main thread | Results intentionally excluded |
|
||||
| `pending_request_count()` / `active_task_count()` | Query | Support loader concurrency loop | Main thread | None |
|
||||
| `clear()` | Command | Release all bookkeeping and results | Main thread after wait or map reset | Does not interrupt workers |
|
||||
| `diagnostic_snapshot()` | Query | Return detached scalar requests/tasks/results | Main thread; result mailbox locked | Water data omitted |
|
||||
|
||||
## Inputs and outputs
|
||||
|
||||
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|
||||
|---|---|---|---|---|---|
|
||||
| Input | Tile key and ADT path | Loader tile state | Pending FIFO | Copied Strings | Until dequeue/cancel/clear |
|
||||
| Input | Opaque task ID | Loader WorkerThreadPool adapter | Active task map | Integer value | Until wait/completion/clear |
|
||||
| Input | Tile/path/parsed ADT Dictionary | Worker parser adapter | Result mailbox | Dictionary reference retained | Until pop/clear |
|
||||
| Output | Oldest request Dictionary | Pipeline state | Loader task-start adapter | Fresh record from FIFO | One start attempt |
|
||||
| Output | Active task ID snapshot | Pipeline state | Loader shutdown wait | Detached Array | Shutdown loop |
|
||||
| Output | Oldest result Dictionary | Pipeline state | Loader finalize adapter | Mailbox record transferred | One finalize attempt |
|
||||
| Output | Scalar diagnostics | Pipeline state | Verifier/future metrics | Detached records | Snapshot lifetime |
|
||||
|
||||
Side effects are limited to collection mutation, reference retention and result
|
||||
mutex locking. Parsing, permits and scene mutation remain outside the module.
|
||||
|
||||
## Data flow
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Enqueue[enqueue tile/path] --> Duplicate{Queued or active?}
|
||||
Duplicate -->|yes| Reject[false; unchanged]
|
||||
Duplicate -->|no| FIFO[Append FIFO and queued key]
|
||||
FIFO --> Take[take next; release queued marker]
|
||||
Take --> Validate[Loader checks tile/path]
|
||||
Validate --> Start[Loader starts worker and remembers task ID]
|
||||
Start --> Parse[Worker parses ADT]
|
||||
Parse --> Publish[Mutex-protected publish result]
|
||||
Publish --> Permit[Loader obtains WATER_FINALIZE permit]
|
||||
Permit --> Pop[Pop oldest result]
|
||||
Pop --> Wait[Loader waits task and completes active ID]
|
||||
Wait --> Stale{Tile/path/root current?}
|
||||
Stale -->|no| Discard[Discard result]
|
||||
Stale -->|yes| Attach[Build and attach water scene]
|
||||
```
|
||||
|
||||
## Lifecycle/state
|
||||
|
||||
A tile request progresses through `Absent`, `Queued`, `Active` and
|
||||
`ResultReady`; the loader wait/completion returns bookkeeping to `Absent` before
|
||||
validating/attaching the result. Queued tile release cancels only `Queued`.
|
||||
Active work is not interrupted; a later stale result is drained and discarded.
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Absent
|
||||
Absent --> Queued: accepted enqueue
|
||||
Queued --> Absent: pending cancellation or invalid dequeue
|
||||
Queued --> Active: loader starts task
|
||||
Active --> ResultReady: worker publishes result
|
||||
ResultReady --> Absent: loader waits and completes task
|
||||
Queued --> Absent: clear
|
||||
Active --> Absent: clear bookkeeping only
|
||||
ResultReady --> Absent: clear
|
||||
```
|
||||
|
||||
## Main sequence
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Loader as StreamingWorldLoader main thread
|
||||
participant State as AdtWaterLoadPipelineState
|
||||
participant Worker as WorkerThreadPool task
|
||||
participant Budget as RenderBudgetScheduler
|
||||
participant Finalizer as AdtWaterSceneFinalizer
|
||||
Loader->>State: enqueue_request(tile key, ADT path)
|
||||
Loader->>State: take_next_request()
|
||||
Loader->>Worker: add_task(parse binding)
|
||||
Loader->>State: remember_active_task(key, task ID)
|
||||
Worker->>Worker: ADTLoader.load_adt(path)
|
||||
Worker->>State: publish_result(key, path, data)
|
||||
Note over State: result mutex protects publication/pop
|
||||
Loader->>Budget: try_consume_permit(WATER_FINALIZE)
|
||||
Loader->>State: pop_result()
|
||||
Loader->>Worker: wait_for_task_completion(task ID)
|
||||
Loader->>State: complete_active_task(key)
|
||||
Loader->>Loader: reject stale tile/path/root
|
||||
Loader->>Finalizer: attach_water_scene(data, origin, tile root, editor owner)
|
||||
Loader->>Loader: mark water_loaded
|
||||
```
|
||||
|
||||
## Dependency diagram
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
Loader[StreamingWorldLoader] --> State[AdtWaterLoadPipelineState]
|
||||
Loader --> Scheduler[RenderBudgetScheduler]
|
||||
Loader --> Worker[WorkerThreadPool / ADTLoader]
|
||||
Loader --> Finalizer[AdtWaterSceneFinalizer]
|
||||
Finalizer --> Builder[ADTBuilder / SceneTree]
|
||||
Worker -->|publish only| State
|
||||
State -. no dependency .-> Scheduler
|
||||
State -. no dependency .-> Finalizer
|
||||
```
|
||||
|
||||
## Ownership, threading and resources
|
||||
|
||||
- Main thread owns pending and active-task collection mutation.
|
||||
- Worker threads may call only `publish_result`; result observe/pop/clear also lock.
|
||||
- Parsed water Dictionary identity is retained until main-thread pop.
|
||||
- Loader owns WorkerThreadPool task lifetime and must wait before orderly shutdown clear.
|
||||
- Loader owns tile states and the tile subtree; `AdtWaterSceneFinalizer` performs
|
||||
the main-thread ADTBuilder call and attachment without retaining Nodes.
|
||||
- `clear` cannot cancel a worker; post-reset publication becomes a stale result
|
||||
and is discarded because no active task/tile ownership remains.
|
||||
|
||||
## Errors, cancellation and recovery
|
||||
|
||||
| Failure | Detection | Behavior | Diagnostic | Recovery |
|
||||
|---|---|---|---|---|
|
||||
| Empty/duplicate request | Enqueue guards | Reject unchanged | Contract verifier | Correct caller state |
|
||||
| Tile removed while queued | Loader release | Remove matching pending requests | Source/lifecycle verifier | Reload can enqueue later |
|
||||
| Tile removed while active | Loader stale check after result | Wait task, remove active ID, discard result | Existing metrics | Reload can enqueue later |
|
||||
| Parser unavailable/empty | Worker publishes empty Dictionary | Loader marks `water_loaded` without Node | Existing behavior | Future tile reload |
|
||||
| Path changed | Loader compares result path | Discard after task completion | Source regression | Current tile can request again |
|
||||
| Shutdown | Loader snapshots/waits active IDs | Clear all bookkeeping/results | Shutdown/source verifier | New loader starts empty |
|
||||
|
||||
## Configuration and capabilities
|
||||
|
||||
| Setting/capability | Default | Profile | Runtime mutable | Effect |
|
||||
|---|---|---|---|---|
|
||||
| `enable_water` | Loader setting | Quality/custom | Yes | Gates enqueue/process, unchanged |
|
||||
| `max_concurrent_water_tasks` | Loader setting | Quality/custom | Yes | Bounds active tasks outside state |
|
||||
| `water_finalize_ops_per_tick` | Loader setting | Quality/custom | Yes | Bounds result pops via scheduler |
|
||||
|
||||
## Persistence, cache and migration
|
||||
|
||||
No state is serialized or cached. ADT source/cache formats and liquid material
|
||||
versions are unchanged; no migration or rebuild is required.
|
||||
|
||||
## Diagnostics and observability
|
||||
|
||||
- `total_load_count` preserves the prior pending-plus-active `water` metric.
|
||||
- Snapshot exposes FIFO tile/path, sorted active keys and result tile/path only.
|
||||
- Parsed liquid payloads, Nodes and task internals are not exposed.
|
||||
- No logs or correlation IDs are introduced.
|
||||
|
||||
## Verification
|
||||
|
||||
- `verify_adt_water_load_pipeline_state.gd`: invalid/dedupe/FIFO behavior,
|
||||
active IDs, payload identity, real Thread publication, pending cancellation,
|
||||
clear, detached diagnostics, loader boundaries and bounded timing.
|
||||
- Existing material/shutdown/facade/streaming regressions cover adjacent behavior.
|
||||
- Fidelity evidence is preservation of current scheduling/finalize transitions;
|
||||
no MH2O/MCLQ visual or original-client parity claim is made.
|
||||
- Performance budget: 100 cycles of 256 request/task/result transitions under 1 second.
|
||||
|
||||
## Extension points
|
||||
|
||||
- ADT parse execution can later move behind a parser/job adapter without changing state.
|
||||
- Water scene materialization is now delegated to `AdtWaterSceneFinalizer`,
|
||||
independently of this async bookkeeping service.
|
||||
|
||||
## Capability status
|
||||
|
||||
| Capability | Status | Evidence | Gap/next step |
|
||||
|---|---|---|---|
|
||||
| ADT water load pipeline state | Implemented extraction | FIFO/task/thread/source/timing verifier | Asset-backed traversal/leak evidence pending |
|
||||
| ADT liquid parsing | Existing/loader-owned adapter | Native ADTLoader and dry-run paths | MH2O/MCLQ fidelity fixtures pending |
|
||||
| Water scene finalization | Implemented extraction | Dedicated synthetic scene/ownership verifier | Asset-backed traversal/hitch evidence pending |
|
||||
|
||||
## Known gaps and risks
|
||||
|
||||
- `clear` intentionally does not interrupt active WorkerThreadPool work.
|
||||
- Result payload Dictionaries are retained by reference and must not be mutated after publication.
|
||||
- Result availability and pop use separate mutex acquisitions; one serialized
|
||||
main-thread consumer preserves ordering, but the API is not a multi-consumer queue.
|
||||
- No proprietary liquid corpus, traversal/leak run, p95/p99 or paired capture is included.
|
||||
|
||||
## Source map
|
||||
|
||||
| Path | Responsibility |
|
||||
|---|---|
|
||||
| `src/render/liquid/adt_water_load_pipeline_state.gd` | Request/task/result bookkeeping and result mutex |
|
||||
| `src/render/liquid/adt_water_scene_finalizer.gd` | Main-thread ADTBuilder call, attach and optional editor ownership |
|
||||
| `src/scenes/streaming/streaming_world_loader.gd` | Parse task execution, permits, stale checks and tile loaded timing |
|
||||
| `addons/mpq_extractor/loaders/adt_builder.gd` | ADT liquid geometry/material construction |
|
||||
| `src/tools/verify_adt_water_load_pipeline_state.gd` | Lifecycle/thread/boundary/timing regression |
|
||||
|
||||
## Related decisions and references
|
||||
|
||||
- [`world-renderer.md`](world-renderer.md)
|
||||
- [`adt-water-scene-finalizer.md`](adt-water-scene-finalizer.md)
|
||||
- [`render-budget-scheduler.md`](render-budget-scheduler.md)
|
||||
- [`../../RENDER.md`](../../RENDER.md)
|
||||
- [`../../targets/roadmap/02-rendering-and-graphics.md`](../../targets/roadmap/02-rendering-and-graphics.md)
|
||||
@@ -0,0 +1,232 @@
|
||||
# ADT Water Scene Finalizer
|
||||
|
||||
## Metadata
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Status | Implemented extraction |
|
||||
| Target/work package | M03 / `M03-RND-ADT-WATER-SCENE-FINALIZER-001` |
|
||||
| Owners | Main-thread ADT water subtree build, attach and optional editor ownership |
|
||||
| Last verified | Worktree `work/sindo-main-codex/m03-adt-water-scene-finalizer`, 2026-07-17 |
|
||||
| Profiles/capabilities | Existing ADT MH2O/MCLQ scene output; profile-independent adapter |
|
||||
|
||||
## Purpose
|
||||
|
||||
Provide one liquid-specific main-thread boundary for converting parsed ADT
|
||||
water data through the existing `ADTBuilder`, attaching the resulting `Water`
|
||||
subtree to a caller-owned tile root and assigning optional persisted Editor
|
||||
ownership. The service is stateless and retains no Node or Resource.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Parse or validate MH2O/MCLQ binary structures.
|
||||
- Replace ADTBuilder geometry, material or coordinate calculations.
|
||||
- Own water request/task/result state, permits or concurrency.
|
||||
- Validate tile/path/root freshness beyond receiving a valid root.
|
||||
- Mark `water_loaded`, enable water or remove water subtrees.
|
||||
|
||||
## Context and boundaries
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Parse[Parsed ADT Dictionary] --> Loader[StreamingWorldLoader]
|
||||
Loader -->|validated data, origin, tile root| Finalizer[AdtWaterSceneFinalizer]
|
||||
Finalizer --> Builder[ADTBuilder]
|
||||
Builder --> Water[Water Node3D or null]
|
||||
Water --> Finalizer
|
||||
Finalizer -->|main-thread add_child| Tile[Caller-owned tile root]
|
||||
Loader -->|after return| Loaded[water_loaded timing]
|
||||
```
|
||||
|
||||
Allowed dependencies are `ADTBuilder` and Godot Node/mesh types needed to adopt
|
||||
its output. WorkerThreadPool, scheduler, tile-state dictionaries, facade,
|
||||
gameplay, network and editor plugin packages are forbidden.
|
||||
|
||||
## Public API
|
||||
|
||||
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|
||||
|---|---|---|---|---|
|
||||
| `attach_water_scene(water_data, tile_origin_godot_units, tile_root, editor_owner)` | Command/query | Build and attach one existing-format water subtree | Renderer main thread; returned Node is borrowed for tile lifetime | Empty data, invalid root or dry build returns null without attachment |
|
||||
|
||||
`editor_owner` is optional. When supplied it must be an ancestor suitable for
|
||||
Godot `Node.owner`; ownership is recursively assigned to the generated subtree.
|
||||
|
||||
## Inputs and outputs
|
||||
|
||||
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|
||||
|---|---|---|---|---|---|
|
||||
| Input | Parsed ADT water-containing Dictionary | ADTLoader/raw tile path | Finalizer/ADTBuilder | Caller retains Dictionary; not mutated | One main-thread call |
|
||||
| Input | Tile origin in Godot world units | Loader tile state/ADTBuilder | Finalizer | Value copy | One call |
|
||||
| Input | Valid tile root | StreamingWorldLoader | Finalizer | Caller/SceneTree-owned | Must outlive call and attached subtree |
|
||||
| Input | Optional edited scene root | Loader Editor policy | Finalizer | SceneTree-owned borrowed Node | One call |
|
||||
| Output | Attached `Water` Node3D or null | Finalizer | Loader/SceneTree | Tile root owns attached Node | Tile subtree lifetime |
|
||||
|
||||
Side effects are limited to ADTBuilder mesh/material creation, `add_child` and
|
||||
optional recursive `Node.owner` assignment. No cache, filesystem, task or tile
|
||||
state is mutated.
|
||||
|
||||
## Data flow
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Input[water data + Godot origin + tile root] --> Guard{Data and root valid?}
|
||||
Guard -->|no| None[Return null; unchanged]
|
||||
Guard -->|yes| Build[ADTBuilder.build_tile_water_scene]
|
||||
Build --> Dry{Water root exists?}
|
||||
Dry -->|no| None
|
||||
Dry -->|yes| Attach[tile root add_child]
|
||||
Attach --> Owner{Editor owner supplied?}
|
||||
Owner -->|yes| Assign[Assign owner recursively]
|
||||
Owner -->|no| Return[Return borrowed root]
|
||||
Assign --> Return
|
||||
```
|
||||
|
||||
## Lifecycle/state
|
||||
|
||||
The service has no retained lifecycle. Each call moves one temporary builder
|
||||
result through the following call-local states.
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Validating
|
||||
Validating --> Rejected: empty data or invalid root
|
||||
Validating --> Building: valid input
|
||||
Building --> Dry: no geometry
|
||||
Building --> Attached: Water root built
|
||||
Attached --> EditorOwned: owner supplied
|
||||
Attached --> Complete: no owner
|
||||
EditorOwned --> Complete
|
||||
Rejected --> [*]
|
||||
Dry --> [*]
|
||||
Complete --> [*]
|
||||
```
|
||||
|
||||
## Main sequence
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Loader as StreamingWorldLoader main thread
|
||||
participant Finalizer as AdtWaterSceneFinalizer
|
||||
participant Builder as ADTBuilder
|
||||
participant Tile as Tile root
|
||||
Loader->>Finalizer: attach_water_scene(data, origin, tile, editor owner)
|
||||
Finalizer->>Builder: build_tile_water_scene(data, origin)
|
||||
Builder-->>Finalizer: Water root or null
|
||||
alt Water root exists
|
||||
Finalizer->>Tile: add_child(Water)
|
||||
opt editor owner supplied
|
||||
Finalizer->>Finalizer: assign owner recursively
|
||||
end
|
||||
Finalizer-->>Loader: borrowed Water root
|
||||
else dry/invalid
|
||||
Finalizer-->>Loader: null
|
||||
end
|
||||
Loader->>Loader: preserve existing water_loaded timing
|
||||
```
|
||||
|
||||
## Dependency diagram
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
Loader[StreamingWorldLoader] --> Finalizer[AdtWaterSceneFinalizer]
|
||||
Finalizer --> Builder[ADTBuilder]
|
||||
Builder --> Material[WowLiquidMaterial]
|
||||
Finalizer --> Scene[Godot SceneTree]
|
||||
Pipeline[AdtWaterLoadPipelineState] --> Loader
|
||||
Scheduler[RenderBudgetScheduler] --> Loader
|
||||
Finalizer -. no dependency .-> Pipeline
|
||||
Finalizer -. no dependency .-> Scheduler
|
||||
```
|
||||
|
||||
## Ownership, threading and resources
|
||||
|
||||
- The method is main-thread only because it creates Mesh/Material/Node objects
|
||||
and mutates the SceneTree.
|
||||
- `ADTBuilder` owns temporary construction. It frees an empty `Water` root and
|
||||
returns null for dry/invalid liquid geometry.
|
||||
- After `add_child`, the caller's tile subtree owns the returned Water root and
|
||||
all MeshInstance children; the finalizer retains no references.
|
||||
- Optional editor ownership affects serialization ownership only and does not
|
||||
replace SceneTree lifetime ownership.
|
||||
- Parsed Dictionaries are borrowed and not mutated.
|
||||
|
||||
## Errors, cancellation and recovery
|
||||
|
||||
| Failure | Detection | Behavior | Diagnostic | Recovery |
|
||||
|---|---|---|---|---|
|
||||
| Empty parsed data | Input guard | Return null, no builder call | Contract verifier | Loader preserves existing loaded timing |
|
||||
| Missing/freed tile root | Input guard | Return null, no scene mutation | Contract verifier | Stale-result validation normally rejects earlier |
|
||||
| Dry or malformed liquid arrays | ADTBuilder returns null | No attachment | Builder/shutdown regression | Reload after corrected source |
|
||||
| Tile/path stale | Loader before call | Finalizer is not invoked | Existing loader behavior | Current tile may enqueue again |
|
||||
| Shutdown/cancellation | Loader drains/cancels before call | No retained finalizer work | Shutdown verifier | New loader starts stateless |
|
||||
|
||||
## Configuration and capabilities
|
||||
|
||||
| Setting/capability | Default | Profile | Runtime mutable | Effect |
|
||||
|---|---|---|---|---|
|
||||
| `enable_water` | Loader setting | Quality/custom | Yes | Gates both caller paths; not owned here |
|
||||
| `water_finalize_ops_per_tick` | `1` | Quality/custom | Yes | Budgets async calls before finalizer |
|
||||
| `editor_persist_generated_nodes` | `false` | Editor only | Yes | Supplies or omits editor owner |
|
||||
|
||||
## Persistence, cache and migration
|
||||
|
||||
The service writes no files and changes no cache/resource schema. Existing ADT,
|
||||
terrain cache and liquid material versions remain valid; no rebuild or migration
|
||||
is required. Persisted Editor ownership follows the loader's existing opt-in.
|
||||
|
||||
## Diagnostics and observability
|
||||
|
||||
- Logs: none; existing loader/builder diagnostics remain authoritative.
|
||||
- Metrics: no independent counters; existing water queue/finalize metrics remain.
|
||||
- Debug views: generated `Water/Liquid_<id>` names remain unchanged.
|
||||
- Correlation IDs: tile/path correlation stays in loader pipeline state.
|
||||
|
||||
## Verification
|
||||
|
||||
- `verify_adt_water_scene_finalizer.gd`: empty/invalid/dry guards, one-cell
|
||||
geometry, attachment, recursive owner, loader delegation and bounded timing.
|
||||
- `verify_adt_water_load_pipeline_state.gd`: adjacent async ownership and loaded timing.
|
||||
- Renderer material and shutdown verifiers cover material construction and dry-root cleanup.
|
||||
- Fidelity evidence is the unchanged ADTBuilder implementation and unchanged two
|
||||
loader call positions; no new original-client liquid parity claim is made.
|
||||
- Performance budget: 100 empty calls under one second.
|
||||
|
||||
## Extension points
|
||||
|
||||
- A future liquid renderer may replace the internal builder only after paired
|
||||
MH2O/MCLQ fixtures establish equivalent geometry/material behavior.
|
||||
- Removal/eviction remains ordinary tile subtree ownership and needs no parallel API.
|
||||
|
||||
## Capability status
|
||||
|
||||
| Capability | Status | Evidence | Gap/next step |
|
||||
|---|---|---|---|
|
||||
| ADT water scene finalization seam | Implemented extraction | Synthetic geometry/attach/owner/source verifier | Asset-backed traversal and hitch metrics pending |
|
||||
| ADT liquid geometry/materials | Existing implementation | ADTBuilder/material/checkpoint evidence | MH2O/MCLQ and original-client parity incomplete |
|
||||
| Async water scheduling | Existing extracted state | Water pipeline verifier | Active worker interruption remains loader-owned |
|
||||
|
||||
## Known gaps and risks
|
||||
|
||||
- The service intentionally wraps the existing Dictionary-based ADTBuilder boundary.
|
||||
- It does not verify that `editor_owner` is an ancestor; the loader supplies the
|
||||
edited scene root under the existing policy.
|
||||
- Mesh/material creation remains synchronous main-thread work behind the existing permit.
|
||||
- No proprietary liquid corpus, long traversal leak run, p95/p99 or paired capture is included.
|
||||
|
||||
## Source map
|
||||
|
||||
| Path | Responsibility |
|
||||
|---|---|
|
||||
| `src/render/liquid/adt_water_scene_finalizer.gd` | Main-thread build, attach and optional recursive editor ownership |
|
||||
| `addons/mpq_extractor/loaders/adt_builder.gd` | Existing ADT liquid geometry/material creation and dry-root cleanup |
|
||||
| `src/render/liquid/adt_water_load_pipeline_state.gd` | Pending/task/result state before finalization |
|
||||
| `src/scenes/streaming/streaming_world_loader.gd` | Permits, stale checks, tile state and finalizer calls |
|
||||
| `src/tools/verify_adt_water_scene_finalizer.gd` | Synthetic scene/ownership/boundary/timing regression |
|
||||
|
||||
## Related decisions and references
|
||||
|
||||
- [`adt-water-load-pipeline-state.md`](adt-water-load-pipeline-state.md)
|
||||
- [`world-renderer.md`](world-renderer.md)
|
||||
- [`render-budget-scheduler.md`](render-budget-scheduler.md)
|
||||
- [`../../RENDER.md`](../../RENDER.md)
|
||||
- [`../../targets/roadmap/02-rendering-and-graphics.md`](../../targets/roadmap/02-rendering-and-graphics.md)
|
||||
@@ -0,0 +1,231 @@
|
||||
# M2 Animated Instance Materializer
|
||||
|
||||
## Metadata
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Status | Implemented extraction |
|
||||
| Target/work package | M03 / `M03-RND-M2-ANIMATED-INSTANCE-MATERIALIZER-001` |
|
||||
| Owners | Animated M2 duplicate/batch construction, render settings, playback startup and attachment |
|
||||
| Last verified | Worktree `work/sindo-main-codex/m03-m2-animated-instance-materializer`, 2026-07-18 |
|
||||
| Profiles/capabilities | Imported GLB and native experimental animated M2 instances |
|
||||
|
||||
## Purpose
|
||||
|
||||
Materialize one ordered animated M2 build batch on the main thread. The service
|
||||
duplicates an accepted prototype, preserves historical names and transforms,
|
||||
applies visibility/shadow settings recursively, starts native/imported playback,
|
||||
attaches a non-empty batch and returns detached native diagnostic entries.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Plan build batches, advance job cursors or consume render budgets.
|
||||
- Load, finalize, select or cache animated prototypes.
|
||||
- Select animations or implement native animator deformation.
|
||||
- Format logs, normalize paths, assign Editor owners or clean up tile roots.
|
||||
- Materialize static M2 `MultiMesh` batches.
|
||||
|
||||
## Context and boundaries
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Loader[StreamingWorldLoader adapter] --> Materializer[M2AnimatedInstanceMaterializer]
|
||||
Prototype[Accepted animated prototype] --> Materializer
|
||||
Plan[Transforms and batch slice] --> Materializer
|
||||
Materializer --> Playback[M2AnimationPlaybackController]
|
||||
Materializer --> Finalizer[M2AnimatedSceneFinalizer traversal]
|
||||
Materializer --> Batch[Attached animated batch]
|
||||
Materializer --> Diagnostics[Detached diagnostic entries]
|
||||
```
|
||||
|
||||
Allowed dependencies are Godot Node/Node3D/GeometryInstance3D APIs and the
|
||||
accepted animated finalizer and playback services. Resource loading, files,
|
||||
workers, caches, scheduler policy, Editor ownership and `MultiMesh` are forbidden.
|
||||
|
||||
## Public API
|
||||
|
||||
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|
||||
|---|---|---|---|---|
|
||||
| `materialize_batch(parent, path, prototype, transforms, start, count, serial, visibility_end, visibility_margin, cast_shadows, native_script, collect_diagnostics)` | Command/query | Build, start and attach one ordered animated batch | Main thread; borrowed inputs, parent owns result | Invalid/empty input or all failed duplicates returns `{}` |
|
||||
|
||||
## Inputs and outputs
|
||||
|
||||
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|
||||
|---|---|---|---|---|---|
|
||||
| Input | M2 parent root | Loader build job | Batch attachment | Borrowed Node3D | Tile/job lifetime |
|
||||
| Input | Relative path, start/count and serial | Batch planner/loader adapter | Names, phase and slice | Copied scalars | One call |
|
||||
| Input | Accepted animated prototype | Prototype cache | Duplicate source | Borrowed Node3D | Cache-owned |
|
||||
| Input | Ordered transforms | Placement grouping/build job | Instance transforms | Borrowed Array | Job lifetime |
|
||||
| Input | Visibility/shadow settings | Renderer configuration | Recursive render mutation | Copied scalars | One call |
|
||||
| Input | Native animator Script identity | Loader composition | Playback controller | Borrowed Script | Application lifetime |
|
||||
| Output | `batch_root` | Materializer | Parent SceneTree and loader Editor-owner adapter | Parent-owned Node3D | Tile lifetime |
|
||||
| Output | `native_diagnostics` entries | Playback controller/materializer | Loader log adapter | Caller-owned Array/Dictionaries | Debug call |
|
||||
|
||||
Side effects are Node duplication, instance naming/transform assignment,
|
||||
recursive geometry mutation, playback mutation and SceneTree attachment. The
|
||||
service retains no batch, prototype, transform or diagnostic references.
|
||||
|
||||
## Data flow
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Slice[Ordered transform slice] --> Duplicate[Duplicate prototype with signals, groups and scripts]
|
||||
Duplicate --> Identity[Assign historical name and transform]
|
||||
Identity --> Render[Apply visibility margin and shadow recursively]
|
||||
Render --> NativeCopy[Copy native animator runtime fields]
|
||||
NativeCopy --> AttachInstance[Attach duplicate to detached batch]
|
||||
AttachInstance --> Players[Inventory AnimationPlayers]
|
||||
Players --> Start[Start deterministic native/imported playback]
|
||||
Start --> More{More slice entries?}
|
||||
More -->|yes| Duplicate
|
||||
More -->|no, non-empty| AttachBatch[Attach batch to supplied parent]
|
||||
Start --> Diagnostics[Tag detached states with instance index]
|
||||
```
|
||||
|
||||
## Lifecycle/state
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Validating
|
||||
Validating --> Empty: invalid input
|
||||
Validating --> Building: valid slice
|
||||
Building --> Building: next successful duplicate
|
||||
Building --> Empty: no successful duplicates
|
||||
Building --> Attached: non-empty batch
|
||||
Empty --> [*]
|
||||
Attached --> [*]: parent/tile owner releases subtree
|
||||
```
|
||||
|
||||
The `RefCounted` service is stateless; lifecycle labels describe each batch call.
|
||||
|
||||
## Main sequence
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant L as StreamingWorldLoader
|
||||
participant M as M2AnimatedInstanceMaterializer
|
||||
participant F as M2AnimatedSceneFinalizer
|
||||
participant P as M2AnimationPlaybackController
|
||||
participant R as M2 parent root
|
||||
L->>M: materialize_batch(slice, prototype, render settings)
|
||||
loop ordered instance
|
||||
M->>M: duplicate, name, transform, render settings
|
||||
M->>P: copy native animator fields
|
||||
M->>F: animation_players_in_subtree(duplicate)
|
||||
M->>P: start_instance_playback(path, index, players, debug)
|
||||
end
|
||||
M->>R: add_child(non-empty batch)
|
||||
M-->>L: batch root and indexed diagnostics
|
||||
L->>L: format logs and assign Editor owner
|
||||
```
|
||||
|
||||
## Dependency diagram
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
Loader[StreamingWorldLoader] --> Materializer[M2AnimatedInstanceMaterializer]
|
||||
Materializer --> Playback[M2AnimationPlaybackController]
|
||||
Materializer --> Finalizer[M2AnimatedSceneFinalizer]
|
||||
Materializer --> Engine[Node3D / GeometryInstance3D / SceneTree APIs]
|
||||
Loader --> EditorOwner[Editor ownership adapter]
|
||||
Loader --> BuildJob[Build cursor and budget]
|
||||
Materializer -. no dependency .-> Cache[Prototype/cache/load state]
|
||||
Materializer -. no dependency .-> MultiMesh[Static M2 materialization]
|
||||
```
|
||||
|
||||
## Ownership, threading and resources
|
||||
|
||||
- Loader/build state borrows the accepted prototype and ordered transform Array.
|
||||
- Each successful duplicate becomes a child of the detached batch immediately.
|
||||
- A non-empty batch transfers to the supplied parent; the parent owns its lifetime.
|
||||
- A batch with no successful duplicates is queued for deletion and no reference returns.
|
||||
- Playback/finalizer services borrow each duplicate only during synchronous calls.
|
||||
- Returned diagnostic entries contain the stable instance index and a detached state.
|
||||
- All Node duplication, mutation and attachment is main-thread-only.
|
||||
|
||||
## Errors, cancellation and recovery
|
||||
|
||||
| Failure | Detection | Behavior | Diagnostic | Recovery |
|
||||
|---|---|---|---|---|
|
||||
| Null parent/prototype | Entry guard | Return `{}` without mutation | Contract verifier | Correct composition/cache state |
|
||||
| Empty transforms or non-positive count | Entry guard | Return `{}` | Contract verifier | Planner supplies a non-empty slice |
|
||||
| Duplicate is not Node3D | Cast result | Skip that entry and preserve order of successes | Child-count contract | Repair prototype scene root |
|
||||
| Every duplicate fails | Empty batch check | Queue empty batch, return `{}` | Empty-result contract | Rebuild/import prototype |
|
||||
| Shutdown during/after call | Outside service | Call is synchronous; loader owns cancellation and subtree release | Shutdown regression | Loader cancels job/releases parent |
|
||||
|
||||
Transform bounds remain a planner/build-job precondition exactly as before the
|
||||
extraction; this service does not clamp or silently reorder an invalid slice.
|
||||
|
||||
## Configuration and capabilities
|
||||
|
||||
| Setting/capability | Default | Profile | Runtime mutable | Effect |
|
||||
|---|---|---|---|---|
|
||||
| Duplicate flags | Signals + groups + scripts | All | No | Preserves historical instance behavior |
|
||||
| Visibility end/margin | Loader renderer settings / chunk size | All | Yes, between calls | Applied to every GeometryInstance3D descendant |
|
||||
| Shadow mode | Loader `m2_cast_shadows` | All | Yes, between calls | Enables/disables descendant shadow casting |
|
||||
| Native diagnostics | Loader `debug_streaming` | Debug | Yes | Collects indexed state only when requested |
|
||||
|
||||
## Persistence, cache and migration
|
||||
|
||||
No data is serialized and no cache/schema version changes. Editor owner
|
||||
assignment remains a loader concern after attachment, so generated-scene
|
||||
persistence behavior and migration remain unchanged.
|
||||
|
||||
## Diagnostics and observability
|
||||
|
||||
- The materializer emits no logs, metrics or debug views.
|
||||
- Native state remains detached and is tagged with the absolute instance index.
|
||||
- Loader preserves the existing normalized-path `M2_NATIVE_ANIMATOR` log text.
|
||||
- No correlation ID is introduced; path/index remains the existing identity.
|
||||
|
||||
## Verification
|
||||
|
||||
- `verify_m2_animated_instance_materializer.gd` covers invalid input, names,
|
||||
order, transforms, recursive visibility/margin/shadows, playback, native field
|
||||
references, indexed diagnostics, prototype immutability and ownership boundaries.
|
||||
- Playback/finalizer/pipeline/build/prototype/shutdown/material/facade/internal-
|
||||
access regressions protect adjacent behavior.
|
||||
- Fidelity evidence is exact extraction of existing flags/order/settings/calls;
|
||||
no original-client visual comparison or proprietary asset result is claimed.
|
||||
- Performance budget: 1,000 synthetic Node3D instances under one second.
|
||||
|
||||
## Extension points
|
||||
|
||||
A later facade-owned animated presentation command may supply the same explicit
|
||||
batch contract. Async SceneTree mutation, callbacks and a generic scene factory
|
||||
are intentionally excluded until measurements show a need.
|
||||
|
||||
## Capability status
|
||||
|
||||
| Capability | Status | Evidence | Gap/next step |
|
||||
|---|---|---|---|
|
||||
| Ordered animated instance/batch materialization | Implemented extraction | Synthetic batch contract verifier | Proprietary asset traversal pending |
|
||||
| Recursive visibility/shadow settings | Implemented extraction | Nested geometry fixture | Visual distance/shadow comparison pending |
|
||||
| Native/imported playback startup | Delegated to implemented controller | Playback + materializer verifiers | Full WoW animation-state mapping pending |
|
||||
| Editor persistence | Existing loader-owned | Source boundary and editor helper | Editor integration fixture remains separate |
|
||||
|
||||
## Known gaps and risks
|
||||
|
||||
- The existing build planner must continue to guarantee valid transform bounds.
|
||||
- Default animation selection and native deformation retain their documented gaps.
|
||||
- No private asset, paired original-client, descriptor-pressure, leak or p95/p99 run exists.
|
||||
- Recursive render mutation remains proportional to duplicate subtree size.
|
||||
|
||||
## Source map
|
||||
|
||||
| Path | Responsibility |
|
||||
|---|---|
|
||||
| `src/render/m2/m2_animated_instance_materializer.gd` | Duplicate, render mutation, playback startup and non-empty batch attachment |
|
||||
| `src/render/m2/m2_animation_playback_controller.gd` | Phase, selection and native/imported playback mutation |
|
||||
| `src/render/m2/m2_animated_scene_finalizer.gd` | AnimationPlayer inventory for each duplicate |
|
||||
| `src/scenes/streaming/streaming_world_loader.gd` | Build adapter, diagnostic formatting and Editor ownership |
|
||||
| `src/tools/verify_m2_animated_instance_materializer.gd` | Batch/render/playback/boundary/timing regression |
|
||||
|
||||
## Related decisions and references
|
||||
|
||||
- [`m2-animation-playback-controller.md`](m2-animation-playback-controller.md)
|
||||
- [`m2-animated-scene-finalizer.md`](m2-animated-scene-finalizer.md)
|
||||
- [`m2-build-batch-planner.md`](m2-build-batch-planner.md)
|
||||
- [`world-renderer.md`](world-renderer.md)
|
||||
- [`../../RENDER.md`](../../RENDER.md)
|
||||
- [`../../targets/roadmap/02-rendering-and-graphics.md`](../../targets/roadmap/02-rendering-and-graphics.md)
|
||||
@@ -0,0 +1,212 @@
|
||||
# M2 Animated Scene Finalizer
|
||||
|
||||
## Metadata
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Status | Implemented extraction |
|
||||
| Target/work package | M03 / `M03-RND-M2-ANIMATED-SCENE-FINALIZER-001` |
|
||||
| Owners | Detached animated candidate, material repair and player validation |
|
||||
| Last verified | Worktree `work/sindo-main-codex/m03-m2-animated-scene-finalizer`, 2026-07-18 |
|
||||
| Profiles/capabilities | Existing optional cached-GLB animated M2 path |
|
||||
|
||||
## Purpose
|
||||
|
||||
Finalize one loaded animated M2 scene on the main thread: instantiate a valid
|
||||
detached Node3D candidate, copy the historical static-prototype material mapping
|
||||
and accept only candidates containing AnimationPlayer descendants.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Call ResourceLoader or interpret threaded-load statuses.
|
||||
- Choose animation eligibility, cache paths, permits or material prototypes.
|
||||
- Adopt prototypes, mark static fallback or select/play animations.
|
||||
- Process native M2 animators or static Mesh rebuilds.
|
||||
|
||||
## Context and boundaries
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
ResourceFinalizer[M2AnimationResourceFinalizer] -->|loaded Resource and material source| Finalizer[M2AnimatedSceneFinalizer]
|
||||
Finalizer -->|accepted Node3D and player count| ResourceFinalizer
|
||||
ResourceFinalizer --> Cache[M2PrototypeCacheState]
|
||||
```
|
||||
|
||||
Allowed dependencies are Godot scene/resource/material types. ResourceLoader,
|
||||
filesystem, workers, scheduler, builders, raw repositories, prototype cache and
|
||||
other application layers are forbidden.
|
||||
|
||||
## Public API
|
||||
|
||||
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|
||||
|---|---|---|---|---|
|
||||
| `instantiate_candidate(resource)` | Ownership query | Instantiate PackedScene as detached Node3D | Main thread; caller receives valid candidate | Unsupported/invalid null; created wrong-type root freed |
|
||||
| `repair_materials(animated_root, material_source_root)` | Command | Apply historical material overrides | Main thread; borrowed roots | Null/empty roots no-op |
|
||||
| `finalize_candidate(candidate_root)` | Ownership command/query | Require AnimationPlayer and transfer root/count | Main thread; detached candidate | Null empty; rejected candidate freed |
|
||||
| `mesh_instances_in_subtree(root)` | Query | Depth-first MeshInstance3D inventory | Main thread; borrowed subtree | Null returns empty |
|
||||
| `animation_players_in_subtree(root)` | Query | Depth-first AnimationPlayer inventory | Main thread; borrowed subtree | Null returns empty |
|
||||
|
||||
## Inputs and outputs
|
||||
|
||||
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|
||||
|---|---|---|---|---|---|
|
||||
| Input | Loaded Resource | Animation resource finalizer | Candidate instantiator | Borrowed Resource | One finalize permit |
|
||||
| Input | Static material prototype Node3D | Loader cache/build adapter | Material repair | Borrowed Node | One repair call |
|
||||
| Input | Detached animated candidate | Instantiator | Repair/final validation | Finalizer then caller/release | One attempt |
|
||||
| Output | Accepted prototype and player count | Finalizer | Resource-finalizer adoption/log adapter | Exact Node transferred | Shutdown cache lifetime |
|
||||
| Output | Depth-first engine-node arrays | Traversal | Loader preparation/playback | Borrowed references | One call |
|
||||
|
||||
Side effects are PackedScene instantiation, surface override assignment and
|
||||
synchronous destruction of rejected detached roots.
|
||||
|
||||
## Data flow
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Resource[Loaded Resource] --> Packed{PackedScene?}
|
||||
Packed -->|no| Reject[Return null]
|
||||
Packed -->|yes| Instantiate[Instantiate]
|
||||
Instantiate --> Root{Node3D?}
|
||||
Root -->|no| FreeWrong[Free root and reject]
|
||||
Root -->|yes| Repair[Repair materials]
|
||||
Repair --> Players{AnimationPlayer exists?}
|
||||
Players -->|no| FreeCandidate[Free and reject]
|
||||
Players -->|yes| Transfer[Transfer exact root and count]
|
||||
```
|
||||
|
||||
## Lifecycle/state
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Absent
|
||||
Absent --> Candidate: instantiate valid scene
|
||||
Candidate --> Candidate: repair materials
|
||||
Candidate --> Accepted: player found
|
||||
Candidate --> Released: no player
|
||||
Accepted --> [*]: ownership transferred
|
||||
Released --> [*]
|
||||
```
|
||||
|
||||
## Main sequence
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant R as M2AnimationResourceFinalizer
|
||||
participant F as M2AnimatedSceneFinalizer
|
||||
participant C as M2PrototypeCacheState
|
||||
R->>F: instantiate_candidate(Resource)
|
||||
F-->>R: detached Node3D or null
|
||||
R->>F: repair_materials(candidate, source)
|
||||
R->>F: finalize_candidate(candidate)
|
||||
alt accepted
|
||||
F-->>R: exact Node3D and player count
|
||||
R->>C: adopt animated prototype
|
||||
else rejected
|
||||
F-->>R: empty; candidate freed
|
||||
R->>C: mark animation static
|
||||
end
|
||||
```
|
||||
|
||||
## Dependency diagram
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
ResourceFinalizer[M2AnimationResourceFinalizer] --> Finalizer[M2AnimatedSceneFinalizer]
|
||||
Finalizer --> Engine[PackedScene / Node3D / Mesh / Material / AnimationPlayer]
|
||||
ResourceFinalizer --> Resource[ResourceLoader]
|
||||
Loader --> Budget[RenderBudgetScheduler]
|
||||
ResourceFinalizer --> Prototype[M2PrototypeCacheState]
|
||||
Finalizer -. no dependency .-> Resource
|
||||
Finalizer -. no dependency .-> Budget
|
||||
Finalizer -. no dependency .-> Prototype
|
||||
```
|
||||
|
||||
## Ownership, threading and resources
|
||||
|
||||
- Every method runs synchronously on the renderer main thread.
|
||||
- A valid candidate is detached and finalizer-owned until acceptance.
|
||||
- Acceptance transfers the exact Node3D to the resource-finalizer/cache path.
|
||||
- Rejection frees the candidate synchronously, including wrong-type roots.
|
||||
- Traversal results borrow Nodes; source materials remain Resource-owned.
|
||||
|
||||
## Errors, cancellation and recovery
|
||||
|
||||
| Failure | Detection | Behavior | Diagnostic | Recovery |
|
||||
|---|---|---|---|---|
|
||||
| Null/unsupported Resource | Type guard | Return null | Dedicated verifier | Resource finalizer marks static |
|
||||
| Wrong root type | Instantiated type guard | Free and return null | Node-count regression | Rebuild cache |
|
||||
| No material source/meshes | Null/empty traversal | Leave materials unchanged | Material fixture | Imported materials remain |
|
||||
| Missing source surface | Material lookup | First source material fallback | Mapping fixture | Repair static source |
|
||||
| No AnimationPlayer | Descendant inventory | Free and return empty | Lifetime regression | Resource finalizer marks static |
|
||||
| Shutdown/cancellation | Not owned | No retained state | N/A | Loader drains first |
|
||||
|
||||
## Configuration and capabilities
|
||||
|
||||
| Setting/capability | Default | Profile | Runtime mutable | Effect |
|
||||
|---|---|---|---|---|
|
||||
| Finalize permit | `m2_animation_finalize_ops_per_tick = 1` | Existing profiles | Yes | Caller bounds calls |
|
||||
| Material mapping | Depth-first/index-clamped | All | No | Preserves current appearance |
|
||||
| Required players | At least one | All | No | Rejects non-animated scenes |
|
||||
|
||||
## Persistence, cache and migration
|
||||
|
||||
The service retains no state and serializes nothing. GLB/cache formats, material
|
||||
versions and prototype lifetimes are unchanged; no rebake is required.
|
||||
|
||||
## Diagnostics and observability
|
||||
|
||||
- Accepted result reports player count for the existing `M2_ANIM_CACHE` log.
|
||||
- Traversal order is deterministic depth-first preorder.
|
||||
- The service owns no logging or metrics.
|
||||
|
||||
## Verification
|
||||
|
||||
- `verify_m2_animated_scene_finalizer.gd` covers invalid Resources, wrong-root
|
||||
release, exact transfer, player traversal/count, override priority, fallback,
|
||||
source-index mapping, source boundaries and timing.
|
||||
- Adjacent animation pipeline/prototype/shutdown/material/build tests protect callers.
|
||||
- Fidelity evidence is exact mapping/lifecycle extraction; no asset-backed or
|
||||
original-client visual parity claim is made.
|
||||
- Performance budget: 10,000 depth-eight player inventories under one second.
|
||||
|
||||
## Extension points
|
||||
|
||||
Animation playback policy and native animator field copying now belong to
|
||||
`M2AnimationPlaybackController`; accepted instance/batch materialization is owned
|
||||
by `M2AnimatedInstanceMaterializer`.
|
||||
|
||||
## Capability status
|
||||
|
||||
| Capability | Status | Evidence | Gap/next step |
|
||||
|---|---|---|---|
|
||||
| PackedScene candidate | Implemented extraction | Type/lifetime fixture | Proprietary corrupt corpus pending |
|
||||
| Material repair | Implemented extraction | Exact-reference mapping | Asset-backed comparison pending |
|
||||
| Player validation | Implemented extraction | Nested traversal/transfer | Multi-player GLB corpus pending |
|
||||
|
||||
## Known gaps and risks
|
||||
|
||||
- Material matching remains positional rather than semantic.
|
||||
- Accepted prototypes remain unbounded until final loader shutdown.
|
||||
- No private traversal, descriptor-pressure, p95/p99 or paired-client run exists.
|
||||
|
||||
## Source map
|
||||
|
||||
| Path | Responsibility |
|
||||
|---|---|
|
||||
| `src/render/m2/m2_animated_scene_finalizer.gd` | Candidate ownership, traversal, material repair and validation |
|
||||
| `src/render/m2/m2_animation_playback_controller.gd` | Accepted-instance playback and native animator startup |
|
||||
| `src/render/m2/m2_animated_instance_materializer.gd` | Per-duplicate player inventory consumer and batch owner |
|
||||
| `src/render/m2/m2_animation_load_pipeline_state.gd` | Pending/terminal records before finalization |
|
||||
| `src/render/m2/m2_prototype_cache_state.gd` | Accepted prototype/static-only outcomes |
|
||||
| `src/render/m2/m2_animation_resource_finalizer.gd` | I/O, candidate composition, adoption and logs |
|
||||
| `src/scenes/streaming/streaming_world_loader.gd` | Permits and material-source lookup |
|
||||
| `src/tools/verify_m2_animated_scene_finalizer.gd` | Scene/material/lifetime/boundary/timing regression |
|
||||
|
||||
## Related decisions and references
|
||||
|
||||
- [`m2-animation-playback-controller.md`](m2-animation-playback-controller.md)
|
||||
- [`m2-animation-load-pipeline-state.md`](m2-animation-load-pipeline-state.md)
|
||||
- [`m2-prototype-cache-state.md`](m2-prototype-cache-state.md)
|
||||
- [`world-renderer.md`](world-renderer.md)
|
||||
- [`../../RENDER.md`](../../RENDER.md)
|
||||
- [`../../targets/roadmap/02-rendering-and-graphics.md`](../../targets/roadmap/02-rendering-and-graphics.md)
|
||||
@@ -0,0 +1,231 @@
|
||||
# M2 Animation Load Pipeline State
|
||||
|
||||
## Metadata
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Status | Implemented extraction |
|
||||
| Target/work package | M03 / `M03-RND-M2-ANIMATION-LOAD-PIPELINE-001` |
|
||||
| Owners | Animated M2 threaded request records and terminal finalize FIFO |
|
||||
| Last verified | Worktree `work/sindo-main-codex/m03-m2-animation-load-pipeline`, 2026-07-17 |
|
||||
| Profiles/capabilities | Existing optional cached-GLB animated M2 path |
|
||||
|
||||
## Purpose
|
||||
|
||||
Own cross-frame bookkeeping between a successful animated M2 ResourceLoader
|
||||
request, terminal polling and budgeted main-thread scene finalization. This is
|
||||
an exact state extraction; cached animation eligibility and request admission
|
||||
belong to `M2CachedAnimationResourceObserver`; terminal I/O/outcomes belong to
|
||||
`M2AnimationResourceFinalizer`, while validation and retained Node lifetime
|
||||
belong to `M2AnimatedSceneFinalizer` and `M2PrototypeCacheState`.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Select cache paths, interpret ResourceLoader statuses or perform I/O.
|
||||
- Decide animated/static fallback or own prototype Nodes.
|
||||
- Instantiate PackedScenes, repair materials or consume render permits.
|
||||
- Merge the distinct animated and static-Mesh pipelines.
|
||||
|
||||
## Context and boundaries
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Observer[M2CachedAnimationResourceObserver] --> IO[ResourceLoader request]
|
||||
Observer --> State[M2AnimationLoadPipelineState]
|
||||
Finalizer[M2AnimationResourceFinalizer] --> IO
|
||||
Finalizer --> State
|
||||
State -->|detached pending records| Finalizer
|
||||
Finalizer -->|opaque terminal status| State
|
||||
State -->|completion FIFO| Finalizer
|
||||
Loader[StreamingWorldLoader] --> Finalizer
|
||||
Loader --> Budget[M2_ANIMATION_FINALIZE permit]
|
||||
Loader --> Prototype[M2PrototypeCacheState]
|
||||
```
|
||||
|
||||
Allowed dependencies are value containers, Strings and opaque integer statuses.
|
||||
ResourceLoader, workers, scheduler, Node/Resource/Mesh ownership and other
|
||||
renderer services are forbidden.
|
||||
|
||||
## Public API
|
||||
|
||||
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|
||||
|---|---|---|---|---|
|
||||
| `remember_request(normalized_relative_path, resource_path)` | Command/query | Insert one pending request | Renderer main thread; until transition/clear | Empty/duplicate false |
|
||||
| `has_request(path)` | Query | Pending-request dedupe | Main thread | Empty/unknown false |
|
||||
| `request_records_snapshot()` | Query | Detached pending records in insertion order | Main thread; caller-owned | None |
|
||||
| `complete_request(path, terminal_status)` | Command/query | Move copied record into completion FIFO | Main thread after poll | Unknown false |
|
||||
| `discard_request(path)` | Command/query | Remove without finalization | Main thread | Unknown false |
|
||||
| `has_finalize_record()` / `pop_finalize_record()` | Query/command | Drain completion-order FIFO | Main-thread budget drain | Empty pop returns `{}` |
|
||||
| `total_work_count()` | Query | Pending plus finalize metric | Main thread | None |
|
||||
| `pending_request_count()` / `finalize_record_count()` | Query | Stage diagnostics | Main thread | None |
|
||||
| `clear()` | Command | Drop bookkeeping after caller I/O drain/reset | Main thread | Idempotent; no I/O drain |
|
||||
| `diagnostic_snapshot()` | Query | Detached paths/status records | Main thread | No Resources exposed |
|
||||
|
||||
## Inputs and outputs
|
||||
|
||||
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|
||||
|---|---|---|---|---|---|
|
||||
| Input | Normalized M2 path and GLB Resource path | Loader request adapter | Pending map | Copied Strings | Until completion/discard/clear |
|
||||
| Input | Opaque terminal status | Resource finalizer | Finalize FIFO | Integer value | Until pop/clear |
|
||||
| Output | Detached pending records | State | Resource finalizer/shutdown adapter | Caller-owned copies | One pass |
|
||||
| Output | Oldest completion record | State | Resource finalizer | Transferred Dictionary | One finalize attempt |
|
||||
| Output | Detached diagnostics | State | Verifier/future metrics | Caller-owned copies | Snapshot lifetime |
|
||||
|
||||
Side effects are limited to collection mutation and retaining String/integer values.
|
||||
|
||||
## Data flow
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start[Successful threaded request] --> Remember[Remember request]
|
||||
Remember --> Poll[Resource finalizer polls detached snapshot]
|
||||
Poll --> Terminal{Loaded or failed?}
|
||||
Terminal -->|no| Poll
|
||||
Terminal -->|yes| Complete[Complete with opaque status]
|
||||
Complete --> FIFO[Finalize FIFO]
|
||||
FIFO --> Permit{Permit available?}
|
||||
Permit -->|no| FIFO
|
||||
Permit -->|yes| Pop[Pop oldest record]
|
||||
Pop --> Finalize[Resource finalizer loads/instantiates or marks static]
|
||||
```
|
||||
|
||||
## Lifecycle/state
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Absent
|
||||
Absent --> Pending: remember
|
||||
Pending --> TerminalQueued: complete
|
||||
Pending --> Absent: discard or clear
|
||||
TerminalQueued --> Absent: pop or clear
|
||||
```
|
||||
|
||||
## Main sequence
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant O as CachedAnimationObserver
|
||||
participant L as StreamingWorldLoader
|
||||
participant F as AnimationResourceFinalizer
|
||||
participant R as ResourceLoader
|
||||
participant S as M2AnimationLoadPipelineState
|
||||
participant P as M2PrototypeCacheState
|
||||
O->>R: load_threaded_request(GLB)
|
||||
O->>S: remember_request(path, GLB)
|
||||
loop frames
|
||||
L->>F: poll terminal requests
|
||||
F->>S: request_records_snapshot()
|
||||
F->>R: load_threaded_get_status(GLB)
|
||||
end
|
||||
F->>S: complete_request(path, status)
|
||||
L->>F: prepare after permit
|
||||
F->>S: pop_finalize_record()
|
||||
F->>R: load_threaded_get(GLB)
|
||||
F->>P: adopt animated prototype or mark static
|
||||
```
|
||||
|
||||
## Ownership, threading and resources
|
||||
|
||||
- Main thread serializes all mutation.
|
||||
- State owns only request/finalize Dictionaries with copied paths and statuses.
|
||||
- Loader drains pending ResourceLoader paths before orderly shutdown clear.
|
||||
- Resource finalizer owns terminal I/O/outcomes and composes scene validation;
|
||||
prototype state owns accepted detached Nodes and static-only outcomes.
|
||||
|
||||
## Dependency diagram
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
Observer[M2CachedAnimationResourceObserver] --> State[M2AnimationLoadPipelineState]
|
||||
Observer --> Resource[ResourceLoader request]
|
||||
Finalizer[M2AnimationResourceFinalizer] --> State[M2AnimationLoadPipelineState]
|
||||
Finalizer --> Resource
|
||||
Loader[StreamingWorldLoader] --> Finalizer
|
||||
Loader --> Budget[RenderBudgetScheduler]
|
||||
Loader --> Prototype[M2PrototypeCacheState]
|
||||
State -. no dependency .-> Resource
|
||||
State -. no dependency .-> Budget
|
||||
State -. no dependency .-> Prototype
|
||||
```
|
||||
|
||||
## Errors, cancellation and recovery
|
||||
|
||||
| Failure | Detection | Behavior | Diagnostic | Recovery |
|
||||
|---|---|---|---|---|
|
||||
| Empty/duplicate request | State guard | Reject unchanged | Contract verifier | Correct caller or request later |
|
||||
| Request start/cache miss | Cached observer | No insertion; mark static | Observer contract | Cache correction/reload |
|
||||
| Non-terminal status | Loader | Keep pending | Existing metric | Poll next frame |
|
||||
| Failed terminal load | Popped status | Loader marks static | Existing behavior | Future map/session reload |
|
||||
| Empty defensive path | Loader poll | Discard and mark static | Source contract | Correct producer |
|
||||
| Shutdown | Loader drains pending paths | Clear state | Source/shutdown regressions | New loader starts empty |
|
||||
|
||||
## Configuration and capabilities
|
||||
|
||||
| Setting/capability | Default | Profile | Runtime mutable | Effect |
|
||||
|---|---|---|---|---|
|
||||
| `enable_m2_animated_instances` | `true` | Existing renderer profile | Yes | Enables caller request path |
|
||||
| `m2_animation_finalize_ops_per_tick` | `1` | Quality/custom | Yes | Bounds caller FIFO drain |
|
||||
| Animated allow/deny/primitive rules | Existing values | Existing renderer profile | Yes | Observer filters before state insertion |
|
||||
|
||||
## Persistence, cache and migration
|
||||
|
||||
State is not serialized. Cache formats and prototype lifetimes are unchanged;
|
||||
no rebake or migration is required.
|
||||
|
||||
## Diagnostics and observability
|
||||
|
||||
- `total_work_count()` preserves all three historical `m2_animation` metrics.
|
||||
- Snapshots expose only paths and opaque statuses, never Resources or Nodes.
|
||||
- Normalized M2 path is the correlation key; existing loader logs are unchanged.
|
||||
|
||||
## Verification
|
||||
|
||||
- `verify_m2_animation_load_pipeline_state.gd` covers validation, dedupe,
|
||||
insertion order, completion FIFO, opaque status, discard, detached snapshots,
|
||||
source boundaries and 100-by-256 timing.
|
||||
- Adjacent renderer, scheduler, prototype and shutdown regressions cover callers.
|
||||
- Fidelity evidence is exact state/lifecycle extraction; no visual 3.3.5a parity
|
||||
or proprietary asset-backed claim is made.
|
||||
- Performance budget: 25,600 request/complete/pop transitions under one second.
|
||||
|
||||
## Extension points
|
||||
|
||||
ResourceLoader polling/finalization now belongs to a sibling service without
|
||||
changing this value-only state contract.
|
||||
|
||||
## Capability status
|
||||
|
||||
| Capability | Status | Evidence | Gap/next step |
|
||||
|---|---|---|---|
|
||||
| Animated request/finalize state | Implemented extraction | Synthetic contract/source/timing verifier | Asset-backed traversal/leak/p95/p99 pending |
|
||||
| Cached request admission and GLB selection | Implemented in observer | Policy/GLB/source verifier | Asset-backed traversal pending |
|
||||
| Terminal ResourceLoader polling | Implemented finalizer extraction | Status/order/source regressions | Asset-backed traversal pending |
|
||||
| Animated prototype outcomes | Implemented extraction | Prototype cache verifier | Asset-backed animation fidelity pending |
|
||||
|
||||
## Known gaps and risks
|
||||
|
||||
- Dictionary records preserve the existing dynamic ResourceLoader boundary.
|
||||
- `clear()` does not drain I/O; caller ordering remains mandatory.
|
||||
- No private asset traversal, leak/descriptor-pressure or paired-client run is included.
|
||||
|
||||
## Source map
|
||||
|
||||
| Path | Responsibility |
|
||||
|---|---|
|
||||
| `src/render/m2/m2_animation_load_pipeline_state.gd` | Pending records, completion FIFO and metrics |
|
||||
| `src/render/m2/m2_animation_resource_finalizer.gd` | Terminal polling, Resource load and prototype outcome |
|
||||
| `src/render/m2/m2_cached_animation_resource_observer.gd` | Eligibility, GLB selection and request admission |
|
||||
| `src/render/m2/m2_animated_scene_finalizer.gd` | Candidate instantiation, material repair and player validation |
|
||||
| `src/render/m2/m2_prototype_cache_state.gd` | Animated prototype/static-only outcomes |
|
||||
| `src/scenes/streaming/streaming_world_loader.gd` | Permit loop, material lookup and composition |
|
||||
| `src/tools/verify_m2_animation_load_pipeline_state.gd` | Lifecycle/boundary/timing regression |
|
||||
|
||||
## Related decisions and references
|
||||
|
||||
- [`m2-animated-scene-finalizer.md`](m2-animated-scene-finalizer.md)
|
||||
- [`m2-animation-resource-finalizer.md`](m2-animation-resource-finalizer.md)
|
||||
- [`m2-prototype-cache-state.md`](m2-prototype-cache-state.md)
|
||||
- [`m2-mesh-load-pipeline-state.md`](m2-mesh-load-pipeline-state.md)
|
||||
- [`world-renderer.md`](world-renderer.md)
|
||||
- [`../../RENDER.md`](../../RENDER.md)
|
||||
- [`../../targets/roadmap/02-rendering-and-graphics.md`](../../targets/roadmap/02-rendering-and-graphics.md)
|
||||
@@ -0,0 +1,214 @@
|
||||
# M2 Animation Playback Controller
|
||||
|
||||
## Metadata
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Status | Implemented extraction |
|
||||
| Target/work package | M03 / `M03-RND-M2-ANIMATION-PLAYBACK-001` |
|
||||
| Owners | Per-instance AnimationPlayer/native animator playback mutation |
|
||||
| Last verified | Worktree `work/sindo-main-codex/m03-m2-animation-playback`, 2026-07-18 |
|
||||
| Profiles/capabilities | Imported GLB and native experimental animated M2 instances |
|
||||
|
||||
## Purpose
|
||||
|
||||
Apply deterministic playback to one already duplicated M2 instance: copy native
|
||||
animator runtime fields, derive a stable phase, prepare/phase native animators,
|
||||
select an imported animation and configure linear looping/playback/seek.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Duplicate, name, transform or attach instances/batch roots.
|
||||
- Apply visibility, shadows or Editor ownership.
|
||||
- Load/finalize/cache animated scenes or decide eligibility.
|
||||
- Change animation priorities, phase formula or native animator implementation.
|
||||
|
||||
## Context and boundaries
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Prototype[Animated prototype] --> Materializer[M2AnimatedInstanceMaterializer]
|
||||
Materializer -->|source and duplicate| Playback[M2AnimationPlaybackController]
|
||||
Finalizer[M2AnimatedSceneFinalizer player inventory] --> Playback
|
||||
Playback --> Native[M2NativeAnimator mutation]
|
||||
Playback --> Imported[AnimationPlayer mutation]
|
||||
```
|
||||
|
||||
Allowed dependencies are supplied Nodes, Script identity, AnimationPlayer and
|
||||
Animation resources. ResourceLoader, files, workers, caches, scheduler,
|
||||
MultiMesh, SceneTree attachment and application layers are forbidden.
|
||||
|
||||
## Public API
|
||||
|
||||
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|
||||
|---|---|---|---|---|
|
||||
| `copy_native_animator_data(source_root, target_root, script)` | Command | Copy five runtime fields in depth-first pair order | Main thread; borrowed nodes | Null/script mismatch yields no pairs |
|
||||
| `start_instance_playback(root, path, index, script, players, collect_diagnostics)` | Command/query | Start native/imported playback and optionally return native state | Main thread; one instance | Null/empty inventories no-op |
|
||||
| `native_animators_in_subtree(root, script)` | Query | Exact-script depth-first inventory | Main thread; borrowed nodes | Null root/script returns empty |
|
||||
| `phase_for_instance(path, index)` | Pure query | Stable phase in `[0, 1)` | Any thread; scalar | Historical hash behavior retained |
|
||||
| `choose_default_animation(player, path)` | Pure engine query | Apply exact ordinary/fish/bird/fallback priority | Main thread; borrowed player | Null/no animations returns empty |
|
||||
|
||||
## Inputs and outputs
|
||||
|
||||
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|
||||
|---|---|---|---|---|---|
|
||||
| Input | Prototype and duplicate subtrees | M2 animated instance materializer | Native field copy | Borrowed Nodes | One duplicate |
|
||||
| Input | Exact native animator Script | Loader composition | Native inventory | Borrowed Script | Call-local |
|
||||
| Input | Relative path and instance index | Build batch adapter | Phase/selection | Copied values | One start |
|
||||
| Input | AnimationPlayer inventory | Animated scene finalizer traversal | Imported playback | Borrowed players | One start |
|
||||
| Output | Mutated native/imported playback | Controller | Rendered instance | Nodes retain state/resources | Instance lifetime |
|
||||
| Output | Detached native diagnostic records | Controller | Loader log adapter | Caller-owned Dictionaries | Debug call |
|
||||
|
||||
Side effects are native field assignment, prepare/phase calls, animation loop
|
||||
mutation, play and seek. The service retains no inputs.
|
||||
|
||||
## Data flow
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Identity[Path and index] --> Phase[Stable hash phase]
|
||||
NativeInventory[Exact-script native inventory] --> Prepare[prepare runtime if available]
|
||||
Phase --> NativePhase[set native phase]
|
||||
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?}
|
||||
Diagnostics -->|yes| Snapshot[Detached runtime state]
|
||||
```
|
||||
|
||||
## Lifecycle/state
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Unstarted
|
||||
Unstarted --> Prepared: copy native data and start
|
||||
Prepared --> Playing: native phase and/or AnimationPlayer play
|
||||
Playing --> Playing: repeated deterministic start
|
||||
Playing --> [*]: instance owner releases subtree
|
||||
```
|
||||
|
||||
The controller itself is stateless; lifecycle labels describe borrowed instance state.
|
||||
|
||||
## Main sequence
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant M as M2AnimatedInstanceMaterializer
|
||||
participant F as M2AnimatedSceneFinalizer
|
||||
participant P as M2AnimationPlaybackController
|
||||
participant N as M2NativeAnimator
|
||||
participant A as AnimationPlayer
|
||||
M->>P: copy_native_animator_data(prototype, duplicate, script)
|
||||
M->>F: animation_players_in_subtree(duplicate)
|
||||
F-->>M: ordered players
|
||||
M->>P: start_instance_playback(path, index, players, debug)
|
||||
P->>N: prepare_runtime and set_phase
|
||||
P->>A: choose, loop, play and seek
|
||||
P-->>M: optional detached native diagnostics
|
||||
M-->>M: tag states with instance index
|
||||
```
|
||||
|
||||
## Dependency diagram
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
Loader[StreamingWorldLoader] --> Materializer[M2AnimatedInstanceMaterializer]
|
||||
Materializer --> Playback[M2AnimationPlaybackController]
|
||||
Materializer --> Finalizer[M2AnimatedSceneFinalizer]
|
||||
Playback --> Engine[Node / Script / AnimationPlayer / Animation]
|
||||
Materializer --> Batch[SceneTree batch materialization]
|
||||
Loader --> NativeScript[M2NativeAnimator script]
|
||||
Playback -. no dependency .-> ResourceLoader
|
||||
Playback -. no dependency .-> Cache[M2PrototypeCacheState]
|
||||
Playback -. no dependency .-> Scheduler[RenderBudgetScheduler]
|
||||
```
|
||||
|
||||
## Ownership, threading and resources
|
||||
|
||||
- Materializer owns prototype duplication, instance/batch roots and SceneTree attachment.
|
||||
- Materializer uses finalizer traversal for borrowed AnimationPlayer references.
|
||||
- Controller borrows Nodes/Script/players only for the synchronous call.
|
||||
- Native arrays are assigned by reference exactly as before extraction.
|
||||
- Diagnostic Dictionaries are deep-duplicated before return.
|
||||
- Main thread performs all engine-object mutation; pure phase math is thread-safe.
|
||||
|
||||
## Errors, cancellation and recovery
|
||||
|
||||
| Failure | Detection | Behavior | Diagnostic | Recovery |
|
||||
|---|---|---|---|---|
|
||||
| Missing roots/script | Inventory guard | No native copy/start | Contract fixture | Correct composition |
|
||||
| Fewer target animators | Pair-count minimum | Copy available pairs only | Native copy fixture | Rebuild import/prototype |
|
||||
| No matching name | Ordered fallback | Substring, first name, then empty | Selection fixtures | Add compatible animation |
|
||||
| Empty animation selection | Empty result | Skip loop/play/seek for that player | Selection fixture | Static/native path continues |
|
||||
| Zero-length selected animation | Length guard | Play without seek | Playback contract | Imported timing remains engine-owned |
|
||||
| Cancellation/shutdown | Outside service | No retained state | N/A | Loader owns subtree release |
|
||||
|
||||
## Configuration and capabilities
|
||||
|
||||
| Setting/capability | Default | Profile | Runtime mutable | Effect |
|
||||
|---|---|---|---|---|
|
||||
| Phase buckets | 1000 | All | No | Stable per path/index desynchronization |
|
||||
| Loop mode | `LOOP_LINEAR` | All | No | Every available imported animation loops |
|
||||
| Native diagnostics | `debug_streaming` | Debug | Yes | Samples state only when requested |
|
||||
|
||||
## Persistence, cache and migration
|
||||
|
||||
No state or format is serialized. GLB/native cache formats, prototype lifetime
|
||||
and material versions are unchanged; no rebake is required.
|
||||
|
||||
## Diagnostics and observability
|
||||
|
||||
- Optional native states remain detached and contain existing prepared,
|
||||
processing, mesh, bone, surface and length fields.
|
||||
- Loader retains `M2_NATIVE_ANIMATOR` log formatting and path normalization.
|
||||
- Controller emits no logs or metrics.
|
||||
|
||||
## Verification
|
||||
|
||||
- `verify_m2_animation_playback_controller.gd` covers exact phase, ordinary/
|
||||
fish/bird priorities, substring/first fallback, loop/play/seek, native exact-
|
||||
script order, five-field copy, phase, detached diagnostics and boundaries.
|
||||
- Finalizer/build/prototype/material/shutdown regressions protect adjacent behavior.
|
||||
- Fidelity evidence is exact policy/mutation extraction; no private asset or
|
||||
original-client animation comparison is claimed.
|
||||
- Performance budget: 20,000 phase-and-selection pairs under one second.
|
||||
|
||||
## Extension points
|
||||
|
||||
Server-driven animation state may later replace default selection through a
|
||||
separate presentation contract; this fallback controller must remain available
|
||||
for world doodads and compatibility fixtures.
|
||||
|
||||
## Capability status
|
||||
|
||||
| Capability | Status | Evidence | Gap/next step |
|
||||
|---|---|---|---|
|
||||
| Imported default selection/playback | Implemented extraction | Synthetic priority/play/seek verifier | Asset-backed animation-name corpus pending |
|
||||
| Native data/phase startup | Implemented extraction | Exact-reference/native state verifier | Native visual fidelity remains experimental |
|
||||
| Instance/batch materialization | Implemented extraction | Materializer contract verifier | Asset-backed traversal pending |
|
||||
|
||||
## Known gaps and risks
|
||||
|
||||
- Hash phase intentionally depends on existing Godot String hashing behavior.
|
||||
- Default-name heuristics are not a complete WoW animation-state mapping.
|
||||
- No proprietary traversal, animation timing comparison, p95/p99 or paired-client run exists.
|
||||
|
||||
## Source map
|
||||
|
||||
| Path | Responsibility |
|
||||
|---|---|
|
||||
| `src/render/m2/m2_animation_playback_controller.gd` | Phase, selection, native copy/start and imported playback |
|
||||
| `src/render/m2/m2_animated_scene_finalizer.gd` | Accepted prototype/player inventory |
|
||||
| `src/scenes/streaming/m2_native_animator.gd` | Experimental native deformation runtime |
|
||||
| `src/render/m2/m2_animated_instance_materializer.gd` | Instance duplication/attachment and playback composition |
|
||||
| `src/scenes/streaming/streaming_world_loader.gd` | Build adapter, diagnostic formatting and Editor ownership |
|
||||
| `src/tools/verify_m2_animation_playback_controller.gd` | Policy/mutation/boundary/timing regression |
|
||||
|
||||
## Related decisions and references
|
||||
|
||||
- [`m2-animated-scene-finalizer.md`](m2-animated-scene-finalizer.md)
|
||||
- [`m2-build-batch-planner.md`](m2-build-batch-planner.md)
|
||||
- [`world-renderer.md`](world-renderer.md)
|
||||
- [`../../RENDER.md`](../../RENDER.md)
|
||||
- [`../../targets/roadmap/02-rendering-and-graphics.md`](../../targets/roadmap/02-rendering-and-graphics.md)
|
||||
@@ -0,0 +1,249 @@
|
||||
# M2 Animation Resource Finalizer
|
||||
|
||||
## Metadata
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Status | Implemented extraction |
|
||||
| Target/work package | M03 / `M03-RND-M2-ANIMATION-RESOURCE-FINALIZER-001` |
|
||||
| Owners | Cached animated Resource polling, terminal load and prototype outcome |
|
||||
| Last verified | Worktree `work/sindo-main-codex/m03-m2-animation-resource-finalizer`, 2026-07-18 |
|
||||
| Profiles/capabilities | Existing optional cached-GLB animated M2 path |
|
||||
|
||||
## Purpose
|
||||
|
||||
Drain cached animated M2 ResourceLoader work without keeping terminal I/O and
|
||||
prototype outcome logic in `StreamingWorldLoader`. The service polls pending
|
||||
requests, prepares a detached scene candidate and completes material repair,
|
||||
validation, cache adoption or static-only fallback.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Select cached GLB paths, apply allow/deny policy or admit threaded requests.
|
||||
- Select/build native animated M2 prototypes.
|
||||
- Look up the static material prototype used for repair.
|
||||
- Own render permits, build jobs, playback, instances or SceneTree roots.
|
||||
- Change ResourceLoader ordering, cache formats, profiles or visible output.
|
||||
|
||||
## Context and boundaries
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Observer[M2CachedAnimationResourceObserver] --> Pipeline[M2AnimationLoadPipelineState]
|
||||
Loader[StreamingWorldLoader] --> Finalizer[M2AnimationResourceFinalizer]
|
||||
Pipeline --> Finalizer
|
||||
ResourceLoader --> Finalizer
|
||||
Finalizer --> SceneFinalizer[M2AnimatedSceneFinalizer]
|
||||
Finalizer --> Cache[M2PrototypeCacheState]
|
||||
Loader --> Material[Material prototype lookup]
|
||||
Material --> Finalizer
|
||||
```
|
||||
|
||||
The two-phase API preserves the historical ordering: a loaded Resource must
|
||||
instantiate a candidate before the loader performs material-prototype lookup.
|
||||
|
||||
## Public API
|
||||
|
||||
| Symbol | Kind | Purpose | Failure behavior |
|
||||
|---|---|---|---|
|
||||
| `poll_terminal_requests(pipeline, cache)` | I/O command | Move LOADED/FAILED requests to completion FIFO | Invalid composition returns zero |
|
||||
| `prepare_next_candidate(pipeline, cache)` | I/O command/query | Pop one record, load Resource and instantiate candidate | Skip/failed/invalid outcome returns empty Dictionary |
|
||||
| `finalize_prepared_candidate(preparation, material_source, cache, debug)` | Command/query | Repair, validate, adopt/log or mark static-only | Invalid/finalization failure returns null |
|
||||
| `load_threaded_get_status(path)` | I/O adapter | Production status query with injectable test seam | Returns ResourceLoader status |
|
||||
| `load_threaded_get(path)` | I/O adapter | Production terminal Resource retrieval with injectable test seam | May return null |
|
||||
|
||||
## Inputs and outputs
|
||||
|
||||
| Direction | Data | Producer | Consumer | Ownership/lifetime |
|
||||
|---|---|---|---|---|
|
||||
| Input | Pending/finalize records | Animation pipeline | Finalizer | Pipeline-owned copied Dictionaries |
|
||||
| Input | ResourceLoader status/Resource | Godot ResourceLoader | Finalizer | Resource reference for one operation |
|
||||
| Input | Material source root | Loader lookup | Scene finalizer | Borrowed exact Node3D |
|
||||
| Input | Prototype/static-only state | Prototype cache | Finalizer | Borrowed service; loader session |
|
||||
| Output | Prepared candidate Dictionary | Finalizer | Loader/finalizer completion | Caller-owned, immediate operation |
|
||||
| Output | Canonical animated prototype | Prototype cache | Loader build path | Borrowed exact Node3D |
|
||||
| Output | Static-only outcome | Finalizer | Prototype cache | Copied path; loader session |
|
||||
| Output | Success diagnostic | Finalizer | Runtime log | Debug-gated one-line record |
|
||||
|
||||
## Data flow
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Poll[Poll pending records in insertion order] --> Path{Resource path empty?}
|
||||
Path -->|yes| Discard[Discard and mark static-only]
|
||||
Path -->|no| Status{LOADED or FAILED?}
|
||||
Status -->|no| Pending[Keep pending]
|
||||
Status -->|yes| Complete[Append completion FIFO]
|
||||
Complete --> Permit[Loader consumes one finalize permit]
|
||||
Permit --> Pop[Pop oldest terminal record]
|
||||
Pop --> Existing{Empty/cached/static-only?}
|
||||
Existing -->|yes| Skip[Finish permit operation]
|
||||
Existing -->|no| Loaded{Status LOADED?}
|
||||
Loaded -->|no| Mark[Mark static-only]
|
||||
Loaded -->|yes| Get[Get terminal Resource]
|
||||
Get --> Instantiate[Instantiate detached candidate]
|
||||
Instantiate --> Valid{Candidate exists?}
|
||||
Valid -->|no| Mark
|
||||
Valid -->|yes| Material[Loader resolves material source]
|
||||
Material --> Repair[Repair and validate]
|
||||
Repair --> Prototype{Prototype accepted?}
|
||||
Prototype -->|no| Mark
|
||||
Prototype -->|yes| Adopt[Adopt and optionally log]
|
||||
```
|
||||
|
||||
## Lifecycle/state
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Polling
|
||||
Polling --> Pending: nonterminal status
|
||||
Polling --> TerminalQueued: LOADED or FAILED
|
||||
Polling --> StaticOnly: empty Resource path
|
||||
TerminalQueued --> Skipped: cached/static/invalid record
|
||||
TerminalQueued --> StaticOnly: failed load or invalid candidate
|
||||
TerminalQueued --> Prepared: detached candidate
|
||||
Prepared --> Adopted: repair/finalize succeeds
|
||||
Prepared --> StaticOnly: validation fails
|
||||
Pending --> [*]
|
||||
Skipped --> [*]
|
||||
StaticOnly --> [*]
|
||||
Adopted --> [*]
|
||||
```
|
||||
|
||||
## Main sequence
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant L as StreamingWorldLoader
|
||||
participant F as AnimationResourceFinalizer
|
||||
participant P as AnimationLoadPipelineState
|
||||
participant R as ResourceLoader
|
||||
participant S as AnimatedSceneFinalizer
|
||||
participant C as PrototypeCacheState
|
||||
L->>F: poll_terminal_requests(P, C)
|
||||
F->>R: load_threaded_get_status(path)
|
||||
F->>P: complete terminal request
|
||||
L->>L: consume M2_ANIMATION_FINALIZE permit
|
||||
L->>F: prepare_next_candidate(P, C)
|
||||
F->>P: pop oldest finalize record
|
||||
F->>R: load_threaded_get(path)
|
||||
F->>S: instantiate_candidate(Resource)
|
||||
alt candidate available
|
||||
F-->>L: normalized/path/candidate
|
||||
L->>L: resolve material prototype
|
||||
L->>F: finalize_prepared_candidate(...)
|
||||
F->>S: repair_materials + finalize_candidate
|
||||
F->>C: adopt prototype or mark static-only
|
||||
else rejected
|
||||
F->>C: mark static-only
|
||||
end
|
||||
```
|
||||
|
||||
## Dependency diagram
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
Finalizer[M2AnimationResourceFinalizer] --> ResourceLoader
|
||||
Finalizer --> Pipeline[M2AnimationLoadPipelineState]
|
||||
Finalizer --> SceneFinalizer[M2AnimatedSceneFinalizer]
|
||||
Finalizer --> Cache[M2PrototypeCacheState]
|
||||
Loader[StreamingWorldLoader] --> Finalizer
|
||||
Loader --> Scheduler[RenderBudgetScheduler]
|
||||
Loader --> MaterialLookup[Material prototype lookup]
|
||||
Finalizer -. no dependency .-> Scheduler
|
||||
Finalizer -. no dependency .-> BuildQueue[M2BuildQueue]
|
||||
Finalizer -. no dependency .-> Playback[M2AnimationPlaybackController]
|
||||
```
|
||||
|
||||
## Ownership, threading and resources
|
||||
|
||||
- All methods run synchronously on the renderer main thread.
|
||||
- Pipeline owns pending and completion records; one loader permit pops one record.
|
||||
- The prepared Dictionary temporarily owns the detached candidate reference. The
|
||||
loader must call completion synchronously after material lookup.
|
||||
- Existing scene finalizer frees rejected candidate roots. Prototype cache owns
|
||||
accepted detached prototypes until final shutdown.
|
||||
- Service retains only scene-finalizer and optional test-adapter references. The
|
||||
production ResourceLoader path creates no self-reference or retained Resource.
|
||||
- Loader retains scheduler permits, material lookup and all instance/SceneTree work.
|
||||
|
||||
## Errors, cancellation and recovery
|
||||
|
||||
| Failure/state | Behavior | Recovery |
|
||||
|---|---|---|
|
||||
| Missing service dependency | Return zero/empty/null | Correct renderer composition |
|
||||
| Empty Resource path | Discard pending request and mark static-only | Repair cache and start new session |
|
||||
| Nonterminal status | Keep request pending | Poll next tick |
|
||||
| FAILED terminal status | Pop one record and mark static-only | Repair cache and start new session |
|
||||
| Existing cached/static state | Pop and skip without terminal Resource get | None |
|
||||
| Null/unsupported Resource | Mark static-only | Repair imported cache |
|
||||
| Candidate validation failure | Scene finalizer frees candidate; mark static-only | Repair animation/cache |
|
||||
| Tile cancellation | No direct transition; shared cache work continues | Loader queue remains authoritative |
|
||||
| Shutdown | Loader drains ResourceLoader before pipeline/cache clear | New loader starts empty |
|
||||
|
||||
## Configuration and capabilities
|
||||
|
||||
The module adds no setting. Existing cached-animation allow/deny policy,
|
||||
`m2_animation_finalize_ops_per_tick`, debug flag and scheduler lane remain exact.
|
||||
|
||||
## Persistence, cache and migration
|
||||
|
||||
No persistence, schema or cache path changes are introduced. Existing `.glb`
|
||||
imports and `pivot_prefix_v1` eligibility remain owned by the cached observer.
|
||||
|
||||
## Diagnostics and observability
|
||||
|
||||
Successful debug-enabled adoption emits the unchanged `M2_ANIM_CACHE` record
|
||||
with normalized path, Resource cache path and AnimationPlayer count. Pipeline
|
||||
work metrics remain pending plus completion FIFO size.
|
||||
|
||||
## Verification
|
||||
|
||||
- Dedicated verifier covers pending/loaded/failed polling order, empty path,
|
||||
completion FIFO, cached/static skips, terminal Resource get, candidate identity,
|
||||
material repair arguments, adoption, rejection, source ownership and timing.
|
||||
- Pipeline, scene-finalizer, prototype-cache, cached-observer, shutdown, facade,
|
||||
internal-access and baseline regressions protect adjacent behavior.
|
||||
- Fidelity evidence is exact lifecycle/I/O extraction only; no private asset,
|
||||
original-client animation, visual, leak-pressure or p95/p99 claim is made.
|
||||
|
||||
## Extension points
|
||||
|
||||
Material-prototype lookup may later become a dedicated resource observer, which
|
||||
would allow the entire two-phase operation to compose outside the loader. Static
|
||||
Mesh Resource finalization remains separate to avoid premature generalization.
|
||||
|
||||
## Capability status
|
||||
|
||||
| Capability | Status | Evidence | Gap/next step |
|
||||
|---|---|---|---|
|
||||
| Animated request terminal polling | Implemented extraction | Status/order/source verifier | Asset-backed long traversal pending |
|
||||
| Animated candidate finalization/outcome | Implemented extraction | Identity/repair/adoption verifier | Private visual/leak/p95/p99 pending |
|
||||
| Material prototype lookup | Loader-owned | Existing renderer regressions | Dedicated observer optional |
|
||||
|
||||
## Known gaps and risks
|
||||
|
||||
- Terminal Resource get, instantiation and repair remain synchronous main-thread work.
|
||||
- Prepared candidate ownership relies on immediate completion by the loader adapter.
|
||||
- Private asset traversal, descriptor pressure, leak and p95/p99 evidence is absent.
|
||||
|
||||
## Source map
|
||||
|
||||
| Path | Responsibility |
|
||||
|---|---|
|
||||
| `src/render/m2/m2_animation_resource_finalizer.gd` | Poll/load/prepare/finalize/cache/log service |
|
||||
| `src/render/m2/m2_animation_load_pipeline_state.gd` | Pending and completion FIFO ownership |
|
||||
| `src/render/m2/m2_animated_scene_finalizer.gd` | Candidate instantiation/repair/validation/rejection cleanup |
|
||||
| `src/render/m2/m2_prototype_cache_state.gd` | Accepted prototype/static-only ownership |
|
||||
| `src/scenes/streaming/streaming_world_loader.gd` | Permits, material lookup and synchronous composition |
|
||||
| `src/tools/verify_m2_animation_resource_finalizer.gd` | Polling/finalization/boundary/timing regression |
|
||||
|
||||
## Related decisions and references
|
||||
|
||||
- [`m2-animation-load-pipeline-state.md`](m2-animation-load-pipeline-state.md)
|
||||
- [`m2-animated-scene-finalizer.md`](m2-animated-scene-finalizer.md)
|
||||
- [`m2-cached-animation-resource-observer.md`](m2-cached-animation-resource-observer.md)
|
||||
- [`m2-prototype-cache-state.md`](m2-prototype-cache-state.md)
|
||||
- [`world-renderer.md`](world-renderer.md)
|
||||
- [`../../RENDER.md`](../../RENDER.md)
|
||||
- [`../../targets/roadmap/02-rendering-and-graphics.md`](../../targets/roadmap/02-rendering-and-graphics.md)
|
||||
@@ -32,8 +32,8 @@ flowchart LR
|
||||
Loader --> Planner
|
||||
Planner --> Plan[Detached batch plan]
|
||||
Plan --> Loader
|
||||
Loader --> Ready{Resource ready?}
|
||||
Ready --> Materialize[Animated or MultiMesh materialization]
|
||||
Loader --> Dispatch[M2BuildDispatchPlanner]
|
||||
Dispatch --> Materialize[Animated or MultiMesh materialization]
|
||||
Loader --> Budget[RenderBudgetScheduler permit]
|
||||
```
|
||||
|
||||
@@ -102,8 +102,10 @@ sequenceDiagram
|
||||
## Ownership, threading and resources
|
||||
|
||||
- The planner owns only call-local scalar values and the returned Dictionary.
|
||||
- The loader owns build-job Dictionaries, queue ordering, tile checks, resource
|
||||
readiness/retry, serial numbers and group-index mutation.
|
||||
- `M2BuildQueue` owns typed jobs, FIFO ordering, serial numbers and group/offset
|
||||
cursors. `M2BuildDispatchPlanner` owns the pure resource-state action decision;
|
||||
the loader owns tile checks and native-first orchestration, while static and
|
||||
cached animation observers own their observation/retry phases.
|
||||
- Materializers own main-thread Node/MultiMesh construction under loader roots.
|
||||
- The scheduler owns the frame-local `M2_BUILD` counter.
|
||||
- Pure planning is thread-safe, though the current adapter calls it on main thread.
|
||||
@@ -149,8 +151,8 @@ queue depth, build activity and hitch observability.
|
||||
|
||||
## Extension points
|
||||
|
||||
- A later package may define typed build-job state once queue/resource transitions
|
||||
are extracted together with explicit cancellation and recovery.
|
||||
- Remaining native resource observation may be extracted while retaining the
|
||||
dispatch and typed build-job/FIFO contracts.
|
||||
- Spatial-cell batching must use measured culling/performance evidence and must
|
||||
not silently change this model-path batch cursor.
|
||||
|
||||
@@ -159,12 +161,15 @@ queue depth, build activity and hitch observability.
|
||||
| Capability | Status | Evidence | Gap/next step |
|
||||
|---|---|---|---|
|
||||
| Static/animated batch cursor planning | Implemented extraction | Contract/source/timing verifier | Asset-backed p95/p99 pending |
|
||||
| Build queue/resource state machine | Remains in loader | Existing lifecycle regressions | Stateful extraction pending |
|
||||
| Typed build queue/cursor state | Implemented extraction | M2 build queue lifecycle verifier | Asset-backed traversal pending |
|
||||
| Resource dispatch decision | Implemented extraction | M2 build dispatch planner verifier | Asset-backed traversal pending |
|
||||
| Resource observation/requests | Remains in loader | Existing lifecycle regressions | Stateful extraction pending |
|
||||
| Spatial-cell batching | Planned | Renderer roadmap | Culling evidence/design pending |
|
||||
|
||||
## Known gaps and risks
|
||||
|
||||
- The result remains a Dictionary while loader build jobs are untyped Dictionaries.
|
||||
- The planner result remains a Dictionary; `M2BuildQueue` adopts its scalar
|
||||
cursor values through one explicit progress call.
|
||||
- Negative offsets are preserved rather than rejected to avoid a hidden behavior change.
|
||||
- Planning timing does not measure resource loading or materialization.
|
||||
- Private asset-backed p95/p99 and visual comparison remain unavailable.
|
||||
@@ -174,7 +179,12 @@ queue depth, build activity and hitch observability.
|
||||
| Path | Responsibility |
|
||||
|---|---|
|
||||
| `src/render/m2/m2_build_batch_planner.gd` | Pure limit/count/cursor planning |
|
||||
| `src/scenes/streaming/streaming_world_loader.gd` | Build job, queue, resources, materialization and budgets |
|
||||
| `src/render/m2/m2_build_dispatch_planner.gd` | Pure observed-state action/transition planning |
|
||||
| `src/render/m2/m2_build_queue.gd` | Typed pending jobs, FIFO order and cursor ownership |
|
||||
| `src/scenes/streaming/streaming_world_loader.gd` | Tile checks, resource readiness, progress adoption, materializer adapters and budgets |
|
||||
| `src/render/m2/m2_static_batch_materializer.gd` | Planned static-slice MultiMesh construction and attachment |
|
||||
| `src/render/m2/m2_animation_playback_controller.gd` | Animated-instance phase/selection/native/imported playback |
|
||||
| `src/render/m2/m2_animated_instance_materializer.gd` | Planned animated-slice duplication, playback startup and attachment |
|
||||
| `src/render/m2/m2_placement_grouper.gd` | Produces grouped transform arrays |
|
||||
| `src/tools/verify_m2_build_batch_planner.gd` | Formula, source and timing regression |
|
||||
|
||||
@@ -182,6 +192,7 @@ queue depth, build activity and hitch observability.
|
||||
|
||||
- [`m2-placement-grouper.md`](m2-placement-grouper.md)
|
||||
- [`m2-placement-transform-resolver.md`](m2-placement-transform-resolver.md)
|
||||
- [`m2-static-batch-materializer.md`](m2-static-batch-materializer.md)
|
||||
- [`world-renderer.md`](world-renderer.md)
|
||||
- [`../../RENDER.md`](../../RENDER.md)
|
||||
- [`../../targets/roadmap/02-rendering-and-graphics.md`](../../targets/roadmap/02-rendering-and-graphics.md)
|
||||
|
||||
@@ -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)
|
||||
@@ -0,0 +1,265 @@
|
||||
# M2 Build Queue
|
||||
|
||||
## Metadata
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Status | Implemented |
|
||||
| Target/work package | M03 / `M03-RND-M2-BUILD-QUEUE-001` |
|
||||
| Owners | Pending M2 build-job records, FIFO tile keys and build cursors |
|
||||
| Last verified | Worktree `work/sindo-main-codex/m03-m2-build-queue`, 2026-07-18 |
|
||||
| Profiles/capabilities | Existing static MultiMesh and animated-instance build paths |
|
||||
|
||||
## Purpose
|
||||
|
||||
Own typed pending M2 build state outside `StreamingWorldLoader`: a keyed job
|
||||
retains its M2 root, grouped transforms, insertion-order group keys and three
|
||||
progress cursors, while a separate FIFO retains scheduling order and stale keys.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Decide tile eligibility, detail radius, resource readiness or retries.
|
||||
- Consume scheduler permits or plan batch sizes.
|
||||
- Create, attach or free Nodes, Meshes, MultiMeshes or animated instances.
|
||||
- Load/cache resources, group placements or mutate tile state.
|
||||
- Change model order, transform order, render settings or visible output.
|
||||
|
||||
## Context and boundaries
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Groups[M2PlacementGrouper result] --> Loader[StreamingWorldLoader adapter]
|
||||
Loader --> Queue[M2BuildQueue]
|
||||
Queue --> Job[M2BuildJob]
|
||||
Queue --> Loader
|
||||
Loader --> Planner[M2BuildBatchPlanner]
|
||||
Loader --> Dispatch[M2BuildDispatchPlanner]
|
||||
Loader --> Static[M2StaticBatchMaterializer]
|
||||
Loader --> Animated[M2AnimatedInstanceMaterializer]
|
||||
Loader --> Scheduler[RenderBudgetScheduler]
|
||||
```
|
||||
|
||||
Allowed dependencies are String, Dictionary, Array, RefCounted and a strong
|
||||
Node3D reference. SceneTree destruction, materializers, Mesh/MultiMesh,
|
||||
RenderingServer/RIDs, ResourceLoader/files, workers/mutexes, scheduler policy,
|
||||
gameplay, network and Editor APIs are forbidden.
|
||||
|
||||
## Public API
|
||||
|
||||
### `M2BuildJob`
|
||||
|
||||
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|
||||
|---|---|---|---|---|
|
||||
| `tile_key()` | Query | Immutable keyed identity | Main thread; job lifetime | None |
|
||||
| `root()` | Query | Borrow strongly retained M2 root | Main thread; no ownership transfer | May be externally invalidated |
|
||||
| `groups()` | Query | Borrow exact grouped-transform Dictionary | Main thread; job lifetime | None |
|
||||
| `group_keys()` | Query | Borrow enqueue-time insertion-order key snapshot | Main thread; job lifetime | None |
|
||||
| `group_index()` | Query | Current model-path cursor | Main thread | None |
|
||||
| `transform_offset()` | Query | Current transform cursor within group | Main thread | None |
|
||||
| `batch_serial()` | Query | Current batch naming serial | Main thread | None |
|
||||
| `adopt_progress(group_index, offset, serial)` | Command | Atomically replace all progress values | Main thread | Raw integer semantics retained |
|
||||
| `diagnostic_snapshot()` | Query | Detached scalar/count state | Any serialized caller | Omits engine references |
|
||||
|
||||
### `M2BuildQueue`
|
||||
|
||||
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|
||||
|---|---|---|---|---|
|
||||
| `enqueue(tile_key, root, groups)` | Command | Replace keyed job and append FIFO key | Main thread; until erase/clear | Invalid/empty input returns false |
|
||||
| `has_pending/front_key/pop_front/rotate_front` | Commands/queries | Inspect, drain or rotate FIFO independently of job validity | Main thread | Empty returns false/empty String |
|
||||
| `has_job/job_for` | Queries | Inspect keyed record, including stale-key distinction | Main thread | Missing returns false/null |
|
||||
| `root_for/groups_for/group_keys_for` | Queries | Borrow current job inputs | Main thread | Stale returns null/empty collection |
|
||||
| `group_index_for/transform_offset_for/batch_serial_for` | Queries | Read current progress | Main thread | Stale returns zero |
|
||||
| `adopt_progress(tile_key, group_index, offset, serial)` | Command | Atomically update current job progress | Main thread | Stale returns false |
|
||||
| `erase_job(tile_key)` | Command | Remove keyed job but preserve FIFO keys | Main thread | Unknown returns false |
|
||||
| `job_keys()` | Query | Detached active-key snapshot for loader cleanup | Main thread | Empty returns empty Array |
|
||||
| `clear()` | Command | Release all job and FIFO references | Main thread | Idempotent; never frees Nodes |
|
||||
| `pending_count/active_job_count` | Queries | FIFO and keyed-job metrics | Main thread | None |
|
||||
| `diagnostic_snapshot()` | Query | Detached FIFO and sorted job diagnostics | Main thread | None |
|
||||
|
||||
## Inputs and outputs
|
||||
|
||||
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|
||||
|---|---|---|---|---|---|
|
||||
| Input | Tile key and M2 root | Loader group-result finalizer | Queue/job | Copied String; strong borrowed Node reference | Until erase/clear |
|
||||
| Input | Grouped transforms Dictionary | M2PlacementGrouper through loader mailbox | Job | Exact strong reference | Job lifetime |
|
||||
| Input | `groups.keys()` order | Job constructor | Loader group selection | Job-owned Array snapshot | Job lifetime |
|
||||
| Input | Next group/offset/serial | Planner/materializer adapter | Job progress | Copied integers | Until next adoption |
|
||||
| Output | Front/rotated/popped tile key | Queue | Loader scheduling loop | Copied String | One drain step |
|
||||
| Output | Borrowed root/groups/group keys | Job/queue | Loader readiness/materialization | No ownership transfer | One drain step |
|
||||
| Output | Detached diagnostics/key snapshots | Queue/job | Tests/metrics/cleanup adapter | Caller-owned collections | Call result |
|
||||
|
||||
Side effects are keyed/FIFO collection mutation, strong-reference retention and
|
||||
cursor mutation. No engine object is created, destroyed or attached.
|
||||
|
||||
## Data flow
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Enqueue[Tile key, root and grouped transforms] --> Job[Create or replace M2BuildJob]
|
||||
Enqueue --> FIFO[Append tile key to FIFO]
|
||||
FIFO --> Front[Borrow front key]
|
||||
Front --> Current{Current keyed job exists?}
|
||||
Current -->|no| Pop[Pop stale key]
|
||||
Current -->|yes| Loader[Loader eligibility/readiness/materialization]
|
||||
Loader -->|resource pending| Rotate[Move front key to tail]
|
||||
Loader -->|operation complete| Progress[Adopt group, offset and serial]
|
||||
Loader -->|finish/cancel| Erase[Erase job only]
|
||||
Erase --> Pop
|
||||
```
|
||||
|
||||
## Lifecycle/state
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Absent
|
||||
Absent --> Queued: enqueue valid job and FIFO key
|
||||
Queued --> Rotated: resource pending
|
||||
Rotated --> Queued: returns to FIFO front
|
||||
Queued --> Progressed: adopt progress
|
||||
Progressed --> Queued: group remains
|
||||
Queued --> StaleQueued: erase job before FIFO pop
|
||||
Progressed --> StaleQueued: finish or cancel
|
||||
StaleQueued --> Absent: pop stale key
|
||||
Queued --> Absent: clear
|
||||
StaleQueued --> Absent: clear
|
||||
```
|
||||
|
||||
Duplicate enqueue replaces the current keyed job and adds another FIFO entry;
|
||||
later erase can therefore leave multiple stale entries, matching historical raw state.
|
||||
|
||||
## Main sequence
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant G as M2 group-result drain
|
||||
participant Q as M2BuildQueue
|
||||
participant L as StreamingWorldLoader
|
||||
participant P as M2BuildBatchPlanner
|
||||
participant M as M2 materializer
|
||||
G->>Q: enqueue(tile key, root, groups)
|
||||
L->>Q: front_key and has_job
|
||||
Q-->>L: borrowed root/groups/keys and cursors
|
||||
L->>P: plan selected transform slice
|
||||
alt resource pending
|
||||
L->>Q: rotate_front
|
||||
else materializable
|
||||
L->>M: materialize selected slice
|
||||
L->>Q: adopt_progress(next index, offset, serial)
|
||||
else cancelled or complete
|
||||
L->>L: free/queue-free root if required
|
||||
L->>Q: erase_job
|
||||
L->>Q: pop_front
|
||||
end
|
||||
```
|
||||
|
||||
## Dependency diagram
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
Loader[StreamingWorldLoader] --> Queue[M2BuildQueue]
|
||||
Queue --> Job[M2BuildJob]
|
||||
Job --> EngineBase[Node3D strong reference]
|
||||
Loader --> Planner[M2BuildBatchPlanner]
|
||||
Loader --> Materializers[Static and animated materializers]
|
||||
Loader --> Scheduler[RenderBudgetScheduler]
|
||||
Queue -. no dependency .-> SceneMutation[SceneTree destruction/attachment]
|
||||
Queue -. no dependency .-> Resources[Mesh / MultiMesh / ResourceLoader]
|
||||
```
|
||||
|
||||
## Ownership, threading and resources
|
||||
|
||||
- Queue owns keyed job records and FIFO String entries.
|
||||
- Job retains the exact groups Dictionary, a fresh group-key snapshot and root.
|
||||
- Root ownership remains with the loader/tile SceneTree; queue release never frees it.
|
||||
- Loader validates `is_instance_valid`, frees empty/aborted roots and mutates tile state.
|
||||
- `M2BuildDispatchPlanner` selects wait/materializer/advance actions without
|
||||
borrowing queue-owned engine references.
|
||||
- All current operations run on the renderer main thread; no mutex is required.
|
||||
- Group worker results cross their existing mutex mailbox before enqueue.
|
||||
|
||||
## Errors, cancellation and recovery
|
||||
|
||||
| Failure/state | Detection | Behavior | Diagnostic | Recovery |
|
||||
|---|---|---|---|---|
|
||||
| Invalid enqueue input | Entry guard | Reject without mutation | Contract fixture | Correct caller composition |
|
||||
| Duplicate tile key | Keyed assignment | Replace job and append FIFO key | Duplicate fixture | Stale entries drain normally |
|
||||
| Stale FIFO key | `has_job == false` | Loader pops and continues without permit | Stale fixture | No action required |
|
||||
| Resource pending | Loader cache state | Rotate one front key and consume existing permit | Rotation fixture/build diagnostics | Retry when key returns front |
|
||||
| Root externally invalid | Loader validity check | Erase job and pop key | Loader source contract | Tile may regroup/requeue later |
|
||||
| Tile cancelled | Loader policy | Queue-free root, erase job; FIFO key drains/pops | Shutdown/lifetime regression | Eligible tile may requeue |
|
||||
| World clear | Loader job-key snapshot | Cancel roots, then clear queue | Shutdown regression | New map starts empty |
|
||||
|
||||
## Configuration and capabilities
|
||||
|
||||
The queue introduces no settings. Existing static/animated batch limits,
|
||||
visibility/shadow settings, resource caches and `M2_BUILD` permits remain loader,
|
||||
planner, materializer and scheduler contracts.
|
||||
|
||||
## Persistence, cache and migration
|
||||
|
||||
No state is serialized. No ADT/M2 cache format or version changes; no rebake or
|
||||
migration is required.
|
||||
|
||||
## Diagnostics and observability
|
||||
|
||||
- `pending_count` preserves the existing FIFO-backed `m2_build` queue metric.
|
||||
- `active_job_count` distinguishes keyed jobs from stale/duplicate FIFO entries.
|
||||
- Diagnostic snapshots retain FIFO order and sort job records by tile key.
|
||||
- Job diagnostics expose cursors/counts but never root/groups references.
|
||||
- The module emits no logs.
|
||||
|
||||
## Verification
|
||||
|
||||
- `verify_m2_build_queue.gd` covers invalid input, exact groups/root retention,
|
||||
FIFO, duplicate replacement, rotation, stale entries, atomic progress, erase/
|
||||
clear engine lifetime, detached sorted diagnostics and loader boundaries.
|
||||
- Planner, static/animated materializer, Mesh pipeline/cache/prototype, shutdown,
|
||||
facade, internal-access and checkpoint regressions protect adjacent behavior.
|
||||
- Fidelity evidence is exact state-transition extraction; visual rules are not changed.
|
||||
- Performance budget: 100 cycles of 256 enqueue/rotate/erase operations under one second.
|
||||
- No original-client or proprietary asset comparison is claimed for bookkeeping state.
|
||||
|
||||
## Extension points
|
||||
|
||||
Remaining resource readiness and dispatch logic may later move behind explicit
|
||||
commands once its cancellation semantics are independently fixed. A generic
|
||||
queue base, signals and callbacks are intentionally excluded.
|
||||
|
||||
## Capability status
|
||||
|
||||
| Capability | Status | Evidence | Gap/next step |
|
||||
|---|---|---|---|
|
||||
| Typed keyed M2 jobs and FIFO | Implemented extraction | Lifecycle/order/source/timing verifier | Asset-backed traversal pending |
|
||||
| Cursor/serial ownership | Implemented extraction | Atomic progress fixtures | Typed batch-plan result remains Dictionary |
|
||||
| Root destruction | Existing loader-owned | Lifetime/source/shutdown regressions | Keep outside state service |
|
||||
| Resource dispatch decision | Implemented separately | Dispatch planner verifier | Asset-backed traversal pending |
|
||||
| Resource observation/requests | Existing loader-owned | Adjacent cache/build tests | Safe extraction remains |
|
||||
|
||||
## Known gaps and risks
|
||||
|
||||
- Grouped transforms remain untyped Dictionary/Array data.
|
||||
- Queue permits duplicate and stale FIFO keys intentionally for compatibility.
|
||||
- Job accessors return borrowed mutable groups/key collections; current loader is the sole consumer.
|
||||
- Private asset traversal, long-flight p95/p99 and leak evidence remain unavailable.
|
||||
|
||||
## Source map
|
||||
|
||||
| Path | Responsibility |
|
||||
|---|---|
|
||||
| `src/render/m2/m2_build_job.gd` | Strong root/groups references, key snapshot and progress cursors |
|
||||
| `src/render/m2/m2_build_queue.gd` | Keyed job ownership, FIFO/stale/rotation lifecycle and diagnostics |
|
||||
| `src/scenes/streaming/streaming_world_loader.gd` | Eligibility, readiness, permits, materialization, tile state and root cleanup |
|
||||
| `src/render/m2/m2_build_batch_planner.gd` | Batch count and cursor-plan calculation |
|
||||
| `src/render/m2/m2_build_dispatch_planner.gd` | Resource-state action and transition planning |
|
||||
| `src/tools/verify_m2_build_queue.gd` | Lifecycle/order/lifetime/boundary/timing regression |
|
||||
|
||||
## Related decisions and references
|
||||
|
||||
- [`m2-build-batch-planner.md`](m2-build-batch-planner.md)
|
||||
- [`m2-build-dispatch-planner.md`](m2-build-dispatch-planner.md)
|
||||
- [`m2-build-resource-snapshot.md`](m2-build-resource-snapshot.md)
|
||||
- [`m2-static-batch-materializer.md`](m2-static-batch-materializer.md)
|
||||
- [`m2-animated-instance-materializer.md`](m2-animated-instance-materializer.md)
|
||||
- [`world-renderer.md`](world-renderer.md)
|
||||
- [`../../RENDER.md`](../../RENDER.md)
|
||||
- [`../../targets/roadmap/02-rendering-and-graphics.md`](../../targets/roadmap/02-rendering-and-graphics.md)
|
||||
@@ -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)
|
||||
@@ -0,0 +1,250 @@
|
||||
# M2 Mesh Load Pipeline State
|
||||
|
||||
## Metadata
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Status | Implemented extraction |
|
||||
| Target/work package | M03 / `M03-RND-M2-MESH-LOAD-PIPELINE-001` |
|
||||
| Owners | Static M2 threaded request records and terminal finalize FIFO |
|
||||
| Last verified | Worktree `work/sindo-main-codex/m03-m2-mesh-load-pipeline`, 2026-07-17 |
|
||||
| Profiles/capabilities | Existing static M2 cached `.tscn`/`.glb` load path |
|
||||
|
||||
## Purpose
|
||||
|
||||
Own cross-frame bookkeeping between successful static M2 ResourceLoader request
|
||||
start, terminal loaded/failed polling and budgeted main-thread finalization. The
|
||||
loader retains every I/O and engine-resource operation.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Call ResourceLoader or select cache paths/formats.
|
||||
- Own M2 scene, missing or material-refresh caches, or prepared Mesh references.
|
||||
- Extract Meshes from PackedScene/GLB Resources.
|
||||
- Consume finalize permits or rebuild/adopt runtime Meshes.
|
||||
- Merge static and animated M2 pipelines.
|
||||
|
||||
## Context and boundaries
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Need[Static M2 mesh needed] --> Loader[StreamingWorldLoader]
|
||||
Loader --> IO[ResourceLoader request]
|
||||
Loader --> State[M2MeshLoadPipelineState]
|
||||
State --> Poll[Detached pending records]
|
||||
Poll --> Loader
|
||||
Loader --> IO
|
||||
Loader -->|terminal status| State
|
||||
State --> FIFO[Finalize FIFO]
|
||||
FIFO --> Loader
|
||||
Loader --> Budget[M2_MESH_FINALIZE permit]
|
||||
Loader --> Cache[M2 Mesh resource cache or M2 prototype outcome state]
|
||||
```
|
||||
|
||||
Allowed dependencies are value containers, Strings and opaque integer statuses.
|
||||
ResourceLoader, filesystem, WorkerThreadPool, Mesh/Resource/Node/RID ownership,
|
||||
scheduler and other application layers are forbidden.
|
||||
|
||||
## Public API
|
||||
|
||||
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|
||||
|---|---|---|---|---|
|
||||
| `remember_request(normalized_relative_path, resource_path)` | Command/query | Insert one pending request | Renderer main thread; until terminal/discard/clear | Empty/duplicate rejected |
|
||||
| `has_request(path)` | Query | Test pending dedupe | Main thread | Empty/unknown false |
|
||||
| `request_records_snapshot()` | Query | Copy pending records in insertion order | Main thread; caller-owned | None |
|
||||
| `complete_request(path, terminal_status)` | Command/query | Move request to terminal FIFO | Main thread after poll | Unknown rejected |
|
||||
| `discard_request(path)` | Command/query | Remove request without finalization | Main thread | Unknown false |
|
||||
| `has_finalize_record()` / `pop_finalize_record()` | Query/command | Observe/pop completion-order FIFO | Main-thread budget drain | Empty pop returns `{}` |
|
||||
| `total_work_count()` | Query | Preserve pending-plus-finalize metric | Main thread | None |
|
||||
| `pending_request_count()` / `finalize_record_count()` | Query | Stage diagnostics | Main thread | None |
|
||||
| `clear()` | Command | Drop bookkeeping after I/O drain/reset | Main thread | Idempotent; does not drain I/O |
|
||||
| `diagnostic_snapshot()` | Query | Detached request/finalize records | Main thread | No Resources exposed |
|
||||
|
||||
## Inputs and outputs
|
||||
|
||||
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|
||||
|---|---|---|---|---|---|
|
||||
| Input | Normalized M2 path and cache Resource path | Loader request adapter | Pending map | Copied Strings | Until terminal/discard/clear |
|
||||
| Input | Opaque terminal ResourceLoader status | Loader poll adapter | Finalize record | Integer value | Until pop/clear |
|
||||
| Output | Detached pending records | Pipeline state | Loader poll/shutdown adapter | Caller-owned copies | One poll/wait pass |
|
||||
| Output | Oldest terminal record | Pipeline state | Loader finalizer | Record ownership transferred | One finalize attempt |
|
||||
| Output | Detached diagnostics | Pipeline state | Verifier/future metrics | Caller-owned copies | Snapshot lifetime |
|
||||
|
||||
Side effects are limited to collection mutation and retaining String/integer values.
|
||||
|
||||
## Data flow
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start[Successful ResourceLoader request] --> Remember[remember request]
|
||||
Remember --> Snapshot[request records snapshot]
|
||||
Snapshot --> Poll[Mesh resource finalizer polls status]
|
||||
Poll --> Active{In progress?}
|
||||
Active -->|yes| Snapshot
|
||||
Active -->|no| Complete[complete request with terminal status]
|
||||
Complete --> FIFO[Append finalize FIFO]
|
||||
FIFO --> Permit{Loader permit available?}
|
||||
Permit -->|no| FIFO
|
||||
Permit -->|yes| Pop[Pop oldest terminal record]
|
||||
Pop --> Finalize[Mesh resource finalizer gets Resource and extracts first Mesh]
|
||||
Finalize --> Prepare[M2RuntimeMeshFinalizer prepares Mesh]
|
||||
Prepare --> Adopt[Loader adopts Mesh or marks prototype outcome state]
|
||||
```
|
||||
|
||||
## Lifecycle/state
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Absent
|
||||
Absent --> Pending: remember request
|
||||
Pending --> Pending: non-terminal poll
|
||||
Pending --> TerminalQueued: complete request
|
||||
Pending --> Absent: discard
|
||||
TerminalQueued --> Absent: pop finalize
|
||||
Pending --> Absent: clear
|
||||
TerminalQueued --> Absent: clear
|
||||
```
|
||||
|
||||
One path has at most one pending request. After completion it may be requested
|
||||
again only if loader cache/missing rules permit it.
|
||||
|
||||
## Main sequence
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Loader as StreamingWorldLoader
|
||||
participant Finalizer as M2MeshResourceFinalizer
|
||||
participant Resource as ResourceLoader
|
||||
participant State as M2MeshLoadPipelineState
|
||||
participant Budget as RenderBudgetScheduler
|
||||
Loader->>Resource: load_threaded_request(cache path)
|
||||
Resource-->>Loader: OK or ERR_BUSY
|
||||
Loader->>State: remember_request(normalized, cache path)
|
||||
loop frames
|
||||
Loader->>Finalizer: poll_terminal_requests(state, prototype cache)
|
||||
Finalizer->>State: request_records_snapshot()
|
||||
Finalizer->>Resource: load_threaded_get_status(path)
|
||||
end
|
||||
Finalizer->>State: complete_request(normalized, terminal status)
|
||||
Loader->>Budget: try_consume_permit(M2_MESH_FINALIZE)
|
||||
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
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
Loader[StreamingWorldLoader] --> State[M2MeshLoadPipelineState]
|
||||
Loader --> MeshFinalizer[M2MeshResourceFinalizer]
|
||||
MeshFinalizer --> State
|
||||
MeshFinalizer --> Resource[ResourceLoader]
|
||||
Loader --> Budget[RenderBudgetScheduler]
|
||||
Loader --> MeshCache[M2 Mesh resource and prototype outcome cache states]
|
||||
Loader --> Finalizer[M2RuntimeMeshFinalizer]
|
||||
State -. no dependency .-> Resource
|
||||
State -. no dependency .-> Budget
|
||||
State -. no dependency .-> MeshCache
|
||||
```
|
||||
|
||||
## Ownership, threading and resources
|
||||
|
||||
- Main thread serializes all state mutation and snapshots.
|
||||
- State owns only request/finalize Dictionaries containing Strings and status integers.
|
||||
- `M2MeshResourceFinalizer` owns terminal status polling, terminal Resource
|
||||
retrieval, first-Mesh extraction, runtime preparation and cache/missing adoption.
|
||||
- Loader owns request admission, scheduler permits and shutdown drain ordering.
|
||||
- `M2MeshResourceCacheState` owns prepared static Mesh references and
|
||||
`M2RuntimeMeshFinalizer` owns refresh/rebuild/fallback. The loader owns shared
|
||||
adoption decisions, raw I/O and remaining engine resources; the prototype
|
||||
cache service owns missing outcomes.
|
||||
- Snapshot and diagnostic records are detached; caller mutation cannot affect state.
|
||||
|
||||
## Errors, cancellation and recovery
|
||||
|
||||
| Failure | Detection | Behavior | Diagnostic | Recovery |
|
||||
|---|---|---|---|---|
|
||||
| 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 | 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
|
||||
|
||||
| Setting/capability | Default | Profile | Runtime mutable | Effect |
|
||||
|---|---|---|---|---|
|
||||
| `m2_mesh_finalize_ops_per_tick` | `1` | Quality/custom | Yes | Bounds terminal FIFO pops outside state |
|
||||
| Cache extension order | `.tscn`, `.glb` | Existing M2 path | No | Loader selects request path |
|
||||
| Animated pivot-prefix GLB exclusion | Existing rule | All | No | Loader filters before request |
|
||||
|
||||
## Persistence, cache and migration
|
||||
|
||||
No pipeline state is serialized. M2 cache formats, material refresh version and
|
||||
rebuild policy are unchanged; no migration or rebake is required.
|
||||
|
||||
## Diagnostics and observability
|
||||
|
||||
- `total_work_count` preserves all three historical `m2_mesh` metrics.
|
||||
- Diagnostic snapshots expose only paths/statuses, never loaded Resources or Meshes.
|
||||
- Existing loader logs and hitch sections remain unchanged.
|
||||
- Normalized M2 path is the correlation key.
|
||||
|
||||
## Verification
|
||||
|
||||
- `verify_m2_mesh_load_pipeline_state.gd`: validation, dedupe, insertion-order
|
||||
polling, terminal transition, completion FIFO, opaque status, discard, metrics,
|
||||
detached diagnostics, loader ownership and 100-by-256 timing.
|
||||
- Classifier/material/shutdown/M2 placement/build regressions cover adjacent behavior.
|
||||
- Fidelity evidence is exact state/lifecycle extraction; no asset-backed or
|
||||
original-client visual parity claim is made.
|
||||
- Performance budget: 25,600 request/complete/pop transitions under one second.
|
||||
|
||||
## Extension points
|
||||
|
||||
- Mesh/missing cache adoption can move to a separate finalizer without changing this state.
|
||||
- Animated M2 has distinct candidate/static fallback semantics and remains separate.
|
||||
|
||||
## Capability status
|
||||
|
||||
| Capability | Status | Evidence | Gap/next step |
|
||||
|---|---|---|---|
|
||||
| Static M2 request/finalize state | Implemented extraction | Contract/source/timing verifier | Asset-backed long traversal pending |
|
||||
| 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 |
|
||||
| Mesh materialization | Existing loader-owned | Renderer/material tests | Further safe extraction pending |
|
||||
|
||||
## Known gaps and risks
|
||||
|
||||
- Records remain Dictionaries because ResourceLoader polling is a dynamic Godot boundary.
|
||||
- Clear does not drain ResourceLoader; caller ordering is mandatory.
|
||||
- Status integers are intentionally opaque inside state.
|
||||
- No private asset traversal, leak/descriptor-pressure or p95/p99 run is included.
|
||||
|
||||
## Source map
|
||||
|
||||
| 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, 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 |
|
||||
|
||||
## Related decisions and references
|
||||
|
||||
- [`m2-runtime-mesh-rebuild-classifier.md`](m2-runtime-mesh-rebuild-classifier.md)
|
||||
- [`m2-build-batch-planner.md`](m2-build-batch-planner.md)
|
||||
- [`world-renderer.md`](world-renderer.md)
|
||||
- [`../../RENDER.md`](../../RENDER.md)
|
||||
- [`../../targets/roadmap/02-rendering-and-graphics.md`](../../targets/roadmap/02-rendering-and-graphics.md)
|
||||
@@ -0,0 +1,220 @@
|
||||
# M2 Mesh Resource Cache State
|
||||
|
||||
## Metadata
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Status | Implemented extraction |
|
||||
| Target/work package | M03 / `M03-RND-M2-MESH-RESOURCE-CACHE-001` |
|
||||
| Owners | Prepared static M2 Mesh resource references by normalized path |
|
||||
| Last verified | Worktree `work/sindo-main-codex/m03-m2-mesh-resource-cache`, 2026-07-17 |
|
||||
| Profiles/capabilities | Existing static M2 cached and raw fallback paths |
|
||||
|
||||
## Purpose
|
||||
|
||||
Own the runtime cache of prepared static M2 `Mesh` references outside
|
||||
`StreamingWorldLoader`. Dedicated services extract and prepare each Mesh; the
|
||||
loader remains responsible for finding, loading, adopting and materializing it.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Own the shared M2 missing-path, prototype or animated-scene caches.
|
||||
- Call ResourceLoader/FileAccess or select `.tscn`/`.glb` paths.
|
||||
- Extract a Mesh from a Resource or Node.
|
||||
- Refresh materials, rebuild Meshes or create MultiMesh instances.
|
||||
- Add eviction, persistence, cache versions or quality-profile behavior.
|
||||
|
||||
## Context and boundaries
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Need[Static M2 mesh lookup] --> Loader[StreamingWorldLoader]
|
||||
Loader --> Cache[M2MeshResourceCacheState]
|
||||
Cache -->|cached Mesh or null| Loader
|
||||
Loader --> Pipeline[M2MeshLoadPipelineState]
|
||||
Pipeline --> IO[ResourceLoader]
|
||||
IO --> Extract[M2MeshResourceExtractor]
|
||||
Extract --> Prepare[M2RuntimeMeshFinalizer]
|
||||
Prepare --> Cache
|
||||
Loader --> Build[M2StaticBatchMaterializer]
|
||||
```
|
||||
|
||||
The cache may retain `Mesh` resources and copied normalized-path Strings. It has
|
||||
no Node, ResourceLoader, filesystem, worker, scheduler, missing-state, builder,
|
||||
gameplay, network or editor dependency.
|
||||
|
||||
## Public API
|
||||
|
||||
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|
||||
|---|---|---|---|---|
|
||||
| `store_mesh(path, mesh)` | Command/query | Retain or replace one prepared Mesh | Renderer main thread; final shutdown | Empty path/null Mesh rejected |
|
||||
| `has_mesh(path)` | Query | Test whether a Mesh is retained | Renderer main thread | Empty/unknown false |
|
||||
| `find_mesh(path)` | Query | Borrow exact retained Mesh reference | Renderer main thread | Empty/unknown null |
|
||||
| `mesh_count()` | Query | Return retained entry count | Diagnostics/tests | None |
|
||||
| `normalized_paths_snapshot()` | Query | Copy insertion-order path keys | Diagnostics/tests | Never exposes Mesh references |
|
||||
| `clear()` | Command | Release every retained Mesh reference | Final shutdown after async drain | Idempotent |
|
||||
|
||||
## Inputs and outputs
|
||||
|
||||
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|
||||
|---|---|---|---|---|---|
|
||||
| Input | Normalized relative M2 path | Loader normalization adapter | Cache key | Copied String | Cache lifetime |
|
||||
| Input | Prepared static Mesh | Loader finalizer or synchronous fallback | Cache value | Strong reference retained | Until replacement/clear |
|
||||
| Output | Borrowed exact Mesh | Cache lookup | Static M2 batch builder | Cache retains reference | One build/materialization pass |
|
||||
| Output | Detached normalized paths | Cache diagnostics | Verifier/future metrics | Caller-owned Strings | Snapshot lifetime |
|
||||
|
||||
Store and clear mutate only the cache Dictionary and Resource reference counts.
|
||||
|
||||
## Data flow
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Lookup[Lookup normalized M2 path] --> Cached{Mesh cached?}
|
||||
Cached -->|yes| Return[Return retained Mesh]
|
||||
Cached -->|no| Missing{Prototype outcome says missing?}
|
||||
Missing -->|yes| Null[Return null]
|
||||
Missing -->|no| Load[Loader request or synchronous fallback]
|
||||
Load --> Prepare[Extractor and runtime finalizer prepare Mesh]
|
||||
Prepare --> Valid{Prepared Mesh valid?}
|
||||
Valid -->|yes| Store[Store or replace cache entry]
|
||||
Valid -->|no| MarkMissing[Loader updates prototype outcome state]
|
||||
Store --> Return
|
||||
```
|
||||
|
||||
## Lifecycle/state
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Absent
|
||||
Absent --> Cached: store valid Mesh
|
||||
Cached --> Cached: replace Mesh reference
|
||||
Cached --> Absent: final shutdown clear
|
||||
```
|
||||
|
||||
Map/world refresh retains cached Meshes. Only the existing final runtime-cache
|
||||
release clears them after asynchronous shutdown drain.
|
||||
|
||||
## Main sequence
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Loader as StreamingWorldLoader
|
||||
participant Cache as M2MeshResourceCacheState
|
||||
participant Pipeline as M2MeshLoadPipelineState
|
||||
Loader->>Cache: find/has normalized path
|
||||
alt cache miss
|
||||
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
|
||||
```
|
||||
|
||||
## Dependency diagram
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
Loader[StreamingWorldLoader] --> Cache[M2MeshResourceCacheState]
|
||||
Loader --> Pipeline[M2MeshLoadPipelineState]
|
||||
Loader --> Finalizer[M2RuntimeMeshFinalizer]
|
||||
Loader --> Builder[M2Builder]
|
||||
Loader --> Materializer[M2StaticBatchMaterializer]
|
||||
Cache --> Mesh[Godot Mesh Resource]
|
||||
Cache -. no dependency .-> Pipeline
|
||||
Cache -. no dependency .-> Classifier
|
||||
Cache -. no dependency .-> Builder
|
||||
```
|
||||
|
||||
## Ownership, threading and resources
|
||||
|
||||
- Renderer main-thread adapters serialize every cache operation.
|
||||
- The cache owns normalized-path Strings and strong Mesh references.
|
||||
- Borrowed Mesh lookups do not transfer ownership or duplicate resources.
|
||||
- `M2MeshResourceExtractor` owns first-Mesh selection and temporary PackedScene
|
||||
instances. `M2PrototypeCacheState` owns missing/prototype/animated state; the
|
||||
static materializer owns MultiMesh construction/attachment;
|
||||
`M2MeshResourceFinalizer` owns resource adoption, while the loader owns
|
||||
build-job and scheduler-permit decisions.
|
||||
- The loader drains asynchronous work before the final cache clear.
|
||||
|
||||
## Errors, cancellation and recovery
|
||||
|
||||
| Failure/state | Detection | Behavior | Diagnostic | Recovery |
|
||||
|---|---|---|---|---|
|
||||
| Empty path or null Mesh | Cache guard | Reject unchanged | Contract verifier | Correct caller input |
|
||||
| Unknown lookup | Membership query | Return null | Existing loader path | Request/load or missing decision |
|
||||
| Replacement | Existing key | Retain new exact reference | Contract verifier | Normal subsequent lookup |
|
||||
| Load/preparation failure | Loader boundary | Do not store; loader marks missing | Existing renderer behavior | Cache/source correction and reload |
|
||||
| Final shutdown | Loader lifecycle | Clear releases references | Shutdown/source verifier | New loader starts empty |
|
||||
|
||||
## Configuration and capabilities
|
||||
|
||||
The module adds no setting, budget or profile branch. Cache path order, finalize
|
||||
permits and batching remain loader-owned; refresh version `2` belongs to
|
||||
`M2RuntimeMeshFinalizer`.
|
||||
|
||||
## Persistence, cache and migration
|
||||
|
||||
The state is runtime-only and serializes nothing. Existing `.tscn`/`.glb`
|
||||
formats, import caches and material refresh metadata are unchanged; no migration
|
||||
or rebake is required.
|
||||
|
||||
## Diagnostics and observability
|
||||
|
||||
`mesh_count` and `normalized_paths_snapshot` expose scalar ownership diagnostics
|
||||
without Mesh references. Existing renderer metrics/logging are unchanged because
|
||||
the historical Mesh cache had no queue contribution or log site.
|
||||
|
||||
## Verification
|
||||
|
||||
- `verify_m2_mesh_resource_cache_state.gd`: invalid input, exact reference,
|
||||
replacement, lookup, detached path snapshot, idempotent clear, loader source
|
||||
ownership and 100-by-256 bounded timing.
|
||||
- Mesh pipeline/classifier/material/shutdown/M2 build/facade regressions cover
|
||||
adjacent behavior.
|
||||
- Fidelity evidence is exact cache/lifetime extraction; no original-client
|
||||
visual-parity or asset-backed performance claim is made.
|
||||
|
||||
## Extension points
|
||||
|
||||
- `M2RuntimeMeshFinalizer` returns a prepared Mesh for `store_mesh` without
|
||||
changing cache ownership.
|
||||
- Eviction requires measured memory pressure and explicit lifetime semantics.
|
||||
|
||||
## Capability status
|
||||
|
||||
| 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/finalizer verifiers | Asset-backed traversal pending |
|
||||
| M2 Mesh extraction | Implemented extraction | Resource/order/lifetime verifier | Asset-backed corrupt-scene fixture pending |
|
||||
| M2 Mesh preparation | Implemented extraction | Runtime finalizer transition/rebuild verifier | Asset-backed material comparison pending |
|
||||
|
||||
## Known gaps and risks
|
||||
|
||||
- Strong Mesh references intentionally remain until final shutdown.
|
||||
- Missing/prototype/animated lookup state is now a separate sibling service.
|
||||
- No proprietary M2 traversal, leak/descriptor-pressure, p95/p99 or paired
|
||||
original-client capture is included.
|
||||
|
||||
## Source map
|
||||
|
||||
| Path | Responsibility |
|
||||
|---|---|
|
||||
| `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, 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 |
|
||||
|
||||
## Related decisions and references
|
||||
|
||||
- [`m2-mesh-load-pipeline-state.md`](m2-mesh-load-pipeline-state.md)
|
||||
- [`m2-runtime-mesh-rebuild-classifier.md`](m2-runtime-mesh-rebuild-classifier.md)
|
||||
- [`world-renderer.md`](world-renderer.md)
|
||||
- [`../../RENDER.md`](../../RENDER.md)
|
||||
- [`../../targets/roadmap/02-rendering-and-graphics.md`](../../targets/roadmap/02-rendering-and-graphics.md)
|
||||
@@ -0,0 +1,211 @@
|
||||
# M2 Mesh Resource Extractor
|
||||
|
||||
## Metadata
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Status | Implemented extraction |
|
||||
| Target/work package | M03 / `M03-RND-M2-MESH-RESOURCE-EXTRACTOR-001` |
|
||||
| Owners | First-Mesh selection and temporary PackedScene instance lifetime |
|
||||
| Last verified | Worktree `work/sindo-main-codex/m03-m2-mesh-resource-extractor`, 2026-07-17 |
|
||||
| Profiles/capabilities | Static M2 threaded cache and synchronous raw/prototype fallback paths |
|
||||
|
||||
## Purpose
|
||||
|
||||
Select the first M2 Mesh from a direct Resource, PackedScene or existing Node
|
||||
subtree while keeping traversal and temporary-node lifetime outside
|
||||
`StreamingWorldLoader`.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Request/load Resources or choose cache paths.
|
||||
- Load raw M2 data, refresh materials or rebuild Meshes.
|
||||
- Own Mesh/prototype/missing caches or pending/finalize queues.
|
||||
- Create MultiMeshes, attach Nodes or consume render permits.
|
||||
- Change first-Mesh selection order or visible output.
|
||||
|
||||
## Context and boundaries
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Pipeline[M2 load pipeline] --> Loader[StreamingWorldLoader]
|
||||
Loader --> Resource[Loaded Mesh or PackedScene Resource]
|
||||
Resource --> Extractor[M2MeshResourceExtractor]
|
||||
Extractor -->|first Mesh or null| Loader
|
||||
Loader --> Prepare[M2RuntimeMeshFinalizer]
|
||||
Prepare --> Cache[M2MeshResourceCacheState]
|
||||
Prototype[Existing M2 Node subtree] --> Extractor
|
||||
```
|
||||
|
||||
The extractor may inspect `Resource`, `PackedScene`, `Node`, `MeshInstance3D`
|
||||
and `Mesh` engine types. It has no ResourceLoader, filesystem, worker, cache,
|
||||
scheduler, builder, gameplay, network or editor dependency.
|
||||
|
||||
## Public API
|
||||
|
||||
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|
||||
|---|---|---|---|---|
|
||||
| `extract_first_mesh(resource)` | Query with temporary ownership | Return direct Mesh or first Mesh from one PackedScene instance | Renderer main thread; temporary root freed before return | Null/unsupported/empty returns null |
|
||||
| `find_first_mesh_in_subtree(root)` | Query | Return first Mesh in historical depth-first preorder | Renderer main thread; caller-owned subtree unchanged | Null/no Mesh returns null |
|
||||
|
||||
## Inputs and outputs
|
||||
|
||||
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|
||||
|---|---|---|---|---|---|
|
||||
| Input | Direct Mesh Resource | Loader threaded finalizer | Extractor | Borrowed Resource | One call |
|
||||
| Input | PackedScene Resource | Loader threaded finalizer | Extractor | Borrowed scene; extractor owns temporary instance | Temporary root until return |
|
||||
| Input | Existing prototype/rebuild Node subtree | Loader builder/fallback | Extractor | Borrowed Node tree | One traversal |
|
||||
| Output | First Mesh reference | Extractor | Loader preparation/cache adapter | Borrowed exact reference | Cache may retain later |
|
||||
|
||||
The only side effect is synchronous creation and immediate `free()` of a
|
||||
temporary PackedScene instance.
|
||||
|
||||
## Data flow
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Input[Resource or Node subtree] --> Direct{Direct Mesh?}
|
||||
Direct -->|yes| Return[Return exact Mesh]
|
||||
Direct -->|no| Packed{PackedScene?}
|
||||
Packed -->|yes| Instantiate[Instantiate temporary root]
|
||||
Packed -->|no| Traverse[Traverse caller-owned subtree]
|
||||
Instantiate --> TraverseTemp[Depth-first preorder traversal]
|
||||
TraverseTemp --> Free[Free temporary root]
|
||||
Free --> ReturnOrNull[Return first Mesh or null]
|
||||
Traverse --> ReturnOrNull
|
||||
```
|
||||
|
||||
## Lifecycle/state
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Stateless
|
||||
Stateless --> TemporaryInstance: extract PackedScene
|
||||
TemporaryInstance --> Traversing: instantiate succeeded
|
||||
Traversing --> Stateless: free temporary root before return
|
||||
Stateless --> Stateless: direct Mesh/subtree/invalid input
|
||||
```
|
||||
|
||||
No state or Resource reference is retained between calls.
|
||||
|
||||
## Main sequence
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Finalizer as M2MeshResourceFinalizer
|
||||
participant Extractor as M2MeshResourceExtractor
|
||||
participant Scene as PackedScene
|
||||
Finalizer->>Extractor: extract_first_mesh(loaded Resource)
|
||||
alt direct Mesh
|
||||
Extractor-->>Finalizer: same Mesh reference
|
||||
else PackedScene
|
||||
Extractor->>Scene: instantiate()
|
||||
Scene-->>Extractor: temporary root
|
||||
Extractor->>Extractor: depth-first first-Mesh search
|
||||
Extractor->>Extractor: temporary_root.free()
|
||||
Extractor-->>Finalizer: Mesh or null
|
||||
end
|
||||
Finalizer->>Finalizer: prepare and cache Mesh or mark missing
|
||||
```
|
||||
|
||||
## Dependency diagram
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
Finalizer[M2MeshResourceFinalizer] --> Extractor[M2MeshResourceExtractor]
|
||||
Extractor --> Types[Resource / PackedScene / Node / Mesh]
|
||||
Loader --> Pipeline[M2MeshLoadPipelineState]
|
||||
Loader --> Cache[M2MeshResourceCacheState]
|
||||
Loader --> Finalizer[M2RuntimeMeshFinalizer]
|
||||
Extractor -. no dependency .-> Pipeline
|
||||
Extractor -. no dependency .-> Cache
|
||||
Extractor -. no dependency .-> Classifier
|
||||
```
|
||||
|
||||
## Ownership, threading and resources
|
||||
|
||||
- The renderer main thread performs all traversal and PackedScene instantiation.
|
||||
- Direct and subtree Mesh references are borrowed without duplication.
|
||||
- A PackedScene temporary root is extractor-owned and synchronously freed.
|
||||
- Existing prototype/rebuild subtrees remain caller-owned and are never mutated.
|
||||
- Cache retention remains a cache-state concern; runtime material preparation
|
||||
belongs to `M2RuntimeMeshFinalizer`.
|
||||
|
||||
## Errors, cancellation and recovery
|
||||
|
||||
| Failure/state | Detection | Behavior | Diagnostic | Recovery |
|
||||
|---|---|---|---|---|
|
||||
| Null/unsupported Resource | Type guard | Return null | Contract verifier | Loader marks missing/falls back |
|
||||
| PackedScene instantiate failure | Null root | Return null | Existing loader behavior | Rebuild cache/source |
|
||||
| No Mesh in subtree | Traversal exhaustion | Return null | Contract verifier | Loader marks missing/falls back |
|
||||
| Null MeshInstance Mesh | Null traversal result | Parent continues with later siblings | Order verifier | Normal sibling search |
|
||||
| Shutdown/cancel | No retained state | No action | Ownership docs | Caller owns surrounding job |
|
||||
|
||||
## Configuration and capabilities
|
||||
|
||||
The module adds no settings or profile branches. Cache format order, material
|
||||
refresh version, render permits and batching remain unchanged outside it.
|
||||
|
||||
## Persistence, cache and migration
|
||||
|
||||
The extractor is stateless and serializes nothing. Existing `.tscn`/`.glb`
|
||||
formats, cache versions and rebuild metadata are unchanged; no migration or
|
||||
rebake is required.
|
||||
|
||||
## Diagnostics and observability
|
||||
|
||||
The module emits no logs and retains no counters. Loader correlation remains the
|
||||
normalized M2 path. Its verifier measures traversal time and temporary Node count.
|
||||
|
||||
## Verification
|
||||
|
||||
- `verify_m2_mesh_resource_extractor.gd`: direct/invalid Resources, depth-first
|
||||
child order, null-Mesh sibling continuation, subtree ownership, PackedScene
|
||||
exact Mesh identity, temporary Node release, source boundaries and 10,000
|
||||
bounded traversals.
|
||||
- M2 pipeline/cache/classifier/material/shutdown/build regressions cover adjacent
|
||||
behavior. No asset-backed visual parity claim is made.
|
||||
|
||||
## Extension points
|
||||
|
||||
- `M2MeshResourceFinalizer` passes the Mesh to `M2RuntimeMeshFinalizer` and
|
||||
stores its result in `M2MeshResourceCacheState`.
|
||||
- Broader generic scene traversal is intentionally excluded until another real
|
||||
consumer requires the same exact contract.
|
||||
|
||||
## Capability status
|
||||
|
||||
| Capability | Status | Evidence | Gap/next step |
|
||||
|---|---|---|---|
|
||||
| Direct/PackedScene first-Mesh extraction | Implemented extraction | Resource/order/lifetime verifier | Asset-backed corrupt-scene fixture pending |
|
||||
| Prototype/rebuild subtree selection | Implemented extraction | Synthetic preorder verifier | Asset-backed M2 traversal pending |
|
||||
| Material refresh/rebuild finalization | Implemented extraction | Runtime finalizer transition/rebuild verifier | Asset-backed material comparison pending |
|
||||
|
||||
## Known gaps and risks
|
||||
|
||||
- PackedScene instantiation remains synchronous main-thread work behind the
|
||||
existing finalize permit.
|
||||
- The first Mesh rule intentionally ignores additional MeshInstances.
|
||||
- No proprietary M2 corpus, corrupt cache, leak/descriptor-pressure, p95/p99 or
|
||||
paired original-client capture is included.
|
||||
|
||||
## Source map
|
||||
|
||||
| Path | Responsibility |
|
||||
|---|---|
|
||||
| `src/render/m2/m2_mesh_resource_extractor.gd` | First-Mesh traversal and temporary PackedScene lifetime |
|
||||
| `src/render/m2/m2_mesh_resource_finalizer.gd` | Terminal Resource consumer and extraction caller |
|
||||
| `src/render/m2/m2_prototype_cache_state.gd` | Prototype and missing/static-only lookup state |
|
||||
| `src/scenes/streaming/streaming_world_loader.gd` | Request admission, permits and materialization |
|
||||
| `src/render/m2/m2_runtime_mesh_finalizer.gd` | Refresh/rebuild/fallback preparation |
|
||||
| `src/render/m2/m2_mesh_resource_cache_state.gd` | Prepared Mesh retention |
|
||||
| `src/tools/verify_m2_mesh_resource_extractor.gd` | Resource/order/lifetime/boundary/timing regression |
|
||||
|
||||
## Related decisions and references
|
||||
|
||||
- [`m2-mesh-resource-cache-state.md`](m2-mesh-resource-cache-state.md)
|
||||
- [`m2-mesh-load-pipeline-state.md`](m2-mesh-load-pipeline-state.md)
|
||||
- [`m2-runtime-mesh-rebuild-classifier.md`](m2-runtime-mesh-rebuild-classifier.md)
|
||||
- [`world-renderer.md`](world-renderer.md)
|
||||
- [`../../RENDER.md`](../../RENDER.md)
|
||||
- [`../../targets/roadmap/02-rendering-and-graphics.md`](../../targets/roadmap/02-rendering-and-graphics.md)
|
||||
@@ -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)
|
||||
@@ -0,0 +1,258 @@
|
||||
# M2 Prototype Cache State
|
||||
|
||||
## Metadata
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Status | Implemented extraction |
|
||||
| Target/work package | M03 / `M03-RND-M2-PROTOTYPE-CACHE-001` |
|
||||
| Owners | Detached static/animated prototypes and missing/static-animation outcomes |
|
||||
| Last verified | Worktree `work/sindo-main-codex/m03-m2-prototype-cache`, 2026-07-17 |
|
||||
| Profiles/capabilities | Static, GLB animated and native animated M2 prototype reuse |
|
||||
|
||||
## Purpose
|
||||
|
||||
Own the four shutdown-lifetime M2 prototype lookup states previously stored in
|
||||
`StreamingWorldLoader`: static and animated `Node3D` references, missing model
|
||||
paths and paths whose animation fallback is static-only.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Normalize paths, choose cache files, call ResourceLoader or parse raw M2 data.
|
||||
- Build, repair or instantiate prototypes, Meshes, materials or MultiMeshes.
|
||||
- Own animation/Mesh request queues, permits or tile/build state.
|
||||
- Add eviction, persistence, retries, profile rules or format changes.
|
||||
|
||||
## Context and boundaries
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Loader[StreamingWorldLoader] --> State[M2PrototypeCacheState]
|
||||
Observer[M2 resource observers] --> State
|
||||
Resource[ResourceLoader / raw builders] --> Loader
|
||||
Resource --> Observer
|
||||
State --> Static[Static prototype Node3D]
|
||||
State --> Animated[Animated prototype Node3D]
|
||||
State --> Missing[Missing-model path set]
|
||||
State --> StaticOnly[Static-animation path set]
|
||||
Loader --> Instances[Duplicate/animated instance materialization]
|
||||
```
|
||||
|
||||
The state may depend on `Node3D` lifetime APIs and value-only path containers.
|
||||
Filesystem/native/ResourceLoader APIs, builders, worker pools, render scheduling,
|
||||
Mesh traversal and other application layers are forbidden.
|
||||
|
||||
## Public API
|
||||
|
||||
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|
||||
|---|---|---|---|---|
|
||||
| `has_static_prototype(path)` | Query | Test for one valid retained static prototype | Renderer main thread; shutdown lifetime | Empty/invalid returns false |
|
||||
| `find_static_prototype(path)` | Query | Return exact retained static Node reference | Renderer main thread; borrowed | Empty/unknown/invalid returns null |
|
||||
| `adopt_static_prototype(path, prototype)` | Command/query | Retain first valid static prototype and return canonical reference | Renderer main thread; until shutdown | Invalid input returns null; later candidate released |
|
||||
| `has_animated_prototype(path)` | Query | Test for one valid retained animated prototype | Renderer main thread; shutdown lifetime | Empty/invalid returns false |
|
||||
| `find_animated_prototype(path)` | Query | Return exact retained animated Node reference | Renderer main thread; borrowed | Empty/unknown/invalid returns null |
|
||||
| `adopt_animated_prototype(path, prototype)` | Command/query | Retain first valid animated prototype and return canonical reference | Renderer main thread; until shutdown | Invalid input returns null; later candidate released |
|
||||
| `mark_model_missing(path)` / `is_model_missing(path)` | Command/query | Store/query a missing-model outcome | Renderer main thread; until shutdown | Empty mark rejected |
|
||||
| `mark_animation_static(path)` / `is_animation_static(path)` | Command/query | Store/query static-only animation outcome | Renderer main thread; until shutdown | Empty mark rejected |
|
||||
| `diagnostic_snapshot()` | Diagnostic query | Return detached sorted path arrays for all four states | Caller-owned result | Never exposes Nodes |
|
||||
| `clear_and_release()` | Lifecycle command | Release prototypes and clear all state | Final shutdown after async drain | Idempotent |
|
||||
|
||||
## Inputs and outputs
|
||||
|
||||
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|
||||
|---|---|---|---|---|---|
|
||||
| Input | Normalized non-empty M2 path | Loader normalization | Cache state | Copied String key | Shutdown lifetime |
|
||||
| Input | Detached static Node3D | Cached scene/raw builder adapter | Cache state | Adopted on success | Until final shutdown |
|
||||
| Input | Detached animated Node3D | GLB/native animation adapter | Cache state | Adopted on success | Until final shutdown |
|
||||
| Input | Missing/static-only outcome | Resource observers/finalizers | Cache state | Boolean set entry | Until final shutdown |
|
||||
| Output | Canonical prototype Node3D | Cache state | Loader instance/material adapter | Borrowed exact reference | One lookup/use |
|
||||
| Output | Detached path-only snapshot | Cache state | Tests/diagnostics | Fresh caller-owned arrays | One query |
|
||||
|
||||
Side effects are strong Node retention, release of rejected duplicate candidates,
|
||||
final Node destruction/queueing and mutation of runtime-only path state.
|
||||
|
||||
## Data flow
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Request[Normalized prototype request] --> Positive{Valid prototype cached?}
|
||||
Positive -->|yes| Return[Return exact prototype]
|
||||
Positive -->|no| Negative{Missing or static-only outcome?}
|
||||
Negative -->|yes| Fallback[Return caller fallback]
|
||||
Negative -->|no| Load[Loader performs existing I/O/build]
|
||||
Load --> Success{Valid prototype?}
|
||||
Success -->|yes| Adopt[Adopt first candidate]
|
||||
Adopt --> Return
|
||||
Success -->|no| Mark[Mark relevant negative outcome]
|
||||
Mark --> Fallback
|
||||
```
|
||||
|
||||
## Lifecycle/state
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Empty
|
||||
Empty --> Positive: adopt static/animated prototype
|
||||
Empty --> Negative: mark missing/static-only path
|
||||
Positive --> Positive: lookup or same-reference adoption
|
||||
Positive --> Positive: duplicate candidate released; first wins
|
||||
Negative --> Negative: repeated mark/query
|
||||
Positive --> Released: final shutdown
|
||||
Negative --> Released: final shutdown
|
||||
Released --> Released: repeated clear
|
||||
Released --> [*]
|
||||
```
|
||||
|
||||
Map refresh does not transition or clear this service. Final scene shutdown calls
|
||||
`clear_and_release` only after worker and ResourceLoader work is drained.
|
||||
|
||||
## Main sequence
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Loader as StreamingWorldLoader
|
||||
participant State as M2PrototypeCacheState
|
||||
participant Source as ResourceLoader/raw builder
|
||||
Loader->>State: find static/animated(path)
|
||||
alt positive hit
|
||||
State-->>Loader: exact retained Node3D
|
||||
else negative hit
|
||||
Loader->>State: is missing/static-only(path)
|
||||
State-->>Loader: true; use historical fallback
|
||||
else uncached
|
||||
Loader->>Source: existing load/build operation
|
||||
alt valid prototype
|
||||
Loader->>State: adopt(path, prototype)
|
||||
State-->>Loader: canonical Node3D
|
||||
else failure
|
||||
Loader->>State: mark missing/static-only(path)
|
||||
end
|
||||
end
|
||||
Loader->>State: shutdown clear_and_release()
|
||||
State-->>Loader: all paths empty; Nodes released
|
||||
```
|
||||
|
||||
## Dependency diagram
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
Loader[StreamingWorldLoader] --> State[M2PrototypeCacheState]
|
||||
Observer[M2 resource observers] --> State
|
||||
State --> Node3D
|
||||
Loader --> Raw[M2RawModelRepository]
|
||||
Observer --> ResourceLoader
|
||||
Loader --> StaticBuilder[M2Builder]
|
||||
Observer --> AnimatedBuilder[M2NativeAnimatedBuilder]
|
||||
State -. no dependency .-> Raw
|
||||
State -. no dependency .-> ResourceLoader
|
||||
State -. no dependency .-> StaticBuilder
|
||||
State -. no dependency .-> MeshCache[M2MeshResourceCacheState]
|
||||
```
|
||||
|
||||
## Ownership, threading and resources
|
||||
|
||||
- Renderer main-thread adapters serialize all operations.
|
||||
- The state owns strong references to adopted static and animated prototype Nodes.
|
||||
- A duplicate detached candidate is released immediately; the first valid Node
|
||||
stays canonical for that path.
|
||||
- Lookups borrow exact Node references without transferring ownership.
|
||||
- Detached Nodes are synchronously `free()`d at shutdown. Defensive in-tree
|
||||
Nodes use `queue_free()` to preserve SceneTree rules.
|
||||
- Negative entries own only copied normalized-path Strings.
|
||||
|
||||
## Errors, cancellation and recovery
|
||||
|
||||
| Failure/state | Detection | Behavior | Diagnostic | Recovery |
|
||||
|---|---|---|---|---|
|
||||
| Empty path/null/invalid Node | Admission guard | Reject/null/false | Dedicated verifier | Correct caller input |
|
||||
| Unknown positive lookup | Map miss | Return null | Path snapshot | Loader continues existing load/fallback |
|
||||
| Duplicate candidate | Occupied valid path | Release candidate; return first | Identity fixture | None required |
|
||||
| Model source/load failure | Loader result | Mark missing outcome | Existing fallback behavior | New loader session/source repair |
|
||||
| Animation unavailable/unsafe | Observer/native result | Mark static-only outcome | Existing fallback behavior | New loader session/cache repair |
|
||||
| Shutdown | Loader lifecycle | Release positive and clear all state | Shutdown verifier | New loader begins empty |
|
||||
| Cancellation | Not owned | No state transition inside this service | N/A | Loader drains/cancels before shutdown clear |
|
||||
|
||||
## Configuration and capabilities
|
||||
|
||||
| Setting/capability | Default | Profile | Runtime mutable | Effect |
|
||||
|---|---|---|---|---|
|
||||
| Positive cache lifetime | Final loader shutdown | All | No | Reuses detached prototypes across map refresh |
|
||||
| Negative cache lifetime | Final loader shutdown | All | No | Preserves historical fallback suppression |
|
||||
| Eviction capacity | Unbounded historical behavior | All | No | No mid-session Node destruction |
|
||||
|
||||
Animation enablement, candidate/allow/deny rules and cache paths remain loader
|
||||
configuration consumed by the cached observer. Per-frame permits remain loader-owned.
|
||||
|
||||
## Persistence, cache and migration
|
||||
|
||||
This is runtime-only state and serializes nothing. `.tscn/.glb`, material refresh
|
||||
and native M2 formats are unchanged; no migration or rebake is introduced.
|
||||
|
||||
## Diagnostics and observability
|
||||
|
||||
- `diagnostic_snapshot` exposes four sorted path arrays without Node references.
|
||||
- Cached eligibility rejection logging belongs to the cached observer; native
|
||||
success logging belongs to the native observer and cached terminal success
|
||||
logging belongs to the animation resource finalizer. Static terminal missing
|
||||
outcomes are produced by `M2MeshResourceFinalizer`.
|
||||
- Existing renderer queue metrics remain unchanged because these tables never
|
||||
contributed work counts.
|
||||
- Normalized relative path remains the correlation key.
|
||||
|
||||
## Verification
|
||||
|
||||
- `verify_m2_prototype_cache_state.gd`: invalid admission, exact static/animated
|
||||
identity, independent positive/negative states, first-wins duplicate release,
|
||||
detached/in-tree shutdown ownership, idempotent clear, detached diagnostics,
|
||||
loader source boundaries and 100-by-256 bounded path-state timing.
|
||||
- Runtime-cache shutdown regression verifies final release through the real loader.
|
||||
- Raw repository/finalizer/extractor/Mesh cache/pipeline/material/animation/M2
|
||||
build regressions protect adjacent behavior.
|
||||
- Fidelity evidence is exact cache transition and lifetime extraction. No new
|
||||
proprietary asset or original-client visual comparison is claimed.
|
||||
|
||||
## Extension points
|
||||
|
||||
Eviction or byte/count budgets require measured memory evidence and explicit
|
||||
prototype-user lifetime rules. Cached and native animation observers consume
|
||||
this state without moving ResourceLoader or builder ownership into cache state.
|
||||
|
||||
## Capability status
|
||||
|
||||
| Capability | Status | Evidence | Gap/next step |
|
||||
|---|---|---|---|
|
||||
| Static prototype retention | Implemented extraction | Identity/source/shutdown verifier | Asset-backed traversal/leak run pending |
|
||||
| Animated prototype retention | Implemented extraction | Independent identity/shutdown verifier | Legal animated success fixture pending |
|
||||
| Missing/static-only outcomes | Implemented extraction | Independent negative-state verifier | Recovery still requires new loader session |
|
||||
| Bounded eviction | Historical unbounded behavior | Documented lifetime | Add only after memory profiling |
|
||||
|
||||
## Known gaps and risks
|
||||
|
||||
- Strong Node references intentionally live until final shutdown.
|
||||
- Negative outcomes do not retain reason codes and cannot be individually retried,
|
||||
preserving historical behavior.
|
||||
- No proprietary long traversal, descriptor pressure, leak, p95/p99 or paired
|
||||
original-client evidence is included.
|
||||
|
||||
## Source map
|
||||
|
||||
| Path | Responsibility |
|
||||
|---|---|
|
||||
| `src/render/m2/m2_prototype_cache_state.gd` | Positive Node ownership, negative path state and shutdown release |
|
||||
| `src/render/m2/m2_native_animation_resource_observer.gd` | Animated raw/build and positive/static-only transitions |
|
||||
| `src/render/m2/m2_animation_resource_finalizer.gd` | Cached terminal positive/static-only transitions |
|
||||
| `src/render/m2/m2_mesh_resource_finalizer.gd` | Static terminal missing transitions |
|
||||
| `src/scenes/streaming/streaming_world_loader.gd` | Normalization, observer order, remaining I/O/fallback adapters |
|
||||
| `src/tools/verify_m2_prototype_cache_state.gd` | Admission/identity/lifecycle/source/timing regression |
|
||||
| `src/tools/verify_render_runtime_cache_shutdown.gd` | Integrated final-shutdown release regression |
|
||||
|
||||
## Related decisions and references
|
||||
|
||||
- [`m2-raw-model-repository.md`](m2-raw-model-repository.md)
|
||||
- [`m2-animated-scene-finalizer.md`](m2-animated-scene-finalizer.md)
|
||||
- [`m2-animation-load-pipeline-state.md`](m2-animation-load-pipeline-state.md)
|
||||
- [`m2-mesh-resource-cache-state.md`](m2-mesh-resource-cache-state.md)
|
||||
- [`m2-mesh-load-pipeline-state.md`](m2-mesh-load-pipeline-state.md)
|
||||
- [`world-renderer.md`](world-renderer.md)
|
||||
- [`../../RENDER.md`](../../RENDER.md)
|
||||
- [`../../targets/roadmap/02-rendering-and-graphics.md`](../../targets/roadmap/02-rendering-and-graphics.md)
|
||||
@@ -0,0 +1,235 @@
|
||||
# M2 Raw Model Repository
|
||||
|
||||
## Metadata
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Status | Implemented extraction |
|
||||
| Target/work package | M03 / `M03-RND-M2-RAW-MODEL-REPOSITORY-001` |
|
||||
| Owners | Native M2Loader availability, file existence and raw Dictionary loading |
|
||||
| Last verified | Worktree `work/sindo-main-codex/m03-m2-raw-model-repository`, 2026-07-17 |
|
||||
| Profiles/capabilities | Static and native-animated raw M2 reads |
|
||||
|
||||
## Purpose
|
||||
|
||||
Provide one stateless boundary between renderer orchestration and the optional
|
||||
native `M2Loader`. The repository resolves an extracted path and returns the raw
|
||||
static or animated Dictionary consumed by existing builders and classifiers.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Normalize relative paths or choose `.tscn`/`.glb` cache fallbacks.
|
||||
- Cache successful data, missing paths, Meshes, Nodes or animation decisions.
|
||||
- Build geometry/materials, own queues or attach renderer objects.
|
||||
- Change native parsing, cache formats, retry policy or visible behavior.
|
||||
|
||||
## Context and boundaries
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
MeshFinalizer[M2MeshResourceFinalizer] --> Repository[M2RawModelRepository]
|
||||
Loader[StreamingWorldLoader] --> Repository
|
||||
Observer[M2NativeAnimationResourceObserver] --> Repository
|
||||
Repository --> File[Extracted M2 file]
|
||||
Repository --> Native[ClassDB M2Loader]
|
||||
Native --> Raw[Raw Dictionary]
|
||||
Raw --> MeshFinalizer
|
||||
Raw --> Loader
|
||||
Raw --> Observer
|
||||
Loader --> Builder[Existing M2 builders/finalizer]
|
||||
```
|
||||
|
||||
Allowed dependencies are `ProjectSettings`, `FileAccess` and `ClassDB` for the
|
||||
native file boundary. Resource caches, builders, SceneTree types, worker pools,
|
||||
renderer policy and other application layers are forbidden.
|
||||
|
||||
## Public API
|
||||
|
||||
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|
||||
|---|---|---|---|---|
|
||||
| `load_static_model_data(extracted_directory, normalized_relative_path)` | Query | Invoke native `load_m2` for one existing extracted file | Synchronous; stateless | Returns `{}` for rejected/unavailable/invalid result |
|
||||
| `load_animated_model_data(extracted_directory, normalized_relative_path)` | Query | Invoke native `load_m2_animated` for one existing extracted file | Synchronous; stateless | Returns `{}` for rejected/unavailable/invalid result |
|
||||
|
||||
## Inputs and outputs
|
||||
|
||||
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|
||||
|---|---|---|---|---|---|
|
||||
| Input | Extracted directory String | Loader configuration | Repository path resolution | Copied value | One call |
|
||||
| Input | Already-normalized relative M2 path String | Loader normalization | Repository | Copied value | One call |
|
||||
| Input | Extracted `.m2` bytes | Local legal extraction | Native M2Loader | File-owned | Native call |
|
||||
| Output | Static raw M2 Dictionary | Native `load_m2` | Mesh resource finalizer/M2Builder | Fresh native result | One caller operation |
|
||||
| Output | Animated raw M2 Dictionary | Native `load_m2_animated` | Native animation observer/builder | Fresh native result | One caller operation |
|
||||
| Output | Empty Dictionary | Repository guards | Loader fallback and prototype outcome adapter | Fresh value | One failed call |
|
||||
|
||||
Side effects are limited to file-existence inspection, synchronous native file
|
||||
parsing and creation of one temporary native loader instance per accepted call.
|
||||
|
||||
## Data flow
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Request[Directory plus normalized path] --> Empty{Path empty?}
|
||||
Empty -->|yes| Reject[Return empty Dictionary]
|
||||
Empty -->|no| Class{M2Loader registered?}
|
||||
Class -->|no| Reject
|
||||
Class -->|yes| Resolve[globalize directory/path join]
|
||||
Resolve --> Exists{File exists?}
|
||||
Exists -->|no| Reject
|
||||
Exists -->|yes| Instance[Instantiate M2Loader]
|
||||
Instance --> Method{Instance and method available?}
|
||||
Method -->|no| Reject
|
||||
Method -->|yes| Parse[Call static or animated method]
|
||||
Parse --> Type{Dictionary result?}
|
||||
Type -->|no| Reject
|
||||
Type -->|yes| Return[Return raw Dictionary]
|
||||
```
|
||||
|
||||
## Lifecycle/state
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Ready: construct repository
|
||||
Ready --> Resolving: load request
|
||||
Resolving --> Ready: rejected input/unavailable dependency
|
||||
Resolving --> Parsing: file and native method available
|
||||
Parsing --> Ready: Dictionary or empty result returned
|
||||
Ready --> [*]: caller releases reference
|
||||
```
|
||||
|
||||
No request, result or failure state survives a call.
|
||||
|
||||
## Main sequence
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Caller as Loader or NativeAnimationObserver
|
||||
participant Repo as M2RawModelRepository
|
||||
participant File as FileAccess
|
||||
participant Native as M2Loader
|
||||
Caller->>Repo: load static/animated(directory, path)
|
||||
Repo->>File: file_exists(globalized joined path)
|
||||
alt dependency or file unavailable
|
||||
Repo-->>Caller: empty Dictionary
|
||||
else available
|
||||
Repo->>Native: instantiate and call exact native method
|
||||
Native-->>Repo: Variant
|
||||
Repo-->>Caller: Dictionary or empty Dictionary
|
||||
end
|
||||
```
|
||||
|
||||
## Dependency diagram
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
Loader[StreamingWorldLoader] --> Repository[M2RawModelRepository]
|
||||
Observer[M2NativeAnimationResourceObserver] --> Repository
|
||||
Repository --> ProjectSettings
|
||||
Repository --> FileAccess
|
||||
Repository --> ClassDB
|
||||
ClassDB --> Native[M2Loader extension]
|
||||
Loader --> Finalizer[M2RuntimeMeshFinalizer]
|
||||
Loader --> StaticBuilder[M2Builder]
|
||||
Observer --> AnimatedBuilder[M2NativeAnimatedBuilder]
|
||||
Repository -. no dependency .-> Finalizer
|
||||
Repository -. no dependency .-> StaticBuilder
|
||||
Repository -. no dependency .-> Cache[Renderer caches/queues]
|
||||
```
|
||||
|
||||
## Ownership, threading and resources
|
||||
|
||||
- The repository owns only call-local path, native instance and result values.
|
||||
- The loader owns path normalization and fallback selection; the Mesh resource
|
||||
finalizer owns static refresh reads, while the native observer owns the
|
||||
animated raw-read/build decision;
|
||||
`M2PrototypeCacheState` owns prototype/negative adoption.
|
||||
- Native `M2Loader` owns parsing behavior and returns a new Dictionary value.
|
||||
- Calls are synchronous on the caller's thread; current renderer callers use the
|
||||
main thread. The module starts no tasks and touches no SceneTree objects.
|
||||
- No file handle, native instance, Dictionary or engine Resource is retained.
|
||||
|
||||
## Errors, cancellation and recovery
|
||||
|
||||
| Failure | Detection | Behavior | Diagnostic | Recovery |
|
||||
|---|---|---|---|---|
|
||||
| Empty normalized path | String guard | Return `{}` | Dedicated verifier | Correct caller normalization |
|
||||
| Native extension absent | `ClassDB.class_exists` | Return `{}` | Existing renderer fallback | Install/load extension and retry |
|
||||
| Extracted file absent | `FileAccess.file_exists` | Return `{}` | Prototype cache records historical missing state through loader | Restore extraction and start a new loader session |
|
||||
| Instance/method absent | Null/`has_method` guard | Return `{}` | Source contract verifier | Repair extension registration |
|
||||
| Parse/non-Dictionary result | Result type check | Return `{}` | Existing loader fallback | Repair asset/parser and retry |
|
||||
| Cancellation requested | Not supported inside synchronous native call | Call completes | None | Caller controls whether to start call |
|
||||
|
||||
## Configuration and capabilities
|
||||
|
||||
| Setting/capability | Default | Profile | Runtime mutable | Effect |
|
||||
|---|---|---|---|---|
|
||||
| Native class name | `M2Loader` | All | No | Exact GDExtension registration |
|
||||
| Static method | `load_m2` | All | No | Existing static parser path |
|
||||
| Animated method | `load_m2_animated` | Experimental animated path | No | Existing native animation parser |
|
||||
| Extracted directory | Loader `extracted_dir` | Scene configuration | Yes, caller-owned | Root used for path join |
|
||||
|
||||
## Persistence, cache and migration
|
||||
|
||||
The module adds no persistence or cache. Native methods, normalized relative
|
||||
paths and `.tscn/.glb`/material versions are unchanged, so no migration or rebake
|
||||
is required.
|
||||
|
||||
## Diagnostics and observability
|
||||
|
||||
- Logs: none; loader retains existing fallback and debug output.
|
||||
- Metrics: none; rejected-input timing is verifier-only.
|
||||
- Debug views: none.
|
||||
- Correlation ID: normalized relative M2 path in the caller.
|
||||
|
||||
## Verification
|
||||
|
||||
- `verify_m2_raw_model_repository.gd` covers empty/missing/repeated failures,
|
||||
exact static/animated method ownership, three loader delegations, forbidden
|
||||
dependencies and 10,000 empty-path rejects under one second.
|
||||
- Adjacent finalizer/extractor/cache/pipeline/animation/material/shutdown tests
|
||||
protect caller behavior and ownership.
|
||||
- Fidelity evidence is structural: exact path join/globalization and native
|
||||
method names are preserved. No proprietary M2 or original-client visual claim
|
||||
is made by this extraction.
|
||||
|
||||
## Extension points
|
||||
|
||||
Future asset-backed verification may add a legal fixture around these value
|
||||
contracts. Caching, asynchronous I/O and parser replacement require separate
|
||||
measured work packages rather than expansion of this repository.
|
||||
|
||||
## Capability status
|
||||
|
||||
| Capability | Status | Evidence | Gap/next step |
|
||||
|---|---|---|---|
|
||||
| Static raw M2 read | Implemented extraction | Source/invalid/missing verifier | Legal success fixture pending |
|
||||
| Native animated raw M2 read | Implemented extraction | Exact method/source contract | Legal animated fixture pending |
|
||||
| Stateless failure behavior | Verified synthetic | Repeated missing call and source boundary | Asset-backed corrupt input pending |
|
||||
|
||||
## Known gaps and risks
|
||||
|
||||
- Native parsing remains synchronous and has no mid-call cancellation.
|
||||
- The repository deliberately does not distinguish missing dependency, missing
|
||||
file and parser failure; loader fallback behavior historically treats them alike.
|
||||
- Proprietary asset traversal, descriptor pressure, p95/p99 and visual comparison
|
||||
remain outside this structural extraction.
|
||||
|
||||
## Source map
|
||||
|
||||
| Path | Responsibility |
|
||||
|---|---|
|
||||
| `src/render/m2/m2_raw_model_repository.gd` | Stateless native class/file/method boundary |
|
||||
| `src/render/m2/m2_mesh_resource_finalizer.gd` | Static refresh repository consumer |
|
||||
| `src/render/m2/m2_native_animation_resource_observer.gd` | Animated raw-data consumer and builder adapter |
|
||||
| `src/render/m2/m2_prototype_cache_state.gd` | Prototype and negative-result retention |
|
||||
| `src/scenes/streaming/streaming_world_loader.gd` | Path normalization, fallback decisions and result consumers |
|
||||
| `src/native/src/m2_loader.cpp` | Native static/animated parsing implementation |
|
||||
| `src/tools/verify_m2_raw_model_repository.gd` | Rejected-input/source/dependency/timing regression |
|
||||
|
||||
## Related decisions and references
|
||||
|
||||
- [`m2-runtime-mesh-finalizer.md`](m2-runtime-mesh-finalizer.md)
|
||||
- [`m2-mesh-resource-cache-state.md`](m2-mesh-resource-cache-state.md)
|
||||
- [`m2-mesh-load-pipeline-state.md`](m2-mesh-load-pipeline-state.md)
|
||||
- [`world-renderer.md`](world-renderer.md)
|
||||
- [`../../RENDER.md`](../../RENDER.md)
|
||||
- [`../../targets/roadmap/02-rendering-and-graphics.md`](../../targets/roadmap/02-rendering-and-graphics.md)
|
||||
@@ -0,0 +1,225 @@
|
||||
# M2 Runtime Mesh Finalizer
|
||||
|
||||
## Metadata
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Status | Implemented extraction |
|
||||
| Target/work package | M03 / `M03-RND-M2-RUNTIME-MESH-FINALIZER-001` |
|
||||
| Owners | Stale Mesh refresh version, rebuild classification, M2Builder rebuild and fallback |
|
||||
| Last verified | Worktree `work/sindo-main-codex/m03-m2-runtime-mesh-finalizer`, 2026-07-17 |
|
||||
| Profiles/capabilities | Static cached and prototype M2 runtime material preparation |
|
||||
|
||||
## Purpose
|
||||
|
||||
Finalize an extracted static M2 Mesh for runtime use by accepting already-loaded
|
||||
raw M2 data, preserving material refresh version `2`, rebuilding only billboard/
|
||||
UV-rotation cases and returning the historical original-Mesh fallback.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Read raw `.m2` files or call ClassDB/FileAccess/ResourceLoader.
|
||||
- Own request/finalize queues, Mesh/missing/prototype caches or MultiMeshes.
|
||||
- Select cache paths or extract a Mesh from the originally loaded Resource.
|
||||
- Change classifier predicates, builder materials, refresh version or profiles.
|
||||
|
||||
## Context and boundaries
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
ResourceFinalizer[M2MeshResourceFinalizer] -->|is raw data required?| Finalizer[M2RuntimeMeshFinalizer]
|
||||
ResourceFinalizer --> Raw[M2RawModelRepository]
|
||||
Raw --> Finalizer
|
||||
Finalizer --> Classifier[M2RuntimeMeshRebuildClassifier]
|
||||
Finalizer --> Builder[M2Builder]
|
||||
Builder --> Subtree[M2MeshResourceExtractor]
|
||||
Finalizer -->|prepared Mesh| Cache[M2MeshResourceCacheState]
|
||||
```
|
||||
|
||||
The finalizer may use Mesh metadata, raw value Dictionaries, M2Builder and the
|
||||
two existing scene-free M2 services. Raw-file I/O, cache state, render permits,
|
||||
Nodes outside temporary rebuild roots and other application layers are forbidden.
|
||||
|
||||
## Public API
|
||||
|
||||
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|
||||
|---|---|---|---|---|
|
||||
| `requires_raw_data_for_refresh(mesh)` | Query | Tell loader whether stale Mesh needs raw-file lookup | Renderer main thread | Null/current false |
|
||||
| `finalize_mesh(path, mesh, raw_data, extracted_directory)` | Command/query | Return current, marked, rebuilt or fallback Mesh | Renderer main thread; one finalize operation | Null Mesh returns null; invalid rebuild falls back |
|
||||
| `clear()` | Command | Clear memoized classifier decisions | Map reset/final shutdown | Idempotent |
|
||||
| `cached_rebuild_decision_count()` | Diagnostic query | Expose memoized path-decision count | Tests/diagnostics | None |
|
||||
|
||||
## Inputs and outputs
|
||||
|
||||
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|
||||
|---|---|---|---|---|---|
|
||||
| Input | Extracted Mesh and normalized M2 path | Mesh resource finalizer | Finalizer | Borrowed Mesh reference/String copy | One call |
|
||||
| Input | Already-loaded raw M2 Dictionary | `M2RawModelRepository` through resource finalizer | Classifier/M2Builder | Caller-owned value container | One call |
|
||||
| Input | Extracted directory path | Loader configuration | M2Builder texture resolution | Copied String | One rebuild |
|
||||
| Output | Original or rebuilt current Mesh | Finalizer | Cache/prototype adapter | Borrowed/new Resource reference | Cache may retain |
|
||||
|
||||
Side effects are Mesh refresh metadata, classifier memoization and temporary
|
||||
M2Builder prototype creation/destruction during required rebuilds.
|
||||
|
||||
## Data flow
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Mesh[Extracted Mesh] --> Current{Refresh version >= 2?}
|
||||
Current -->|yes| ReturnOriginal[Return original unchanged]
|
||||
Current -->|no| NeedRaw[Repository supplies raw M2 data through loader]
|
||||
NeedRaw --> HasData{Raw data available?}
|
||||
HasData -->|no| Mark[Mark original version 2]
|
||||
HasData -->|yes| Classify{Billboard or UV rotation?}
|
||||
Classify -->|no| Mark
|
||||
Classify -->|yes| Build[M2Builder temporary prototype]
|
||||
Build --> Extract[M2MeshResourceExtractor first Mesh]
|
||||
Extract --> Rebuilt{Rebuilt Mesh exists?}
|
||||
Rebuilt -->|yes| ReturnRebuilt[Return rebuilt Mesh]
|
||||
Rebuilt -->|no| Mark
|
||||
Mark --> ReturnOriginal
|
||||
```
|
||||
|
||||
## Lifecycle/state
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Current: Mesh metadata >= 2
|
||||
[*] --> Stale: Mesh metadata < 2
|
||||
Stale --> Current: empty/ordinary data marks original
|
||||
Stale --> Rebuilding: classifier requires rebuild
|
||||
Rebuilding --> Current: rebuilt Mesh returned
|
||||
Rebuilding --> Current: failure marks original fallback
|
||||
Current --> Current: later preparation is no-op
|
||||
```
|
||||
|
||||
Classifier decisions are memoized per normalized path until either existing
|
||||
map/reset clear site calls `clear()`.
|
||||
|
||||
## Main sequence
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant ResourceFinalizer as M2MeshResourceFinalizer
|
||||
participant Finalizer as M2RuntimeMeshFinalizer
|
||||
participant Raw as M2Loader boundary
|
||||
participant Classifier as RebuildClassifier
|
||||
participant Builder as M2Builder
|
||||
ResourceFinalizer->>Finalizer: requires_raw_data_for_refresh(mesh)
|
||||
alt current Mesh
|
||||
Finalizer-->>ResourceFinalizer: false; reuse Mesh
|
||||
else stale Mesh
|
||||
Finalizer-->>ResourceFinalizer: true
|
||||
ResourceFinalizer->>Raw: load static raw Dictionary
|
||||
ResourceFinalizer->>Finalizer: finalize_mesh(path, mesh, raw, extracted dir)
|
||||
Finalizer->>Classifier: needs_runtime_mesh_rebuild(path, raw)
|
||||
opt rebuild required
|
||||
Finalizer->>Builder: build(raw, extracted dir)
|
||||
Finalizer->>Finalizer: extract first Mesh; free prototype
|
||||
end
|
||||
Finalizer-->>ResourceFinalizer: rebuilt or marked fallback Mesh
|
||||
end
|
||||
```
|
||||
|
||||
## Dependency diagram
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
ResourceFinalizer[M2MeshResourceFinalizer] --> Finalizer[M2RuntimeMeshFinalizer]
|
||||
Finalizer --> Classifier[M2RuntimeMeshRebuildClassifier]
|
||||
Finalizer --> Extractor[M2MeshResourceExtractor]
|
||||
Finalizer --> Builder[M2Builder]
|
||||
ResourceFinalizer --> Raw[M2RawModelRepository]
|
||||
ResourceFinalizer --> Cache[M2MeshResourceCacheState]
|
||||
Finalizer -. no dependency .-> Raw
|
||||
Finalizer -. no dependency .-> Cache
|
||||
```
|
||||
|
||||
## Ownership, threading and resources
|
||||
|
||||
- Renderer main thread executes metadata changes and M2Builder work.
|
||||
- `M2RawModelRepository` owns raw M2 file/native calls; the Mesh resource
|
||||
finalizer supplies its value result.
|
||||
- Finalizer owns classifier memoization and temporary rebuild prototype lifetime.
|
||||
- M2Builder owns construction rules; extractor selects the first rebuilt Mesh.
|
||||
- Cache/prototype adapters decide where the returned Mesh reference is retained.
|
||||
|
||||
## Errors, cancellation and recovery
|
||||
|
||||
| Failure/state | Detection | Behavior | Diagnostic | Recovery |
|
||||
|---|---|---|---|---|
|
||||
| Null Mesh | Guard | Return null | Contract verifier | Caller marks missing |
|
||||
| Current Mesh | Metadata query | Return exact reference; no raw load | Contract verifier | None needed |
|
||||
| Raw file/parse unavailable | Empty Dictionary from loader | Mark/reuse original | Existing renderer behavior | Source/cache repair |
|
||||
| Ordinary stale Mesh | Classifier false | Mark/reuse original | Classifier verifier | None needed |
|
||||
| Builder produces no Mesh | Extractor null | Mark/reuse original | Finalizer fallback fixture | Source/builder repair |
|
||||
| Reset/shutdown | Loader lifecycle | Clear classifier decisions | Source/clear verifier | Recompute next session |
|
||||
|
||||
## Configuration and capabilities
|
||||
|
||||
`MATERIAL_REFRESH_VERSION` remains `2` and is now owned by this finalizer.
|
||||
Finalize permits, cache paths, extracted directory configuration and quality
|
||||
profiles remain unchanged in the loader.
|
||||
|
||||
## Persistence, cache and migration
|
||||
|
||||
No new format is serialized. Existing Mesh metadata key
|
||||
`wow_m2_material_refresh_version`, M2Builder `MATERIAL_FORMAT_VERSION == 2` and
|
||||
`.tscn/.glb` caches are unchanged; no migration or rebake is introduced.
|
||||
|
||||
## Diagnostics and observability
|
||||
|
||||
`cached_rebuild_decision_count` exposes only a scalar count. Existing renderer
|
||||
logs/queue metrics are unchanged. Normalized M2 path remains the correlation key.
|
||||
|
||||
## Verification
|
||||
|
||||
- `verify_m2_runtime_mesh_finalizer.gd`: null/current/raw requirement, empty and
|
||||
ordinary marking, synthetic triangle billboard rebuild, rebuild failure
|
||||
fallback, refresh metadata, classifier clear, source boundary and 100-by-256
|
||||
bounded finalization.
|
||||
- Classifier/extractor/cache/pipeline/material/shutdown/M2 build regressions cover
|
||||
adjacent behavior. No asset-backed or original-client visual claim is made.
|
||||
|
||||
## Extension points
|
||||
|
||||
- Asset-backed fixture coverage can be added without changing the finalizer's
|
||||
value-data contract.
|
||||
- Refresh versions above `2` require explicit cache/fidelity evidence and docs.
|
||||
|
||||
## Capability status
|
||||
|
||||
| Capability | Status | Evidence | Gap/next step |
|
||||
|---|---|---|---|
|
||||
| Runtime Mesh refresh/fallback | Implemented extraction | Synthetic transition/rebuild verifier | Asset-backed corrupt/stale cache pending |
|
||||
| Rebuild predicate | Implemented extraction dependency | Classifier fixtures | Original-client material comparison pending |
|
||||
| Raw M2 refresh I/O | Implemented repository dependency | Repository source/invalid verifier | Legal success fixture pending |
|
||||
|
||||
## Known gaps and risks
|
||||
|
||||
- Required rebuild remains synchronous main-thread work behind existing permits.
|
||||
- Synthetic geometry proves transition/build behavior, not full asset materials.
|
||||
- No proprietary M2 traversal, descriptor-pressure/leak, p95/p99 or paired
|
||||
original-client capture is included.
|
||||
|
||||
## Source map
|
||||
|
||||
| Path | Responsibility |
|
||||
|---|---|
|
||||
| `src/render/m2/m2_runtime_mesh_finalizer.gd` | Refresh version, classification, rebuild and fallback |
|
||||
| `src/render/m2/m2_raw_model_repository.gd` | Raw-file/native I/O and Dictionary result |
|
||||
| `src/render/m2/m2_mesh_resource_finalizer.gd` | Repository call and returned-Mesh adoption |
|
||||
| `src/scenes/streaming/streaming_world_loader.gd` | Service composition, permits and materialization |
|
||||
| `src/render/m2/m2_runtime_mesh_rebuild_classifier.gd` | Memoized billboard/UV-rotation predicate |
|
||||
| `src/render/m2/m2_mesh_resource_extractor.gd` | First rebuilt-Mesh selection |
|
||||
| `src/tools/verify_m2_runtime_mesh_finalizer.gd` | Transition/rebuild/boundary/timing regression |
|
||||
|
||||
## Related decisions and references
|
||||
|
||||
- [`m2-runtime-mesh-rebuild-classifier.md`](m2-runtime-mesh-rebuild-classifier.md)
|
||||
- [`m2-mesh-resource-extractor.md`](m2-mesh-resource-extractor.md)
|
||||
- [`m2-mesh-resource-cache-state.md`](m2-mesh-resource-cache-state.md)
|
||||
- [`m2-raw-model-repository.md`](m2-raw-model-repository.md)
|
||||
- [`world-renderer.md`](world-renderer.md)
|
||||
- [`../../RENDER.md`](../../RENDER.md)
|
||||
- [`../../targets/roadmap/02-rendering-and-graphics.md`](../../targets/roadmap/02-rendering-and-graphics.md)
|
||||
@@ -0,0 +1,224 @@
|
||||
# M2 Runtime Mesh Rebuild Classifier
|
||||
|
||||
## Metadata
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Status | Implemented extraction |
|
||||
| Target/work package | M03 / `M03-RND-M2-RUNTIME-REBUILD-CLASSIFIER-001` |
|
||||
| Owners | Billboard/UV-rotation rebuild decision and per-path memoization |
|
||||
| Last verified | Worktree `work/sindo-main-codex/m03-m2-runtime-rebuild-classifier`, 2026-07-17 |
|
||||
| Profiles/capabilities | Existing M2 material-refresh path; profile-independent |
|
||||
|
||||
## Purpose
|
||||
|
||||
Determine whether a stale cached M2 mesh must be rebuilt in memory from raw M2
|
||||
data to restore billboard custom attributes or UV-rotation material inputs. The
|
||||
first decision is memoized by normalized relative path for the current map/
|
||||
runtime-cache lifetime.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Read M2 files or invoke `M2Loader`/`M2Builder`.
|
||||
- Own cached Meshes, scenes, materials, Nodes or RIDs.
|
||||
- Select cache paths, perform ResourceLoader transitions or consume permits.
|
||||
- Change refresh/cache format versions or shader behavior.
|
||||
- Classify animation, particles, ribbons or general visual fidelity.
|
||||
|
||||
## Context and boundaries
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Cache[Stale cached M2 Mesh] --> Finalizer[M2RuntimeMeshFinalizer]
|
||||
Loader[StreamingWorldLoader] --> Raw[M2Loader raw Dictionary]
|
||||
Raw --> Classifier[M2RuntimeMeshRebuildClassifier]
|
||||
Classifier --> Decision[Memoized rebuild boolean]
|
||||
Decision --> Finalizer
|
||||
Finalizer -->|true| Builder[M2Builder rebuild]
|
||||
Finalizer -->|false| Refresh[Mark material refresh version]
|
||||
```
|
||||
|
||||
Allowed dependencies are Dictionary/Array, `PackedInt32Array`, `Vector4` and
|
||||
scalar math. Files, ResourceLoader, parser/builder classes, SceneTree,
|
||||
WorkerThreadPool, Mesh/Node/RID ownership and other layers are forbidden.
|
||||
|
||||
## Public API
|
||||
|
||||
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|
||||
|---|---|---|---|---|
|
||||
| `needs_runtime_mesh_rebuild(normalized_relative_path, raw_m2_data)` | Memoized query | Detect billboard or referenced UV rotation requirements | Renderer main thread; cached until clear | Invalid variants/indices are skipped; first path decision wins |
|
||||
| `clear()` | Command | Drop all memoized path decisions | Main thread; map reset/shutdown | Idempotent |
|
||||
| `cached_path_count()` | Query | Return memoized path count | Main thread | None |
|
||||
| `diagnostic_snapshot()` | Query | Return detached path/decision map | Main thread; caller-owned | None |
|
||||
|
||||
## Inputs and outputs
|
||||
|
||||
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|
||||
|---|---|---|---|---|---|
|
||||
| Input | Normalized M2 relative path | Loader cache lookup | Classifier cache key | String value copied | Until clear |
|
||||
| Input | Raw M2 batches/transforms/combos Dictionary | `M2RawModelRepository` through loader adapter | Classifier | Borrowed; not mutated | First classification call per path |
|
||||
| Output | Rebuild-required boolean | Classifier | Runtime Mesh finalizer | Scalar | Memoized until clear |
|
||||
| Output | Detached decision Dictionary | Classifier | Verifier/future diagnostics | Caller-owned copy | Snapshot lifetime |
|
||||
|
||||
Side effects are limited to inserting and clearing boolean memoization entries.
|
||||
|
||||
## Data flow
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Query[path + raw M2 data] --> Cached{Path cached?}
|
||||
Cached -->|yes| Return[Return first decision]
|
||||
Cached -->|no| Billboard{Any billboard flag or positive vertex count?}
|
||||
Billboard -->|yes| StoreTrue[Store true]
|
||||
Billboard -->|no| Inputs{Transforms and combos present?}
|
||||
Inputs -->|no| StoreFalse[Store false]
|
||||
Inputs -->|yes| Batch[For each Dictionary batch]
|
||||
Batch --> Stage[Inspect at most four referenced stages]
|
||||
Stage --> Rotation{Non-identity rotation or speed above epsilon?}
|
||||
Rotation -->|yes| StoreTrue
|
||||
Rotation -->|no| StoreFalse
|
||||
StoreTrue --> Return
|
||||
StoreFalse --> Return
|
||||
```
|
||||
|
||||
## Lifecycle/state
|
||||
|
||||
Each path moves from unknown to one immutable cached boolean. Clear returns all
|
||||
paths to unknown; no resource lifecycle participates.
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Unknown
|
||||
Unknown --> RebuildRequired: billboard or referenced UV rotation
|
||||
Unknown --> RebuildNotRequired: no relevant metadata
|
||||
RebuildRequired --> RebuildRequired: repeated query
|
||||
RebuildNotRequired --> RebuildNotRequired: repeated query
|
||||
RebuildRequired --> Unknown: clear
|
||||
RebuildNotRequired --> Unknown: clear
|
||||
```
|
||||
|
||||
## Main sequence
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Loader as StreamingWorldLoader
|
||||
participant Native as M2Loader
|
||||
participant Finalizer as M2RuntimeMeshFinalizer
|
||||
participant Classifier as M2RuntimeMeshRebuildClassifier
|
||||
participant Builder as M2Builder
|
||||
Loader->>Native: load_m2(normalized path)
|
||||
Native-->>Loader: raw Dictionary
|
||||
Loader->>Finalizer: finalize_mesh(path, mesh, data, dir)
|
||||
Finalizer->>Classifier: needs_runtime_mesh_rebuild(path, data)
|
||||
Classifier-->>Finalizer: memoized boolean
|
||||
alt rebuild required
|
||||
Finalizer->>Builder: rebuild mesh from raw data
|
||||
else no rebuild required
|
||||
Finalizer->>Finalizer: stamp material refresh version
|
||||
end
|
||||
```
|
||||
|
||||
## Dependency diagram
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
Loader[StreamingWorldLoader] --> Finalizer[M2RuntimeMeshFinalizer]
|
||||
Finalizer --> Classifier[M2RuntimeMeshRebuildClassifier]
|
||||
Loader --> Native[M2Loader]
|
||||
Loader --> Builder[M2Builder]
|
||||
Loader --> Cache[M2 Mesh/Scene caches]
|
||||
Classifier -. no dependency .-> Native
|
||||
Classifier -. no dependency .-> Builder
|
||||
Classifier -. no dependency .-> Cache
|
||||
```
|
||||
|
||||
## Ownership, threading and resources
|
||||
|
||||
- The classifier owns only normalized-path boolean entries.
|
||||
- The finalizer owns classifier lifetime, rebuild decisions and refresh metadata.
|
||||
- The loader owns raw loading, cache/resource selection and Mesh adoption.
|
||||
- Raw Dictionaries are borrowed and never retained; only the computed boolean is cached.
|
||||
- Current calls and clears are serialized on the renderer main thread.
|
||||
- No Node, Resource, Mesh, material or RID reference crosses into the classifier.
|
||||
|
||||
## Errors, cancellation and recovery
|
||||
|
||||
| Failure | Detection | Behavior | Diagnostic | Recovery |
|
||||
|---|---|---|---|---|
|
||||
| Non-Dictionary batch/transform | Type guard | Skip entry or use identity defaults | Synthetic verifier | Correct parser data on next cleared lifetime |
|
||||
| Missing transforms/combos | Empty guard | No UV-driven rebuild | Synthetic verifier | Billboard classification still applies |
|
||||
| Invalid combo/transform index | Bounds guard | Skip stage | Synthetic verifier | Correct raw data and clear |
|
||||
| Repeated path with changed data | Cache hit | Preserve first decision | Memoization fixture | Map/reset clear recomputes |
|
||||
| Load/build failure | Outside classifier | Existing mesh fallback remains | Loader diagnostics | Existing reload/fallback path |
|
||||
|
||||
## Configuration and capabilities
|
||||
|
||||
| Setting/capability | Default | Profile | Runtime mutable | Effect |
|
||||
|---|---|---|---|---|
|
||||
| Maximum texture stages inspected | `4` | Existing M2 material path | No | Preserves historical four-stage shader boundary |
|
||||
| UV rotation-speed epsilon | `0.000001` exclusive | Existing M2 material path | No | Larger absolute speed requires rebuild |
|
||||
| `MATERIAL_REFRESH_VERSION` | Finalizer constant `2` | All | Code version | Gates whether classifier is called |
|
||||
|
||||
## Persistence, cache and migration
|
||||
|
||||
Memoized booleans are runtime-only and are cleared at the two historical loader
|
||||
reset/shutdown sites. No persisted cache version or rebuild instruction changes.
|
||||
|
||||
## Diagnostics and observability
|
||||
|
||||
- Logs: none.
|
||||
- Metrics: `cached_path_count` is contract-level only and not added to runtime logs.
|
||||
- Debug views: detached path-to-boolean snapshot.
|
||||
- Correlation IDs: normalized M2 relative path.
|
||||
|
||||
## Verification
|
||||
|
||||
- `verify_m2_runtime_mesh_rebuild_classifier.gd`: billboard flag/count, invalid
|
||||
variants, identity/non-identity rotation, positive/negative speed, exact
|
||||
epsilon, four-stage cap, invalid indices, memoization, clear, source boundary
|
||||
and 100-by-256 timing.
|
||||
- Existing M2 builder/material verifiers cover downstream shader/cache behavior.
|
||||
- Fidelity evidence is exact predicate/cache extraction. No new asset-backed or
|
||||
original-client parity claim is made.
|
||||
- Performance budget: 25,600 classifications and clears under one second.
|
||||
|
||||
## Extension points
|
||||
|
||||
- A typed raw M2 material descriptor may replace the Dictionary only with parser
|
||||
fixtures and a compatibility adapter.
|
||||
- Mesh request/cache/finalization state can be extracted separately without
|
||||
changing this rebuild predicate.
|
||||
|
||||
## Capability status
|
||||
|
||||
| Capability | Status | Evidence | Gap/next step |
|
||||
|---|---|---|---|
|
||||
| Billboard rebuild classification | Implemented extraction | Flag/count fixtures | Asset-backed cache refresh timing pending |
|
||||
| UV-rotation rebuild classification | Implemented extraction | Combo/stage/threshold fixtures | More build-12340 material fixtures pending |
|
||||
| Runtime memoization lifetime | Implemented extraction | First-decision/clear/source fixtures | Byte/count runtime metric not exposed |
|
||||
| Runtime Mesh finalization | Implemented extraction | Finalizer transition/rebuild fixtures | Asset-backed material comparison pending |
|
||||
|
||||
## Known gaps and risks
|
||||
|
||||
- Raw parser data remains Dictionary-based.
|
||||
- The first decision wins even if different raw data is passed for the same path;
|
||||
this intentionally preserves prior cache behavior.
|
||||
- Only four texture stages are inspected, matching current material support.
|
||||
- No private asset traversal, descriptor-pressure, p95/p99 or paired visual run is included.
|
||||
|
||||
## Source map
|
||||
|
||||
| Path | Responsibility |
|
||||
|---|---|
|
||||
| `src/render/m2/m2_runtime_mesh_rebuild_classifier.gd` | Predicate, thresholds and boolean memoization |
|
||||
| `src/scenes/streaming/streaming_world_loader.gd` | Raw loading, Mesh rebuild/adoption, refresh version and clear calls |
|
||||
| `addons/mpq_extractor/loaders/m2_builder.gd` | Mesh/material rebuild implementation |
|
||||
| `src/tools/verify_m2_runtime_mesh_rebuild_classifier.gd` | Synthetic predicate/cache/boundary/timing regression |
|
||||
|
||||
## Related decisions and references
|
||||
|
||||
- [`m2-build-batch-planner.md`](m2-build-batch-planner.md)
|
||||
- [`m2-placement-grouper.md`](m2-placement-grouper.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,224 @@
|
||||
# M2 Static Batch Materializer
|
||||
|
||||
## Metadata
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Status | Implemented |
|
||||
| Target/work package | M03 / `M03-RND-M2-STATIC-BATCH-MATERIALIZER-001` |
|
||||
| Owners | Static M2 MultiMesh construction, render settings and attachment |
|
||||
| Last verified | Worktree `work/sindo-main-codex/m03-m2-static-batch-materializer`, 2026-07-18 |
|
||||
| Profiles/capabilities | Existing static M2 placement path |
|
||||
|
||||
## Purpose
|
||||
|
||||
Build and attach one static M2 `MultiMeshInstance3D` from an already prepared
|
||||
Mesh and an ordered transform slice. The service preserves the existing
|
||||
transform format, Mesh identity, batch name, visibility and shadow behavior.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Group placements, size batches or advance build-job cursors.
|
||||
- Load, refresh, build or cache Mesh resources.
|
||||
- Consume render permits, rotate queues or decide retries/cancellation.
|
||||
- Assign Editor owners or release tile roots.
|
||||
- Materialize animated instances or introduce spatial-cell batching.
|
||||
|
||||
## Context and boundaries
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Groups[Ordered transform group] --> Loader[StreamingWorldLoader build adapter]
|
||||
MeshCache[Prepared Mesh cache] --> Loader
|
||||
Planner[M2BuildBatchPlanner] --> Slice[Start and count]
|
||||
Slice --> Loader
|
||||
Loader --> Materializer[M2StaticBatchMaterializer]
|
||||
Materializer --> Batch[Attached MultiMeshInstance3D]
|
||||
Loader --> EditorOwner[Editor owner adapter]
|
||||
```
|
||||
|
||||
Allowed dependencies are Godot `Mesh`, `MultiMesh`, `MultiMeshInstance3D`,
|
||||
`GeometryInstance3D` and main-thread SceneTree APIs. Files, ResourceLoader,
|
||||
workers, RenderingServer calls, caches, build jobs, scheduler policy, gameplay,
|
||||
network and Editor ownership are forbidden.
|
||||
|
||||
## Public API
|
||||
|
||||
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|
||||
|---|---|---|---|---|
|
||||
| `materialize_batch(parent, path, mesh, transforms, start, count, serial, visibility_end, visibility_margin, cast_shadows)` | Command/query | Create, configure and attach one static MultiMesh batch | Main thread; borrowed inputs, parent owns result | Invalid/empty input returns null; transform bounds are a caller precondition |
|
||||
|
||||
## Inputs and outputs
|
||||
|
||||
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|
||||
|---|---|---|---|---|---|
|
||||
| Input | M2 parent root | Loader build job | Batch attachment | Borrowed Node3D | Tile/job lifetime |
|
||||
| Input | Relative model path and serial | Loader build adapter | Batch node name | Copied values | One call |
|
||||
| Input | Prepared static Mesh | M2 Mesh resource cache/finalizer | MultiMesh | Borrowed Resource; exact reference retained by MultiMesh | Cache and batch lifetimes |
|
||||
| Input | Ordered transforms, start and count | Placement group and batch plan | MultiMesh transform buffer | Borrowed Array/scalars | One call |
|
||||
| Input | Visibility end/margin and shadow flag | Renderer configuration | MultiMeshInstance3D properties | Copied scalars | One call |
|
||||
| Output | Attached `MultiMeshInstance3D` | Materializer | Parent SceneTree and loader Editor-owner adapter | Parent-owned node/resource | Tile lifetime |
|
||||
|
||||
Side effects are MultiMesh allocation, transform-buffer mutation,
|
||||
MultiMeshInstance3D allocation/configuration and SceneTree attachment. The
|
||||
service retains no direct reference after return.
|
||||
|
||||
## Data flow
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Validate[Validate parent, Mesh, transforms and count] --> MultiMesh[Allocate TRANSFORM_3D MultiMesh]
|
||||
MultiMesh --> MeshIdentity[Assign exact prepared Mesh]
|
||||
MeshIdentity --> Count[Set instance count]
|
||||
Count --> Upload[Upload transforms start plus ordered batch offset]
|
||||
Upload --> Node[Create named MultiMeshInstance3D]
|
||||
Node --> Render[Apply shadow and optional visibility end/margin]
|
||||
Render --> Attach[Attach once to supplied parent]
|
||||
Attach --> Return[Return attached node]
|
||||
Validate -->|invalid| Null[Return null without attachment]
|
||||
```
|
||||
|
||||
## Lifecycle/state
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Validating
|
||||
Validating --> Rejected: invalid or empty input
|
||||
Validating --> Building: valid slice
|
||||
Building --> Configured: transforms and render properties set
|
||||
Configured --> Attached: parent add_child
|
||||
Rejected --> [*]
|
||||
Attached --> [*]: parent/tile owner releases subtree
|
||||
```
|
||||
|
||||
The `RefCounted` service is stateless; states describe a single call.
|
||||
|
||||
## Main sequence
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant L as StreamingWorldLoader
|
||||
participant P as M2BuildBatchPlanner
|
||||
participant M as M2StaticBatchMaterializer
|
||||
participant R as M2 parent root
|
||||
L->>P: plan_batch(transform count, offset, static limits)
|
||||
P-->>L: start/count and cursor transition
|
||||
L->>L: resolve prepared Mesh or rotate pending job
|
||||
L->>M: materialize_batch(parent, Mesh, transform slice, render settings)
|
||||
M->>M: allocate MultiMesh and upload ordered transforms
|
||||
M->>R: add_child(MultiMeshInstance3D)
|
||||
M-->>L: attached node
|
||||
L->>L: assign optional Editor owner
|
||||
```
|
||||
|
||||
## Dependency diagram
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
Loader[StreamingWorldLoader] --> Materializer[M2StaticBatchMaterializer]
|
||||
Planner[M2BuildBatchPlanner] --> Loader
|
||||
MeshState[M2MeshResourceCacheState] --> Loader
|
||||
Materializer --> Engine[Mesh / MultiMesh / MultiMeshInstance3D]
|
||||
Loader --> EditorOwner[Editor ownership]
|
||||
Loader --> Scheduler[RenderBudgetScheduler]
|
||||
Materializer -. no dependency .-> ResourceIO[ResourceLoader / files]
|
||||
Materializer -. no dependency .-> BuildState[Build jobs / queues / caches]
|
||||
```
|
||||
|
||||
## Ownership, threading and resources
|
||||
|
||||
- The caller borrows a prepared Mesh and transform Array for the synchronous call.
|
||||
- The new MultiMesh retains the exact Mesh Resource reference.
|
||||
- The new MultiMeshInstance3D retains the MultiMesh and transfers to the parent.
|
||||
- Parent/tile teardown owns node and Resource release through the SceneTree.
|
||||
- Loader assigns an Editor owner after successful attachment when configured.
|
||||
- All Resource and SceneTree mutation is main-thread-only.
|
||||
- No RID is acquired or released directly by this service.
|
||||
|
||||
## Errors, cancellation and recovery
|
||||
|
||||
| Failure | Detection | Behavior | Diagnostic | Recovery |
|
||||
|---|---|---|---|---|
|
||||
| Null parent or Mesh | Entry guard | Return null without allocation/attachment | Synthetic contract fixture | Correct build composition/cache state |
|
||||
| Empty transforms or non-positive count | Entry guard | Return null | Synthetic contract fixture | Planner supplies non-empty slice |
|
||||
| Invalid transform bounds | Caller precondition | Historical indexed access behavior remains | Build planner/source regression | Repair job cursor/group state |
|
||||
| Mesh pending | Outside service | Loader rotates job without calling service | Existing build/cache diagnostics | Async completion retries job |
|
||||
| Tile cancelled | Outside service | Loader cancels before materialization or releases parent later | Shutdown regression | Eligible tile may requeue |
|
||||
|
||||
The service does not swallow invalid bounds or silently clamp/reorder transforms,
|
||||
because doing so would change the accepted build-planner contract.
|
||||
|
||||
## Configuration and capabilities
|
||||
|
||||
| Setting/capability | Default | Profile | Runtime mutable | Effect |
|
||||
|---|---|---|---|---|
|
||||
| Transform format | `MultiMesh.TRANSFORM_3D` | All | No | Preserves full 3D M2 placements |
|
||||
| Static batch count | Loader `m2_multimesh_batch_size` through planner | Quality preset | Yes, between plans | Controls per-batch transform count |
|
||||
| Visibility end/margin | Loader M2 range / chunk size | Quality preset | Yes, between calls | Applied only when end is positive |
|
||||
| Shadow mode | Loader `m2_cast_shadows` | Quality preset | Yes, between calls | Selects ON/OFF casting setting |
|
||||
|
||||
## Persistence, cache and migration
|
||||
|
||||
No state is serialized and no cache/schema version changes. The service consumes
|
||||
the accepted prepared Mesh cache contract; no rebake or migration is required.
|
||||
|
||||
## Diagnostics and observability
|
||||
|
||||
- The service emits no logs, metrics or debug views.
|
||||
- Existing loader queue/hitch metrics continue to account for `m2build` work.
|
||||
- Path/serial remain visible through the unchanged batch node name.
|
||||
- Editor ownership remains observable through the existing loader adapter.
|
||||
|
||||
## Verification
|
||||
|
||||
- `verify_m2_static_batch_materializer.gd` covers invalid inputs, node naming,
|
||||
exact Mesh identity, transform format/count, ordered slice source contract,
|
||||
visibility, shadows, single attachment, ownership boundaries and timing.
|
||||
- Build planner, Mesh cache/extractor/finalizer, prototype, animated materializer,
|
||||
shutdown, materials, facade and internal-access regressions protect neighbors.
|
||||
- The headless dummy renderer does not round-trip per-instance transforms through
|
||||
`MultiMesh.get_instance_transform`; exact ordered upload is protected by the
|
||||
source contract and render checkpoints remain the observable regression gate.
|
||||
- Fidelity evidence is exact extraction only; no new build-12340 parity claim.
|
||||
- Performance budget: 10,000 transform API writes under one second in headless.
|
||||
|
||||
## Extension points
|
||||
|
||||
Measured spatial-cell batching may later provide smaller transform slices while
|
||||
reusing this materializer. A generic static/animated base class, async mutation
|
||||
and callback framework are intentionally excluded.
|
||||
|
||||
## Capability status
|
||||
|
||||
| Capability | Status | Evidence | Gap/next step |
|
||||
|---|---|---|---|
|
||||
| Static M2 MultiMesh materialization | Implemented extraction | Synthetic node/resource/render/source/timing verifier | Asset-backed traversal pending |
|
||||
| Transform ordering | Implemented extraction | Exact source contract and existing checkpoints | Headless per-instance getter unavailable |
|
||||
| Editor persistence | Existing loader-owned | Source boundary and editor helper | Editor integration fixture remains separate |
|
||||
| Spatial-cell batching | Planned | Renderer roadmap | Culling/performance design and evidence pending |
|
||||
|
||||
## Known gaps and risks
|
||||
|
||||
- Materialization remains synchronous main-thread work under the existing permit.
|
||||
- MultiMesh transform upload timing in a headless dummy renderer does not measure GPU cost.
|
||||
- No private asset traversal, visual comparison, descriptor-pressure, leak or p95/p99 run exists.
|
||||
- Current model-path batching is not culling-driven spatial-cell batching.
|
||||
|
||||
## Source map
|
||||
|
||||
| Path | Responsibility |
|
||||
|---|---|
|
||||
| `src/render/m2/m2_static_batch_materializer.gd` | Static MultiMesh allocation, transform upload, render setup and attachment |
|
||||
| `src/render/m2/m2_build_batch_planner.gd` | Static/animated batch count and cursor planning |
|
||||
| `src/render/m2/m2_mesh_resource_cache_state.gd` | Prepared static Mesh reference ownership |
|
||||
| `src/scenes/streaming/streaming_world_loader.gd` | Resource readiness, job/budget adapter and Editor ownership |
|
||||
| `src/tools/verify_m2_static_batch_materializer.gd` | Node/resource/render/source/timing regression |
|
||||
|
||||
## Related decisions and references
|
||||
|
||||
- [`m2-build-batch-planner.md`](m2-build-batch-planner.md)
|
||||
- [`m2-mesh-resource-cache-state.md`](m2-mesh-resource-cache-state.md)
|
||||
- [`m2-animated-instance-materializer.md`](m2-animated-instance-materializer.md)
|
||||
- [`world-renderer.md`](world-renderer.md)
|
||||
- [`../../RENDER.md`](../../RENDER.md)
|
||||
- [`../../targets/roadmap/02-rendering-and-graphics.md`](../../targets/roadmap/02-rendering-and-graphics.md)
|
||||
@@ -0,0 +1,211 @@
|
||||
# M2 Static Build Resource Observer
|
||||
|
||||
## Metadata
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Status | Implemented extraction |
|
||||
| Target/work package | M03 / `M03-RND-M2-STATIC-BUILD-RESOURCE-OBSERVER-001` |
|
||||
| Owners | Static build Mesh lookup, request selection and missing transition |
|
||||
| Last verified | Worktree `work/sindo-main-codex/m03-m2-static-build-resource-observer`, 2026-07-18 |
|
||||
| Profiles/capabilities | Existing static MultiMesh M2 build path |
|
||||
|
||||
## Purpose
|
||||
|
||||
Produce the static phase of `M2BuildResourceSnapshot`: reuse a prepared Mesh,
|
||||
wait for an existing request, start the first eligible cache request, or record
|
||||
a terminal missing model while preserving historical path and GLB rules.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Observe animated/native prototypes or finalize threaded loads.
|
||||
- Own/free Meshes, Nodes, cache entries or snapshot lifetime.
|
||||
- Materialize instances, plan batches, rotate queues or consume permits.
|
||||
- Change cache format, candidate order, profiles or visible output.
|
||||
|
||||
## Context and boundaries
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Loader[StreamingWorldLoader] --> Observer[M2StaticBuildResourceObserver]
|
||||
MeshCache[M2MeshResourceCacheState] --> Observer
|
||||
Missing[M2PrototypeCacheState] --> Observer
|
||||
Pipeline[M2MeshLoadPipelineState] --> Observer
|
||||
Observer --> Snapshot[M2BuildResourceSnapshot]
|
||||
Snapshot --> Dispatch[M2BuildDispatchPlanner]
|
||||
```
|
||||
|
||||
## Public API
|
||||
|
||||
| Symbol | Kind | Purpose | Errors |
|
||||
|---|---|---|---|
|
||||
| `observe(snapshot, path, cache_dir, mesh_cache, prototype_cache, pipeline)` | Command/query | Adopt cached/pending/requested/missing static result | Invalid inputs return `rejected` |
|
||||
| `cache_resource_paths(cache_dir, path, extensions)` | Pure query | Return unique historical nested/lowercase/basename candidates | Empty values yield fewer/empty candidates |
|
||||
| `glb_animation_schema(path)` | Read query | Read OpenWC schema marker from GLB JSON | Invalid/missing GLB returns empty String |
|
||||
| `OUTCOME_*` | Constants | Stable diagnostic transition identifiers | None |
|
||||
|
||||
## Inputs and outputs
|
||||
|
||||
| Direction | Data | Producer | Consumer | Ownership/lifetime |
|
||||
|---|---|---|---|---|
|
||||
| Input | Normalized M2 path/cache directory | Loader | Observer | Copied Strings; one observation |
|
||||
| Input | Mesh/missing/request states | M2 state services | Observer | Borrowed services; map/session |
|
||||
| Input/output | Resource snapshot | Loader | Observer/dispatch/materializer | Borrowed; one build operation |
|
||||
| Output | Cached Mesh or missing/pending values | Observer | Snapshot | Exact borrowed Mesh/scalars |
|
||||
| Output | Threaded request record | Observer | Mesh pipeline | Pipeline-owned path record |
|
||||
| Output | Outcome StringName | Observer | Tests/future diagnostics | Immutable scalar |
|
||||
|
||||
## Data flow
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start[Observe static path] --> Cached{Mesh cached?}
|
||||
Cached -->|yes| AdoptMesh[Adopt exact Mesh; cached]
|
||||
Cached -->|no| Missing{Already missing?}
|
||||
Missing -->|yes| AdoptMissing[Adopt missing]
|
||||
Missing -->|no| Pending{Request pending?}
|
||||
Pending -->|yes| AdoptPending[Adopt unresolved; pending]
|
||||
Pending -->|no| Candidates[Generate tscn then glb candidates]
|
||||
Candidates --> Eligible{Exists and not pivot-prefix GLB?}
|
||||
Eligible -->|yes| Request[Threaded request]
|
||||
Request -->|OK or busy| Remember[Remember request; requested]
|
||||
Eligible -->|none/error| MarkMissing[Mark/adopt missing]
|
||||
```
|
||||
|
||||
## Lifecycle/state
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Observing
|
||||
Observing --> Cached
|
||||
Observing --> Pending
|
||||
Observing --> Requested
|
||||
Observing --> Missing
|
||||
Observing --> Rejected
|
||||
Cached --> [*]
|
||||
Pending --> [*]
|
||||
Requested --> [*]
|
||||
Missing --> [*]
|
||||
Rejected --> [*]
|
||||
```
|
||||
|
||||
## Main sequence
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant L as StreamingWorldLoader
|
||||
participant O as StaticBuildResourceObserver
|
||||
participant C as Mesh/prototype cache state
|
||||
participant P as MeshLoadPipelineState
|
||||
participant R as ResourceLoader
|
||||
participant S as M2BuildResourceSnapshot
|
||||
L->>O: observe(snapshot, path, cache_dir, states)
|
||||
O->>C: cached/missing lookup
|
||||
alt cache hit or missing
|
||||
O->>S: adopt static observation
|
||||
else pending
|
||||
O->>P: has_request
|
||||
O->>S: adopt unresolved observation
|
||||
else request candidate
|
||||
O->>R: exists + load_threaded_request
|
||||
O->>P: remember_request
|
||||
O->>S: adopt unresolved observation
|
||||
end
|
||||
```
|
||||
|
||||
## Dependency diagram
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
Observer[M2StaticBuildResourceObserver] --> ResourceLoader
|
||||
Observer --> FileAccess[GLB header/JSON read]
|
||||
Observer --> MeshCache[M2MeshResourceCacheState]
|
||||
Observer --> PrototypeCache[M2PrototypeCacheState]
|
||||
Observer --> Pipeline[M2MeshLoadPipelineState]
|
||||
Observer --> Snapshot[M2BuildResourceSnapshot]
|
||||
Observer -. no dependency .-> SceneTree
|
||||
Observer -. no dependency .-> Materializers
|
||||
Observer -. no dependency .-> Scheduler
|
||||
```
|
||||
|
||||
## Ownership, threading and resources
|
||||
|
||||
- Observer retains no request, cache, snapshot or engine reference.
|
||||
- Cache/pipeline services retain their existing state and resource ownership.
|
||||
- Snapshot borrows the exact cached Mesh; observer never frees it.
|
||||
- Resource existence/request and GLB inspection run on renderer main thread.
|
||||
- `M2MeshResourceFinalizer` performs terminal polling, Resource extraction,
|
||||
Mesh preparation and cache/missing adoption from the loader permit loop.
|
||||
|
||||
## Errors, cancellation and recovery
|
||||
|
||||
| State | Behavior | Recovery |
|
||||
|---|---|---|
|
||||
| Invalid dependency/path | Return `rejected`; no mutation | Correct composition |
|
||||
| Existing request | Return `pending`; no duplicate request | Retry when queue rotates |
|
||||
| Pivot-prefix GLB | Skip as static candidate | Continue next candidate |
|
||||
| Request error | Stop historical search and mark missing | Later reset/new session |
|
||||
| No candidate | Mark/adopt terminal missing | Cache content becomes available in new session |
|
||||
| Tile cancellation | Observer retains nothing | Loader/queue cancellation remains authoritative |
|
||||
|
||||
## Configuration and capabilities
|
||||
|
||||
The observer consumes existing `m2_cache_dir`. It adds no profile, cache version,
|
||||
batch, visibility, shadow or scheduler setting.
|
||||
|
||||
## Persistence, cache and migration
|
||||
|
||||
No new persistence is introduced. Existing `.tscn/.glb` layout, pipeline records
|
||||
and missing-cache lifetime are unchanged; no rebake is required.
|
||||
|
||||
## Diagnostics and observability
|
||||
|
||||
Returned outcomes distinguish rejected/cached/pending/requested/missing without
|
||||
logging. Existing queue and hitch metrics remain unchanged.
|
||||
|
||||
## Verification
|
||||
|
||||
- Dedicated verifier covers invalid, cached identity, missing, pending, path
|
||||
order/deduplication, absent GLB schema, source ownership and bounded timing.
|
||||
- Snapshot/dispatch/pipeline/cache/shutdown/facade/internal-access/checkpoint
|
||||
regressions protect adjacent behavior.
|
||||
- No original-client visual claim is made; this is bookkeeping/I/O extraction.
|
||||
|
||||
## Extension points
|
||||
|
||||
Animated/native observation may become a separate producer of the animation
|
||||
phase. Shared cache candidate/GLB inspection can later be reused without adding
|
||||
a generic resource framework.
|
||||
|
||||
## Capability status
|
||||
|
||||
| Capability | Status | Evidence | Gap |
|
||||
|---|---|---|---|
|
||||
| Static build lookup/request production | Implemented extraction | Lifecycle/path/source/timing verifier | Asset-backed traversal pending |
|
||||
| Static finalize/preparation | Implemented extraction | Mesh resource finalizer verifier | Asset-backed traversal pending |
|
||||
| Animated observation | Existing loader-owned | Animation regressions | Separate producer pending |
|
||||
|
||||
## Known gaps and risks
|
||||
|
||||
- Successful request behavior is protected by source plus existing pipeline
|
||||
tests; starting an undrained asynchronous request in the unit fixture would leak.
|
||||
- GLB schema parser reads synchronously, matching the previous loader behavior.
|
||||
- Private traversal, p95/p99, leak and visual evidence remain unavailable.
|
||||
|
||||
## Source map
|
||||
|
||||
| Path | Responsibility |
|
||||
|---|---|
|
||||
| `src/render/m2/m2_static_build_resource_observer.gd` | Static cache/request/missing observation |
|
||||
| `src/render/m2/m2_build_resource_snapshot.gd` | Typed observation result |
|
||||
| `src/render/m2/m2_mesh_resource_finalizer.gd` | Terminal polling, preparation and adoption |
|
||||
| `src/scenes/streaming/streaming_world_loader.gd` | Composition, permits and action execution |
|
||||
| `src/tools/verify_m2_static_build_resource_observer.gd` | Lifecycle/path/boundary/timing regression |
|
||||
|
||||
## Related decisions and references
|
||||
|
||||
- [`m2-build-resource-snapshot.md`](m2-build-resource-snapshot.md)
|
||||
- [`m2-mesh-load-pipeline-state.md`](m2-mesh-load-pipeline-state.md)
|
||||
- [`m2-mesh-resource-cache-state.md`](m2-mesh-resource-cache-state.md)
|
||||
- [`world-renderer.md`](world-renderer.md)
|
||||
- [`../../RENDER.md`](../../RENDER.md)
|
||||
@@ -0,0 +1,207 @@
|
||||
# WMO Placement Registry
|
||||
|
||||
## Metadata
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Status | Implemented extraction |
|
||||
| Target/work package | M03 / `M03-RND-WMO-PLACEMENT-REGISTRY-001` |
|
||||
| Owners | WMO placement-key to tile/global reference-set ownership |
|
||||
| Last verified | Worktree `work/sindo-main-codex/m03-wmo-placement-registry`, 2026-07-17 |
|
||||
| Profiles/capabilities | Existing ADT/WDT WMO placement paths; profile-independent |
|
||||
|
||||
## Purpose
|
||||
|
||||
Own the reference sets that keep one resolved WMO placement active across one
|
||||
or more ADT tiles, or across the global WDT placement lifetime. This separates
|
||||
value-only placement ownership from scene and render-resource ownership.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Resolve WMO paths, placement identity or world transforms.
|
||||
- Own Nodes, Resources, RIDs, roots, jobs, queues, tasks or caches.
|
||||
- Load, instantiate, build or free WMO content.
|
||||
- Change duplicate order, budgets, retries or global-WMO behavior.
|
||||
- Claim original-client WMO visual, portal, room or material parity.
|
||||
|
||||
## Context and boundaries
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Parsed[ADT/WDT placement] --> Resolver[WmoPlacementResolver]
|
||||
Resolver --> Key[Placement key]
|
||||
Tile[Tile/global reference key] --> Loader[StreamingWorldLoader]
|
||||
Key --> Loader
|
||||
Loader --> Registry[WmoPlacementRegistry]
|
||||
Registry --> Decision[Existing or final-release decision]
|
||||
Decision --> Loader
|
||||
Loader --> NodeMap[Loader key-to-Node map]
|
||||
Loader --> Jobs[Loader jobs/queues/resources]
|
||||
```
|
||||
|
||||
The registry depends only on `RefCounted`, `Dictionary`, `Array` and `String`.
|
||||
SceneTree, Node, Resource, RID, files, loaders, threads, locks, network,
|
||||
gameplay and editor UI are forbidden dependencies.
|
||||
|
||||
## Public API
|
||||
|
||||
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|
||||
|---|---|---|---|---|
|
||||
| `add_reference(placement_key, reference_key)` | Command/query | Idempotently add a reference; return true only when creating the placement key | Renderer main thread; map session | Empty key returns false and changes nothing |
|
||||
| `release_reference(placement_key, reference_key)` | Command/query | Remove an owned reference; return true only on final removal | Renderer main thread; map session | Empty, unknown or non-owner input returns false |
|
||||
| `contains(placement_key)` | Query | Test whether any reference keeps a placement active | Renderer main thread; map session | Empty key returns false |
|
||||
| `active_count()` | Query | Count active placement keys, not references | Renderer main thread; map session | None |
|
||||
| `diagnostic_snapshot()` | Query | Return detached, placement/reference-sorted records | Renderer main thread; caller-owned result | None |
|
||||
| `clear()` | Command | Remove all ownership records idempotently | Renderer main thread; reset/shutdown | None |
|
||||
|
||||
## Inputs and outputs
|
||||
|
||||
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|
||||
|---|---|---|---|---|---|
|
||||
| Input | Resolved `uid:*`, `tile:*:*` or `global` placement key | `WmoPlacementResolver` / loader global adapter | Registry | Copied String dictionary key | Until final release/clear |
|
||||
| Input | ADT tile key or `__global__` reference key | Loader | Registry | Copied String set key | Until release/clear |
|
||||
| Output | First-placement Boolean | Registry | Loader creation path | Value | One call |
|
||||
| Output | Final-release Boolean | Registry | Loader cancellation/free path | Value | One call |
|
||||
| Output | Active count and sorted diagnostic records | Registry | Renderer diagnostics/verifier | Detached values | Caller lifetime |
|
||||
|
||||
## Data flow
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Add[add placement + reference] --> Valid{Both non-empty?}
|
||||
Valid -->|no| Reject[false; unchanged]
|
||||
Valid -->|yes| Existing{Placement exists?}
|
||||
Existing -->|no| Create[Create reference set; true]
|
||||
Existing -->|yes| Retain[Idempotently add reference; false]
|
||||
Release[release placement + reference] --> Owner{Known owner reference?}
|
||||
Owner -->|no| Reject
|
||||
Owner -->|yes| Erase[Erase reference]
|
||||
Erase --> Empty{Set empty?}
|
||||
Empty -->|no| Retained[false; placement remains]
|
||||
Empty -->|yes| Final[Erase placement; true]
|
||||
```
|
||||
|
||||
## Lifecycle and state
|
||||
|
||||
The states for one placement are `Absent`, `Referenced` and back to `Absent`.
|
||||
The first valid add enters `Referenced`; further tile/global adds remain there.
|
||||
Only release of the final owned reference returns to `Absent`. Map reset and
|
||||
shutdown call `clear`, which is safe from either state.
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Absent
|
||||
Absent --> Referenced: first valid add / true
|
||||
Referenced --> Referenced: idempotent/shared add / false
|
||||
Referenced --> Referenced: non-owner/non-final release / false
|
||||
Referenced --> Absent: final owned release / true
|
||||
Referenced --> Absent: clear
|
||||
```
|
||||
|
||||
## Main sequence
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Loader as StreamingWorldLoader
|
||||
participant Registry as WmoPlacementRegistry
|
||||
participant Nodes as Loader Node map
|
||||
Loader->>Registry: contains(placement key)
|
||||
alt existing placement
|
||||
Loader->>Registry: add_reference(key, tile)
|
||||
else new placement built successfully
|
||||
Loader->>Nodes: store key-to-Node
|
||||
Loader->>Registry: add_reference(key, tile/global)
|
||||
end
|
||||
Loader->>Registry: release_reference(key, tile)
|
||||
alt final reference
|
||||
Registry-->>Loader: true
|
||||
Loader->>Loader: cancel render build
|
||||
Loader->>Nodes: queue-free and erase Node
|
||||
else shared/unknown reference
|
||||
Registry-->>Loader: false
|
||||
end
|
||||
```
|
||||
|
||||
## Ownership, threading and resources
|
||||
|
||||
- The registry owns only placement-key Dictionaries and reference-key sets.
|
||||
- The loader owns the parallel placement-key-to-Node map and every Node action.
|
||||
- The loader also owns WMO roots, tile state, caches, requests, jobs, queues,
|
||||
retries, cancellation and render-resource lifetime.
|
||||
- Current calls are serialized on the renderer main thread. The registry has no
|
||||
lock and must not be mutated concurrently.
|
||||
|
||||
## Errors, cancellation and recovery
|
||||
|
||||
| Failure | Detection | Behavior | Diagnostic | Recovery |
|
||||
|---|---|---|---|---|
|
||||
| Empty placement/reference key | API guard | Reject without mutation | Contract verifier | Repair caller input |
|
||||
| Duplicate add | Existing set member | Idempotent retain | Sorted snapshot | No recovery required |
|
||||
| Unknown/non-owner release | Missing placement/reference | Ignore without mutation | Contract verifier | Release from actual owner or reset |
|
||||
| Tile build cancellation | Loader-owned condition | Loader cancels job, then releases recorded refs | Existing queue metrics | Retry when tile becomes wanted |
|
||||
| Final release | Empty set after erase | Return true and erase registry record | Active count | Loader cancels/free Node exactly once |
|
||||
| Map reset/shutdown | Loader lifecycle | Loader frees Nodes/jobs and clears registry | Shutdown regression | Next map begins empty |
|
||||
|
||||
## Configuration and capabilities
|
||||
|
||||
There is no setting, profile branch or capacity knob. WMO tile radius, cache
|
||||
limits, render budgets and visibility settings remain loader-owned.
|
||||
|
||||
## Persistence, cache and migration
|
||||
|
||||
No data is serialized and no cache/schema/version changes. Placement and
|
||||
reference keys exist only for the current renderer map session.
|
||||
|
||||
## Diagnostics and observability
|
||||
|
||||
`active_count` backs existing WMO instance metrics. `diagnostic_snapshot`
|
||||
returns deterministic detached records for tests and future read-only
|
||||
diagnostics; mutating the returned arrays cannot alter registry state.
|
||||
|
||||
## Verification
|
||||
|
||||
- `verify_wmo_placement_registry.gd`: invalid/first/idempotent/shared/non-owner/
|
||||
final/global/clear behavior, deterministic detached diagnostics, source
|
||||
ownership boundaries and bounded 100-by-256 shared-reference cycles.
|
||||
- WMO resolver, material, runtime-cache shutdown and the renderer regression
|
||||
suite verify the unchanged loader adapters and surrounding lifecycle.
|
||||
- Fidelity evidence is exact preservation of the repository's prior shared-ref
|
||||
and final-release semantics. Asset-backed 3.3.5a comparison is still pending.
|
||||
|
||||
## Extension points
|
||||
|
||||
- WMO build/resource state can be extracted separately without moving Nodes
|
||||
into this registry.
|
||||
- A future diagnostic adapter may expose the detached snapshot through the
|
||||
facade without exposing mutable loader internals.
|
||||
|
||||
## Capability status
|
||||
|
||||
| Capability | Status | Evidence | Gap/next step |
|
||||
|---|---|---|---|
|
||||
| WMO placement reference ownership | Implemented extraction | Contract/source/timing verifier | Asset-backed cross-tile corpus pending |
|
||||
| WMO build/resource scheduling | Remains in loader | Existing runtime regressions | Separate state/cancellation package |
|
||||
| WMO visual/portal/material fidelity | Partial | Renderer baseline/material checks | Original-client paired evidence pending |
|
||||
|
||||
## Known gaps and risks
|
||||
|
||||
- Correctness assumes the loader records each successful placement reference in
|
||||
the tile `wmo_refs` array before unregister.
|
||||
- Old caches without positive UID retain resolver-defined per-tile identity.
|
||||
- No proprietary WMO corpus or original-client paired capture is in this package.
|
||||
|
||||
## Source map
|
||||
|
||||
| Path | Responsibility |
|
||||
|---|---|
|
||||
| `src/render/wmo/wmo_placement_registry.gd` | Placement-key/reference-set ownership |
|
||||
| `src/render/wmo/wmo_placement_resolver.gd` | Stable path, identity and transform rules |
|
||||
| `src/scenes/streaming/streaming_world_loader.gd` | Node map, instances, resources, jobs, queues and lifecycle reactions |
|
||||
| `src/tools/verify_wmo_placement_registry.gd` | Contract, boundary and timing regression |
|
||||
|
||||
## Related decisions and references
|
||||
|
||||
- [`wmo-placement-resolver.md`](wmo-placement-resolver.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,7 +19,8 @@ live-prototype instance paths.
|
||||
## Non-goals
|
||||
|
||||
- Parse ADT/WDT or convert canonical WoW coordinates.
|
||||
- Own WMO registry references, tile state, jobs, queues or retries.
|
||||
- Own WMO registry references, tile state, jobs, queues or retries; reference
|
||||
ownership belongs to `WmoPlacementRegistry`.
|
||||
- Load/validate cached scenes or lightweight render resources.
|
||||
- Create Nodes, Mesh/MultiMesh, materials or RIDs.
|
||||
- Implement portals, rooms, visibility, materials, doodads or WMO parity.
|
||||
@@ -34,7 +35,7 @@ flowchart LR
|
||||
Resolver --> Identity[Registry unique key]
|
||||
Resolver --> Transform[World Transform3D]
|
||||
CacheKey --> Cache[Loader WMO caches/requests]
|
||||
Identity --> Registry[Loader WMO registry]
|
||||
Identity --> Registry[WmoPlacementRegistry]
|
||||
Transform --> RenderRoot[Lightweight render root]
|
||||
Transform --> Scene[Cached scene instance]
|
||||
Transform --> Live[Live prototype instance]
|
||||
@@ -60,7 +61,7 @@ WorkerThreadPool, mutexes, files, gameplay, network and editor UI are forbidden.
|
||||
| Input | Raw placement Dictionary | WDT global or ADT MODF parser result | Identity/transform queries | Read-only shallow value | One call |
|
||||
| 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 | Loader WMO registry/ref arrays | New String value | Until unregister/reset |
|
||||
| Output | `uid:*` or `tile:*:*` key | Resolver | `WmoPlacementRegistry` and loader ref arrays | New String value | Until unregister/reset |
|
||||
| Output | World `Transform3D` | Resolver | Three WMO instance adapters | Value copy | Instance lifetime after assignment |
|
||||
|
||||
The resolver retains no source Dictionary, output or engine resource.
|
||||
@@ -91,7 +92,7 @@ and shutdown require no resolver operation.
|
||||
sequenceDiagram
|
||||
participant Loader as StreamingWorldLoader
|
||||
participant Resolver as WmoPlacementResolver
|
||||
participant Registry as Loader WMO registry/cache
|
||||
participant Registry as WmoPlacementRegistry
|
||||
participant Instance as Render/cached/live instance
|
||||
Loader->>Resolver: normalize_relative_path(path)
|
||||
Resolver-->>Loader: cache key
|
||||
@@ -105,8 +106,9 @@ sequenceDiagram
|
||||
## Ownership, threading and resources
|
||||
|
||||
- The resolver owns only call-local scalar/value calculations.
|
||||
- The loader owns WMO registry entries/ref sets, cache/load-request state, build
|
||||
jobs/queues, resource fallback and cancellation.
|
||||
- `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.
|
||||
- Pure calls are thread-safe; current consumers execute on the main thread.
|
||||
|
||||
@@ -152,8 +154,8 @@ in the facade/loader diagnostic snapshot.
|
||||
|
||||
## Extension points
|
||||
|
||||
- A later package may extract WMO registry ownership/ref release using these
|
||||
stable keys.
|
||||
- `WmoPlacementRegistry` consumes these stable keys; later packages may extract
|
||||
WMO build/resource state without changing the resolver.
|
||||
- Render-group build cursor and resource-selection state require separate
|
||||
lifecycle/cancellation contracts.
|
||||
|
||||
@@ -162,7 +164,8 @@ in the facade/loader diagnostic snapshot.
|
||||
| Capability | Status | Evidence | Gap/next step |
|
||||
|---|---|---|---|
|
||||
| Consistent WMO placement identity/transform | Implemented extraction | Contract/source/timing verifier | Asset-backed placement comparison pending |
|
||||
| WMO registry/build services | Remains in loader | Existing runtime regressions | Stateful extraction pending |
|
||||
| WMO placement registry | Implemented extraction | Ownership/source/timing verifier | Asset-backed cross-tile corpus pending |
|
||||
| WMO build services | Remains in loader | Existing runtime regressions | Stateful extraction pending |
|
||||
| Portals/rooms/material fidelity | Partial | M00 checkpoint/material evidence | Broader WMO implementation pending |
|
||||
|
||||
## Known gaps and risks
|
||||
@@ -177,11 +180,13 @@ in the facade/loader diagnostic snapshot.
|
||||
| Path | Responsibility |
|
||||
|---|---|
|
||||
| `src/render/wmo/wmo_placement_resolver.gd` | Path, identity and transform rules |
|
||||
| `src/scenes/streaming/streaming_world_loader.gd` | Registry/cache/build/resource/Node ownership |
|
||||
| `src/render/wmo/wmo_placement_registry.gd` | Placement reference-set ownership |
|
||||
| `src/scenes/streaming/streaming_world_loader.gd` | Node map/cache/build/resource ownership |
|
||||
| `src/tools/verify_wmo_placement_resolver.gd` | Contract, source and timing regression |
|
||||
|
||||
## Related decisions and references
|
||||
|
||||
- [`world-renderer.md`](world-renderer.md)
|
||||
- [`wmo-placement-registry.md`](wmo-placement-registry.md)
|
||||
- [`../../RENDER.md`](../../RENDER.md)
|
||||
- [`../../targets/roadmap/02-rendering-and-graphics.md`](../../targets/roadmap/02-rendering-and-graphics.md)
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
# WMO Render Build Queue
|
||||
|
||||
## Metadata
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Status | Implemented |
|
||||
| Target/work package | M03 / `M03-RND-WMO-RENDER-BUILD-QUEUE-001` |
|
||||
| Owners | Typed lightweight-WMO build jobs and FIFO placement-key queue |
|
||||
| Last verified | Worktree `work/sindo-main-codex/m03-wmo-render-build-queue`, 2026-07-17 |
|
||||
| Profiles/capabilities | Lightweight WMO render-cache path; profile-independent |
|
||||
|
||||
## Purpose
|
||||
|
||||
Own pending lightweight-WMO render build records and their FIFO placement-key
|
||||
order outside the monolithic loader. Each typed job retains its placement key,
|
||||
scene root, render resource and mesh/MultiMesh cursors while the loader performs
|
||||
all validation, planning, materialization, permit consumption and destruction.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Free or queue-free Nodes, Resources or RIDs.
|
||||
- Create MeshInstance3D/MultiMeshInstance3D or refresh materials.
|
||||
- Own frame permits, placement references, render cache state or load I/O.
|
||||
- Change duplicate enqueue, stale-key cleanup, cancellation or ordering.
|
||||
- Change cache versions, WMO transforms, shadows, visibility or fidelity rules.
|
||||
|
||||
## Context and boundaries
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Placement[Successful WMO placement] --> Loader[StreamingWorldLoader]
|
||||
Root[Node3D root] --> Loader
|
||||
Cache[WMOStreamingResource] --> Loader
|
||||
Loader --> Queue[WmoRenderBuildQueue]
|
||||
Queue --> Job[WmoRenderBuildJob]
|
||||
Job --> Loader
|
||||
Loader --> Planner[WmoRenderBuildStepPlanner]
|
||||
Planner --> Step[Operation and next cursors]
|
||||
Step --> Loader
|
||||
Loader --> Scene[Main-thread group Node attach]
|
||||
Loader --> Budget[RenderBudgetScheduler permit]
|
||||
```
|
||||
|
||||
The queue may strongly reference Node3D and Resource through typed jobs, but it
|
||||
must not destroy, mutate or inspect their render content. RenderingServer, RID,
|
||||
Mesh/MultiMesh instances, files, worker threads, mutexes, gameplay, network and
|
||||
editor UI are forbidden dependencies.
|
||||
|
||||
## Public API
|
||||
|
||||
### `WmoRenderBuildJob`
|
||||
|
||||
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|
||||
|---|---|---|---|---|
|
||||
| constructor `(placement_key, root, render_resource)` | Constructor | Create cursor-zero job with strong references | Renderer main thread; until cancel/clear/replacement | Queue validates inputs before construction |
|
||||
| `placement_key()` | Query | Return immutable queue identity | Any serialized caller; job lifetime | None |
|
||||
| `root()` | Query | Borrow retained Node3D reference | Main thread; no ownership transfer | May later be invalid; loader checks |
|
||||
| `render_resource()` | Query | Borrow retained Resource reference | Main thread; no ownership transfer | Queue does not validate cache contents |
|
||||
| `mesh_index()` / `multimesh_index()` | Query | Return current group cursors | Serialized job drain | None |
|
||||
| `adopt_cursors(next_mesh_index, next_multimesh_index)` | Command | Atomically adopt planner cursors | Renderer main thread | Raw integers are accepted |
|
||||
| `diagnostic_snapshot()` | Query | Return detached scalar state and presence flags | Caller-owned result | Never exposes engine objects |
|
||||
|
||||
### `WmoRenderBuildQueue`
|
||||
|
||||
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|
||||
|---|---|---|---|---|
|
||||
| `enqueue(placement_key, root, render_resource)` | Command | Replace keyed job and append FIFO key | Renderer main thread; map session | Empty/null input returns false |
|
||||
| `has_pending()` / `pending_count()` | Query | Observe FIFO entries including duplicate/stale keys | Renderer main thread | None |
|
||||
| `active_job_count()` | Query | Count current keyed jobs | Renderer main thread | None |
|
||||
| `front_key()` / `pop_front()` | Queue operation | Inspect/remove one front FIFO key | Renderer main thread | Empty queue returns empty String |
|
||||
| `has_job(placement_key)` | Query | Distinguish a live job from a stale FIFO key | Renderer main thread | Unknown key returns false |
|
||||
| `job_for(placement_key)` | Diagnostic/test query | Borrow current job through its `RefCounted` base or return null | Renderer main thread | Runtime loader uses typed accessors instead |
|
||||
| `root_for` / `render_resource_for` | Typed query | Borrow current engine references without exposing job implementation type | Renderer main thread | Stale key returns null |
|
||||
| `mesh_index_for` / `multimesh_index_for` | Typed query | Return current cursors | Renderer main thread | Stale key returns zero |
|
||||
| `adopt_cursors(placement_key, next_mesh_index, next_multimesh_index)` | Command/query | Atomically update current job cursors | Renderer main thread | Stale key returns false |
|
||||
| `cancel(placement_key)` | Command/query | Remove keyed job and first matching FIFO key | Renderer main thread | Unknown key returns false; stale duplicates remain |
|
||||
| `clear()` | Command | Release all job/key references idempotently | Reset/shutdown | Does not free engine objects |
|
||||
| `diagnostic_snapshot()` | Query | Return detached FIFO order and key-sorted scalar jobs | Caller-owned result | None |
|
||||
|
||||
## Inputs and outputs
|
||||
|
||||
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|
||||
|---|---|---|---|---|---|
|
||||
| Input | Resolved placement key | Placement/build adapter | Queue/job | Copied String | Until cancel/clear/replacement |
|
||||
| Input | WMO root Node3D | Loader instance adapter | Job | Strong reference; loader owns engine lifetime | Until job release |
|
||||
| Input | Validated WMOStreamingResource | Loader cache adapter | Job | Strong Resource reference; cache/loader owns semantics | Until job release |
|
||||
| Input | Next mesh/MultiMesh cursors | Build step planner through loader | Job | Integer values | Until next adoption |
|
||||
| Output | FIFO front key and typed job | Queue | Loader drain | Borrowed references | One drain iteration |
|
||||
| Output | Detached scalar diagnostics | Job/queue | Verifier/future facade adapter | Fresh values | Caller lifetime |
|
||||
|
||||
Side effects are limited to reference retention/release, cursor mutation and
|
||||
Dictionary/Array ordering. Scene tree, material, RID, file and permit effects
|
||||
remain outside the module.
|
||||
|
||||
## Data flow
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Enqueue[enqueue key/root/resource] --> Valid{Inputs valid?}
|
||||
Valid -->|no| Reject[false; unchanged]
|
||||
Valid -->|yes| Replace[Replace keyed typed job]
|
||||
Replace --> Append[Append key even when duplicate]
|
||||
Drain[front key] --> Lookup{Current keyed job exists?}
|
||||
Lookup -->|no stale| Pop[pop front and continue]
|
||||
Lookup -->|yes| Borrow[Borrow job to loader]
|
||||
Borrow --> Plan[Loader invokes step planner]
|
||||
Plan --> Adopt[Job adopts both next cursors]
|
||||
Cancel[cancel key] --> EraseJob[Erase keyed job]
|
||||
EraseJob --> EraseFirst[Erase first matching FIFO key]
|
||||
```
|
||||
|
||||
## Lifecycle/state
|
||||
|
||||
The queue can be `Empty`, `Pending` or `StaleFront`. Duplicate enqueue followed
|
||||
by cancel can intentionally produce `StaleFront`, matching the previous separate
|
||||
Dictionary/Array behavior. The loader pops a stale front without consuming a
|
||||
permit. `clear` returns every state to `Empty` without freeing engine objects.
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Empty
|
||||
Empty --> Pending: valid enqueue
|
||||
Pending --> Pending: enqueue / cursor adoption
|
||||
Pending --> Empty: final cancel or clear
|
||||
Pending --> StaleFront: duplicate key then cancel first occurrence
|
||||
StaleFront --> Pending: pop stale, more live entries remain
|
||||
StaleFront --> Empty: pop stale or clear
|
||||
```
|
||||
|
||||
## Main sequence
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Loader as StreamingWorldLoader
|
||||
participant Queue as WmoRenderBuildQueue
|
||||
participant Job as WmoRenderBuildJob
|
||||
participant Planner as WmoRenderBuildStepPlanner
|
||||
Loader->>Queue: enqueue(key, root, resource)
|
||||
loop while permit and pending key
|
||||
Loader->>Queue: front_key + has_job
|
||||
alt stale key
|
||||
Loader->>Queue: pop_front
|
||||
else live job
|
||||
Loader->>Queue: typed root/resource/cursor accessors
|
||||
Loader->>Planner: plan_step(counts, job cursors)
|
||||
Planner-->>Loader: operation and next cursors
|
||||
Loader->>Loader: materialize one group on main thread
|
||||
Loader->>Queue: adopt_cursors(key, next mesh, next MultiMesh)
|
||||
Loader->>Loader: consume one permit
|
||||
end
|
||||
end
|
||||
Loader->>Queue: cancel(key) or clear()
|
||||
Note over Queue: references released; Nodes/Resources not freed
|
||||
```
|
||||
|
||||
## Ownership, threading and resources
|
||||
|
||||
- Queue owns its job Dictionary and FIFO key Array.
|
||||
- Job owns strong references, but loader owns Node/Resource semantic lifetime.
|
||||
- Loader checks `is_instance_valid`, creates/frees Nodes and controls caches.
|
||||
- Loader invokes planner and adopts cursor output on the main thread.
|
||||
- Queue/job contain no mutex; all mutation is serialized by the renderer drain.
|
||||
- `clear` may release the last Resource reference but never calls engine free APIs.
|
||||
|
||||
## Errors, cancellation and recovery
|
||||
|
||||
| Failure/state | Detection | Behavior | Diagnostic | Recovery |
|
||||
|---|---|---|---|---|
|
||||
| Empty key/null root/resource | Enqueue guards | Reject without mutation | Contract verifier | Repair caller input |
|
||||
| Duplicate key | Existing keyed job | Replace job and append key | Queue diagnostics | Historical stale handling remains |
|
||||
| Stale FIFO key | `job_for` returns null | Loader pops front without permit | Contract/source verifier | Continue drain |
|
||||
| Root invalidated externally | Loader validity check | Loader cancels keyed job | Shutdown/runtime diagnostics | Placement can rebuild later |
|
||||
| Resource absent | Loader check | Loader cancels keyed job | Runtime diagnostics | Reload/requeue placement |
|
||||
| Final placement release | Placement registry returns final | Loader cancels build then frees Node | Registry/shutdown regressions | Later placement starts fresh |
|
||||
| Reset/shutdown | Loader lifecycle | Queue clear releases references | Shutdown verifier | Next map starts empty |
|
||||
|
||||
## Configuration and capabilities
|
||||
|
||||
The module introduces no settings or profile branches. WMO group permit count,
|
||||
quality presets, cache limits, visibility and shadows remain loader-owned.
|
||||
|
||||
## Persistence, cache and migration
|
||||
|
||||
No data is serialized. WMO cache format/version/path and load request behavior
|
||||
are unchanged. No migration or cache rebuild is required.
|
||||
|
||||
## Diagnostics and observability
|
||||
|
||||
`pending_count` preserves existing queue metrics, including stale/duplicate keys.
|
||||
The detached snapshot exposes FIFO order, cursor values and root/resource presence
|
||||
without exposing mutable jobs or engine objects. No runtime logs are emitted.
|
||||
|
||||
## Verification
|
||||
|
||||
- `verify_wmo_render_build_queue.gd`: invalid inputs, FIFO, typed references,
|
||||
cursor adoption, duplicate replacement, stale pop, first-key cancel, clear,
|
||||
engine lifetime, detached sorted diagnostics, source boundaries and timing.
|
||||
- WMO planner/registry/resolver/material/shutdown and renderer regressions verify
|
||||
unchanged ordering, permit use, destruction and surrounding lifecycle.
|
||||
- Fidelity evidence is exact preservation of current queue/Dictionary semantics;
|
||||
no original-client visual or asset-backed performance claim is made.
|
||||
|
||||
## Extension points
|
||||
|
||||
- Cache/load-request ownership can be extracted separately without changing jobs.
|
||||
- A later typed materialization command may consume the borrowed job and planner
|
||||
output while this queue remains the pending-state owner.
|
||||
|
||||
## Capability status
|
||||
|
||||
| Capability | Status | Evidence | Gap/next step |
|
||||
|---|---|---|---|
|
||||
| WMO render build pending-state ownership | Implemented | Lifecycle/source/timing verifier | Asset-backed traversal/leak evidence pending |
|
||||
| WMO group planning | Implemented | Build-step planner verifier | Asset-backed p95/p99 pending |
|
||||
| WMO render Resource state | Implemented extraction | Cache-state lifecycle/source/timing verifier | Asset-backed traversal/leak evidence pending |
|
||||
| WMO materialization/cache I/O | Partial | Existing runtime regressions | Cached-scene and I/O seams remain |
|
||||
|
||||
## Known gaps and risks
|
||||
|
||||
- Jobs intentionally retain strong Node/Resource references until cancel/clear.
|
||||
- Duplicate enqueue preserves a stale-key edge case rather than normalizing it.
|
||||
- Resource array shape remains a WMO cache-builder invariant.
|
||||
- No proprietary WMO corpus, traversal profile or original-client paired capture
|
||||
is included in this package.
|
||||
|
||||
## Source map
|
||||
|
||||
| Path | Responsibility |
|
||||
|---|---|
|
||||
| `src/render/wmo/wmo_render_build_job.gd` | Typed references and cursors |
|
||||
| `src/render/wmo/wmo_render_build_queue.gd` | Keyed jobs, FIFO order, cancel/clear and diagnostics |
|
||||
| `src/render/wmo/wmo_render_build_step_planner.gd` | Pure operation/cursor planning |
|
||||
| `src/scenes/streaming/streaming_world_loader.gd` | Validation, materialization, permits and engine destruction |
|
||||
| `src/tools/verify_wmo_render_build_queue.gd` | Lifecycle, ownership, boundary and timing regression |
|
||||
|
||||
## Related decisions and references
|
||||
|
||||
- [`wmo-render-build-step-planner.md`](wmo-render-build-step-planner.md)
|
||||
- [`wmo-render-resource-cache-state.md`](wmo-render-resource-cache-state.md)
|
||||
- [`wmo-placement-registry.md`](wmo-placement-registry.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,189 @@
|
||||
# WMO Render Build Step Planner
|
||||
|
||||
## Metadata
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Status | Implemented |
|
||||
| Target/work package | M03 / `M03-RND-WMO-RENDER-BUILD-PLANNER-001` |
|
||||
| Owners | Pure mesh-first WMO render-group cursor decision |
|
||||
| Last verified | Worktree `work/sindo-main-codex/m03-wmo-render-build-planner`, 2026-07-17 |
|
||||
| Profiles/capabilities | Lightweight WMO render-cache path; profile-independent |
|
||||
|
||||
## Purpose
|
||||
|
||||
Select exactly one next lightweight WMO render build operation from mesh and
|
||||
MultiMesh counts/cursors. The planner makes the ordering and cursor transition
|
||||
testable without loading a WMO, creating a Node or consuming a frame budget.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Own render Resources, Nodes, materials, jobs, queues or permits.
|
||||
- Read WMO arrays, create group instances or adopt cursors in a job.
|
||||
- Change group ordering, names, transforms, shadows or visibility.
|
||||
- Load, validate, version or persist a WMO cache.
|
||||
- Implement portals, rooms, doodad semantics or WMO visual parity.
|
||||
|
||||
## Context and boundaries
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Cache[WMOStreamingResource arrays] --> Loader[StreamingWorldLoader]
|
||||
Job[Loader mesh/MultiMesh cursors] --> Loader
|
||||
Loader --> Planner[WmoRenderBuildStepPlanner]
|
||||
Planner --> Step[Operation + selected index + next cursors]
|
||||
Step --> Loader
|
||||
Loader --> Material[Material refresh]
|
||||
Loader --> Node[MeshInstance3D or MultiMeshInstance3D]
|
||||
Loader --> Budget[RenderBudgetScheduler permit]
|
||||
```
|
||||
|
||||
The planner depends only on `RefCounted`, integers, `StringName` and a fresh
|
||||
Dictionary result. Node, Resource, Mesh, MultiMesh, RID, SceneTree, files,
|
||||
threads, locks, caches, gameplay, network and editor UI are forbidden.
|
||||
|
||||
## Public API
|
||||
|
||||
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|
||||
|---|---|---|---|---|
|
||||
| `OPERATION_MESH` | Constant | Identifies one mesh-group operation | Immutable | None |
|
||||
| `OPERATION_MULTIMESH` | Constant | Identifies one doodad MultiMesh-group operation | Immutable | None |
|
||||
| `OPERATION_COMPLETE` | Constant | Identifies exhausted mesh and MultiMesh ranges | Immutable | None |
|
||||
| `plan_step(mesh_count, mesh_index, multimesh_count, multimesh_index)` | Pure query | Select one operation, selected index and next cursors | Any thread; caller-owned result | No clamp/error branch; raw integer comparisons are preserved |
|
||||
|
||||
## Inputs and outputs
|
||||
|
||||
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|
||||
|---|---|---|---|---|---|
|
||||
| Input | Mesh array count and current mesh cursor | Loader resource/job adapter | Planner | Integer values | One call |
|
||||
| Input | MultiMesh array count and current MultiMesh cursor | Loader resource/job adapter | Planner | Integer values | One call |
|
||||
| Output | `mesh`, `multimesh` or `complete` operation | Planner | Loader materializer/cancellation branch | Immutable `StringName` | One call |
|
||||
| Output | Selected index | Planner | Loader Resource array access | Integer value | One operation |
|
||||
| Output | Next mesh and MultiMesh cursors | Planner | Loader job adoption | Fresh Dictionary values | Until adopted/discarded |
|
||||
|
||||
The planner retains no input or output. Scene-tree mutation, material refresh,
|
||||
job writes, permit consumption and cancellation are loader side effects.
|
||||
|
||||
## Data flow
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Input[Counts and cursors] --> MeshRemaining{mesh index < mesh count?}
|
||||
MeshRemaining -->|yes| MeshStep[mesh at mesh index; increment mesh cursor]
|
||||
MeshRemaining -->|no| MultiRemaining{MultiMesh index < MultiMesh count?}
|
||||
MultiRemaining -->|yes| MultiStep[MultiMesh at its index; increment MultiMesh cursor]
|
||||
MultiRemaining -->|no| Complete[complete; keep both cursors]
|
||||
```
|
||||
|
||||
## Lifecycle/state
|
||||
|
||||
The planner is stateless. State belongs to the loader job. Repeated loader calls
|
||||
advance through `mesh` steps, then `multimesh` steps, then `complete`. Planner
|
||||
construction, cancellation, map reset and shutdown require no cleanup.
|
||||
|
||||
## Main sequence
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Loader as StreamingWorldLoader
|
||||
participant Planner as WmoRenderBuildStepPlanner
|
||||
participant Scene as SceneTree
|
||||
participant Budget as RenderBudgetScheduler
|
||||
Loader->>Planner: plan_step(counts, cursors)
|
||||
Planner-->>Loader: operation, selected index, next cursors
|
||||
alt mesh or MultiMesh
|
||||
Loader->>Loader: refresh material and create group Node
|
||||
Loader->>Scene: attach Node and editor owner
|
||||
Loader->>Loader: adopt returned cursor
|
||||
Loader->>Budget: consume one WMO group permit
|
||||
else complete
|
||||
Loader->>Loader: cancel/remove job and queue key
|
||||
end
|
||||
```
|
||||
|
||||
## Ownership, threading and resources
|
||||
|
||||
- The planner owns only call-local scalar comparisons and the returned value map.
|
||||
- `WmoRenderBuildQueue` owns typed jobs, FIFO keys and strong Node3D/Resource
|
||||
references. The loader owns mesh-array interpretation and engine lifetime.
|
||||
- The loader creates and owns every MeshInstance3D/MultiMeshInstance3D and
|
||||
performs material refresh and editor-owner assignment on the main thread.
|
||||
- `RenderBudgetScheduler` owns permits; the planner neither reads nor consumes one.
|
||||
- Pure calls are thread-safe because the planner has no mutable state.
|
||||
|
||||
## Errors, cancellation and recovery
|
||||
|
||||
| Failure/state | Detection | Behavior | Diagnostic | Recovery |
|
||||
|---|---|---|---|---|
|
||||
| Empty arrays | Both `0 < 0` comparisons false | Return `complete` | Contract verifier | Loader removes job |
|
||||
| Cursor at/beyond count | Raw integer comparison | Skip that range | Contract verifier | Continue next range or complete |
|
||||
| Negative cursor/count | Raw historical comparison | No clamp; select exactly as prior loader condition | Edge fixture | Caller should maintain valid job cursors |
|
||||
| Missing/invalid Resource | Loader validation before planning | Loader cancels job | Existing shutdown/runtime diagnostics | Reload/requeue placement |
|
||||
| Root freed | Loader validity check before planning | Loader cancels job | Runtime-cache shutdown verifier | Placement may rebuild later |
|
||||
| Placement final release | Loader/registry lifecycle | Loader cancels queued build | Registry regression | Later placement starts at cursor zero |
|
||||
|
||||
## Configuration and capabilities
|
||||
|
||||
The planner has no setting or profile branch. `wmo_render_group_ops_per_tick`,
|
||||
quality presets, shadow/visibility settings and cache limits remain loader-owned.
|
||||
|
||||
## Persistence, cache and migration
|
||||
|
||||
No data is persisted. WMO render resource format/version, cache path and job
|
||||
Dictionary fields are unchanged. No migration or cache rebuild is required.
|
||||
|
||||
## Diagnostics and observability
|
||||
|
||||
The planner emits no logs. Its verifier reports operation/cursor/source contracts
|
||||
and bounded timing. Queue lengths, active WMO count and hitch sections remain in
|
||||
existing loader/facade diagnostics.
|
||||
|
||||
## Verification
|
||||
|
||||
- `verify_wmo_render_build_step_planner.gd`: mesh-first order, cursor increments,
|
||||
completion, empty and raw negative integer behavior, fresh results, loader
|
||||
delegation, dependency boundary and 20,000-plan timing.
|
||||
- WMO registry/resolver/material/shutdown plus renderer regressions verify the
|
||||
unchanged ownership and surrounding runtime path.
|
||||
- Fidelity evidence is exact extraction of existing mesh-before-MultiMesh and
|
||||
one-index-per-permit behavior. No asset-backed build-12340 visual claim is made.
|
||||
|
||||
## Extension points
|
||||
|
||||
- `WmoRenderBuildQueue` now owns typed pending state; cache/load requests and
|
||||
materialization remain separate extraction seams.
|
||||
- Materialization can later receive typed commands without changing this planner.
|
||||
|
||||
## Capability status
|
||||
|
||||
| Capability | Status | Evidence | Gap/next step |
|
||||
|---|---|---|---|
|
||||
| WMO render build step planning | Implemented | Pure contract/source/timing verifier | Asset-backed traversal p95/p99 pending |
|
||||
| WMO build job/queue ownership | Implemented | Typed lifecycle/source/timing verifier | Asset-backed traversal/leak evidence pending |
|
||||
| WMO visual/portal/material fidelity | Partial | Renderer baseline/material checks | Original-client paired evidence pending |
|
||||
|
||||
## Known gaps and risks
|
||||
|
||||
- Loader build jobs remain untyped Dictionaries.
|
||||
- Resource array shape/length consistency remains a cache-builder invariant.
|
||||
- No proprietary WMO corpus, traversal profile or paired client capture is part
|
||||
of this extraction.
|
||||
|
||||
## Source map
|
||||
|
||||
| Path | Responsibility |
|
||||
|---|---|
|
||||
| `src/render/wmo/wmo_render_build_step_planner.gd` | Pure operation and cursor selection |
|
||||
| `src/render/wmo/wmo_render_build_job.gd` | Typed engine references and cursors |
|
||||
| `src/render/wmo/wmo_render_build_queue.gd` | Pending jobs, FIFO keys and cancellation state |
|
||||
| `src/scenes/streaming/streaming_world_loader.gd` | Resource validation, materialization, permits and engine lifetime |
|
||||
| `src/tools/verify_wmo_render_build_step_planner.gd` | Contract, boundary and timing regression |
|
||||
|
||||
## Related decisions and references
|
||||
|
||||
- [`wmo-placement-registry.md`](wmo-placement-registry.md)
|
||||
- [`wmo-render-build-queue.md`](wmo-render-build-queue.md)
|
||||
- [`wmo-placement-resolver.md`](wmo-placement-resolver.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,226 @@
|
||||
# WMO Render Resource Cache State
|
||||
|
||||
## Metadata
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Status | Implemented |
|
||||
| Target/work package | M03 / `M03-RND-WMO-RENDER-RESOURCE-CACHE-001` |
|
||||
| Owners | Validated lightweight-WMO render Resources, negative cache and pending request paths |
|
||||
| Last verified | Worktree `work/sindo-main-codex/m03-wmo-render-resource-cache`, 2026-07-17 |
|
||||
| Profiles/capabilities | Lightweight WMO render-cache path; profile-independent |
|
||||
|
||||
## Purpose
|
||||
|
||||
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 terminal I/O lifecycle belongs to
|
||||
`WmoRenderResourceFinalizer`.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Normalize WMO paths or construct `.res` cache paths.
|
||||
- Call `ResourceLoader`, inspect files or validate script/format versions.
|
||||
- Own WMO cached scenes, live prototypes, placements, build jobs or engine Nodes.
|
||||
- Change fallback order, retry timing, quality profiles or visible output.
|
||||
- Persist, migrate or build WMO caches.
|
||||
|
||||
## Context and boundaries
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Placement[Normalized WMO render lookup] --> Loader[StreamingWorldLoader]
|
||||
Loader --> State[WmoRenderResourceCacheState]
|
||||
State -->|cached Resource| Loader
|
||||
Loader -->|cache path| ResourceLoader[Godot ResourceLoader]
|
||||
ResourceLoader -->|status and loaded Resource| Loader
|
||||
Loader --> Finalizer[WmoRenderResourceFinalizer]
|
||||
Finalizer --> Validate[Script and FORMAT_VERSION validation]
|
||||
Validate -->|accepted Resource or missing| State
|
||||
Loader --> Fallback[Cached scene or live-prototype fallback]
|
||||
```
|
||||
|
||||
The module may retain validated `Resource` references and copied path Strings.
|
||||
It has no FileAccess, ResourceLoader, Node, RID, worker, mutex, gameplay, network
|
||||
or editor dependency. Cache validation stays at the I/O boundary in the loader.
|
||||
|
||||
## Public API
|
||||
|
||||
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|
||||
|---|---|---|---|---|
|
||||
| `resource_for(path)` | Query | Borrow validated cached Resource | Renderer main thread; until full clear | Empty/unknown returns null |
|
||||
| `contains_resource(path)` | Query | Test accepted cache state | Renderer main thread | Empty returns false |
|
||||
| `is_missing(path)` | Query | Test negative cache state | Renderer main thread | Empty returns false |
|
||||
| `has_request(path)` | Query | Test pending threaded-request state | Renderer main thread | Empty returns false |
|
||||
| `remember_request(path, cache_path)` | Command/query | Record one loader-started request | Renderer main thread; until terminal/reset | Invalid or occupied state returns false |
|
||||
| `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 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 |
|
||||
| `diagnostic_snapshot()` | Query | Return sorted detached scalar keys | Tests/diagnostics | Never exposes Resources |
|
||||
|
||||
## Inputs and outputs
|
||||
|
||||
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|
||||
|---|---|---|---|---|---|
|
||||
| Input | Normalized relative WMO path | Placement resolver through loader | Cache state | Copied String | Map/cache lifetime |
|
||||
| Input | `.res` cache path after accepted threaded-request result | Loader | Pending state | Copied String | Until terminal/reset |
|
||||
| Input | Script/version-validated WMO Resource | Loader validation adapter | Resource cache | Strong reference | Until full clear |
|
||||
| Output | Borrowed validated Resource | Cache state | Loader render-root/build adapters | State retains reference | Lookup/build start |
|
||||
| Output | Detached pending-path snapshot | Cache state | Loader ResourceLoader polling/shutdown | Caller-owned Dictionary | One drain pass |
|
||||
| Output | Sorted scalar diagnostic keys | Cache state | Verifier/future metrics | Caller-owned values | Snapshot lifetime |
|
||||
|
||||
## Data flow
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Lookup[Lookup normalized path] --> Cached{Cached?}
|
||||
Cached -->|yes| Return[Return validated Resource]
|
||||
Cached -->|no| Blocked{Missing or pending?}
|
||||
Blocked -->|yes| Null[Return null; loader waits/falls back]
|
||||
Blocked -->|no| Start[Loader starts threaded request]
|
||||
Start --> Remember[remember_request]
|
||||
Remember --> Poll[Finalizer polls detached request snapshot]
|
||||
Poll --> Terminal{Loaded or failed?}
|
||||
Terminal -->|no| Poll
|
||||
Terminal -->|failed| Missing[complete as missing]
|
||||
Terminal -->|loaded| Validate[Finalizer validates script/version]
|
||||
Validate -->|valid| Adopt[complete with Resource]
|
||||
Validate -->|invalid| Missing
|
||||
```
|
||||
|
||||
## Lifecycle/state
|
||||
|
||||
Each normalized path is `Absent`, `Pending`, `Cached` or `Missing`. Valid request
|
||||
completion leaves exactly one terminal state. Transient clear moves `Pending` and
|
||||
`Missing` to `Absent` but retains `Cached`; full clear moves every state to
|
||||
`Absent` and releases retained Resources.
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Absent
|
||||
Absent --> Pending: remember valid request
|
||||
Pending --> Cached: validated Resource completion
|
||||
Pending --> Missing: load/validation failure
|
||||
Pending --> Absent: clear transient/full
|
||||
Missing --> Absent: clear transient/full
|
||||
Cached --> Absent: full clear
|
||||
```
|
||||
|
||||
## Main sequence
|
||||
|
||||
```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->>Finalizer: poll_terminal_requests(State)
|
||||
Finalizer->>State: request_paths_snapshot()
|
||||
Finalizer->>RL: load_threaded_get_status(cache path)
|
||||
end
|
||||
alt load failed
|
||||
Finalizer->>State: complete_request_as_missing(normalized)
|
||||
else loaded
|
||||
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 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.
|
||||
|
||||
## Errors, cancellation and recovery
|
||||
|
||||
| Failure/state | Detection | Behavior | Diagnostic | Recovery |
|
||||
|---|---|---|---|---|
|
||||
| Empty path/cache path | Request guard | Reject unchanged | Contract verifier | Repair caller input |
|
||||
| Duplicate or occupied path | State membership | Reject request unchanged | Contract/source verifier | Await terminal/reset |
|
||||
| Threaded load failed | Loader status | Complete as missing | Existing renderer metrics | Transient reset permits retry |
|
||||
| Wrong script/old format | Loader validation | Complete as missing | Cache-version source gate | Rebuild cache, then reset |
|
||||
| Shutdown while pending | Loader drains ResourceLoader snapshot | Clear transient afterward | Shutdown verifier | Next runtime starts absent |
|
||||
| Final cache release | Loader shutdown lifecycle | Full clear releases Resources | Shutdown verifier | Recreated with loader |
|
||||
|
||||
## Configuration and capabilities
|
||||
|
||||
The module adds no setting or profile branch. `wmo_render_cache_dir`, resource
|
||||
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 finalizer; no
|
||||
cache migration or rebuild is introduced by this extraction.
|
||||
|
||||
## Diagnostics and observability
|
||||
|
||||
`pending_request_count` preserves the existing `wmobuild` metric contribution.
|
||||
Diagnostics expose only sorted normalized-path keys for cached/missing/pending
|
||||
states; Resource references and cache file paths are not exposed. No logs emit.
|
||||
|
||||
## Verification
|
||||
|
||||
- `verify_wmo_render_resource_cache_state.gd`: invalid input, detached requests,
|
||||
successful/missing terminal transitions, exclusivity, partial/full reset,
|
||||
sorted diagnostics, ownership/source boundary and bounded timing.
|
||||
- `verify_render_runtime_cache_shutdown.gd`: full release of accepted Resources
|
||||
and negative state through the loader lifecycle.
|
||||
- WMO queue/planner/registry/resolver and surrounding renderer regressions verify
|
||||
unchanged fallback/build behavior. This package makes no visual parity claim.
|
||||
|
||||
## Extension points
|
||||
|
||||
- Cached WMO scene request state is a separate typed module because its size
|
||||
limit, PackedScene validation and missing-file behavior differ.
|
||||
- A later loader I/O adapter may consume the detached request snapshot while
|
||||
preserving this state contract and cache-version boundary.
|
||||
|
||||
## Capability status
|
||||
|
||||
| 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 | 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 |
|
||||
|
||||
## Known gaps and risks
|
||||
|
||||
- Strong Resource references live until final cache release, matching prior state.
|
||||
- Missing cache files remain unrecorded for render Resources, so lookup can retry;
|
||||
this intentionally differs from the cached-scene path.
|
||||
- No proprietary WMO corpus, 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_cache_state.gd` | Resource/missing/request state and resets |
|
||||
| `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 |
|
||||
|
||||
## Related decisions and references
|
||||
|
||||
- [`wmo-render-build-queue.md`](wmo-render-build-queue.md)
|
||||
- [`wmo-scene-resource-cache-state.md`](wmo-scene-resource-cache-state.md)
|
||||
- [`wmo-placement-resolver.md`](wmo-placement-resolver.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,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)
|
||||
@@ -0,0 +1,212 @@
|
||||
# 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 traversal/build step] -->|Mesh plus extracted directory| Finalizer[WmoRuntimeMeshFinalizer]
|
||||
Finalizer -->|material definition plus compact texture paths| Builder[WMOBuilder material boundary]
|
||||
Builder -->|rebuilt Material| Finalizer
|
||||
Finalizer -->|same Mesh identity| Loader
|
||||
```
|
||||
|
||||
The loader owns scene traversal 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 | Loader traversal/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 Finalizer as WmoRuntimeMeshFinalizer
|
||||
participant Mesh as ArrayMesh
|
||||
participant Builder as WMOBuilder
|
||||
Loader->>Finalizer: finalize_mesh(mesh, extracted_directory)
|
||||
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
|
||||
```
|
||||
|
||||
## Dependency diagram
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
Loader[StreamingWorldLoader] --> Finalizer[WmoRuntimeMeshFinalizer]
|
||||
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/materialization | Loader-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/scenes/streaming/streaming_world_loader.gd` | Composition, Node traversal, build jobs 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)
|
||||
@@ -0,0 +1,218 @@
|
||||
# WMO Scene Resource Cache State
|
||||
|
||||
## Metadata
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Status | Implemented |
|
||||
| Target/work package | M03 / `M03-RND-WMO-SCENE-RESOURCE-CACHE-001` |
|
||||
| Owners | Validated cached-WMO PackedScenes, negative cache and pending request paths |
|
||||
| Last verified | Worktree `work/sindo-main-codex/m03-wmo-scene-resource-cache`, 2026-07-17 |
|
||||
| Profiles/capabilities | Cached WMO `.tscn` fallback; profile-independent state |
|
||||
|
||||
## Purpose
|
||||
|
||||
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. Size admission remains in
|
||||
`StreamingWorldLoader`; terminal I/O/probe validation belongs to
|
||||
`WmoSceneResourceFinalizer`.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Normalize WMO paths, build `.tscn` paths or measure files.
|
||||
- Call ResourceLoader/FileAccess, instantiate scenes or validate cache metadata.
|
||||
- Own live prototypes, placement/build jobs, instantiated Nodes or RIDs.
|
||||
- Change missing, oversize, request-error, retry or fallback behavior.
|
||||
- Persist, migrate or build WMO caches.
|
||||
|
||||
## Context and boundaries
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Lookup[Normalized WMO scene lookup] --> Loader[StreamingWorldLoader]
|
||||
Loader --> State[WmoSceneResourceCacheState]
|
||||
Loader --> Size[File existence and size limit]
|
||||
Size --> ResourceLoader[Godot ResourceLoader]
|
||||
ResourceLoader --> Finalizer[WmoSceneResourceFinalizer]
|
||||
Finalizer --> Probe[Instantiate, metadata/version check, free]
|
||||
Probe -->|accepted PackedScene or missing| State
|
||||
Loader --> Live[Live-prototype fallback]
|
||||
```
|
||||
|
||||
The module retains validated PackedScenes and copied paths only. ResourceLoader,
|
||||
FileAccess, WMOBuilder, Node, RID, worker, gameplay, network and editor concerns
|
||||
remain outside it.
|
||||
|
||||
## Public API
|
||||
|
||||
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|
||||
|---|---|---|---|---|
|
||||
| `scene_for(path)` / `contains_scene(path)` | Query | Borrow/test validated PackedScene | Renderer main thread; until full clear | Empty/unknown returns null/false |
|
||||
| `is_missing(path)` / `has_request(path)` | Query | Test negative/pending state | Renderer main thread | Empty returns false |
|
||||
| `remember_request(path, cache_path)` | Command/query | Record loader-started request | Until terminal/reset | Invalid/occupied returns false |
|
||||
| `mark_missing(path)` | Command/query | Record absent/oversize/request-start failure without pending state | Until transient/full clear | Invalid/occupied returns false |
|
||||
| `request_paths_snapshot()` | Query | Copy pending normalized/cache-path mapping | Poll/shutdown drain | Detached Dictionary |
|
||||
| `complete_request_with_scene(path, scene)` | Command/query | Remove pending and adopt validated PackedScene | Terminal poll | Unknown/null returns false |
|
||||
| `complete_request_as_missing(path)` | Command/query | Remove pending and adopt negative state | Terminal poll | Unknown returns false |
|
||||
| `clear_transient_state()` | Command | Clear pending/missing and retain scenes | Map reset/request drain | Idempotent |
|
||||
| `clear_all()` | Command | Release scenes and transient state | Final runtime-cache release | Idempotent |
|
||||
| `pending_request_count()` | Query | Preserve renderer queue metric | Diagnostics | None |
|
||||
| `diagnostic_snapshot()` | Query | Return sorted detached scalar keys | Tests/diagnostics | No PackedScene exposure |
|
||||
|
||||
## Inputs and outputs
|
||||
|
||||
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|
||||
|---|---|---|---|---|---|
|
||||
| Input | Normalized WMO path | Placement resolver through loader | State | Copied String | Cache lifetime |
|
||||
| Input | `.tscn` path after accepted request start | Loader | Pending state | Copied String | Until terminal/reset |
|
||||
| Input | Metadata/version-validated PackedScene | Loader probe adapter | Scene cache | Strong reference | Until full clear |
|
||||
| Input | Missing decision | Loader file/size/request/load/validation adapters | Negative cache | Boolean membership | Until transient/full clear |
|
||||
| Output | Borrowed PackedScene | State | Loader scene instantiation | State retains reference | Lookup/instantiate call |
|
||||
| Output | Detached request snapshot | State | Loader polling/shutdown | Caller-owned Dictionary | One drain pass |
|
||||
|
||||
## Data flow
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Lookup --> Known{Cached, missing or pending?}
|
||||
Known -->|cached| Return[Return PackedScene]
|
||||
Known -->|missing/pending| Null[Return null]
|
||||
Known -->|absent| Check[Loader checks file and size]
|
||||
Check -->|missing/oversize| Mark[mark_missing]
|
||||
Check -->|allowed| Request[Start threaded request]
|
||||
Request -->|error| Mark
|
||||
Request -->|accepted| Remember[remember_request]
|
||||
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]
|
||||
Validate -->|invalid| CompleteMissing
|
||||
```
|
||||
|
||||
## Lifecycle/state
|
||||
|
||||
Each normalized path is `Absent`, `Pending`, `Cached` or `Missing`. Direct file,
|
||||
size and request-start rejection moves `Absent` directly to `Missing`; terminal
|
||||
loads move `Pending` to `Cached` or `Missing`. Transient clear retains `Cached`,
|
||||
while full clear returns every path to `Absent`.
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Absent
|
||||
Absent --> Pending: accepted request
|
||||
Absent --> Missing: absent, oversize or request error
|
||||
Pending --> Cached: validated PackedScene
|
||||
Pending --> Missing: load or validation failure
|
||||
Pending --> Absent: transient/full clear
|
||||
Missing --> Absent: transient/full clear
|
||||
Cached --> Absent: full clear
|
||||
```
|
||||
|
||||
## Main sequence
|
||||
|
||||
```mermaid
|
||||
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->>Finalizer: poll_terminal_requests(State)
|
||||
Finalizer->>State: request_paths_snapshot()
|
||||
Finalizer->>RL: load_threaded_get_status(path)
|
||||
end
|
||||
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, 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.
|
||||
|
||||
## Errors, cancellation and recovery
|
||||
|
||||
| Failure/state | Detection | Behavior | Recovery |
|
||||
|---|---|---|---|
|
||||
| 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 | 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
|
||||
|
||||
No setting is added. `wmo_cache_dir`, `wmo_max_runtime_scene_mb`, request timing,
|
||||
quality profiles and WMO build budgets stay loader-owned.
|
||||
|
||||
## Persistence, cache and migration
|
||||
|
||||
The module is runtime-only. Existing WMOBuilder cache metadata/version checks
|
||||
remain unchanged and execute before state adoption. No cache format, migration or
|
||||
rebuild is introduced.
|
||||
|
||||
## Diagnostics and observability
|
||||
|
||||
Pending count preserves the existing `wmobuild` metric contribution. The snapshot
|
||||
exposes sorted scene/missing/request keys only. Existing oversize logging remains
|
||||
in the loader; the state emits no logs.
|
||||
|
||||
## Verification
|
||||
|
||||
- `verify_wmo_scene_resource_cache_state.gd`: invalid input, direct missing,
|
||||
exclusivity, request completion, partial/full reset, diagnostics, boundaries
|
||||
and bounded timing.
|
||||
- `verify_render_runtime_cache_shutdown.gd`: loader full release of scenes,
|
||||
negative and pending state.
|
||||
- Adjacent WMO/renderer regressions verify unchanged lookup/fallback behavior.
|
||||
This extraction makes no visual or build-12340 parity claim.
|
||||
|
||||
## Extension points
|
||||
|
||||
- A later I/O adapter may consume request snapshots while retaining this contract.
|
||||
- Scene instantiation/material preparation can be extracted separately because it
|
||||
owns Nodes and main-thread side effects rather than cache state.
|
||||
|
||||
## Capability status
|
||||
|
||||
| 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 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
|
||||
|
||||
- PackedScenes remain strongly referenced until final shutdown cache release.
|
||||
- Negative caching suppresses retries until transient reset, matching prior behavior.
|
||||
- No proprietary WMO corpus, stale/oversize asset fixture, leak run, traversal
|
||||
p95/p99 or paired original-client capture is included.
|
||||
|
||||
## Source map
|
||||
|
||||
| Path | Responsibility |
|
||||
|---|---|
|
||||
| `src/render/wmo/wmo_scene_resource_cache_state.gd` | Scene/missing/request state and resets |
|
||||
| `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 |
|
||||
|
||||
## 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)
|
||||
@@ -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)
|
||||
@@ -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-wmo-placement-resolver`, 2026-07-17 |
|
||||
| Last verified | Worktree `work/sindo-main-codex/m03-m2-animation-playback`, 2026-07-18 |
|
||||
| Profiles/capabilities | `Performance`, `Balanced`, `High`, `Custom`; Blizzlike fidelity incomplete |
|
||||
|
||||
## Purpose
|
||||
@@ -54,8 +54,22 @@ flowchart LR
|
||||
M2Transform --> Loader
|
||||
Loader --> M2Batch[M2BuildBatchPlanner]
|
||||
M2Batch --> Loader
|
||||
Loader --> M2Dispatch[M2BuildDispatchPlanner]
|
||||
M2Dispatch --> Loader
|
||||
Loader --> M2Resources[M2BuildResourceSnapshot]
|
||||
M2Resources --> M2Dispatch
|
||||
Loader --> M2Queue[M2BuildQueue]
|
||||
M2Queue --> Loader
|
||||
Loader --> M2Static[M2StaticBatchMaterializer]
|
||||
M2Static --> Scene
|
||||
Loader --> WmoPlacement[WmoPlacementResolver]
|
||||
WmoPlacement --> Loader
|
||||
Loader --> WmoRegistry[WmoPlacementRegistry]
|
||||
WmoRegistry --> Loader
|
||||
Loader --> WmoBuildStep[WmoRenderBuildStepPlanner]
|
||||
WmoBuildStep --> Loader
|
||||
Loader --> WmoBuildQueue[WmoRenderBuildQueue]
|
||||
WmoBuildQueue --> Loader
|
||||
Native --> Parsed[Parsed tile/model data]
|
||||
Parsed --> Loader
|
||||
Loader --> Scene[SceneTree nodes]
|
||||
@@ -126,7 +140,36 @@ 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 |
|
||||
| `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 |
|
||||
|
||||
Публичным contract не считаются `StreamingWorldLoader` methods/properties,
|
||||
внутренние dictionaries, queues, job records и generated node names. Scene-owned
|
||||
@@ -155,7 +198,23 @@ 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 render cache | Normalized path, cache path and validated Resource | Loader / `WmoRenderResourceCacheState` | Loader lookup, ResourceLoader poll and build queue | State-owned Resource/path references; detached request snapshots | Until transient/full clear |
|
||||
| Internal WMO scene cache | Normalized path, `.tscn` path and validated PackedScene | Loader / `WmoSceneResourceCacheState` | Loader lookup, request poll and scene instantiation | State-owned PackedScene/path references; detached request snapshots | Until transient/full clear |
|
||||
| Internal ADT water load | Tile key, ADT path, task ID and parsed Dictionary | Loader/worker / `AdtWaterLoadPipelineState` | Loader task start, budgeted drain and finalization | State-owned records; mutex result mailbox | Request through result completion/reset |
|
||||
| Internal raw M2 read | Extracted directory and normalized relative path | Loader/native observer via `M2RawModelRepository` | Finalizer, static or animated builder adapters | Fresh Dictionary; repository retains nothing | One synchronous native call |
|
||||
| Internal animated M2 load | Normalized path, cached GLB path and opaque terminal status | Cached observer / loader / `M2AnimationLoadPipelineState` | Loader poll/finalize adapters | State-owned String/status records; detached pending snapshots | Request through terminal finalize/reset |
|
||||
| Internal animated M2 candidate | Loaded PackedScene, static material root and detached candidate | Loader / `M2AnimatedSceneFinalizer` | Loader prototype adoption/static fallback | Finalizer owns until rejection or exact-root transfer | One main-thread finalize permit |
|
||||
| Internal animated M2 playback | Prototype/instance native animators, path/index and player inventory | Materializer/finalizer / `M2AnimationPlaybackController` | Native and imported animation state | Borrowed Nodes; detached optional diagnostics | One duplicated instance startup |
|
||||
| Internal animated M2 materialization | Parent, prototype, ordered transform slice and render settings | Loader / `M2AnimatedInstanceMaterializer` | Attached animated batch and indexed diagnostics | Parent owns batch; caller owns detached diagnostics | One main-thread build batch |
|
||||
| Internal M2 prototype state | Normalized path, adopted static/animated Node or negative outcome | Loader / `M2PrototypeCacheState` | Loader reuse/fallback adapters | State-owned strong Node refs and Strings; detached diagnostics | Until final shutdown |
|
||||
| Output | Desired tile/detail operations | Streamer plan application | Finalize queues | Loader-owned | Cross-frame |
|
||||
| Output | Terrain/M2/WMO/liquid instances | Loader/builders | Godot world/renderer | Loader/world owner | Main-thread attach |
|
||||
| Output | Detached renderer metrics | `WorldRenderFacade` | Capture/baseline tools | Caller-owned deep copy | On demand |
|
||||
@@ -203,9 +262,17 @@ flowchart TD
|
||||
M2Registry --> M2Grouper[M2PlacementGrouper]
|
||||
M2Transform[M2PlacementTransformResolver] --> M2Grouper
|
||||
M2Grouper --> M2Batch[M2BuildBatchPlanner]
|
||||
M2Batch --> M2
|
||||
M2Grouper --> M2Queue[M2BuildQueue]
|
||||
M2Queue --> M2Batch
|
||||
M2Batch --> M2Resources[M2BuildResourceSnapshot]
|
||||
M2Resources --> M2Dispatch[M2BuildDispatchPlanner]
|
||||
M2Dispatch --> M2Static[M2StaticBatchMaterializer]
|
||||
M2Static --> M2
|
||||
R --> WmoPlacement[WmoPlacementResolver]
|
||||
WmoPlacement --> WMO
|
||||
WmoPlacement --> WmoRegistry[WmoPlacementRegistry]
|
||||
WmoRegistry --> WmoBuildQueue[WmoRenderBuildQueue]
|
||||
WmoBuildQueue --> WmoBuildStep[WmoRenderBuildStepPlanner]
|
||||
WmoBuildStep --> WMO
|
||||
R --> B[RenderBudgetScheduler permits]
|
||||
B --> Terrain[Terrain attach/upgrade]
|
||||
B --> M2[M2 group/MultiMesh attach]
|
||||
@@ -324,11 +391,74 @@ sequenceDiagram
|
||||
grouper owns worker-path final transforms; direct placeholder/instance
|
||||
transforms and every build/render side effect remain loader-owned.
|
||||
- `M2PlacementGrouper` is stateless and owns only call-local grouped transforms.
|
||||
The loader retains tasks, mutex/result queues, stale checks and build state.
|
||||
- `M2BuildBatchPlanner` is stateless and owns only call-local scalar plans. The
|
||||
loader retains queue/resource transitions, cursor adoption and materialization.
|
||||
The loader retains tasks, mutex/result queues and stale-result checks; accepted
|
||||
groups enter `M2BuildQueue` as typed pending jobs.
|
||||
- `M2BuildBatchPlanner` is stateless and owns only call-local scalar plans.
|
||||
`M2BuildDispatchPlanner` is stateless and owns only call-local action/transition
|
||||
plans after the loader observes resource availability. `M2BuildResourceSnapshot`
|
||||
carries those per-step observations and borrows exact prototype/Mesh references
|
||||
without controlling engine lifetime.
|
||||
`M2BuildQueue` owns typed pending jobs, FIFO/stale keys, grouped-transform
|
||||
references and group/offset/serial cursors without freeing engine objects.
|
||||
`M2StaticBuildResourceObserver` owns static Mesh lookup/request admission and
|
||||
snapshot adoption. `M2CachedAnimationResourceObserver` owns cached animated
|
||||
prototype lookup, GLB policy/request admission and snapshot production.
|
||||
`M2NativeAnimationResourceObserver` owns native candidate selection, raw read,
|
||||
detached build and prototype/static-only cache outcome.
|
||||
`M2StaticBatchMaterializer` owns static MultiMesh construction/attachment; the
|
||||
loader retains action execution, cursor adoption, root cleanup, budgets and
|
||||
Editor ownership.
|
||||
- `WmoPlacementResolver` is stateless and owns only call-local cache-key,
|
||||
identity and transform values. The loader retains WMO refs/caches/jobs/resources.
|
||||
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.
|
||||
`WmoRenderResourceCacheState` owns validated render Resources, negative entries
|
||||
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, materialization, permits, 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,
|
||||
stale tile/path/root checks and `water_loaded` timing. `AdtWaterSceneFinalizer`
|
||||
performs the existing ADTBuilder call, tile attach and optional recursive
|
||||
Editor ownership on the main thread without retaining the generated subtree.
|
||||
- `M2RuntimeMeshRebuildClassifier` owns only normalized-path boolean decisions
|
||||
for billboard/UV-rotation material refresh 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 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 static observer admits ResourceLoader
|
||||
requests and initial cache/missing adoption; the Mesh resource finalizer owns
|
||||
terminal extraction/adoption, while the loader retains materialization.
|
||||
- `M2RuntimeMeshFinalizer` owns refresh version `2`, classifier lifetime,
|
||||
M2Builder rebuild and original-Mesh fallback. `M2MeshResourceFinalizer` loads
|
||||
raw data only after the runtime finalizer reports that a cached Mesh is stale.
|
||||
- `M2RawModelRepository` owns FileAccess/ClassDB availability and the exact
|
||||
`load_m2`/`load_m2_animated` calls. The loader retains normalization, fallback
|
||||
order and every result consumer; `M2PrototypeCacheState` retains outcomes.
|
||||
- `M2PrototypeCacheState` owns exact static/animated prototype references,
|
||||
missing-model and static-animation path sets, duplicate-candidate release and
|
||||
final-shutdown Node cleanup. ResourceLoader, builders and fallback decisions
|
||||
remain loader-owned.
|
||||
- Rendered-ground query results and diagnostic snapshots are caller-owned values;
|
||||
the facade never returns Mesh, Node, tile-state or queue references.
|
||||
- Loaded-mesh ground sampling is renderer diagnostics, not authoritative gameplay
|
||||
@@ -422,6 +552,21 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
|
||||
count, completion/cursor rules, source boundary and bounded timing.
|
||||
- WMO placement contract: cache-path normalization, positive/fallback identity,
|
||||
default/rotated/unclamped transforms, three consumers and bounded timing.
|
||||
- WMO placement registry contract: first/idempotent/shared/non-owner/final/global
|
||||
lifecycle, detached sorted diagnostics, source ownership and bounded timing.
|
||||
- WMO render build step contract: mesh-before-MultiMesh order, one-index cursor
|
||||
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 Resource cache contract: invalid/duplicate request rejection,
|
||||
validated/missing terminal transitions, transient/full reset, detached sorted
|
||||
diagnostics, loader-owned version validation and bounded timing.
|
||||
- WMO scene Resource cache contract: direct missing sources, pending terminal
|
||||
transitions, transient/full reset, detached diagnostics, loader-owned size/
|
||||
metadata validation and bounded timing.
|
||||
- ADT water load pipeline contract: FIFO/dedupe, active task identity, real
|
||||
worker-thread publication, pending-only cancellation, result payload/order,
|
||||
clear/source boundaries and bounded timing.
|
||||
- Budget scheduler contract: exact lane exhaustion, shared chunk removal/create
|
||||
priority, independent lanes, frame reset, invalid limits, terminal cancellation,
|
||||
dependency boundaries and bounded permit timing without loading a world scene.
|
||||
@@ -451,12 +596,35 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
|
||||
|---|---|---|---|
|
||||
| ADT streaming/terrain | Partial | M00 checkpoints and current scenes | Fidelity/performance gaps remain |
|
||||
| Static M2 placement | Partial | MultiMesh/dedupe probes | Full materials/animation/effects |
|
||||
| M2 static batch materializer | Implemented extraction | Synthetic node/Mesh/render/source/timing contract | Asset-backed traversal/visual/GPU p95/p99 pending |
|
||||
| M2 unique placement registry | Implemented extraction | Scene-free ownership/lifecycle/timing contract and historical `uid:11785` smoke | Group/build/tasks/finalization and asset-backed p95/p99 remain pending |
|
||||
| M2 placement transform resolver | Implemented extraction | Scene-free formula/source/timing contract across three consumers | Asset-backed visual recheck and general placement parity pending |
|
||||
| M2 placement grouper | Implemented extraction | Scene-free validation/order/transform/source/timing contract | Worker/build state, spatial cells and asset-backed p95/p99 remain pending |
|
||||
| M2 build batch planner | Implemented extraction | Scene-free limit/count/cursor/source/timing contract | Queue/resource state, spatial cells and asset-backed p95/p99 remain pending |
|
||||
| WMO placement resolver | Implemented extraction | Scene-free path/identity/transform/source/timing contract | Registry/build extraction and asset-backed comparison pending |
|
||||
| M2 build batch planner | Implemented extraction | Scene-free limit/count/cursor/source/timing contract | Spatial cells and asset-backed p95/p99 remain pending |
|
||||
| M2 build dispatch planner | Implemented extraction | Scene-free priority/action/transition/source/timing contract | Resource observation/orchestration and asset-backed traversal remain pending |
|
||||
| M2 build resource snapshot | Implemented extraction | Typed identity/adoption/lifetime/source/timing contract | Resource observation service and asset-backed traversal remain pending |
|
||||
| M2 static build resource observer | Implemented extraction | Cache/pending/path/request/source/timing contract | Asset-backed traversal pending |
|
||||
| M2 cached animation resource observer | Implemented extraction | Cache/pending/policy/GLB/request/source/timing contract | Asset-backed traversal pending |
|
||||
| M2 native animation resource observer | Implemented extraction | Candidate/cache/raw/build/adoption/source/timing contract | Asset-backed traversal/leak/p95/p99 pending |
|
||||
| M2 animation resource finalizer | Implemented extraction | Status/FIFO/load/candidate/repair/adoption/source/timing contract | Asset-backed traversal/leak/p95/p99 pending |
|
||||
| M2 mesh resource finalizer | Implemented extraction | Status/FIFO/load/extract/prepare/adoption/source/timing contract | Asset-backed traversal/leak/p95/p99 pending |
|
||||
| M2 build queue | Implemented extraction | Typed lifecycle/FIFO/progress/lifetime/source/timing contract | Resource dispatch and asset-backed traversal remain pending |
|
||||
| M2 animation load pipeline state | Implemented extraction | Synthetic lifecycle/FIFO/source/timing contract | Asset-backed traversal/leak/animation-fidelity/p95/p99 pending |
|
||||
| M2 animated scene finalizer | Implemented extraction | Synthetic type/lifetime/material/player/source/timing contract | Asset-backed GLB traversal/material/animation comparison pending |
|
||||
| M2 animation playback controller | Implemented extraction | Synthetic phase/selection/loop/native-copy/source/timing contract | Asset-backed animation timing/name/native comparison pending |
|
||||
| M2 animated instance materializer | Implemented extraction | Synthetic order/transform/render/playback/attachment/source/timing contract | Asset-backed traversal/visual/leak/p95/p99 pending |
|
||||
| M2 prototype cache state | Implemented extraction | Synthetic identity/negative/lifecycle/source/timing plus shutdown contract | Asset-backed traversal/leak/p95/p99 pending |
|
||||
| WMO placement resolver | Implemented extraction | Scene-free path/identity/transform/source/timing contract | Asset-backed comparison pending |
|
||||
| WMO placement registry | Implemented extraction | Scene-free ownership/lifecycle/source/timing contract | Build/resource state and asset-backed cross-tile corpus pending |
|
||||
| WMO render build step planner | Implemented extraction | Scene-free order/cursor/source/timing contract | Asset-backed traversal p95/p99 pending |
|
||||
| WMO render build queue | Implemented extraction | Typed lifecycle/order/ownership/source/timing contract | Materialization and asset-backed traversal/leak evidence pending |
|
||||
| WMO render Resource cache state | Implemented extraction | Scene-free lifecycle/exclusivity/source/timing plus shutdown contract | 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 |
|
||||
| Sky/light | Partial | DBC controller/logs | Zone transition paired validation |
|
||||
| Character presentation | Prototype | Character creator/player experiment | Full customization/equipment states |
|
||||
@@ -506,6 +674,32 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
|
||||
| `src/render/entities/world_entity_presenter.gd` | Main-thread entity visual subtree ownership and lifecycle |
|
||||
| `src/render/terrain/rendered_ground_sample.gd` | Immutable renderer-owned available/unavailable ground query result |
|
||||
| `src/render/terrain/terrain_quality_mesh_cache.gd` | Full-quality terrain revisit Mesh retention and LRU ownership |
|
||||
| `src/render/liquid/adt_water_load_pipeline_state.gd` | ADT water request/task/result bookkeeping and worker-safe mailbox |
|
||||
| `src/render/liquid/adt_water_scene_finalizer.gd` | Main-thread ADT water build, tile attachment and optional editor ownership |
|
||||
| `src/render/m2/m2_runtime_mesh_rebuild_classifier.gd` | Billboard/UV-rotation stale cached-mesh rebuild decision and memoization |
|
||||
| `src/render/m2/m2_animation_load_pipeline_state.gd` | Animated M2 pending Resource paths, terminal statuses and finalize FIFO |
|
||||
| `src/render/m2/m2_animation_resource_finalizer.gd` | Cached animation terminal polling/load/finalize outcomes |
|
||||
| `src/render/m2/m2_animated_scene_finalizer.gd` | Animated scene candidate ownership, material repair and player validation |
|
||||
| `src/render/m2/m2_animation_playback_controller.gd` | Per-instance phase, selection, native copy/start and imported playback |
|
||||
| `src/render/m2/m2_animated_instance_materializer.gd` | Animated duplicate/render/playback startup and non-empty batch attachment |
|
||||
| `src/render/m2/m2_static_batch_materializer.gd` | Static MultiMesh construction, render setup and attachment |
|
||||
| `src/render/m2/m2_build_dispatch_planner.gd` | M2 wait/materializer/advance action priority and transition plan |
|
||||
| `src/render/m2/m2_build_resource_snapshot.gd` | Typed per-step animated/static observations and borrowed references |
|
||||
| `src/render/m2/m2_static_build_resource_observer.gd` | Static Mesh lookup/request/missing snapshot production |
|
||||
| `src/render/m2/m2_cached_animation_resource_observer.gd` | Cached animated GLB policy/request/snapshot production |
|
||||
| `src/render/m2/m2_build_job.gd` | M2 root/groups references and group/offset/serial progress |
|
||||
| `src/render/m2/m2_build_queue.gd` | Keyed pending jobs and FIFO/stale tile-key lifecycle |
|
||||
| `src/render/m2/m2_mesh_load_pipeline_state.gd` | Static M2 pending Resource paths, terminal statuses and finalize FIFO |
|
||||
| `src/render/m2/m2_mesh_resource_cache_state.gd` | Prepared static M2 Mesh references and final-shutdown release |
|
||||
| `src/render/m2/m2_mesh_resource_extractor.gd` | First-Mesh selection and temporary PackedScene instance lifetime |
|
||||
| `src/render/m2/m2_mesh_resource_finalizer.gd` | Static terminal polling, Mesh preparation and cache/missing adoption |
|
||||
| `src/render/m2/m2_runtime_mesh_finalizer.gd` | Runtime refresh version, rebuild classification/build and fallback |
|
||||
| `src/render/m2/m2_raw_model_repository.gd` | Stateless static/animated native M2 file boundary |
|
||||
| `src/render/m2/m2_native_animation_resource_observer.gd` | Native candidate/read/build/cache observation |
|
||||
| `src/render/m2/m2_prototype_cache_state.gd` | Detached prototype ownership and negative lookup outcomes |
|
||||
| `src/render/wmo/wmo_render_resource_finalizer.gd` | Lightweight WMO terminal polling, validation and publication |
|
||||
| `src/render/wmo/wmo_scene_resource_finalizer.gd` | Cached WMO terminal polling, probe validation/lifetime and publication |
|
||||
| `src/render/wmo/wmo_runtime_mesh_finalizer.gd` | Cached WMO runtime refresh admission, surface iteration and material reconstruction |
|
||||
| `src/render/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 |
|
||||
@@ -518,6 +712,31 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
|
||||
| `src/tools/verify_world_entity_presentation.gd` | Entity snapshot, create/update/replace/remove and ownership regression |
|
||||
| `src/tools/verify_rendered_ground_sample.gd` | Cold-start renderer ground result invariant regression |
|
||||
| `src/tools/verify_terrain_quality_mesh_cache.gd` | Terrain cache admission, LRU, ownership and loader-boundary regression |
|
||||
| `src/tools/verify_adt_water_load_pipeline_state.gd` | ADT water FIFO/task/thread/boundary/timing regression |
|
||||
| `src/tools/verify_adt_water_scene_finalizer.gd` | Synthetic ADT water scene/ownership/boundary/timing regression |
|
||||
| `src/tools/verify_m2_runtime_mesh_rebuild_classifier.gd` | M2 rebuild predicate/cache/boundary/timing regression |
|
||||
| `src/tools/verify_m2_animation_load_pipeline_state.gd` | Animated M2 request/terminal/FIFO/boundary/timing regression |
|
||||
| `src/tools/verify_m2_animation_resource_finalizer.gd` | Animated M2 polling/load/finalize/adoption/boundary/timing regression |
|
||||
| `src/tools/verify_m2_mesh_resource_finalizer.gd` | Static M2 polling/extraction/preparation/adoption/boundary/timing regression |
|
||||
| `src/tools/verify_m2_animated_scene_finalizer.gd` | Animated M2 candidate/material/player/lifetime/boundary/timing regression |
|
||||
| `src/tools/verify_m2_animation_playback_controller.gd` | Animated M2 phase/selection/playback/native/boundary/timing regression |
|
||||
| `src/tools/verify_m2_animated_instance_materializer.gd` | Animated M2 order/render/playback/attachment/boundary/timing regression |
|
||||
| `src/tools/verify_m2_static_batch_materializer.gd` | Static M2 node/Mesh/render/attachment/boundary/timing regression |
|
||||
| `src/tools/verify_m2_build_dispatch_planner.gd` | M2 dispatch priority/action/transition/boundary/timing regression |
|
||||
| `src/tools/verify_m2_build_resource_snapshot.gd` | M2 observation identity/adoption/lifetime/boundary/timing regression |
|
||||
| `src/tools/verify_m2_static_build_resource_observer.gd` | Static lookup/request/path/boundary/timing regression |
|
||||
| `src/tools/verify_m2_cached_animation_resource_observer.gd` | Cached animation lifecycle/policy/GLB/boundary/timing regression |
|
||||
| `src/tools/verify_m2_build_queue.gd` | M2 job/FIFO/progress/lifetime/boundary/timing regression |
|
||||
| `src/tools/verify_m2_mesh_load_pipeline_state.gd` | Static M2 request/terminal/FIFO/boundary/timing regression |
|
||||
| `src/tools/verify_m2_mesh_resource_cache_state.gd` | Static M2 Mesh cache ownership/lifetime/boundary/timing regression |
|
||||
| `src/tools/verify_m2_mesh_resource_extractor.gd` | M2 direct/PackedScene/subtree order/lifetime/boundary/timing regression |
|
||||
| `src/tools/verify_m2_runtime_mesh_finalizer.gd` | M2 current/stale/rebuild/fallback/boundary/timing regression |
|
||||
| `src/tools/verify_m2_raw_model_repository.gd` | M2 invalid/missing/native-boundary/dependency/timing regression |
|
||||
| `src/tools/verify_m2_native_animation_resource_observer.gd` | Native candidate/cache/raw/build/adoption/boundary/timing regression |
|
||||
| `src/tools/verify_m2_prototype_cache_state.gd` | M2 prototype identity/negative/lifecycle/boundary/timing regression |
|
||||
| `src/tools/verify_wmo_render_resource_finalizer.gd` | WMO render status/order/validation/adoption/boundary/timing regression |
|
||||
| `src/tools/verify_wmo_scene_resource_finalizer.gd` | WMO scene status/order/probe/lifetime/adoption/boundary/timing regression |
|
||||
| `src/tools/verify_wmo_runtime_mesh_finalizer.gd` | WMO Mesh identity/version/material-definition/boundary/timing regression |
|
||||
| `src/tools/verify_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 |
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
class_name AdtWaterLoadPipelineState
|
||||
extends RefCounted
|
||||
|
||||
## Owns ADT water request, active-task and worker-result bookkeeping. The caller
|
||||
## owns WorkerThreadPool operations, ADT parsing, permits and scene finalization.
|
||||
|
||||
var _pending_requests: Array[Dictionary] = []
|
||||
var _queued_tile_keys: Dictionary = {}
|
||||
var _active_task_ids_by_tile_key: Dictionary = {}
|
||||
var _result_mutex := Mutex.new()
|
||||
var _results: Array[Dictionary] = []
|
||||
|
||||
|
||||
## Appends one tile/path request in FIFO order. Invalid, queued or active tile
|
||||
## keys are rejected without mutation.
|
||||
func enqueue_request(tile_key: String, adt_path: String) -> bool:
|
||||
if tile_key.is_empty() or adt_path.is_empty():
|
||||
return false
|
||||
if _queued_tile_keys.has(tile_key) or _active_task_ids_by_tile_key.has(tile_key):
|
||||
return false
|
||||
_pending_requests.append({
|
||||
"key": tile_key,
|
||||
"path": adt_path,
|
||||
})
|
||||
_queued_tile_keys[tile_key] = true
|
||||
return true
|
||||
|
||||
|
||||
## Removes and returns the oldest request, also releasing its queued-key marker.
|
||||
## An empty Dictionary means no pending request exists.
|
||||
func take_next_request() -> Dictionary:
|
||||
if _pending_requests.is_empty():
|
||||
return {}
|
||||
var request: Dictionary = _pending_requests.pop_front()
|
||||
_queued_tile_keys.erase(String(request.get("key", "")))
|
||||
return request
|
||||
|
||||
|
||||
## Removes all pending requests for one tile without affecting active work.
|
||||
## Returns the number of removed FIFO entries.
|
||||
func cancel_pending_requests(tile_key: String) -> int:
|
||||
if tile_key.is_empty():
|
||||
return 0
|
||||
_queued_tile_keys.erase(tile_key)
|
||||
var removed_count := 0
|
||||
for request_index in range(_pending_requests.size() - 1, -1, -1):
|
||||
var request: Dictionary = _pending_requests[request_index]
|
||||
if String(request.get("key", "")) != tile_key:
|
||||
continue
|
||||
_pending_requests.remove_at(request_index)
|
||||
removed_count += 1
|
||||
return removed_count
|
||||
|
||||
|
||||
## Records one caller-started worker task. The opaque task ID is retained until
|
||||
## the caller waits for and completes it.
|
||||
func remember_active_task(tile_key: String, task_id: int) -> bool:
|
||||
if tile_key.is_empty() or _active_task_ids_by_tile_key.has(tile_key):
|
||||
return false
|
||||
_active_task_ids_by_tile_key[tile_key] = task_id
|
||||
return true
|
||||
|
||||
|
||||
## Returns whether a worker task is active for the tile.
|
||||
func has_active_task(tile_key: String) -> bool:
|
||||
return not tile_key.is_empty() and _active_task_ids_by_tile_key.has(tile_key)
|
||||
|
||||
|
||||
## Returns the opaque active task ID, or -1 for an unknown tile.
|
||||
func active_task_id_for(tile_key: String) -> int:
|
||||
return int(_active_task_ids_by_tile_key.get(tile_key, -1))
|
||||
|
||||
|
||||
## Removes and returns one completed task ID, or -1 for an unknown tile.
|
||||
func complete_active_task(tile_key: String) -> int:
|
||||
if tile_key.is_empty() or not _active_task_ids_by_tile_key.has(tile_key):
|
||||
return -1
|
||||
var task_id := int(_active_task_ids_by_tile_key[tile_key])
|
||||
_active_task_ids_by_tile_key.erase(tile_key)
|
||||
return task_id
|
||||
|
||||
|
||||
## Returns a detached snapshot of opaque task IDs for orderly shutdown waiting.
|
||||
func active_task_ids_snapshot() -> Array:
|
||||
return _active_task_ids_by_tile_key.values().duplicate()
|
||||
|
||||
|
||||
## Publishes one parsed ADT result from a worker thread. Payload identity is
|
||||
## retained; the caller must not mutate it after publication.
|
||||
func publish_result(tile_key: String, adt_path: String, water_data: Dictionary) -> bool:
|
||||
if tile_key.is_empty() or adt_path.is_empty():
|
||||
return false
|
||||
_result_mutex.lock()
|
||||
_results.append({
|
||||
"key": tile_key,
|
||||
"path": adt_path,
|
||||
"data": water_data,
|
||||
})
|
||||
_result_mutex.unlock()
|
||||
return true
|
||||
|
||||
|
||||
## Returns whether a worker result is currently ready.
|
||||
func has_result() -> bool:
|
||||
_result_mutex.lock()
|
||||
var result_available := not _results.is_empty()
|
||||
_result_mutex.unlock()
|
||||
return result_available
|
||||
|
||||
|
||||
## Removes and returns the oldest worker result. An empty Dictionary means no
|
||||
## result was ready. Intended for the serialized renderer main thread.
|
||||
func pop_result() -> Dictionary:
|
||||
_result_mutex.lock()
|
||||
var result: Dictionary = {}
|
||||
if not _results.is_empty():
|
||||
result = _results.pop_front()
|
||||
_result_mutex.unlock()
|
||||
return result
|
||||
|
||||
|
||||
## Returns pending plus active load count for existing renderer metrics.
|
||||
func total_load_count() -> int:
|
||||
return _pending_requests.size() + _active_task_ids_by_tile_key.size()
|
||||
|
||||
|
||||
## Returns the pending request count.
|
||||
func pending_request_count() -> int:
|
||||
return _pending_requests.size()
|
||||
|
||||
|
||||
## Returns the active worker-task count.
|
||||
func active_task_count() -> int:
|
||||
return _active_task_ids_by_tile_key.size()
|
||||
|
||||
|
||||
## Clears bookkeeping and queued results without interrupting worker tasks.
|
||||
## Orderly shutdown must wait the task-ID snapshot before calling this method.
|
||||
func clear() -> void:
|
||||
_pending_requests.clear()
|
||||
_queued_tile_keys.clear()
|
||||
_active_task_ids_by_tile_key.clear()
|
||||
_result_mutex.lock()
|
||||
_results.clear()
|
||||
_result_mutex.unlock()
|
||||
|
||||
|
||||
## Returns detached FIFO/task/result scalar diagnostics without water payloads.
|
||||
func diagnostic_snapshot() -> Dictionary:
|
||||
var pending_requests: Array[Dictionary] = []
|
||||
for request in _pending_requests:
|
||||
pending_requests.append(request.duplicate())
|
||||
var active_tile_keys: Array = _active_task_ids_by_tile_key.keys()
|
||||
active_tile_keys.sort()
|
||||
_result_mutex.lock()
|
||||
var result_records: Array[Dictionary] = []
|
||||
for result in _results:
|
||||
result_records.append({
|
||||
"key": String(result.get("key", "")),
|
||||
"path": String(result.get("path", "")),
|
||||
})
|
||||
_result_mutex.unlock()
|
||||
return {
|
||||
"pending_requests": pending_requests,
|
||||
"active_tile_keys": active_tile_keys.duplicate(),
|
||||
"result_records": result_records,
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://pwqm3n25pnn7
|
||||
@@ -0,0 +1,40 @@
|
||||
class_name AdtWaterSceneFinalizer
|
||||
extends RefCounted
|
||||
|
||||
## Builds and attaches one ADT water subtree on the renderer main thread. The
|
||||
## supplied tile root owns the returned subtree; this service retains no state.
|
||||
|
||||
const ADT_BUILDER_SCRIPT := preload("res://addons/mpq_extractor/loaders/adt_builder.gd")
|
||||
|
||||
|
||||
## Builds water from parsed ADT data and attaches it once to [param tile_root].
|
||||
## [param tile_origin_godot_units] is the existing Godot-world tile origin used
|
||||
## by [ADTBuilder]. When [param editor_owner] is non-null and is an ancestor,
|
||||
## ownership is assigned recursively for persisted Editor previews. Returns the
|
||||
## attached borrowed root, or null for empty/invalid input or dry ADT data.
|
||||
## This method mutates the SceneTree and must run on the main thread.
|
||||
func attach_water_scene(
|
||||
water_data: Dictionary,
|
||||
tile_origin_godot_units: Vector3,
|
||||
tile_root: Node,
|
||||
editor_owner: Node = null) -> Node3D:
|
||||
if water_data.is_empty() or tile_root == null or not is_instance_valid(tile_root):
|
||||
return null
|
||||
|
||||
var water_root: Node3D = ADT_BUILDER_SCRIPT.build_tile_water_scene(
|
||||
water_data,
|
||||
tile_origin_godot_units
|
||||
)
|
||||
if water_root == null:
|
||||
return null
|
||||
|
||||
tile_root.add_child(water_root)
|
||||
if editor_owner != null and is_instance_valid(editor_owner):
|
||||
_assign_owner_recursive(water_root, editor_owner)
|
||||
return water_root
|
||||
|
||||
|
||||
func _assign_owner_recursive(generated_node: Node, editor_owner: Node) -> void:
|
||||
generated_node.owner = editor_owner
|
||||
for child in generated_node.get_children():
|
||||
_assign_owner_recursive(child, editor_owner)
|
||||
@@ -0,0 +1 @@
|
||||
uid://ck4sdc8a3kg00
|
||||
@@ -0,0 +1,120 @@
|
||||
class_name M2AnimatedInstanceMaterializer
|
||||
extends RefCounted
|
||||
|
||||
## Duplicates one ordered animated M2 batch, applies instance render settings,
|
||||
## starts playback and attaches a non-empty batch to the supplied parent.
|
||||
## All calls mutate Godot scene objects and therefore belong on the main thread.
|
||||
|
||||
const M2_ANIMATED_SCENE_FINALIZER_SCRIPT := preload(
|
||||
"res://src/render/m2/m2_animated_scene_finalizer.gd"
|
||||
)
|
||||
const M2_ANIMATION_PLAYBACK_CONTROLLER_SCRIPT := preload(
|
||||
"res://src/render/m2/m2_animation_playback_controller.gd"
|
||||
)
|
||||
|
||||
var _animated_scene_finalizer := M2_ANIMATED_SCENE_FINALIZER_SCRIPT.new()
|
||||
var _animation_playback_controller := M2_ANIMATION_PLAYBACK_CONTROLLER_SCRIPT.new()
|
||||
|
||||
|
||||
## Returns the attached batch root and detached diagnostic entries. Invalid or
|
||||
## empty input, and a batch whose duplicates all fail, returns an empty result.
|
||||
## The caller owns the returned/attached batch through `m2_parent_root`.
|
||||
func materialize_batch(
|
||||
m2_parent_root: Node3D,
|
||||
relative_path: String,
|
||||
prototype: Node3D,
|
||||
transforms: Array,
|
||||
start_index: int,
|
||||
instance_count: int,
|
||||
batch_serial: int,
|
||||
visibility_range_end: float,
|
||||
visibility_range_margin: float,
|
||||
cast_shadows: bool,
|
||||
native_animator_script: Script,
|
||||
collect_native_diagnostics: bool
|
||||
) -> Dictionary:
|
||||
if (
|
||||
m2_parent_root == null
|
||||
or transforms.is_empty()
|
||||
or instance_count <= 0
|
||||
or prototype == null
|
||||
):
|
||||
return {}
|
||||
|
||||
var model_name := relative_path.get_file().get_basename()
|
||||
var batch_root := Node3D.new()
|
||||
batch_root.name = "%s_anim_%d" % [model_name, batch_serial]
|
||||
var diagnostic_entries: Array[Dictionary] = []
|
||||
for batch_offset in instance_count:
|
||||
var instance_index := start_index + batch_offset
|
||||
var instance := prototype.duplicate(
|
||||
Node.DUPLICATE_SIGNALS | Node.DUPLICATE_GROUPS | Node.DUPLICATE_SCRIPTS
|
||||
) as Node3D
|
||||
if instance == null:
|
||||
continue
|
||||
instance.name = "%s_%d" % [model_name, instance_index]
|
||||
instance.transform = transforms[instance_index]
|
||||
_apply_visibility_range_recursive(
|
||||
instance,
|
||||
visibility_range_end,
|
||||
visibility_range_margin
|
||||
)
|
||||
_apply_shadow_cast_recursive(instance, cast_shadows)
|
||||
_animation_playback_controller.copy_native_animator_data(
|
||||
prototype,
|
||||
instance,
|
||||
native_animator_script
|
||||
)
|
||||
batch_root.add_child(instance)
|
||||
var animation_players := _animated_scene_finalizer.animation_players_in_subtree(
|
||||
instance
|
||||
)
|
||||
var native_diagnostics := _animation_playback_controller.start_instance_playback(
|
||||
instance,
|
||||
relative_path,
|
||||
instance_index,
|
||||
native_animator_script,
|
||||
animation_players,
|
||||
collect_native_diagnostics
|
||||
)
|
||||
for native_diagnostic in native_diagnostics:
|
||||
diagnostic_entries.append({
|
||||
"instance_index": instance_index,
|
||||
"state": native_diagnostic,
|
||||
})
|
||||
|
||||
if batch_root.get_child_count() <= 0:
|
||||
batch_root.queue_free()
|
||||
return {}
|
||||
m2_parent_root.add_child(batch_root)
|
||||
return {
|
||||
"batch_root": batch_root,
|
||||
"native_diagnostics": diagnostic_entries,
|
||||
}
|
||||
|
||||
|
||||
func _apply_visibility_range_recursive(
|
||||
node: Node,
|
||||
range_end: float,
|
||||
range_end_margin: float
|
||||
) -> void:
|
||||
if range_end <= 0.0:
|
||||
return
|
||||
if node is GeometryInstance3D:
|
||||
var geometry := node as GeometryInstance3D
|
||||
geometry.visibility_range_end = range_end
|
||||
geometry.visibility_range_end_margin = range_end_margin
|
||||
for child in node.get_children():
|
||||
_apply_visibility_range_recursive(child, range_end, range_end_margin)
|
||||
|
||||
|
||||
func _apply_shadow_cast_recursive(node: Node, cast_shadows: bool) -> void:
|
||||
if node is GeometryInstance3D:
|
||||
var geometry := node as GeometryInstance3D
|
||||
geometry.cast_shadow = (
|
||||
GeometryInstance3D.SHADOW_CASTING_SETTING_ON
|
||||
if cast_shadows
|
||||
else GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
|
||||
)
|
||||
for child in node.get_children():
|
||||
_apply_shadow_cast_recursive(child, cast_shadows)
|
||||
@@ -0,0 +1 @@
|
||||
uid://dkgy3rvsmlkvm
|
||||
@@ -0,0 +1,115 @@
|
||||
class_name M2AnimatedSceneFinalizer
|
||||
extends RefCounted
|
||||
|
||||
## Instantiates and validates detached animated M2 scene candidates and repairs
|
||||
## their surface materials from an existing static material prototype.
|
||||
## The caller owns ResourceLoader I/O, permits and prototype cache adoption.
|
||||
|
||||
|
||||
## Instantiates one PackedScene as a detached Node3D candidate. Unsupported
|
||||
## Resources return null; a created non-Node3D root is freed before return.
|
||||
func instantiate_candidate(resource: Resource) -> Node3D:
|
||||
if not (resource is PackedScene):
|
||||
return null
|
||||
var candidate_root := (resource as PackedScene).instantiate()
|
||||
if candidate_root is Node3D:
|
||||
return candidate_root as Node3D
|
||||
if candidate_root != null:
|
||||
candidate_root.free()
|
||||
return null
|
||||
|
||||
|
||||
## Applies the historical depth-first source-to-target material mapping. A null
|
||||
## source or target leaves the candidate unchanged.
|
||||
func repair_materials(animated_root: Node3D, material_source_root: Node3D) -> void:
|
||||
if animated_root == null or material_source_root == null:
|
||||
return
|
||||
var source_meshes := mesh_instances_in_subtree(material_source_root)
|
||||
var target_meshes := mesh_instances_in_subtree(animated_root)
|
||||
if source_meshes.is_empty() or target_meshes.is_empty():
|
||||
return
|
||||
|
||||
var fallback_material := _first_mesh_material(source_meshes)
|
||||
for target_index in target_meshes.size():
|
||||
var target := target_meshes[target_index]
|
||||
if target == null or target.mesh == null:
|
||||
continue
|
||||
var source := source_meshes[mini(target_index, source_meshes.size() - 1)]
|
||||
for surface_index in target.mesh.get_surface_count():
|
||||
var material := _mesh_instance_surface_material(source, surface_index)
|
||||
if material == null:
|
||||
material = fallback_material
|
||||
if material != null:
|
||||
target.set_surface_override_material(surface_index, material)
|
||||
|
||||
|
||||
## Accepts a detached candidate only when it contains an AnimationPlayer.
|
||||
## Rejected candidates are freed synchronously. A successful result transfers
|
||||
## the exact prototype reference and its AnimationPlayer count to the caller.
|
||||
func finalize_candidate(candidate_root: Node3D) -> Dictionary:
|
||||
if candidate_root == null:
|
||||
return {}
|
||||
var animation_players := animation_players_in_subtree(candidate_root)
|
||||
if animation_players.is_empty():
|
||||
candidate_root.free()
|
||||
return {}
|
||||
return {
|
||||
"prototype": candidate_root,
|
||||
"animation_player_count": animation_players.size(),
|
||||
}
|
||||
|
||||
|
||||
## Returns MeshInstance3D descendants in depth-first preorder, including root.
|
||||
func mesh_instances_in_subtree(root: Node) -> Array[MeshInstance3D]:
|
||||
var mesh_instances: Array[MeshInstance3D] = []
|
||||
if root != null:
|
||||
_collect_mesh_instances(root, mesh_instances)
|
||||
return mesh_instances
|
||||
|
||||
|
||||
## Returns AnimationPlayer descendants in depth-first preorder, including root.
|
||||
func animation_players_in_subtree(root: Node) -> Array[AnimationPlayer]:
|
||||
var animation_players: Array[AnimationPlayer] = []
|
||||
if root != null:
|
||||
_collect_animation_players(root, animation_players)
|
||||
return animation_players
|
||||
|
||||
|
||||
func _collect_mesh_instances(node: Node, result: Array[MeshInstance3D]) -> void:
|
||||
if node is MeshInstance3D:
|
||||
result.append(node as MeshInstance3D)
|
||||
for child in node.get_children():
|
||||
_collect_mesh_instances(child, result)
|
||||
|
||||
|
||||
func _collect_animation_players(node: Node, result: Array[AnimationPlayer]) -> void:
|
||||
if node is AnimationPlayer:
|
||||
result.append(node as AnimationPlayer)
|
||||
for child in node.get_children():
|
||||
_collect_animation_players(child, result)
|
||||
|
||||
|
||||
func _first_mesh_material(meshes: Array[MeshInstance3D]) -> Material:
|
||||
for mesh_instance in meshes:
|
||||
if mesh_instance == null or mesh_instance.mesh == null:
|
||||
continue
|
||||
for surface_index in mesh_instance.mesh.get_surface_count():
|
||||
var material := _mesh_instance_surface_material(mesh_instance, surface_index)
|
||||
if material != null:
|
||||
return material
|
||||
return null
|
||||
|
||||
|
||||
func _mesh_instance_surface_material(
|
||||
mesh_instance: MeshInstance3D,
|
||||
surface_index: int
|
||||
) -> Material:
|
||||
if mesh_instance == null or mesh_instance.mesh == null:
|
||||
return null
|
||||
if surface_index < mesh_instance.get_surface_override_material_count():
|
||||
var override_material := mesh_instance.get_surface_override_material(surface_index)
|
||||
if override_material != null:
|
||||
return override_material
|
||||
if surface_index < mesh_instance.mesh.get_surface_count():
|
||||
return mesh_instance.mesh.surface_get_material(surface_index)
|
||||
return null
|
||||
@@ -0,0 +1 @@
|
||||
uid://cjjim3v675w66
|
||||
@@ -0,0 +1,102 @@
|
||||
class_name M2AnimationLoadPipelineState
|
||||
extends RefCounted
|
||||
|
||||
## Owns animated M2 threaded-load request records and the terminal finalize FIFO.
|
||||
## The caller owns ResourceLoader I/O, permits, Nodes and prototype outcomes.
|
||||
|
||||
var _request_by_normalized_path: Dictionary = {}
|
||||
var _finalize_records: Array[Dictionary] = []
|
||||
|
||||
|
||||
## Records one normalized M2 path and ResourceLoader path. Empty or duplicate
|
||||
## values are rejected without mutation.
|
||||
func remember_request(normalized_relative_path: String, resource_path: String) -> bool:
|
||||
if normalized_relative_path.is_empty() or resource_path.is_empty():
|
||||
return false
|
||||
if _request_by_normalized_path.has(normalized_relative_path):
|
||||
return false
|
||||
_request_by_normalized_path[normalized_relative_path] = {
|
||||
"normalized": normalized_relative_path,
|
||||
"path": resource_path,
|
||||
}
|
||||
return true
|
||||
|
||||
|
||||
## Returns whether one normalized path currently has a pending threaded request.
|
||||
func has_request(normalized_relative_path: String) -> bool:
|
||||
return (
|
||||
not normalized_relative_path.is_empty()
|
||||
and _request_by_normalized_path.has(normalized_relative_path)
|
||||
)
|
||||
|
||||
|
||||
## Returns detached pending records in their existing Dictionary insertion order.
|
||||
func request_records_snapshot() -> Array[Dictionary]:
|
||||
var records: Array[Dictionary] = []
|
||||
for request_variant in _request_by_normalized_path.values():
|
||||
var request: Dictionary = request_variant
|
||||
records.append(request.duplicate())
|
||||
return records
|
||||
|
||||
|
||||
## Removes one pending request and appends its terminal status to the finalize
|
||||
## FIFO. Unknown paths are rejected without mutation.
|
||||
func complete_request(normalized_relative_path: String, terminal_status: int) -> bool:
|
||||
if not _request_by_normalized_path.has(normalized_relative_path):
|
||||
return false
|
||||
var request: Dictionary = _request_by_normalized_path[normalized_relative_path]
|
||||
_request_by_normalized_path.erase(normalized_relative_path)
|
||||
var finalize_record := request.duplicate()
|
||||
finalize_record["status"] = terminal_status
|
||||
_finalize_records.append(finalize_record)
|
||||
return true
|
||||
|
||||
|
||||
## Removes one pending request without enqueuing finalization.
|
||||
func discard_request(normalized_relative_path: String) -> bool:
|
||||
return _request_by_normalized_path.erase(normalized_relative_path)
|
||||
|
||||
|
||||
## Returns whether a terminal record is waiting for budgeted finalization.
|
||||
func has_finalize_record() -> bool:
|
||||
return not _finalize_records.is_empty()
|
||||
|
||||
|
||||
## Removes and returns the oldest terminal record. Empty means none is ready.
|
||||
func pop_finalize_record() -> Dictionary:
|
||||
if _finalize_records.is_empty():
|
||||
return {}
|
||||
return _finalize_records.pop_front()
|
||||
|
||||
|
||||
## Returns pending plus terminal-finalize work for existing renderer metrics.
|
||||
func total_work_count() -> int:
|
||||
return _request_by_normalized_path.size() + _finalize_records.size()
|
||||
|
||||
|
||||
## Returns the pending request count.
|
||||
func pending_request_count() -> int:
|
||||
return _request_by_normalized_path.size()
|
||||
|
||||
|
||||
## Returns the terminal finalize FIFO count.
|
||||
func finalize_record_count() -> int:
|
||||
return _finalize_records.size()
|
||||
|
||||
|
||||
## Clears request/finalize bookkeeping. The caller must drain ResourceLoader
|
||||
## requests before orderly shutdown clear.
|
||||
func clear() -> void:
|
||||
_request_by_normalized_path.clear()
|
||||
_finalize_records.clear()
|
||||
|
||||
|
||||
## Returns detached request and finalize records without loaded Resources/Nodes.
|
||||
func diagnostic_snapshot() -> Dictionary:
|
||||
var finalize_records: Array[Dictionary] = []
|
||||
for record in _finalize_records:
|
||||
finalize_records.append(record.duplicate())
|
||||
return {
|
||||
"requests": request_records_snapshot(),
|
||||
"finalize_records": finalize_records,
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://h5rk6njgaujd
|
||||
@@ -0,0 +1,114 @@
|
||||
class_name M2AnimationPlaybackController
|
||||
extends RefCounted
|
||||
|
||||
## Applies deterministic playback to one animated M2 instance and copies the
|
||||
## runtime fields of exact-script native animators. The materializer owns
|
||||
## instance duplication/attachment; its caller owns diagnostic log formatting.
|
||||
|
||||
|
||||
## Copies the five historical runtime fields between matching native animators
|
||||
## in depth-first order. References are intentionally retained exactly.
|
||||
func copy_native_animator_data(
|
||||
source_root: Node,
|
||||
target_root: Node,
|
||||
native_animator_script: Script
|
||||
) -> void:
|
||||
var source_animators := native_animators_in_subtree(source_root, native_animator_script)
|
||||
var target_animators := native_animators_in_subtree(target_root, native_animator_script)
|
||||
for animator_index in range(mini(source_animators.size(), target_animators.size())):
|
||||
var source := source_animators[animator_index]
|
||||
var target := target_animators[animator_index]
|
||||
target.mesh_instance_path = source.mesh_instance_path
|
||||
target.bones = source.bones
|
||||
target.surfaces = source.surfaces
|
||||
target.animation_length = source.animation_length
|
||||
target.playback_speed = source.playback_speed
|
||||
|
||||
|
||||
## Prepares native animators, applies deterministic phase, and starts supplied
|
||||
## AnimationPlayers with the historical loop/name/seek rules. Runtime debug
|
||||
## state is sampled only when requested and returned as detached Dictionaries.
|
||||
func start_instance_playback(
|
||||
root: Node,
|
||||
relative_path: String,
|
||||
instance_index: int,
|
||||
native_animator_script: Script,
|
||||
animation_players: Array[AnimationPlayer],
|
||||
collect_native_diagnostics: bool
|
||||
) -> Array[Dictionary]:
|
||||
var phase := phase_for_instance(relative_path, instance_index)
|
||||
var native_diagnostics: Array[Dictionary] = []
|
||||
for animator in native_animators_in_subtree(root, native_animator_script):
|
||||
if animator.has_method("prepare_runtime"):
|
||||
animator.prepare_runtime()
|
||||
animator.set_phase(phase)
|
||||
if collect_native_diagnostics and animator.has_method("runtime_debug_state"):
|
||||
var diagnostic_variant = animator.runtime_debug_state()
|
||||
if diagnostic_variant is Dictionary:
|
||||
native_diagnostics.append((diagnostic_variant as Dictionary).duplicate(true))
|
||||
|
||||
for player in animation_players:
|
||||
if player == null:
|
||||
continue
|
||||
var animation_name := choose_default_animation(player, relative_path)
|
||||
if animation_name.is_empty():
|
||||
continue
|
||||
for available_name in player.get_animation_list():
|
||||
var available_animation := player.get_animation(available_name)
|
||||
if available_animation != null:
|
||||
available_animation.loop_mode = Animation.LOOP_LINEAR
|
||||
player.play(animation_name)
|
||||
var selected_animation := player.get_animation(animation_name)
|
||||
if selected_animation != null and selected_animation.length > 0.0:
|
||||
player.seek(selected_animation.length * phase, true)
|
||||
return native_diagnostics
|
||||
|
||||
|
||||
## Returns exact-script native animators in depth-first preorder.
|
||||
func native_animators_in_subtree(
|
||||
root: Node,
|
||||
native_animator_script: Script
|
||||
) -> Array[Node]:
|
||||
var native_animators: Array[Node] = []
|
||||
if root != null and native_animator_script != null:
|
||||
_collect_native_animators(root, native_animator_script, native_animators)
|
||||
return native_animators
|
||||
|
||||
|
||||
## Returns the existing stable path/index phase in the inclusive-lower,
|
||||
## exclusive-upper range [0, 1).
|
||||
func phase_for_instance(relative_path: String, instance_index: int) -> float:
|
||||
return float(abs(("%s:%d" % [relative_path, instance_index]).hash()) % 1000) / 1000.0
|
||||
|
||||
|
||||
## Chooses the historical default animation name for ordinary, fish and bird
|
||||
## paths, then case-insensitive substring and first-name fallbacks.
|
||||
func choose_default_animation(player: AnimationPlayer, relative_path: String = "") -> String:
|
||||
if player == null:
|
||||
return ""
|
||||
var lower_path := relative_path.to_lower()
|
||||
var candidates: Array[String] = ["Stand", "Idle", "Run", "Walk"]
|
||||
if lower_path.contains("fish"):
|
||||
candidates = ["Run", "Walk", "Swim", "Stand", "Idle", "Death"]
|
||||
elif lower_path.contains("eagle") or lower_path.contains("bird") or lower_path.contains("gull"):
|
||||
candidates = ["Run", "Walk", "Swim", "Stand", "Idle"]
|
||||
for candidate in candidates:
|
||||
if player.has_animation(candidate):
|
||||
return candidate
|
||||
var available_names := player.get_animation_list()
|
||||
for candidate in ["run", "walk", "swim", "stand", "idle", "loop"]:
|
||||
for available_name in available_names:
|
||||
if String(available_name).to_lower().contains(candidate):
|
||||
return String(available_name)
|
||||
return String(available_names[0]) if not available_names.is_empty() else ""
|
||||
|
||||
|
||||
func _collect_native_animators(
|
||||
node: Node,
|
||||
native_animator_script: Script,
|
||||
result: Array[Node]
|
||||
) -> void:
|
||||
if node.get_script() == native_animator_script:
|
||||
result.append(node)
|
||||
for child in node.get_children():
|
||||
_collect_native_animators(child, native_animator_script, result)
|
||||
@@ -0,0 +1 @@
|
||||
uid://c6oln217sml80
|
||||
@@ -0,0 +1,187 @@
|
||||
class_name M2AnimationResourceFinalizer
|
||||
extends RefCounted
|
||||
|
||||
## Polls cached animated M2 ResourceLoader requests and finalizes terminal scenes.
|
||||
## The caller owns permits, material-source lookup and SceneTree materialization.
|
||||
|
||||
const M2_ANIMATED_SCENE_FINALIZER_SCRIPT := preload(
|
||||
"res://src/render/m2/m2_animated_scene_finalizer.gd"
|
||||
)
|
||||
|
||||
var _animated_scene_finalizer: RefCounted
|
||||
var _resource_loader_adapter: Object
|
||||
|
||||
|
||||
func _init(
|
||||
animated_scene_finalizer: RefCounted = null,
|
||||
resource_loader_adapter: Object = null
|
||||
) -> void:
|
||||
_animated_scene_finalizer = animated_scene_finalizer
|
||||
if _animated_scene_finalizer == null:
|
||||
_animated_scene_finalizer = M2_ANIMATED_SCENE_FINALIZER_SCRIPT.new()
|
||||
_resource_loader_adapter = resource_loader_adapter
|
||||
|
||||
|
||||
## Moves terminal threaded requests into the pipeline finalize FIFO.
|
||||
## Empty resource paths retain the historical immediate static-only outcome.
|
||||
func poll_terminal_requests(
|
||||
load_pipeline_state: RefCounted,
|
||||
prototype_cache_state: RefCounted
|
||||
) -> int:
|
||||
if load_pipeline_state == null or prototype_cache_state == null:
|
||||
return 0
|
||||
var completed_request_count := 0
|
||||
var request_records: Array = load_pipeline_state.call(
|
||||
"request_records_snapshot"
|
||||
)
|
||||
for request_variant in request_records:
|
||||
if not (request_variant is Dictionary):
|
||||
continue
|
||||
var request: Dictionary = request_variant
|
||||
var normalized_relative_path := String(request.get("normalized", ""))
|
||||
var resource_path := String(request.get("path", ""))
|
||||
if resource_path.is_empty():
|
||||
load_pipeline_state.call(
|
||||
"discard_request",
|
||||
normalized_relative_path
|
||||
)
|
||||
prototype_cache_state.call(
|
||||
"mark_animation_static",
|
||||
normalized_relative_path
|
||||
)
|
||||
completed_request_count += 1
|
||||
continue
|
||||
var load_status := load_threaded_get_status(resource_path)
|
||||
if (
|
||||
load_status != ResourceLoader.THREAD_LOAD_LOADED
|
||||
and load_status != ResourceLoader.THREAD_LOAD_FAILED
|
||||
):
|
||||
continue
|
||||
load_pipeline_state.call(
|
||||
"complete_request",
|
||||
normalized_relative_path,
|
||||
load_status
|
||||
)
|
||||
completed_request_count += 1
|
||||
return completed_request_count
|
||||
|
||||
|
||||
## Pops one terminal record and prepares a detached animated scene candidate.
|
||||
## An empty result means the record was skipped or resolved as static-only.
|
||||
func prepare_next_candidate(
|
||||
load_pipeline_state: RefCounted,
|
||||
prototype_cache_state: RefCounted
|
||||
) -> Dictionary:
|
||||
if load_pipeline_state == null or prototype_cache_state == null:
|
||||
return {}
|
||||
var terminal_record: Dictionary = load_pipeline_state.call(
|
||||
"pop_finalize_record"
|
||||
)
|
||||
if terminal_record.is_empty():
|
||||
return {}
|
||||
var normalized_relative_path := String(
|
||||
terminal_record.get("normalized", "")
|
||||
)
|
||||
if (
|
||||
normalized_relative_path.is_empty()
|
||||
or bool(prototype_cache_state.call(
|
||||
"has_animated_prototype",
|
||||
normalized_relative_path
|
||||
))
|
||||
or bool(prototype_cache_state.call(
|
||||
"is_animation_static",
|
||||
normalized_relative_path
|
||||
))
|
||||
):
|
||||
return {}
|
||||
if int(terminal_record.get(
|
||||
"status",
|
||||
ResourceLoader.THREAD_LOAD_FAILED
|
||||
)) != ResourceLoader.THREAD_LOAD_LOADED:
|
||||
prototype_cache_state.call(
|
||||
"mark_animation_static",
|
||||
normalized_relative_path
|
||||
)
|
||||
return {}
|
||||
|
||||
var resource_path := String(terminal_record.get("path", ""))
|
||||
var loaded_resource := load_threaded_get(resource_path)
|
||||
var candidate := _animated_scene_finalizer.call(
|
||||
"instantiate_candidate",
|
||||
loaded_resource
|
||||
) as Node3D
|
||||
if candidate == null:
|
||||
prototype_cache_state.call(
|
||||
"mark_animation_static",
|
||||
normalized_relative_path
|
||||
)
|
||||
return {}
|
||||
return {
|
||||
"normalized": normalized_relative_path,
|
||||
"path": resource_path,
|
||||
"candidate": candidate,
|
||||
}
|
||||
|
||||
|
||||
## Repairs, validates and adopts one prepared candidate. The preparation must be
|
||||
## completed synchronously so its detached candidate always receives an owner.
|
||||
func finalize_prepared_candidate(
|
||||
preparation: Dictionary,
|
||||
material_source_root: Node3D,
|
||||
prototype_cache_state: RefCounted,
|
||||
debug_logging_enabled: bool = false
|
||||
) -> Node3D:
|
||||
if prototype_cache_state == null:
|
||||
return null
|
||||
var normalized_relative_path := String(preparation.get("normalized", ""))
|
||||
var candidate := preparation.get("candidate", null) as Node3D
|
||||
if normalized_relative_path.is_empty() or candidate == null:
|
||||
return null
|
||||
_animated_scene_finalizer.call(
|
||||
"repair_materials",
|
||||
candidate,
|
||||
material_source_root
|
||||
)
|
||||
var finalization: Dictionary = _animated_scene_finalizer.call(
|
||||
"finalize_candidate",
|
||||
candidate
|
||||
)
|
||||
var prototype := finalization.get("prototype", null) as Node3D
|
||||
if prototype == null:
|
||||
prototype_cache_state.call(
|
||||
"mark_animation_static",
|
||||
normalized_relative_path
|
||||
)
|
||||
return null
|
||||
var canonical_prototype := prototype_cache_state.call(
|
||||
"adopt_animated_prototype",
|
||||
normalized_relative_path,
|
||||
prototype
|
||||
) as Node3D
|
||||
if debug_logging_enabled:
|
||||
print("M2_ANIM_CACHE path=%s cache=%s players=%d" % [
|
||||
normalized_relative_path,
|
||||
String(preparation.get("path", "")),
|
||||
int(finalization.get("animation_player_count", 0)),
|
||||
])
|
||||
return canonical_prototype
|
||||
|
||||
|
||||
## Production ResourceLoader status adapter; injectable in synthetic tests.
|
||||
func load_threaded_get_status(resource_path: String) -> int:
|
||||
if _resource_loader_adapter != null:
|
||||
return int(_resource_loader_adapter.call(
|
||||
"load_threaded_get_status",
|
||||
resource_path
|
||||
))
|
||||
return ResourceLoader.load_threaded_get_status(resource_path)
|
||||
|
||||
|
||||
## Production ResourceLoader terminal-result adapter; injectable in tests.
|
||||
func load_threaded_get(resource_path: String) -> Resource:
|
||||
if _resource_loader_adapter != null:
|
||||
return _resource_loader_adapter.call(
|
||||
"load_threaded_get",
|
||||
resource_path
|
||||
) as Resource
|
||||
return ResourceLoader.load_threaded_get(resource_path)
|
||||
@@ -0,0 +1 @@
|
||||
uid://2rnsjucg3dul
|
||||
@@ -0,0 +1,47 @@
|
||||
class_name M2BuildDispatchPlanner
|
||||
extends RefCounted
|
||||
|
||||
## Selects the next M2 build action from already observed resource state.
|
||||
## The planner retains no state or engine-object references.
|
||||
|
||||
const ACTION_WAIT_FOR_ANIMATION := &"wait_for_animation"
|
||||
const ACTION_MATERIALIZE_ANIMATED := &"materialize_animated"
|
||||
const ACTION_WAIT_FOR_STATIC_MESH := &"wait_for_static_mesh"
|
||||
const ACTION_MATERIALIZE_STATIC := &"materialize_static"
|
||||
const ACTION_ADVANCE_WITHOUT_MATERIALIZATION := &"advance_without_materialization"
|
||||
|
||||
|
||||
## Returns a detached action/transition plan for one M2 build operation.
|
||||
func plan_step(
|
||||
batch_count: int,
|
||||
resource_snapshot: RefCounted
|
||||
) -> Dictionary:
|
||||
if resource_snapshot == null:
|
||||
return _wait_plan(ACTION_WAIT_FOR_STATIC_MESH)
|
||||
if bool(resource_snapshot.call("animation_request_pending")):
|
||||
return _wait_plan(ACTION_WAIT_FOR_ANIMATION)
|
||||
if batch_count <= 0:
|
||||
return _advance_plan(ACTION_ADVANCE_WITHOUT_MATERIALIZATION, false)
|
||||
if bool(resource_snapshot.call("has_animated_prototype")):
|
||||
return _advance_plan(ACTION_MATERIALIZE_ANIMATED, true)
|
||||
if bool(resource_snapshot.call("has_static_mesh")):
|
||||
return _advance_plan(ACTION_MATERIALIZE_STATIC, true)
|
||||
if bool(resource_snapshot.call("static_model_missing")):
|
||||
return _advance_plan(ACTION_ADVANCE_WITHOUT_MATERIALIZATION, true)
|
||||
return _wait_plan(ACTION_WAIT_FOR_STATIC_MESH)
|
||||
|
||||
|
||||
func _wait_plan(action: StringName) -> Dictionary:
|
||||
return {
|
||||
"action": action,
|
||||
"rotate_queue": true,
|
||||
"increment_batch_serial": false,
|
||||
}
|
||||
|
||||
|
||||
func _advance_plan(action: StringName, increment_batch_serial: bool) -> Dictionary:
|
||||
return {
|
||||
"action": action,
|
||||
"rotate_queue": false,
|
||||
"increment_batch_serial": increment_batch_serial,
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://df0bv1x2x4j2x
|
||||
@@ -0,0 +1,78 @@
|
||||
class_name M2BuildJob
|
||||
extends RefCounted
|
||||
|
||||
## Holds one M2 build cursor, grouped transforms and a strong root reference.
|
||||
## Releasing this record never frees the referenced Node.
|
||||
|
||||
var _tile_key: String
|
||||
var _root: Node3D
|
||||
var _groups: Dictionary
|
||||
var _group_keys: Array
|
||||
var _group_index: int = 0
|
||||
var _transform_offset: int = 0
|
||||
var _batch_serial: int = 0
|
||||
|
||||
|
||||
func _init(tile_key: String, root: Node3D, groups: Dictionary) -> void:
|
||||
_tile_key = tile_key
|
||||
_root = root
|
||||
_groups = groups
|
||||
_group_keys = groups.keys()
|
||||
|
||||
|
||||
## Returns the immutable tile identity used by the queue.
|
||||
func tile_key() -> String:
|
||||
return _tile_key
|
||||
|
||||
|
||||
## Returns the strongly referenced M2 scene root without transferring ownership.
|
||||
func root() -> Node3D:
|
||||
return _root
|
||||
|
||||
|
||||
## Returns the exact grouped-transform Dictionary supplied at enqueue time.
|
||||
func groups() -> Dictionary:
|
||||
return _groups
|
||||
|
||||
|
||||
## Returns the insertion-order group-key snapshot created at enqueue time.
|
||||
func group_keys() -> Array:
|
||||
return _group_keys
|
||||
|
||||
|
||||
## Returns the current model-path group cursor.
|
||||
func group_index() -> int:
|
||||
return _group_index
|
||||
|
||||
|
||||
## Returns the current transform offset within the selected model-path group.
|
||||
func transform_offset() -> int:
|
||||
return _transform_offset
|
||||
|
||||
|
||||
## Returns the next static/animated batch serial used for node naming.
|
||||
func batch_serial() -> int:
|
||||
return _batch_serial
|
||||
|
||||
|
||||
## Atomically adopts all progress values after one planned build operation.
|
||||
func adopt_progress(
|
||||
next_group_index: int,
|
||||
next_transform_offset: int,
|
||||
next_batch_serial: int
|
||||
) -> void:
|
||||
_group_index = next_group_index
|
||||
_transform_offset = next_transform_offset
|
||||
_batch_serial = next_batch_serial
|
||||
|
||||
|
||||
## Returns detached scalar/count diagnostics without engine-object references.
|
||||
func diagnostic_snapshot() -> Dictionary:
|
||||
return {
|
||||
"tile_key": _tile_key,
|
||||
"group_index": _group_index,
|
||||
"transform_offset": _transform_offset,
|
||||
"batch_serial": _batch_serial,
|
||||
"group_count": _group_keys.size(),
|
||||
"has_root": _root != null,
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://drawyd5qmel7j
|
||||
@@ -0,0 +1,169 @@
|
||||
class_name M2BuildQueue
|
||||
extends RefCounted
|
||||
|
||||
## Owns pending M2 build-job records and FIFO tile keys. Stale FIFO keys are
|
||||
## intentional: erasing a job never removes its queued key.
|
||||
|
||||
const JOB_SCRIPT := preload("res://src/render/m2/m2_build_job.gd")
|
||||
|
||||
var _jobs_by_tile_key: Dictionary = {}
|
||||
var _queued_tile_keys: Array[String] = []
|
||||
|
||||
|
||||
## Enqueues a typed job. Duplicate tile keys replace the keyed job and append
|
||||
## another FIFO entry, matching raw Dictionary assignment plus Array append.
|
||||
func enqueue(tile_key: String, root: Node3D, groups: Dictionary) -> bool:
|
||||
if tile_key.is_empty() or root == null or groups.is_empty():
|
||||
return false
|
||||
_jobs_by_tile_key[tile_key] = JOB_SCRIPT.new(tile_key, root, groups)
|
||||
_queued_tile_keys.append(tile_key)
|
||||
return true
|
||||
|
||||
|
||||
## Returns whether at least one FIFO key, including a stale key, is pending.
|
||||
func has_pending() -> bool:
|
||||
return not _queued_tile_keys.is_empty()
|
||||
|
||||
|
||||
## Returns the front FIFO tile key, or an empty String when the queue is empty.
|
||||
func front_key() -> String:
|
||||
if _queued_tile_keys.is_empty():
|
||||
return ""
|
||||
return _queued_tile_keys.front()
|
||||
|
||||
|
||||
## Pops and returns the front FIFO key, or an empty String when already empty.
|
||||
func pop_front() -> String:
|
||||
if _queued_tile_keys.is_empty():
|
||||
return ""
|
||||
return _queued_tile_keys.pop_front()
|
||||
|
||||
|
||||
## Moves the front FIFO key to the tail and reports whether a key existed.
|
||||
func rotate_front() -> bool:
|
||||
if _queued_tile_keys.is_empty():
|
||||
return false
|
||||
_queued_tile_keys.append(_queued_tile_keys.pop_front())
|
||||
return true
|
||||
|
||||
|
||||
## Returns whether a tile key still has a current job record.
|
||||
func has_job(tile_key: String) -> bool:
|
||||
return _jobs_by_tile_key.has(tile_key)
|
||||
|
||||
|
||||
## Returns the current job as its engine base type, or null for a stale key.
|
||||
func job_for(tile_key: String) -> RefCounted:
|
||||
return _jobs_by_tile_key.get(tile_key, null) as RefCounted
|
||||
|
||||
|
||||
## Returns the borrowed root for a current job, or null for a stale key.
|
||||
func root_for(tile_key: String) -> Node3D:
|
||||
var job := job_for(tile_key)
|
||||
if job == null:
|
||||
return null
|
||||
return job.call("root") as Node3D
|
||||
|
||||
|
||||
## Returns the exact grouped-transform Dictionary, or an empty Dictionary.
|
||||
func groups_for(tile_key: String) -> Dictionary:
|
||||
var job := job_for(tile_key)
|
||||
if job == null:
|
||||
return {}
|
||||
return job.call("groups") as Dictionary
|
||||
|
||||
|
||||
## Returns the borrowed group-key snapshot, or an empty Array for a stale key.
|
||||
func group_keys_for(tile_key: String) -> Array:
|
||||
var job := job_for(tile_key)
|
||||
if job == null:
|
||||
return []
|
||||
return job.call("group_keys") as Array
|
||||
|
||||
|
||||
## Returns the current model-path group cursor, or zero for a stale key.
|
||||
func group_index_for(tile_key: String) -> int:
|
||||
var job := job_for(tile_key)
|
||||
if job == null:
|
||||
return 0
|
||||
return int(job.call("group_index"))
|
||||
|
||||
|
||||
## Returns the current in-group transform offset, or zero for a stale key.
|
||||
func transform_offset_for(tile_key: String) -> int:
|
||||
var job := job_for(tile_key)
|
||||
if job == null:
|
||||
return 0
|
||||
return int(job.call("transform_offset"))
|
||||
|
||||
|
||||
## Returns the current batch serial, or zero for a stale key.
|
||||
func batch_serial_for(tile_key: String) -> int:
|
||||
var job := job_for(tile_key)
|
||||
if job == null:
|
||||
return 0
|
||||
return int(job.call("batch_serial"))
|
||||
|
||||
|
||||
## Atomically adopts all progress values for a current job.
|
||||
func adopt_progress(
|
||||
tile_key: String,
|
||||
next_group_index: int,
|
||||
next_transform_offset: int,
|
||||
next_batch_serial: int
|
||||
) -> bool:
|
||||
var job := job_for(tile_key)
|
||||
if job == null:
|
||||
return false
|
||||
job.call(
|
||||
"adopt_progress",
|
||||
next_group_index,
|
||||
next_transform_offset,
|
||||
next_batch_serial
|
||||
)
|
||||
return true
|
||||
|
||||
|
||||
## Erases only the keyed job and intentionally leaves every FIFO entry stale.
|
||||
## Returns whether an active record existed. Engine objects are never freed.
|
||||
func erase_job(tile_key: String) -> bool:
|
||||
var had_job := _jobs_by_tile_key.has(tile_key)
|
||||
_jobs_by_tile_key.erase(tile_key)
|
||||
return had_job
|
||||
|
||||
|
||||
## Returns a detached snapshot of active job keys for loader-owned cleanup.
|
||||
func job_keys() -> Array:
|
||||
return _jobs_by_tile_key.keys()
|
||||
|
||||
|
||||
## Releases all job/key references without freeing referenced engine objects.
|
||||
func clear() -> void:
|
||||
_jobs_by_tile_key.clear()
|
||||
_queued_tile_keys.clear()
|
||||
|
||||
|
||||
## Returns FIFO entry count, including duplicate and stale keys.
|
||||
func pending_count() -> int:
|
||||
return _queued_tile_keys.size()
|
||||
|
||||
|
||||
## Returns active keyed job count, excluding stale FIFO keys.
|
||||
func active_job_count() -> int:
|
||||
return _jobs_by_tile_key.size()
|
||||
|
||||
|
||||
## Returns detached FIFO order and tile-key-sorted scalar job diagnostics.
|
||||
func diagnostic_snapshot() -> Dictionary:
|
||||
var tile_keys: Array = _jobs_by_tile_key.keys()
|
||||
tile_keys.sort()
|
||||
var jobs: Array[Dictionary] = []
|
||||
for tile_key_variant in tile_keys:
|
||||
var tile_key := String(tile_key_variant)
|
||||
var job := job_for(tile_key)
|
||||
if job != null:
|
||||
jobs.append(job.call("diagnostic_snapshot") as Dictionary)
|
||||
return {
|
||||
"queued_tile_keys": _queued_tile_keys.duplicate(),
|
||||
"jobs": jobs,
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://57qrpiqqgpr4
|
||||
@@ -0,0 +1,72 @@
|
||||
class_name M2BuildResourceSnapshot
|
||||
extends RefCounted
|
||||
|
||||
## Holds one M2 build-step resource observation without owning engine lifetime.
|
||||
|
||||
var _normalized_relative_path: String
|
||||
var _animated_prototype: Node3D
|
||||
var _animation_request_pending: bool
|
||||
var _static_mesh: Mesh = null
|
||||
var _static_model_missing: bool = false
|
||||
|
||||
|
||||
func _init(
|
||||
normalized_relative_path: String,
|
||||
animated_prototype: Node3D,
|
||||
animation_request_pending: bool
|
||||
) -> void:
|
||||
_normalized_relative_path = normalized_relative_path
|
||||
_animated_prototype = animated_prototype
|
||||
_animation_request_pending = animation_request_pending
|
||||
|
||||
|
||||
## Returns the normalized M2 path observed by the loader.
|
||||
func normalized_relative_path() -> String:
|
||||
return _normalized_relative_path
|
||||
|
||||
|
||||
## Returns the borrowed animated prototype without transferring ownership.
|
||||
func animated_prototype() -> Node3D:
|
||||
return _animated_prototype
|
||||
|
||||
|
||||
## Returns whether an animated prototype was observed.
|
||||
func has_animated_prototype() -> bool:
|
||||
return _animated_prototype != null
|
||||
|
||||
|
||||
## Returns whether the animation request remains pending.
|
||||
func animation_request_pending() -> bool:
|
||||
return _animation_request_pending
|
||||
|
||||
|
||||
## Adopts the optional static Mesh and terminal missing-model observation.
|
||||
func adopt_static_observation(static_mesh: Mesh, static_model_missing: bool) -> void:
|
||||
_static_mesh = static_mesh
|
||||
_static_model_missing = static_model_missing
|
||||
|
||||
|
||||
## Returns the borrowed static Mesh without transferring ownership.
|
||||
func static_mesh() -> Mesh:
|
||||
return _static_mesh
|
||||
|
||||
|
||||
## Returns whether a prepared static Mesh was observed.
|
||||
func has_static_mesh() -> bool:
|
||||
return _static_mesh != null
|
||||
|
||||
|
||||
## Returns whether the static model lookup reached a terminal missing outcome.
|
||||
func static_model_missing() -> bool:
|
||||
return _static_model_missing
|
||||
|
||||
|
||||
## Returns detached path/availability diagnostics without engine references.
|
||||
func diagnostic_snapshot() -> Dictionary:
|
||||
return {
|
||||
"normalized_relative_path": _normalized_relative_path,
|
||||
"has_animated_prototype": has_animated_prototype(),
|
||||
"animation_request_pending": _animation_request_pending,
|
||||
"has_static_mesh": has_static_mesh(),
|
||||
"static_model_missing": _static_model_missing,
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://cgleoy15rtmby
|
||||
@@ -0,0 +1,238 @@
|
||||
class_name M2CachedAnimationResourceObserver
|
||||
extends RefCounted
|
||||
|
||||
## Observes or requests the cached-GLB animated phase of one M2 build step.
|
||||
|
||||
const M2_BUILD_RESOURCE_SNAPSHOT_SCRIPT := preload(
|
||||
"res://src/render/m2/m2_build_resource_snapshot.gd"
|
||||
)
|
||||
|
||||
|
||||
## Returns a snapshot containing the exact cached prototype or pending state.
|
||||
## Invalid composition and terminal failures produce a non-pending snapshot.
|
||||
func observe(
|
||||
normalized_relative_path: String,
|
||||
cache_directory: String,
|
||||
maximum_primitive_count: int,
|
||||
allowlist_patterns: PackedStringArray,
|
||||
denylist_patterns: PackedStringArray,
|
||||
prototype_cache_state: RefCounted,
|
||||
load_pipeline_state: RefCounted,
|
||||
debug_logging_enabled: bool = false
|
||||
) -> RefCounted:
|
||||
var empty_snapshot: RefCounted = M2_BUILD_RESOURCE_SNAPSHOT_SCRIPT.new(
|
||||
normalized_relative_path,
|
||||
null,
|
||||
false
|
||||
)
|
||||
if (
|
||||
normalized_relative_path.is_empty()
|
||||
or prototype_cache_state == null
|
||||
or load_pipeline_state == null
|
||||
):
|
||||
return empty_snapshot
|
||||
|
||||
var cached_prototype := prototype_cache_state.call(
|
||||
"find_animated_prototype",
|
||||
normalized_relative_path
|
||||
) as Node3D
|
||||
if cached_prototype != null:
|
||||
return M2_BUILD_RESOURCE_SNAPSHOT_SCRIPT.new(
|
||||
normalized_relative_path,
|
||||
cached_prototype,
|
||||
false
|
||||
)
|
||||
if bool(prototype_cache_state.call(
|
||||
"is_animation_static",
|
||||
normalized_relative_path
|
||||
)):
|
||||
return empty_snapshot
|
||||
if bool(load_pipeline_state.call("has_request", normalized_relative_path)):
|
||||
return M2_BUILD_RESOURCE_SNAPSHOT_SCRIPT.new(
|
||||
normalized_relative_path,
|
||||
null,
|
||||
true
|
||||
)
|
||||
|
||||
var cache_resource_path := find_eligible_glb_cache_path(
|
||||
normalized_relative_path,
|
||||
cache_directory,
|
||||
maximum_primitive_count,
|
||||
allowlist_patterns,
|
||||
denylist_patterns,
|
||||
debug_logging_enabled
|
||||
)
|
||||
if cache_resource_path.is_empty():
|
||||
prototype_cache_state.call(
|
||||
"mark_animation_static",
|
||||
normalized_relative_path
|
||||
)
|
||||
return empty_snapshot
|
||||
|
||||
var request_error := ResourceLoader.load_threaded_request(
|
||||
cache_resource_path,
|
||||
"",
|
||||
false,
|
||||
ResourceLoader.CACHE_MODE_REUSE
|
||||
)
|
||||
if request_error == OK or request_error == ERR_BUSY:
|
||||
load_pipeline_state.call(
|
||||
"remember_request",
|
||||
normalized_relative_path,
|
||||
cache_resource_path
|
||||
)
|
||||
return M2_BUILD_RESOURCE_SNAPSHOT_SCRIPT.new(
|
||||
normalized_relative_path,
|
||||
null,
|
||||
true
|
||||
)
|
||||
|
||||
prototype_cache_state.call("mark_animation_static", normalized_relative_path)
|
||||
return empty_snapshot
|
||||
|
||||
|
||||
## Returns the first eligible historical `.glb` cache candidate.
|
||||
func find_eligible_glb_cache_path(
|
||||
normalized_relative_path: String,
|
||||
cache_directory: String,
|
||||
maximum_primitive_count: int,
|
||||
allowlist_patterns: PackedStringArray,
|
||||
denylist_patterns: PackedStringArray,
|
||||
debug_logging_enabled: bool = false
|
||||
) -> String:
|
||||
if not is_animation_path_allowed(
|
||||
normalized_relative_path,
|
||||
allowlist_patterns,
|
||||
denylist_patterns
|
||||
):
|
||||
return ""
|
||||
for cache_resource_path in cache_resource_paths(
|
||||
cache_directory,
|
||||
normalized_relative_path
|
||||
):
|
||||
if not ResourceLoader.exists(cache_resource_path):
|
||||
continue
|
||||
if glb_cache_is_safe_for_runtime_animation(
|
||||
cache_resource_path,
|
||||
maximum_primitive_count,
|
||||
debug_logging_enabled
|
||||
):
|
||||
return cache_resource_path
|
||||
return ""
|
||||
|
||||
|
||||
## Applies the historical stripped, case-insensitive allow/deny substring policy.
|
||||
func is_animation_path_allowed(
|
||||
normalized_relative_path: String,
|
||||
allowlist_patterns: PackedStringArray,
|
||||
denylist_patterns: PackedStringArray
|
||||
) -> bool:
|
||||
if normalized_relative_path.is_empty() or allowlist_patterns.is_empty():
|
||||
return false
|
||||
var lowercase_path := normalized_relative_path.to_lower()
|
||||
var allowed := false
|
||||
for pattern in allowlist_patterns:
|
||||
var needle := String(pattern).strip_edges().to_lower()
|
||||
if not needle.is_empty() and lowercase_path.contains(needle):
|
||||
allowed = true
|
||||
break
|
||||
if not allowed:
|
||||
return false
|
||||
for pattern in denylist_patterns:
|
||||
var needle := String(pattern).strip_edges().to_lower()
|
||||
if not needle.is_empty() and lowercase_path.contains(needle):
|
||||
return false
|
||||
return true
|
||||
|
||||
|
||||
## Returns historical nested/lowercase/basename `.glb` candidates without I/O.
|
||||
func cache_resource_paths(
|
||||
cache_directory: String,
|
||||
relative_path: String
|
||||
) -> PackedStringArray:
|
||||
var normalized := relative_path.replace("\\", "/")
|
||||
var lowercase := normalized.to_lower()
|
||||
var stems := [
|
||||
normalized.get_basename(),
|
||||
lowercase.get_basename(),
|
||||
normalized.get_file().get_basename(),
|
||||
lowercase.get_file().get_basename(),
|
||||
]
|
||||
var result := PackedStringArray()
|
||||
for stem in stems:
|
||||
if stem.is_empty():
|
||||
continue
|
||||
var cache_resource_path := cache_directory.path_join(stem + ".glb")
|
||||
if not result.has(cache_resource_path):
|
||||
result.append(cache_resource_path)
|
||||
return result
|
||||
|
||||
|
||||
## Checks animation presence, primitive limit and accepted OpenWC schema.
|
||||
func glb_cache_is_safe_for_runtime_animation(
|
||||
cache_resource_path: String,
|
||||
maximum_primitive_count: int,
|
||||
debug_logging_enabled: bool = false
|
||||
) -> bool:
|
||||
var gltf := read_glb_json(cache_resource_path)
|
||||
if gltf.is_empty():
|
||||
return false
|
||||
var animations: Array = gltf.get("animations", [])
|
||||
if animations.is_empty():
|
||||
return false
|
||||
var primitive_count := glb_primitive_count(gltf)
|
||||
if maximum_primitive_count > 0 and primitive_count > maximum_primitive_count:
|
||||
return false
|
||||
var schema := glb_animation_schema(gltf)
|
||||
if schema == "pivot_prefix_v1" or schema.is_empty():
|
||||
return true
|
||||
if debug_logging_enabled:
|
||||
print(
|
||||
"M2_ANIM_REJECT schema=%s primitives=%d cache=%s"
|
||||
% [schema, primitive_count, cache_resource_path]
|
||||
)
|
||||
return false
|
||||
|
||||
|
||||
## Reads the JSON chunk of a version-2 GLB or returns an empty Dictionary.
|
||||
func read_glb_json(cache_resource_path: String) -> Dictionary:
|
||||
var absolute_path := ProjectSettings.globalize_path(cache_resource_path)
|
||||
if not FileAccess.file_exists(absolute_path):
|
||||
return {}
|
||||
var file := FileAccess.open(absolute_path, FileAccess.READ)
|
||||
if file == null or file.get_length() < 20:
|
||||
return {}
|
||||
var magic := file.get_32()
|
||||
var version := file.get_32()
|
||||
file.get_32()
|
||||
if magic != 0x46546c67 or version != 2:
|
||||
return {}
|
||||
var json_length := int(file.get_32())
|
||||
var chunk_type := file.get_32()
|
||||
if json_length <= 0 or chunk_type != 0x4e4f534a:
|
||||
return {}
|
||||
var parsed: Variant = JSON.parse_string(
|
||||
file.get_buffer(json_length).get_string_from_utf8()
|
||||
)
|
||||
return parsed as Dictionary if parsed is Dictionary else {}
|
||||
|
||||
|
||||
## Counts all glTF Mesh primitives using the historical safety rule.
|
||||
func glb_primitive_count(gltf: Dictionary) -> int:
|
||||
var count := 0
|
||||
var meshes: Array = gltf.get("meshes", [])
|
||||
for mesh_variant in meshes:
|
||||
if mesh_variant is Dictionary:
|
||||
var primitives: Array = (mesh_variant as Dictionary).get(
|
||||
"primitives",
|
||||
[]
|
||||
)
|
||||
count += primitives.size()
|
||||
return count
|
||||
|
||||
|
||||
## Returns the OpenWC animation schema marker from parsed glTF metadata.
|
||||
func glb_animation_schema(gltf: Dictionary) -> String:
|
||||
var asset: Dictionary = gltf.get("asset", {})
|
||||
var extras: Dictionary = asset.get("extras", {})
|
||||
return String(extras.get("openwc_m2_anim_schema", ""))
|
||||
@@ -0,0 +1 @@
|
||||
uid://d0ptej41k2smt
|
||||
@@ -0,0 +1,103 @@
|
||||
class_name M2MeshLoadPipelineState
|
||||
extends RefCounted
|
||||
|
||||
## Owns static M2 threaded-load request records and the terminal finalize FIFO.
|
||||
## The caller owns ResourceLoader I/O, permits, Mesh caches and materialization.
|
||||
|
||||
var _request_by_normalized_path: Dictionary = {}
|
||||
var _finalize_records: Array[Dictionary] = []
|
||||
|
||||
|
||||
## Records one normalized M2 path and ResourceLoader path. Empty or duplicate
|
||||
## values are rejected without mutation.
|
||||
func remember_request(normalized_relative_path: String, resource_path: String) -> bool:
|
||||
if normalized_relative_path.is_empty() or resource_path.is_empty():
|
||||
return false
|
||||
if _request_by_normalized_path.has(normalized_relative_path):
|
||||
return false
|
||||
_request_by_normalized_path[normalized_relative_path] = {
|
||||
"normalized": normalized_relative_path,
|
||||
"path": resource_path,
|
||||
}
|
||||
return true
|
||||
|
||||
|
||||
## Returns whether one normalized path currently has a pending threaded request.
|
||||
func has_request(normalized_relative_path: String) -> bool:
|
||||
return (
|
||||
not normalized_relative_path.is_empty()
|
||||
and _request_by_normalized_path.has(normalized_relative_path)
|
||||
)
|
||||
|
||||
|
||||
## Returns detached pending records in their existing Dictionary insertion order.
|
||||
func request_records_snapshot() -> Array[Dictionary]:
|
||||
var records: Array[Dictionary] = []
|
||||
for request_variant in _request_by_normalized_path.values():
|
||||
var request: Dictionary = request_variant
|
||||
records.append(request.duplicate())
|
||||
return records
|
||||
|
||||
|
||||
## Removes one pending request and appends its terminal status to the finalize
|
||||
## FIFO. Unknown paths are rejected without mutation.
|
||||
func complete_request(normalized_relative_path: String, terminal_status: int) -> bool:
|
||||
if not _request_by_normalized_path.has(normalized_relative_path):
|
||||
return false
|
||||
var request: Dictionary = _request_by_normalized_path[normalized_relative_path]
|
||||
_request_by_normalized_path.erase(normalized_relative_path)
|
||||
var finalize_record := request.duplicate()
|
||||
finalize_record["status"] = terminal_status
|
||||
_finalize_records.append(finalize_record)
|
||||
return true
|
||||
|
||||
|
||||
## Removes one pending request without enqueuing finalization. Returns whether a
|
||||
## record was removed.
|
||||
func discard_request(normalized_relative_path: String) -> bool:
|
||||
return _request_by_normalized_path.erase(normalized_relative_path)
|
||||
|
||||
|
||||
## Returns whether a terminal record is waiting for budgeted finalization.
|
||||
func has_finalize_record() -> bool:
|
||||
return not _finalize_records.is_empty()
|
||||
|
||||
|
||||
## Removes and returns the oldest terminal record. Empty means none is ready.
|
||||
func pop_finalize_record() -> Dictionary:
|
||||
if _finalize_records.is_empty():
|
||||
return {}
|
||||
return _finalize_records.pop_front()
|
||||
|
||||
|
||||
## Returns pending plus terminal-finalize work for existing renderer metrics.
|
||||
func total_work_count() -> int:
|
||||
return _request_by_normalized_path.size() + _finalize_records.size()
|
||||
|
||||
|
||||
## Returns the pending request count.
|
||||
func pending_request_count() -> int:
|
||||
return _request_by_normalized_path.size()
|
||||
|
||||
|
||||
## Returns the terminal finalize FIFO count.
|
||||
func finalize_record_count() -> int:
|
||||
return _finalize_records.size()
|
||||
|
||||
|
||||
## Clears request/finalize bookkeeping. The caller must drain ResourceLoader
|
||||
## requests before orderly shutdown clear.
|
||||
func clear() -> void:
|
||||
_request_by_normalized_path.clear()
|
||||
_finalize_records.clear()
|
||||
|
||||
|
||||
## Returns detached request and finalize records without loaded Resources/Meshes.
|
||||
func diagnostic_snapshot() -> Dictionary:
|
||||
var finalize_records: Array[Dictionary] = []
|
||||
for record in _finalize_records:
|
||||
finalize_records.append(record.duplicate())
|
||||
return {
|
||||
"requests": request_records_snapshot(),
|
||||
"finalize_records": finalize_records,
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://dadsxuswq0tnd
|
||||
@@ -0,0 +1,50 @@
|
||||
class_name M2MeshResourceCacheState
|
||||
extends RefCounted
|
||||
|
||||
## Owns normalized-path references to prepared static M2 Mesh resources.
|
||||
## The caller owns loading, preparation, missing-state and materialization.
|
||||
|
||||
var _mesh_by_normalized_path: Dictionary = {}
|
||||
|
||||
|
||||
## Stores one prepared Mesh for a non-empty normalized M2 path. A later store
|
||||
## for the same path replaces the retained reference.
|
||||
func store_mesh(normalized_relative_path: String, mesh: Mesh) -> bool:
|
||||
if normalized_relative_path.is_empty() or mesh == null:
|
||||
return false
|
||||
_mesh_by_normalized_path[normalized_relative_path] = mesh
|
||||
return true
|
||||
|
||||
|
||||
## Returns whether one normalized path currently has a retained Mesh.
|
||||
func has_mesh(normalized_relative_path: String) -> bool:
|
||||
return (
|
||||
not normalized_relative_path.is_empty()
|
||||
and _mesh_by_normalized_path.has(normalized_relative_path)
|
||||
)
|
||||
|
||||
|
||||
## Returns the exact retained Mesh reference, or null for empty/unknown paths.
|
||||
func find_mesh(normalized_relative_path: String) -> Mesh:
|
||||
if not has_mesh(normalized_relative_path):
|
||||
return null
|
||||
return _mesh_by_normalized_path[normalized_relative_path] as Mesh
|
||||
|
||||
|
||||
## Returns the number of retained static M2 Mesh resources.
|
||||
func mesh_count() -> int:
|
||||
return _mesh_by_normalized_path.size()
|
||||
|
||||
|
||||
## Returns a detached insertion-order list of cached normalized paths.
|
||||
func normalized_paths_snapshot() -> Array[String]:
|
||||
var normalized_paths: Array[String] = []
|
||||
for normalized_path_variant in _mesh_by_normalized_path.keys():
|
||||
normalized_paths.append(String(normalized_path_variant))
|
||||
return normalized_paths
|
||||
|
||||
|
||||
## Releases all retained Mesh references. The loader calls this only after
|
||||
## asynchronous work has completed during final scene shutdown.
|
||||
func clear() -> void:
|
||||
_mesh_by_normalized_path.clear()
|
||||
@@ -0,0 +1 @@
|
||||
uid://dhkyjcrdblgkd
|
||||
@@ -0,0 +1,34 @@
|
||||
class_name M2MeshResourceExtractor
|
||||
extends RefCounted
|
||||
|
||||
## Selects the first Mesh from loaded M2 Resources or existing Node subtrees.
|
||||
## The caller owns ResourceLoader I/O, cache adoption and material preparation.
|
||||
|
||||
|
||||
## Returns the direct Mesh Resource or the first Mesh in a temporary PackedScene
|
||||
## instance. The temporary instance is freed before this method returns.
|
||||
func extract_first_mesh(resource: Resource) -> Mesh:
|
||||
if resource is Mesh:
|
||||
return resource as Mesh
|
||||
if not (resource is PackedScene):
|
||||
return null
|
||||
var temporary_root := (resource as PackedScene).instantiate()
|
||||
if temporary_root == null:
|
||||
return null
|
||||
var mesh := find_first_mesh_in_subtree(temporary_root)
|
||||
temporary_root.free()
|
||||
return mesh
|
||||
|
||||
|
||||
## Returns the first MeshInstance3D Mesh in depth-first preorder. The subtree
|
||||
## and Mesh ownership remain unchanged.
|
||||
func find_first_mesh_in_subtree(root: Node) -> Mesh:
|
||||
if root == null:
|
||||
return null
|
||||
if root is MeshInstance3D:
|
||||
return (root as MeshInstance3D).mesh
|
||||
for child in root.get_children():
|
||||
var mesh := find_first_mesh_in_subtree(child)
|
||||
if mesh != null:
|
||||
return mesh
|
||||
return null
|
||||
@@ -0,0 +1 @@
|
||||
uid://dea4gekrxshjf
|
||||
@@ -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,157 @@
|
||||
class_name M2PrototypeCacheState
|
||||
extends RefCounted
|
||||
|
||||
## Owns detached static/animated M2 prototypes and their negative lookup
|
||||
## outcomes until final renderer shutdown.
|
||||
|
||||
var _static_prototypes_by_path: Dictionary = {}
|
||||
var _animated_prototypes_by_path: Dictionary = {}
|
||||
var _missing_model_paths: Dictionary = {}
|
||||
var _static_animation_paths: Dictionary = {}
|
||||
|
||||
|
||||
## Returns whether a valid static prototype is retained for the normalized path.
|
||||
func has_static_prototype(normalized_relative_path: String) -> bool:
|
||||
return find_static_prototype(normalized_relative_path) != null
|
||||
|
||||
|
||||
## Returns the exact retained static prototype or null.
|
||||
func find_static_prototype(normalized_relative_path: String) -> Node3D:
|
||||
return _find_valid_prototype(_static_prototypes_by_path, normalized_relative_path)
|
||||
|
||||
|
||||
## Adopts a static prototype and returns the canonical retained reference. The
|
||||
## first valid prototype wins; a later detached candidate is released.
|
||||
func adopt_static_prototype(
|
||||
normalized_relative_path: String,
|
||||
prototype: Node3D) -> Node3D:
|
||||
return _adopt_prototype(
|
||||
_static_prototypes_by_path,
|
||||
normalized_relative_path,
|
||||
prototype
|
||||
)
|
||||
|
||||
|
||||
## Returns whether a valid animated prototype is retained for the normalized path.
|
||||
func has_animated_prototype(normalized_relative_path: String) -> bool:
|
||||
return find_animated_prototype(normalized_relative_path) != null
|
||||
|
||||
|
||||
## Returns the exact retained animated prototype or null.
|
||||
func find_animated_prototype(normalized_relative_path: String) -> Node3D:
|
||||
return _find_valid_prototype(_animated_prototypes_by_path, normalized_relative_path)
|
||||
|
||||
|
||||
## Adopts an animated prototype and returns the canonical retained reference.
|
||||
func adopt_animated_prototype(
|
||||
normalized_relative_path: String,
|
||||
prototype: Node3D) -> Node3D:
|
||||
return _adopt_prototype(
|
||||
_animated_prototypes_by_path,
|
||||
normalized_relative_path,
|
||||
prototype
|
||||
)
|
||||
|
||||
|
||||
## Records that no model prototype can currently be loaded for this path.
|
||||
func mark_model_missing(normalized_relative_path: String) -> bool:
|
||||
return _mark_path(_missing_model_paths, normalized_relative_path)
|
||||
|
||||
|
||||
## Returns whether the model path has a retained missing outcome.
|
||||
func is_model_missing(normalized_relative_path: String) -> bool:
|
||||
return not normalized_relative_path.is_empty() and _missing_model_paths.has(
|
||||
normalized_relative_path
|
||||
)
|
||||
|
||||
|
||||
## Records that the path must use static presentation instead of animation.
|
||||
func mark_animation_static(normalized_relative_path: String) -> bool:
|
||||
return _mark_path(_static_animation_paths, normalized_relative_path)
|
||||
|
||||
|
||||
## Returns whether the path has a retained static-animation outcome.
|
||||
func is_animation_static(normalized_relative_path: String) -> bool:
|
||||
return not normalized_relative_path.is_empty() and _static_animation_paths.has(
|
||||
normalized_relative_path
|
||||
)
|
||||
|
||||
|
||||
## Returns detached path-only cache state for diagnostics and verification.
|
||||
func diagnostic_snapshot() -> Dictionary:
|
||||
return {
|
||||
"static_prototype_paths": _sorted_paths(_static_prototypes_by_path),
|
||||
"animated_prototype_paths": _sorted_paths(_animated_prototypes_by_path),
|
||||
"missing_model_paths": _sorted_paths(_missing_model_paths),
|
||||
"static_animation_paths": _sorted_paths(_static_animation_paths),
|
||||
}
|
||||
|
||||
|
||||
## Releases every retained prototype and clears positive and negative state.
|
||||
## Detached Nodes are freed synchronously; in-tree Nodes are queued for deletion.
|
||||
func clear_and_release() -> void:
|
||||
_release_prototypes(_static_prototypes_by_path)
|
||||
_release_prototypes(_animated_prototypes_by_path)
|
||||
_missing_model_paths.clear()
|
||||
_static_animation_paths.clear()
|
||||
|
||||
|
||||
func _find_valid_prototype(
|
||||
prototypes_by_path: Dictionary,
|
||||
normalized_relative_path: String) -> Node3D:
|
||||
if normalized_relative_path.is_empty():
|
||||
return null
|
||||
var prototype_variant: Variant = prototypes_by_path.get(normalized_relative_path)
|
||||
if not (prototype_variant is Node3D):
|
||||
return null
|
||||
var prototype := prototype_variant as Node3D
|
||||
return prototype if is_instance_valid(prototype) else null
|
||||
|
||||
|
||||
func _adopt_prototype(
|
||||
prototypes_by_path: Dictionary,
|
||||
normalized_relative_path: String,
|
||||
prototype: Node3D) -> Node3D:
|
||||
if normalized_relative_path.is_empty() or prototype == null or not is_instance_valid(prototype):
|
||||
return null
|
||||
var retained_prototype := _find_valid_prototype(
|
||||
prototypes_by_path,
|
||||
normalized_relative_path
|
||||
)
|
||||
if retained_prototype != null:
|
||||
if not is_same(retained_prototype, prototype):
|
||||
_release_prototype(prototype)
|
||||
return retained_prototype
|
||||
prototypes_by_path[normalized_relative_path] = prototype
|
||||
return prototype
|
||||
|
||||
|
||||
func _mark_path(paths: Dictionary, normalized_relative_path: String) -> bool:
|
||||
if normalized_relative_path.is_empty():
|
||||
return false
|
||||
paths[normalized_relative_path] = true
|
||||
return true
|
||||
|
||||
|
||||
func _release_prototypes(prototypes_by_path: Dictionary) -> void:
|
||||
for prototype_variant in prototypes_by_path.values():
|
||||
if prototype_variant is Node3D:
|
||||
_release_prototype(prototype_variant as Node3D)
|
||||
prototypes_by_path.clear()
|
||||
|
||||
|
||||
func _release_prototype(prototype: Node3D) -> void:
|
||||
if prototype == null or not is_instance_valid(prototype):
|
||||
return
|
||||
if prototype.is_inside_tree():
|
||||
prototype.queue_free()
|
||||
else:
|
||||
prototype.free()
|
||||
|
||||
|
||||
func _sorted_paths(paths: Dictionary) -> Array[String]:
|
||||
var result: Array[String] = []
|
||||
for path_variant in paths.keys():
|
||||
result.append(String(path_variant))
|
||||
result.sort()
|
||||
return result
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user