# 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 Loader[StreamingWorldLoader] -->|loaded Resource and material source| Finalizer[M2AnimatedSceneFinalizer] Finalizer -->|accepted Node3D and player count| Loader Loader --> 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 | Loader I/O adapter | Candidate instantiator | Borrowed Resource | One finalize permit | | Input | Static material prototype Node3D | Loader cache/build adapter | Material repair | Borrowed Node | One repair call | | Input | Detached animated candidate | Instantiator | Repair/final validation | Finalizer then caller/release | One attempt | | Output | Accepted prototype and player count | Finalizer | Loader adoption/log adapter | Exact Node transferred | Shutdown cache lifetime | | Output | 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 L as StreamingWorldLoader participant F as M2AnimatedSceneFinalizer participant C as M2PrototypeCacheState L->>F: instantiate_candidate(Resource) F-->>L: detached Node3D or null L->>L: get static material prototype L->>F: repair_materials(candidate, source) L->>F: finalize_candidate(candidate) alt accepted F-->>L: exact Node3D and player count L->>C: adopt animated prototype else rejected F-->>L: empty; candidate freed L->>C: mark animation static end ``` ## Dependency diagram ```mermaid flowchart TB Loader[StreamingWorldLoader] --> Finalizer[M2AnimatedSceneFinalizer] Finalizer --> Engine[PackedScene / Node3D / Mesh / Material / AnimationPlayer] Loader --> Resource[ResourceLoader] Loader --> Budget[RenderBudgetScheduler] Loader --> 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 loader/prototype 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 | Loader marks static | | Wrong root type | Instantiated type guard | Free and return null | Node-count regression | Rebuild cache | | No material source/meshes | Null/empty traversal | Leave materials unchanged | Material fixture | Imported materials remain | | Missing source surface | Material lookup | First source material fallback | Mapping fixture | Repair static source | | No AnimationPlayer | Descendant inventory | Free and return empty | Lifetime regression | Loader marks static | | 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 cloning remain separate services. ## 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_load_pipeline_state.gd` | Pending/terminal records before finalization | | `src/render/m2/m2_prototype_cache_state.gd` | Accepted prototype/static-only outcomes | | `src/scenes/streaming/streaming_world_loader.gd` | I/O, permits, material source, adoption and logs | | `src/tools/verify_m2_animated_scene_finalizer.gd` | Scene/material/lifetime/boundary/timing regression | ## Related decisions and references - [`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)