refactor(M03): extract M2 runtime rebuild classifier
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
# 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] --> Loader[StreamingWorldLoader]
|
||||
Loader --> Raw[M2Loader raw Dictionary]
|
||||
Raw --> Classifier[M2RuntimeMeshRebuildClassifier]
|
||||
Classifier --> Decision[Memoized rebuild boolean]
|
||||
Decision --> Loader
|
||||
Loader -->|true| Builder[M2Builder rebuild]
|
||||
Loader -->|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 | Native `M2Loader` adapter | Classifier | Borrowed; not mutated | First classification call per path |
|
||||
| Output | Rebuild-required boolean | Classifier | Loader material-refresh adapter | 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 Classifier as M2RuntimeMeshRebuildClassifier
|
||||
participant Builder as M2Builder
|
||||
Loader->>Native: load_m2(normalized path)
|
||||
Native-->>Loader: raw Dictionary
|
||||
Loader->>Classifier: needs_runtime_mesh_rebuild(path, data)
|
||||
Classifier-->>Loader: memoized boolean
|
||||
alt rebuild required
|
||||
Loader->>Builder: rebuild mesh from raw data
|
||||
else no rebuild required
|
||||
Loader->>Loader: stamp material refresh version
|
||||
end
|
||||
```
|
||||
|
||||
## Dependency diagram
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
Loader[StreamingWorldLoader] --> 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 loader owns raw loading, cache/resource selection, Mesh replacement and
|
||||
material refresh metadata.
|
||||
- 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 |
|
||||
| `M2_MATERIAL_REFRESH_VERSION` | Loader 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 |
|
||||
| M2 mesh loading/finalization | Existing loader-owned | Material/shutdown regressions | Separate state extraction 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)
|
||||
Reference in New Issue
Block a user