Skip to content

Game save support: read a world from a .sav and apply it to the plan #584

Description

@Maelstromeous

Upload a Satisfactory save, read the facts about that world out of it, hold them on the plan, and let the planner check the plan against them.

Today the planner assumes one world: the default vanilla map, every recipe unlocked, every building available. Both halves of that assumption are wrong for most people, and silently so.

  • Raw Resources (feat: measure a plan against what the map actually holds #602) measures a plan against a hand-typed table of the vanilla map. A 1.2 world generated with node randomisation or one of the resource-rich presets has different nodes, so every figure in that table is wrong for it, and nothing on screen can tell.
  • Every "at 250%" ceiling assumes a Miner Mk.3. On a save without Mk.3 unlocked the real ceiling is half what we show.
  • A plan can use the Converter, the Resource Well Pressurizer or an alternate recipe the player has never unlocked, and the planner will happily balance it. It is not buildable and we never say so.

This replaces the Import world [WIP] button and WorldImport.vue, which currently only counts Somersloops.

What a save actually contains

Verified by parsing four 1.2 saves (a vanilla new game, a node-randomised one, a fossil-fuel-rich one, and a real 8-tier world).

World generation — on BP_GameState_C:

Property Example
mNodeRandomization ENodeRandomizationMode::NRM_Strict, NRM_FossilFuelRich
mNodePuritySettings ENodePuritySettings::NPS_AllRandom
mNodeRandomizationSeed 162272096

Per-node overrides — every BP_ResourceNode and BP_FrackingSatellite carries mResourceClassOverride (an ObjectProperty naming Desc_OreCopper_C and so on) and mPurityOverride (RP_Inpure / RP_Normal / RP_Pure). A full per-node resource and purity table, no seed reproduction needed.

Progression — SchematicManager.mPurchasedSchematics gives milestones by tier (Schematic_4-2), MAM and hard-drive schematics; RecipeManager.mAvailableRecipes gives every recipe currently buildable including buildings and alternates; GamePhaseManager.mCurrentGamePhase gives the phase.

A real save read this way: phase 3, 38 milestones across 8 tiers, 411 recipes, 52 alternates, and no Resource Well Pressurizer, Miner Mk.3, Converter, Particle Accelerator or Quantum Encoder.

A save is a diff, not a snapshot. A vanilla save contains none of the above — no overrides, not even the settings properties, because nothing differs from the level defaults. So the parser cannot replace world-resources.ts; it patches it. The hand-typed table stays as the baseline.

Scope

Part 1 — ingest and state (the bulk of the work)

  • Parse a .sav in the browser, locally. Nothing is uploaded anywhere.
  • Extract: world generation settings, per-node resource + purity, purchased schematics, available recipes, game phase, and extractors already built on nodes.
  • Store the result on the plan, persisted with it, versioned so an old snapshot survives a format change.
  • Feed it into the calculation engine behind a toggle: plan-only (what we do today) or plan-against-this-world.

Part 2 — UI

  • Drop zone replacing the current dialog, re-uploadable as the world progresses.
  • The toggle, and a clear indication of which mode is on.
  • Raw Resources reads the world's real node counts and its best available extractor.
  • Products and buildings flag what the save cannot build yet.

Probably one PR — the state layer is untestable without something consuming it — but Part 1 should land reviewable on its own inside it.

Test fixtures

Commit real saves so the parser is tested against actual files rather than fabricated bytes. Recommend:

  • One small vanilla new game (~140 KB) for the parse path and the "no overrides" case.
  • One node-randomised and one resource-rich save for the override paths.
  • For progression, a JSON fixture extracted from a progressed save rather than the save itself — a real 8-tier world is ~10 MB and does not belong in the repo.

Open questions

  • Does sav2json@1.0.1 still work? It predates 1.0. A hand-rolled parser reads these 1.2 saves correctly in ~120 lines (header list, then length-prefixed data blocks in the same order); worth measuring against the dependency before committing to either. Whatever wins must handle the ~10 MB real-world case without locking the tab — likely a Web Worker.
  • Where does the snapshot live — on the plan (syncs, shareable, bigger payload) or in localStorage beside it?
  • What does the toggle do to a plan that becomes invalid when it goes on? Warn, or refuse to apply?
  • Does a shared plan carry its world with it?

Out of scope

#437 (recipe cost and power multipliers). Those settings are not written to a save at default, and the work here is a natural place to hang them later.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Annoying but not fundamentally world ending bugs, moderate functionality gapenhancementNew feature or requestwebsiteIssues relating to the website

    Projects

    • Status
      No status

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions