Docs/Skills and code/skill video-visual-production

.claude/skills/video-visual-production/references/plan-format.md

Markdown112 lines12.1 KBSkill: video-visual-production

What the file says about itself

Plan format: how to storyboard a beat with few words: You do not write the beat sheet. You write a short plan (research/the-fall-of-moser-baer/video/plan-B#.json), and compose-beat.js turns it into the full beat sheet and ledger: it works…

Plan format: how to storyboard a beat with few words

You do not write the beat sheet. You write a short plan (research/the-fall-of-moser-baer/video/plan-B#.json), and compose-beat.js turns it into the full beat sheet and ledger: it works out word indexes, the on-screen text list, spoken-as mappings and default sounds. Run: node .claude/skills/video-visual-production/scripts/compose-beat.js --beat B#. It prints errors in plain words (which sentence is not covered, which word it could not find) and writes nothing until the plan is right.

The file

{
  "beat": "B7", "title": "Sunlight and a date",
  "facts": { "id": ["V", "the claim in one line", "source name and date", "https://url"] },
  "assets": [ ],
  "scenes": [
    { "s": 1, "type": "slam", "props": { "lines": [ { "text": "A huge bill, and a date", "cue": "line", "size": 100, "tone": "ink" } ] }, "cues": { "line": "@bill" } },
    { "s": [2, 3], "type": "stat", "props": { "value": "1,700", "label": "crore, the bill" }, "cues": { "stat": "@crore" }, "facts": ["id"], "say": { "1,700": "1,700" } }
  ]
}

Fields of a scene

FieldMeaning
sthe sentence number from the brief (S1 is 1), or [from, to] for several. Every sentence of the beat must be covered, in order, once
woptional ["@firstword", "@lastword"]: use only part of the sentence(s), so a long sentence can be two scenes (the next scene then also says s and its own w)
typea scene type from the lists below
propswhat to draw; strings you write appear on screen. Copy the example for the type and change the words
cuesname -> the spoken word when that build starts. "@word" = first time the word is said in this scene (case and punctuation ignored); "@word#2" = second time; "@start", "@end"; "@+3" = 3 words after the start; or a word index. Use the cue names the example uses
factsids of facts (defined in the plan's facts) that this scene draws. A scene of class D needs at least one
sayfor a number shown on screen: how the voice says it, when the voice does not say the digits ({"3rd": "teesra"}). If the voice does say the digits, no say is needed
recall{"1983": 3}: the number was spoken earlier in the beat at word 3 and is repeated on screen
classE evidence, D data, M diagram, T typography. Defaults: stat/bars/line/unitgrid D, doc/photo2 E, slam/date T, the rest M
sfxleave out for the default sounds (one or two per scene of a data type, thinned to the limits); "none" for silence; "paper": true on a doc adds the slide-in sound
enter"fade" for a short cross-fade (rare); default is a cut
waive_numerals{"17": "reason"}: a spoken number that must NOT be drawn (an unverified fact). Use only with a reason, and report it
blacktrue: a declared black frame (a pause), with type black
textonly for scene types with no automatic text list (the error says so)

Facts

"facts": { "id": [status, claim, source, url] }. Status V (checked on a page), V-1source, derived, analysis (the narrator's reasoning). Never CHECK or V-search: the lint refuses them. Copy claim, source and url from the script's fact ledger (research/the-fall-of-moser-baer/script/SCRIPT-v7.md section 5); the brief lists candidate rows.

Assets (only for doc and photo2 scenes)

Which scene for which sentence

The sentenceScene
a single plain statement, or nothing honest to drawslam (never more than 3 in a row, never more than 40% of the beat)
one big figurestat (status dotted if single-source or derived, ghost if searched for and not found)
two or more figures compared in the same unitbars (a negative value hangs below zero; ghost: true bar for "no record found")
a figure that moves over timeline (one or two series)
a datedate (a year) or calendar (a day circled)
a number of daysdeadline
a published passage the voice quotes or paraphrasesdoc (capture or quote asset)
a photograph from Commonsphoto2
a list of reasons or items, one of them missingchips (ghost: true for the missing one)
several problems counted aloudcards
two things beside each other with pricesobjects
a count too big to showunitgrid
a repeated resultstamps
a late start, a racerace
a price war with no winnerstalls
running to stay in placetreadmill; a stop button that is not pressed: stopbtn
a commitment that keeps growingqueue
a stack of layerslayers
a choice between two pathspaths
the pivot count (first used in B0 and B1)rungs

Examples by type (props and cue names that draw correctly; in a plan replace the cue values with "@word")

photo2 (no self-test image): props {"asset":"photo1","caption":"what the photo is","caption2":"a note if it is not the thing itself","credit1":"Photo: Author Name","credit2":"CC BY-SA 4.0, Wikimedia Commons","notes":[{"text":"a note","cue":"n1"}]}; cue names: photo, n1. doc: props {"asset":"bt2011-a","tag":"Source: Business Today, 17 Apr 2011","highlights":[{"phrase":"phrase one","cue":"p1"}],"focus":"phrase one"}; cue names: the highlight cues. slam: props {"lines":[{"text":"two to seven words","cue":"line","size":100,"tone":"ink"}]} (tone: ink, muted, accent, good; up to two lines); cue names: the line cues. date: props {"year":"2012","chapter":"OPTIONAL SMALL LABEL"}; cue name: year.

Rules that the checks enforce (so the plan should respect them)

Show the raw text
# Plan format: how to storyboard a beat with few words

You do not write the beat sheet. You write a short **plan** (`research/the-fall-of-moser-baer/video/plan-B#.json`), and `compose-beat.js` turns it into the full beat sheet and ledger: it works out word indexes, the on-screen text list, spoken-as mappings and default sounds. Run: `node .claude/skills/video-visual-production/scripts/compose-beat.js --beat B#`. It prints errors in plain words (which sentence is not covered, which word it could not find) and writes nothing until the plan is right.

## The file

```json
{
  "beat": "B7", "title": "Sunlight and a date",
  "facts": { "id": ["V", "the claim in one line", "source name and date", "https://url"] },
  "assets": [ ],
  "scenes": [
    { "s": 1, "type": "slam", "props": { "lines": [ { "text": "A huge bill, and a date", "cue": "line", "size": 100, "tone": "ink" } ] }, "cues": { "line": "@bill" } },
    { "s": [2, 3], "type": "stat", "props": { "value": "1,700", "label": "crore, the bill" }, "cues": { "stat": "@crore" }, "facts": ["id"], "say": { "1,700": "1,700" } }
  ]
}
```

## Fields of a scene

| Field | Meaning |
|---|---|
| `s` | the sentence number from the brief (S1 is 1), or `[from, to]` for several. Every sentence of the beat must be covered, in order, once |
| `w` | optional `["@firstword", "@lastword"]`: use only part of the sentence(s), so a long sentence can be two scenes (the next scene then also says `s` and its own `w`) |
| `type` | a scene type from the lists below |
| `props` | what to draw; strings you write appear on screen. Copy the example for the type and change the words |
| `cues` | name -> the spoken word when that build starts. `"@word"` = first time the word is said in this scene (case and punctuation ignored); `"@word#2"` = second time; `"@start"`, `"@end"`; `"@+3"` = 3 words after the start; or a word index. Use the cue names the example uses |
| `facts` | ids of facts (defined in the plan's `facts`) that this scene draws. A scene of class D needs at least one |
| `say` | for a number shown on screen: how the voice says it, when the voice does not say the digits (`{"3rd": "teesra"}`). If the voice does say the digits, no `say` is needed |
| `recall` | `{"1983": 3}`: the number was spoken earlier in the beat at word 3 and is repeated on screen |
| `class` | E evidence, D data, M diagram, T typography. Defaults: stat/bars/line/unitgrid D, doc/photo2 E, slam/date T, the rest M |
| `sfx` | leave out for the default sounds (one or two per scene of a data type, thinned to the limits); `"none"` for silence; `"paper": true` on a doc adds the slide-in sound |
| `enter` | `"fade"` for a short cross-fade (rare); default is a cut |
| `waive_numerals` | `{"17": "reason"}`: a spoken number that must NOT be drawn (an unverified fact). Use only with a reason, and report it |
| `black` | `true`: a declared black frame (a pause), with type `black` |
| `text` | only for scene types with no automatic text list (the error says so) |

## Facts

`"facts": { "id": [status, claim, source, url] }`. Status `V` (checked on a page), `V-1source`, `derived`, `analysis` (the narrator's reasoning). Never `CHECK` or `V-search`: the lint refuses them. Copy claim, source and url from the script's fact ledger (`research/the-fall-of-moser-baer/script/SCRIPT-v7.md` section 5); the brief lists candidate rows.

## Assets (only for doc and photo2 scenes)

- Article passage as a picture of the page: `{ "id": "bt2011", "kind": "capture", "fallback": true, "url": "...", "outlet": "Business Today", "published": "17 Apr 2011", "crops": [ { "id": "bt2011-a", "anchor": ["first words of the passage", "last words of the passage"], "highlights": ["phrase the voice quotes"], "fallback_text": "the passage, verbatim" } ] }`. The words must exist on the page: check with `curl -s -A "Mozilla/5.0" URL` and search. Then `props: { "asset": "bt2011-a", "tag": "Source: Business Today, 17 Apr 2011", "highlights": [ { "phrase": "...", "cue": "p1" } ] }` and a cue for each highlight. The crop shows whole lines, so check the picture for unspoken figures.
- A short quote typeset on purpose: `{ "id": "q1", "kind": "quote", "url": "...", "outlet": "...", "published": "...", "text": "... fragment ...", "highlights": ["fragment"] }`; only wording you have read on the page or that a [V] ledger row quotes.
- A photo: Wikimedia Commons, free licence only: `{ "id": "photo1", "kind": "commons", "title": "File:Name.jpg", "width": 1500, "use": "what it shows" }`; find files with `node research/video-production/probes/probe-commons.js "search words"`. Use scene `photo2` with credit lines; if the photo shows a different thing from the one the voice names, the caption says so.
- After composing run `node .claude/skills/video-visual-production/scripts/assets.js --sheet research/the-fall-of-moser-baer/video/beat-sheet-B#.json` and read its output.

## Which scene for which sentence

| The sentence | Scene |
|---|---|
| a single plain statement, or nothing honest to draw | `slam` (never more than 3 in a row, never more than 40% of the beat) |
| one big figure | `stat` (status `dotted` if single-source or derived, `ghost` if searched for and not found) |
| two or more figures compared in the same unit | `bars` (a negative value hangs below zero; `ghost: true` bar for "no record found") |
| a figure that moves over time | `line` (one or two series) |
| a date | `date` (a year) or `calendar` (a day circled) |
| a number of days | `deadline` |
| a published passage the voice quotes or paraphrases | `doc` (capture or quote asset) |
| a photograph from Commons | `photo2` |
| a list of reasons or items, one of them missing | `chips` (`ghost: true` for the missing one) |
| several problems counted aloud | `cards` |
| two things beside each other with prices | `objects` |
| a count too big to show | `unitgrid` |
| a repeated result | `stamps` |
| a late start, a race | `race` |
| a price war with no winner | `stalls` |
| running to stay in place | `treadmill`; a stop button that is not pressed: `stopbtn` |
| a commitment that keeps growing | `queue` |
| a stack of layers | `layers` |
| a choice between two paths | `paths` |
| the pivot count (first used in B0 and B1) | `rungs` |

## Examples by type (props and cue names that draw correctly; in a plan replace the cue values with `"@word"`)

- `stat`: props `{"value":"1,100","label":"crore, planned in 2000","sub":"when the country chased services","source":"Source: Business Today, 22 Jun 2003","status":"solid"}`; cue names: stat, label
- `bars`: props `{"bars":[{"label":"FY07","text":"110","unit":"crore","value":110,"cue":"a"},{"label":"FY08","text":"79","unit":"crore loss","value":-79,"cue":"b"},{"label":"next","text":"no record","value":0,"ghost":true,"cue":"c"}],"source":"Source: Storage Newsletter"}`; cue names: a, b, c
- `line`: props `{"series":[{"label":"price","points":[{"x":"Oct 2002","y":28,"text":"28","sub":"cents","cue":"p1"},{"x":"later","y":14,"text":"14","sub":"cents","cue":"p2"}]}],"source":"Source: Taipei Times, 12 Oct 2002"}`; cue names: p1, p2
- `chips`: props `{"title":"four reasons given","items":[{"text":"cheaper imports","cue":"a"},{"text":"weak demand","cue":"b"},{"text":"pen drives","cue":"c","tone":"muted"},{"text":"price","cue":"d","tone":"accent"},{"text":"a missing customer: not mentioned","cue":"e","ghost":true}]}`; cue names: a, b, c, d, e
- `race`: props `{"track":"CD-R","lanes":[{"label":"Taiwan","pos":0.7,"cue":"a","tone":"muted"},{"label":"India","pos":0,"cue":"b","tone":"accent"}]}`; cue names: a, b
- `stalls`: props `{"stalls":[{"label":"stall A","boards":[{"text":"10","cue":"a1"},{"text":"8","cue":"a2"},{"text":"6","cue":"a3"}]},{"label":"stall B","boards":[{"text":"10","cue":"b1"},{"text":"9","cue":"b2"},{"text":"7","cue":"b3"}]}],"fill":{"count":10,"cue":"fill","text":"nobody is richer"}}`; cue names: a1, b1, a2, b2, a3, b3, fill
- `treadmill`: props `{"speeds":[{"cue":"s1","v":1},{"cue":"s2","v":2.2}],"outline":{"cue":"o"},"label":"running faster to stay in place","labelCue":"s1"}`; cue names: s1, s2, o
- `unitgrid`: props `{"icon":"disc","count":25,"cols":5,"legend":"1 icon = 100 million discs","value":"2.5 billion","span":2}`; cue names: grid, legend, value
- `stamps`: props `{"count":5,"text":"WON","title":"five cases in Europe","labels":["1","2","3","4","5"],"every":0.4}`; cue names: stamps, title
- `paths`: props `{"from":"now","options":[{"text":"stay small","cue":"a","tone":"muted"},{"text":"pivot again","cue":"b","tone":"accent","glow":true}]}`; cue names: from, a, b
- `rungs`: props `{"count":4,"rungs":[{"state":"filled","cue":"r1","label":"floppy"},{"state":"filled","cue":"r2","label":"CD-R"},{"state":"glow","cue":"r3","label":"next"},{"state":"hollow"}],"title":"PIVOT 3","titleCue":"t"}`; cue names: r1, r2, r3, t
- `calendar`: props `{"month":"JUNE","day":"21","year":"2012","stamp":"BOND DUE"}`; cue names: page, circle, stamp
- `deadline`: props `{"value":"197","unit":"days","fromLabel":"7 Dec 2011","toLabel":"21 Jun 2012","shrink":0.18}`; cue names: clock, value, from, to
- `layers`: props `{"title":"a plate coated in thin layers","layers":[{"label":"base plate","cue":"a"},{"label":"thin film","cue":"b"},{"label":"contact","cue":"c"}]}`; cue names: title, a, b, c
- `objects`: props `{"items":[{"icon":"disc","label":"a film on disc","tag":"34","cue":"a"},{"icon":"bar","label":"a chocolate","tag":"28","cue":"b"}]}`; cue names: a, b
- `cards`: props `{"cards":[{"title":"the discs lost money","sub":"FY08","source":"Source: Storage Newsletter","cue":"a"},{"title":"solar lost three years running","sub":"to 2011","source":"Source: Business Today","cue":"b"},{"title":"a bond fell due","sub":"21 June 2012","source":"Source: Business Today","cue":"c","tone":"loss"}]}`; cue names: a, b, c
- `queue`: props `{"counter":"the counter","count":10,"committed":3,"label":"a longer line, not a shorter one"}`; cue names: counter, line, label
- `stopbtn`: props `{"label":"the stop button"}`; cue names: button, fade
- `nested`: props `{"outer":{"text":"4,828","value":4828,"label":"crore claimed in all"},"inner":{"text":"1,101","label":"crore","value":1101,"alleged":true},"rest":{"label":"no allegation found"},"chip":{"text":"alleged, not proved","cue":"inner"}}`; cue names: outer, inner, rest
- `stat-alleged` (scene type `stat`): props `{"value":"354","label":"crore, one case","sub":"as alleged by the CBI","status":"alleged","source":"Source: The Week, 2019"}`; cue names: stat, label
- `chips-gone` (scene type `chips`): props `{"items":[{"text":"insurance cover","cue":"a","gone":"g1"},{"text":"medical cover","cue":"b","gone":"g2"},{"text":"company buses","cue":"c","gone":"g3"},{"text":"salary on time","cue":"d","gone":"g4"}]}`; cue names: a, b, c, d, g1, g2, g3, g4
- `unitgrid-cells` (scene type `unitgrid`): props `{"icon":"cell","count":100,"cols":10,"lit":7,"legend":"a share of a hundred, not a payout","value":"7","span":1.2}`; cue names: grid, lit, legend, value

`photo2` (no self-test image): props `{"asset":"photo1","caption":"what the photo is","caption2":"a note if it is not the thing itself","credit1":"Photo: Author Name","credit2":"CC BY-SA 4.0, Wikimedia Commons","notes":[{"text":"a note","cue":"n1"}]}`; cue names: photo, n1.
`doc`: props `{"asset":"bt2011-a","tag":"Source: Business Today, 17 Apr 2011","highlights":[{"phrase":"phrase one","cue":"p1"}],"focus":"phrase one"}`; cue names: the highlight cues.
`slam`: props `{"lines":[{"text":"two to seven words","cue":"line","size":100,"tone":"ink"}]}` (tone: ink, muted, accent, good; up to two lines); cue names: the line cues.
`date`: props `{"year":"2012","chapter":"OPTIONAL SMALL LABEL"}`; cue name: year.

## Rules that the checks enforce (so the plan should respect them)

- Text stays on screen at least 1.2 s, or 0.35 s per word if longer: do not put a long text on the last short sentence of a scene. Cue it on an early word.
- Every number the voice says is shown in that scene; every number shown is said (or recalled).
- A source line (`source` or `tag`) on every evidence or data scene.
- Never draw a fact whose status is CHECK or V-search. Never invent a number, a name, a face, a logo or a picture of a real place.
- Reuse the same look for the same idea (the ladder is `rungs`).
- Keep a scene 3 to 8 seconds; split a long sentence with `w`; merge two very short ones with `s: [a, b]`.

Shown as it is in the repository at build time (2026-11-03); the home directory appears as <home>. If this page and the file differ, the file is right. Back to the catalogue.