Remix.run Logo
chmod775 5 hours ago

I've done similar shenanigans before. That main loop is probably simplified? It won't work well with anything that uses timing primitives for debouncing (massively slowing such code down, only progressing with each frame). Also a setInterval with, say 5ms may not "look" the same when it's always 1000/fps milliseconds later instead (if you're capturing at 24fps/30fps, that would be a huge difference).

What you should do is put everything that was scheduled on a timeline (every setTimeout, setInterval, requestAnimationFrame), then "play" through it until you arrive at the next frame, rather than calling each setTimeout/setInterval callback only for each frame.

Also their main loop will let async code "escape" their control. You want to make sure the microtask queue is drained before actually capturing anything. If you don't care about performance, you can use something like await new Promise(resolve => setTimeout(resolve, 0)) for this (using the real setTimeout) before you capture your frame. Use the MessageChannel trick if you want to avoid the delay this causes.

For correctness you should also make sure to drain the queue before calling each of the setTimeout/setInterval callbacks.

I'm leaning towards that code being simplified, since they'd probably have noticed the breakage this causes. Or maybe, given that this is their business, their whole solution is vibe-coded and they have no idea why it's sometimes acting strange. Anyone taking bets?