85453da49f
regression / regression-shards (style-16-prod style-9-prod style-17-prod iframe-render-compat variables-prod mp4-h265-sdr, shard-4) (push) Has been cancelled
regression / regression-shards (style-4-prod style-11-prod style-2-prod animejs-adapter typegpu-adapter parallel-capture-regression, shard-5) (push) Has been cancelled
regression / regression-shards (style-7-prod style-8-prod style-10-prod css-spinner-render-compat webm-transparency mp4-h264-sdr webm-vp9, shard-3) (push) Has been cancelled
regression / regression-shards (sub-composition-video style-18-prod raf-ball-render-compat font-variant-numeric sub-comp-t0 sub-comp-id-selector, shard-7) (push) Has been cancelled
Windows render verification / Detect changes (push) Has been cancelled
Windows render verification / Preflight (lint + format) (push) Has been cancelled
Windows render verification / Render on windows-latest (push) Has been cancelled
Windows render verification / Tests on windows-latest (push) Has been cancelled
CI / Detect changes (push) Has been cancelled
CI / Build (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Fallow audit (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Producer: integration tests (push) Has been cancelled
CI / Producer: unit tests (push) Has been cancelled
CI / File size check (push) Has been cancelled
CI / Test: skills (push) Has been cancelled
CI / Skills: manifest in sync (push) Has been cancelled
CI / CLI: npx shim (macos-latest) (push) Has been cancelled
CI / CLI: npx shim (ubuntu-latest) (push) Has been cancelled
CI / CLI: npx shim (windows-latest) (push) Has been cancelled
CI / SDK: unit + contract + smoke (push) Has been cancelled
CI / Test: runtime contract (push) Has been cancelled
CI / Studio: load smoke (push) Has been cancelled
CI / Smoke: global install (push) Has been cancelled
CI / CLI smoke (required) (push) Has been cancelled
CI / Semantic PR title (push) Has been cancelled
Player perf / Detect changes (push) Has been cancelled
Player perf / Preflight (lint + format) (push) Has been cancelled
Player perf / player-perf (push) Has been cancelled
Player perf / Perf: drift (push) Has been cancelled
Player perf / Perf: fps (push) Has been cancelled
Player perf / Perf: parity (push) Has been cancelled
Player perf / Perf: scrub (push) Has been cancelled
Player perf / Perf: load (push) Has been cancelled
preview-regression / Detect changes (push) Has been cancelled
preview-regression / Preflight (lint + format) (push) Has been cancelled
preview-regression / Preview parity (push) Has been cancelled
preview-regression / preview-regression (push) Has been cancelled
regression / regression (push) Has been cancelled
regression / Detect changes (push) Has been cancelled
regression / Preflight (lint + format) (push) Has been cancelled
regression / regression-shards (hdr-regression style-5-prod style-3-prod mov-prores, shard-1) (push) Has been cancelled
regression / regression-shards (overlay-montage-prod style-12-prod chat missing-host-comp-id png-sequence portrait-edge-bleed, shard-6) (push) Has been cancelled
regression / regression-shards (style-13-prod style-6-prod vignelli-stacking gsap-letters-render-compat audio-mux-parity, shard-8) (push) Has been cancelled
regression / regression-shards (style-15-prod hdr-hlg-regression style-1-prod many-cuts vfr-screen-recording render-symlinked-assets, shard-2) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Docs / Validate docs (push) Has been cancelled
Sync skills to ClawHub / Publish changed skills (push) Has been cancelled
104 lines
4.9 KiB
Plaintext
104 lines
4.9 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 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>
|