How HyperFrames Achieves Deterministic Video
July 16, 2026 · blog
The hardest constraint in HyperFrames' video engine isn't speed — it's determinism. Same HTML input must produce identical pixel output every time, on every machine. Here's how the framework enforces that.
What Breaks Determinism
In a browser, these things vary between runs:
Date.now()— different each render. If any animation uses wall-clock time, output changes.- Unseeded
Math.random()— particle effects, noise textures. Random = non-reproducible. - Network fetches at render time — if an image loads from CDN during render, timing varies.
- Font rendering — different OS, different font rasterizer, different anti-aliasing.
- Async operations — setTimeout, requestAnimationFrame, Promise resolution order under load.
Any of these in a video composition = different pixels on different runs.
HyperFrames' Determinism Contract
The framework enforces strict rules:
- No
Date.now()orperformance.now()in composition code. Time comes exclusively from the framework's seek position. - No unseeded randomness. All random values must use a deterministic seed provided by the framework.
- No render-time network fetches. All media must be resolved before rendering starts (loading gate).
- Docker mode for cross-platform parity. Exact Chrome version, exact font set, exact rendering config.
- Integer frame math. Time =
floor(frame) / fps. No floating point accumulation drift.
The Rendering Pipeline
Load HTML composition
→ Resolve all media (images, fonts, audio) — block until ready
→ Signal __renderReady
→ For each frame 0..N:
→ Seek all animations to time T (integer frame / fps)
→ Wait for browser layout
→ HeadlessExperimental.beginFrame (atomic pixel capture)
→ Pipe frame buffer to FFmpeg stdin
→ FFmpeg encodes stream → MP4
Key detail: HyperFrames uses Chrome's HeadlessExperimental.beginFrame API, not regular screenshots. This gives atomic frame capture — the browser completes all layout and paint operations before pixels are grabbed. No mid-paint captures, no tearing.
Seek-Safe Animations
The tricky part: animations must be seekable to arbitrary times without playing forward.
- GSAP: Paused timelines registered on
window.__timelines. Seek viatimeline.time(t). - CSS Animations: Paused via
animation-play-state: paused. Seek viaanimation-delay: -Ts(negative delay = jump to position). - Web Animations API: Paused, seek via
animation.currentTime = t * 1000.
All three adapters expose the same interface to the renderer: seekTo(timeInSeconds). The renderer doesn't know or care which animation library is underneath.
The rule: no animation can have side effects. Seeking to T=3.5 then T=1.2 must produce the same result at T=1.2 as seeking directly to T=1.2. No accumulated state, no "played forward from last position."
Parallel Rendering
For long videos, HyperFrames splits the frame range across worker processes. Each worker gets a frame range, a fresh browser tab, and an FFmpeg pipe. Results are concatenated.
This works BECAUSE rendering is deterministic and stateless. Frame 500 doesn't depend on frame 499 being rendered first — the renderer can seek directly to frame 500 from cold start. Parallelism is free.
Result
20–30 seconds for a 4-minute video. Pixel-identical across runs. Cacheable (same input hash → same output, skip re-render). The determinism contract makes everything else possible — caching, parallelism, diffing, CI validation.
This is an elegant demonstration of how a strict constraint (determinism) enables capabilities rather than restricting them.