# Explainer film A — "The note is the diagram": storyboard, edit plan and animatic

Deliverables: this file, and `artifacts/animatic-16x9.mp4` (640x360, 40.000 s, 30 fps, silent AAC track).
The edit plan is executable: `marketing/explainer/film-a/plan.py` holds every number quoted below,
`render.py` assembles the film from the layers, and `check.py` measures the result.
Source of the brief: `marketing/explainer/BRIEF-A.md` and `marketing/kit/BRIEF.md`. The note itself
comes from `marketing/explainer/layers/` (with `layers/geometry.json`), and the plates and end cards
come from `marketing/kit/`.

**Evidence labels.** Every claim carries one of three tags:
- **[M] measured**: read from a file in the tree, from ffprobe or ffmpeg output, or from a decoded
  frame of a render made on 2026-10-07.
- **[I] inferred**: a design decision or an interpretation of the brief that has not been measured.
- **[O] open**: needs a decision or an input from outside this step. All of these are collected in
  section 9.

---

## 0. Answer in brief

- The film is a 40.000 s, 1200-frame cut on the brief's fixed grid: cold open 0–3, vault 3–11,
  oracle 11–20, redemption 20–28, liquidation 28–33.5, push and blink 33.5–36.5, ivory flash on
  frames 1095–1096, end card 1097–1199. **[M]** (check.py output, section 8)
- The grammar is **isolate → act → reassemble**. Each beat fades every layer except the element in
  play (and, for beats 2–4, the paper). The element performs its mechanism, and the note re-stacks
  pixel-identically before the next visit. This is what keeps every caption over a clean ground,
  either plain ink or plain paper. **[I]**
- The pipeline was executed. `render.py` produced `artifacts/animatic-16x9.mp4`, which passes every
  check: H.264 High yuv420p, AAC 48 kHz stereo, 30/1 fps, 1200 counted frames, video and container
  both 40.000 s, the frame at pts 36.500000 s is the first flash frame, moov before mdat, audio peak
  −91.0 dB. **[M]**
- The same code also produced 360-wide 1:1 and 9:16 test renders. They pass the same checks. **[M]**
  They were not delivered.

## 1. Inputs used (all in the tree; nothing generated by an image or video model)

| Input | Use | Measured fact |
|---|---|---|
| `layers/{paper,border,rosette,portrait,seal,corners,banner,serials}.png` (3264x1400) | the note, layer by layer | Stacked in `stackOrder`, they match `plates/note-master.png` with a max channel delta of 2 and 0 pixels differing by more than 2. This was re-measured by `render.py prep`. **[M]** |
| `layers/serials-empty.png` | the empty strip that the cut "types" into | Text bbox in `serials.png` versus the empty strip: x 727–1094, y 1175–1209 (left), and x 2171–2538 (right). There are 12 glyph runs. **[M]** |
| `layers/seal-stamp.png` (1000², seal R 280 at centre, rim R 362) | the stamp | — |
| `layers/coin-imd.png`, `coin-simd.png` (800², R 360) | the vault coin | — |
| `layers/caption-{a1,a2,a3,a4,a5,a5-alt}-{ivory,ink}.png` (1920 wide) | every word on screen | The text box starts 40 px from the plate top. One-line plates end at y 97–111, the two-line plate at 204. Ivory plates are the single colour #F7F5EF. **[M]** |
| `kit/plates/portrait-detail.png`, `portrait-look.png`, `blink-*.png` (2048²) | the push and the blink | The crop is (204, 24, 1640, 2000) and the eye band is (292, 708, 1180, 287) within it, both taken from `marketing/launch/render.py`. **[M]** |
| `kit/plates/note-angle-16x9.png` (1920x1080, opaque on ink) | the second note of beat 4 | The note was cut from its ink ground by flood fill. Its bbox is (192, 121)–(1727, 957). **[M]** |
| `kit/endcard/endcard-{1920x1080,1080x1080,1080x1920}.png` | the end card | They are used unchanged apart from scaling. **[M]** |

Generated by code, and only in palette colours: four ink cover rectangles (beat 1), one hairline rule
(the "line" of beat 4), a green silhouette of the angle note (the flood), and a pen-line signature
flourish in ink with no letters (beat 2), drawn by Python as a parametric curve. **[M]** (`render.py prep`)

## 2. Coordinates, camera and easing

- **Note space** is master pixels of the 3264x1400 note, with x to the right and y down. The anchor
  points come from `geometry.json`: oval centre (1632, 764) with rx 330.3 and ry 406.6; portrait box
  x 1253.16–2010.84, y 302–1226; rosette (724, 700) R 320; seal (2540, 700) R 280; serial strips
  y 1158–1234. **[M]**
- **Camera** = (cx, cy, Vh). This is the master point at the frame centre and the master height the
  frame shows. It maps note→screen as `X = W/2 + (H/Vh)(x − cx)` and `Y = H/2 + (H/Vh)(y − cy)`.
  Between keys, Vh is interpolated geometrically (constant-speed zoom) and cx and cy linearly, both
  through the key's easing. The final push is *anchored*: the portrait centre's offset from frame
  centre shrinks linearly with the eased progress while the zoom runs geometrically. That makes it a
  push *into* the face, not a swoop from below. **[M]** (plan.py `Camera`; the swoop was seen in a
  test render and fixed)
- **Sprite transform**: a native pixel (u, v) lands at `O + (u, v)/d`. It is then scaled by
  (sx, sy), rotated by `rot` (degrees, clockwise on screen) about pivot P, and translated by
  (dx, dy). **[M]**
- **Easings**: cubic in-out (the default), cubic out, cubic in, linear, and `launch` =
  0.5u + 0.5·smoothstep(u), which is the launch cut's push curve. **[M]**
- **Masks**: the note never needs a soft mask. Rectangular reveals are ink rectangles in note space,
  above the layers they hold back. The high-resolution portrait uses the launch cut's feathered
  ellipse: centre (820, 1000), radii (838, 992), a 1.2 % feather, in the 1640x2000 crop. **[M]**

### Framings (camera keys) per format **[M]** (values from plan.py)

| Key | 16:9 (cx, cy, Vh) | 1:1 | 9:16 | Intent |
|---|---|---|---|---|
| WIDE0 | 1632, 700, 2160 | 1632, 700, 3627 | 1632, 700, 6307 | the whole note, centred; width 0.85 / 0.90 / 0.92 of frame |
| OVAL | 1632, 923.3, 1593 | 1632, 918, 1540 | 1632, 898.6, 1682 | oval about 0.58 H tall, centred at 0.40–0.42 H |
| WIDEC | 1632, 872.8, 2160 | 1632, 990.2, 3627 | 1632, 1204.6, 6307 | whole note lifted to 0.42 H, ink below for the caption |
| ROS | 1231, 700, 1385 | 724, 775, 1250 | 724, 700, 1400 | rosette; frame entirely inside the note |
| FULL | 1632, 700, 1400 | 1632, 700, 1400 | 1632, 700, 1400 | pass-through between rosette and seal |
| SEAL | 2033, 700, 1385 | 2540, 775, 1250 | 2540, 700, 1400 | seal; frame inside the note (16:9 puts the seal at 0.71 W) |
| ROW | 1632, 1712.5, 4050 | 1632, 1482, 4600 | 1632, 1764, 7600 | three notes and the line, ink below |
| END | 1632, 764, 384.2 | 1632, 764, 577.5 | 1632, 764, 1244 | 16:9 and 1:1 are the launch cut's end framings (Vw 683 and 577.5); 9:16 shows the whole face **[I]** |

"Inside the note" means Vh ≤ 1400 and the visible width ≤ 3264. ROS, SEAL and FULL satisfy this,
so their lower third is always paper. **[M]** (arithmetic on the values above)

## 3. Shot table

All formats share times, layers, motion, easing and captions. Only the framing (section 2) and the
beat-4 staging (section 4.4) differ. The "Framing" column names the camera key.

| # | Start–end (s) | Layers used | Framing (all formats) | Motion | Easing | Transition in → out | Caption |
|---|---|---|---|---|---|---|---|
| 0 | 0.000–3.000 | all 8 note layers | WIDE0, locked | none (still) | — | from black (frame 0 is the note) → cut on the 3.0 accent | none |
| 1a | 3.000–4.300 | portrait; the 7 others fade out 3.0–3.5 | WIDE0 → OVAL | camera push | in-out | accent at 3.0 | a1 ivory, fades in 3.8–4.3 |
| 1b | 4.300–6.500 | portrait, coin-imd → coin-simd | OVAL | coin rolls in and settles; flips to sIMD | out (roll), in/out (flip) | — | a1 |
| 1c | 6.400–8.600 | the 7 layers back at 6.45 under 4 ink covers; portrait-top; coin-simd | OVAL → WIDEC | covers open outward from the oval box: the note "prints out" from under the coin | in-out | — | a1 |
| 1d | 8.600–11.000 | whole note, coin-simd | WIDEC, Vh −2.8 % drift | coin shrinks into the oval 9.4–10.2 ("locked away") | in | → camera move on the 11.0 accent | a1 out 10.2–10.6 |
| 2a | 11.000–12.200 | paper + rosette; the others out 11.0–11.6 | WIDEC → ROS | camera move | in-out | accent at 11.0 | a2 ink (2 lines), in 11.7–12.2 |
| 2b | 12.200–15.000 | rosette-spin, rosette-ghost (counter-rotating, α 0.55) | ROS, slow push (Vh −1.8 % to 19.2) | spin, then both converge to 0.16 scale | in | — | a2 |
| 2c | 15.000–16.900 | medallion (rosette at 0.16), flourish | ROS | flourish drawn over 27 frames (15.0–15.9) | linear (pen) | — | a2 out 16.4–16.8 |
| 2d | 16.900–18.500 | medallion, serials-empty (in 16.6–16.9), 24 glyph sprites | ROS | medallion drops onto the strip, then runs along it; each glyph appears as it passes | in-out, linear | — | none |
| 2e | 18.500–20.000 | the 7 layers back 18.8–19.4; rosette home 18.5–19.3 | ROS → FULL (19.2–20.0) | reassembly | out / in-out | → pan into the 20.0 accent | none |
| 3a | 20.000–21.000 | paper + seal; the others out 20.3–20.9 | FULL → SEAL | pan | in-out | accent at 20.0 | a3 ink, in 21.0–21.5 |
| 3b | 21.000–22.300 | seal, seal-stamp | SEAL | stamp appears lifted, hovers, slams down at **22.300** | in (slam) | — | a3 |
| 3c | 22.300–25.600 | seal, seal-stamp | SEAL, Vh −2 % drift; shake 22.30–22.43 | hold, rim lettering legible | linear | — | a3 |
| 3d | 25.600–28.000 | stamp fades out 25.6–26.4; layers back 26.8–27.4 | SEAL → WIDEC (26.8–28.0) | "settles back": the rim goes, the seal stays | in-out | → the 28.0 accent | a3 out 26.3–26.7 |
| 4a | 28.000–28.900 | paper + corners; the others out 28.0–28.4 | WIDEC | the four denominations alone | in-out | accent at 28.0 | a4 ivory, in 28.3–28.8 |
| 4b | 28.900–30.000 | whole note + 2 stacked copies (α in 28.9–29.4) + hairline | WIDEC → ROW | pull back; the notes lean; the line draws 29.3–29.9 | in-out / out | — | a4 |
| 4c | 30.000–31.600 | second note: flat copy → angle plate (30.0–30.4) → green flood (30.6–31.1) | ROW | tilts and slides below the line (30.0–30.8); pulled off frame (31.1–31.6) | in-out, in | — | a4 out 31.1–31.5 |
| 4d | 31.500–33.500 | the two remaining notes, the line | ROW, Vh −1.5 % drift | the notes straighten (31.5–32.3) | out | → the push begins on the 33.5 accent | a5 ivory, in 31.65–32.15 |
| 5 | 33.500–36.500 | whole note + portrait-detail (from 33.45) + 9 eye-band plates | ROW → END, anchored on (1632, 764) | the launch push, compressed from 6.5 s to 3.0 s; the blink at 35.45 | `launch` | hard cut to the flash | a5 out 34.35–34.75 |
| 6 | 36.500–36.567 | — | full frame | two frames of ivory #F7F5EF (frames 1095, 1096) | — | hard cut in and out | none |
| 7 | 36.567–40.000 | end card for the format | full frame | still, frames 1097–1199 | — | ends at 40.000 | the card's own lettering |

Times are measured from plan.py. Whether each shot reads as intended was judged on decoded frames of
the 16:9 animatic and on 1:1 and 9:16 test stills. **[M]** That the framings work at full resolution
is still **[O]**, because no full-resolution render was made (section 9).

## 4. Exact transforms per beat (master coordinates)

### 4.1 Beat 1 — the vault (portrait)
- **Isolation.** The 7 non-portrait layers go α 1→0 over 3.0–3.5 (in-out). They come back at
  6.45–6.95, but only behind the covers.
- **coin-imd.** Density d = 360/300 = 1.2 px per master px (coin R 300 inside the oval's rx 330).
  O = (1298.67, 430.67), P = (1632, 764).
  - dx: −2732 → 0 over 4.3–5.5, cubic out.
  - rot: = dx/300 rad, i.e. −521.8° → 0°. It rolls without slipping.
  - α: 1 from 4.3.
  - The flip: sx 1→0 over 5.9–6.2 (in), and the coin's α is cut at 6.2.
- **coin-simd.** The same rig.
  - sx: 0→1 over 6.2–6.5 (out).
  - Lock-away: sx and sy 1→0 over 9.4–10.2 (in), with α 1→0 over 10.15–10.2.
- **Print-out.** Four ink rectangles, each 9000x9000 master, active 6.4–8.3. They start on the
  portrait box and move to the positions below over 6.5–8.3 (in-out):
  - top: bottom edge at y 302 → dy −502
  - bottom: top edge at y 1226 → dy +374
  - left: right edge at x 1253.16 → dx −1453.16
  - right: left edge at x 2010.84 → dx +1453.16

  The opening grows from the oval's box to beyond the note. The portrait is drawn again above the
  covers (portrait-top) while the stacked portrait hides, so it is never doubled. **[M]**
- **Camera.** WIDE0 → OVAL over 3.0–4.3; hold to 6.5; OVAL → WIDEC over 6.5–8.6; drift to Vh ×0.972
  by 11.0.

### 4.2 Beat 2 — the oracle (rosette)
- **Isolation.** All layers except paper and rosette go out 11.0–11.6. The stacked rosette hands
  over to `rosette-spin` at 12.15. Both are drawn identically at that instant, so the swap is
  invisible.
- **rosette-spin.** P = (724, 700).
  - rot: 0 → 200° over 12.2–15.0 (in), then → 360° over 15.0–19.3 (out). 360° looks identical to
    0°, so the handback is invisible.
  - scale: 1 → 0.16 over 13.8–15.0 (in); hold; 0.16 → 1 over 18.5–19.3 (out).
  - dx: 0 → −24 (16.9–17.3) → +386 (17.3–18.5, linear) → 0 (18.5–19.3).
  - dy: 0 → +496 (16.9–17.3) → hold → 0 (18.5–19.3).

  At scale 0.16 its centre therefore runs along the strip's mid-line, y 1196, from x 700 to 1110.
- **rosette-ghost.** The same pivot and scale track, rot = −spin, α 0 → 0.55 over 12.2–12.7 and
  → 0 over 14.4–15.0. This is "each line is one agent's answer". **[I]**
- **flourish.** 560x200 master at d = 2.0, O = (444, 770), so it sits under the medallion. Drawn
  over frames 450–476 (15.0–15.9). α out 16.9–17.2.
- **Typing.** serials-empty α in 16.6–16.9. Each of the 12 glyphs per strip is a crop of
  `serials.png` (the glyph ±2 px, y 1173–1211) placed at its own master position. It switches on at
  t = 17.3 + 1.2·(x_glyph − 700)/410, which is when the medallion reaches it. The right strip types
  in sync, offset +1444 px. All of them go off at 19.4, under the re-stacked `serials`, which carries
  the same pixels. **[M]**
- **Camera.** WIDEC → ROS over 11.0–12.0; Vh ×0.982 by 19.2; → FULL over 19.2–20.0.

### 4.3 Beat 3 — redemption (seal)
- **seal-stamp.** d = 1, O = (2040, 200), P = (2540, 700). Its centre and R 280 coincide with
  `seal.png`. **[M]**
  - scale: 1.35 (21.0) → 1.30 (22.1) → 1.00 (22.3, cubic in).
  - rot: −12° → −8° → 0°.
  - dy: −110 → −110 → 0.
  - α: in 21.0–21.4; out 25.6–26.4. This is the "settle back": only the rim lettering leaves, and
    the seal underneath is the same art.
- **Impact.** Frame 669 (22.300 s). Camera vertical shake +10, −6, +3, 0 master px on frames
  670–673 (linear).
- **Camera.** FULL → SEAL over 20.0–21.0; Vh ×0.98 by 26.8; → WIDEC over 26.8–28.0.

### 4.4 Beat 4 — the peg and what guards it (corners)
- **Isolation.** Paper + corners alone over 28.0–28.4 → 28.9–29.3 (the "1"s in all four corners).
- **Main note.** It leans: rot 0 → −2° over 29.0–30.0, holds, then → 0 over 31.5–32.3 (cubic out),
  about (1632, 700).
- **Other notes.** The stacked composite at 0.7 scale (d = 1/0.7), α in 28.9–29.4.
  - 16:9: centres (−1218, 760) and (4482, 760).
  - 1:1 and 9:16: (1632, −550) above, and (1932, 1950) for the second note.
  - The "other" note leans 0 → −4° and straightens with the main note.
- **The line.** A 12000x8 hairline (#D8D3C7) at y 1631 (16:9) or 1700 (1:1, 9:16). sx 0→1 about the
  centre over 29.3–29.9 (out).
- **Second note.**
  - rot: 0 → 3° (29.0–30.0) → 8° (30.8) → 25° (31.6, in).
  - It slides by (0, +1420) in 16:9 or (0, +450) vertically over 30.0–30.8, which puts it below the
    line.
  - Pulled off frame by (+4500, +300) in 16:9 or (+1800, +3800) vertically over 31.1–31.6 (in).
  - The flat copy crossfades to the angle plate over 30.0–30.4. The plate is scaled so its measured
    note width, 1536 px, equals the flat note's 2284.8 master px (d = 0.6723).
  - The green silhouette (#2F5D50) floods it over 30.6–31.1.
- Note: the rule that "border ink floods it" is read as the border's engraved green, because pure
  ink would vanish into the ink ground. **[I]**

### 4.5 The push and the blink (33.5–36.5)
- **Camera.** ROW → END, `launch` easing, anchored on the oval centre (1632, 764). Zoom ratios:
  4050/384.2 = 10.5× (16:9), 4600/577.5 = 8.0× (1:1), 7600/1244 = 6.1× (9:16). **[M]**
- **Portrait-detail.** The crop (204, 24, 1640, 2000) with the feathered ellipse, O = (1253.16, 302),
  d = 1/0.462 = 2.1645. It cuts in at 33.45–33.5 over the identical layer, so the push can go 2.16×
  tighter than the layer's own resolution. **[M]**
- **Blink.** The launch cut's schedule, moved to the same distance from the hit: TB = 36.5 − 1.05
  = 35.45 s.
  - The lids close looking away over 35.45–35.57, crossfading through blink-075/050/025/000 (v =
    4·u/0.12).
  - Closed by 35.57; hold.
  - They reopen *looking at the viewer* over 35.61–35.77, through blink-look-025/050/075 to
    portrait-look.
  - The stare holds 0.73 s to the hit.

  Each eye-band plate (crop 496, 732, 1180, 287 of the 2048² plates) is a sprite at
  O = (1388.06, 629.10), d = 2.1645. The lower plate of a pair is opaque and the upper one carries
  the crossfade weight. Sampled at 30 fps, frames 1064–1066 (35.467–35.533) are closing, frame 1067
  (35.567) is fully closed, and frames 1068–1072 are the reopen. **[M]** The closed frame and the
  look-at-you frame were seen in decoded stills at 35.55 and 35.65 s. **[M]**
- **Flash and card.** Frames 1095–1096 are ivory. The end card for the format runs 1097–1199. The
  measured flash mean RGB is (245.0, 244.0, 237.0) after yuv420p, against palette (247, 245, 239).
  The end card's first and last frames are identical. **[M]**

## 5. Captions: placement, entry, exit

Every word on screen comes from `layers/caption-*.png`, or from the lettered layers: serial glyphs,
seal rim, coins, banner and corners. Nothing is typed by code. The flourish has no letters. **[M]**

| Caption | Plate | On (fade in) | Full | Fade out ends | On screen | Ground under it |
|---|---|---|---|---|---|---|
| a1 "Lock IMD. Borrow dollars." | ivory | 3.80–4.30 | 4.30–10.20 | 10.60 | 6.80 s | ink (oval framed above it; note lifted to 0.42 H) |
| a2 "The price is a question. / A panel of agents answers it." | ink | 11.70–12.20 | 12.20–16.40 | 16.80 | 5.10 s | paper (isolated) |
| a3 "Every note redeems for a dollar of IMD." | ink | 21.00–21.50 | 21.50–26.30 | 26.70 | 5.70 s | paper (isolated) |
| a4 "Fall short, and the swarm collects." | ivory | 28.30–28.80 | 28.80–31.10 | 31.50 | 3.20 s | ink |
| a5 "Minted by agents. Redeemable by anyone." (or a5-alt via `--kicker a5-alt`) | ivory | 31.65–32.15 | 32.15–34.35 | 34.75 | 3.10 s | ink until it is gone |

- **Entry.** α 0→1 over 0.5 s with cubic out, plus a 12 px rise (dy 12→0, cubic out).
- **Exit.** α 1→0 over 0.4 s, cubic in-out, in place. Nothing pops. **[M]**
- **Placement.**
  - 16:9: plate scale 1.0; the top of a 1-line plate at y 880 (text 920–990); the 2-line plate at
    y 800 (text 840–1004).
  - 1:1: scale 0.5625; tops at 938 and 901 (text 960–1000 and 924–1016).
  - 9:16: scale 0.5625; top at 1536 (text 1558–1598 and 1558–1651). **[M]**
- **Kicker clearance.** For the kicker to stay over ink, it must be gone before the note's bottom
  edge reaches the text. That edge reaches the text top at 34.927 s (16:9), 35.177 s (1:1) and
  35.693 s (9:16). a5 is gone at 34.75. No caption ever sits over the portrait's face. **[M]**
  (computed from plan.py's camera, and seen on decoded frames)

## 6. The rules, checked

| Rule | Status |
|---|---|
| No legible text from an image or video model | **[M]** No model is in the pipeline. All lettering is from repo plates. |
| No parameter values, percentages, addresses or network names | Film body **[M]**: none. The note's own "1" and serial "IMD 00000001 A" are existing lettered layers. **End card [O]**: the supplied cards carry the chip "MAINNET · STAGING", which is a network name, and the brief mandates the card unchanged. |
| Palette only | **[I]** Every generated pixel is ink, ivory, green or hair. Blends at anti-aliased edges, fades and resampling necessarily create intermediate shades. The repo plates (angle plate, end cards) were not re-audited. |
| The note is composited from the layers | **[M]** The main note is always its 8 layers. The side notes are those layers pre-stacked (equal to note-master within 2 levels). The angle plate is a kit plate, as the brief specifies. |
| The portrait is dignified; the blink is the one facial motion | **[M]** The face is never deformed. **[I]** The coin passes over the face in beat 1, which is a deliberate covering, not a motion of the face. |
| Clearly fictional | **[I]** Unchanged from the launch art (a frog portrait and the parody palette). |
| Captions in the lower third, ≥ 3.0 s, fading | **[M]** for placement and fades. **[O]** for the definition of "on screen": a4 and a5 are 3.2 s and 3.1 s including their fades, but only 2.3 s and 2.2 s fully opaque. |

## 7. The pipeline (executed)

Requirements: Python 3 (standard library only) and FFmpeg with libx264 and aac. Run it with
FFmpeg 7.0.2, the static build from johnvansickle.com, on PATH. **[M]** FFmpeg is not committed;
the repository's other renderers make the same assumption. From the repository root:

```sh
# animatic (delivered): 640x360, silent AAC track
python3 marketing/explainer/film-a/render.py --aspect 16x9 --width 640 --out artifacts/animatic-16x9.mp4
python3 marketing/explainer/film-a/check.py artifacts/animatic-16x9.mp4 --silent \
        --json marketing/explainer/film-a/verification-animatic-16x9.json

# full renders, once the music step delivers (anthem-a.wav: 40.000 s, 48 kHz)
python3 marketing/explainer/film-a/render.py --aspect 16x9 --music anthem-a.wav --out film-a-16x9.mp4
python3 marketing/explainer/film-a/render.py --aspect 1x1  --music anthem-a.wav --out film-a-1x1.mp4
python3 marketing/explainer/film-a/render.py --aspect 9x16 --music anthem-a.wav --out film-a-9x16.mp4
#   --kicker a5-alt swaps the kicker; --resume continues from finished 1 s segments
```

What `render.py` does:
1. **prep** (cached in `test/scratch/film-a/prep`):
   - stacks the 8 layers and checks the result against note-master;
   - cuts the angle note from its ink ground (flood fill, tolerance 40) and makes its green flood;
   - applies the feathered ellipse to portrait-detail;
   - draws the flourish's 27 frames.
2. **plan.** `plan.build(aspect)` returns the camera and 63 sprites (layers, covers, coins, glyphs,
   plates and captions), each with its rig and α track.
3. **Segments.** 1 s segments, cut at frame 1095, and halved while any sprite's scale changes by more
   than 2× (the rosette's collapse and the push give 15-frame segments). For each segment, every
   visible sprite is cropped to the region that can be seen and lanczos pre-scaled to its largest
   on-screen size, in one FFmpeg call. Sprites sharing a rig share the crop, so stacked layers
   resample identically.
4. **Frames.** Each of frames 0–1094 is one FFmpeg graph at 2× size (1280x720 for the animatic), in
   parallel:
   - each sprite is padded with a 2 px transparent margin;
   - it is warped by `perspective=…:sense=destination` with constant corners computed in Python;
   - it is faded by `colorchannelmixer=aa`;
   - it is overlaid on ink in stack order.

   The frame is then lanczos-reduced and streamed to an FFV1 segment.
5. **Assembly.** 2 ivory frames, then the end card scaled to size for 103 frames. Everything is
   concatenated and encoded with `libx264 -preset slow -crf 18 -profile:v high`, keyint 30,
   `yuv420p`, AAC 256 kb/s 48 kHz stereo (the music, or `anullsrc`), `-frames:v 1200`,
   `-t 40.000`, `-movflags +faststart`.
6. **check.py.** Uses ffprobe `-count_frames`: codec, profile, pix_fmt, fps, frame count, stream and
   container durations, and the pts of frames 1094–1097. It decodes the mean RGB of frames 1094,
   1095, 1096, 1097 and 1199, reads the top-level MP4 box order, and gets the audio peak from
   volumedetect. It exits non-zero on any breach.

Two FFmpeg behaviours were found while building this, and the pipeline guards against both. **[M]**
1. Inside `perspective`, the per-frame variable `in` does not count reliably (it stayed at one value
   behind `loop` in this build). The pipeline therefore never uses per-frame expressions.
2. `perspective` clamps out-of-range samples, which smeared edge pixels across the frame until the
   transparent margin was added.

A third, smaller one: PNG inputs default to a 1/25 timebase, which matters to any `setpts`-based
timing.

## 8. Measured results

`verification-animatic-16x9.json` (committed beside the code), for `artifacts/animatic-16x9.mp4`:
**[M]**

| Property | Value |
|---|---|
| Size | 6,046,776 bytes, 640x360 |
| Video | h264 High, yuv420p, r_frame_rate 30/1 |
| Frames | 1200 counted; 1200 pts |
| Durations | video 40.000000 s; container 40.000000 s; audio 40.000000 s |
| Audio | aac 48 kHz, 2 ch, max −91.0 dB (silent) |
| pts of frames 1094–1097 | 36.466667, **36.500000**, 36.533333, 36.566667 |
| Mean RGB | 1094 (159.4, 168.2, 142.8) the face; 1095 and 1096 (245.0, 244.0, 237.0) the flash; 1097 and 1199 (240.7, 239.8, 233.3) the card |
| Boxes | ftyp, moov, free, mdat (faststart) |
| Checks | all 11 passed |

The same pipeline at 360 px wide, for 1:1 (360x360) and 9:16 (360x640), wrote
`verification-test-1x1-360.json` and `verification-test-9x16-360.json`. All checks passed. These
test videos were not delivered. **[M]**

A 30-tile contact sheet decoded from the delivered MP4 (one frame every 1.5 s) shows every beat and
all five captions in order. **[M]**

## 9. Open items

1. **The end card shows a network name.** The supplied end cards carry "MAINNET · STAGING". The
   rules forbid network names, but the brief says to cut to these cards unchanged. A card without
   the chip needs a decision from the owner of `marketing/kit/render.mjs`. **[O]**
2. **No music yet.** The cut is planned against the brief's cue sheet: accents at 3.0, 11.0, 20.0
   and 28.0, the build from 33.5, the HIT at 36.500, silence by 39.900. The stamp impact at 22.300
   and the blink at 35.45 are picture events that the music may or may not hit. Once the takes
   arrive, the MEASURED section times in the music README must be compared with these, and the
   audio checked for silence after 39.900 (check.py does not yet test that window). **[O]**
3. **No full-resolution render yet.** Two things are untested at 1920x1080, 1080x1080 and
   1080x1920:
   - Render time: per-frame graphs cost roughly 4 to 10× more pixels than the animatic. **[I]**
   - Shimmer of fine engraving during the push and the rosette collapse. Segment-wise pre-scaling
     limits each warp to ≤ 2× minification, but this has not been judged at full size.

   The push ends 1.3× beyond portrait-detail's resolution in 16:9, as in the launch cut. **[O]**
4. **Caption duration definition.** a4 and a5 meet "≥ 3.0 s" only when the fades are counted. If
   the brief means fully opaque, beat 4 is too short to hold both captions without crossing the
   face rule. The kicker would then have to move off the push or shorten the a4 hold. **[O]**
5. **The kicker starts in beat 4.** The kicker starts at 31.65 instead of 33.5. This is the only
   way found to give it ≥ 3 s on clean ink before the push brings the portrait under the lower
   third. **[I]** A director's call. **[O]**
6. **9:16 caption size.** Plates at 0.5625 scale give about 40 px cap-height text on a 1920-tall
   frame. That is legible but small. Bigger plates would need two-line versions of a3 and a5, which
   do not exist yet. **[O]**
7. **Readings of the brief** that a director should confirm. **[I]/[O]**
   - "the figure travels along the serial strip" is read as the converged rosette medallion running
     the strip and typing the existing serial glyphs. No price is ever shown.
   - "border ink" is read as the engraved green.
   - "below the line" uses a hairline rule invented for the beat.
   - The coin covers the face for about 4 s in beat 1.
8. **9:16 crops.** In 9:16 the lifted stamp (scale 1.3) is cropped at the sides for about 1 s, and
   the end framing shows the banner's lettered "imdUSD" above the face. Both were seen in the test
   stills. Acceptable, but untested at full size. **[M]/[O]**
9. **Animatic sampling.** Every frame was rendered, but frames were inspected only as stills and
   contact sheets. No one has watched it in real time for rhythm. **[O]**
