# Anatomy of a Factorio mod (2.0 / Space Age) — reference

> Research (Grok 4.3 live web, 2026-06-23) cross-checked against Claude's knowledge. For the
> deterministic-quality mod project — see `DESIGN.md`.

## 1. Package
- Mod = folder or zip. **Distribution zip:** `modname_1.2.3.zip` containing one top folder
  `modname_1.2.3/`. **Dev:** an unzipped folder (can be named just `modname`) in the mods dir.
- Mods dir: Windows `%APPDATA%\Factorio\mods`, Linux `~/.factorio/mods`, macOS
  `~/Library/Application Support/factorio/mods`. `mod-list.json` tracks enabled mods.
- Folder/zip name must agree with `name` + `version` in `info.json`.

## 2. `info.json` (mod root)
- **Required:** `name`, `version`, `title`, `author`. **Always set** `factorio_version` ("2.0").
- `dependencies` prefixes: *(none)* = required · `!` incompatible · `?` optional · `(?)` hidden
  optional · `~` required but does NOT affect load order. Version ops `>= <= = < >`. `base` is
  auto-depended unless overridden.

## 3. Load stages (strict phases, across all mods at once, dependency-ordered)
| Stage | Files (in order) | Purpose | Game access? |
|---|---|---|---|
| **Settings** | `settings.lua` → `settings-updates.lua` → `settings-final-fixes.lua` | Define mod settings; 3 types: `startup`, `runtime-global`, `runtime-per-user` | No |
| **Data (prototypes)** | `data.lua` → `data-updates.lua` → `data-final-fixes.lua` | Build prototypes into global `data.raw` via `data:extend{...}` (items/entities/recipes/tech/…). Build-once, frozen. Can read other mods' prototypes + `settings.startup`. | **No** (no game/state) |
| **Control (runtime)** | `control.lua` (+ `require`d files) | Only stage that runs in a loaded save. Event-driven scripting. | **Yes** |

**Control essentials:**
```lua
script.on_init(fn)                 -- once, new save / mod added
script.on_load(fn)                 -- every load; MUST NOT write storage
script.on_configuration_changed(fn)-- mod/version/prototype change → migrations
script.on_event(defines.events.on_tick, fn)
```
- **`storage`** = persistent per-mod saved table. **(Was `global` in 1.1 — renamed in 2.0.)**
- Also `remote` (inter-mod), `commands` (custom `/cmds`), `game`, `defines`.
- Data stage CANNOT touch runtime; control stage CANNOT define prototypes.

## 4. Other standard files
- `locale/<lang>/*.cfg` — INI with `[category]` sections (`[item-name]`, `[entity-name]`,
  `[recipe-name]`, `[mod-setting-name]`, …).
- `graphics/`, `sounds/` — assets.
- `migrations/` — `*.lua` run as control code on loading an older save; `*.json` for prototype renames.
- `changelog.txt` — strict format: 99-hyphen separators, `Version:` / `Date:` headers, 2-space
  category headers (`Bugfixes:`), 4-space `- ` entries.
- `thumbnail.png` — mod portal (~144px+).

## 5. Asset paths
`"__modname__/graphics/x.png"`. Specials: `__base__` (base game assets/prototypes), `__core__` (engine).

## 6. Settings access from control.lua
`settings.startup["k"].value` · `settings.global["k"].value` ·
`settings.get_player_settings(player)["k"].value`.

## 7. Tooling
- `lua-api.factorio.com` — versioned; **prototype docs (data) vs runtime API docs are separate**.
- VS Code + **FMTK** (Factorio Modding Tool Kit) + sumneko Lua → autocomplete/diagnostics.
- Wiki `Tutorial:Modding`; portal `mods.factorio.com`. Debug: `log()`, `serpent.block()`, F4 menu,
  `--instrument-mod`, `/c` console.

## 8. 2.0 vs 1.1 must-knows
- `global` → **`storage`**.
- **Quality / Space Age / Elevated Rails are separate mods** — depend on `quality` / `space-age`
  explicitly when using their features.
- Versioned API docs; `factorio_version: "2.0"`.
- Quality fields touch nearly every item/entity prototype; rail/fluid/inserter changes; Space Age
  adds planets/space platforms.

## Minimal skeleton
```
my-mod/
├─ info.json
├─ settings.lua        (optional)
├─ data.lua
├─ control.lua
├─ changelog.txt
├─ thumbnail.png
└─ locale/en/locale.cfg
```
