Skip to content

ChrisonSimtian/ErpForFactoryGames

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

190 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ERP for Factory Games

CI Latest release GitHub last commit .NET 10 License: MIT Wiki Open issues Open PRs GitHub Sponsors

An ERP-style production planner for factory games — starting with Satisfactory. It plans factories from the inputs you have to the outputs you need, and ingests your live .sav file so it can plan around what's already placed in your world. The planner, persistence, and UI are game-agnostic; per-game adapters (catalogue parsing, save parsing) live in their own module so additional factory games can slot in next to Satisfactory.

🌐 Web home: erp-for-factory.games (each supported game gets a subdomain — satisfactory.erp-for-factory.games is the first). See ADR-0020 for the rebrand decision and what's deliberately out of scope (UI rebrand, namespace refactor).

📖 Deep-dive docs live in the GitHub Wiki. Start with Getting Started, Architecture, Save File Parsing, or LP Planner.

Sponsorship

This project is OSS and runs on out-of-pocket spend. If you find it useful, sponsor via GitHub Sponsors — every contribution goes back into keeping it running.

See where the money goes for the running list of project costs.

What's new in v1.0 — First stable release

v1.0 is the shipped product: a hosted planner at satisfactory.erp-for-factory.games, a winget-installable Windows agent that uploads your catalogue and save files in the background, and an LP planner that picks recipes, miners, generators, and pipe tiers from what you have to what you need.

Planner

  • LP-driven recipe selection — OR-Tools picks recipes, allocates miners to resource nodes, sizes fluid pipes, and emits shadow prices + reduced costs so you can see why a plan is shaped the way it is (#129).
  • Generator-aware planning — pass a PowerTargetMw and the LP picks generator kinds + fuels freely; missing fuel surfaces as a MissingInput rather than infeasibility (#91 / #137).
  • Variance warnings for plans that touch miners or fluid extractors — base-power × count under-reports peak draw by ~50%, so the plan flags it (#91).
  • Fluid throughput constraints — per-item pipe requirements with recommended tier on the resulting plan (#90).

Live factory state

  • Save-file ingestion end-to-end — items, buildings, recipes, conveyor polylines, and pipe polylines (mSplineData deep-parse) all land on the map as GeoJSON (#12 / #138).
  • Auto-ingest — TickerQ background scheduler picks up newer .sav files without manual reload (#115).
  • /dashboard page — glance-able snapshot, auto-refresh, in-game-browser friendly (#131).

Agent + catalogue handover (ADR-0025)

  • Windows agentwinget install ErpForFactoryGames.Agent installs a background service that watches Saved/SaveGames/ and uploads new .sav files plus your Docs.json to your planner account (#201, #205, #210, #228).
  • Per-player catalogue store — planner endpoints resolve the catalogue from the agent's upload instead of a server-local file, with parsed-state cached in memory by DocsHash (#238, #251).
  • erp-agent:// deep-link pairing — mint a token on the My Agents page, click "Open in agent", and the installed agent picks it up via the URL protocol handler (#237, #246). erp-agent --setup is the CLI fallback for headless installs.
  • Re-ingest on demand — "Re-ingest catalogue" button on My Agents flips a sticky flag the agent honours on its next log-tail poll (~60 s), forcing a re-upload regardless of the agent's cached hash (#239, #252).

Hosted deploy

  • satisfactory.erp-for-factory.games runs the planner as Docker containers on the homelab behind a Cloudflare Tunnel (ADR-0023).
  • Tag-to-deploy — pushing a vX.Y.Z tag builds and publishes Docker images; the homelab pulls them on the next compose-up.

Foundations — Onion + CQRS + Wolverine on .NET 10 / Blazor Server / Aspire; MudBlazor 9 UI; PostgreSQL via Npgsql + EF Core with migration-drift CI guard.

Map editing (drag-to-plan, belt reroute, plan-vs-actual diff) is the v2 epic tracked under milestone 14.

See the full backlog at milestones or the wiki Roadmap.

Fancy Charts

Alt

Run it

Requires the .NET SDK pinned in global.json (currently .NET 10 preview). Get it from dot.net or winget install Microsoft.DotNet.SDK.Preview.

You also need the GitHub CLI — the build restores SatisfactorySaveNet from the fork's GitHub Packages feed, which always requires auth (even for public packages). One-time:

gh auth refresh -h github.com -s read:packages

Then:

git clone https://github.com/ChrisonSimtian/ErpForFactoryGames.git
cd ErpForFactoryGames
export GITHUB_TOKEN=$(gh auth token)
dotnet run --project src/AppHost

The Aspire dashboard URL prints in the console — open it and click webfrontend.

For more detail, see the wiki's Getting Started page.

Build / test / format

Everything runs through NUKE — the same commands work locally and in CI:

./build.sh Compile          # restore + build
./build.sh Test             # build + run xUnit suite (TRX → artifacts/test-results/)
./build.sh Format           # dotnet format --verify-no-changes (vendor/ excluded)
./build.sh ComputeVersion   # print the NB.GV-computed version for HEAD

Windows: ./build.ps1 <Target> or build.cmd <Target>. Mac/Linux: ./build.sh <Target> (executable bit is tracked in git). Targets live in build/Build.cs.

UI tests (test/Web/Web.UiTests) drive a real browser via Playwright. ./build.sh Test installs the required chromium build automatically — the InstallPlaywrightBrowsers target runs before Test. If you'd rather pre-install manually:

pwsh test/Web/Web.UiTests/bin/Debug/net10.0/playwright.ps1 install chromium

Game catalogue

The planner reads items, buildings, and recipes from the catalogue JSON shipped with your Satisfactory install. Modern installs use per-locale files (en-US.json, de-DE.json, …); legacy installs had a single Docs.json. Either shape works — point us at the directory and we'll pick en-US.json automatically, or point us at a specific file.

On first run, the app tries to find the catalogue in this order:

  1. ERP_SATISFACTORY_DOCS_PATH environment variable.
  2. A user-saved path (set via the in-app Settings page).
  3. Catalogue:Satisfactory:DocsPath in appsettings.json.
  4. Steam library auto-detect on Windows.

The typical Steam Windows location is:

C:\Program Files (x86)\Steam\steamapps\common\Satisfactory\CommunityResources\Docs

Item icons and other external assets

Per ADR-0016, per-item icons and the wiki map-backdrop source files live in a gitignored .assets/ folder at the repo root, served at /assets/* by the Web project at runtime. A fresh clone has no icons — the Planner picker degrades to text-only until they're downloaded.

To populate .assets/:

# 1. Start the app (the script reads /catalog/items from the running ApiService).
dotnet run --project src/AppHost

# 2. In another shell:
pwsh tools/Update-Assets.ps1

The script pulls icons from satisfactory.wiki.gg with a polite 1 req/s rate-limit. Existing files are skipped — pass -Force to re-download (e.g. after a game patch changed icon art).

Live factory state

The Factory state page (/factory/ingest) ingests a Satisfactory .sav file and surfaces what's actually placed in your world — miners by tier, buildings by type, belts, generators, resource node counts. The save path is resolved via the same chain as the catalogue (ERP_SATISFACTORY_SAVE_PATH env var → app config → auto-detect under %LocalAppData%\FactoryGame\Saved\SaveGames\).

The .sav parser is a forked, v1.2-patched copy of R3dByt3/SatisfactorySaveNet — see the Save File Parsing wiki page or ADR-0014 for the lineage and rationale.

Architecture

  • Onion with CQRS handlers dispatched via Wolverine.
  • Two bounded contexts: ERP (the planner) and Satisfactory (game-specific adapters).
  • Aspire orchestrates local dev across ApiService, Web, and ServiceDefaults.
flowchart TB
    subgraph CR["Composition root"]
        Aspire["AppHost — Aspire"]
        Api["ApiService — Minimal API"]
        Web["Web — Blazor + MudBlazor"]
    end
    subgraph Infra["ERP.Infrastructure"]
        ORTools["OrToolsRecipePlanner"]
        EF["EF Core<br/>SQLite · Postgres"]
        Ticker["TickerQ jobs"]
        SaveAdapter["SatisfactorySaveNet adapter"]
    end
    subgraph App["ERP.Application"]
        Ports["IRecipePlanner · IFactoryStateProvider · IPlanRepository"]
        Wolverine["Wolverine handlers"]
    end
    subgraph Dom["ERP.Domain"]
        Domain["ProductionPlan · ProductionTarget · ..."]
    end
    Aspire --> Api & Web
    Api --> Wolverine
    Web --> Api
    Wolverine --> Ports
    Ports -.->|adapters| ORTools & SaveAdapter & EF
    Ticker --> Wolverine
    App --> Dom
    Infra --> Dom
Loading

And here's what happens when you ingest a save and ask for a plan:

flowchart LR
    Sav[".sav file"] --> Reader["SaveFileReader"]
    Reader --> State["IFactoryStateProvider"]
    Docs["Docs.json"] --> Catalog["Catalog parser"]
    User["User input<br/>Targets · Available · PowerMW"] --> Query["PlanProductionQuery"]
    State --> Query
    Catalog --> Query
    Query --> Bus["Wolverine bus"]
    Bus --> LP["OrToolsRecipePlanner<br/>GLOP solver"]
    LP --> Plan["ProductionPlan"]
    Plan --> ApiOut["GET /plan"]
    ApiOut --> UI["Blazor — /planner · /dashboard"]
    AutoIngest["TickerQ auto-ingest"] -.watches.-> Sav
Loading

All architecturally significant decisions live in docs/adr/. Notable ones:

ID Title
0004 Onion architecture
0005 CQRS in the Application layer
0006 Wolverine as mediator
0009 Runtime catalogue ingestion
0010 Game-agnostic contract
0014 Pure-C# save ingestion

Built on

The headline libraries powering ERP for Factory Games. The wiki pages go deeper into how each one is wired.

Library Role
.NET Aspire Local-dev orchestrator — dotnet run --project src/AppHost
Blazor + MudBlazor Server-side UI (ADR-0002, ADR-0017)
Wolverine In-process CQRS mediator (ADR-0006)
Google OR-Tools (GLOP) The LP planner under OrToolsRecipePlanner
TickerQ Background scheduler — auto-ingest, plan re-optimisation (ADR-0019)
EF Core + Npgsql Dual-provider persistence (SQLite default, Postgres opt-in) (ADR-0018)
Nerdbank.GitVersioning Version stamping from git height + version.json
Playwright UI tests against a real Chromium build (ADR-0008)
xUnit + FluentAssertions Unit + integration testing
OpenTelemetry Traces / metrics / logs from Aspire defaults
NUKE Build automation — same targets locally + in CI

Forks we maintain

We rely on one library that needed game-format work upstream couldn't take on the same cadence as the Satisfactory team's releases — so we maintain a patched fork on GitHub Packages:

Library Upstream Our fork What we added
SatisfactorySaveNet R3dByt3/SatisfactorySaveNet ChrisonSimtian/SatisfactorySaveNet (currently 4.1.3 on GitHub Packages) Save format v1.2 (SaveVersion 60) TOC + Data Blob structure; deep-parse for ObjectProperty, ArrayProperty<ObjectProperty>, ArrayProperty<StructProperty> (incl. pipe mSplineData), StrProperty; chain-actor v1.2 fallback; continuous publish workflow. See ADR-0014 and the Save File Parsing wiki page.

The fork is consumed as a PackageReference from the fork's GitHub Packages feed (see nuget.config). GitHub Packages NuGet always requires auth, even for public packages — set GITHUB_TOKEN before restoring:

export GITHUB_TOKEN=$(gh auth token)   # token needs read:packages
dotnet build ErpForFactoryGames.slnx

The submodule at vendor/SatisfactorySaveNet/ is an optional local drop-in for fork iteration; it's marked update = none + ignore = all and not required for the main build path.

Project layout

src/
  AppHost/                # Aspire orchestrator — `dotnet run` entry point
  ApiService/             # Minimal-API backend
  Web/                    # Blazor Server UI (FICSIT-themed)
  ServiceDefaults/        # Aspire defaults
  ERP/
    Domain/               # Pure entities
    Application/          # Ports + CQRS handlers
    Infrastructure/       # Adapters + DI wiring
  Satisfactory/
    Catalog/              # Docs.json parser
    Save/                 # .sav parser (wraps the SatisfactorySaveNet fork)

test/                     # xUnit projects per layer
build/                    # NUKE C# build scripts
vendor/SatisfactorySaveNet # Forked .sav parser submodule
deploy/Homelab.Stacks.ErpForFactoryGames # Compose stack submodule (off the build graph)
docs/adr/                 # Architecture decisions
.claude/                  # Claude Code conventions for this repo

CI / versioning / releases

Every push to main and every PR targeting main runs:

  • Lintdotnet format --verify-no-changes (Ubuntu)
  • Build & Test — restore + build + xUnit (Ubuntu). OS-specific regressions surface locally on Windows/Mac before reaching CI.
  • Publish test results — TRX files surface as a commit/PR check

Pushes to main additionally trigger:

  • Release — auto-creates a GitHub release tagged with the Nerdbank.GitVersioning-computed version (v0.1.N). Release notes auto-generated from PRs since the previous tag.

Bump the major/minor by editing version.json — the next commit becomes vX.Y.0. Patch increments per commit automatically.

main is protected: PRs require all four matrix checks to pass before merging.

Self-hosting

The Blazor apps run on Chris's homelab as Docker containers behind a Cloudflare Tunnel — see ADR-0023 for the why and docs/operations/deploy.md for the tag-to-deploy runbook.

The compose stack lives in a sibling repo, Homelab.Stacks.ErpForFactoryGames, attached here as a submodule at deploy/Homelab.Stacks.ErpForFactoryGames/. Like the vendor/* submodules it's off the build graph (update = none, ignore = all); fetch it explicitly only when you want to operate the homelab:

git submodule update --init --checkout deploy/Homelab.Stacks.ErpForFactoryGames

Backlog

Conventions

Repo-level Claude conventions live in CLAUDE.md and .claude/ — repo layout, onion rules, the ADA in-game assistant agent. Contributor workflow (branch names, commit style, CI gates) lives on the wiki: Contributing.

About

ERP-style production planner for factory games — starting with Satisfactory.

Topics

Resources

License

Stars

Watchers

Forks

Sponsor this project

 

Packages

 
 
 

Contributors