# Working On Factorio

> Factorio plugin development for Savecraft. Use when working on files in plugins/factorio/, including the Lua mod (control.lua, info.json), WASM parser, reference modules (recipe_lookup, ratio_calculator), datagen pipeline, sprite sheet generator, or Factorio-specific views. Triggers on Factorio plugin code, Lua mod API, ratio calculator, recipe lookup, production chains, datagen, sprite sheets, or Factorio game data.

- Skill: `joshsymonds/working-on-factorio` (Agent Skill)
- Install (CLI): `npx skillmds@latest add joshsymonds/working-on-factorio`
- Raw SKILL.md: https://api.skillmd.com/api/skills/joshsymonds/working-on-factorio/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: joshsymonds (https://skillmd.com/u/joshsymonds)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/joshsymonds/working-on-factorio

---


# Working on the Factorio Plugin

Factorio uses a **hybrid mod + daemon** architecture — unique among Savecraft plugins. A Lua mod writes JSON to `script-output/savecraft/`, the daemon watches via fsnotify, and a thin WASM parser converts to ndjson. Design doc: `plugins/factorio/README.md`.

## Architecture

```
plugins/factorio/
├── mod/                    # Factorio Lua mod (control.lua, info.json)
├── parser/                 # WASM pass-through parser (JSON → ndjson)
├── reference/              # WASM reference modules (recipe_lookup, ratio_calculator)
│   ├── data/               # Generated Go struct literals (*_gen.go)
│   └── views/              # Svelte reference views (ratio-calculator.svelte)
├── tools/
│   ├── datagen/            # Parses data-raw-dump.json → generated Go files
│   └── spritesheet/        # Packs icon PNGs into sprite sheet + manifest
├── sprites/                # Generated sprite sheets (items.png, fluids.png + JSON manifests)
├── plugin.toml             # Plugin metadata, sources = ["wasm", "mod"]
└── Justfile
```

## Verification

```bash
cd plugins/factorio
just test                   # Parser + reference module tests (32 tests)
just build                  # Build parser.wasm + reference.wasm
just datagen                # Regenerate from data-raw-dump.json
just spritesheet            # Regenerate sprite sheets from .reference/ PNGs
```

Full suite: `just test-go` (all Go) + `just test-worker` (all Worker) + `just build-views` (view compilation).

**View changes require `just build-views` + committing `views.gen.ts`.** See `working-on-views` skill for details. CI does not rebuild views — forgetting this ships stale HTML.

## Key Conventions

### Recipe Disambiguation

**Never guess which recipe to use.** When multiple non-recycling recipes produce the same item (e.g., solid-fuel from 3 oil sources), `resolveRecipe()` returns an error listing the options. The AI uses `recipe_lookup` (product query) to find options, picks contextually, and passes the explicit recipe name via the `recipe` or `recipe_overrides` parameters.

### Anti-Hallucination Modules Have No Views

`recipe_lookup` and `tech_tree_navigator` return data for the LLM to reason over. No player-facing visualization — the AI narrates results. Views exist only for modules whose output the player needs to SEE (ratio_calculator, oil_balancer, etc.).

### Shared Chart Components

`FlowChart` lives in `views/src/components/charts/` (shared, game-agnostic Sankey-style flow visualization). Factorio-specific components (`ProductionChain`, `MachineNode`, `factorio-colors.ts`) live in `plugins/factorio/components/`. `FactorioIcon` is in `views/src/components/factorio/`.

## Data Pipeline

### Source Data (from Steam Deck)

```
.reference/factorio-data-raw-dump.json    # factorio --dump-data (27MB, all prototypes)
.reference/factorio-sprites/{item,fluid,...}/  # factorio --dump-icon-sprites (64x64 PNGs)
.reference/factorio-locale/*-locale.json  # factorio --dump-prototype-locale (display names)
.reference/factorio-saves/                # Test save files
```

### Extracting Fresh Data

```bash
# SSH to Steam Deck (deck@172.31.0.39, password in memory)
# WARNING: Factorio CLI commands may steal focus from active games on the Deck
nix-shell -p sshpass --run 'sshpass -p "..." ssh deck@172.31.0.39 \
  "~/.steam/steam/steamapps/common/Factorio/bin/x64/factorio --dump-data"'
# Then SCP from ~/.factorio/script-output/
```

### Datagen Flow

```
.reference/factorio-data-raw-dump.json
  → go run ./plugins/factorio/tools/datagen/
  → plugins/factorio/reference/data/*_gen.go
    recipes_gen.go    (659 recipes)
    technologies_gen.go (275 techs)
    machines_gen.go   (17 crafting machines)
    modules_gen.go    (12 modules)
    logistics_gen.go  (belts, inserters, beacons)
    fluids_gen.go     (33 fluids)
  → compiled into reference.wasm (GOOS=wasip1 GOARCH=wasm)
```

**Factorio data quirk:** Empty collections are `{}` (object) not `[]` (array). The datagen tool handles this with `parseStringArray()`.

### Sprite Sheet Flow

```
.reference/factorio-sprites/item/*.png (340 icons, 64x64)
  → go run ./plugins/factorio/tools/spritesheet/
  → plugins/factorio/sprites/items.png (2048x704, 2.3MB)
  → plugins/factorio/sprites/items.json (manifest: name → {x,y,w,h,label})
```

Labels come from `.reference/factorio-locale/item-locale.json`.

## Lua Mod (Factorio 2.0 API)

### Critical 2.0 Renames

| 1.x | 2.0 |
|-----|-----|
| `game.write_file` | `helpers.write_file` |
| `game.table_to_json` | `helpers.table_to_json` |
| `game.item_prototypes` | `prototypes.item` |
| `global` | `storage` |
| `force.evolution_factor` | `force.get_evolution_factor(surface)` |

### Mod Structure

- `mod/info.json` — mod metadata, `factorio_version: "2.0"` (two-part only)
- `mod/control.lua` — `script.on_nth_tick()` hooks: lightweight stats every 300 ticks (5s), heavy entity scans every 1800 ticks (30s)
- Output path: `script-output/savecraft/state.json`
- **Mod is untested in-game** — API calls are based on research, not runtime verification

## Reference Module Architecture

Go WASM with baked-in data (RimWorld pattern, NOT native TypeScript like MTGA). Entry point: `reference/main.go` with query routing.

### Ratio Calculator Formulas

```
effective_speed = machine.CraftingSpeed × (1 + module_speed + beacon_speed)
beacon_speed = Σ(module_effect × dist_effectivity / √n) across all beacons
output_per_craft = result_amount × (1 + productivity_bonus)
items_per_sec = (effective_speed / craft_time) × output_per_craft
```

**Productivity does NOT increase ingredient consumption** — only gives free bonus output. This is validated by `TestValidation_ProductivityModules_AM3`.

### Adding New Reference Modules

1. Add handler function in `plugins/factorio/reference/`
2. Wire into `main.go` switch statement
3. Add to `schema()` in `main.go`
4. Add to `[reference.modules.*]` in `plugin.toml`
5. If player-facing: add view in `plugins/factorio/reference/views/`

## Attribution

`views/src/attributions.ts` has a `wube` entry for Factorio content. Plugin.toml declares `[attribution] sources = ["wube"]`.

