Recording and playback
The lifecycle
Section titled “The lifecycle”A recording moves through a small number of states, and the useful mental model is that pausing is reversible until you commit:
- Arm.
RewindRecorder.start(level, cfg, host)begins journaling the level. Play continues normally. - Pause. Vanilla ticking freezes, players lock in place, and everyone sees a
PAUSEDtitle plus a boss-bar scrub timeline showing position, total, and the nearest marker. - Scrub. The host seeks to any earlier tick. The world, players, blocks, entities and registered slices all snap to that moment. They can keep scrubbing as much as they like.
- Commit or cancel. Commit accepts the scrubbed position and truncates the future; cancel returns to the live head as though nothing happened.
- Resume. Ordinary live play continues from wherever step 4 left things.
- Stop. At match end the host is asked whether to save a durable record.
On a GameHost: the host arms it
Section titled “On a GameHost: the host arms it”GameHost.of("duel", "Duel") .rewind(RewindConfig.fromConfig()) .onRoundStart(r -> MatchRewind.bind(r.level(), match)); // r.recording() exists hereA host asked to .rewind(cfg) records every match. It arms after the arena is built and stops before it comes down, so the block journal (non-evictable by design) never holds the arena’s own blocks; it freezes the match’s phase clock and callbacks while the recording is paused or scrubbing; and it rewinds the round itself with the world: phase, clock, who is still in, arena size, with a player rewound to before their elimination released from spectating. Do not call RewindRecorder.start yourself on a host.
Starting one from code, without a host
Section titled “Starting one from code, without a host”Build the arena first, then arm. Every block write in a recorded level is journalled and the journal is never evicted, so arming before the build puts the whole arena in it for the life of the match.
Recording rec = RewindRecorder.start(instanceLevel, RewindConfig.fromConfig(), hostUuid);The two-argument form omits the host and is fine for automated recordings that nobody will drive by hand. RewindConfig.fromConfig() reads the server’s conduit.toml; construct a RewindConfig directly if one game needs a different window from the server default.
Useful lookups:
RewindRecorder.get(level) // the Recording on a level, or nullRewindRecorder.isFrozen(level) // paused or scrubbing, gate your tick on thisRewindRecorder.anyRecording() // cheap volatile read, any levelRewindRecorder.anyFrozen() // cheap volatile read, any levelRewindRecorder.active() // every live RecordingRewindRecorder.stop(rec)Commands
Section titled “Commands”All gated behind the conduit.rewind.control permission.
/conduit rewind arm | stop/conduit rewind pause | resume/conduit rewind back <seconds> | step <frames> | to <marker|seconds>/conduit rewind commit | cancel/conduit rewind mark <label>/conduit rewind status/conduit rewind save [name] | discard (at match end)/conduit rewind list | load <id>mark is worth using habitually while filming. A labelled marker is much easier to scrub to than a remembered timestamp, and markers are what a saved tape’s timeline is built from, so they are also how you seek inside a replay months later.
Events
Section titled “Events”RecordingStartedEvent RecordingStoppedEventGamePausedEvent GameResumedEventRewindBeganEvent RewindCommittedEventGamePaused/GameResumed are the pair you want for suspending your own logic. RewindBegan/RewindCommitted bracket an actual scrub, which is the right place to hang anything that should happen once per do-over rather than once per pause.
What a restore guarantees
Section titled “What a restore guarantees”A restore is must-happen work. Rewind lives in instance dimensions, whose chunks become prime unload candidates the moment players are back in the hub, so a restore frequently targets chunks that are no longer resident.
Rather than loading them on the tick thread, a restore derives the diff’s chunk set up front and requests them through the non-blocking entry point, firing the tick they arrive. When everything is already resident it applies immediately, exactly as before.
Two consequences worth knowing:
- Scrub targets that pile up while chunks load coalesce to the latest. Drag the seek bar across ten notches quickly and you get the tenth, not all ten in sequence.
- After 15 seconds the apply proceeds with blocking loads and a warning. Writes are never dropped. Play never resumes into un-restored geometry.