The 4-Gate Workflow: Don't Rush to Code
Why agents must never rush straight to code: understand concepts, storyboards, TASTE.md, and QA approval gates.
Eliminate randomness and clocks in code scenes to guarantee identical frame-for-frame re-renders.
A version is only worth keeping if it can be rebuilt. Render the same source twice and you should get the same video, frame for frame. That holds as long as every frame depends on one thing only: its time on the timeline.
Why it matters:
HyperFrames doesn't play your page in real time. It pauses the GSAP timeline and seeks it to each frame, one after another. Anything that doesn't come from that timeline breaks the promise. Three rules keep it.
Math.random() gives a new value on every render, so scattered particles land somewhere else each time. When you want something that looks random, use a generator with a fixed seed:
// ✗ A different layout on every render
const x = Math.random() * 1920;
// ✓ The same layout on every render: a seeded generator
function mulberry32(seed) {
return function () {
seed = (seed + 0x6d2b79f5) | 0;
let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
const rand = mulberry32(42); // change the seed for a new layout
const x2 = rand() * 1920;
Date.now(), setTimeout, setInterval and your own requestAnimationFrame loops run on the wall clock, which has nothing to do with the frame being captured. Put every change on the timeline instead:
// ✗ Runs on the wall clock, so it depends on how fast the render goes
setInterval(() => {
el.textContent = new Date().toLocaleTimeString();
}, 1000);
// ✓ On the timeline: at 3.0 s it always shows 3.0
const clock = { s: 0 };
tl.to(clock, {
s: 6,
duration: 6,
ease: "none",
onUpdate: () => { el.textContent = clock.s.toFixed(1); },
}, 0);
The same goes for loops: repeat: -1 never ends, so give it a count that fills the scene.
// ✗ Never ends
tl.to("#steam", { y: -20, yoyo: true, repeat: -1, duration: 0.5 }, 0);
// ✓ Fills the 6 s scene exactly: 12 runs of 0.5 s
tl.to("#steam", { y: -20, yoyo: true, repeat: 11, duration: 0.5 }, 0);
A fetch() or a remote image URL can return something else next month, or nothing at all. Keep every image, font, video and sound inside the version, under assets/. motionmind init copies your brand logos and fonts into src/assets/brand/ for this reason, and each version's package.json pins the HyperFrames version it was built with.
<!-- ✗ Fetched at render time: it can change, move or fail -->
<img src="https://cdn.example.com/logo.png" alt="">
<!-- ✓ A file inside the version -->
<img src="assets/brand/logos/logo.svg" alt="">
Run the linter inside a version's src/ folder (the agent does this after every change, as part of check):
cd projects/demo/launch-6s/runs/v1-steam/src
npx hyperframes lint
It reports Math.random() and Date.now() as errors and repeat: -1 as a warning. It doesn't catch everything: a setTimeout or a fetch() passes. That's why the rules are also written into the skill the agent follows.
This is the check the agent runs before a version goes to production. Run it from the workspace folder; framemd5 hashes every frame of a video, so diff prints nothing when all frames match:
V=projects/demo/launch-6s/runs/v1-steam
cp $V/video.mp4 /tmp/a.mp4
motionmind render demo/launch-6s v1-steam
ffmpeg -v error -i /tmp/a.mp4 -map 0:v -f framemd5 - > /tmp/a.md5
ffmpeg -v error -i $V/video.mp4 -map 0:v -f framemd5 - > /tmp/b.md5
diff /tmp/a.md5 /tmp/b.md5 && echo identical
This compares two renders on the same machine. For identical output across different machines, HyperFrames can also render inside Docker (npx hyperframes render --docker).
Why agents must never rush straight to code: understand concepts, storyboards, TASTE.md, and QA approval gates.
Frame-accurate, reproducible motion graphics built with HTML and GSAP that can be edited visually without code.
Lock color roles, typography, easing curves, and audio rules so subsequent versions stay consistent.
Open your terminal, launch motionmind ui, and brief your AI agent.