# 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 `RenderBudgetScheduler` permits. - Create or modify Nodes, MultiMesh, Mesh, materials or RIDs. - Introduce spatial cells or change profile batch limits. ## Context and boundaries ```mermaid 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 ```mermaid 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 ```mermaid 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_BUILD` counter. - 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 | ## Related decisions and references - [`m2-placement-grouper.md`](m2-placement-grouper.md) - [`m2-placement-transform-resolver.md`](m2-placement-transform-resolver.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)