What are the HyperFrames determinism rules, in one checklist?
Same composition, same video, every time, as long as nothing in a frame reads the clock, an unseeded random number or the network. The rules, the reason for each, and the lint codes that enforce them.
HyperFrames does not play your video. It asks for one frame at a time, and the answer to "what does frame 90 look like?" depends on exactly one thing that changes: the number 90. Everything below follows from that.
How a frame is made
- Frame clock. The engine works out the time with integer math:
time = floor(frame) / fps. Real time is never consulted. - Seek. Every animation, DOM change and canvas draw is moved to exactly that time. All GSAP timelines are paused and seeked, never played.
- Capture. Chrome's
HeadlessExperimental.beginFramegrabs the pixels in one atomic operation, so there are no half-painted frames. - Encode. FFmpeg turns the frames into the MP4 and mixes in audio from your
<audio>and<video>elements.
The checklist
Technical requirements. Breaking these produces incorrect renders.
- Register every timeline on
window.__timelines. The renderer cannot seek an animation it does not know about. The key must equaldata-composition-id. (Why animation is static) - Create timelines paused.
gsap.timeline({ paused: true }). - Keep sound on the video, or use
<audio>. A video with sound keeps it on the<video>withdata-has-audio="true"and nomuted. Silent footage and b-roll takemuted. Use a separate<audio>for music and voiceover. - No wall clock. No
Date.now(), norequestAnimationFrame, no system timers. - No unseeded randomness. No bare
Math.random(). Use a seeded PRNG. - No fetching mid-render. Every asset loads before the first frame. No
async,awaitorfetch()while a timeline is being built. - Fixed output.
fps,widthandheightare locked before frame 0. - A finite length. Every composition has a known end. Prefer an explicit root
data-duration. - Timed elements need
class="clip", plusdata-start,data-durationanddata-track-index. (A video needs its own timing too: video renders black.)
Best practices. The skills apply these by default and you may override them with a reason: add entrance animations to every scene, and add transitions between scenes.
Seek-order rules
A render worker may seek non-linearly, so anything whose value depends on when or in what order it ran can differ from your preview. Each of these has a lint code.
- State the visible end state of anything that starts hidden.
gsap_cold_seek_hidden_fromto_missing_reveal. (Explained) - Set initial hidden state outside the timeline.
gsap_timeline_set_initial_hide. - Do not stack a relative tween on a property another writer animates.
gsap_relative_value_second_writer. - Do not combine
repeatRefresh: truewith a relative value.gsap_repeat_refresh_relative_value. - Function-valued tween vars get
(index, target, targets).gsap_function_value_hazard. - Do not measure DOM geometry in a timeline callback.
gsap_callback_dom_measurement. - Do not centre with CSS
translate(-50%, -50%)on an element GSAP moves withxory. Centre with flex or grid, or let GSAP own the offset withxPercentandyPercent.gsap_css_transform_conflict.
Rule out your machine
Fonts and Chrome versions differ between computers, so a local render can shift by a pixel from one machine to the next. Render in Docker for exact reproducibility, which pins the Chromium version, the font set and the FFmpeg encoder:
npx hyperframes render --docker --output output.mp4
One caveat on "identical every time": parallel workers are not bit-identical to each other. A default multi-worker render can differ by a few plus or minus one pixel levels at worker chunk boundaries. If you need exact bits, pass --workers 1 and compare lossless frames rather than the encoded MP4.
Check it worked
npx hyperframes lint
npx hyperframes check
npx hyperframes snapshot --at 0,3,8
lint enforces the rules above, and snapshot shows you seeked frames. Neither replaces looking at a draft render before you ship. See why a green check can still mean a wrong video.
The rules, applied by the agent

GenMotion's agent writes from the HyperFrames skills, which encode these rules, and validates the composition after it edits. You get a deterministic project without keeping the checklist in your head, and you can still read and edit the HTML yourself, because the project is a plain HyperFrames folder.
Related answers
Why is an element invisible in my HyperFrames render but fine in preview?
A render worker seeks straight to a frame and restores the authored state, not whatever the preview last showed. Elements that start hidden need their visible end state stated explicitly, and a fromTo shows its from-state before it starts.
Why is my HyperFrames animation static?
If the render shows your elements frozen in their start state, HyperFrames almost certainly cannot find or seek your timeline. The timeline has to be paused and registered under the exact composition ID.
Why does my HyperFrames render look different from the preview?
Preview and render run the same runtime, so a real difference has a specific cause: fonts, remote media, a cold-seek state problem, the wrong entry file, or a variable that never arrived. Here is how to find which.
Why did HyperFrames check pass and render exit 0 when my video is wrong?
HyperFrames can produce a valid-looking MP4 from a composition that is structurally fine and visually wrong. Here is the class of bug, what has been fixed to fail loudly, and what to verify yourself.
Why does my video render look different from the preview?
The principle behind every preview-versus-render mismatch in code-to-video tools: a frame must be a pure function of its time. Here is what breaks that, how HyperFrames and Remotion each enforce it, and how to test for it.
Sources
- HyperFrames: Deterministic Rendering
- HyperFrames rules and anti-patterns
- HyperFrames: Animate with GSAP
- HyperFrames: Time elements with data attributes
This answer as plain Markdown, for agents: /answers/hyperframes-determinism-rules.md