NarraLeaf

SavedGame

This page is under construction.

Beta feature, subject to change.

interface SavedGame {
    name: string;
    meta: SavedGameMetaData;
    game: {
        store: { [key: string]: SerializedNamespaceData; };
        elementStates: RawData<ElementStateRaw>[];
        stage: PlayerStateData;
        services: { [key: string]: unknown; };
        stackModel: StackModelRawData;
        asyncStackModels: StackModelRawData[];
        history?: SerializedGameHistory[];
    };
}

Note: The save format is versioned through meta.version (currently 3). Saves written before the backlog was persisted omit history and load with an empty backlog.

Save format v3 (0.26.0): an older engine cannot read a v3 save correctly. It applies the elementStates entries and skips the reset the format now relies on, so elements the save leaves out keep whatever the running session put in them. The other direction is fine — v1 and v2 saves load unchanged in 0.26.0.

name

The name of the saved game

meta

The metadata of the saved game, see SavedGameMetaData

game

Game data of the saved game

store

Every namespace's contents, keyed by namespace name. Values are stored in their tagged form, see SerializedNamespaceData.

This covers both the namespaces registered with story.registerPersistent and the scene locals behind scene.local.

Read this through Storable / Namespace rather than by hand. A stored value is not the value itself but a {type, data} wrapper, which is what lets a Date survive JSON.

elementStates

The elements whose state differs from what the script wrote, each entry carrying the element's id alongside its data.

Leaving an element out is not a loss of information: loading resets every element before applying the save, so an element the save does not name is restored by being reset rather than left holding whatever the running session put in it. What differs at any moment is small, because leaving a scene already returns everything that scene put on stage to its authored state — in practice this is the current scene's elements plus the few that outlive a scene by design, such as the story camera and sounds still playing.

Before 0.26.0 this listed every element of every scene the story can reach, so a project's whole cast was written into every save and into every per-line history snapshot besides, at a cost that grew with the size of the project rather than with what was on stage.

An element written from outside the engine's action dispatch is not seen. An element is considered for the save when an action runs against it; a host that writes element state directly — through DevTools, say — should call element.markDirty(). With app.debug: true, the engine periodically walks every element and warns, naming any whose state has drifted with nothing marking it, and marks them so the next save carries them.

A generated element id describes a position in the action tree rather than an identity, so an application that restores saves should name its cast through DevTools.setElementStaticId. This matters more since 0.26.0, because characters now occupy save entries too.

stage

The state of the player, including the scenes and displayable elements currently on stage.

services

The serialized state of every service registered with story.registerService.

stackModel

The main execution stack, which is where the game resumes from when the save is loaded.

asyncStackModels

The execution stacks running alongside the main one — the actions started by Control.doAsync and Control.allAsync.

history

The persisted backlog (save format v2+). Each entry pairs the rendered say/menu content with a stable action id and a self-contained snapshot of the game at that line, which is what lets liveGame.getHistory() return a full backlog after a load and liveGame.restoreToHistory(token) jump back to any of these lines. Optional: legacy saves have no history, and a save whose story has since changed drops entries whose action no longer exists.

On this page