11 KiB
M2 Build Dispatch Planner
Metadata
| Field | Value |
|---|---|
| Status | Implemented extraction |
| Target/work package | M03 / M03-RND-M2-BUILD-DISPATCH-PLANNER-001 |
| Owners | Pure M2 resource-state to build-action decision |
| Last verified | Worktree work/sindo-main-codex/m03-m2-build-dispatch-planner, 2026-07-18 |
| Profiles/capabilities | Existing static MultiMesh and animated-instance build paths |
Purpose
Select one M2 build action after the loader observes animation/static resource state. The planner makes wait priority, materializer selection and the historical missing-model serial transition explicit without owning resources or engine work.
Non-goals
- Locate, request, load, cache or validate M2 resources.
- Size batches, own build jobs/queues or consume renderer permits.
- Create, attach or free Nodes, Meshes, MultiMeshes or animated instances.
- Change transforms, ordering, render settings, profiles or visible output.
- Add spatial-cell batching or M2 format/fidelity behavior.
Context and boundaries
flowchart LR
Loader[StreamingWorldLoader resource observations] --> Planner[M2BuildDispatchPlanner]
Planner --> Action[Detached action and transition plan]
Action --> Loader
Loader --> Queue[M2BuildQueue rotate/progress]
Loader --> Animated[M2AnimatedInstanceMaterializer]
Loader --> Static[M2StaticBatchMaterializer]
Loader --> Budget[RenderBudgetScheduler]
The planner depends only on RefCounted, scalar values, StringName constants
and a fresh Dictionary. ResourceLoader, caches, Nodes, Meshes, RenderingServer,
SceneTree, files, workers, mutexes, gameplay, network and Editor APIs are forbidden.
Public API
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|---|---|---|---|---|
ACTION_WAIT_FOR_ANIMATION |
Constant | Pending animation request blocks all other actions | Immutable | None |
ACTION_MATERIALIZE_ANIMATED |
Constant | Build the selected slice from animated prototype | Immutable | None |
ACTION_WAIT_FOR_STATIC_MESH |
Constant | Static resource is unresolved and queue must rotate | Immutable | None |
ACTION_MATERIALIZE_STATIC |
Constant | Build the selected slice from prepared static Mesh | Immutable | None |
ACTION_ADVANCE_WITHOUT_MATERIALIZATION |
Constant | Empty or terminally missing slice advances without a Node | Immutable | None |
plan_step(batch_count, resource_snapshot) |
Pure query | Return action, queue-rotation and serial-transition values | Any thread; call-local result | Null snapshot waits for static Mesh; non-positive count advances |
Inputs and outputs
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|---|---|---|---|---|---|
| Input | Planned batch count | M2BuildBatchPlanner through loader |
Dispatch planner | Integer copy | One call |
| Input | Typed animated/static availability, pending and missing observations | Loader / M2BuildResourceSnapshot |
Dispatch planner | Borrowed snapshot; engine references are not read | One call |
| Output | Action | Dispatch planner | Loader materializer/wait branch | Immutable StringName | One operation |
| Output | rotate_queue |
Dispatch planner | Loader queue adapter | Detached boolean | One operation |
| Output | increment_batch_serial |
Dispatch planner | Loader progress adapter | Detached boolean | One operation |
The output Dictionary is newly allocated and caller-owned. The planner retains no state, resource or engine-object reference.
Data flow
flowchart TD
Input[Observed state] --> AnimationPending{Animation request pending?}
AnimationPending -->|yes| WaitAnimation[Wait animation; rotate]
AnimationPending -->|no| Positive{Batch count positive?}
Positive -->|no| AdvanceEmpty[Advance; keep serial]
Positive -->|yes| Animated{Animated prototype?}
Animated -->|yes| BuildAnimated[Materialize animated; increment serial]
Animated -->|no| StaticReady{Static Mesh ready?}
StaticReady -->|yes| BuildStatic[Materialize static; increment serial]
StaticReady -->|no| Missing{Model terminally missing?}
Missing -->|yes| AdvanceMissing[Advance without Node; increment serial]
Missing -->|no| WaitStatic[Wait static Mesh; rotate]
Lifecycle/state
stateDiagram-v2
[*] --> Observe
Observe --> Waiting: animation/static unresolved
Observe --> Materializing: animated/static resource ready
Observe --> Advancing: empty or terminally missing batch
Waiting --> [*]: detached wait plan
Materializing --> [*]: detached materialization plan
Advancing --> [*]: detached advance plan
The planner itself is stateless. Queue rotation, cursor adoption, cancellation, map reset and shutdown remain external lifecycle transitions.
Main sequence
sequenceDiagram
participant L as StreamingWorldLoader
participant B as M2BuildBatchPlanner
participant D as M2BuildDispatchPlanner
participant Q as M2BuildQueue
participant M as M2 materializer
participant S as RenderBudgetScheduler
L->>L: observe animation request/prototype
alt animation not pending
L->>B: plan_batch
L->>L: lookup/request static Mesh when required
end
L->>D: plan_step(batch count, resource snapshot)
D-->>L: action, rotate flag, serial flag
alt wait action
L->>Q: rotate_front
else materialize action
L->>M: materialize selected slice
L->>Q: adopt progress and serial
else advance action
L->>Q: adopt progress and optional serial
end
L->>S: consume one M2_BUILD permit
Dependency diagram
flowchart TB
Loader[StreamingWorldLoader] --> Dispatch[M2BuildDispatchPlanner]
Loader --> Batch[M2BuildBatchPlanner]
Loader --> Queue[M2BuildQueue]
Loader --> Caches[M2 cache/pipeline state]
Loader --> Materializers[M2 materializers]
Dispatch --> Snapshot[M2BuildResourceSnapshot accessors]
Dispatch --> Values[Integers + booleans + StringName]
Dispatch -. no dependency .-> Engine[Node / Mesh / ResourceLoader]
Dispatch -. no dependency .-> State[Queue / cache / scheduler state]
Ownership, threading and resources
- Planner owns call-local comparisons and the returned Dictionary only.
M2BuildResourceSnapshotowns the typed call-local observation contract; dispatch reads availability/pending/missing accessors but no engine references.- Loader owns resource lookup/request order, action execution and permit use.
M2BuildQueueowns pending jobs, FIFO keys and progress cursors.- Materializers own main-thread scene construction under loader-owned roots.
- Pure calls are thread-safe; the current loader adapter calls on main thread.
Errors, cancellation and recovery
| Failure/state | Detection | Behavior | Diagnostic | Recovery |
|---|---|---|---|---|
| Animation request pending | Boolean observation | Wait wins even if other flags are true | Priority fixture | Retry when queue returns front |
| Non-positive batch | Count comparison | Advance without serial increment | Zero/negative fixtures | Next group/cursor operation |
| Static Mesh unresolved | No Mesh and not missing | Rotate without serial increment | Retry fixture | Loader request/cache completes |
| Static model missing | Terminal missing flag | Advance without Node and increment serial | Missing fixture | Later group continues |
| Contradictory static ready/missing | Both true | Ready Mesh wins | Priority fixture | Loader normally prevents contradiction |
| Tile cancellation/root invalid | Outside planner | Loader cancels before dispatch | Queue/shutdown regressions | Eligible tile may requeue |
Configuration and capabilities
The planner adds no setting. Existing animated/static batch limits, animation
enable flag, visibility/shadow settings and M2_BUILD permits remain external.
Persistence, cache and migration
No state is serialized. No ADT/M2 cache format or version changes; no rebake or migration is required.
Diagnostics and observability
The planner emits no logs. Its verifier reports the action matrix, source boundaries and bounded 20,000-call timing. Existing loader queue/hitch metrics remain unchanged.
Verification
verify_m2_build_dispatch_planner.gdcovers wait priority, zero/negative counts, animated/static priority, unresolved retry, terminal missing, detached results, loader/dependency boundaries and bounded timing.- Queue, batch planner, materializers, caches, shutdown, facade, internal-access and checkpoint tests protect adjacent behavior.
- Fidelity evidence is exact branch/transition extraction only. No private asset or original-client visual comparison is claimed.
Extension points
A later package may move resource observation/request execution behind a service
that produces M2BuildResourceSnapshot. Generic callbacks, signals and a shared
state-machine framework remain intentionally excluded.
Capability status
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| M2 build action selection | Implemented extraction | Priority/matrix/source/timing verifier | Asset-backed traversal pending |
| Resource observation and requests | Loader-owned | Cache/pipeline regressions | Typed orchestration extraction pending |
| Queue/cursor state | Implemented separately | M2 build queue verifier | Asset-backed traversal pending |
| Materialization | Implemented separately | Static/animated materializer verifiers | GPU/p95/p99 evidence pending |
Known gaps and risks
- Loader still performs synchronous/native prototype attempts and static request I/O.
- Synthetic timing does not measure ResourceLoader, Node or GPU work.
- Private traversal, leak, visual and p95/p99 evidence remains unavailable.
Source map
| Path | Responsibility |
|---|---|
src/render/m2/m2_build_dispatch_planner.gd |
Pure action priority and transition plan |
src/render/m2/m2_build_resource_snapshot.gd |
Typed per-step resource observation contract |
src/scenes/streaming/streaming_world_loader.gd |
Resource observation, action execution, permits and engine lifetime |
src/render/m2/m2_build_batch_planner.gd |
Batch count and cursor plan |
src/render/m2/m2_build_queue.gd |
Pending jobs, FIFO and cursor ownership |
src/tools/verify_m2_build_dispatch_planner.gd |
Matrix, boundary and timing regression |