# 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 Loader as StreamingWorldLoader participant Extractor as M2MeshResourceExtractor participant Scene as PackedScene Loader->>Extractor: extract_first_mesh(loaded Resource) alt direct Mesh Extractor-->>Loader: same Mesh reference else PackedScene Extractor->>Scene: instantiate() Scene-->>Extractor: temporary root Extractor->>Extractor: depth-first first-Mesh search Extractor->>Extractor: temporary_root.free() Extractor-->>Loader: Mesh or null end Loader->>Loader: prepare and cache Mesh or mark missing ``` ## Dependency diagram ```mermaid flowchart TB Loader[StreamingWorldLoader] --> 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 - `M2RuntimeMeshFinalizer` consumes the Mesh returned here and the loader 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/scenes/streaming/streaming_world_loader.gd` | ResourceLoader/raw I/O, caching, missing state 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)