Table of Contents

Placement previews and predictions

Problem. When the player drags a building out, the game shows what it would produce before it is built. Your building shows nothing.

Solution. A prediction simulation — a second, lightweight simulation of your building that runs against hypothetical inputs to compute hypothetical outputs.

What predictions are for

The prediction system is what powers the placement preview: the little display of "this cutter would output these two shapes". It is a parallel simulation graph, run on demand rather than every tick, so the preview can be computed without touching the real factory.

Every processing building in the game has one. Without it your building is placeable but the preview is blank, which reads as broken.

Through Flow

For a building you are adding, it is one stage in the chain:

.WithPrediction(new MyPredictionFactoryBuilder(), logger)

DiagonalCutter supplies Operation1In1OutPredictionFactoryBuilder, which is the shortcut worth knowing: for a machine that takes one item and produces one item via a shape operation, the generic 1-in-1-out builder does the work — you hand it the operation and it derives the prediction.

IBuildingPredictionFactoryBuilder   // buildings
IIslandPredictionFactoryBuilder     // islands

The corresponding extenders are BuildingPredictionExtender and IslandPredictionExtender.

A transport island without one is a hole in the graph

For a machine, a missing prediction means its own preview is blank. For anything that carries items through — a belt-like island, a crossing, a custom space path — the cost is bigger: the prediction graph stops there, so everything downstream reads as empty while real items flow through it perfectly. The usual report is "the shape/fluid readout flashes Empty even though it's working".

Forwarding is cheap to model. Vanilla's SpacePathPredictionSimulation is the whole pattern — one bundle handed out as both receiver and provider, so whatever it is told arrives at the other end:

public class MyPathPrediction : IItemBundlePredictionSimulation, ISimulation, IUpdatableSimulation
{
    private readonly ItemPredictionBundle<ItemPredictionConverter> Bundle =
        ItemPredictionBundle.Create<ItemPredictionConverter>();

    public int NumItemProviderBundles => 1;
    public int NumItemReceiverBundles => 1;

    public IItemPredictionProviderBundle GetItemProviderBundle(int outputIndex) => Bundle;
    public IItemPredictionReceiverBundle GetItemReceiverBundle(int inputIndex) => Bundle;

    public void Update(Ticks startTicks, Ticks deltaTicks) => Bundle.Update(deltaTicks);
}

ConnectableIslandPredictionSimulation pairs prediction bundles to connectors by the same declaration order ConnectableIslandSimulation uses for the real ones, so one connector order serves both — an island with two independent paths returns two bundles and needs no extra wiring.

Reaching WithPrediction on the island chain

The island fluent interfaces fork, and the obvious route is a dead end. WithSimulation off IDefinedUnlockableIslandExtender returns IAtomicIslandExtender, which has no WithPrediction. The prediction branch is only reachable through IDefinedSimulatableIslandExtender — and nothing in the chain returns that interface. Cast to it:

IAtomicIslandExtender simulated = AtomicIslands.Extend()
    .AllScenarios()
    .WithIsland(island, group)
    .UnlockedAtMilestone(new ByIndexMilestoneSelector(0))
    .WithSimulation(new MySimulationFactory());

((IDefinedSimulatableIslandExtender)simulated)
    .WithDefaultPlacement()
    .InToolbar(slot)
    .WithPrediction(new MyPredictionFactory(), logger)
    .WithoutModules()
    .Build();

Safe, because every one of those interfaces is implemented by the same AtomicIslandExtender instance and both WithDefaultPlacement overloads are the same no-op.

Registering a prediction system directly

For prediction behaviour not tied to one building:

using ShapezShifter.Hijack.Predictions;

public class MyPredictionsRewirer : IPredictionSystemsRewirer
{
    public void ModifyPredictionSystems(
        ICollection<ISimulationSystem> simulationSystems,
        PredictionSystemsDependencies dependencies)
    {
        simulationSystems.Add(new MyPredictionSystem(dependencies.ShapeRegistry));
    }
}

GameRewirers.AddRewirer(new MyPredictionsRewirer());

Prediction systems are ordinary ISimulationSystems — see Add a simulation system — registered into a separate collection from the real ones.

PredictionSystemsDependencies is a slimmer set than the simulation one:

public readonly GameMode Mode;
public readonly IGameResourcesMap ResourcesMap;
public readonly IShapeRegistry ShapeRegistry;
public readonly IShapeIdManager ShapeIdManager;
public readonly IResearchUnlockManager ResearchUnlockManager;
public readonly ILogger Logger;
public readonly ITrainHashCalculatorHeuristic TrainHashHeuristic;

No fluid registry, no signal channels — a hint at what predictions are expected to model.

What vanilla prediction systems look like

Worth reading before writing one:

Class Predicts
AtomicBuildingPredictionSimulationSystem the generic single-building case
AtomicIslandPredictionSimulationSystem the island equivalent
RailOpenOutputsPredictionSimulationSystem where rail outputs would go
TrainLauncherCatcherPredictionSimulationSystem train launch/catch pairing
TrainCargoLoaderPredictionSubSimulationSystem cargo loading
TrashPredictionSimulationSystem the trash island — the simplest of the set

TrashPredictionSimulationSystem is the one to start from: minimal, and it shows the shape of a prediction sub-simulation without train complexity.

Gotchas

  • Predictions must agree with reality. If your prediction says one thing and your simulation does another, players will report it as a bug in your building — and they will be right. Derive both from the same operation where you can, which is exactly what the 1-in-1-out builder achieves.
  • Predictions run on placement, i.e. during interactive dragging. Keep them cheap; a slow prediction shows up as input lag on the placement preview.
  • A prediction has no real inputs. It is computing "given an input of X, what comes out" — do not reach for live lane state inside one.
  • Islands and buildings have separate prediction paths; adding an island with simulation means the island variant.
Note

DiagonalCutter is the only sample with a prediction, and it uses the generic 1-in-1-out builder rather than a hand-written system. Anything beyond that shape is unexplored — read the vanilla systems above and verify against your game version.