Code checks first, then a reviewer's marks, in bounded rounds, with your stops; the Mark and Review shapes.
A scene is judged in two passes: code checks every frame, then a reviewer agent reads a contact sheet. It goes round until it passes or reaches you. Checks, reviewers, and people all write the same mark.
1. Code checks
After every render, rapid check holds the scene to its format and style before any agent looks, and writes renders/<id>/r<n>.checks.json: { "pass": boolean, "marks": Mark[] }, each mark authored { "kind": "check", "name": <check> }.
| Check | Holds | Severity |
|---|---|---|
crop | type, cards, bubbles, and objects (not data-macro) stay inside the frame | major |
band | type stays inside one of the format's readable bands | major |
keep-out | elements stay clear of the keep-outs that apply to their role | major |
safe | elements stay inside the safe area (textures excepted) | minor |
min-text | type's line height is at least the format's min_text | major |
fonts | every face the scene asked for loaded | blocker |
problems | the scene's code threw nothing | blocker |
colour | a sampled field reads the colour it should | major |
dead | something on screen moves: no span over 0.8 s stands still outside the meta's holds | major |
camera | a camera move spans its beat: one that finishes early and then stands for over 0.8 s is marked. The quick moves' own frames are let through | major |
empty | at the key frame, empty field covers at most the meta's room (default 0.4), and neither the top nor the bottom third is bare | major |
grid | at the key frame, text blocks align with each other and the style's grid | minor; major for two or more |
scale | at the key frame, the hero type is at least video.hero_ratio (default 3) times the body text | minor; major under two thirds of it |
beat | in a video cut to a track, each landing cue (land, hit, enter, drop, pop, kick) and each ducked cue sits on a beat | minor past 2 frames off; major past 5 |
The placement checks (crop, band, keep-out, safe) hold only elements at rest: an element moving between frames is entering or leaving, so it isn't marked, and one wholly off the frame isn't seen. min-text holds whenever type is on the frame. An edge may cross a guide by half a pixel. Hits of one check on one element on consecutive frames merge into one mark with a span.
Blockers and majors fail the scene; minors and nits go to the reviewer as notes. In a video, a scene with code problems fails its render before any check runs. The checks see only elements with data-el and data-role (tracked elements).
Key frames first
Before a scene moves, its hero is built as a still at meta.key. Code checks the still (rapid check --still, renders/<id>/key-k<try>.checks.json): placement, fonts, problems, empty, grid, scale; in a video every scene but the storyboard's one breath is held to 40% empty field whatever its room. The reviewer reads the frame into the review's frame (each third, how much is bare, hero over body, the visual doing work) and judges it as a poster against the storyboard's key and the style's proof: reviews/<id>-k<try>.json. A major from the checks, or a review that never read the frame, makes a pass a revise. Three tries, then an approval. --keys person has you approve the still instead; --keys off skips key frames.
2. The contact sheet
The reviewer never watches the video. rapid sheet draws frames at chosen moments, each labelled with its time and frame number:
- Round 1: the poster, then every cue. With no cues, frames spread over the scene.
- Later rounds: the starts and ends of the changed spans, the cues inside them, then the poster.
- At most
--max(12) frames, no two in one frame.
With agent review, rapid sheet --micro adds a strip 0.3 s either side of the key and of every mark the review must answer. A rapid-reviewer reads the sheet, the strip, the checks, and the previous scene's sheet in a fresh context, against taste.md and the refs; it never edits the scene and doesn't repeat what the checks found. It writes reviews/<id>-r<n>.json.
3. Rounds
| Verdict | Means | Then |
|---|---|---|
pass | nothing above minor | accepted |
revise | a blocker or major a builder can fix | the next round builds from the marks, while rounds remain; then an approval, "accept |
needs_you | a person must decide | an approval |
--rounds (default 4, 1–10). From round 2 the builder reads the last review and checks and changes only what their marks ask, near their times. A review lists every earlier mark it was handed (the last round's, the key frame's, the director's) in answers; a pass that leaves one carried, or leaves one out, counts as a revise. With --review none, passing checks accept the scene and failing ones go straight to an approval.
What a builder reads: the last review and checks; renders/<id>/marks.json, a person's marks (the screening page, add_mark, rapid revise --at), read first and acted on before anything else; and any rapid redo --note, in its prompt.
4. Stops
With --stops all, approvals wait after the storyboard, on a key frame that didn't settle (every key frame with --keys person), and on a scene out of rounds or sent to you. --stops storyboard keeps only the first; --stops none keeps none and takes the best try or round instead. At a stop:
- approve:
rapid approve <node>,/rapid approve <node>, Approve in the pane, or ask Claude; - redo the step before it with a direction:
rapid redo <node> --note "…"; - look first:
/rapid open <node>, or Open in the pane.
Mark
Mark in contracts/src/marks.ts.
| Field | Type | Means |
|---|---|---|
id | string | unique; a reviewer names it for its subject (r2-title-measure) |
scene_id | string | |
version | string | the scene file's sha256 when the mark was made |
t | seconds | the moment, or a span's start |
t_end | seconds, optional | a span's end, after t |
element | string, up to 120, optional | the data-el; with a span, tracked as it moves |
region | { x, y, w, h }, optional | in shares of the frame, 0–1 |
note | string, 1–4000 | what to change |
severity | blocker, major, minor, nit | a scene can't ship / must be fixed / fix when cheap / a preference |
author | { kind, name } | kind person, agent, or check; name 1–80 |
created_at | ISO time |
{
"id": "r1-card-hold",
"scene_id": "card",
"version": "9c1e…",
"t": 0.8,
"t_end": 2.3,
"element": "card",
"note": "The card holds too long before it leaves; cut the hold to about 0.8 s so it leaves on the settle.",
"severity": "major",
"author": { "kind": "agent", "name": "rapid-reviewer" },
"created_at": "2026-10-28T12:00:00.000Z"
}
POST /api/marks takes a mark without id and created_at, with an optional job_id (HTTP API).
Review
One round's judgment, reviews/<id>-r<round>.json, or a key frame's, reviews/<id>-k<try>.json (round is then the try, and every mark sits at the key time).
| Field | Type | Means |
|---|---|---|
scene_id, version | strings | as in its marks |
round | positive integer | from 1 |
verdict | pass, revise, needs_you | |
marks | array of Mark | |
summary | string, up to 8000 | the scene's state and what matters most |
looked_at | array of [from, to] | the spans reviewed; default [] |
answers | array of { mark, status, note }, optional | every earlier mark handed over: status answered or carried |
frame | KeyReading, optional | a key frame's reading: top, middle, bottom (what fills each third), empty (0–1), hero_body (ratio or null), visual |