8.7 KiB
M2 Build Batch Planner
Metadata
| Field | Value |
|---|---|
| Status | Implemented |
| Target/work package | M03 / M03-RND-M2-BUILD-BATCH-PLANNER-001 |
| Owners | Pure static/animated M2 batch sizing and group cursor progression |
| Last verified | Worktree work/sindo-main-codex/m03-m2-build-batch-planner, 2026-07-17 |
| Profiles/capabilities | Existing static MultiMesh and animated-instance build paths |
Purpose
Select the effective per-tick M2 batch limit, cap it to remaining transforms and plan the next group offset without accessing loader state or renderer resources.
Non-goals
- Own M2 build jobs, queues, tile state, retries or cancellation.
- Decide whether an animated prototype or static mesh is ready.
- Rotate queues or consume
RenderBudgetSchedulerpermits. - Create or modify Nodes, MultiMesh, Mesh, materials or RIDs.
- Introduce spatial cells or change profile batch limits.
Context and boundaries
flowchart LR
Groups[M2PlacementGrouper transform arrays] --> Loader[Loader build loop]
Limits[Animated/static batch limits] --> Planner[M2BuildBatchPlanner]
Loader --> Planner
Planner --> Plan[Detached batch plan]
Plan --> Loader
Loader --> Ready{Resource ready?}
Ready --> Materialize[Animated or MultiMesh materialization]
Loader --> Budget[RenderBudgetScheduler permit]
Allowed dependencies are integer/boolean scalar math and a fresh Dictionary. Node, SceneTree, RenderingServer, ResourceLoader, WorkerThreadPool, mutexes, renderer caches, gameplay, network, editor UI and files are forbidden.
Public API
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|---|---|---|---|---|
plan_batch(transform_count, current_offset, has_animated_prototype, animated_batch_limit, static_batch_limit) |
Pure query | Return the selected batch size/count and next group cursor | Any thread; call-local result | Non-positive selected limit clamps to one; empty/past-end range completes |
Inputs and outputs
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|---|---|---|---|---|---|
| Input | Transform count | Grouped transform array | Planner remaining-count formula | Scalar copy | One plan |
| Input | Current group offset | Loader build job | Planner cursor formula | Scalar copy | One plan |
| Input | Animated prototype availability | Loader resource selection | Planner limit selector | Scalar copy | One plan |
| Input | Animated/static raw limits | Renderer profile/exports | Planner clamp | Scalar copy | One plan |
| Output | effective_batch_size |
Planner | Tests/diagnostics | Detached integer | One plan |
| Output | batch_count |
Planner | Loader materialization adapter | Detached integer | One plan |
| Output | next_offset |
Planner | Loader build-job cursor | Detached integer | Until next batch |
| Output | group_complete |
Planner | Loader group-index progression | Detached boolean | One plan |
The output Dictionary is newly allocated and caller-owned. The planner retains no reference to it or any input.
Data flow
flowchart TD
Select{Animated prototype?} -->|yes| Animated[animated_batch_limit]
Select -->|no| Static[static_batch_limit]
Animated --> Clamp[max 1]
Static --> Clamp
Clamp --> Remaining[max 0 of transform_count - current_offset]
Remaining --> Count[min effective limit, remaining]
Count --> Complete{count <= 0 or offset + count >= total?}
Complete -->|yes| Done[group_complete true; next_offset 0]
Complete -->|no| Continue[group_complete false; next_offset offset + count]
Lifecycle/state
The planner is stateless. It has no initialization, reset, cancellation, recovery or shutdown work. Each call returns a complete detached value plan.
Main sequence
sequenceDiagram
participant Loader as StreamingWorldLoader build loop
participant Planner as M2BuildBatchPlanner
participant Materializer as Animated/MultiMesh adapter
participant Scheduler as RenderBudgetScheduler
Loader->>Planner: plan_batch(count, offset, path kind, limits)
Planner-->>Loader: batch_count, next_offset, group_complete
Loader->>Loader: resolve/request prototype or mesh
Loader->>Materializer: materialize selected transform slice
Loader->>Loader: adopt cursor/group completion
Loader->>Scheduler: consume M2_BUILD permit
Ownership, threading and resources
- The planner owns only call-local scalar values and the returned Dictionary.
- The loader owns build-job Dictionaries, queue ordering, tile checks, resource readiness/retry, serial numbers and group-index mutation.
- Materializers own main-thread Node/MultiMesh construction under loader roots.
- The scheduler owns the frame-local
M2_BUILDcounter. - Pure planning is thread-safe, though the current adapter calls it on main thread.
Errors, cancellation and recovery
| Failure | Detection | Behavior | Diagnostic | Recovery |
|---|---|---|---|---|
| Zero/negative selected limit | Scalar clamp | Effective size becomes one | Contract fixture | Correct profile value later |
| Empty transform group | Remaining count zero | Count zero; group completes | Contract fixture | Loader advances group |
| Offset at/past end | Remaining count clamp | Count zero; offset resets | Contract fixture | Loader advances group |
| Negative offset | No new validation | Historical arithmetic is preserved | Contract fixture | Loader inputs should remain valid |
| Resource pending/missing | Outside planner | Loader rotates or advances as before | Existing cache/build diagnostics | Resource loader retry/fallback |
| Tile cancelled | Outside planner | Loader cancels job before planning/materialization | Existing lifecycle gates | Eligible tile may requeue |
Configuration and capabilities
The planner introduces no setting. It consumes existing
m2_animated_instances_per_tick and m2_multimesh_batch_size values after the
loader determines whether an animated prototype is available.
Persistence, cache and migration
No persistence, schema or cache format participates. ADT/M2 cache versions, quality presets and rebuild instructions are unchanged.
Diagnostics and observability
The planner emits no logs. Its verifier covers deterministic formula/source boundaries and records bounded 20,000-plan timing. Existing loader metrics retain queue depth, build activity and hitch observability.
Verification
verify_m2_build_batch_planner.gd: static/animated selection, invalid-limit clamp, remaining cap, exact completion, empty/past-end and negative-offset behavior, loader/dependency source boundaries and bounded timing.- Grouper, transform resolver, unique registry/dedupe, facade, internal-access, manifest, shutdown, scheduler, streaming, coordinate, documentation and coordination regressions remain required.
- Fidelity evidence is formula extraction only. Asset-backed frame pacing and visual output require the renderer checkpoint/performance workflow.
Extension points
- A later package may define typed build-job state once queue/resource transitions are extracted together with explicit cancellation and recovery.
- Spatial-cell batching must use measured culling/performance evidence and must not silently change this model-path batch cursor.
Capability status
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| Static/animated batch cursor planning | Implemented extraction | Contract/source/timing verifier | Asset-backed p95/p99 pending |
| Build queue/resource state machine | Remains in loader | Existing lifecycle regressions | Stateful extraction pending |
| Spatial-cell batching | Planned | Renderer roadmap | Culling evidence/design pending |
Known gaps and risks
- The result remains a Dictionary while loader build jobs are untyped Dictionaries.
- Negative offsets are preserved rather than rejected to avoid a hidden behavior change.
- Planning timing does not measure resource loading or materialization.
- Private asset-backed p95/p99 and visual comparison remain unavailable.
Source map
| Path | Responsibility |
|---|---|
src/render/m2/m2_build_batch_planner.gd |
Pure limit/count/cursor planning |
src/scenes/streaming/streaming_world_loader.gd |
Build job, queue, resources, materialization and budgets |
src/render/m2/m2_placement_grouper.gd |
Produces grouped transform arrays |
src/tools/verify_m2_build_batch_planner.gd |
Formula, source and timing regression |