Factorio’s quality mechanic rolls for higher item tiers, and Wube’s framing is that it all comes down to statistics: craft enough and the distribution does the work. Gegell, a CS grad student working in 3D computer vision, spent two years proving there is another route — read the game’s random number generator out of crafting observations, predict thousands of rolls ahead, and skip the RNG forward until a legendary item is guaranteed.
Most of the post is about what determinism actually buys you.
- Factorio uses
taus88from Boost: three linear feedback shift registers, XORed. A former Wube developer confirmed the choice on the forums in 2014 — fastest of Boost’s generators, and randomness was never a bottleneck. - Wube ships a
.pdbwith the binary, so a decompiler yields readableRandomGenerator::getInt(). Three seed words, three independent updates, XOR on return. - LFSRs are linear over GF(2). Each step is a matrix multiply, and the matrix is invertible — so observed outputs can be run backwards to recover internal state.
- Three consecutive full-width outputs are enough to solve for the state. After that, every future roll is computable.
The observation channel is the interesting constraint. You never see a full 32-bit roll from inside the game, only implied facts like “did this craft upgrade quality”:
- Each recipe with random outputs leaks some top bits. Uranium processing has a 0.7% surprise that reveals 7 bits; repair pack recycling yields exactly one bit per item at a clean 50/50 split.
- That makes recipe choice an information-theory problem:
E[bits] = p⌊−log₂p⌋ + (1−p)⌊−log₂(1−p)⌋— a discretised Shannon entropy, best near even odds. - Repair pack recycling wins at 2 bits per craft, cheap and unlocked early. 44 recycler measurement units give the 88 bits needed to reconstruct the 96-bit state.
- The matrix algebra is precomputed in Python. Only a GF(2) matrix-vector product runs in-game, built from decider combinators and implicit wire summation.
Then the manipulation. You cannot force the generator to a value — but you can burn the calls in front of a good one. Scrap recycling eats 12 calls per craft at 0.2s base speed, gears pad the remainder, a buffer of pre-filtered good offsets drives the skipper, and a divergence detector auto-restarts the machine when predictions drift from what the assemblers actually produce.
The limitations are as useful as the technique:
- One global
generalRandomGeneratorhandles item creation, so any other random craft anywhere in the factory desynchronises everything. - The hidden callers are numerous: tile variant changes, entity name randomisation, dust particles when you walk, mining drills, spidertron legs, lightning on Fulgora.
- Scale kills it — a factory making thousands of items per second outruns the ability to precompute the sequence.
- Factorio 2.1 changed the usage pattern and broke the machine; at one point in the experimental branch, fish movement drew from the same generator, which would have meant deleting every fish on the map before a dedicated
fishRandomGeneratorappeared.
Worth reading as a case study in the gap between “random enough” and “unpredictable” — and as a reminder that a deterministic simulation with an observable side channel is a linear system waiting to be solved.