factory-sim
A fast, tick-exact simulator of Factorio's early game: train agents at simulator speed, then check them against the real game.
pip install factory-sim
It simulates a small slice of the game exactly: a character walking, reaching and hand-mining; burner mining drills; stone furnaces; transport belts, burner inserters and wooden chests; fuel; items on the ground; walls and water. Every rule and number comes from measurements of Factorio 2.0.60, and the simulator is checked decision by decision, and tick by tick, against traces recorded from the running game by FactorioGym. A mechanic is added here only after it has been measured there.
The point is speed. The real game runs about 260 decisions a second across eight workers. This runs 950,000 on 16 threads, and 530,000 through the full RL path (observation tensors, masks, rewards) on eight. The 40 million decisions a PPO run below uses would be about two days of nonstop play on the game. A belt-heavy scene, 338 belts with about 180 items moving, runs at about 200,000 ticks a second on one laptop core.
Three ways to solve a task
Reinforcement learning. train.py is masked PPO in one file: CleanRL's
layout at PufferLib's scale, with the choices and their sources in its
docstring. Policies trained here are evaluated on the real game with
FactorioGym's tools/sim_transfer.py. On held-out layouts they score:
| Task | Simulator | Factorio |
|---|---|---|
construct_smelting_line |
92.0% | 90.6% |
build_line |
99.2% | 90.6% |
This is one checkpoint per task, evaluated over 512 simulator episodes and 32 engine episodes. FactorioGym's README has the per-split numbers and the evidence files.
Program search. evolve/ has a language model write short Python
def build(world): programs, scores them in the simulator, and breeds the
better ones: islands, tournament selection, a genealogy of every attempt. A
program sees exactly what a trained policy sees, and every call it makes is one
decision of the same action space. It runs in a static sandbox with no imports
and no reflection. Selection uses a validation set, and the frozen held-out
scenes are touched only for reporting.
On construct_smelting_line, 1,000 held-out scenes that no run or analysis
had seen:
| Held-out success | Simulated decisions | |
|---|---|---|
| Evolved programs, 4 runs from a program that does nothing | 0.994, 1.000, 0.990, 0.999 | 0.46–0.69M to pass 0.9 |
| PPO, 4 runs, sampled actions | 0.928, 0.858, 0.837, 0.804 | 40M each |
Sampling is PPO's best mode: played greedily, the same checkpoints score 0.01–0.19. The model knows things PPO has to learn, pathfinding for a start, so fewer simulated decisions is not less compute. Seeding the search with a hand-written builder made it generalise worse: it kept the builder's wall-blind walker and patched individual validation scenes. The default seed is now a program that does nothing. Played on the real game, the four runs' programs succeed in 397 of 400 held-out episodes (99.3%), and the simulator agrees on every episode.
Training a language model. The same program task is an RL environment for
language models: factorio-build
on Prime Intellect's Environments Hub (prime env install divagr/factorio-build). The model writes one program per row, and the reward
is the fraction of the row's unseen scenes it solves.
recipes/grpo-factorio-build trains
Qwen3.5-9B on it with prime-rl. Scored in the simulator on
construct_smelting_line, with PlanBench Blocksworld to measure what the
training costs the model's general planning:
| Qwen3.5-9B | val (sim) |
holdout (sim) |
PlanBench generation | PlanBench execution |
|---|---|---|---|---|
| base | 2.1% | 1.6% | 53.2 | 39.8 |
| SFT on 800 evolved programs + GRPO | 85.9% | 84.0% | 2.8 | 3.0 |
| Evolve & Reinforce, round 1 | 85.6% | 71.9% | 46.4 | 27.8 |
Evolve & Reinforce builds the warm start from the model's own explanations of
the evolved programs, with replay of its answers to general prompts, then runs
GRPO. It keeps most of the planning ability that SFT on bare programs
destroyed, and gives up 12 points of holdout. A sample of the SFT + GRPO
model's holdout programs succeeds in 136
of 160 episodes on the real game, again in agreement with the simulator on
every episode. Models:
qwen3.5-9b-factorio-build-er-r1
and -er-r1-sft;
all of them in the models collection.
Use it
As a Gymnasium environment (pip install factory-sim[gym]):
import gymnasium as gym
import fsim.gym_env # registers the ids
env = gym.make("fsim/BuildLine-v0", split="test") # the held-out family
obs, info = env.reset(seed=0)
obs, reward, terminated, truncated, info = env.step(env.action_space.sample())
The ids are fsim/ConstructSmeltingLine-v0, fsim/BuildLine-v0 and
fsim/PlateLine-v0. The same environment is also available through
PufferLib, OpenEnv and the verifiers / Prime Environments Hub
format; docs/interfaces.md covers each.
Train PPO (uv sync --group train for PyTorch):
python train.py --run sparse-s1 --seed 1 --steps 20000000
python train.py --run belt-s1 --task belt_smelting # the v3 profile, its own budget
Run program search. It needs an OpenAI-compatible endpoint, set in a JSON
file with base_url, api_key and model, plus optional price and
max_usd for a hard spending cap:
FSIM_LLM_CONFIG=provider.json python -m evolve.run --name demo --budget-candidates 150 --max-usd 1
--task belt_smelting searches for belt-line programs instead.
Tasks
| Task | The agent has to |
|---|---|
construct_smelting_line |
find an ore patch it can only partly see, place a drill on ore and a furnace under the drill's drop point, fuel both, and get 10 machine-made plates in a verification minute |
build_line |
build a drill-and-furnace line and keep it running: success reads the output of a window after construction has settled, so a line that ran once and died fails |
plate_line |
commission a line that is already built by fuelling the right machines; one family adds decoy machines and items |
belt_smelting |
walk between an iron patch, a coal patch and an output chest that no standing spot reaches two of, and build drills, furnaces, belts and burner inserters that deliver 150 iron plates into the chest in a ten-minute window. The 20 starting coal runs a line for about 79 plates, so the line also needs coal from the coal patch |
Each task has training families and held-out families (a patch behind a wall,
a narrow strip, a chest at the far end of the belt budget), drawn exactly as
FactorioGym draws them, so a seed names the same scene in both projects.
belt_smelting's scripted reference (fsim/belt_expert.py, a port of
FactorioGym's reference solver) solves 599 of its first 600 scenes; the one
failure has its whole coal patch on water.
Action and observation profiles
The first three tasks use MultiDiscrete([22, 33, 122, 5, 15, 4]): operation,
target, placement tile, direction, item and amount, in two variants (v1, and
v2, where the target is a table row and the placement a fixed tile).
belt_smelting uses v3, MultiDiscrete([25, 97, 226, 5, 19, 4]):
- 25 operations: v2's 22 plus hand-mining a resource tile (
mine_tile),take_fuelandfinish, which ends the build phase and runs the verification window at once; - a mask per operation, legal where the game accepts: entities within the engine's reach (10 tiles from the character to the entity's box), resource tiles within 2.7 tiles, and placements within build distance;
- the observation adds each belt's lane counts and turn, each inserter's hand and its pickup and drop points, a drill's drop point, the task's public markers, and the free inventory slots.
Programs on belt_smelting hold WorldV3, which speaks the same profile.
What "tick-exact" means
- All 24 golden scenarios match the game at every decision, and all 19
tick-level traces match on every tick. Twelve of the scenarios exercise what
belt_smeltingneeds: belts, inserters, chests, hand-mining,take_fuelandfinish. - The belt and inserter rules come from five logistics probes and two
hand-mining probes on the engine: belt-line segments and their merge delays,
turns, sideloads, loops, and inserters picking from moving belts. FactorioGym's
docs/sim-logistics.mdrecords every measurement. - Doubles match to within 1e-12, because the game prints some with an imprecise last digit.
- Ore under a drill is compared as a total over its four tiles: the order a drill visits its tiles follows the engine's internal entity order, which could not be reduced to a rule.
- The RL layer matches FactorioGym bit for bit. Under v2 every encoded observation hashes identically, and so do the mask, reward, termination and success. Under v3 the observation tensors and masks do, at every decision of every golden scenario.
Build from source
uv sync # installs the checkout editable, compiling csrc/
uv run python build_fsim.py # rebuilds fsim/_fsim in place after a C change
uv run pytest # includes parity against tests/golden
uv run python bench/bench.py # throughput; results in bench/RESULTS.md
tools/sync_golden.py --from ../FactorioGym refreshes the golden traces from
a FactorioGym checkout, checking every trace's hash.
Citing
If you use factory-sim in academic work, please cite it. GitHub's "Cite this repository" button reads CITATION.cff; in BibTeX:
@software{agrawal2026factorysim,
author = {Agrawal, Divyansh},
title = {{factory-sim}: a fast, tick-exact simulator of an early-game factory},
year = {2026},
version = {0.2.0},
url = {https://github.com/divagr18/factory-sim},
license = {Apache-2.0}
}
License
Apache-2.0; see LICENSE. Factorio is a game and trademark of Wube Software Ltd. This project is independent, and is not affiliated with or endorsed by Wube. It contains no game code or assets; see NOTICE.
Release files for factory-sim 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| factory_sim-0.2.0.tar.gz | 711.6 kB | Details |
Built distributions (wheels)
Total release size: 6.7 MB
Release files / factory_sim-0.2.0.tar.gz
| Download URL | factory_sim-0.2.0.tar.gz |
|---|---|
| Size | 711.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
722b6f602a4ee0f57ee57016f7d18cd6be6bf37af1b555c29b83637fcdb06dde
|
|
BLAKE2b-256 checksum How to use checksums |
bde369efac446c18b25d3fc3932f78c531d414b3e4c02652746d604b6bfeb79a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.
Transparency logRelease files / factory_sim-0.2.0-cp313-cp313-win_amd64.whl
| Download URL | factory_sim-0.2.0-cp313-cp313-win_amd64.whl |
|---|---|
| Size | 587.5 kB |
| Tags | CPython 3.13 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
33082b3dadfdf98d351cdc37433044d2feb060a5830e30f40142435101c94dd8
|
|
BLAKE2b-256 checksum How to use checksums |
9ee05d5063dd7f722a1688d98a62ef565a2e4312e5740f9c80783ce00018da0d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.
Transparency logRelease files / factory_sim-0.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl
| Download URL | factory_sim-0.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 809.1 kB |
| Tags | CPython 3.13 Linux glibc 2.17+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
9464d7578979a6f00bce6d43d54b8d1a9773696d9cb339ca184ac9b0ab822c35
|
|
BLAKE2b-256 checksum How to use checksums |
165f2addf4f9a4942ae90be3c81089faaf27cec213eb71e6caf416ecf7ae9869
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.
Transparency logRelease files / factory_sim-0.2.0-cp313-cp313-macosx_11_0_arm64.whl
| Download URL | factory_sim-0.2.0-cp313-cp313-macosx_11_0_arm64.whl |
|---|---|
| Size | 604.5 kB |
| Tags | CPython 3.13 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
eed1ca7a2b58e3c55e6287279677c434aa3d4db36b266fe2bf47d4fde1d23bd0
|
|
BLAKE2b-256 checksum How to use checksums |
a8ad2747ca6156151b6da1bbbba192649ea4cb11343d699f200222d9cebd4c0c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.
Transparency logRelease files / factory_sim-0.2.0-cp312-cp312-win_amd64.whl
| Download URL | factory_sim-0.2.0-cp312-cp312-win_amd64.whl |
|---|---|
| Size | 587.5 kB |
| Tags | CPython 3.12 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
c28ff04a5e19a67cb8c01b49fd54eeb21955c7283b7def7214c76c6c8377e002
|
|
BLAKE2b-256 checksum How to use checksums |
e6d2151ad7a2f08384a0d8598eb2ab0f19562039b1105b8524ca97d4a919875a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.
Transparency logRelease files / factory_sim-0.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl
| Download URL | factory_sim-0.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 809.1 kB |
| Tags | CPython 3.12 Linux glibc 2.17+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
3ec00b8fda2d2c0bf84bac52e1badaeb372128ab004f7ab60d0f3517fdd6fe7a
|
|
BLAKE2b-256 checksum How to use checksums |
9859b5ae742bd4b4c43aa91d27b284c2b2d7d0dcabb1225e6b7760aa412fc75c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.
Transparency logRelease files / factory_sim-0.2.0-cp312-cp312-macosx_11_0_arm64.whl
| Download URL | factory_sim-0.2.0-cp312-cp312-macosx_11_0_arm64.whl |
|---|---|
| Size | 604.5 kB |
| Tags | CPython 3.12 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
af92f3b2aaaa0ba516b944119ff5e893917b7ec769d836e8f46340623837aa71
|
|
BLAKE2b-256 checksum How to use checksums |
e915ceb119c1075bb44a9c8e7714d8ab4eb699aa5f1c8109b767f00ee33507a3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.
Transparency logRelease files / factory_sim-0.2.0-cp311-cp311-win_amd64.whl
| Download URL | factory_sim-0.2.0-cp311-cp311-win_amd64.whl |
|---|---|
| Size | 587.4 kB |
| Tags | CPython 3.11 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
7dae6eb0d73a73a4ec76632fc0f73395e291b2eb6f0ebcf19a26288e7b135f82
|
|
BLAKE2b-256 checksum How to use checksums |
197e1e560458a9a4c16c22d6aa588a42349a884584855b47c4ef369a814a1701
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.
Transparency logRelease files / factory_sim-0.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl
| Download URL | factory_sim-0.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 808.2 kB |
| Tags | CPython 3.11 Linux glibc 2.17+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
9d11f50a99b9c59b001f9022627fe304f592e684832177d4794658037b7ae3c2
|
|
BLAKE2b-256 checksum How to use checksums |
1aa4833386863fbf853bd0d5aecfe23da0ad31815e26b4288756e8dbec23ccf4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.
Transparency logRelease files / factory_sim-0.2.0-cp311-cp311-macosx_11_0_arm64.whl
| Download URL | factory_sim-0.2.0-cp311-cp311-macosx_11_0_arm64.whl |
|---|---|
| Size | 604.3 kB |
| Tags | CPython 3.11 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
54a1d6d5af66f9f56207033cd8e77d678f706c2fb30638a3ed98ebee26dabd94
|
|
BLAKE2b-256 checksum How to use checksums |
2f47057b27417ee4dd67fa0b87b43aaea5e5c9919293ab7f7ac42ba5be45f254
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.
Transparency log