render: extract static M2 batch materializer
This commit is contained in:
@@ -0,0 +1,224 @@
|
||||
# M2 Static Batch Materializer
|
||||
|
||||
## Metadata
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Status | Implemented |
|
||||
| Target/work package | M03 / `M03-RND-M2-STATIC-BATCH-MATERIALIZER-001` |
|
||||
| Owners | Static M2 MultiMesh construction, render settings and attachment |
|
||||
| Last verified | Worktree `work/sindo-main-codex/m03-m2-static-batch-materializer`, 2026-07-18 |
|
||||
| Profiles/capabilities | Existing static M2 placement path |
|
||||
|
||||
## Purpose
|
||||
|
||||
Build and attach one static M2 `MultiMeshInstance3D` from an already prepared
|
||||
Mesh and an ordered transform slice. The service preserves the existing
|
||||
transform format, Mesh identity, batch name, visibility and shadow behavior.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Group placements, size batches or advance build-job cursors.
|
||||
- Load, refresh, build or cache Mesh resources.
|
||||
- Consume render permits, rotate queues or decide retries/cancellation.
|
||||
- Assign Editor owners or release tile roots.
|
||||
- Materialize animated instances or introduce spatial-cell batching.
|
||||
|
||||
## Context and boundaries
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Groups[Ordered transform group] --> Loader[StreamingWorldLoader build adapter]
|
||||
MeshCache[Prepared Mesh cache] --> Loader
|
||||
Planner[M2BuildBatchPlanner] --> Slice[Start and count]
|
||||
Slice --> Loader
|
||||
Loader --> Materializer[M2StaticBatchMaterializer]
|
||||
Materializer --> Batch[Attached MultiMeshInstance3D]
|
||||
Loader --> EditorOwner[Editor owner adapter]
|
||||
```
|
||||
|
||||
Allowed dependencies are Godot `Mesh`, `MultiMesh`, `MultiMeshInstance3D`,
|
||||
`GeometryInstance3D` and main-thread SceneTree APIs. Files, ResourceLoader,
|
||||
workers, RenderingServer calls, caches, build jobs, scheduler policy, gameplay,
|
||||
network and Editor ownership are forbidden.
|
||||
|
||||
## Public API
|
||||
|
||||
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|
||||
|---|---|---|---|---|
|
||||
| `materialize_batch(parent, path, mesh, transforms, start, count, serial, visibility_end, visibility_margin, cast_shadows)` | Command/query | Create, configure and attach one static MultiMesh batch | Main thread; borrowed inputs, parent owns result | Invalid/empty input returns null; transform bounds are a caller precondition |
|
||||
|
||||
## Inputs and outputs
|
||||
|
||||
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|
||||
|---|---|---|---|---|---|
|
||||
| Input | M2 parent root | Loader build job | Batch attachment | Borrowed Node3D | Tile/job lifetime |
|
||||
| Input | Relative model path and serial | Loader build adapter | Batch node name | Copied values | One call |
|
||||
| Input | Prepared static Mesh | M2 Mesh resource cache/finalizer | MultiMesh | Borrowed Resource; exact reference retained by MultiMesh | Cache and batch lifetimes |
|
||||
| Input | Ordered transforms, start and count | Placement group and batch plan | MultiMesh transform buffer | Borrowed Array/scalars | One call |
|
||||
| Input | Visibility end/margin and shadow flag | Renderer configuration | MultiMeshInstance3D properties | Copied scalars | One call |
|
||||
| Output | Attached `MultiMeshInstance3D` | Materializer | Parent SceneTree and loader Editor-owner adapter | Parent-owned node/resource | Tile lifetime |
|
||||
|
||||
Side effects are MultiMesh allocation, transform-buffer mutation,
|
||||
MultiMeshInstance3D allocation/configuration and SceneTree attachment. The
|
||||
service retains no direct reference after return.
|
||||
|
||||
## Data flow
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Validate[Validate parent, Mesh, transforms and count] --> MultiMesh[Allocate TRANSFORM_3D MultiMesh]
|
||||
MultiMesh --> MeshIdentity[Assign exact prepared Mesh]
|
||||
MeshIdentity --> Count[Set instance count]
|
||||
Count --> Upload[Upload transforms start plus ordered batch offset]
|
||||
Upload --> Node[Create named MultiMeshInstance3D]
|
||||
Node --> Render[Apply shadow and optional visibility end/margin]
|
||||
Render --> Attach[Attach once to supplied parent]
|
||||
Attach --> Return[Return attached node]
|
||||
Validate -->|invalid| Null[Return null without attachment]
|
||||
```
|
||||
|
||||
## Lifecycle/state
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Validating
|
||||
Validating --> Rejected: invalid or empty input
|
||||
Validating --> Building: valid slice
|
||||
Building --> Configured: transforms and render properties set
|
||||
Configured --> Attached: parent add_child
|
||||
Rejected --> [*]
|
||||
Attached --> [*]: parent/tile owner releases subtree
|
||||
```
|
||||
|
||||
The `RefCounted` service is stateless; states describe a single call.
|
||||
|
||||
## Main sequence
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant L as StreamingWorldLoader
|
||||
participant P as M2BuildBatchPlanner
|
||||
participant M as M2StaticBatchMaterializer
|
||||
participant R as M2 parent root
|
||||
L->>P: plan_batch(transform count, offset, static limits)
|
||||
P-->>L: start/count and cursor transition
|
||||
L->>L: resolve prepared Mesh or rotate pending job
|
||||
L->>M: materialize_batch(parent, Mesh, transform slice, render settings)
|
||||
M->>M: allocate MultiMesh and upload ordered transforms
|
||||
M->>R: add_child(MultiMeshInstance3D)
|
||||
M-->>L: attached node
|
||||
L->>L: assign optional Editor owner
|
||||
```
|
||||
|
||||
## Dependency diagram
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
Loader[StreamingWorldLoader] --> Materializer[M2StaticBatchMaterializer]
|
||||
Planner[M2BuildBatchPlanner] --> Loader
|
||||
MeshState[M2MeshResourceCacheState] --> Loader
|
||||
Materializer --> Engine[Mesh / MultiMesh / MultiMeshInstance3D]
|
||||
Loader --> EditorOwner[Editor ownership]
|
||||
Loader --> Scheduler[RenderBudgetScheduler]
|
||||
Materializer -. no dependency .-> ResourceIO[ResourceLoader / files]
|
||||
Materializer -. no dependency .-> BuildState[Build jobs / queues / caches]
|
||||
```
|
||||
|
||||
## Ownership, threading and resources
|
||||
|
||||
- The caller borrows a prepared Mesh and transform Array for the synchronous call.
|
||||
- The new MultiMesh retains the exact Mesh Resource reference.
|
||||
- The new MultiMeshInstance3D retains the MultiMesh and transfers to the parent.
|
||||
- Parent/tile teardown owns node and Resource release through the SceneTree.
|
||||
- Loader assigns an Editor owner after successful attachment when configured.
|
||||
- All Resource and SceneTree mutation is main-thread-only.
|
||||
- No RID is acquired or released directly by this service.
|
||||
|
||||
## Errors, cancellation and recovery
|
||||
|
||||
| Failure | Detection | Behavior | Diagnostic | Recovery |
|
||||
|---|---|---|---|---|
|
||||
| Null parent or Mesh | Entry guard | Return null without allocation/attachment | Synthetic contract fixture | Correct build composition/cache state |
|
||||
| Empty transforms or non-positive count | Entry guard | Return null | Synthetic contract fixture | Planner supplies non-empty slice |
|
||||
| Invalid transform bounds | Caller precondition | Historical indexed access behavior remains | Build planner/source regression | Repair job cursor/group state |
|
||||
| Mesh pending | Outside service | Loader rotates job without calling service | Existing build/cache diagnostics | Async completion retries job |
|
||||
| Tile cancelled | Outside service | Loader cancels before materialization or releases parent later | Shutdown regression | Eligible tile may requeue |
|
||||
|
||||
The service does not swallow invalid bounds or silently clamp/reorder transforms,
|
||||
because doing so would change the accepted build-planner contract.
|
||||
|
||||
## Configuration and capabilities
|
||||
|
||||
| Setting/capability | Default | Profile | Runtime mutable | Effect |
|
||||
|---|---|---|---|---|
|
||||
| Transform format | `MultiMesh.TRANSFORM_3D` | All | No | Preserves full 3D M2 placements |
|
||||
| Static batch count | Loader `m2_multimesh_batch_size` through planner | Quality preset | Yes, between plans | Controls per-batch transform count |
|
||||
| Visibility end/margin | Loader M2 range / chunk size | Quality preset | Yes, between calls | Applied only when end is positive |
|
||||
| Shadow mode | Loader `m2_cast_shadows` | Quality preset | Yes, between calls | Selects ON/OFF casting setting |
|
||||
|
||||
## Persistence, cache and migration
|
||||
|
||||
No state is serialized and no cache/schema version changes. The service consumes
|
||||
the accepted prepared Mesh cache contract; no rebake or migration is required.
|
||||
|
||||
## Diagnostics and observability
|
||||
|
||||
- The service emits no logs, metrics or debug views.
|
||||
- Existing loader queue/hitch metrics continue to account for `m2build` work.
|
||||
- Path/serial remain visible through the unchanged batch node name.
|
||||
- Editor ownership remains observable through the existing loader adapter.
|
||||
|
||||
## Verification
|
||||
|
||||
- `verify_m2_static_batch_materializer.gd` covers invalid inputs, node naming,
|
||||
exact Mesh identity, transform format/count, ordered slice source contract,
|
||||
visibility, shadows, single attachment, ownership boundaries and timing.
|
||||
- Build planner, Mesh cache/extractor/finalizer, prototype, animated materializer,
|
||||
shutdown, materials, facade and internal-access regressions protect neighbors.
|
||||
- The headless dummy renderer does not round-trip per-instance transforms through
|
||||
`MultiMesh.get_instance_transform`; exact ordered upload is protected by the
|
||||
source contract and render checkpoints remain the observable regression gate.
|
||||
- Fidelity evidence is exact extraction only; no new build-12340 parity claim.
|
||||
- Performance budget: 10,000 transform API writes under one second in headless.
|
||||
|
||||
## Extension points
|
||||
|
||||
Measured spatial-cell batching may later provide smaller transform slices while
|
||||
reusing this materializer. A generic static/animated base class, async mutation
|
||||
and callback framework are intentionally excluded.
|
||||
|
||||
## Capability status
|
||||
|
||||
| Capability | Status | Evidence | Gap/next step |
|
||||
|---|---|---|---|
|
||||
| Static M2 MultiMesh materialization | Implemented extraction | Synthetic node/resource/render/source/timing verifier | Asset-backed traversal pending |
|
||||
| Transform ordering | Implemented extraction | Exact source contract and existing checkpoints | Headless per-instance getter unavailable |
|
||||
| Editor persistence | Existing loader-owned | Source boundary and editor helper | Editor integration fixture remains separate |
|
||||
| Spatial-cell batching | Planned | Renderer roadmap | Culling/performance design and evidence pending |
|
||||
|
||||
## Known gaps and risks
|
||||
|
||||
- Materialization remains synchronous main-thread work under the existing permit.
|
||||
- MultiMesh transform upload timing in a headless dummy renderer does not measure GPU cost.
|
||||
- No private asset traversal, visual comparison, descriptor-pressure, leak or p95/p99 run exists.
|
||||
- Current model-path batching is not culling-driven spatial-cell batching.
|
||||
|
||||
## Source map
|
||||
|
||||
| Path | Responsibility |
|
||||
|---|---|
|
||||
| `src/render/m2/m2_static_batch_materializer.gd` | Static MultiMesh allocation, transform upload, render setup and attachment |
|
||||
| `src/render/m2/m2_build_batch_planner.gd` | Static/animated batch count and cursor planning |
|
||||
| `src/render/m2/m2_mesh_resource_cache_state.gd` | Prepared static Mesh reference ownership |
|
||||
| `src/scenes/streaming/streaming_world_loader.gd` | Resource readiness, job/budget adapter and Editor ownership |
|
||||
| `src/tools/verify_m2_static_batch_materializer.gd` | Node/resource/render/source/timing regression |
|
||||
|
||||
## Related decisions and references
|
||||
|
||||
- [`m2-build-batch-planner.md`](m2-build-batch-planner.md)
|
||||
- [`m2-mesh-resource-cache-state.md`](m2-mesh-resource-cache-state.md)
|
||||
- [`m2-animated-instance-materializer.md`](m2-animated-instance-materializer.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