# 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)