# Explainer film B, "The bank and the swarm": storyboard, edit plan and animatic

Source brief: `marketing/explainer/BRIEF-B.md`. Pipeline: `marketing/explainer/film-b/build_b.py`
(ffmpeg + Python standard library; SHA-256 `5037c4fc…0e430`). Animatic: `artifacts/animatic-16x9.mp4`
(SHA-256 `b72eb2fd13154a32801eef48bc25618e80c09d25d946ce4eaf2d3f59a4a7de38`, 647,721 bytes).

**Evidence tags.** Every claim carries one tag:
- **[M] measured**: I ran it or read it from a file in this tree, and the command or file is named.
- **[I] inferred**: a design decision or a conclusion I drew from measurements but did not check directly.
- **[O] open**: unknown until the keyframes, the clips or the music exist, or a decision someone else owns.

I did not receive the keyframe images (K1–K6), any image-to-video clips, or the anthem WAVs. Everything
about them below is a plan against the brief's declared names and sizes **[M: BRIEF-B.md, keyframe
table]**. The layers, the caption plates and the end cards are real and were used as they are.

---

## 0. Fixed contract, and how the animatic meets it

| Item | Contract | Animatic result | Tag |
|---|---|---|---|
| Length | 40.000 s at 30 fps = 1200 frames | 1200 frames counted; video stream 40.000000 s; container 40.000000 s | [M] ffprobe `-count_frames` |
| HIT | 36.500 s = frame 1095 | Flat ivory frames are exactly 1095 and 1096 (after frame 900); frame 1095 pts = 36.500000 | [M] ffprobe + `signalstats` |
| Flash | two ivory frames | frames 1095–1096 `#F7F5EF`; frame 1094 is not flat (YMIN 5) | [M] |
| End card | 36.567–40.000 s (frames 1097–1199) | PSNR against `endcard-1920x1080.png` = 54.13 dB | [M] ffmpeg `psnr` |
| Music silent by 39.900 | — | Animatic is silent (a silent AAC track). The music path's check passed on a synthetic test tone: −91.0 dB after 39.9 s, −31.5 dB from 39.0 to 39.8 s, so the check is not vacuous | [M] in test/scratch; [O] for the real anthem |
| Encode | H.264 yuv420p, AAC, +faststart | h264 High, yuv420p, 640x360, 30/1; AAC LC 48 kHz stereo; top-level atoms `ftyp, moov, free, mdat` | [M] |

Music cue times, as the music step reported them in `marketing/explainer/music/README.md`: 144.6575 BPM;
section changes at 4.000, 14.000, 24.000, 29.000 (the agreement note) and 31.000; the HIT at 36.500 on
the downbeat of bar 23; digital silence from 36.2926 to 36.5000 s; last nonzero sample at 39.89 s
**[M: read from that README; the WAVs are not in this tree, so I could not re-measure them → O]**. Every
picture cut below lands on one of those times **[I]**.

---

## 1. Shot table: 16:9 (1920x1080 reference; the animatic is the same plan at a scale of 1/3)

Times are seconds on the film clock. "Push z a→b about (x, y)" is a zoom whose fixed point is (x, y) in
keyframe pixels. Captions are the real plates `layers/caption-b*-ink.png` on an ivory card with a 2 px
hairline `#D8D3C7` border (card = the plate's ink bounding box + 48 px left and right, + 22 px top and
bottom). Plates are centred at x 960, y 864 (the lower third). The exception is b1, which sits on the
bare ivory card.

| # | Start | End | Source | Framing | Motion | Transition out | Caption |
|---|---|---|---|---|---|---|---|
| 0 | 0.000 | 4.000 | flat ivory `#F7F5EF` | full frame | none | hard cut at 4.000 | **b1** typed: an ivory cover slides off the real plate, 40 ms a character, from 0.400 to 1.800 (35 characters); a blinking ink cursor; holds; fades out 3.600–4.000 |
| 1 | 4.000 | 9.500 | K1 `k1-bank.png` | full 1920x1080 | push z 1.00→1.06 about (960, 600), the vault door | cross-dissolve 8.500–9.500 | — |
| 2 | 8.500 | 16.000 | K2 `k2-board.png` | full | push z 1.00→1.05 about (960, 430), the raised blank coin | line dissolve 14.000–16.000 | — |
| 3 | 14.000 | 20.500 | K3 `k3-dissolve.png` | full | push z 1.00→1.03 about (960, 540) | line dissolve 19.000–20.500 | **b2** in 16.000–16.400, out 22.600–23.000 (7.0 s) |
| 4 | 19.000 | 24.000 | K4 `k4-swarm.png` | full | pull z 1.08→1.00 about (960, 540): the wall reads floor to ceiling | hard cut at 24.000 | (b2 continues) |
| 5 | 24.000 | 31.000 | K5 `k5-converge.png` + typing + flourish | full | push z 1.00→1.08 about (960, 460), the screen's centre | hard cut at 31.000 | **b3** in 27.800–28.100, out 30.700–31.000 (3.2 s) |
| 6 | 31.000 | 36.500 | K6 `k6-press.png` + the note from `layers/` | full | static until 34.000, then push z 1.00→1.15 (quadratic ease-in) about (960, 364.48), the portrait's centre | HIT | **b4** in 32.600–32.900, held to the cut (3.9 s) |
| 7 | 36.500 | 36.567 | flat ivory | full | — | hard cut | — |
| 8 | 36.567 | 40.000 | `marketing/kit/endcard/endcard-1920x1080.png` | full | none | end | (the end card's own lettering) |

All of this is [M] in the sense that the animatic was built from exactly these numbers. Every choice of
time, focus point or zoom is [I], a design decision. Each caption is on screen for at least 3.0 s: b1
3.6 s, b2 7.0 s, b3 3.2 s, b4 3.9 s **[M: from the script's constants; the caption-band checks at 2.5,
19.5, 29.5 and 35.0 s pass, and the band is empty at 0.1, 12.0, 25.0 and 31.5 s]**.

## 1b. Shot table: 1:1 (1080x1080)

Same times, sources, motion and transitions as 16:9. The scene is the 16:9 picture centre-cropped at x
420–1500. Captions are the same ink plates on an ivory card, scaled 0.5625 (1080 wide) and centred at
y 880. The end card is `endcard-1080x1080.png`.

| # | Start | End | Framing difference from 16:9 |
|---|---|---|---|
| 0 | 0.000 | 4.000 | b1 typed at 0.5625 scale |
| 1–4 | 4.000 | 24.000 | centre crop. **Every keyframe must keep its subject inside x 420–1500** (the brief's rule) [M: BRIEF-B] |
| 5 | 24.000 | 31.000 | the screen (x 660–1260) sits inside the crop [M: geometry] |
| 6 | 31.000 | 36.500 | the note (x 437.76–1482.24) sits inside the crop at z 1.00; the push to 1.15 crops its outer border by design [M: rendered in test/scratch, checks pass] |
| 7–8 | 36.500 | 40.000 | flash, then the end card |

## 1c. Shot table: 9:16 (1080x1920)

Same times and motion. The ground is ink `#16202E`. The scene is the 16:9 picture scaled 0.5625 to
1080x608 and placed at y 500 (the band runs y 500–1108). Captions are the **ivory** plates
(`caption-b*-ivory.png`), scaled 0.5625, on the bare ink ground, centred at y 1300 below the band. b1's
typing cover is ink. The flash is full-frame ivory; the end card is `endcard-1080x1920.png`.

| # | Start | End | Framing difference |
|---|---|---|---|
| 0 | 0.000 | 4.000 | ivory band on ink; b1 types in ivory below it |
| 1–6 | 4.000 | 36.500 | the letterboxed 16:9 picture; captions below the band, never over the engraving |
| 7–8 | 36.500 | 40.000 | full-frame flash, then the 9:16 end card |

1:1 and 9:16 were rendered at 360 px wide in test/scratch and pass every check that applies to them
**[M]**. They are not delivered.

---

## 2. Keyframes: image-to-video prompts, settings, and code-only fallbacks

Rules for every clip **[M: BRIEF-B]**:
- Engraving register only: ivory paper, ink linework, green for secondary pattern and for traces, oxblood
  only for one seal or one lamp per frame, hairline for faint rules. No flat colour, no gradients, no
  photographic texture.
- **No legible text, letters or numerals.** If any glyph-like mark appears in a clip, reject the clip.
- No new objects. No faces that read as real people. No real bank, logo or currency.

Settings for every clip: 1920x1080, generated at the model's native rate and conformed to 30 fps by the
pipeline (`fps=30`). Use the first frame as the conditioning image, with the strongest image-adherence
setting the model offers. Motion strength low. Seed noted in the delivery README **[I]**.

Shot lengths are fixed by the edit **[M: script SHOTS]**. A clip may be longer, but it is cut to the shot.
The pipeline refuses a clip that is too short: a 4.0 s K6 clip was rejected with "needs 5.500 s"
**[M: test/scratch]**.

Negative prompt for all clips: *text, letters, numbers, signage, logos, watermark, faces, photograph,
colour gradient, glow, lens flare, depth of field, film grain, new objects, people entering frame.*

### K1 `k1-bank.png`: clip 5.5 s (4.0–9.5)
- **Prompt:** "A steel-engraved banknote vignette of an empty classical bank façade, symmetrical columns
  and pediment, the great door open on a vault door in shadow. Fine intaglio hatching in dark ink on
  ivory paper. The camera dollies forward very slowly toward the doorway. Nothing else moves."
- **Camera:** slow dolly-in, about 6 percent by the end, centred on the doorway (960, 600).
- **Must not change:** the symmetry, the column count, the hatching pitch. The pediment cartouche stays
  EMPTY. No people.
- **Fallback (code):** the still with a push z 1.00→1.06 about (960, 600), linear. This is what the
  animatic does.

### K2 `k2-board.png`: clip 7.5 s (8.5–16.0)
- **Prompt:** "The same engraving style: a long boardroom table seen from behind seated figures in suits;
  at the head, one standing figure slowly raises a single blank coin. Ivory paper, ink hatching. Slow
  push toward the standing figure."
- **Camera:** push in, about 5 percent, toward (960, 430).
- **Must not change:** no faces (backs and silhouettes only). The coin stays blank, with no relief
  letters. The table perspective is held, because K3 must match it. The figure count is fixed.
- **Fallback:** still push z 1.00→1.05 about (960, 430). The coin is already raised in the still.

### K3 `k3-dissolve.png`: clip 6.5 s (14.0–20.5)
- **Prompt:** "The same boardroom, the right half of the frame turned into a wall of engraved instrument
  panels (round dials, needle meters, small oscilloscope screens with one green trace each), the left
  half still the table and the figures, in one perspective. Needles tremble slightly. The camera is
  almost still."
- **Camera:** locked off or a push of 3 percent at most.
- **Must not change:** the left half must match K2. The seam at the centre must not drift. Traces green
  only.
- **Fallback:** still push z 1.00→1.03 about (960, 540).

### K4 `k4-swarm.png`: clip 5.0 s (19.0–24.0)
- **Prompt:** "A wall of hundreds of small engraved instruments floor to ceiling, repeated like
  guilloché lathe work, a few screens with a single green trace; needles tremble. Slow pull back."
- **Camera:** pull back from 1.08 to 1.00.
- **Must not change:** the lathe-like repetition (not sci-fi). No lamps except at most one oxblood.
  Nothing on any dial face that reads as a numeral (tick marks only).
- **Fallback:** still pull z 1.08→1.00 about (960, 540).

### K5 `k5-converge.png`: clip 7.0 s (24.0–31.0), **LOCKED OFF**
- **Prompt:** "Close on the instrument wall: many dials, all needles slowly swinging to point at the same
  mark and settling together; in the centre, one empty framed screen stays blank. Locked-off camera."
- **Camera:** none. The pipeline adds the push (1.00→1.08 about the screen's centre) after the typing is
  drawn, so the typing stays glued to the screen. A camera move inside the clip would make the typing
  slide **[I]**.
- **Must not change:** the empty screen's rectangle. Its inner edge must be at **x 660–1260, y 280–640**
  in the keyframe (the stand-in draws it there), and its interior must stay plain ivory. The needles
  should settle by 29.0 s (5.0 s into the clip). No oxblood (the frame's one oxblood allowance is
  unused).
- **Fallback:** the still and the code push. The needles then don't move. That is acceptable, because
  the typing carries the beat.

### K6 `k6-press.png`: clip 5.5 s (31.0–36.5), **LOCKED OFF**, or a still (recommended)
- **Prompt:** "An engraved intaglio printing press slot, a blank ivory sheet resting where it emerged,
  the rollers' hatching shimmering slightly; locked-off camera, the sheet does not move."
- **Camera:** none. The note is composited onto the sheet; if the sheet moves, the note would need corner
  tracking, which this pipeline does not do **[O]**.
- **Must not change:** the sheet's position and shape. **The sheet must exactly underlie the note at
  x 437.76–1482.24, y 120–568** (the stand-in draws it there). One oxblood at most, and the note's seal
  already uses it, so there should be no oxblood elsewhere in K6.
- **Fallback (and recommendation):** use the still. The note assembly and the push are entirely code.

**How the pipeline picks sources [M]:** for each name it uses `keyframes/<name>.mp4` (or `.mov`) if
present, then `<name>.png`, then with `--standins` a stand-in. A clip is conformed and cut, with no code
push for K1–K4. K5 and K6 always get the code push. The clip path was exercised with synthetic 24 fps
clips for K2 and K5; their motion reached the output and all checks passed **[M: test/scratch]**.

---

## 3. The dissolve scheme: from the boardroom to the wall

| Transition | Window | Kind | Tag |
|---|---|---|---|
| K1 → K2 | 8.500–9.500 | ordinary cross-dissolve (`xfade=fade`, 1.0 s) | [M] |
| K2 → K3 | 14.000–16.000 | **line dissolve**, 2.0 s, on the music's machine entry at 14.000 | [M] |
| K3 alone | 16.000–19.000 | the half-transformed room holds under b2 | [M] |
| K3 → K4 | 19.000–20.500 | line dissolve, 1.5 s | [M] |
| K4 alone | 20.500–24.000 | the full wall, pull back | [M] |

**How the line dissolve works:** it is an `xfade` custom expression. Each pixel switches from the old
frame to the new one when the progress q = 1 − P passes a threshold of `0.8·(1 − X/W) + 0.05·(group of
4 rows)`, softened over 0.05 of progress:

```
A + (B − A) · clip(((1 − P) − (0.8·(1 − X/W) + 0.05·mod(floor(Y / max(1, H/270)), 4))) / 0.05, 0, 1)
```

So the change sweeps **right to left**: the right half, where K3 has already become panels, goes first.
It travels in staggered bands of engraved lines (a line pitch of 4 px at 1080), like a burin re-cutting
the plate **[I: the intent]**. On the stand-ins the sweep and the bands are visible on the tick marks and
the hairline frame **[M: contact sheets in test/scratch]**.

Because K2 and K3 share the left half and one perspective, the K2 → K3 sweep only visibly changes the
right half. Whether the real K2 and K3 line up well enough for that to read is open **[O]**.

**Bug found and fixed:** the first version used `st()`/`ld()` registers in the expression. xfade
evaluates slices in parallel, so the registers raced and the result was coloured noise. The registers
were removed **[M]**.

---

## 4. The typed question and figure on K5's empty screen

Everything is typed by the cut in DejaVu Sans Mono, ink `#16202E`, at 40 ms a character **[M: script]**.
Each prefix of a line is drawn for its 40 ms window, so no glyph metrics are needed.

| Element | Text | Size | Position (top-left, keyframe px) | Types | Tag |
|---|---|---|---|---|---|
| Question, line 1 | `What is one IMD` | 40 px | (700, 314) | 24.600–25.160 | [M] |
| Question, line 2 | `worth in dollars?` | 40 px | (700, 366) | 25.300–25.940 | [M] |
| Figure | `1.2345` | 110 px | (700, 436) | 28.800–29.000; **the last digit lands on 29.000**, the agreement note | [M] |
| Signature flourish | a code-drawn ink stroke, three loops and a tail; no letters | 2.5 px radius | box (700, 570, 520×50) | drawn on 29.100–29.600 | [M] |

- **Why this font:** the plates' font stack is `'SFMono-Regular', Menlo, Consolas, 'Liberation Mono',
  monospace` **[M: `marketing/the-note/note.js` line 205]**. b1's plate spans x 201–1712, and DejaVu Sans
  Mono rendered by ffmpeg at 72 px spans x 201–1711 **[M]**. Its advance, 1233/2048 em (43.35 px), also
  puts every space of b1 inside the plate's measured gaps **[M]**. So the plates were almost certainly
  rendered in DejaVu Sans Mono **[I]**. A copy of the font is in `marketing/explainer/film-b/fonts/`, with
  its licence.
- **The figure is a placeholder [O].** The sequential `1.2345` was picked to read as a sample, not as an
  IMD price. It is not a parameter value or a percentage. Marketing must approve it or swap it. Two other
  choices are worth considering: a figure that is obviously illustrative, or no figure at all, with only
  the flourish.
- **The question's wording is mine [O].** It is meant to be "the one fixed question" of the brief.

---

## 5. The note's assembly at 31.000 s: stack order and exact transforms

All eight layers are 3264x1400 and transparent, and they share master coordinates. Stacked in order they
reproduce `note-master.png` (`geometry.json → stackEqualsMaster.differingPixels = 0`)
**[M: geometry.json; PNG headers read]**. So **one transform serves every layer**:

```
x' = 437.76 + 0.32 · X        y' = 120 + 0.32 · Y        (X, Y in master pixels; 1920x1080 keyframe)
note on screen: 1044.48 x 448 at (437.76, 120); portrait centre (1632, 764) → (960.00, 364.48)
```

| Order | Layer | Opacity 0 → 1, linear over 0.20 s (6 frames) | Transform |
|---|---|---|---|
| 1 | `paper.png` | 31.000–31.200 | as above |
| 2 | `border.png` | 31.300–31.500 | as above |
| 3 | `rosette.png` | 31.600–31.800 | as above |
| 4 | `portrait.png` | 31.900–32.100 | as above |
| 5 | `seal.png` | 32.200–32.400 | as above |
| 6 | `corners.png` | 32.500–32.700 | as above |
| 7 | `banner.png` | 32.800–33.000 | as above |
| 8 | `serials.png` | 33.100–33.300 | as above |

- The note is complete at 33.300. The push to the portrait runs 34.000–36.500 with
  z(t) = 1 + 0.15·clip((frame − 90)/74, 0, 1)², fixed point (960, 364.48) **[M: script]**.
- **Scale changed from 0.40 to 0.32 [M].** At 0.40 the note was 1306 px wide, wider than the 1:1 safe
  area. The 1:1 render cut off its corner numerals, so the scale is now 0.32.
- The layers are downscaled with `flags=area` straight from the masters, and are never re-drawn by any
  model **[M]**.
- Check: dark pixels in the note's region go from 0 at 31.0 s to 11,037 at 34.0 s **[M]**.
- **Perspective [O].** The brief's sheet emerges "at a slight angle". If the final K6 tilts the sheet,
  replace the flat transform with a corner-pin: take the sheet's four corners measured on the real K6,
  and apply ffmpeg `perspective=` with `sense=destination` to each scaled layer before the overlay.
  This is not implemented, because the corners are unknown.

---

## 6. The pipeline (executed)

`marketing/explainer/film-b/build_b.py` uses only ffmpeg/ffprobe and the Python standard library.
It was run here with ffmpeg 8.0.1 (Ubuntu build) **[M]**.

```
python3 marketing/explainer/film-b/build_b.py --standins --aspect 16x9 --width 640 \
    --out artifacts/animatic-16x9.mp4                      # the delivered animatic: ALL CHECKS PASS
# production: put k1-bank.png … k6-press.png (or .mp4 clips) in marketing/explainer/film-b/keyframes/
python3 marketing/explainer/film-b/build_b.py --aspect 16x9 --music anthem-a.wav --out film-b-16x9.mp4
python3 marketing/explainer/film-b/build_b.py --aspect 1x1  --music anthem-a.wav --out film-b-1x1.mp4
python3 marketing/explainer/film-b/build_b.py --aspect 9x16 --music anthem-a.wav --out film-b-9x16.mp4
```

**Stages [M]:**
1. **Stand-ins.** These are drawn only where a keyframe is missing. Each is flat ivory at 1920x1080 with
   palette-only marks and no text:
   - a hairline 1:1 safe frame;
   - K-index ink ticks (n squares for Kn) at y 80, which let the check confirm each beat;
   - K2: a green outline where the coin is held;
   - K5: the screen, an ink frame with a green outer rule;
   - K6: the sheet outline and an ink slot.
2. **Scene to 36.5 s, rendered as four lossless FFV1 segments and concatenated:**
   - ivory card, 120 frames;
   - bank and dissolves, 600 frames: zoompan pushes with 2x supersampling, then the xfade chain;
   - screen, 210 frames: drawtext typing, the flourish PNG sequence written by the stdlib PNG writer,
     then the push;
   - press, 165 frames: the eight layer overlays with alpha fades, then the push.
3. **Per aspect:**
   - the ground and the crop or letterbox;
   - the caption cards: plate, ivory card and hairline, with alpha fades;
   - b1 typing, done by an overlay cover and cursor whose x is `201.42 + 43.348·n(t)` in plate px, with
     n(t) = clip(floor((t − 0.40)/0.04) + 1, 0, 35);
   - frames 0–1094, then two ivory frames, then the end card for 103 frames;
   - the audio: the anthem, or silent AAC;
   - the encode: `libx264 -crf 18 -pix_fmt yuv420p -profile:v high`, with keyframes forced at 36.500 and
     36.567, then `-c:a aac -b:a 192k -movflags +faststart -t 40`.
4. **Checks**, all with ffprobe or ffmpeg, all of which must pass for exit 0:
   - codec, pixel format, size, frame rate, 1200 frames, the video and audio durations;
   - moov before mdat, read from the top-level atoms;
   - the cut: flat ivory frames after 900 must be exactly [1095, 1096], frame 1095 must be at pts
     36.500, and frame 1094 must not be flat;
   - end-card PSNR above 30 dB;
   - each caption present at its midpoint and absent between captions;
   - the beat tick counts K1–K6 (stand-ins only);
   - the note assembled;
   - with `--music`: max volume after 39.900 s at or below −80 dB.

Delivered run on 2026-10-07, 640x360, stand-ins, silent: 29 of 29 checks pass, at about 30 s of wall time
**[M: log]**. Selected values:
- container 40.000000 s; 1200 frames;
- HIT frames [1095, 1096], at pts 36.500000;
- end card at 54.13 dB;
- beat ticks 1, 2, 3, 4, 5, 6 at 6.0, 11.0, 17.5, 22.5, 26.0 and 32.0 s.

Bugs found by running it and fixed:
- **The xfade register race** (section 3).
- **The tick counter.** Counting the maximum number of runs over all rows was inflated by anti-aliased
  edge rows, and the scan band also caught the note's top border. It now uses the darkest row above
  y 115.
- **Relative work paths.** They broke the concat list, so the work path is made absolute.
- **The note's scale**, 0.40 → 0.32 (section 5).

Not production-ready yet **[I]**:
- zoompan rounds its crop to whole pixels, which can shimmer on fine hatching. At full size, raise
  `--supersample` to 4 and judge the result.
- CRF 18 is a starting point.

---

## 7. Rules audit

| Rule | Status | Tag |
|---|---|---|
| No model text | Every word comes from the caption plates or is typed by drawtext in the plates' font. Stand-ins carry no glyphs. | [M] |
| No parameter values, percentages, addresses, network names in the film | None in the captions, the typed screen or the note layers I used (the note's "1" numerals and serial strips belong to the real note). **Exception, out of my scope: the kit end card's status badge carries a network-type word** | [M] read the end card; [O] for its owner |
| Palette only | The stand-ins, typing, flourish, cards, flash and grounds are palette colours. The end card PNG contains #5A6472, #868A8E and #BFBFBF (slate and the logo's greys) besides the palette | [M] pixel histogram of `endcard-1920x1080.png`; [O] |
| The note is never re-rendered by a model | It is composited only from `layers/` | [M] |
| Captions ≥ 3.0 s, lower third, fading | b1 3.6 s, b2 7.0 s, b3 3.2 s, b4 3.9 s. b4 fades in and leaves on the HIT cut rather than fading out | [M]; [O] whether a hard exit on the HIT is acceptable |
| Clearly fictional | No real bank or currency is shown; the figure is a placeholder | [I]; [O] approval of the figure |

---

## 8. What remains open

1. **The six keyframes.** The real images; whether each one holds its subject inside the 1:1 crop; and
   whether the K5 screen and K6 sheet land on the declared rectangles (screen x 660–1260, y 280–640;
   sheet x 437.76–1482.24, y 120–568).
2. **Image-to-video.** Which model, and whether it holds the engraving without inventing glyphs. If it
   doesn't, the code-only pushes and dissolves are complete and are what the animatic shows.
3. **K6 perspective.** If the sheet is tilted, a corner-pin is needed, and the corners must be measured
   on the real K6.
4. **The music.** `anthem-a.wav` and `anthem-b.wav` are not in this tree. Their cue times are reported
   by the music step but not re-measured here. The real silence check runs with `--music`.
5. **The K5 figure and question wording.** These need sign-off: `1.2345` is a placeholder.
6. **The end card.** It has off-palette greys and its status badge wording. It belongs to
   `marketing/kit/`, so I didn't change it.
7. **Caption backing.** The ivory card behind the captions is my choice for legibility over hatching. It
   is judged here only on flat stand-ins.
8. **Full-resolution render.** It hasn't been run here. 1920x1080 and the 1:1 and 9:16 aspects work at
   small sizes; zoompan shimmer at full size is not yet judged.
