# 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/render/m2/m2_prototype_cache_state.gd` | Prototype and missing/static-only lookup state |
| `src/scenes/streaming/streaming_world_loader.gd` | ResourceLoader/raw I/O, cache decisions and materialization |
| `src/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)
