In Progress Scheduling Core Systems

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.

Design intent: The scheduling system ignores transit time entirely. Inefficiencies caused by machine placement and travel distance are intentionally left for the player to solve.

⚙️ Operation Lifecycle

Each operation entity progresses through these states. States owned by the logistics system are marked — this doc covers scheduling-owned states only.

StateOwnerMeaningExit condition
WaitingForPartsSchedulingOne or more input parts not yet produced, or currently in transitAll inputs exist with qty > 0 and are not in transit
WaitingForMachineSchedulingInputs ready; no machine has a free capacity slotA matching machine slot becomes free
ReceiverAwaitingDeliveryLogisticsAssigned to a machine; input parts are being transported to itBot delivers all inputs
QueuedSchedulingInputs present at machine; waiting for ops ahead in queue to completeOp reaches front of machine queue
InProgressSchedulingActively processing on the machineprogress ≥ 1.0
SenderAwaitingExportLogisticsOutput ready; waiting for logistics to assign and dispatch a botBot picks up output
CompleteSchedulingOutput 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.

Why unified slots?

Prevents a machine being flooded with new work while its completed outputs are waiting for pickup. Backpressure propagates naturally up the dependency graph.

Slot accounting

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.

Per-process multiplier

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.

Overall machine multiplier

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.

Scheduling note: MWKR calculations use predicted duration, ignoring any modifiers. Actual throughput will differ from scheduled estimates.

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

CheckFailure behaviour
Machine offers the required processOrder rejected. Player notified per failing step: "Bot [name]: [Part] requires process [type] — no capable machine available."
Machine is enabled
If any step fails validation the entire order is rejected. Partial orders are not queued.

📐 Dispatch Algorithm

Runs every tick. Assigns unassigned-but-ready operations to machines.

Step-by-step

#StepDetail
1Collect ready opsOps with no AssignedMachine whose inputs all exist, qty > 0, not in transit
2SortBotPriority ASC (= order age, oldest first), then MWKR DESC — deterministic; within a priority level, longest critical path goes first
3AssignFor 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.
4Update stragglersAny 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

Circular dependency

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:

1. Check for reuse

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.

2. Destroy if no reuse found

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

Not yet started

Operations still in WaitingForParts or WaitingForMachine are destroyed immediately — no parts have been consumed yet.

Queued or in progress

Operation is aborted. Any input parts already consumed are gone. Any output parts already produced follow the reuse/destroy logic above.