Concept: The Fragment Model

A save slot is not one file - it is a directory of fragments. Each registered system owns one fragment; the world subsystem owns per-level slabs; a header holds the slot's metadata. Nothing reads another system's fragment, which is what keeps plugins decoupled.

Glossary

ConceptDescription
FragmentOne .sav file per system per save slot. Written and read independently from all other systems.
Auto-serializationAny UPROPERTY(SaveGame) field on the registered object is captured automatically via the engine's proxy archive - no custom fragment subclass needed.
Custom fragmentA UCrimsonSaveGameFragmentBase subclass for data auto-serialization can't hold (UObject references, GAS state, computed values). Runs alongside auto-serialization.
Global vs player-scopedGlobal fragments are shared across all players (world state, game progress). Player-scoped fragments are keyed per-player and stored as FragmentName__PlayerKey.sav.
Save slotA directory under Saved/SaveGames/<Slot>/ containing all fragment files, world slabs, and a header. One slot = one playthrough.
HeaderA metadata .sav per slot for the save-menu UI - display name, date, play time, slot type, optional thumbnail.

How a save runs

  1. Register - each system calls RegisterSaveableSystem on startup and returns a unique GetFragmentName.
  2. Gather - on save the manager captures every SaveGame property into a blob and calls GatherSaveData for an optional custom fragment. Both run for every system.
  3. Write - the manager writes the header, one .sav per fragment, and (with bIncludeWorldStateInMainSave) the world slabs.
  4. Match - on load each fragment file is matched to its registered system by name.
  5. Restore - the manager deserializes the SaveGame blob, then calls RestoreFromSaveData for anything the custom fragment holds.
Auto-serialization vs custom fragment
You never choose one or the other - they run together. GatherSaveData returning nullptr just means "nothing beyond my SaveGame fields." Start with SaveGame properties; add a custom fragment only for what they can't hold. See How-To: Custom Fragments & Versioning.

Saves and loads are asynchronous

Loads complete through delegates, not synchronously. Gate "loaded" UI and screen transitions on OnLoadComplete, and rebuild slot lists on OnSaveSlotListChanged. The one synchronous entry point is SaveGameToActiveSlotSynchronous, used for save-on-exit. See How-To: Handle Save/Load Errors.

On-disk layout

Everything for a slot lives under Saved/SaveGames/<Slot>/: a header file, one FragmentName.sav per global fragment, FragmentName__PlayerKey.sav per player-scoped fragment, and WorldSlab_<Level>.sav per level touched.

Slot kindIndex rangeNotes
Manual0 and upNamed playthroughs; the only slots GetAllSaveSlotHeaders returns.
Auto-save10000-10009Rotating; oldest overwritten past MaxAutoSaves.
Quick-save10100-10109Rotating; oldest overwritten past MaxQuickSaves.
One save saves everything
With bIncludeWorldStateInMainSave (default), a single RequestSaveProgress persists both registered systems (fragments) and world actors (slabs), and loading a slot restores both. See Concept: World State & Identity.