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.
Deterministic replay is one of openturn’s core guarantees: an action log plus a seed reproduces the exact match. This tutorial captures a tic-tac-toe match, persists it as JSON, and builds a viewer that loads replays and replays them locally.
It reuses the @my/tic-tac-toe-game package from tutorial 1.
Reference code: examples/replays/tic-tac-toe-replay-viewer.
1. Capture a replay from a local match
@openturn/replay turns any local session’s action log into a serializable envelope.
From the React app
In the React component from tutorial 1, add a “download replay” button:
import { createSavedReplayFromSession, serializeSavedReplay } from "@openturn/replay";
const view = useMatch();
if (view.mode !== "local") throw new Error("download requires a local match");
const { replayData } = view.state;
function onDownload() {
const envelope = createSavedReplayFromSession({
gameID: "example/tic-tac-toe",
playerID: "0",
session: { getReplayData: () => replayData },
metadata: { label: "Tic-tac-toe replay" },
});
const blob = new Blob([serializeSavedReplay(envelope)], { type: "application/json" });
const url = URL.createObjectURL(blob);
Object.assign(document.createElement("a"), { href: url, download: "replay.json" }).click();
URL.revokeObjectURL(url);
}
replayData is exposed on the local match.state. It carries the initial bootstrap, the seed, the initial time, and the action log. createSavedReplayFromSession wraps it into a versioned envelope.
From the CLI
If you also ran the CLI variant, pass --save-replay <path>:
bun --filter @my/tic-tac-toe-cli demo -- --save-replay out.json
Both paths produce the same JSON shape, so the viewer can load either.
2. Build a viewer app
A replay viewer is just another local-runtime openturn app that, instead of letting the player click, replays a loaded action log.
app/app/openturn.ts
export const metadata = { name: "Tic Tac Toe Replay Viewer", runtime: "local" };
app/app/page.tsx
Same shape as before — it renders a React component.
app/src/components/TicTacToeReplayViewer.tsx
import { useState } from "react";
import { createOpenturnBindings } from "@openturn/react";
import { parseSavedReplay, type SavedReplayEnvelope } from "@openturn/replay";
import { ticTacToe } from "@my/tic-tac-toe-game";
const { useMatch } = createOpenturnBindings(ticTacToe, {
runtime: "local",
match: { players: ticTacToe.playerIDs },
});
function useLocalMatch() {
const view = useMatch();
if (view.mode !== "local") throw new Error("replay viewer requires a local match");
return view.state;
}
export function TicTacToeReplayViewer() {
const match = useLocalMatch();
const [loaded, setLoaded] = useState(false);
async function onPickFile(file: File) {
const envelope = parseSavedReplay(await file.text());
if (envelope.gameID !== "example/tic-tac-toe") {
alert(`Unknown replay game: ${envelope.gameID}`);
return;
}
match.reset();
replayActions(match.dispatch, envelope);
setLoaded(true);
}
return (
<>
{!loaded && (
<input type="file" accept="application/json" onChange={(e) => {
const file = e.target.files?.[0];
if (file) void onPickFile(file);
}} />
)}
<Board snapshot={match.snapshot} />
</>
);
}
function replayActions(
dispatch: ReturnType<typeof useLocalMatch>["dispatch"],
envelope: SavedReplayEnvelope<typeof ticTacToe.playerIDs>,
) {
for (const action of envelope.actions) {
// The dispatch map is keyed by event name. We index by the recorded
// event string and forward the recorded playerID + payload.
const fn = dispatch[action.event as keyof typeof dispatch];
fn(action.playerID, action.payload);
}
}
The viewer runs a fresh local session, resets it, and re-dispatches the recorded actions in order. Because the authored game is deterministic, the resulting snapshot on each step is byte-identical to the original match.
3. Browse frames with inspector
For an interactive timeline, drop in the ReplayInspector returned by @openturn/inspector-ui’s createInspector. It gives you a timeline, state diff viewer, and graph highlight:
import { createOpenturnBindings } from "@openturn/react";
import { createInspector } from "@openturn/inspector-ui";
import { ticTacToe } from "@my/tic-tac-toe-game";
const bindings = createOpenturnBindings(ticTacToe, {
runtime: "local",
match: { players: ticTacToe.playerIDs },
});
const { ReplayInspector } = createInspector(bindings);
export function TicTacToeReplayInspector({ envelope }) {
return <ReplayInspector replayEnvelope={envelope} />;
}
The inspector materializes the envelope internally with materializeSavedReplay, then renders the timeline, frame diffs, and graph highlights.
What you learned
@openturn/replay serializes matches into a versioned JSON envelope.
- Replaying is just re-dispatching the recorded actions against a fresh session.
@openturn/inspector-ui turns a timeline into an interactive inspector with no extra code.
What to do next