Purpose
Produce a shippable 10-25s looping concept video using the concept-loop
archetype from CREATOR_GRAMMAR.md §8. The archetype's defining pattern:
- Seamless loop — the last frame must visually match the first. The video is built to play on infinite repeat without a visible seam.
- Color-as-emotion — each state in the cycle has its own background color palette. The color is the mood. No explanatory caption.
- In-world text — any text on screen is part of the visual design (e.g. a day label in the character's hand, a status badge floating over the scene), not a platform-layer subtitle burn.
- Opens on emotional payoff — the first state shown is the most resonant, not the logical start of the sequence (e.g. a "Friday" state opens the Monday-through-Friday loop).
- Music-only, beat-synced cuts — no VO, no karaoke captions. Cut density is lower than the silent-product-demo archetype (2 cuts/10s in the body); the rhythm is in the beat-snapped transitions between states.
- Rapid cut burst near the loop point — a small burst (3-5 cuts) signals the loop is resetting, giving infinite-scroll replay a satisfying rhythm.
The molecule accepts a concept brief describing the loop idea and N states,
generates one visual frame per state, renders each state clip, and assembles
them through the EDL renderer with grammar.loop: true.
Inputs
| Input | Required | Notes |
|---|---|---|
loop_concept |
yes | One-sentence description of the loop idea (e.g. "a character cycling through the seven moods of a work week"). |
states |
yes | Ordered list of N state definitions. Each entry: { "label": "<day or state name>", "color": "<hex>", "description": "<visual description of the state>", "in_world_text": "<optional text rendered in-world>" }. Min 3 states, max 8. |
open_state |
yes | The label of the state that opens the video (the emotional payoff). Must NOT be the logical first state (e.g. for a Mon-Fri loop, open on Friday, not Monday). |
music_path |
no | Path to an existing music track. If omitted, find-music is run. |
character_reference |
no | Path to a reference image of the character or object. Drives Nano Banana consistency across states. |
target_duration |
no, default 15 |
Target runtime in seconds (10-25). |
output_dir |
yes | Project directory (canonical 5-folder layout). |
Composed Atoms
atoms/audio-editing/extract-beat-grid— analyse the music track and producebeat-grid.json. Required becauseconcept-loopisbeat_sync: true.atoms/planning/generate-edit-decision-list— build theedl.jsonwith archetypeconcept-loop,grammar.loop: true, beat-snapped cut points, and the loop-closure clip pairing (first state clip referenced again as the final clip).atoms/assembly/render-edl— execute the validatededl.jsonintomaster.mp4. Handles hard-cut concat, music bed, and in-world text captions (rendered asstatic-title-cardcues inside the EDL).atoms/music/find-music— source a royalty-free music track when none is provided. Prefer ambient/loopable tracks that do not have an obvious intro or outro (they'll be trimmed to loop).atoms/image-generation/create-image-nano-banana-fal— render one keyframe per state from the concept description + color + optional character reference. This is the primary visual generation step.atoms/motion-graphics/create-motion-graphics-remotion— optional: if a state requires animated text or kinetic elements (not just a static still-to-motion clip), author the state frame as a Remotion component instead of a Nano Banana still.atoms/motion-graphics/create-kinetic-typography— optional: if in-world text needs kinetic treatment (day label bouncing in, status bar animating), use this atom to author the text animation for that state.atoms/editing/add-zoom-cuts— apply a subtle zoom-punch on the rapid cut burst at the loop point to signal the reset.atoms/review/watch— frame-grounded QC of the final master, including loop-seam check (first frame vs last frame).
Workflow
Phase 0 — Intake and concept lock
- Read
loop_concept,states, andopen_state. Validate:- Minimum 3, maximum 8 states.
open_stateis a label present instates.open_stateis NOT the logical first state (if it is, warn the user and ask them to pick a different opening state).- Each state has a distinct
colorhex value — no two adjacent states in the play order may share a color family (e.g. two blues in a row).
- Derive the play order: move
open_stateto position 0, then continue in the original state sequence, wrapping around. Example: if states are [Mon, Tue, Wed, Thu, Fri] andopen_stateis Fri, the play order is [Fri, Mon, Tue, Wed, Thu, (Fri)]. - Write the play order to
working/play-order.json. - Create
output_dirwith canonical 5-folder structure.
Phase 1 — Music + beat grid
- Music selection:
- If
music_pathis provided, skip to step 2. - Run
find-musicwith descriptors matching the loop concept (e.g. "loopable ambient, 100-120 BPM, chill, no long intro"). Prefer tracks that sustain energy without a dramatic drop (drops disrupt the loop seam). - Write to
assets/music/track.mp3.
- If
- Run
extract-beat-grid:python3 skills/atoms/audio-editing/extract-beat-grid/scripts/extract_beats.py \ --source assets/music/track.mp3 \ --output-dir working/beat-grid/ - Verify
bpmis positive andbeatsis non-empty. - Determine the per-state clip duration:
target_duration / N_states. Adjust each state's duration to the nearest bar boundary in the beat grid (±150ms tolerance) so cuts land on bars.
Phase 2 — Generate per-state frames
For each state in the play order:
- Build a generation prompt:
"<state.description>, background color <state.color>, in-world text '<state.in_world_text>' visible, matching the character/object from <character_reference>" - Run
create-image-nano-banana-falwith the prompt andcharacter_reference(if provided). Save toassets/keyframes/<state_label>.png. - If the state needs animated in-world text (kinetic label, floating status),
run
create-kinetic-typographyorcreate-motion-graphics-remotionto render a short motion clip (assets/clips/<state_label>-anim.mp4) for that state instead of a static still. - Loop closure: after generating all N states, verify that the
open_statekeyframe (assets/keyframes/<open_state_label>.png) can serve as the first AND last frame (visually, the last rendered frame of the sequence should be close to the first). Do not re-generate — the EDL will reference the first state's clip at both ends.
Phase 3 — Generate edit decision list
- Build the scene table at
working/scene-table.json:
The[ { "scene_id": "loop-open", "description": "<open_state.description>", "role": "hook", "color": "<open_state.color>", "in_world_text": "<open_state.in_world_text>" }, { "scene_id": "state-02", "description": "<state[1].description>", "role": "feature", "color": "<state[1].color>", "in_world_text": "<state[1].in_world_text>" }, ... { "scene_id": "loop-close", "description": "<open_state.description>", "role": "feature", "color": "<open_state.color>", "notes": "loop-closure — references the same clip as loop-open" } ]loop-closeentry references theopen_stateclip again (not a new generation). This is what creates the seamless loop. - Run
generate-edit-decision-list:archetype: "concept-loop"source_mode: "generative"beat_grid: "working/beat-grid/beat-grid.json"grammar.loop: truetarget_duration: <target_duration>output_path: "working/edl.json"
- Verify the EDL:
grammar.loopistrue.- The first and last clips reference the same source spec (the
open_state). meta.audio_modeis"music-only".grammar.caption_familyis"static-title-card"(in-world text cues).- No clip has
role: "payoff-hold"— theconcept-looparchetype does not use a payoff hold (payoff_hold.required: falseinarchetypes.json). - All clip
target_invalues snap within ±150ms of a bar boundary.
Phase 4 — Assemble scene clips
For each state clip in the play order:
- If a motion clip was generated in Phase 2 (
assets/clips/<state_label>-anim.mp4), use it directly as the EDL clip source. - If only a still keyframe exists, the EDL records
source.mode: "generate", spec.keyframe: "assets/keyframes/<label>.png".render-edlwill hold the still for the clip's duration (still-image clip handling). - Apply
add-zoom-cutsto the 3-5 clips nearest the loop point (the rapid cut burst): setmotion: { "type": "zoom-punch", "from": 1.0, "to": 1.08 }.
Phase 5 — Render master
Run render-edl:
python3 skills/atoms/assembly/render-edl/scripts/render_edl.py \
--edl working/edl.json \
--output finals/master.mp4 \
--project-dir <output_dir> \
--crf 18
Phase 6 — Loop seam verification
-
Extract the first and last frames:
ffmpeg -ss 0.1 -i finals/master.mp4 -frames:v 1 -q:v 2 working/frame-first.png ffmpeg -sseof -0.1 -i finals/master.mp4 -frames:v 1 -q:v 2 working/frame-last.png -
Run
atoms/review/watchonfinals/master.mp4with the QC prompt:"Check: (1) does the first frame closely match the last frame — same color, same character state, no jarring jump? (2) do cuts land on beats? (3) is the opening state the most emotionally resonant one? (4) are there no VO tracks, no karaoke captions? (5) is in-world text legible and integrated into the visual design? (6) is there a small burst of rapid cuts near the loop point?"
-
Report findings as a timestamp punch list. If the loop seam is visible (significant color or composition mismatch between first/last frames), fix the
loop-closeclip and re-render Phase 5.
Decision Rules
DR-1 — Emotional-payoff-first opening
The first state in the play order must be the emotional peak, not the logical start. For a Mon-Fri loop, "Friday" (relief/energy) opens — not "Monday" (dread). For a product-benefit loop, the "after" state opens — not the "before."
If the user provides states in chronological order and does not specify
open_state, ask rather than guessing. An incorrectly ordered loop opens
on the most forgettable state and loses the scroll-stop hook.
DR-2 — Loop closure (first frame = last frame)
The loop-close entry in the scene table must reference the open_state
clip — not a newly generated frame. Even a visually identical regenerated
frame will have slight differences that break the loop seam. Use the exact
same source: source.spec.keyframe: "assets/keyframes/<open_state>.png".
If the open_state is a motion clip (animated in-world text), the loop-close
clip must use the first frame of that animation held static, not a looped
replay. This prevents audio/visual drift when the platform loops the video.
DR-3 — Color adjacency rule
Adjacent states in the play order must have visibly distinct colors. Do not place two similar-hue states next to each other (e.g. two blues, two greens). The color change IS the editorial cut signal. If the user's state list has adjacent same-hue states, reorder or ask for a color substitution.
DR-4 — No payoff-hold clip
The concept-loop archetype does not use a payoff hold. Do not add a
payoff-hold role to any clip — the EDL validator will pass (this archetype
marks payoff as not required). The loop's "reward" is the return to the
opening state, not a held single shot.
DR-5 — In-world text vs platform caption
All text visible in the video must be rendered as part of the visual frame
(via Nano Banana in-world text, Remotion component, or kinetic typography).
Never use the burn-in-captions atom or ASS captions to display state labels.
The in-world text is part of the art direction; a burned-in subtitle is a
platform UI element, which breaks the aesthetic register.
DR-6 — Loop burst placement
The rapid cut burst (3-5 quick cuts) should occur in the last 15% of the
video (the tail segment), signaling the loop reset. This matches the
concept-loop archetype's cuts_per_10s.tail: 5 target. Set these clips'
transition_in to beat-synced-cut and add motion.zoom-punch via
add-zoom-cuts.
Output
<output_dir>/
├── source/
│ └── character-reference.png ← optional input reference
├── assets/
│ ├── music/
│ │ └── track.mp3
│ └── keyframes/
│ └── <state_label>.png ← one Nano Banana frame per state
├── working/
│ ├── play-order.json ← state sequence starting from open_state
│ ├── beat-grid/
│ │ ├── beat-grid.json
│ │ └── manifest.json
│ ├── scene-table.json
│ ├── edl.json
│ ├── frame-first.png ← Phase 6 loop-seam check frame
│ ├── frame-last.png ← Phase 6 loop-seam check frame
│ └── planning-notes.md
└── finals/
├── master.mp4 ← the delivered looping Reel
└── manifest.json
The finals/master.mp4 is the shippable loop: 9:16, 1080×1920, h264 + AAC,
30fps, 10-25s, with in-world text baked into the visuals and no VO track.
Quality Checks
finals/master.mp4exists and is non-zero;ffprobereports both video and audio streams.- Duration within ±0.5s of
target_duration. - Resolution is 1080×1920 (9:16), 30fps.
edl.jsonhasgrammar.loop: trueand the first clip's source spec matches the last clip's source spec (same keyframe path).working/frame-first.pngandworking/frame-last.pngare visually consistent — same dominant color, same subject in the same position.meta.audio_modeis"music-only"; no VO track inedl.json.grammar.caption_familyis"static-title-card"; no karaoke-pop cues.- Exactly N+1 clips in the timeline (N states + the loop-close clip that mirrors the first state).
- All
beat-synced-cutclip boundaries land within ±50ms of a bar boundary inbeat-grid.json. - Self-QC punch list from Phase 6 has zero critical loop-seam or beat-sync issues.
manifest.jsonexists withstatus: pass.
Failure Modes
- Loop seam visible — the last frame has a different color or character
pose than the first frame. The
loop-closeclip was generated anew instead of referencing theopen_statekeyframe. Fix: point the EDLloop-closeclip toassets/keyframes/<open_state>.pngexplicitly. - Opening state is the logical first, not the emotional payoff — the loop
lacks a scroll-stop hook. Re-select
open_stateper Decision Rule 1. - Adjacent same-hue states — two states with similar colors are adjacent in the play order; the cut is invisible and the loop feels monotonous. Reorder or replace colors per Decision Rule 3.
- In-world text illegible — small text rendered inside the Nano Banana
frame at 9:16 is often too small. If
in_world_textis ≥5 words, consider rendering the text as acreate-motion-graphics-remotioncomponent with larger, styled typography, and composite it over the Nano Banana frame. - Music has an obvious intro/outro — the loop seam is hidden audibly, but
a long intro before the first beat makes the loop feel wrong on repeat. Trim
the music track to start on the first downbeat using
audio.musicwith a start-offset in the EDL, or usefind-musicwith "loopable, no long intro" as the criterion. generate-edit-decision-listreports beat-grid missing for abeat_sync: truearchetype —extract-beat-gridwas skipped or the file path is wrong. Run Phase 1 before Phase 3.- More than 8 states — the per-state clip duration at 15s becomes too
short for any motion treatment (< 1.5s). Reduce to ≤8 states or increase
target_duration. render-edlcannot find generate-mode clips —assets/clips/orassets/keyframes/paths in the EDL do not exist. Verify Phase 2 completed successfully and all paths inedl.jsonare relative toproject_dir.