Determinism

What at(frame) may depend on, what breaks it, and how host-observed values enter without breaking it.

at(frame) is a pure function of exactly three things:

  1. the frame number,
  2. the values of the channel publications it reads,
  3. the explicit resolution options — viewport, channelValues.

Nothing else may influence the result. This is a contract the runtime relies on, not a quality it aspires to.

What breaks it

These all compile and all look correct on first playthrough:

// Wall clock — every resolution differs
elapsed = derived(() => Date.now() - this.startedAt());

// Randomness — the same frame renders differently each time
jitter = derived(() => Math.random() * 4);

// Closed-over mutable state — the answer depends on call order
let seen = 0;
index = derived(() => seen++);

The failure mode is what makes these dangerous. Forward playback looks fine; the bug shows up as flicker under a distributed render, a wrong frame after a seek, or a cached frame that disagrees with a fresh one.

Same input, same output — including across processes

Remotion renders frames concurrently across processes. A scene that resolves differently in two workers produces a video that is inconsistent frame to frame, with no error anywhere.

Deterministic alternatives

Instead ofUse
Date.now()the frame number, or ClockActor
Math.random()a seeded generator whose seed is authored state
A measured DOM boxa channel with record: true
A stepped simulation@motionactor/physics record/replay
A fetched valuepublish once into a channel before rendering

Why recorded channels exist

A latest-wins publication is wrong for anything that is a function of the frame. Consider a measured box published while rendering forward, then a seek back to frame 10: a latest-wins read returns the measurement from frame 200.

record: true keeps a frame-indexed run log, so a read at frame N returns the value recorded at the greatest frame ≤ N. Scrubbing backwards replays what was actually published.

measuredBox = channel<ResolvedLayoutBox>({ record: true });

Per-resolution overrides

The cleanest way for a renderer to supply a host value is to pass it per call, so nothing is written at all:

const state = compiled.at(frame, {
  channelValues: new Map([[createCellKey(card.id, "availableWidth"), width]]),
});

Repeating the frame with the same inputs produces the same output regardless of publication order — which is exactly the property a React render needs, since it may run twice.

What determinism buys

  • Seeking without replaying intermediate frames.
  • Parallel rendering across processes with no coordination.
  • Precise cachingmemoizedScene invalidates per channel key.
  • Headless evaluation — a test or an agent can assert scene state with no renderer at all. See Testing.

On this page