Table of Contents

Read what shapes will flow where

Problem. You want to know which shape arrives at a belt, a port, or a machine — without waiting for an item to physically get there, and without composing shape operations by hand.

Solution. The game already knows. Alongside the real simulation it runs a second complete simulation whose only job is to work out which shapes can appear where, and it is readable from a mod.

This is the reading side of the prediction system. Placement previews is the authoring side — giving your own building a prediction so its placement preview works.

What the prediction graph is

A parallel simulation over the same map layout, built with a different set of systems. Every processing building appears in it as its IItemOperation applied to a set of possible items:

// Processing1In1OutPredictionSimulation.Update
PredictedItem input = Input.PopPrediction();
PredictedItem predictedItem = operation1In1Out.Predict(in input);
Output.Push(in predictedItem);

PredictedItem is a readonly struct holding at most four distinct IItems, with two extension methods worth knowing:

predicted.IsEmpty()        // nothing predicted here
predicted.IsDegenerated()  // more possibilities than the game will track

Because the propagation through belts, mergers and splitters is already done, the shape transfer function of any subgraph can be read rather than derived — you never have to reconstruct a connector graph or compose operations yourself.

Reaching the simulator

It is a private field on the session, reachable because the publicizer opens everything:

ISimulator predictions = orchestrator.PredictionSimulator;
Warning

It is null when shape predictions are switched off in the game's settings, and the session throws it away and builds a new one when they are toggled. Read the field fresh every time; never cache the simulator.

From there it behaves like any ISimulator: Simulations, TryFindChunkSimulation, FindAllConnectedSimulations.

Only ever read providers

Every prediction simulation implements IItemPredictionSimulation:

int NumItemReceivers { get; }
int NumItemProviders { get; }
IItemPredictionReceiver GetItemReceiver(int index);
IItemPredictionProvider GetItemProvider(int index);

Read the value from a provider, which exposes it as a plain property:

if (localized.Simulation is IItemPredictionSimulation prediction
    && prediction.NumItemProviders > 0)
{
    PredictedItem predicted = prediction.GetItemProvider(0).PredictedItem;
}
Warning

Do not call ItemPredictionReceiver.PopPrediction(). It clears the stored value as it returns it — that is how the graph moves predictions along — so calling it from a mod silently corrupts the game's own propagation.

GetItemProvider and GetItemReceiver have default interface implementations that throw NotImplementedException rather than returning null, and a simulation only overrides the side it actually has. Check the counts first, or catch:

private static IItemPredictionProvider ProviderAt(IItemPredictionSimulation prediction, int index)
{
    if (index >= prediction.NumItemProviders) return null;
    try { return prediction.GetItemProvider(index); }
    catch (NotImplementedException) { return null; }
}

Predictions flow downstream from sources

The single most important thing to understand, and the one that costs an implementation if you miss it:

Important

A subgraph with no source inside it has no predictions at all. Predictions propagate forward from extractors and other producers. An isolated copy of a factory — one you assembled yourself, or a blueprint expanded somewhere private — predicts nothing, because nothing is feeding it.

Concrete shapes appear on a live factory's ports only because that factory is being fed concrete shapes. So "what does this blueprint output" is not a question the prediction graph can answer on its own; it answers "what does this blueprint output given these inputs".

Propagation also moves one simulation per update, so after any change a deep graph needs several passes before the far end settles.

Degenerated is the game's own "give up"

When the set of possibilities at a point exceeds four, the game pushes PredictedItem.Degenerated instead of tracking them.

That is worth reusing rather than reinventing: if you need to decide whether some region of factory is simple enough to reason about, IsDegenerated() at its outputs is a criterion the game itself computes and stands behind.

Gotchas found the hard way

A space port is split in two. PredictionSpacePathPortSystem builds the building half facing inward and a separate buffer simulation two tiles out to carry the hop across space. So a space output port's own simulation has no provider to read — it has only a receiver. Reach its shapes through whatever feeds it, by indexing every provider in the region by provider.Next:

Dictionary<IItemPredictionReceiver, IItemPredictionProvider> feeders = new();
// ...for each provider: if (provider.Next != null) feeders[provider.Next] = provider;

IItemPredictionReceiver receiver = sender.GetItemReceiver(0);
if (feeders.TryGetValue(receiver, out IItemPredictionProvider feeder))
{
    PredictedItem predicted = feeder.PredictedItem;
}

Belt ports and fluid ports use the same classes. PredictionSpacePathPortSystem<TInput, TOutput> is generic over its connector types, so SpacePortSenderPredictionSimulation serves both. Tell them apart by what the prediction contains — a ShapeItem versus an IFluid or FluidPackageItem — not by the simulation type.

Do not identify ports by their prediction simulation type. Every conveyor on a platform is also a ForwardingPredictionSimulation, because ConveyorPredictionSimulationSystem uses one — so testing for that type matches the whole belt network rather than the handful of ports. Discriminate on the localized wrapper instead:

Wrapper What it is
IConnectablePort a genuine port transfer between two port buildings
ConnectableBeltPortSender a port building with nothing docked on the far side
ConnectablePathPredictionSimulation a belt run — not a port

See Ports and Notches for the equivalent taxonomy on the real simulation graph.

Off-screen platforms still predict. Clusters absent from the culler's LOD list are updated as SimulationLOD.Lowest, and PredictionUpdateStrategy still schedules those on a round-robin — so predictions exist for platforms the camera is nowhere near, just staler ones.

Note

Derived from the game's own implementation and from a mod that reads the graph, not from an official sample. Verify the type names against your game version.