Papercut build
Build every episode on the library instead of writing a Remotion project from scratch. Read $MOTION_LIB/core/README.md once.
Preflight
- Install: Standard or Full. Needs the library, node 20+, npm, ffmpeg, Python tools (numpy, Pillow, soundfile) and Chrome or Chromium for the audio render. Probe with the commands in
motion-explainer; if something is missing, run./install.sh --doctorand hand over tomotion-setup. - Library:
$MOTION_LIB, else theMOTION_LIB=line in~/.config/motion-explainer/config.env, else ask once. With no library at all, build from the sketches in$MOTION_LIB/style/$MOTION_STYLE/remotion/RECIPES.md. - Load the Remotion best-practices skill first only for the remotion engine (
remotion-best-practices, from the Remotion plugin, ornpx skills add remotion-dev/skills -g -y -a <your harness id>;install.sh --mode standarddoes this) and follow its routing for markup, captions and the Studio. Forkyfram, load thekyframskill instead. - Needs
video/STORYBOARD.mdandnarration/script.jsonfrom the earlier stages. - Smoke test of the whole toolchain:
$MOTION_LIB/core/tools/selftest.shbuildsexamples/hello-reel/in a throwaway folder and renders one still.
1. Scaffold & Verify Engine
The episode project should already be scaffolded from the intake / motion-strategy stage. Verify video/episode.json for the recorded engine:
-
If not yet scaffolded, ask the user (
remotion,fframesorkyfram) and run:$MOTION_LIB/new_episode.sh <series-dir> NN-slug CompId [out-name] --engine remotion|fframes|kyfram -
Preview commands from
video/:- fframes:
cargo run --release -- preview(or install CLI:cargo install --locked cargo-fframes) - kyfram: render once, then
kyfram view out/cache/kyfram/build/<Scene>.lgv(the compiled script: preludes + generated header + your scene) - remotion:
npx remotion studio(ornpm run dev)
- fframes:
-
fframes: episode project is a Rust crate (
media/,src/main.rs). Render withcargo run --release -- renderor$MOTION_LIB/core/tools/render.sh. -
kyfram: episode project is
.lgvscripts (scenes/,media/fonts/,src/plan.json). Render with$MOTION_LIB/core/tools/render.sh(it caches per scene and concats). Invoke thekyframskill first. -
remotion: React project (
src/,package.json). Render withnpx remotion render <CompId> out/<out>.mp4orrender.sh.
2. Plan: episode.json, voice, timing
From the storyboard, fill video/episode.json:
scenes:[["S01Hook", "l1_hook", 0], ["S02Name", "l2_name", "called"], ...](scene id, narration line id, first word as an index or the exact word).banners:{"S01Hook": "TAKEAWAY IN CAPS"}.hook: seemotion-hook.cuts: the Camera / Cut table's cut column.bed,whipScene,cutSfx,gaps(extra beat after a line, in seconds).
Then run motion-voice through its timing step (it ends with build_vo.py and timing_md.py). Then, from video/:
python3 $MOTION_LIB/core/tools/stubs.py # generates multi-file scene stubs based on engine
build_vo.py flags any scene under 1.2 s; merge it into a neighbour. timing_md.py writes the Timing section of STORYBOARD.md (absolute frames and word cues), so agents never open plan.json.
3. Assets
Follow $MOTION_LIB/style/$MOTION_STYLE/docs/asset-prompts.md: an image tool (anything that outputs transparent PNGs) for backgrounds and complex static props, code for characters, all text, tags, gauges, buttons and UI. No text inside generated images. With no image tool, build every prop in code with the paper filter; the style holds up. Backdrops: use the library's <Backdrop> set (style/$MOTION_STYLE/remotion/public/bg/backdrops/_sheet.jpg); generate a new one only when an episode needs a place the library lacks. The proof screenshot: node $MOTION_LIB/core/tools/capture.mjs <url> public/proof/x.png, shown with the kit's RealShot.
4. Scenes (Strict Multi-file Architecture)
NEVER put all scenes in one file. Each scene must live in its own isolated file to allow parallel agent work, independent testing, and clean git diffs.
Check engine from video/episode.json:
For Remotion (engine: "remotion")
- File Structure:
src/scenes/<Scene>.tsx(e.g.S01Hook.tsx,S02Name.tsx) — exactly one file per scene.src/scenes/<Scene>.parts.tsx(optional) — for private sub-components of that scene only.src/scenes/index.ts— registry arraySCENESautomatically managed bystubs.py. Never inline scene bodies here.
- Build
S01Hookyourself first insrc/scenes/S01Hook.tsx, from the HOOK block. It is the reference scene the agents match (cue constants, the camera, clampedinterpolate). - Hand the rest to scene agents in parallel, grouped by scene (2-3 scenes each). The brief is
style/$MOTION_STYLE/remotion/AGENT_BRIEF.md. - File ownership: an agent writes only
src/scenes/<Scene>.tsx, an optional<Scene>.parts.tsxandtools/cue_sheet.d/<Scene>.json. Never edit other scenes orindex.ts. - Agents read
style/$MOTION_STYLE/remotion/CATALOG.mdand$MOTION_LIB/core/audio/CATALOG.md.
For fframes (engine: "fframes")
- File Structure:
src/scenes/<scene_snake>.rs(e.g.s01_hook.rs,s02_name.rs) — exactly one file per scene, defining its struct andimpl Scene for <Scene>.src/scenes/mod.rs— automatically registers all scene modules viastubs.py(pub mod s01_hook; pub use s01_hook::*;).src/main.rs/lib.rs— only mounts the scenes viaVideo::define_scenes(). Never write scene drawing code insidemain.rs.
- Build
s01_hook.rsfirst as the reference scene. - Hand remaining scenes to agents in parallel (each agent owns only
src/scenes/<scene_snake>.rs). - Read
style/$MOTION_STYLE/fframes/src/for available camera, set, and prop structs instead of TSX components.
For kyfram (engine: "kyfram")
- File Structure:
scenes/<Scene>.lgv(e.g.S01Hook.lgv) — exactly one script per scene, shapes only.- The renderer prepends
$MOTION_LIB/core/kyfram/lgv/core.lgv,style/$MOTION_STYLE/kyfram/PRELUDE.lgvand a generated header (SCENE_ID,BANNER,DUR,FPSR,CAPS, and thescene()call), so a scene never callsscene(), never hard-codes its duration or caption cues, and never re-declares a helper.
- Invoke the
kyframskill. BuildS01Hook.lgvyourself first as the reference scene, and verify it withbash $MOTION_LIB/core/tools/render.shplusbash $MOTION_LIB/core/tools/qa.sh hook 0-<N> 6 0.3 6. - Hand remaining scenes to agents in parallel (each agent owns only
scenes/<Scene>.lgv), with thekyframskill named in the brief. - Agents read
style/$MOTION_STYLE/kyfram/PRELUDE.lgvfor tokens and furniture (drawRoom,drawHero,drawBanner,drawCaptions), andout/cache/kyfram/build/<Scene>.lgvafter a render for the exact compiled script. - SFX and bed wiring follow the remotion flow below;
render.sh --voicemuxespublic/vo/voiceover.wavinto the mp4 once that file exists.
Build rules: 1080x1920, 30 fps, scenes chained in the kit's transition series. Global layers: VO and music <Audio>, the taped banner with <Boil>, <WordTagCaptions>, <GrainOverlay>. Every prop is wrapped in <Paper>, entrances use <PopIn>, each scene runs inside its storyboard camera. No CSS animations. Keep the bottom 20% clear. Neighbouring scenes never share a backdrop. The default hero is a boxy orange critter (token C.hero #D67354, shade C.heroLo #A9553B, alias you); accessories and name pills tell the cast apart.
5. Sound
Pick SFX by name from $MOTION_LIB/core/audio/sfx/*.js and the bed by preset from $MOTION_LIB/core/audio/beds/*.json. For kyfram, invoke the kyfram skill first and write per-scene SFX rows in tools/cue_sheet.d/<Scene>.json; it owns the same shared sound pipeline as fframes and Remotion. From video/:
python3 $MOTION_LIB/core/tools/cues.py && node $MOTION_LIB/core/audio/render.mjs
cues.py merges the agents' rows into tools/cues.json and prints a note if air sounds exceed one per 2.5 s. render.mjs writes public/audio/music.wav and fx.wav.
6. Render
Preview: cargo run --release -- preview (fframes), npx remotion studio (remotion) or kyfram view out/cache/kyfram/build/<Scene>.lgv (kyfram). Render only when asked, always through the wrapper — it picks the engine from episode.json:
$MOTION_LIB/core/tools/render.sh # -> out/<out>.mp4
Flags: --workers N (default 3), --conc N, --force, --keep. Each scene is cached, so after a fix only the changed scene re-renders; a change to the kit, plan, voice or captions re-renders everything. The first run downloads Remotion's headless Chrome, and out/cache takes several GB of ProRes.
To fix or rebuild one scene: edit src/scenes/<Scene>.tsx, check it with motion-qa, then run render.sh again.
7. Harvest (after the episode)
python3 $MOTION_LIB/core/tools/harvest.py . # from video/; read-only report
python3 $MOTION_LIB/core/tools/catalog.py # regenerate both catalogs
Promote kit fixes, reusable components, new SFX and recurring beats as library/HARVEST.md describes, and add an entry to library/CHANGELOG.md.
- When a move looks stiff or muddy, check
$MOTION_LIB/style/$MOTION_STYLE/docs/motion-principles.mdfor the matching principle and its kit helper.
Hand-off
Next is motion-qa, which reads stills from the project and STORYBOARD.md. Run it per scene while building and once on the whole reel before the final render. After posting, motion-strategy runs the autopsy.