Concept: World State & Identity

World-actor state is stored in slabs - one UCrimsonSaveWorldData per level - held at UCrimsonSaveGameManagerSubsystem (GameInstance) scope, so they outlive any single level or UWorldSubsystem and survive streaming and seamless travel. Each slab is a TMap<FGuid, FCrimsonSaveWorldActorData>. An actor opts in by implementing ICrimsonSaveableActor; on every save the world subsystem scans loaded levels and captures each one automatically.

What "automatic" means
Capture and restore are automatic once the interface is implemented. OnActorStateChanged / OnActorDestroyed are optional incremental hooks, not a requirement. They change when a change is recorded, not whether your actor is captured - and neither writes to disk. FlushDirtyWorldState() is what writes, and it writes only the actors that changed.

What is captured automatically

CapturedNotes
TransformMovable actors only (root mobility != Static).
Actor UPROPERTY(SaveGame) fieldsThe actor's own tagged properties, via the engine proxy archive.
Component UPROPERTY(SaveGame) fieldsCascades into every component - state on health / inventory / movement components persists with no extra code.
Non-property stateHidden flag, physics & movement velocities, and pawn controller rotation - no SaveGame flag needed.
Cross-object referencesSaveGame AActor* / TWeakObjectPtr<AActor> pointers are stored as stable GUIDs and re-resolved to live instances after load (same level or persistent level).
Custom payloadOptional FInstancedStruct from GatherActorSaveData for anything beyond the above.

Identity

Every saveable actor is keyed by a stable GUID so it can be matched across sessions. Getting this right is most of what makes world saving work: an identity that shifts means an actor silently loses its history, and an identity two actors can share means one inherits the other's.

Actor kindIdentity key
Level-placed, with a Crimson Persistent Id componentThe id baked into the level by that component - the engine's own ActorGuid, carried through the cook. Survives renaming the actor, moving it between World Partition cells, rebuilding the grid, and an actor being deleted and its name later reused.
Level-placed, without oneA GUID derived from the actor's authored object path, normalized so a World Partition cell name never enters it. Stable against everything above except name reuse - see the warning below.
Runtime-spawnedA session FGuid assigned by the subsystem; the actor's class (TSoftClassPtr) is recorded so it can be re-spawned on load.
Add a Crimson Persistent Id component to actors that must never lose their state
It is one component with no configuration and no runtime cost: in the editor it copies the actor's engine-assigned ActorGuid into a normal property, which the cook keeps. That is the whole trick - the engine's own copy is editor-only and does not exist in a packaged build, which is why a save system cannot simply read it.

ACrimsonPersistentActor already carries one. Add it to any other saveable actor. The id populates when the level is next saved in the editor, so a map authored before this existed keeps using the path fallback until you resave it.
Without the component, object-name reuse can cross-wire saved state
Unreal only guarantees globally unique actor names in levels that use One File Per Actor - which World Partition turns on, and a classic sublevel does not. In a non-OFPA level, deleting StaticMeshActor_3 frees that name, and the next actor to take it inherits the deleted actor's saved state. The fallback path cannot tell the two apart, because from its side they are the same actor. The id component is the fix, and it is why it is worth adding rather than relying on the default.

Capture and restore flow

  1. Save - the subsystem scans loaded levels for ICrimsonSaveableActor actors and writes each into its level slab: transform, the actor's SaveGame blob, per-component SaveGame blobs, non-property state, and cross-object reference GUIDs.
  2. Flush - slabs are written to WorldSlab_<Level>.sav in the active slot (as part of the main save, or via SaveWorldState).
  3. Load - slabs are read back into memory when the slot loads; each level's slab is applied when the level is present (LoadWorldState, or automatically as sublevels stream in).
  4. Resolve - after every actor in a level is restored / re-spawned, a deferred pass rewrites SaveGame actor-pointer properties from their stored GUIDs to the live instances.
Cross-object references
Because resolution runs after all actors in a level exist, references between actors in the same level (or to the persistent level) round-trip. References into a different streamed sublevel that is not loaded at restore time cannot be guaranteed, and are left null and logged. See How-To: Save Cross-Object References.

Deletion

Destruction is recorded from the engine's actor-destroyed event on the server, so only a real Destroy() removes an actor from the save - an actor simply being absent does not.

Actor kindOn destroySave-file cost
Runtime-spawnedIts slab entry is removed entirely - it simply stops being saved.None - a removed entry means nothing to re-spawn.
Placed (level)A minimal GUID-only tombstone is written; the copy reloaded from the level is destroyed on load.A few bytes, bounded by how many map actors you actually delete.
Opt into respawning
Override ShouldRespawnAfterDestroyed() (default false) and return true to skip the tombstone for a placed actor, so it respawns from the level on the next load. Runtime actors ignore this flag. See How-To: Persist a Level Actor.

ICrimsonSaveableActor overrides

All interface methods are optional BlueprintNativeEvents. Implement the interface and you are done; override these only for custom state or lifecycle handling.

OverrideWhen used
GatherActorSaveData()Return an FInstancedStruct for data that cannot be expressed as UPROPERTY(SaveGame). Empty (default) means only auto-captured state is saved.
RestoreFromSaveData(ActorData)Receives the FInstancedStruct. Transform, SaveGame fields, and component state are already restored before this runs.
OnPreRestoreState()Before any restoration. Disable physics, clear containers.
OnPostRestoreState()After all restoration. Re-enable physics, broadcast events.
ShouldRespawnAfterDestroyed()Return true so a destroyed placed actor respawns from the level instead of staying deleted. Default false. Runtime actors ignore it.

Streaming & World Partition

A sublevel's state is applied when it streams in and its actors are re-captured just before it streams out, so revisiting a region restores it. World Partition runtime cells stream as levels through that same path, and nothing here treats them as a special case - there is no World Partition setting to enable, because there is no World Partition code path to enable.

Why a partitioned world needs no special handling
A World Partition cell is a generated level: its package name encodes the grid, the coordinates and the data layer, and it is rewritten whenever the grid is rebuilt. Anything that keyed off it would lose every actor's history the first time a cell size changed.

So nothing does. Both the actor's identity and the level its state is grouped under normalize the generated segment away, leaving the world that owns the cell - which is authored, and therefore stable. A world that is not partitioned has no such segment and passes through the same code unchanged. This mirrors the engine's own FWorldPartitionHelpers::ConvertRuntimePathToEditorPath.
Applying a level costs what streamed in, not what you have saved
Restoring a level walks that level's actors and looks each one up, rather than walking the saved state and hunting for a matching actor. In a partitioned world, where cells stream in and out continuously, the difference is between a cost set by the size of the cell and one set by the size of everything you have ever saved.
Server authority
All capture, restore, and runtime respawn happen on the server. Restore forces a net update; clients receive restored state through normal replication and never spawn replicated actors themselves. A SaveGame-only property never reaches clients unless it is also Replicated - see Concept: Multiplayer & Save Scopes.

Driving world state manually (optional)

With bIncludeWorldStateInMainSave (default), a normal save persists world actors too. To drive world state on its own:

Reach the subsystem with Get World Subsystem (Class = Crimson Save World Manager Subsystem), then drag off its output for the call you want. Save World State captures every saveable actor in every loaded level and writes the slabs. Load World State applies them, re-spawns runtime actors, and runs the cross-object reference resolve pass. Flush Dirty World State writes only what changed since the last flush.

On Actor State Changed (In Actor, defaulting to Self) is the optional incremental capture of a single actor, and On Actor Destroyed (In Actor) its optional destruction counterpart - see How-To: Configure Auto-Saving. Every one of these is server-authority only, so gate the chain on Has Authority (Branch, True). With bIncludeWorldStateInMainSave at its default you need none of them.