Validates the pairing of recording and target, without touching either's state.
Three things are checked here rather than at the first step, so a mis-wired replay fails at construction with a useful message:
decodeReplayRecording, so a hand-built object cannot smuggle
a bad field into the player — the cost is one deep copy at construction);target.createSnapshot() (the only
way to read a target's adapter identity; the snapshot's bytes are
discarded and the target is not modified);applyInput
is optional on ReplayTarget — PhysicsWorld does not have it —
and replaying recorded inputs into a target that cannot receive them
would diverge silently, which §42's "warn rather than silently
overwrite" instinct forbids just as much here.the §34 document to replay
the target, the one-step closure, and the inspection knobs
Called after every replayed fixed step, including those a ReplayPlayer.seekToStep re-simulates.
Assignable after construction, in the style of Scheduler.onFixedStep. When
it is undefined a replayed step allocates nothing and computes no
checksum.
ReadonlyrecordingThe document being replayed, in canonical, frozen form (§34).
Whether ReplayPlayer.onStep events carry a per-step checksum.
Cost: off (the default) a step is applyInput? + stepFn and nothing
else. On, every step additionally calls target.checksum(), which for a
real solver hashes the whole world — the dominant cost of the replay, easily
larger than the step itself. Turn it on to bisect a divergence, off to watch
a replay run.
It is a hook, not a check: §34's envelope stores one checksum for the whole run (ReplayRecording.finalChecksum), so the player has nothing per-step to compare against and does not pretend otherwise. What the hook buys is the ability to record two runs' per-step digests and find the first step where they differ — which is how a determinism defect (§33) gets located rather than merely detected.
Real time accumulated toward the next step, in seconds; always
< fixedDeltaTime except immediately after a clamp (§10).
Steps replayed so far; 0 immediately after ReplayPlayer.load.
Replay time the maximumSubSteps clamp discarded since
ReplayPlayer.load, in seconds — §10's TimeState.droppedTime for
the replay clock.
Time that arrives after the recording is exhausted is not counted: there is nothing left to simulate, so nothing was dropped.
The recording's fixed simulation step, in seconds (§10).
Recorded frames fully replayed so far.
A frame counts as completed once every one of its steps has run, so a frame that recorded zero steps (§10 runs none when the accumulator is short) completes as soon as the player reaches its boundary.
The §10 interpolation alpha of the replay clock, in [0, 1] — how far the
accumulator sits between the last replayed step and the next.
Offered for a host that renders an interpolated pose while scrubbing (§43). Like every other alpha it must never feed back into simulation state (§42).
Whether every recorded step has been replayed.
Whether ReplayPlayer.load has run; the other controls require it.
Whether ReplayPlayer.advanceRealtime is advancing time (§10 pause).
The §10 clamp on steps per ReplayPlayer.advanceRealtime call.
Playback rate for ReplayPlayer.advanceRealtime: 0.25 is quarter
speed (§113 slow motion), 1 real time, 4 fast-forward.
This is timeScale on the replay clock (P10-3), not on the simulation:
the fixed step never changes, only how much recorded time one second of real
time is worth. 0 is legal and freezes playback while leaving
ReplayPlayer.isPlaying true; negative values are rejected, because
running the recorded steps backwards is not something a solver can do — step
backwards with ReplayPlayer.seekToStep, which re-simulates.
Recorded frames (§34).
Steps in the recording — the sum of every recorded frame's stepCount
(§34), and the step index at which the run's finalChecksum was taken.
Replays whole recorded frames — the §113 "frame by frame" control.
With no argument it advances to the next frame boundary at or after the
current step, finishing a partially replayed frame rather than running a
whole extra one. With a frameIndex it advances to the end of that frame,
which may be several frames ahead.
A frame is §34's record of one rendered frame: the fixed steps §10's
accumulator ran for it, which is 0 when the frame was short and up to
maximumSubSteps when it was long. Advancing by frames therefore replays
the run's original step pattern, which is what a defect that only appears
on a long frame needs.
OptionalframeIndex: number
the frame to stop at the end of; omit for the next one
fixed steps replayed (0 at the end of the recording, or when the
requested frame ends exactly where the player already is)
RangeError if frameIndex is not an index of a recorded frame, or
names a frame that ended before the current step — advancing never goes
backwards; ReplayPlayer.seekToStep does
Advances playback by elapsedSeconds of the host's real time, running
whole fixed steps out of §10's accumulator.
The rules, mirroring Scheduler.step so a replayed run behaves like a live
one, and reported here because a caller counting steps deserves to know them:
0 returned. Real time
spent paused is not owed to the replay later.accumulator += elapsedSeconds * speed. Slow
motion is a smaller number of accumulated seconds, never a smaller fixed
step (§10: the fixed step is immutable).accumulator >= fixedDeltaTime, at most
ReplayPlayer.maximumSubSteps of them per call, and never past the
end of the recording.fixedDeltaTime (alpha 1) — §10's bound on how
far a single call can fall behind, so a debugger pause in the middle of an
inspection session does not produce a burst of simulation afterwards.droppedTime would misreport a finished replay as a stuttering one.real time since the previous call, in seconds
Optionalspeed: number
per-call override of ReplayPlayer.speed; the property itself is unchanged
fixed steps replayed by this call
Restores the recording's initial snapshot and rewinds to step 0.
Idempotent: calling it twice restores the same bytes and leaves the same state, so it doubles as "restart this replay". It resets the replay clock (accumulator and ReplayPlayer.droppedTime) but deliberately does not touch ReplayPlayer.isPlaying or ReplayPlayer.speed (decision, WP-10.2): those describe the transport the caller is operating, not the recording, and a looping player that had to re-press play after every rewind would be the surprising design.
Pauses playback: ReplayPlayer.advanceRealtime stops accumulating and
runs no steps, exactly as §10's pause is timeScale = 0 for the frame.
The accumulator is left as it is, so resuming continues from the same fraction of a step rather than snapping to a boundary. The explicit controls (ReplayPlayer.stepOnce, ReplayPlayer.advanceFrame, ReplayPlayer.seekToStep) keep working while paused — that is the point of pausing.
Starts (or resumes) ReplayPlayer.advanceRealtime playback.
Moves to any step of the recording, forwards or backwards, by restoring the nearest snapshot at or before it and re-simulating the remainder (§34).
The snapshot chosen is the last periodic snapshot whose step is <= step,
or the initial snapshot when there is none — so with a recorder configured
for snapshotIntervalSteps: N, a seek costs fewer than N steps of
re-simulation instead of up to totalSteps of it. That is the entire reason
§34 has periodic snapshots, and the reason the player prefers them even when
seeking forwards.
Seeking always restores, even for a forward seek the player could reach by simply stepping (decision, WP-10.2). The player does not assume the target's live state is the state the recording says it should be: the host owns that world and may have poked it between calls, and a seek that silently kept a poked state would report a divergence at the wrong step. Restoring is also what makes the operation idempotent, which a scrub bar needs.
Re-simulation applies the recorded inputs exactly as playback does, so by §33 the state reached is the state playback would have reached.
the target step, in [0, totalSteps]
fixed steps re-simulated — the cost of the seek, and what a test asserts on to prove the nearest snapshot was used
Replays exactly one fixed step: the inputs recorded for the current step are
applied first, in insertion order, then stepFn runs (§34).
At the end of the recording this is a no-op returning false, not a
throw (decision, WP-10.2). Reaching the end is the expected outcome of a
replay, not a misuse: while (player.stepOnce()) { … } is the natural
inspection loop, and it only reads that way if the terminal call is
ordinary. Misuse — stepping a player that was never loaded — still throws.
whether a step ran
FourError INVALID_APPLICATION_STATE if ReplayPlayer.load
has not run
§33's gate: does the replayed run end on the checksum the recording ended on?
Only meaningful at the end of the recording — that is the one step index
§34 stored a checksum for — so calling it early is a usage error rather than
a false, which would be indistinguishable from a real divergence.
whether target.checksum() equals the recording's finalChecksum
Plays a §34 replay document back into a ReplayTarget, with the §113 inspection controls.
A player is a small state machine over one recording: constructed (compatible with the target, but the target untouched), loaded (initial snapshot restored, step 0), and then advanced by whichever control the caller prefers — all of them are the same fixed steps in the same order, so they can be mixed freely:
Example