# Review

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](#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](/docs/music), 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](/docs/writing-scenes#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 <id> as it is" |
| `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 | |

```json
{
  "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](/docs/http-api#bodies)).

## 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` |
