Production Scheduling
JIT pull scheduler. Operations are created on demand when a bot order is queued, then dispatched to machines each tick using MWKR priority to minimise critical-path completion time.
🗺 Overview
The player queues a bot order. The scheduler immediately constructs the full operation dependency graph for that order — one operation per recipe step, linked by part handles. From that point, the tick-rate dispatch and progress systems drive execution to completion. Logistics (physical bot movement) is out of scope for this doc.
⚙️ Operation Lifecycle
Each operation entity progresses through these states. States owned by the logistics system are marked — this doc covers scheduling-owned states only.
| State | Owner | Meaning | Exit condition |
|---|---|---|---|
| WaitingForParts | Scheduling | One or more input parts not yet produced, or currently in transit | All inputs exist with qty > 0 and are not in transit |
| WaitingForMachine | Scheduling | Inputs ready; no machine has a free capacity slot | A matching machine slot becomes free |
| ReceiverAwaitingDelivery | Logistics | Assigned to a machine; input parts are being transported to it | Bot delivers all inputs |
| Queued | Scheduling | Inputs present at machine; waiting for ops ahead in queue to complete | Op reaches front of machine queue |
| InProgress | Scheduling | Actively processing on the machine | progress ≥ 1.0 |
| SenderAwaitingExport | Logistics | Output ready; waiting for logistics to assign and dispatch a bot | Bot picks up output |
| Complete | Scheduling | Output parts collected. Operation about to be cleaned up. | Cleanup |
🔒 Capacity Model
Each machine has a fixed OperationCapacity (number of slots). A slot is occupied from the moment an operation is dispatched until the logistics bot removes the output. This means queued, in-progress, and complete-awaiting-pickup operations all compete for the same slots.
Prevents a machine being flooded with new work while its completed outputs are waiting for pickup. Backpressure propagates naturally up the dependency graph.
A slot is held by any operation that has been assigned to the machine and not yet had its output collected — including operations queued, in progress, complete, or awaiting logistics pickup.
⚡ Machine Processing Speed
Effective operation duration is determined by two multipliers applied to the recipe's base duration.
Each process a machine supports has its own speed multiplier. A machine may support multiple processes at different speeds — e.g. a machine that can both smelt and cut, but smelts faster.
A separate 0–N multiplier applied to all processes on that machine. Driven by supplied vs. required power, operating temperature, and adjacency boosts from other machines. Falls to 0 if minimum requirements aren't met.
✅ Order Validation
Runs once when a bot order is placed, before any operations are created. Checks that every step in the dependency graph has at least one valid machine assignment.
Validity criteria
| Check | Failure behaviour |
|---|---|
| Machine offers the required process | Order rejected. Player notified per failing step: "Bot [name]: [Part] requires process [type] — no capable machine available." |
| Machine is enabled |
📐 Dispatch Algorithm
Runs every tick. Assigns unassigned-but-ready operations to machines.
Step-by-step
| # | Step | Detail |
|---|---|---|
| 1 | Collect ready ops | Ops with no AssignedMachine whose inputs all exist, qty > 0, not in transit |
| 2 | Sort | BotPriority ASC (= order age, oldest first), then MWKR DESC — deterministic; within a priority level, longest critical path goes first |
| 3 | Assign | For each op: find the eligible machine with the lowest projected remaining work and a free slot. If two machines are equal on utilisation, prefer the closer one. Commit or mark WaitingForMachine. |
| 4 | Update stragglers | Any unassigned op whose inputs aren't ready → WaitingForParts |
MWKR Definition
MWKR(op) = duration × (1 − progress) + max( MWKR(op') for each op' that consumes an output of op )
Computed recursively, memoised per tick. Represents the critical path length through this operation to the end of the order. Uses predicted duration, ignoring any modifiers.
Machine selection
Among all machines that support the required process type and have a free slot, pick the one where effectiveRemaining + op.Duration is smallest — i.e. the least-loaded eligible machine.
⚠️ Edge Cases
Not possible by construction — the operation graph is a DAG built from acyclic recipes. No cycle detection needed.
🗑 Cancellation & Recovery
Applies when a bot order is cancelled by the player, or when a machine is destroyed while holding assigned operations.
Part recovery logic
For each part that has already been produced but is now redundant:
If the part can satisfy an input requirement in any other active order, it is reassigned there. The consuming operation transitions back through normal dispatch.
If no active order can use the part, it becomes the input to a Destroy operation dispatched to an Incinerator machine. This uses the standard scheduling pipeline — no special-case cleanup code.
In-progress operations
Operations still in WaitingForParts or WaitingForMachine are destroyed immediately — no parts have been consumed yet.
Operation is aborted. Any input parts already consumed are gone. Any output parts already produced follow the reuse/destroy logic above.