Skip to content

Save system

Responsibility

The save system persists a world as versioned JSON and supporting files: game/global state, avatar and NPCs, monsters, overmaps, submaps, vehicles, items, activities, factions, missions, map memory, and mod order, while loading older supported representations.

Entry points

Start in src/savegame.cpp, src/savegame_json.cpp, and src/savegame_legacy.cpp. The constant savegame_version, parsed savegame_loading_version, top-level game load/store, and each type's serialize / deserialize pair are the compatibility boundary.

Data ownership

Each runtime owner serializes its durable state; the world directory owns the file set. The save layer coordinates records but must not become a second runtime owner. Caches, pointers, windows, and local coordinate views are reconstructed.

Dependencies

Saving depends on filesystem/path APIs, JSON archives, worldfactory, map/overmap storage, every durable subsystem's serializer, IDs, mod order, and migration/default logic.

Lifecycle

A new world starts at the current version; saves write a version marker and records; loading detects the stored version, applies field defaults and legacy conversions, reconnects IDs and ownership, rebuilds caches, then returns a live world.

Invariants

Loading old supported fields is non-destructive; one object is serialized by its owner; IDs and absolute coordinates remain stable; failed writes do not masquerade as complete saves; and the version only advances with intentional migration support.

Extension points

Add serialization beside the owning type, use named fields and safe defaults, and add explicit version-gated migration only when necessary. Never serialize raw pointers or derived caches.

Serialization

This subsystem is the serialization contract. A field change must document writer, reader, default, old versions affected, removal horizon, and round-trip evidence; deletion or rename requires a compatibility strategy.

Tests

Use focused serializer/world tests and curated old-save fixtures where available. Verify current round trip, absent field, malformed input handling, and the oldest version touched.

Performance

Save/load walks much of the world and can allocate heavily. Keep streaming boundaries, avoid quadratic ID reconnection, and measure large worlds without hiding failures behind timing.

CCB divergence

CCB's current version and legacy readers are authoritative only for CCB. An upstream serializer cannot be copied without comparing field history, mod migrations, and world layout.

Technical debt

Compatibility logic is distributed across type serializers and version checks. Keep each new exception localized and documented; do not perform a broad format rewrite with unrelated work.