Smooth replay: watch playtest sessions back
With your game's help, the studio's replay viewer plays a session back smoothly (play, pause, scrubbing, 0.5x to 8x) by feeding the game's own recorded state back into the same build. Without it, replays step from save point to save point. Like resumable builds, it's part of saves and replays, which studios have on request (https://studio.betacade.com/saves-replays).
What the game needs#
- Everything needed to draw a frame lives in one JSON-serializable state object, and
render(state)draws the same frame every time. - Simulation and drawing are separate:
state = step(state, input, dt), thenrender(state). - The game hands Betacade its state with
wt.onSnapshot, and draws states it's given back withwt.onReplay. - The state carries a
stateVersion; change it whenever the state's shape changes. - One-shot effects (sounds, particles) go in the snapshot as
events, so replay can play them.
The two hooks#
const state = { stateVersion: 3, player: { x: 0, y: 0 }, score: 0 };
const pending = []; // one-shot events since the last snapshot
function sfx(name) { pending.push(name); playSound(name); }
window.wt?.onSnapshot?.(() => ({ ...state, events: pending.splice(0) }));
let replaying = false;
window.wt?.onReplay?.({
stateVersion: 3,
enter() { replaying = true; },
state(s, { events, seeking }) {
render(s);
if (!seeking) events.forEach(playSound);
},
exit() { replaying = false; },
});
function frame(now) {
if (!replaying) { step(state, readInput(), now); render(state); }
requestAnimationFrame(frame);
}
Betacade asks for a snapshot after your animation frames, up to 30 times a second, stepping down if it takes more than about 1.5 ms. A snapshot identical to the last is skipped, and one over 32 KB isn't recorded. In replay the game doesn't step or take input; it draws what it's handed, and its saves are dropped. Keys set to null come back missing, so treat null and absent alike.
Turn it on#
In the studio, find the build in Builds and choose Mark replayable. From then on, sessions of testers who share data record the game's state; testers are told. Nothing is recorded for testers who play without sharing.
If the game doesn't register wt.onReplay within 25 seconds, or its stateVersion isn't the recording's, the viewer falls back to save points and says why.
Sound in replay#
The simulation doesn't run in replay, so play one-shot sounds from events in state() (not while seeking), start music in enter() and stop it in exit(). The viewer unlocks audio for you.
Unity and Godot#
Both bridge through a little JavaScript: the engine keeps its latest state as JSON on the page each frame (the snapshot getter must answer at once), and replay hands JSON back to the engine. The agent guide has the Unity .jslib and the Godot JavaScriptBridge code.