Files
hyperframes/docs/concepts/determinism.mdx
T
James RussoandClaude Opus 4.7 f8906e8385 docs(guides): add Performance guide and preview-stutter troubleshooting (#327)
* docs(guides): add performance guide and preview-stutter troubleshooting

Adds a dedicated Performance guide covering preview-vs-render cost model,
expensive CSS patterns (backdrop-filter, filter, shadows), image sizing,
and how to diagnose slow compositions with Chrome DevTools.

Cross-links from troubleshooting (new "Preview stutters" accordion) and
common-mistakes (new "Oversized source images" and "Heavy backdrop-filter
stacks" accordions). Wires the new page into docs.json nav.

Also fixes a pre-commit format hook edge case: oxfmt would exit 2 when
the only staged files matching the format glob were all covered by
.prettierignore (e.g. docs-only changes). Add --no-error-on-unmatched-pattern
to the lefthook oxfmt invocation so docs-only commits are not blocked.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: call out preview performance limits at the entry points

The preview command, studio package, and determinism concept pages all
frame preview as visually equivalent to render — correct for fidelity,
misleading for playback smoothness. A user who reads those pages and
then hits a paint-heavy composition has no way to know why preview
stutters, short of drilling into troubleshooting.

Adds short notes at each entry point linking out to the new Performance
guide, so users hit the "preview is hardware-bound, render isn't"
explanation wherever they land first.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 12:22:42 -07:00

104 lines
5.0 KiB
Plaintext

---
title: Deterministic Rendering
description: "Same input, identical output. Every time."
---
Hyperframes is built around a core guarantee: **the same [composition](/concepts/compositions) always produces the same video**. This is what makes automated pipelines, CI testing, and AI-driven workflows reliable.
## How It Works
The rendering pipeline is frame-by-frame and seek-driven. No realtime playback is involved -- every frame is independently seeked and captured.
<Steps>
<Step title="Frame clock">
The [engine](/packages/engine) computes the time for each frame using integer math: `time = floor(frame) / fps`. There is no wall-clock dependency -- rendering is entirely decoupled from real time.
</Step>
<Step title="Seek">
The [frame adapter](/concepts/frame-adapters) receives a `seekFrame(frame)` call and deterministically positions all animations, DOM state, and canvas content to the exact frame. The adapter's `renderSeek` pauses all [GSAP](/guides/gsap-animation) timelines and seeks them to the computed time.
</Step>
<Step title="Capture">
Chrome's `HeadlessExperimental.beginFrame` API captures the pixel buffer for the current frame. This is a single, atomic operation -- no partial paints or race conditions.
</Step>
<Step title="Encode">
FFmpeg encodes the captured frames into the final MP4 video. Audio tracks from `<audio>` and `<video>` elements are mixed in during this stage.
</Step>
</Steps>
```mermaid
graph LR
A["Frame Clock<br/>t = frame / fps"] --> B["Seek<br/>adapter.seekFrame(frame)"]
B --> C["Capture<br/>beginFrame API"]
C --> D["Encode<br/>FFmpeg"]
D --> E["MP4"]
style A fill:#00C4FF,color:#fff
style B fill:#00C4FF,color:#fff
style C fill:#00C4FF,color:#fff
style D fill:#00C4FF,color:#fff
style E fill:#00A8E1,color:#fff
```
## What Makes It Deterministic
- **No wall-clock dependencies** -- rendering does not use `Date.now()`, `requestAnimationFrame`, or system timers
- **No unseeded randomness** -- `Math.random()` without a seed breaks determinism
- **No render-time network fetches** -- all assets must be loaded before rendering starts
- **Fixed output parameters** -- `fps`, `width`, and `height` are locked before the first frame
- **Finite duration** -- every [composition](/concepts/compositions) has a known, finite length
These same rules apply to every [frame adapter](/concepts/frame-adapters). If you are building a custom adapter, you must follow the [determinism contract](/concepts/frame-adapters#determinism-contract).
## Docker Mode
For maximum reproducibility, render in Docker:
```bash
npx hyperframes render --docker --output output.mp4
```
Docker mode uses an exact Chrome version and font set, ensuring:
- Same Chromium rendering engine across all platforms
- Same system fonts (no platform-specific font substitution)
- Same FFmpeg encoder version
See the [Rendering guide](/guides/rendering) for all rendering options.
## Preview vs. Render Parity
The browser preview and the rendered MP4 should match. Hyperframes achieves this through:
- **One runtime** -- the same `hyperframe.runtime` drives both preview and render
- **Producer-canonical behavior** -- the [producer's](/packages/producer) seek semantics are the source of truth
- **Readiness gates** -- `__playerReady` and `__renderReady` ensure the [composition](/concepts/compositions) is fully loaded before any frame is captured
Parity here means **visual fidelity** — every frame looks the same. It does *not* mean performance parity. Preview plays in real time in a browser, so frame-rate limits are bound by your hardware. Render is seek-driven and frame-at-a-time, so it never drops frames regardless of per-frame cost. A composition can stutter in preview and render perfectly. See [Performance](/guides/performance) for why.
<Note>
Local rendering (without Docker) may show slight differences due to platform-specific font rendering and Chrome version. Use Docker mode when exact reproducibility matters.
</Note>
## For Adapter Authors
If you are building a [frame adapter](/concepts/frame-adapters), your adapter must follow the determinism contract:
- `seekFrame(frame)` must be idempotent -- same frame, same result
- No side effects that depend on call order (must handle random access)
- No async operations that resolve after the frame is "committed"
- Clean lifecycle: `init` -> `seekFrame` (N times) -> `destroy`
## Next Steps
<CardGroup cols={2}>
<Card title="Frame Adapters" icon="plug" href="/concepts/frame-adapters">
Build adapters that uphold the determinism contract
</Card>
<Card title="Rendering" icon="film" href="/guides/rendering">
Render to MP4 locally or in Docker
</Card>
<Card title="@hyperframes/producer" icon="clapperboard" href="/packages/producer">
The full rendering pipeline that orchestrates deterministic output
</Card>
<Card title="Common Mistakes" icon="triangle-exclamation" href="/guides/common-mistakes">
Pitfalls that break determinism and how to avoid them
</Card>
</CardGroup>