code-loop
/code-loop
Make a seamless, perfectly looping generative video in code, from a three.js scene or a raw GLSL fragment shader, rendered frame-perfect with jittered anti-aliasing and motion blur, then checked for a visible seam, freezing and blinking. Use when asked for a "seamless loop", a "perfect loop", "generative art video", a "shader loop", "three.js animation to video", to "render a looping animation", a looping GIF from code, or any code-driven animation that has to loop without a visible cut.
- Author
- dotslashsimran ↗
- Repository
- simcraft ↗
- Licence
- MIT
- Last repository change
- 09.11.2026
Assessment and security
- Files in this package
- 6
- Scripts
- 2
- HTML comments
- 0
- URLs in SKILL.md
- 0
No signals detected. Repository changed on 09.11.2026, without references to retired models or APIs.
MIT, open: use and modify while retaining author attribution.
- 2 scripts in this package. Review them before installing, as Claude may execute them.
Scripts: scripts/check_loop.py, scripts/render_loop.mjs
What it does and when to use it
Make a seamless, perfectly looping generative video in code, from a three.js scene or a raw GLSL fragment shader, rendered frame-perfect with jittered anti-aliasing and motion blur, then checked for a visible seam, freezing and blinking. Use when asked for a "seamless loop", a "perfect loop", "generative art video", a "shader loop", "three.js animation to video", to "render a looping animation", a looping GIF from code, or any code-driven animation that has to loop without a visible cut.
Triggers from the description
Original author description
Make a seamless, perfectly looping generative video in code, from a three.js scene or a raw GLSL fragment shader, rendered frame-perfect with jittered anti-aliasing and motion blur, then checked for a visible seam, freezing and blinking. Use when asked for a "seamless loop", a "perfect loop", "generative art video", a "shader loop", "three.js animation to video", to "render a looping animation", a looping GIF from code, or any code-driven animation that has to loop without a visible cut.
How it works
- Quick start
- The page contract
- The rules of a seamless loop
- Why jitter and motion blur
- Craft that decides the quality
- Workflow
- Sharing
- Building on this engine
SKILL.md preview
---
name: code-loop
description: Make a seamless, perfectly looping generative video in code, from a three.js scene or a raw GLSL fragment shader, rendered frame-perfect with jittered anti-aliasing and motion blur, then checked for a visible seam, freezing and blinking. Use when asked for a "seamless loop", a "perfect loop", "generative art video", a "shader loop", "three.js animation to video", to "render a looping animation", a looping GIF from code, or any code-driven animation that has to loop without a visible cut.
---
# Code loop
A loop is seamless only if the frame after the last frame is exactly the first frame, and every motion keeps its speed across that point. The way to get that is to make every frame a pure function of the loop phase, built from motions that complete a whole number of cycles per loop. Then render each frame as an average of many slightly shifted renders, which gives clean edges and real motion blur, and check the result with numbers instead of by eye.
## Quick start
```
mkdir -p loop && cd loop
printf '{"type":"module"}' > package.json && npm i three@0.170.0 playwright@1.60.0
S=<path to this skill>
cp $S/templates/three_starter.html scene.html # or shader_starter.html for a raw GLSL loop
cp $S/scripts/render_loop.mjs $S/scripts/check_loop.py .
node render_loop.mjs scene.html loop.mp4 --stills=0,2,4,6 # full-quality stills in stills/
node render_loop.mjs scene.html preview.mp4 --preview # whole loop, half size, 30 fps, seconds to a minute
node render_loop.mjs scene.html final.mp4 --gif # 60 fps, 8+ samples a frame, plus a GIF
python3 check_loop.py final.mp4 --heat blink.png # seam, freezes, blinking
```
Set `$FFMPEG` if ffmpeg is not on the PATH. Sizes: `--size=1920x1080` (default), `1080x1920`, `1080x1080`, `1080x1350`. `--loops=3` repeats the loop in the mp4. If a long render is interrupted, run the same command again with `--resume`.
## The page contract
Both starters implement it. Any page that does can be rendered by `render_loop.mjs`.
| Hook | What it does |
|---|---|
| `window.LOOP` | Loop length in ms. The renderer renders exactly one loop by default. |
| `window.READY` | Set (or a promise that resolves) once assets, fonts and shaders are ready. |
| `window.render(ms)` | Draws the frame at time `ms`. |
| `window.renderMany([[ms, jx, jy], ...])` | Draws each sub-frame into a float buffer with additive blending at weight 1/n, then tone maps once to the canvas. One screenshot per frame leaves the browser. |
| `window.setJitter(x, y)` | Sub-pixel offset in canvas pixels. In three.js, add `2x/width` and `2y/height` to `projectionMatrix.elements[8]` and `[9]`. In a shader, add it to `gl_FragCoord.xy`. |
| `window.probe(ms)` | Optional. Screen positions `[x, y, ndcZ]` in output pixels of a few points that move. The renderer uses it to decide how many motion blur samples each frame needs. |
| `?w=&h=&scale=` | Output size, and a render scale for previews. |
## The rules of a seamless loop
1. **Everything is a pure function of the phase** `p = ms / LOOP`. No `Date.now()`, no `requestAnimationFrame` deltas, no state carried from the last frame, no `Math.random()` at render time. Seed every random generator, so the preview and the final build the same scene.
2. **Every periodic motion has a whole number of cycles per loop:** `sin(2π·k·p)` with integer `k`. A rotation turns `k` full turns, or a whole number of symmetry steps (a 4-fold grid only needs a quarter turn). A texture scrolls a whole number of its widths. A walk cycle repeats a whole number of times.
3. **Loop noise by walking a circle through it.** Sample `noise(x, y, R·cos 2πp, R·sin 2πp)` with 4D noise (or 3D noise for a 1D field). The radius `R` sets how much it changes over the loop. Adding `p` to a noise coordinate never loops.
4. **Easing must end where it starts.** Use moves that go out and come back (`0.5 - 0.5·cos 2πp`), or keyframes whose last key equals the first, interpolated with a curve that also matches speed at the wrap (a periodic spline). A plain ease-in-out that stops at the seam reads as a hitch.
5. **Do not wrap `ms` with `%` in the page.** Periodic maths does not need it, and leaving it unwrapped lets the exact seam test prove the loop is periodic. Wrap only the lookup inside a keyframe track.
6. **The loop length must be a whole number of frames** at every frame rate you deliver: 60, 30 (platforms often re-encode to 30) and the GIF rate (25 or 20). 4, 6, 8 and 10 seconds all work.
## Why jitter and motion blur
A single render per frame has stair-stepped edges, and thin or distant detail lands on different pixels each frame, so it shimmers and blinks. The renderer instead averages several renders per frame, each shifted by a different sub-pixel offset (a Halton pattern shaped into a tent filter). The same offsets are used every frame, so whatever aliasing is left stays still instead of crawling. When `probe` says things move fast, the renderer adds more samples spread across the shutter time (up to 32), which turns fast motion into a smooth streak instead of a stutter. Previews use one sample, so they shimmer by design. Judge blinking on finals only.
## Craft that decides the quality
- **World-anchored, not screen-anchored.** Hatching, grain, noise textures, dither patterns and haze must be attached to the object or the world. Anything fixed to the screen while the scene moves under it crawls and blinks. The one exception is the output dither, which is below one colour step.
- **Telephoto and z-fighting.** On long lenses the depth buffer runs out of precision. Keep separate surfaces apart (a few cm at 10 m, more at 400 m), push `near` out as far as possible, and paint markings onto one surface instead of stacking planes.
- **Colour.** Render in linear light into float targets, average there, then tone map, grade and encode to sRGB once at the end. Averaging after sRGB encoding makes blur and anti-aliased edges too dark. Both starters use the Khronos PBR Neutral tone map, a light grade, a vignette and a fixed dither against banding.
- **Speed.** Merge static meshes by material at load (`mergeStatic` in the three starter), and use instancing for many copies. Draw calls, not triangles, are usually the limit.
## Workflow
1. **Stills** at 4 to 6 phases with `--stills`. Fix composition, colour and detail here, where each try takes seconds.
2. **Preview** with `--preview` and watch it on loop. Fix timing and motion here.
3. **Final** at full quality. Only once the preview is approved.
4. **Check** with `check_loop.py`. It must say PASS. Look at `blink.png` if anything blinks.
## Sharing
| Where | Size | Notes |
|---|---|---|
| X | 1920x1080 or 1080x1080 | 60 fps is kept. Short clips loop automatically. GIFs up to 15 MB. |
| Instagram feed | 1080x1350 (4:5) | May be re-encoded to 30 fps. Use `--loops` so the post is at least a few seconds long. |
| Reels, TikTok, Shorts | 1080x1920 | Keep the subject in the middle, away from the top and bottom fifth, where captions and buttons sit. |
| GIF | 480 to 720 px wide, 20 or 25 fps | `--gif` writes one with an ordered dither, which stays still between frames and compresses better than error diffusion. |
Fine grain and noise compress badly and turn to mush after a platform re-encode, so keep large smooth shapes and strong contrast.
## Building on this engine
[perspective-illusion](../perspective-illusion/SKILL.md) is one style built on this engine: a telephoto line-art scene with dolly zooms, multi-pass ink lines and a scripted camera. Its templates use an earlier form of the same page contract, without `window.LOOP` and `?w=&h=`, and its `render_fast.mjs` is the ancestor of `render_loop.mjs`.
Details on each topic, more looping techniques and troubleshooting are in [reference.md](reference.md).
Files in this package (6)
- SKILL.md 8 KB
- reference.md 12 KB
- scripts/check_loop.py 9 KB
- scripts/render_loop.mjs 11 KB
- templates/shader_starter.html 11 KB
- templates/three_starter.html 12 KB
Versions
The database has no other skills named code-loop.
Database snapshot: 13.11.2026 · package version ac4d43a3c89e (SHA-256 digest of the entire package)
Downloads in the last 30 days: 0 · ranking
Report a problem with this skill: harmful content, incorrect descriptions or an author’s removal request.
Similar skills
- 90video-seamless-loop
Use when the task is to create a seamless loop and verify the join at normal playback speed. - 100gif-loop
Make a short, seamless, looping GIF (4 to 10 s) for a README, docs page, release note or social post - a product moment, a dashboard coming alive, a process diagram, a launch title - built as a deterministic HTML page and encoded to fit a size budget. Use when the user asks for… - 100gif-maker
Short looping GIF utility. Modes: (A) MP4-to-GIF with 2-pass palette optimization; (B) generate 1-3s clip via Veo/Sora/Kling/Runway/fal-video then convert. Aspect presets 1:1, 9:16, 16:9, 2:1. Use when: 'gif', 'looping animation', 'short loop', 'make a gif', 'сделай гифку',… - 100visual-motion
Animate a branded card into a feed-ready loop: master MP4 plus a LinkedIn GIF that fits the platform's pixel budget, rendered with Remotion (frame-accurate, scrubbable, parallel) or Playwright (quick). Requires a storyboard and a timing file before it will render, and asserts… - 100loop-cover
Turn a newsletter or blog cover into a seamless looping GIF at 896 x 640, 180 frames at 20fps. Only the one element you name moves, the loop is rotated so frame 1 shows the finished picture, and the seam is measured, not eyeballed. Use when someone says "animated cover", "make… - 86motion-export
Export a finished animation for X and the web - full frame render (motion blur for 3D), H.264 MP4 with optional loudness-normalized audio, single-palette GIF with exact centisecond delays, loop-seam check and X limits validation. Use when the user wants to post, share, download…