Skip to main content

Documentation Index

Fetch the complete documentation index at: https://openturn.io/docs/llms.txt

Use this file to discover all available pages before exploring further.

A completed match can be captured as a self-contained JSON envelope that any machine with the game definition can replay. This is the foundation for spectator views, analysis tools, and inspector.

Capture from a local session

import { createSavedReplayFromSession, serializeSavedReplay } from "@openturn/replay";

const envelope = createSavedReplayFromSession({
  gameID: "example/tic-tac-toe",
  playerID: "0",
  session,
  metadata: { label: "Opening match" },
});

const text = serializeSavedReplay(envelope);
// text is pure JSON; write to a file, upload, store in a blob, whatever
The session argument can be an actual LocalGameSession or any object that exposes getReplayData(). React’s useMatch().state.replayData exposes it in local mode; pass { getReplayData: () => replayData } if you want to capture from a component.

Envelope shape

interface SavedReplayEnvelope<TPlayers, TMatchData> {
  version: 1;                            // SavedReplayVersion
  gameID: string;                        // stable identifier for this game
  playerID?: TPlayers[number];           // who captured the replay, optional
  seed: string;                          // RNG seed
  initialNow: number;                    // initial time in ms
  match: MatchInput<TPlayers, TMatchData>;
  actions: readonly GameActionRecord[];  // the action log
  metadata?: Readonly<Record<string, JsonValue>>;
}
Everything is JSON. You can store the envelope anywhere strings go.

Load and materialize

import { materializeSavedReplay, parseSavedReplay } from "@openturn/replay";
import { ticTacToe } from "./game";

const envelope = parseSavedReplay(await readFile("replay.json", "utf-8"));
if (envelope.gameID !== "example/tic-tac-toe") throw new Error("wrong game");

const timeline = materializeSavedReplay(ticTacToe, envelope);
materializeSavedReplay replays the action log against a fresh reducer and returns a ReplayTimeline<TGame>:
interface ReplayTimeline<TGame> {
  frames: readonly ReplayFrame<TGame>[];
  actions: readonly GameActionRecord[];
  branches: readonly ReplayBranch[];
  seed: string;
  initialNow: number;
}

interface ReplayFrame<TGame> {
  snapshot: GameSnapshot<...>;
  step: GameStep<TGame> | null;
  action: GameActionRecord | null;
  revision: number;
  playerView: GamePlayerView<TGame> | null;
}
Each frame has the full snapshot at that point, the transition that produced it, and an optional player view.

Scrub with a cursor

import { createReplayCursor } from "@openturn/replay";

const cursor = createReplayCursor(timeline);
cursor.play();
cursor.pause();
cursor.seekTurn(3);
cursor.seekAction(actionID);
cursor.undo();
cursor.redo();

const state = cursor.getState();
// state.currentFrame is the current frame, state.isPlaying is boolean
The cursor drives scrub-style replay UIs. Every mutating method returns the new ReplayCursorState so you can feed it into React state yourself.

Branching

For “what if” analysis, fork the timeline at an action and replay new actions from there:
import { addReplayBranch } from "@openturn/replay";

const branched = addReplayBranch(timeline, actionID, "branch-1", /* parent = main */ null);
Branches share the prefix up to the fork point but diverge after. Devtools uses this to let you try different moves from a captured state.

Load in inspector

For an interactive inspector, wrap the timeline with the ReplayInspector from @openturn/inspector-ui’s createInspector(bindings). See how-to: debug with inspector.