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-scopedWorld fragments are shared across the session (world state, game progress). Player fragments exist once per player per slot, addressed by that player's profile GUID. Profile fragments live outside any slot and belong to the local machine, so a character can travel between servers.
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

A slot is a set of independently stored blobs: one header, one blob per global fragment, one per player-scoped fragment, and one per level touched. Where those blobs live is decided by the storage provider, not by the save manager - see Concept: Where Saves Are Stored.

Slot kindIndex rangeNotes
Manual0 and upNamed playthroughs; the only slots GetAllSaveSlotHeaders and GetAllSaveSlotIndices return.
Auto-save10000-10009Rotating; oldest overwritten past MaxAutoSaves. List with GetRotatingSaveSlotIndices(AutoSave).
Quick-save10100-10109Rotating; oldest overwritten past MaxQuickSaves. List with GetRotatingSaveSlotIndices(QuickSave).
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.