211 lines
9.0 KiB
Markdown
211 lines
9.0 KiB
Markdown
# 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)
|