Saves, rewind and sharing¶
Axiom is event-sourced: every turn appends events (player input, narrative
text, stat changes, time…) to a journal, the Event_Log. The visible game
state is derived from that journal. This one design decision buys most of
the engine’s tricks:
Rewind — drop the events after turn N and rebuild the state: you are back at turn N.
Fork — copy a save with its journal truncated at a point: an alternate timeline.
Inspect anywhere — materialise the state at any past turn or in-game minute without touching the save.
Edit safely — corrections are applied on top of the journal, never by mutating derived state.
Where saves live¶
The universe definition and the play-throughs are separate things:
~/AxiomAI/
├── universes/<name>/… # definition (source tree + cache)
└── saves/<universe key>/save_<uuid>.db # one file per save (runtime state)
Each save database is self-contained: it carries a copy of the universe definition taken at creation time. Patching the universe never breaks an existing save — the definition copy is resynchronised when the save is opened, while the runtime state survives. A save is therefore a single portable file.
Pulling new Axiom code does not delete saves. They live under
~/AxiomAI/saves/, not in the git tree. The first time you open a save after
an upgrade, migrate_schema runs in place: new tables/columns are added, a
flat inventory bag is copied into the nested Item_Instances tree, and
play-promoted lore that used to sit in Lore_Book is moved to Session_Lore
before a world refresh can wipe it. A one-time save_<id>.db.pre-migrate.bak
is written next to the file on that first structural migrate. Journal,
stats, and turns stay. If migrate itself throws, the save still opens.
(Saves created by very old versions live inside the universe .db; they
remain listed and playable as-is.)
Rewind and checkpoints¶
From Python, rewind is one call on the session:
session.rewind(target_turn_id) # back to that turn
session.list_checkpoints() # every recorded turn you can rewind to
A rewind is comprehensive. axiom.checkpoint.CheckpointManager
drops the Event_Log after turn N and rebuilds every derived view to its
turn-N state in a single transaction:
the materialised stats (
State_Cache), theTimeline, periodicSnapshots, and — in living mode — the structured facts and beliefs;temporary modifiers (buffs/debuffs): these decay in in-game minutes and are not event-sourced, so the engine snapshots the modifier table each turn and restores the one captured at turn N;
the nested inventory (
Item_Instances): same idea, one snapshot of the whole tree per turn (Inventory_Snapshots), so an item gained, lost or moved after turn N goes back to where it was — containers keep their contents. Turns played before inventory snapshots existed have none: rewinding to such a turn leaves the inventory as it is. Viewing or forking a save at a past turn uses the same snapshots;scheduled events: any event that fired after turn N is un-fired, so it triggers again once the in-game clock next crosses its minute.
The semantic memory store is rolled back alongside via
axiom.memory.VectorMemory.rollback(), which removes every narrative
chunk from a later turn. Nothing from a later turn leaks back: buffs, items,
world events and remembered facts all return to exactly how they stood at turn N.
In Hardcore mode, death deletes the save — that is the point of Hardcore.
Inspecting and editing a save¶
The axiom save-* commands (see the CLI guide) expose the editing
toolbox. Points in time are selected by turn (--turn N) or by in-game
minutes (--minute M).
$ axiom save-show MyWorld.db <save_id> --turn 12 # materialised state
$ axiom save-export MyWorld.db <save_id> state.toml # state -> editable TOML
$ axiom save-import MyWorld.db state.toml # TOML -> new save
$ axiom save-fork MyWorld.db <save_id> --turn 10 # alternate timeline
$ axiom save-edit MyWorld.db <save_id> patch.toml # in-place correction
The editable save_state.toml format:
[save]
player_name = "Alice"
difficulty = "Normal"
player_persona = "A reformed clockwork thief."
[point] # informative at export time
turn_id = 12
in_game_minutes = 540
[state.innkeeper] # effective entity stats
Health = "100"
Mood = "friendly"
[[inventory]] # nested: holder is entity, location, or container instance
item_id = "rusty_sword"
quantity = 1
holder_kind = "entity"
holder_id = "player_alice"
entity_id = "player_alice" # legacy alias for holder_kind=entity
[[inventory]]
item_id = "purse"
quantity = 1
holder_kind = "entity"
holder_id = "player_alice"
is_container = true
[[session_lore]] # this playthrough only — not the world lore book
entry_id = "harbour-rumour"
category = "rumour"
name = "The sealed contract"
keywords = "bunker, contract, highport"
content = "Brask is recruiting and will not name her charter."
[[modifiers]]
entity_id = "player_alice"
stat_key = "Strength"
delta = 2
minutes_remaining = 120
Old flat [[inventory]] rows (entity_id / item_id / quantity only) still
import as items carried on that entity.
On the web Hub, Edit opens a structured editor (entities, nested inventory,
session lore, temporary modifiers) with the TOML above as the Advanced tab. Qt
still uses the TOML dialog. Both apply through the same apply_correction path.
Stats the editor shows are the live play values (Event_Log replayed on top
of authored Entity_Stats), not the universe template. Stat keys are matched
case-insensitively: a definition id arousal and an authored key Arousal are
the same field.
Temporary stats are marked on the definition (Creator checkbox, Infer, or
play-start classification). The engine ticks them from that profile: heal
toward resting over days, hold a buildup at max for a few minutes, crash on a
listed event. Extra short overlays still live in [[modifiers]] / the
Temporary tab and count down in in-game minutes. The narrator emits
stat_events (and optional modifiers); it does not invent a decay schedule.
Two rules keep editing safe:
The journal stays the source of truth. Imports create a new save whose journal starts with “genesis” events at turn 0; exports materialise the state by replaying the journal.
An imported save starts with an empty vector memory — it fills up again as you play.
Session lore (Session_Lore) is runtime data on the save. World refresh
and a new game do not see it. Promoting a fact into the universe still goes
through Canonize with world scope.
From Python¶
The same operations are exposed by axiom.savestore (separate save
files, archives) and axiom.saves (materialise / fork / import /
edit), with axiom.checkpoint.CheckpointManager underneath.