# DPL plan format — `dpl.plan/1`

**Status:** first version. Last updated 18 October 2026.

A DPL plan describes a film the way a director plans it: every scene and every shot, what each is for, where the
audience's attention goes, and what is heard. It is **useful on its own**: a crew could shoot from it, a person
can read its shot list, and tools can read its shots in industry terms. It depends on nothing else in this
repository. Converting a plan to other formats is done by separate tools, which this format does not depend on.

**How to read this page.** Section 1 is the shape of the file, with two complete plans you can copy. Section 2 goes
through every part of a plan (the work and its house style, the world, the structure, the shots, the picture layers, the
sound and the transitions) and says what each part is **for**, so you know when to use it. Section 2.10 is the short-form profile for phone and small-crew shoots. Section 3 lists the details that switch the edit-readiness checks
on. Section 4 has the camera terms. Section 5 is validation and how to read its diagnostics.

## 1. Shape

One JSON document, UTF-8. These rules hold everywhere:

- **Names** are `snake_case`. **Terms** (`size`, `movement`, `kind` and the rest) are `snake_case` strings from a fixed
  list. `"Hold"`, `"PushIn"` and `"static-shot"` are not terms.
- **Times** are **whole milliseconds** from the start of the work (`start_ms`, `end_ms`; an end is exclusive). `1500.5` is
  refused, and so is `1500.0`.
- **Required** members must be present. An absent term never falls back to a default.
- **Optional** members may be left out, or written `null`, which means the same. A list that is left out is empty.
  Where a default is shown, the reader applies it when the member is absent.
- **Unknown members are refused**, in every object. So is a misspelt term. Nothing is silently ignored.
- **Ids** are short, plain and unique where the tables below say so. Characters, props and locations share one
  namespace; shot ids are unique across the whole plan.

**Schema.** The exact members, terms and requirements are published as a JSON Schema (draft 2020-12),
`contracts/dpl/dpl.plan-1.schema.json`; print it with `dpl schema plan`. It matches the reader member for member and
is held to it by tests. `contracts/dpl/README.md` says what each schema covers and what only the validator can check.
This page says what the members are for.

**Reading the tables.** Each object in section 2 has a table with its members in the schema's order. *Type* says what
goes there: `text`, a whole number, a number, true or false, a list, another object (named in code font, and described in
its own table), or one of a short list of terms. **required** marks a member that must be present; nothing else needs to
be. Times are milliseconds unless the meaning says otherwise.

### The smallest plan

One scene with one shot:

```json
{"format": "dpl.plan/1", "work": {"id": "w", "title": "W"},
 "scenes": [{"id": "s1", "purpose": "Open.", "start_ms": 0, "end_ms": 2000,
   "shots": [{"id": "a", "purpose": "Show the door.", "subject": "A door.", "start_ms": 0, "end_ms": 2000,
              "camera": {"size": "wide", "movement": "hold"}}]}]}
```

### A small plan to copy: two people talking

A dialogue scene with the details the edit-readiness checks look for: a line of action (`axis`), the people placed in
every shot (`staging`), who speaks to whom (`lines`, one of them turning the scene), the job of each shot (`coverage`)
and the set-up each is filmed from (`setup`). It validates with no diagnostics and `dpl readiness` finds nothing to
report. Change the names and the words, keep the shape.

```json
{
  "format": "dpl.plan/1",
  "work": {"id": "kitchen-short", "title": "The Kitchen", "thesis": "A marriage turns on one sentence about money.", "target_runtime_ms": 14000},
  "characters": [
    {"id": "MAYA", "description": "Maya, forties, has just learned the truth."},
    {"id": "TOM", "description": "Tom, her husband, who hid it."}
  ],
  "locations": [{"id": "KITCHEN", "description": "A small kitchen at dusk."}],
  "scenes": [
    {
      "id": "S1",
      "purpose": "Maya tells Tom what she knows; he cannot answer.",
      "change": "We know that she knows.",
      "start_ms": 0,
      "end_ms": 14000,
      "location": "KITCHEN",
      "axis": {"left": "MAYA", "right": "TOM"},
      "lines": [
        {"id": "L1", "speaker": "TOM", "to": "MAYA", "start_ms": 500, "end_ms": 2500, "text": "You're home early."},
        {"id": "L2", "speaker": "MAYA", "to": "TOM", "start_ms": 3500, "end_ms": 6500, "text": "I went to see your brother."},
        {"id": "L3", "speaker": "MAYA", "to": "TOM", "start_ms": 7500, "end_ms": 10000, "turn": true, "text": "He told me about the money."},
        {"id": "L4", "speaker": "TOM", "to": "MAYA", "start_ms": 12000, "end_ms": 13500, "text": "It's not what you think."}
      ],
      "shots": [
        {
          "id": "K1", "purpose": "Where they are: Maya at the door, Tom at the sink.", "subject": "KITCHEN",
          "start_ms": 0, "end_ms": 3000, "camera": {"size": "wide", "movement": "hold"}, "source": "footage",
          "characters": ["MAYA", "TOM"],
          "coverage": {"role": "establishing", "priority": "must"},
          "staging": [
            {"subject": "MAYA", "side": "left", "facing": "right"},
            {"subject": "TOM", "side": "right", "facing": "left"}
          ],
          "setup": "A"
        },
        {
          "id": "K2", "purpose": "Maya, as she says where she has been.", "subject": "MAYA",
          "start_ms": 3000, "end_ms": 7000, "camera": {"size": "medium", "movement": "hold"}, "source": "footage",
          "characters": ["MAYA"],
          "coverage": {"role": "single", "priority": "should"},
          "staging": [{"subject": "MAYA", "side": "left", "facing": "right"}],
          "setup": "B"
        },
        {
          "id": "K3", "purpose": "Tom takes it in.", "subject": "TOM",
          "start_ms": 7000, "end_ms": 9000, "camera": {"size": "medium", "movement": "hold"}, "source": "footage",
          "characters": ["TOM"],
          "coverage": {"role": "single", "priority": "should"},
          "staging": [{"subject": "TOM", "side": "right", "facing": "left"}],
          "setup": "C"
        },
        {
          "id": "K4", "purpose": "Maya says the sentence the scene turns on.", "subject": "MAYA",
          "start_ms": 9000, "end_ms": 11000, "camera": {"size": "medium_close_up", "movement": "push_in"}, "source": "footage",
          "characters": ["MAYA"],
          "coverage": {"role": "single", "priority": "must"},
          "staging": [{"subject": "MAYA", "side": "left", "facing": "right"}],
          "setup": "D"
        },
        {
          "id": "K5", "purpose": "Tom's face as it lands: he has no answer.", "subject": "TOM",
          "start_ms": 11000, "end_ms": 14000, "camera": {"size": "close_up", "movement": "hold"}, "source": "footage",
          "characters": ["TOM"],
          "coverage": {"role": "reaction", "priority": "must"},
          "staging": [{"subject": "TOM", "side": "right", "facing": "left"}],
          "setup": "C2"
        }
      ]
    }
  ]
}
```

Larger complete plans are in `tests/corpus/` (a kitchen scene with a broken glass, a rooftop chase, a waking scene)
and the fixture `tests/contract/VideoEditor.Dpl.Plan.Tests/Fixtures/the-beacon.dpl.plan.json`.

Examples below that are not complete plans are **fragments**: each is marked with the place it belongs, such as
`scenes[0].shots[0]`. They are checked against that part of the schema by a test, so a term or member that is wrong in
an example fails the build.

### The top level

**Plan** (the plan itself)

| Member | Type | Meaning |
|---|---|---|
| `format` | **required** `"dpl.plan/1"` | Always `"dpl.plan/1"`. Any other text is `DPL-FORMAT`. |
| `work` | **required** `Work` | The film's identity and intent. See "The work". |
| `characters` | list of `Character` | The people (or beings) the story needs. Shots, lines and staging name them by `id`. |
| `locations` | list of `Location` | The places the scenes happen in. A scene names one. |
| `props` | list of `Prop` | Objects the story depends on (the lamp that must be relit). Naming them lets the checks follow them from shot to shot. |
| `continuity_rules` | list of `ContinuityRule` | Rules the picture must keep across the film (a rule of light, a costume). Shots cite them by `id`. |
| `must_not` | list of text | Things the whole film must never show or do, as plain sentences. A shot has its own `must_not` too. |
| `sequences` | list of `Sequence` | Optional grouping of scenes into larger movements of the story, each with its own direction. |
| `journey` | `Journey` | Where the whole film travels, for the scenes no sequence's own `journey` governs. |
| `none` | list of `Absence` | What the whole film deliberately does not have, said outright: today `journey` (the camera travels nowhere). See "Saying no". |
| `scenes` | **required** list of `Scene` | The film, in time order. Each scene says what it is for and holds its shots. At least one scene is needed. |
| `transitions` | list of `Transition` | Joins between shots that were decided on purpose. A join that is not listed is an unremarked cut. |
| `sound` | `Sound` | Everything heard: carriers, spoken lines, and how they sit together over film time. |
| `delivery` | `Delivery` | Short-form profile: the picture shape and runtime limit the work is delivered in. See 2.10. |
| `kit` | `Kit` | Short-form profile: the cameras, supports, sound gear and lights the crew has. |
| `people` | list of `Person` | Short-form profile: the cast and crew, their roles and when they are there. |
| `schedule` | `Schedule` | Short-form profile: the shoot days. |
| `slice_of` | `SliceOf` | Present only on a slice that `dpl slice` cut from a larger plan. See "Slices". |

## 2. What each part is for

### 2.1 The work

`work` is the film's identity and intent. Give it a `thesis` (what the film argues or makes the audience feel), so each
scene can be asked what it does for that. Give `target_runtime_ms` and the validator warns when the scenes do not add up
to it. `design_notes`, `approach` and `quality_floor` are for the director's own reasoning and the bar the finished film
must clear; they are read by people and by review, not by the edit checks.

**Work** (`work`)

| Member | Type | Meaning |
|---|---|---|
| `id` | **required** text | A short id for the film. Findings and exported files carry it. |
| `title` | **required** text | The film's title. |
| `house_style` | `HouseStyle` | The film's look, rhythm and mix targets. See "House style". |
| `thesis` | text | What the film argues or makes the audience feel, in one or two sentences. Say it, so each scene can be asked whether it serves it. |
| `target_runtime_ms` | whole number | The planned length. If the scenes end at some other time, `DPL-RUNTIME` warns. Must be above 0. |
| `approach` | text | How the plan approaches the film: its method, not its content. |
| `design_notes` | list of `DesignNote` | Choices made on purpose, each against what an ordinary plan would do. |
| `quality_floor` | list of text | The minimum the finished film must achieve, whatever else changes, as plain sentences. |
| `motion_share` | number | The share of screen time (0 to 1) that should be in motion: recorded or generated video, not a camera move across a still. Less than this gives `DPL-MOTION`. |
| `motion_counts_animation` | true or false | Whether animation counts toward `motion_share` (a diagram while it evolves, a layer drifting). Absent means video only. Say `true` for a film that moves by drawing. |
| `profile` | one of `film`, `short` | What kind of plan this is: `film` (the default, and every plan written before the short-form profile) or `short`, which opts in to the short-form rules about kit, people, light and the shoot day (2.10). |

**DesignNote** (`work.design_notes[]`)

| Member | Type | Meaning |
|---|---|---|
| `id` | **required** text | A short unique id. |
| `problem` | **required** text | The problem the choice answers. |
| `choice` | **required** text | What the plan does about it, against what an ordinary plan would do. |
| `outcome` | text | What came of it, once known. |

### 2.2 House style

`work.house_style` holds the choices that belong to the whole film rather than to a scene: how its constructs look, how
tightly picture follows the words, what structure it opts into, and how it is mixed. Every part is optional. A part you
leave out switches its checks to *report only*.

**HouseStyle** (`work.house_style`)

| Member | Type | Meaning |
|---|---|---|
| `map` | text | The film's map look, in plain words, for people and for picture briefs. The colours a renderer applies are in `map_palette`. |
| `highlight` | text | The look of a highlight over a document, in plain words. |
| `chapter_card` | text | The look of a chapter card, in plain words. |
| `map_palette` | `MapConstructStyle` | Map colours and named places the pictures can use. |
| `highlight_palette` | `HighlightConstructStyle` | Colours of the highlight drawn over a page. |
| `chapter_palette` | `ChapterConstructStyle` | Colours, type and timing of chapter cards. |
| `drawing_sounds` | `DrawingSoundStyle` | The paper-and-pencil sounds that go with drawn constructs (highlight, circle, route and so on). |
| `presenter` | `PresenterStyle` | The on-screen presenter's identity and the longest the audience goes without seeing them. |
| `timing` | `TimingStyle` | Picture rhythm targets: how closely cuts follow the words, and how long shots run. |
| `structure` | `StructureStyle` | Opt-ins for a cold open, a sponsor spot, a subscribe prompt and an end card. |
| `sound` | `SoundTargetStyle` | Mix targets: how far the bed sits under the voice, loudness, how many cut effects. |
| `readiness` | `ReadinessStyle` | Where this film's craft differs from the usual conventions the readiness checks start from (how long an insert, reaction, cutaway or neutral shot may be held). |

**Constructs and colours.** `map`, `highlight` and `chapter_card` describe the film's map, highlight and chapter card in
plain words. The structured palettes are optional beside them. Colours are CSS hex strings. These fields travel with the
realisation document. A bound picture may override the drawing sounds with `drawing_sound: false`, or a `drawing_sound`
object with `enabled`, per-kind switches and `gain_db`. Bindings provide the page image and normalised highlight
rectangles, map vector polygons and place pin assets, split-screen pane assets and quoted text. `bindings.word_timing`
carries `eil.words/1` when a construct follows spoken words. A new engine release is required before production films can
render these constructs.

**MapConstructStyle** (`work.house_style.map_palette`)

| Member | Type | Meaning |
|---|---|---|
| `sea` | text; default `"#102938"` | Sea colour (CSS hex). |
| `land` | text; default `"#d3c5a7"` | Land colour (CSS hex). |
| `border` | text; default `"#61747a"` | Border line colour (CSS hex). |
| `label` | text; default `"#ffffff"` | Place-name text colour (CSS hex). |
| `accent` | text; default `"#e4b75e"` | The accent colour (CSS hex). |
| `highlight` | text; default `"#e4b75e"` | The colour a highlighted region takes (CSS hex). |
| `places` | list of `MapNamedPlace` | Named places with a position, reusable by every map picture in the film. |

**MapNamedPlace** (`work.house_style.map_palette.places[]`)

| Member | Type | Meaning |
|---|---|---|
| `id` | **required** text | A short unique id for the place. |
| `label` | **required** text | The name shown beside the pin. |
| `lon` | number; default `0` | Longitude in degrees. |
| `lat` | number; default `0` | Latitude in degrees. |
| `emblem` | text | A small mark for the pin, by name. Pictures are bound to the name later. |

**HighlightConstructStyle** (`work.house_style.highlight_palette`)

| Member | Type | Meaning |
|---|---|---|
| `colour` | text; default `"#f4ce55"` | The colour of the highlight (CSS hex). |
| `surface` | text; default `"#253039"` | The colour of the surface the highlight sits on (CSS hex). |

**ChapterConstructStyle** (`work.house_style.chapter_palette`)

| Member | Type | Meaning |
|---|---|---|
| `ground` | text; default `"#101820"` | Background colour of the card (CSS hex). |
| `accent` | text; default `"#e4b75e"` | Accent colour (CSS hex). |
| `ink` | text; default `"#ffffff"` | Text colour (CSS hex). |
| `font_family` | text | The type family for the card. Absent means the renderer's own. |
| `entrance_ms` | whole number; default `700` | How long the card takes to arrive. |
| `exit_ms` | whole number; default `500` | How long the card takes to leave. |

**DrawingSoundStyle** (`work.house_style.drawing_sounds`)

| Member | Type | Meaning |
|---|---|---|
| `enabled` | true or false; default `true` | Whether drawn constructs make a sound at all. |
| `gain_db` | number; default `-18` | How loud, in dB relative to the voice. A negative number sits under it. |
| `highlight` | true or false; default `true` | A sound as a highlight is drawn. |
| `circle` | true or false; default `true` | A sound as a circle is drawn. |
| `route` | true or false; default `true` | A sound as a route is drawn on a map. |
| `pins` | true or false; default `false` | A sound as map pins land. |
| `chapter` | true or false; default `false` | A sound as a chapter card arrives. |
| `subscribe` | true or false; default `false` | A soft sound as the subscribe prompt arrives. |

**The presenter** (playbook Part 1 §1.1–1.2). `presenter` sets who the presenter is and the longest the audience goes
without seeing them. The checks count the gap from the film's start and to its end, and also report a presenter who never
appears.

**PresenterStyle** (`work.house_style.presenter`)

| Member | Type | Meaning |
|---|---|---|
| `id` | **required** text | The presenter's id, as pictures of them are bound. |
| `max_gap_ms` | whole number | The longest the presenter may be absent from the picture. If absent, 120000 is used by the checks. |

**ReadinessStyle** (`work.house_style.readiness`)

| Member | Type | Meaning |
|---|---|---|
| `hold_insert_ms` | whole number | The longest an insert (a detail glance) may run before `RDY-HOLD` flags it, apart from speech inside it. |
| `hold_reaction_ms` | whole number | The longest a reaction may run, apart from speech inside it. |
| `hold_cutaway_ms` | whole number | The longest a cutaway may run, apart from speech inside it. |
| `hold_neutral_ms` | whole number | The longest a neutral (bridging) shot may run, apart from speech inside it. |
| `reaction_window_ms` | whole number | How long after a turning line ends the listener may first appear and still count, for `RDY-REACTION`, as answering it. Default 1000. |

Every member is optional. A member left out keeps the check's own figure. Without `hold_*_ms`, a hold limit is a multiple of
the film's own pace (its median shot length, or `timing.body_median_ms` when declared), scaled by the shot's size:
insert and cutaway 2 times the pace, reaction 2.5, neutral 1.5, times 1.25 for an extreme long shot falling to 0.75 for an
extreme close-up. The reaction window is 1000 ms. A film cut slowly on purpose raises them; a very fast one lowers them.
Whatever the limit, speech inside the shot is added to it: a reaction played under the other person's line, or an insert
over the narration that reads the clause, lasts as long as the words need.

**Picture timing** (playbook Part 1 §1.3). `timing` sets how closely cuts follow the spoken words, and how long shots
run. Omitting the whole object makes render verification report only. An explicit `null` for a median switches off that
section's length gate. Slower musical films may choose their own targets.

**TimingStyle** (`work.house_style.timing`)

| Member | Type | Meaning |
|---|---|---|
| `lead_ms` | whole number | The planner's target for how far a picture change leads the word it belongs to. Default 50 when `timing` is present. |
| `word_window_ms` | whole number | The window, around a word's start, inside which a picture change counts as on that word. Default 250. |
| `paragraph_window_ms` | whole number | The same window for the start of a paragraph. Default 250. |
| `max_shift_ms` | whole number | The most a picture boundary may be moved to meet a word. Default 500. |
| `min_onset_share` | number | The least share of rendered word onsets that must be met. Default 0.84. |
| `min_paragraph_share` | number | The least share of paragraph starts that must be marked by a picture change. Default 0.57. |
| `hook_median_ms` | whole number | Target median shot length in the opening hook. Default 1400. `null` switches this gate off. |
| `body_median_ms` | whole number | Target median shot length in the body. Default 2300. `null` switches this gate off. |
| `presenter_median_ms` | whole number | Target median length of presenter shots. Absent means no target. |
| `shot_median_tolerance_ms` | whole number | How far a median may be from its target. Default 500. |

**Structure on screen** (playbook §1.4). `structure` opts into individual checks, all off unless set. A subscribe prompt is a
`caption` overlay with `style: "subscribe"` over B-roll. An end card is a `caption` overlay with `style: "end_card"`, a
recommendation in `text`, and a built-in subscribe prompt; realisation binds its recommendation picture as the
overlay's `content: {kind: "image", asset: "..."}` and the renderer shows it as a thumbnail. The end card may run across
the shots of its scene. `drawing_sounds.subscribe: true` adds a soft local paper-grain entry sound to the small prompt.
`dplkit.subscribe_prompt`, `dplkit.end_card` and `dplkit.structure_style` author these; `dplkit.add_structure` places
both on timed shots without changing `sound`. The sponsor-loop check observes the fixed words and never changes them.

**StructureStyle** (`work.house_style.structure`)

| Member | Type | Meaning |
|---|---|---|
| `cold_open` | true or false; default `false` | Opt in to the cold-open check: the presenter holds back at the start. |
| `sponsor` | true or false; default `false` | Opt in to the sponsor checks. |
| `subscribe` | true or false; default `false` | Opt in to the subscribe-prompt check. |
| `end_card` | true or false; default `false` | Opt in to the end-card check. |
| `presenter_after_ms` | whole number; default `15000` | When the presenter may first appear. |
| `cold_open_max_ms` | whole number; default `60000` | How long a cold open may run (or until the first `new_act`). |
| `subscribe_from_ms` | whole number; default `40000` | Start of the window for the subscribe prompt. |
| `subscribe_to_ms` | whole number; default `70000` | End of that window. |
| `end_card_ms` | whole number; default `18000` | How long before the end the end card must be on screen. |

**Sound targets** (playbook §1.5). `sound` sets how far the bed sits under the voice and how loud the finished mix is. The
pipeline carries a plan's `sound` object into the house-style view used by mixing and verification; the separate
style file remains unchanged on disk. With it, the pipeline attenuates the bed during speech (smoothly: down in 40 ms,
back over 450 ms) and normalises the finished mix. Verification allows 3 dB bed-depth tolerance, 1 LUFS loudness
tolerance, 0.2 dBTP true-peak tolerance, and at most 15% of cuts with transients for `few` (zero for `none`). Bed depth
verification reports the median voiced-window depth and its p10 separately, and judges the median. With no `sound` object,
these checks report only.

**SoundTargetStyle** (`work.house_style.sound`)

| Member | Type | Meaning |
|---|---|---|
| `bed_under_voice_db` | number; default `21` | How far the bed (music and ambience) sits under the voice while it speaks, in dB. |
| `drawing_sfx_under_voice_db` | number; default `18` | How far the drawing sounds sit under the voice, in dB. |
| `bed_presence_cut_db` | number; default `0` | While the voice speaks, cut the bed's middle band (250 Hz to 5 kHz) by this many dB, so music keeps body and air without masking the words. 0 is off. |
| `bed_hold_ms` | whole number; default `0` | The duck stays down across any pause shorter than this, so the bed does not surge at every breath. |
| `bed_lifts` | list of `BedLift` | Authored moments where the bed comes up. |
| `loudness` | `LoudnessTarget` | Target loudness of the finished mix. |
| `cut_sfx` | text; default `"few"` | How many cut effects: `"few"` (the default) or `"none"`. Any other text is `DPL-SOUND`. |

**BedLift** (`work.house_style.sound.bed_lifts[]`)

| Member | Type | Meaning |
|---|---|---|
| `after` | text; default `""` | The id of a spoken line (`sound.speech`). The bed comes up from the end of that line. |
| `ms` | whole number; default `0` | For how long, undimmed and uncut. Must be above 0. |

**LoudnessTarget** (`work.house_style.sound.loudness`)

| Member | Type | Meaning |
|---|---|---|
| `integrated_lufs` | number; default `-14` | Target integrated loudness in LUFS. |
| `true_peak_dbtp` | number; default `-1` | Highest true peak in dBTP. Must be 0 or less. |

### 2.3 The world: characters, locations, props, rules

The world is what the story is made of. Declare each thing once and name it by `id` everywhere else, so the checks can
follow a person, a place or an object from shot to shot. A character, a prop and a location share one namespace, because
staging, attention and the camera's path can name any of them.

- **Characters** need a `description` (who they are in the story). Add `appearance` (what a picture of them must show) when
  the character will be generated: the validator warns (`DPL-APPEARANCE`) when a character is in two or more generated shots and the
  appearance does not fix their hair (or head covering) and clothing. `costume` and `performance` are for whoever dresses and directs them.
- **Locations** can name their `parts` (the door, the sink). A scene's `entry` and a camera `path` then say which part the
  audience arrives in or is carried into.
- **Props** are the objects the story depends on (the lamp that must be relit). A shot lists the props it shows; a scene's
  `state_changes` and a shot's `states` track how they change.
- **Continuity rules** are rules the picture must keep (a rule of light, a costume). A shot cites a rule by `id` in its
  `continuity`. A rule's `measures` say what it means in the rendered picture, so the render can be measured against it.
  A rule of light described to an image model with dark words is a trap: the validator warns (`DPL-RULE`) when a shot held
  to a strict rule of light is described as dark.
- **`must_not`** at the top level is what the whole film must never show or do. Each shot has its own.

**Character** (`characters[]`)

| Member | Type | Meaning |
|---|---|---|
| `id` | **required** text | A short unique id. It shares one namespace with props and locations. |
| `description` | **required** text | Who they are in the story. |
| `appearance` | text | What they look like in a picture, apart from what they mean. Needed when a generated character is in two or more shots (`DPL-APPEARANCE` warns if it does not fix the look). |
| `costume` | text | What they wear. |
| `performance` | text | How they act and move. |
| `rig` | `CharacterRig` | Makes the character a cut-out puppet that staging actions can move. See "Rigs". |

**Location** (`locations[]`)

| Member | Type | Meaning |
|---|---|---|
| `id` | **required** text | A short unique id. It shares one namespace with characters and props. |
| `description` | **required** text | What the place is like. |
| `parts` | list of text | Named parts of the place (the door, the sink). A scene's `entry` and a camera `path` can name them. |
| `time_of_day` | text | When it is. |
| `light` | text | How it is lit. |
| `setting` | one of `indoor`, `outdoor` | Short-form profile: indoors or outdoors. Outdoors, the shots are held to the usable light. |
| `usable_light` | list of `TimeWindow` | Short-form profile: when there is light enough to film, as clock times. |
| `sun` | text | Short-form profile: where the sun is, if known ("low from the west after 16:00"). |
| `access` | list of `TimeWindow` | Short-form profile: when the crew may be at the place (it opens, closes, is booked). Without any, always. |
| `acoustics` | one of `quiet`, `controllable`, `fixed` | Short-form profile: how the place sounds while people speak; `fixed` noise cannot be switched off. |
| `quieter_position` | true or false | Short-form profile: whether a speaker may move somewhere quieter. |

**Prop** (`props[]`)

| Member | Type | Meaning |
|---|---|---|
| `id` | **required** text | A short unique id. It shares one namespace with characters and locations. |
| `description` | **required** text | What the object is and why the story needs it. |
| `appearance` | text | What it looks like in a picture, apart from what it means. |

**ContinuityRule** (`continuity_rules[]`)

| Member | Type | Meaning |
|---|---|---|
| `id` | **required** text | A short unique id. Shots cite it in their `continuity`. |
| `domain` | text | What kind of rule it is (light, costume, geography). |
| `rule` | **required** text | The rule, as a sentence. |
| `measures` | list of `RuleMeasure` | What the rule means in the rendered picture, measured on every shot that cites it. |

**RuleMeasure** (`continuity_rules[].measures[]`)

| Member | Type | Meaning |
|---|---|---|
| `kind` | **required** one of `min_brightness`, `max_shadow_share` | What is measured: `min_brightness` means mean brightness at least `value`; `max_shadow_share` means the share of the frame in deep shadow at most `value`. |
| `value` | **required** number | The fraction (0 to 1) to hold to. |

<!-- dpl:fragment continuity_rules[0] -->
```json
{"id": "light-of-the-father", "domain": "light", "rule": "The Father of lights: no heavy shadow.",
 "measures": [{"kind": "min_brightness", "value": 0.4}, {"kind": "max_shadow_share", "value": 0.2}]}
```

#### Rigs

A character can be a cut-out puppet, so a shot can move it. `rig: {"body": "figure"}` is the standard jointed figure of
ten parts: `torso`, `head`, `upper_arm_l`, `upper_arm_r`, `forearm_l`, `forearm_r`, `thigh_l`, `thigh_r`, `shin_l` and
`shin_r`. `{"body": "own", "parts": [...]}` is a rig of parts cut from your own art, with exactly one root; a part that is
named as one of the figure's roles is the one the actions move, and `roles` maps a role to a part where the names differ
(the roles the validator accepts are the ten above and `foot_l` and `foot_r`). A shot then gives the character's staging
a list of `actions`, section 2.6. The realiser turns the actions into the rig layer's keys (its port of
`tools/eil-prep/puppet.py`, held to it by a parity fixture) and binds each part's picture from the character's binding:
`roles["role:<id>"].rig = {part: picture}`. A part left unbound is reported (`unbound_rig`), never drawn half-bound. The
figure stands on its staging side of the frame.

**CharacterRig** (`characters[].rig`)

| Member | Type | Meaning |
|---|---|---|
| `body` | **required** one of `figure`, `own` | `figure` is the standard jointed figure of ten parts. `own` means the rig's `parts` are its own. |
| `parts` | list of `RigPart` | For `own`: the parts, with exactly one root. A `figure` rig must not give parts. |
| `roles` | map from role to part id | Maps an action role (such as `head` or `thigh_l`) to one of this rig's part ids, so the actions move the right parts. |
| `ground` | list of [x, y] points | The ground line the figure stands on: at least two [x, y] points with x ascending, each from 0 to 1. |
| `mirror` | true or false; default `false` | Reflect the painted figure about its root when its travel faces against the art. |

**RigPart** (`characters[].rig.parts[]`)

| Member | Type | Meaning |
|---|---|---|
| `id` | **required** text | The part's id. |
| `parent` | text | The part it hangs from. Leave out for the single root. |
| `size` | **required** [width, height] | The part's picture as fractions of the rig's box: [width, height]. |
| `pivot` | **required** [across, down] | Where it turns, as fractions of its own picture: [across, down]. |
| `at` | **required** [across, down] | Where its pivot sits: on its parent's picture (fractions), or for the root on the rig's box. |
| `sole` | [across, down] | For a painted shin: its opaque sole point in its own image, two fractions. |
| `min` | number | The least it may turn from neutral, in degrees. Must not exceed `max`. |
| `max` | number | The most it may turn from neutral, in degrees. |
| `z` | whole number; default `0` | Drawing order: higher is in front. |
| `secondary` | `RigSecondary` | Makes the part trail its parent (a cloak, a braid). |
| `attach` | `RigAttachment` | Fixes the part to another part (a staff in a hand). |

**RigSecondary** (`characters[].rig.parts[].secondary`)

| Member | Type | Meaning |
|---|---|---|
| `lag_ms` | whole number; default `120` | How far behind its parent the part follows. Must be above 0. |
| `damping` | number; default `0.35` | How quickly the trailing settles, from 0 to 1. |

**RigAttachment** (`characters[].rig.parts[].attach`)

| Member | Type | Meaning |
|---|---|---|
| `to` | **required** text | The part it is attached to. |
| `at` | **required** [across, down] | The point on that part, two numbers. |
| `upright` | true or false; default `false` | For a walking staff: keep its painted shaft at its rest angle in the world. |

<!-- dpl:fragment characters[0].rig -->
```json
{"body": "own",
 "parts": [
   {"id": "body", "size": [0.3, 0.6], "pivot": [0.5, 0.5], "at": [0.5, 0.6]},
   {"id": "head", "parent": "body", "size": [0.2, 0.2], "pivot": [0.5, 0.9], "at": [0.5, 0.05], "z": 1}],
 "roles": {"torso": "body", "head": "head"},
 "ground": [[0, 0.9], [1, 0.9]]}
```

### 2.4 Structure: sequences, journey, scenes

The structure says how the film is built. A **scene** is the unit: one place, one job, a run of shots. A **sequence** groups
scenes into a larger movement and can say which way it is heading. A **journey** says where the camera travels across a
sequence or the whole film.

**Purpose everywhere.** Every scene and every shot must say what it is for (`purpose`), not only what it shows
(`subject`). A scene may also say what is different for the audience at its end (`change`). A plan with no purposes is a
shot list; a plan with purposes can be edited, replanned and judged.

**Scenes.** Scenes run in time order, do not overlap, and between them cover the film; a gap is a warning (`DPL-GAP`).
Within a scene, the shots do the same. Most of a scene's members are explained below or in the sections that follow: its `lines`,
`axis`, `travel` and `state_changes` in 2.6 (they are what the readiness checks reason over), and its
`sound`, `hearing` and `phrases` in 2.8.

**Scene** (`scenes[]`)

| Member | Type | Meaning |
|---|---|---|
| `id` | **required** text | A short unique id. |
| `purpose` | **required** text | What the scene is for. |
| `change` | text | What is different for the audience at the end of the scene. |
| `functions` | list of `SceneFunction` | The scene's dramatic job in typed form, and which shot or sound carries it. One of each function per scene. |
| `start_ms` | **required** whole number | When the scene starts, in ms from the start of the work. |
| `end_ms` | **required** whole number | When it ends (exclusive). Scenes run in order and must not overlap. |
| `location` | text | The id of one of the plan's `locations`. |
| `entry` | `SceneEntry` | How the audience arrives in the scene's world. |
| `shots` | **required** list of `Shot` | The scene's shots, in time order. At least one. |
| `attention` | list of `AttentionBeat` | Who or what holds the audience, and when. |
| `sound` | list of `SceneSound` | What each kind of sound is doing in this scene. |
| `hearing` | `Hearing` | Whose hearing the scene takes and how the world sounds to it. |
| `phrases` | list of `Phrase` | How each heard sound's phrase runs through this scene: enters, carries, changes, returns or ends. |
| `treatments` | list of `Treatment` | The directions considered for the scene, with the one taken and why. A plan says what it could have been, not only what it is. |
| `strands` | list of `StrandPart` | What words, sound and picture each do in this scene. One leads; the others answer, support or rest. |
| `removals` | list of `Removal` | Things taken away at a moment (a picture layer, a part of the music), so picture and sound can lose something together on the same word. |
| `axis` | `Axis` | The line of action: who is on the left of frame and who on the right, from the camera's side of the line. The checks need it to catch crossed-line cuts. |
| `travel` | one of `left`, `right`, `toward`, `away`, `none` | Which way travel runs across the screen in this scene (a chase, a journey), kept from shot to shot. Leave it out when nothing travels: `none` is refused here (`DPL-RANGE`), though it is allowed in a shot's `moving`. |
| `lines` | list of `DialogueLine` | The spoken lines of the scene: who says each, to whom, when. |
| `state_changes` | list of `StateChange` | Things that change state during the scene (a glass breaks, a door opens), and whether the audience must see it. |
| `accepts` | list of `Acceptance` | Departures from editing convention that the plan chooses on purpose, each with its reason. |
| `none` | list of `Absence` | What the scene deliberately does not have, said outright: `spoken_lines` (nobody speaks on screen) and `line_of_action` (staged without a line). See "Saying no". |
| `journey_stage` | text | The id of the journey stage this scene is at. Needs a journey (the plan's or its sequence's) that governs the scene. |
| `provenance` | `Provenance` | Who or what set this scene. Inert: no rule reads it (2.10). |

**Functions.** `functions` is the scene's dramatic job in typed form, led by one shot or sound carrier of the scene and
supported by others: `propulsion`, `evidence`, `contrast`, `emphasis`, `reset`, `retention`, `humour`, `dread`,
`anticipation`, `release` or `rest`. These words are owned by DPL.

**SceneFunction** (`scenes[].functions[]`)

| Member | Type | Meaning |
|---|---|---|
| `type` | **required** one of `propulsion`, `evidence`, `contrast`, `emphasis`, `reset`, `retention`, `humour`, `dread`, `anticipation`, `release`, `rest` | The dramatic job. |
| `lead` | **required** text | The shot of this scene, or the sound carrier, chiefly responsible. |
| `support` | list of text | Other shots or carriers that help. |

**Entry.** `entry` says how the audience arrives in the scene's world, and into which part of the location. Use `return`
or `continue` for a scene that is back in a place the audience knows; the geography check then does not ask for the people
to be placed again.

**SceneEntry** (`scenes[].entry`)

| Member | Type | Meaning |
|---|---|---|
| `how` | **required** text | How the audience arrives, in a word: for example `open`, `transport` (taken somewhere new), `return` (back to a known place) or `continue`. The geography check is skipped for `return` and `continue`. |
| `from_part` | text | The part of a location the audience leaves, if any. |
| `to_part` | text | The part of the scene's location the audience arrives in. Must be one of that location's `parts`. |
| `established_by` | text | The id of the shot in this scene that establishes the arrival. |
| `note` | text | Anything else worth saying about the arrival. |

**Attention.** Who or what should hold the audience at each moment: a primary and what comes after it. A beat can name its
`target` and the `effect` it has on attention (concentrate it, hide something, disorient). Say so when the plan means to
disorient or withhold, because the readiness checks then treat the departure as planned.

**AttentionBeat** (`scenes[].attention[]`)

| Member | Type | Meaning |
|---|---|---|
| `start_ms` | whole number | When the beat starts. Give both times or neither. |
| `end_ms` | whole number | When it ends. Without times the beat covers the whole scene. |
| `primary` | **required** text | Who or what should hold the audience, in words. |
| `secondary` | list of text | What comes after the primary, in words. |
| `target` | text | The character, prop, location or sound carrier that holds attention, when it is one. Give `target` and `effect` together, or neither. |
| `effect` | one of `concentrate`, `suppress_competition`, `intensify_recognition`, `reveal`, `withhold`, `disorient`, `waking`, `trance`, `sleep`, `distort_hearing`, `restore_clarity`, `alter_scale` | What the beat does to the audience's attention. A scene whose attention `disorient`s or `withhold`s is not faulted for showing singles before the people are placed. |
| `over` | text | What the target leads while it holds attention (a character, prop, location or sound carrier). |
| `resolves_to` | text | What attention settles on afterwards, in words. |

<!-- dpl:fragment scenes[0].attention -->
```json
[{"primary": "Maya's face", "secondary": ["the letter in her hand"], "start_ms": 0, "end_ms": 6000},
 {"primary": "the lamp", "target": "lamp", "effect": "reveal", "over": "TOM", "resolves_to": "Tom's hands", "start_ms": 6000, "end_ms": 14000}]
```

**Treatments.** A scene can show the directions that were considered and the one taken. Exactly one is `chosen`; a single
treatment is a warning (`DPL-TREATMENT`), because one direction means nothing was weighed.

**Treatment** (`scenes[].treatments[]`)

| Member | Type | Meaning |
|---|---|---|
| `id` | **required** text | A short unique id within the scene. |
| `angle` | **required** text | The angle, in a few words ("stay on his face and close in"). |
| `idea` | **required** text | What the audience would see and hear, and why it could work. |
| `source` | text | Where the idea comes from: prior art, a reference, an owner note. |
| `chosen` | true or false; default `false` | Whether this is the direction taken. Exactly one treatment is chosen. |
| `why` | **required** text | Why it was taken, or why not. |

<!-- dpl:fragment scenes[0].treatments -->
```json
[{"id": "t1", "angle": "stay on his face and close in", "idea": "One slow push on Tom while Maya speaks off screen.", "chosen": true, "why": "The scene is about what he cannot say."},
 {"id": "t2", "angle": "keep them both in the wide", "idea": "Hold the two of them apart across the kitchen.", "source": "owner note, 12 Oct", "why": "It spends the closeness we need for the last shot."}]
```

**Strands.** The three strands a film tells its story on are `words`, `sound` and `picture`. In a scene, give each strand
exactly one part: one `lead`s, the others `answer`, `support` or `rest`. A `unison` is all three striking together on one
moment, and then all three are unison. A sequence's strands run across the sequence, with no one required to lead, and a
sequence that gives strands also needs its `direction`. If one strand leads every scene of a sequence of four or more, the
validator warns (`DPL-STRANDS`).

**StrandPart** (`scenes[].strands[]`, `sequences[].strands[]`)

| Member | Type | Meaning |
|---|---|---|
| `strand` | **required** one of `words`, `sound`, `picture` | Which strand: `words`, `sound` or `picture`. Each appears exactly once. |
| `role` | one of `lead`, `answer`, `support`, `rest`, `unison`; default `"support"` | What it does: `lead`, `answer` (call and response), `support`, `rest`, or `unison` (all three strike together; rare, and then all three are unison). |
| `does` | **required** text | What the strand does here, in its own terms. |
| `at_ms` | whole number | When the strand makes its move (a hit), for example the onset of the word a unison lands on. A unison gives one moment. |

**Removals.** Subtraction is a decision. A removal takes away a picture layer or one sound carrier (or one stem of it) at a
moment, so picture and sound can lose something together on the same word.

**Removal** (`scenes[].removals[]`)

| Member | Type | Meaning |
|---|---|---|
| `at_ms` | **required** whole number | The moment, inside the scene. |
| `layer` | text | A layer (`layers[].id`) of a shot in this scene to take away. Give `layer` or `carrier`, not both. |
| `carrier` | text | A sound carrier to take away.  |
| `stem` | text | The part of the carrier removed: `drums`, `bass`, `other` or `vocals`. The whole carrier when absent. Only with `carrier`. |
| `why` | text | Why it goes here. |

<!-- dpl:fragment scenes[0].removals -->
```json
[{"at_ms": 9000, "layer": "mist", "why": "the air clears on the word"}, {"at_ms": 9000, "carrier": "MUSIC", "stem": "drums", "why": "the drums drop out with it"}]
```

**Accepts.** A scene can accept a departure from editing convention, with its reason (a scene played in one take). The
finding is then reported as overridden, not as a fault.

**Acceptance** (`scenes[].accepts[]`)

| Member | Type | Meaning |
|---|---|---|
| `code` | **required** text | The finding code accepted, for example `RDY-COVERAGE`. |
| `shot` | text | The shot it applies to. Every shot in the scene when absent. |
| `why` | **required** text | The reason. An accepted finding is reported as overridden, not as a fault. |

**Sequences.** A sequence groups scenes by id. Use one when the film has movements that each head somewhere.

**Sequence** (`sequences[]`)

| Member | Type | Meaning |
|---|---|---|
| `id` | **required** text | A short unique id. |
| `purpose` | **required** text | What the sequence is for. |
| `scenes` | **required** list of text | The ids of its scenes, in order. |
| `direction` | text | Where the sequence is heading: the one direction words, sound and picture all move in (for example "from vast to one"). Needed when `strands` are given. |
| `strands` | list of `StrandPart` | What words, sound and picture each do across the sequence. Exactly one part per strand. |
| `journey` | `Journey` | Where the sequence's camera travels, stage by stage. See "The journey". |
| `none` | list of `Absence` | What the sequence deliberately does not have: `journey` (no route of its own). See "Saying no". |

<!-- dpl:fragment sequences[0] -->
```json
{"id": "q1", "purpose": "From the whole sky to one man.", "scenes": ["S1", "S2"], "direction": "from vast to one",
 "strands": [
   {"strand": "words", "role": "lead", "does": "narrows from the heavens to a name"},
   {"strand": "sound", "role": "support", "does": "thins from a chord to one voice"},
   {"strand": "picture", "role": "answer", "does": "closes from the horizon to a face"}]}
```

#### The journey

A sequence, or the whole plan, can say where its camera travels (brief c, 5 Oct 2026; owner, 4 Oct: "Beats are silver; a
journey is gold"). The `axis` says what the route measures: `height` for a ladder, `distance` out from home, `inward`
toward a destination. The `stages` are the route in order. Each has an `id`, a `value` on the axis, and the place's `name`
when a place is visited again (a place visited again is a new stage with the same name and a `return` reason, as M7's
"down again to earth"). The `destination` is the stage where the route ends, with `why`. Each scene then names its
`journey_stage`; its location, parts and entry stay as they are.

The plan validates the route's shape: at least two stages, a destination that is one of them, and a stage for every scene
that names one (`DPL-JOURNEY`). Edit-readiness holds a plan that declares a journey to it (section 3). A plan with no
journey gets no journey finding. With direction, the journey is carried into the editing stage (`editorial.journey_stage@1`). Fixtures:
`tests/corpus/journey/`.

**Journey** (`journey`, `sequences[].journey`)

| Member | Type | Meaning |
|---|---|---|
| `axis` | **required** one of `height`, `distance`, `inward` | What the stages measure: `height` is a ladder, `distance` is out from home, `inward` is toward a destination. |
| `stages` | **required** list of `JourneyStage` | The route, in order. At least two. |
| `destination` | **required** `JourneyDestination` | Where the journey ends, and why. |

**JourneyStage** (`journey.stages[]`)

| Member | Type | Meaning |
|---|---|---|
| `id` | **required** text | A short id. Scenes name it in `journey_stage`. |
| `name` | text | The place as it is called on screen. A place visited again is a new stage with the same `name`. |
| `value` | **required** number | Where the stage is on the axis: a height, a distance out, or how far in. |
| `return` | text | Why the route comes back to a place it has been ("down again to earth"). |

**JourneyDestination** (`journey.destination`)

| Member | Type | Meaning |
|---|---|---|
| `stage` | **required** text | The id of the stage where the journey ends. |
| `why` | **required** text | Why it ends there, in the owner's words. |

<!-- dpl:fragment journey -->
```json
{"axis": "height",
 "stages": [{"id": "earth", "value": 0}, {"id": "heaven", "value": 3}, {"id": "earth2", "name": "earth", "value": 0, "return": "down again to earth"}],
 "destination": {"stage": "heaven", "why": "the whole climb is to see him"}}
```

### 2.5 Shots

A shot is a continuous run of picture. Say what it is **for** (`purpose`) and what it shows (`subject`); give its camera; name
the characters and props in it. Everything else on a shot is optional and earns its place when a check or a maker needs it:
`coverage`, `staging`, `setup`, `states` for the readiness checks (2.6); `overlays`, `layers` and `window` to build the
picture (2.7); `image_brief` and `motion_brief` for whoever makes the picture, whether a crew or a generation model.

**Shot** (`scenes[].shots[]`)

| Member | Type | Meaning |
|---|---|---|
| `id` | **required** text | A short id, unique across the whole plan. Transitions and sound name shots by it. |
| `picture` | `ShotPicture` | What kind of picture the shot is, for picture suggestions and the beat checks. |
| `purpose` | **required** text | What the audience should read from the shot. |
| `subject` | **required** text | What the shot shows. |
| `start_ms` | **required** whole number | When it starts. Shots run in time order and stay inside their scene. |
| `end_ms` | **required** whole number | When it ends (exclusive). |
| `camera` | **required** `Camera` | Size and movement, and the rest of the camera. |
| `source` | one of `footage`, `still`, `graphic`, `typography`, `generated`, `generated_video`, `recall` | What the picture is made from. A camera move on a still is realised as a move across it. |
| `characters` | list of text | Ids of the characters the shot shows. The first one is taken to hold the frame. |
| `props` | list of text | Ids of the props it shows. |
| `path` | list of `PathPhase` | A travelling camera's phases. |
| `introduces` | list of `ShotIntroduction` | Subjects that first come into view in this shot, and how. |
| `visual_state` | text | What must be true in frame (the state of the world, light, props). |
| `continuity` | list of text | Ids of the `continuity_rules` this shot must honour. |
| `image_brief` | text | A brief for whoever makes the picture (crew or generation model). |
| `motion_brief` | text | A brief for how the picture moves. |
| `recall` | `Recall` | For a recalled shot: the earlier movement and shot whose approved media this reuses. |
| `motion_ramp` | `MotionRamp` | A change of the picture's speed inside the shot: the clip slows to stillness on a word. |
| `must_not` | list of text | What this shot must never show or do. |
| `overlays` | list of `Overlay` | Layers over the picture for part of its length: an inset, a nameplate, words that land, a diagram. |
| `layers` | list of `Layer` | The shot built in depth: background, middle and foreground, each with its own timing and motion. |
| `window` | list of `LayerKey` | The camera as a window travelling over the shot's painted picture, as keys of offset and scale. Every layer follows it at its own parallax. |
| `coverage` | `Coverage` | What the shot gives the edit and how much it matters. |
| `staging` | list of `Staging` | Where each subject is in frame, which way they look and move. |
| `setup` | text | A name for the camera set-up the shot is filmed from. Shots that share a name are shot together. |
| `crosses_line` | true or false | True when the camera crosses the line of action on screen, taking the audience with it. |
| `states` | list of `PropState` | The state of each prop that matters, as seen in this shot. |
| `filming` | `Filming` | Short-form profile: how the shot is filmed (its timing role, takes, whether it is a one-take moment). |
| `provenance` | `Provenance` | Who or what set this shot. Inert: no rule reads it. (Not called `source`, which a shot already has.) |

**Source and recall.** `source` says what the picture is made from. `generated` is a still made for the shot;
`generated_video` moves (image to video); `footage` is recorded; `still`, `graphic` and `typography` are designed. A camera
move on a still is realised as a move across it. `recall` reuses the approved picture or clip of an earlier movement (the
same file, not a regeneration that looks different): set `source` to `recall` and name the movement and shot. Only a recall
carries a `recall` member (`DPL-RECALL`).

**Recall** (`scenes[].shots[].recall`)

| Member | Type | Meaning |
|---|---|---|
| `movement` | **required** text | The earlier movement's id (for example `m3`, `opening`). |
| `shot` | **required** text | The shot in that movement whose approved media is reused. |

**Motion ramp.** `motion_ramp` is an authored slowing of the picture to stillness inside a shot, on a word (the storm on "be
still"). It is not a freeze: the hold after it is the plan's intent. Only moving picture (`generated_video`, `footage` or
`recall`) can ramp.

**MotionRamp** (`scenes[].shots[].motion_ramp`)

| Member | Type | Meaning |
|---|---|---|
| `to` | **required** text | What the picture ramps to. Today only `"still"`. |
| `at_ms` | **required** whole number | The moment the motion has stopped, in film time. Inside the shot. |
| `why` | text | Why the picture comes to rest here. |

**The camera.** `size` and `movement` are required, in DPL's own terms (section 4). `angle`, `lens` and `perspective` are
optional. `perspective` says whose view the shot takes. A `subjective` shot also names `whose` eyes it is, and the person
whose eyes they are is never seen in it. `eyeline` says where eyes go: who looks at what, or that no one meets the lens.
`focus` says how sharp the shot is, which can carry meaning (`narrowed`, `disoriented`, `recovering`). `motion` describes
the character of a moving camera.

**Camera** (`scenes[].shots[].camera`)

| Member | Type | Meaning |
|---|---|---|
| `size` | **required** size term (section 4) | How much of the subject is in frame. |
| `movement` | **required** movement term (section 4) | What the camera does during the shot. `hold` is still. |
| `angle` | angle term (section 4) | Where the camera looks from. |
| `lens` | lens term (section 4) | The lens, which sets the feel of depth. |
| `perspective` | perspective term (section 4) | Whose view the shot takes. |
| `note` | text | Anything else about the camera. |
| `focus` | one of `clear`, `selective`, `narrowed`, `disoriented`, `recovering` | How sharp the shot is, which can carry meaning: a `narrowed` or `disoriented` focus is a character's state, `recovering` is coming back from it. |
| `whose` | text | For a subjective view: the character or prop whose eyes the camera is. They are not seen themselves. |
| `eyeline` | text | Where eyes go: who looks at what, or that no one meets the lens. Plain text. |
| `motion` | `MotionFeel` | The feel of a moving camera. |
| `lens_multiplier` | number | Short-form profile, for a phone: the lens as a multiplier of the main camera (`0.5`, `1`, `3`). |
| `focal_mm` | number | Short-form profile, for a camera: the lens's focal length in millimetres. |
| `support` | one of `handheld`, `gimbal`, `tripod`, `clamp`, `slider`, `shoulder`, `dolly`, `jib`, `crane`, `steadicam`, `drone` | Short-form profile: what carries the camera in this shot. |
| `device` | text | Short-form profile: the kit camera that films it (a camera `id` of the kit); any camera that can when absent. |
| `frame_rate` | number | Short-form profile: frames per second, when it is not the usual rate (slow motion). |
| `orientation` | one of `portrait`, `landscape`, `square` | Short-form profile: how the camera is held, which decides the frame's shape. |

**MotionFeel** (`scenes[].shots[].camera.motion`)

| Member | Type | Meaning |
|---|---|---|
| `path` | **required** one of `onward`, `meander`, `rise`, `settle`, `orbit`, `stationary` | The shape of the path it follows. |
| `variation` | **required** one of `organic`, `controlled`, `still` | Whether the motion is natural and uneven (`organic`), exact (`controlled`) or none (`still`). |
| `weight` | **required** one of `buoyant`, `ballistic`, `resisted`, `weighty`, `controlled` | How heavy it feels. |
| `force` | **required** one of `wind`, `gravity`, `contact`, `current`, `self_propelled`, `none` | What drives it. |
| `energy` | **required** one of `still`, `low`, `building`, `high`, `decaying` | How its energy runs over the shot. |
| `irregular` | true or false; default `false` | True when the motion is not steady. |

**A travelling camera's path.** A camera that travels gives its `path`: phases in time order, each with a movement, the size
it arrives at, how it moves relative to a character, prop or location, on which side, and the part of the location it
carries the viewer `into`. The relation (`relation`) is one of `from`, `around`, `past`, `over`, `through`, `toward`,
`into`, `away_from` and `with`; give it together with the `subject` it is relative to. The human shot list spells the path
out in industry terms, for example "0:03 ≈ Boom Shot (rise) from table → 0:04.5 Arc Shot around reader (over right
shoulder) → 0:07 Drone Shot through window into garden" (fixture `the-fly.dpl.plan.json`).

**PathPhase** (`scenes[].shots[].path[]`)

| Member | Type | Meaning |
|---|---|---|
| `at_ms` | **required** whole number | When the phase starts. Inside the shot, in time order. |
| `movement` | **required** movement term (section 4) | How the camera moves in this phase. |
| `size` | size term (section 4) | The framing this phase arrives at. The shot's own size when absent. |
| `relation` | one of `from`, `around`, `past`, `over`, `through`, `toward`, `into`, `away_from`, `with` | How the camera moves relative to the `subject`. Give `relation` and `subject` together, or neither. |
| `subject` | text | The character, prop or location the phase moves relative to. |
| `side` | one of `none`, `left`, `right`, `above`, `below`, `behind`, `front`, `over_left_shoulder`, `over_right_shoulder`; default `"none"` | Which side of the subject the camera is on. |
| `into` | text | The part of the scene's location the phase carries the viewer into. |
| `note` | text | Anything else about the phase. |

<!-- dpl:fragment scenes[0].shots[0].path -->
```json
[{"at_ms": 0, "movement": "rise", "size": "medium", "relation": "from", "subject": "table"},
 {"at_ms": 1500, "movement": "arc", "relation": "around", "subject": "MAYA", "side": "over_right_shoulder"},
 {"at_ms": 4000, "movement": "drone", "relation": "through", "subject": "window", "into": "garden"}]
```

**Introductions.** `introduces` says what first comes into view in a shot, and how: an `entrance`, a `discovery`, a
`light_reveal`, an `emergence`, a `return` or a `cause`. Say why it appears there. Converting tools carry it on as story-critical or (with `minor`) contextual.

**ShotIntroduction** (`scenes[].shots[].introduces[]`)

| Member | Type | Meaning |
|---|---|---|
| `subject` | **required** text | The character, prop or location coming into view. |
| `how` | **required** one of `entrance`, `discovery`, `light_reveal`, `emergence`, `return`, `cause` | How it arrives. |
| `minor` | true or false; default `false` | True when the arrival is only context, not story-critical (converting tools carry it on as contextual). |
| `why` | **required** text | Why it appears here. |

**Picture kinds.** A shot's `picture` names what kind of picture it is: `source_document`, `official_document`, `map`,
`portrait`, `reconstruction`, `footage`, `presenter`, `chapter_card`, `visual_pun`, `quote_text`, `split_screen`,
`sponsor_set` or `other`. `serves` names the beat ids (section 2.8, `sound.beat_functions`) the shot answers. `source_shown`
records evidence visible in another kind of shot, and `note` explains the choice. `suggest-pictures` prints defaults with
reasons and whether a shot explicitly serves each beat. For a `map`, or a map-backed `portrait`, `map_camera` gives `from`
and `to` geographic views `[west, south, east, north]` and a `start_ms`/`end_ms` interval inside the shot; the adapter
carries that timing to realisation, which applies it to the bound map.

**ShotPicture** (`scenes[].shots[].picture`)

| Member | Type | Meaning |
|---|---|---|
| `kind` | **required** one of `source_document`, `official_document`, `map`, `portrait`, `reconstruction`, `footage`, `presenter`, `chapter_card`, `visual_pun`, `quote_text`, `split_screen`, `sponsor_set`, `other` | What kind of picture the shot is. |
| `map_camera` | `MapCameraPlan` | For a `map` (or map-backed `portrait`): a zoom from one view to another. |
| `serves` | list of text | Ids of the `beat_functions` this shot answers. |
| `source_shown` | true or false | True when the evidence for a claim is visible in this shot, even though it is not a source document. |
| `note` | text | Why this picture was chosen. |

**MapCameraPlan** (`scenes[].shots[].picture.map_camera`)

| Member | Type | Meaning |
|---|---|---|
| `from` | **required** [west, south, east, north] | The view at the start: `[west, south, east, north]` in degrees. |
| `to` | **required** [west, south, east, north] | The view at the end, in the same form. |
| `start_ms` | **required** whole number | When the zoom starts. Inside the shot. |
| `end_ms` | **required** whole number | When it ends. |

<!-- dpl:fragment scenes[0].shots[0] -->
```json
{"id": "M1", "purpose": "Show how far the road runs.", "subject": "The road from Jerusalem to Jericho.",
 "start_ms": 0, "end_ms": 6000, "camera": {"size": "extreme_wide", "movement": "drift", "angle": "high", "lens": "wide",
   "perspective": "objective", "focus": "clear",
   "motion": {"path": "onward", "variation": "organic", "weight": "controlled", "force": "wind", "energy": "low"}},
 "source": "graphic", "props": ["lamp"],
 "picture": {"kind": "map", "serves": ["b1"], "note": "a map shows the distance faster than the words can",
   "map_camera": {"from": [30.0, 28.0, 38.0, 34.0], "to": [34.5, 31.2, 35.6, 32.0], "start_ms": 500, "end_ms": 5500}},
 "image_brief": "An aged map, warm paper, the road inked in red.", "motion_brief": "A slow push toward the city.",
 "must_not": ["modern roads"]}
```

### 2.6 Live-action facts: coverage, staging, lines, states

These are the facts the edit-readiness checks reason over (added 1 Oct 2026, `docs/capability/EDIT_READINESS.md`). All are
optional. They let a plan answer, before anything is shot or generated, whether the edit will have what it needs.
Section 3 says which detail switches which check on. Fixtures: `tests/corpus/readiness/`.

**Coverage** says what a shot gives the edit and how much it matters. `role` is one of `establishing`, `master`,
`two_shot`, `single`, `over_shoulder`, `reaction`, `insert`, `cutaway`, `pov` or `neutral`. `priority` is `must`, `should`
or `nice`; a `must` cannot be dropped without losing the scene's job, and `dpl replan` never drops one.

**Coverage** (`scenes[].shots[].coverage`)

| Member | Type | Meaning |
|---|---|---|
| `role` | **required** one of `establishing`, `master`, `two_shot`, `single`, `over_shoulder`, `reaction`, `insert`, `cutaway`, `pov`, `neutral` | What the shot gives the edit. |
| `priority` | one of `must`, `should`, `nice`; default `"should"` | How much the edit depends on it: a `must` cannot be dropped without losing the scene's job. |

**The line of action** (`axis`) is the line between two subjects. It says who is on the left of frame and who is on the
right, from the camera's side of the line, and every shot keeps them there unless the camera crosses on screen
(`crosses_line`). The checks need it to catch crossed-line cuts. **Travel** (`travel`) is the way travel runs across the
screen in a scene, for a chase or a journey: `left`, `right`, `toward` or `away`. It is kept from shot to shot.

**Axis** (`scenes[].axis`)

| Member | Type | Meaning |
|---|---|---|
| `left` | **required** text | The subject on the left of frame, from the camera's side of the line. |
| `right` | **required** text | The subject on the right. Must differ from `left`. |

**Staging** puts each subject in the frame: its `side` of frame (`left`, `centre`, `right`), where it looks (`facing`:
`left`, `right`, `camera`, `away`) and which way it moves (`moving`: `left`, `right`, `toward`, `away`, `none`). A shot stages
each subject once. The `setup` of a shot is a name for the camera set-up it is filmed from (`A`, `B`); shots that share a
name are shot together, which is how the checks see a jump cut and how a replan finds a whole set-up to drop.

**Staging** (`scenes[].shots[].staging[]`)

| Member | Type | Meaning |
|---|---|---|
| `subject` | **required** text | The character, prop or location. A shot stages each subject once. |
| `side` | **required** one of `left`, `centre`, `right` | Where in the frame: `left`, `centre` or `right`. |
| `facing` | one of `left`, `right`, `camera`, `away`; default `"camera"` | Where the subject looks, on screen: `left`, `right`, `camera` or `away`. |
| `moving` | one of `left`, `right`, `toward`, `away`, `none` | Which way the subject moves on screen: `left`, `right`, `toward`, `away` or `none`. |
| `actions` | list of `StagingAction` | For a rigged character: what they do in this shot, one after another. The rig stands in the frame on the subject's `side`. |

**Lines** are the spoken lines of a scene as the edit sees them: who says each (`speaker`), to whom (`to`), when, and
`turn` when how the listener takes it is part of the moment, so the audience must see them. A turning line needs `to`. A
speaker is a character, and a line is not said to its own speaker. `to` is a character, or one of three typed targets for a
line that no single character takes: `camera`, `audience` or `group` (see "Lines to the camera, the audience or a room"
below). Lines here are separate from the `sound.speech` lines that carry the recording (2.8). A scene where nobody speaks
on screen says so with `none` (see "Saying no").

**DialogueLine** (`scenes[].lines[]`)

| Member | Type | Meaning |
|---|---|---|
| `id` | **required** text | A short id, unique in the scene. |
| `speaker` | **required** text | The character who says it. |
| `to` | text | Who it is said to: a character (not the speaker), or `camera`, `audience` or `group`. A character whose id is one of those three words is the character, so do not give characters those ids. |
| `among` | list of text | For a line said to the `group`: the characters in the room, when they are individually known. Not the speaker. Leave it out when the group is a crowd. |
| `start_ms` | **required** whole number | When it starts. Inside the scene. |
| `end_ms` | **required** whole number | When it ends. |
| `turn` | true or false; default `false` | True when the line turns the scene: how the listener takes it is part of the moment, so the audience needs to see them. A turning line needs `to`, and not `camera` or `audience`: nobody on screen takes those. |
| `text` | text | What is said. |

#### Lines to the camera, the audience or a room

A presenter talks to the lens, a vox-pop answers an interviewer who is off screen, a safety video speaks to whoever is
watching, a best man toasts the whole room. None of these has one character to look at, so `to` takes three typed
targets instead of a character id:

| `to` | Meaning | What the checks do |
|---|---|---|
| `camera` | Direct address: the speaker talks to the viewer, at the lens. | The speaker should face the camera (`facing: camera`). No other character is expected to react, so the reaction rule and the pair-of-singles eyeline rule leave the line alone. A line said to the camera cannot `turn`. |
| `audience` | The same, but off the lens: the speaker talks to the viewers (a stage, a lecture, an aside) without looking down the camera. | As `camera`: no character reaction is expected. |
| `group` | A room, a crowd or several characters at once. `among` lists them when they are individually known. | No single listener, so no reaction or pair eyeline is expected for the line. A group line can `turn`; the plan then says the moment is how the room takes it, and it is for the director: readiness does not yet look for the room's reaction. |

`dpl missing` does not ask "who is this said to?" about a line that gives one of these targets, and it offers them as answers
when it does ask.

<!-- dpl:fragment scenes[0].lines -->
```json
[
  {"id": "L1", "speaker": "host", "to": "camera", "start_ms": 1000, "end_ms": 4000, "text": "Welcome back. Today: how to change a flat tyre."},
  {"id": "L2", "speaker": "host", "to": "audience", "start_ms": 4500, "end_ms": 7000, "text": "And yes, you can do this in a dress."},
  {"id": "L3", "speaker": "best_man", "to": "group", "among": ["bride", "groom", "mother"], "start_ms": 8000, "end_ms": 12000, "text": "Raise your glasses."}
]
```

#### Saying no

A plan that is silent about something and a plan that says there is none are different. A scene with no `lines` might be a
silent scene or a scene nobody has described yet; `dpl missing` cannot tell, so it asks. `none` is the way to answer: a
list of things the scene, the sequence or the film deliberately does not have, each with an optional `why`.

**Absence** (`none[]`, `scenes[].none[]`, `sequences[].none[]`)

| Member | Type | Meaning |
|---|---|---|
| `kind` | **required** one of `spoken_lines`, `line_of_action`, `journey` | What there is none of. In a scene: `spoken_lines` or `line_of_action`. In the film or a sequence: `journey`. A kind in the wrong place is `DPL-NONE`; the same kind twice is `DPL-DUPLICATE`. |
| `why` | text | Why there is none ("a silent scene", "360-degree master: the camera circles the table", "the film stays in one room"). **Required for `line_of_action`**, which departs from editing convention (`DPL-REQUIRED`); optional for the others. |

- **`spoken_lines`** (in a scene): nobody speaks on screen. The scene must have no `lines` (`DPL-NONE` if it has). Narration
  in `sound.speech` is separate and unaffected. `dpl missing` stops asking who speaks, and `not_assessed` stops listing
  "spoken lines".
- **`line_of_action`** (in a scene): the scene is staged without a line of action, as in 360-degree coverage or the
  line-free staging of Ozu. The scene must have no `axis` (`DPL-NONE` if it has), and must say `why`. `dpl missing` stops
  asking which side of the line the camera is on. `RDY-AXIS` has nothing to judge here (it does not fire on position, even
  if an `axis` is left in by mistake, which the validator reports). The eyeline and jump-cut checks still apply, and so does
  everything else: the declaration says there is no line, not that the staging is unchecked.
- **`journey`** (in the film or a sequence): the camera travels nowhere. The film or sequence must have no `journey`
  (`DPL-NONE` if it has). `dpl missing` stops asking whether the camera travels, when the film says so, or when every
  sequence does. A sequence's `none` says it has no journey *of its own*; a journey on the film still governs its scenes.

<!-- dpl:fragment scenes[0].none -->
```json
[{"kind": "spoken_lines", "why": "A silent scene: only the wind."},
 {"kind": "line_of_action", "why": "360-degree master: the camera circles the table, so no side of a line is kept."}]
```

<!-- dpl:fragment none -->
```json
[{"kind": "journey", "why": "The film stays in the one room."}]
```

Why a list of terms, not a flag per fact. Each absence carries its own reason, the list is one place to look, a new kind of
"none" is one more term and not another member on another type, and the empty list is still the default, so a plan that
says nothing reads exactly as before. A fact that is left out is *not yet answered*; only an entry in `none` is an answer.

**State changes and states.** A prop that changes (a glass breaks, a door opens) is recorded as a scene `state_changes`
entry, with `shown` set when the audience must see it happen. A shot's `states` say what state each prop is in as the shot
shows it. Together they let the checks catch a prop shown whole after it broke, or a change nobody sees.

**A shot's `states` describe what is seen in it; a shot that contains a change may show either side of it.** A shot that
runs across a `state_changes` entry (the glass breaks at 23,200 ms inside a shot that runs 22,800 to 24,500) can truthfully
be described by the state before the change or the state after it, so the checks accept either `from` or `to` for that prop in
that shot. A shot that does not contain the change shows the state the prop is in at that time. No separate field says which.

**StateChange** (`scenes[].state_changes[]`)

| Member | Type | Meaning |
|---|---|---|
| `prop` | **required** text | The prop that changes. |
| `from` | **required** text | Its state before. |
| `to` | **required** text | Its state after. Must differ from `from`. |
| `at_ms` | **required** whole number | When it changes. Inside the scene. |
| `shown` | true or false; default `false` | True when the audience must see it happen. |

**PropState** (`scenes[].shots[].states[]`)

| Member | Type | Meaning |
|---|---|---|
| `prop` | **required** text | The prop. |
| `state` | **required** text | Its state in this shot, in a word or two. |

<!-- dpl:fragment scenes[0].shots[0] -->
```json
{"id": "K7", "purpose": "The glass slips from his hand and breaks.", "subject": "glass",
 "start_ms": 22800, "end_ms": 24500, "camera": {"size": "detail", "movement": "hold"}, "source": "footage",
 "props": ["glass"], "coverage": {"role": "insert", "priority": "must"}, "setup": "E",
 "states": [{"prop": "glass", "state": "broken"}]}
```

#### Rig actions

A shot's staging of a rigged character can name its `actions`, one after another inside the shot. Each `kind` is one of
`walk`, `argue`, `turn_head`, `kneel`, `look_up`, `point`, `climb` and `stumble`.

- `walk` goes to a frame x or box (`to`) or by a `distance`, with a `step_ms` for the length of a step cycle.
- `argue` swings an arm (`side`, `left` or `right`; right when absent).
- `turn_head` and `look_up` turn the head to an angle (`to`, degrees).
- `kneel` goes down to a kneel.
- `point` draws an arm back and strikes toward an angle (`to`) with the arm named by `side`; the strike lands about
  60 per cent of the way through its span.
- `climb` is a run of climbing steps with a `step_ms` (900 when absent).
- `stumble` is a trip, a catching arm and a recovery. It takes no extra members.

An action is timed in one of two ways. Give `start_ms` and `end_ms`, which must fall inside the shot and follow the action
before. Or give `at`, a word of a measured spoken line (`line`, `word`, and `on: "stress"` to land on the stress rather than
the start), with a `duration_ms`: the hit lands on that word and the start is worked out from the duration. The line needs
`source_ms` and a `words` entry for that word (2.8). The reader accepts `at` on any kind, but it is meant for `point` and
`climb`, whose hit has a contact moment. Only a character with a `rig` can act (`DPL-RIG`).

**StagingAction** (`scenes[].shots[].staging[].actions[]`)

| Member | Type | Meaning |
|---|---|---|
| `kind` | **required** one of `walk`, `argue`, `turn_head`, `kneel`, `look_up`, `point`, `climb`, `stumble` | The action. |
| `start_ms` | whole number | When the action starts. Give `start_ms` and `end_ms`, or `at` with `duration_ms`. |
| `end_ms` | whole number | When it ends. Inside the shot, and after the action before it ends. |
| `at` | `ActionWordAnchor` | Lands the action on a spoken word instead of a time (needs the line's `source_ms` and the word's measured timing). Any kind of action may use it; it is meant for `point` and `climb`, whose hit lands on the word. |
| `duration_ms` | whole number | With `at`: how long the action takes. The start is derived from it. |
| `side` | one of `left`, `centre`, `right` | For `argue` and `point`: the arm, `left` or `right`. Right when absent. `centre` is read but refused (`DPL-RIG`). |
| `to` | number, or list of 4 numbers | For `walk`: where to, either a frame x from 0 to 1, or a destination box of four numbers, each from 0 to 1 (the reader checks only the count and the range). For `turn_head`, `look_up` and `point`: an angle in degrees. Not for the other kinds. |
| `distance` | number | For `walk`: how far, as a fraction of the frame width, from -1 to 1. Use `distance` or `to`, not both. |
| `step_ms` | whole number | For `walk`: the length of one step cycle, 1000 ms when absent. For `climb`: the length of one step, 900 ms when absent. Above 0. Not for the other kinds. |

**ActionWordAnchor** (`scenes[].shots[].staging[].actions[].at`)

| Member | Type | Meaning |
|---|---|---|
| `line` | **required** text | The id of a spoken line (`sound.speech`). |
| `word` | **required** whole number | The one-based index of a word of that line. The line must carry measured `words` and a `source_ms`. |
| `on` | text | Only `stress` is allowed: land on the word's stress (its `stress_ms`) instead of its start. Absent means the start. |

<!-- dpl:fragment scenes[0].shots[0].staging -->
```json
[{"subject": "MAYA", "side": "left", "facing": "right", "moving": "right",
  "actions": [
    {"kind": "walk", "start_ms": 0, "end_ms": 2000, "distance": 0.3, "step_ms": 800},
    {"kind": "point", "at": {"line": "L1", "word": 2}, "duration_ms": 1000, "side": "left", "to": 20},
    {"kind": "stumble", "start_ms": 4000, "end_ms": 5500}]}]
```

### 2.7 Picture layers and overlays

Two ways to build a picture beyond a single image. **Overlays** put something over a shot's picture for part of its
length: an inset, a nameplate, words that land, a counter, a diagram. **Layers** build the shot in depth: the room behind,
anything between, the subject in front, each with its own timing and motion, so they can drift apart or slide off
different ways. Without layers the shot is one picture.

**Overlays.** An overlay's `kind` must match its members: an `inset` shows a `subject`; a `nameplate` or `caption` has
`text`; `words` has its `words`; a `counter` has a `count` running from one number to another; a `diagram` has a `diagram`.
Only a diagram has nodes. A `words`, `caption` or `nameplate` on screen for under 800 ms is a warning, as too brief to read. A caption
with `timing` starts on a spoken moment, so it needs an `anchor`; `timing` gives the allowed range for its end (held long
enough, cleared in time), and its start is never moved to fit. `style` names a technique in lower-case words joined by underscores; realisation
draws it. `backing` is the shape behind text (`solid`, `accent`, `band`, `none`), chosen per use.

A **diagram** is a simple animation over the picture that explains the point: labelled boxes placed on the frame, links
that draw on between them, and markers that travel. Positions are fractions of the frame; times are film milliseconds, each
tied by the plan's author to a word. It may run on across the following shots of its scene, since an argument drawn over a
cut must not restart.

**Overlay** (`scenes[].shots[].overlays[]`)

| Member | Type | Meaning |
|---|---|---|
| `id` | **required** text | A short id, unique in the shot. |
| `kind` | **required** one of `inset`, `nameplate`, `words`, `caption`, `counter`, `diagram` | What it is. An `inset` shows a subject; a `nameplate` or `caption` has text; `words` lands words one by one; a `counter` runs between two numbers; a `diagram` draws an animation. |
| `start_ms` | **required** whole number | When it appears. Inside the shot. If it has an `anchor`, this must agree with it. |
| `end_ms` | **required** whole number | When it goes. Inside the shot, except that a diagram or an `end_card` may run on to the end of its scene. A `words`, `caption` or `nameplate` should stay on at least 800 ms to be read. |
| `purpose` | **required** text | What it does for the story. |
| `position` | one of `centre`, `top_left`, `top_right`, `bottom_left`, `bottom_right`, `lower_third`, `upper_third`; default `"centre"` | Where on the frame it sits. |
| `subject` | text | For an inset: the character, prop or location it shows. |
| `text` | text | The text of a nameplate or caption; the label of a counter. |
| `subtitle` | text | A second line under the text. |
| `words` | list of `TimedWord` | For `words`: each word and when it lands. |
| `style` | text | A named technique for how it arrives, in lower-case words joined by underscores (for example `soft_rise`). Realisation draws it. A `caption` can use `subscribe` or `end_card`. |
| `count` | `Count` | For a counter: the number it runs from and to over its time. |
| `anchor` | `SpeechAnchor` | The spoken moment it appears on. A member that has `timing` needs one. |
| `timing` | `CaptionTiming` | The allowed range for its end (a caption held long enough, cleared in time). |
| `backing` | one of `solid`, `accent`, `band`, `none` | The shape behind the text. Absent means the house style's default for that technique. |
| `diagram` | `Diagram` | For a diagram: the nodes, links and movers. |

**TimedWord** (`scenes[].shots[].overlays[].words[]`)

| Member | Type | Meaning |
|---|---|---|
| `text` | **required** text | The word. |
| `at_ms` | **required** whole number | When it lands. Words land in order, inside the overlay. |
| `highlight` | `WordHighlight` | One word of the text made brighter and heavier on its spoken moment. |
| `page_break` | true or false | On the narrator's page (the `page` technique): this word begins a new page. The card stays and the words before it clear. |
| `turn` | text | How what was said before leaves as this word's page begins: `beneath`, `card_left`, `card_right`, `card_up`, `fold`, `lift`, `file_away`, `drop`, `slide_left` or `slide_right`. Any other text is `DPL-OVERLAY`. Absent, the renderer varies it. |

**WordHighlight** (`scenes[].shots[].overlays[].words[].highlight`)

| Member | Type | Meaning |
|---|---|---|
| `word` | **required** text | A whole word of the overlay's text. |
| `at_ms` | **required** whole number | When it lights. On or after the text lands and before the overlay ends. |

**Count** (`scenes[].shots[].overlays[].count`)

| Member | Type | Meaning |
|---|---|---|
| `from` | **required** whole number | The number it starts on. |
| `to` | **required** whole number | The number it ends on. |

**CaptionTiming** (`scenes[].shots[].overlays[].timing`)

| Member | Type | Meaning |
|---|---|---|
| `min_hold_ms` | whole number | The least time the caption stays. |
| `max_hold_ms` | whole number | The most time it stays. Not below `min_hold_ms`. |
| `clear_before` | `SpeechAnchor` | It must be gone by this spoken moment. |
| `within_shot` | true or false | True when it ends with its shot at the latest. Default true. |

**Diagram** (`scenes[].shots[].overlays[].diagram`)

| Member | Type | Meaning |
|---|---|---|
| `nodes` | **required** list of `DiagramNode` | Labelled boxes placed on the frame. At least one. |
| `links` | list of `DiagramLink` | Links that draw on between two nodes. |
| `movers` | list of `DiagramMover` | Markers that travel between two points. |

**DiagramNode** (`scenes[].shots[].overlays[].diagram.nodes[]`)

| Member | Type | Meaning |
|---|---|---|
| `id` | **required** text | A short id, unique in the diagram. |
| `label` | **required** text | The words in the box. At most 24 characters. |
| `x` | **required** number | Across the frame, from 0.04 to 0.96 (the box's centre). |
| `y` | **required** number | Down the frame, from 0.04 to 0.96. |
| `at_ms` | **required** whole number | When it appears, in film time. Tie it to a spoken word. |
| `out_ms` | whole number | When it leaves. After `at_ms`. |
| `tone` | one of `ink`, `claim`; default `"ink"` | `ink` is the house ink; `claim` is the colour for what the scene asserts. |

**DiagramLink** (`scenes[].shots[].overlays[].diagram.links[]`)

| Member | Type | Meaning |
|---|---|---|
| `from` | **required** text | The id of a node. |
| `to` | **required** text | The id of a different node. |
| `at_ms` | **required** whole number | When it starts drawing on. It should come after the two nodes it joins have appeared (`DPL-OVERLAY` warns if not). |
| `draw_ms` | whole number; default `600` | How long it takes to draw. |
| `out_ms` | whole number | When it leaves. |
| `tone` | one of `ink`, `claim`; default `"ink"` | `ink` or `claim`. |

**DiagramMover** (`scenes[].shots[].overlays[].diagram.movers[]`)

| Member | Type | Meaning |
|---|---|---|
| `id` | **required** text | A short id, unique in the diagram. |
| `label` | text | A label on the marker. |
| `from` | **required** [x, y] | Where it starts: `[x, y]`, each from 0.04 to 0.96. |
| `to` | **required** [x, y] | Where it ends, in the same form. |
| `at_ms` | **required** whole number | When it starts to move. |
| `dur_ms` | **required** whole number | How long it takes. |
| `out_ms` | whole number | When it leaves. |

<!-- dpl:fragment scenes[0].shots[0].overlays -->
```json
[{"id": "ov1", "kind": "nameplate", "start_ms": 500, "end_ms": 3000, "purpose": "Name the speaker.", "position": "lower_third", "text": "Maya", "subtitle": "Tom's wife", "backing": "band"},
 {"id": "ov2", "kind": "counter", "start_ms": 3000, "end_ms": 5000, "purpose": "Show the debt growing.", "text": "pounds owed", "count": {"from": 0, "to": 40000}},
 {"id": "ov3", "kind": "words", "start_ms": 5000, "end_ms": 8000, "purpose": "Land the sentence word by word.", "style": "soft_rise",
  "words": [{"text": "the", "at_ms": 5000}, {"text": "money", "at_ms": 5400, "highlight": {"word": "money", "at_ms": 5800}}]},
 {"id": "ov4", "kind": "caption", "start_ms": 8000, "end_ms": 11000, "purpose": "Subtitle the quote.", "text": "He told me about the money.",
  "anchor": {"line": "L3", "boundary": "onset"}, "timing": {"min_hold_ms": 1500, "max_hold_ms": 4000, "within_shot": true}},
 {"id": "ov5", "kind": "diagram", "start_ms": 11000, "end_ms": 14000, "purpose": "Show where the money went.",
  "diagram": {"nodes": [{"id": "a", "label": "Tom", "x": 0.25, "y": 0.5, "at_ms": 11000}, {"id": "b", "label": "Brother", "x": 0.75, "y": 0.5, "at_ms": 11500, "tone": "claim"}],
              "links": [{"from": "a", "to": "b", "at_ms": 12000, "draw_ms": 800}],
              "movers": [{"id": "m", "label": "cash", "from": [0.25, 0.5], "to": [0.75, 0.5], "at_ms": 12500, "dur_ms": 1000}]}}]
```

**Layers.** A layer has a `depth` (`background`, `intermediate`, `foreground`), a `subject`, and a `purpose`. A foreground
layer's subject must be a character or prop in the shot; a subject in front with no background layer is a warning, because
the shot's own picture is then the background. A layer moves in three ways, which combine: a single `motion` (a hold, a
drift, a rise, a slide, a push, a pull, a parallax, or a converge where layers travel into one frame and settle at `at`);
`keys`, which animate it (offset, scale, rotation, opacity and which way it faces, eased between keys, and a key can land on
a spoken moment); and `actions`, which repeat (a walk's bob, a breath, a flicker). `parallax` is how much the layer follows
the shot's `window`, the camera as a window travelling over the painted picture.

**Layer** (`scenes[].shots[].layers[]`)

| Member | Type | Meaning |
|---|---|---|
| `id` | **required** text | A short id. Removals name layers by it. |
| `depth` | **required** one of `background`, `intermediate`, `foreground` | Where it sits in the shot's depth. A foreground layer's `subject` must be a character or prop in the shot. |
| `subject` | **required** text | The character, prop or location the layer shows. |
| `purpose` | **required** text | What this layer does for the story. |
| `start_ms` | whole number | When the layer is present, inside the shot. Without times, the whole shot. |
| `end_ms` | whole number | When it goes. |
| `motion` | `LayerMove` | A single move the layer makes. |
| `presence` | one of `faint`, `present`, `full`; default `"full"` | How strongly it shows. |
| `entrance` | one of `cut`, `fade`, `emerge`, `slide_in`; default `"cut"` | How it arrives. |
| `exit` | one of `cut`, `fade`, `dissolve`, `slide_off`; default `"cut"` | How it leaves. |
| `region` | one of `full`, `upper`, `lower`, `left`, `right`; default `"full"` | Where in the frame it sits; the rest is left to the layers behind. |
| `style` | text | A named technique for how it appears and behaves, in lower-case words joined by underscores (for example `vision_swell`). |
| `at` | `LayerPlace` | Where a converging layer settles: its centre and size, as shares of the frame. Needed with `motion` `converge`. |
| `keys` | list of `LayerKey` | The layer animated by keys (a cut-out figure crossing a painted landscape, a mist band sweeping across), eased between them. With a `motion`, the motion runs first, then the keys. |
| `actions` | list of `LayerAction` | Repeating movements over the keys (the bob of a walk while the keys carry the figure along). |
| `parallax` | number | How much the layer follows the shot's `window`: 1 moves with the picture behind, more than 1 is nearer, less is farther. From 0 to 4. By depth when absent: background 1, intermediate 1.25, foreground 1.6. |
| `pivot` | [x, y] | The point the layer turns and scales about, as fractions of the frame: `[0.5, 0.92]` is a figure's feet. Absent, the centre of its subject's box, else the frame's centre. |

**LayerMove** (`scenes[].shots[].layers[].motion`)

| Member | Type | Meaning |
|---|---|---|
| `kind` | **required** one of `hold`, `drift`, `rise`, `descend`, `slide_in`, `slide_off`, `push`, `pull`, `parallax`, `converge` | What the layer does: `hold`, `drift`, `rise`, `descend`, `slide_in`, `slide_off`, `push`, `pull`, `parallax` or `converge`. |
| `direction` | one of `none`, `left`, `right`, `up`, `down`, `toward`, `away`; default `"none"` | Which way. A slide, drift or parallax needs one; a converge says which way it travels in. |
| `amount` | one of `subtle`, `moderate`, `strong`; default `"subtle"` | How much, by the house style's measure. |
| `distance` | number | How far exactly, as a share of the frame (above 0, up to 1), in place of `amount`. A drift travels this far one way, then the same the other. |

**LayerPlace** (`scenes[].shots[].layers[].at`)

| Member | Type | Meaning |
|---|---|---|
| `x` | **required** number | Centre across the frame, 0 (left) to 1 (right). |
| `y` | **required** number | Centre down the frame, 0 (top) to 1 (bottom). |
| `scale` | **required** number | The layer's width as a share of the frame's, from 0.05 to 1. |

**LayerKey** (`scenes[].shots[].layers[].keys[]`, `scenes[].shots[].window[]`)

| Member | Type | Meaning |
|---|---|---|
| `at_ms` | **required** whole number | When the key is reached. Keys run in time order, inside the layer's (or the shot's) time. If it has an `anchor`, this must agree with it. |
| `anchor` | `SpeechAnchor` | The spoken moment the key lands on (a step lands on a word). |
| `x` | number | Offset across, as a fraction of the frame from the layer's own place (up to 3 frames away). |
| `y` | number | Offset down. |
| `scale` | number | Size multiplier. Above 0. |
| `rotation` | number | Turn, in degrees. |
| `opacity` | number | From 0 to 1. |
| `turn` | number | Which way a cut-out faces: 1 as drawn, -1 mirrored. Between them it turns edge-on, as a shadow puppet is turned on its screen. |
| `ease` | one of `in_out`, `linear`, `in`, `out`, `hold`, `smooth`; default `"in_out"` | How the layer moves from the key before into this one. `smooth` carries the velocity through the key. |

**LayerAction** (`scenes[].shots[].layers[].actions[]`)

| Member | Type | Meaning |
|---|---|---|
| `kind` | **required** one of `walk`, `bob`, `sway`, `breathe`, `flicker`, `flutter` | The repeating movement: `walk`, `bob`, `sway`, `breathe`, `flicker` or `flutter`. |
| `start_ms` | whole number | When it starts. Without times, the whole layer. |
| `end_ms` | whole number | When it ends. |
| `amount` | one of `subtle`, `moderate`, `strong`; default `"subtle"` | How much. |
| `rate` | number | Cycles per second (a walk's steps), from 0 to 6. By kind when absent. |

<!-- dpl:fragment scenes[0].shots[0].layers -->
```json
[{"id": "sky", "depth": "background", "subject": "KITCHEN", "purpose": "The room behind.", "motion": {"kind": "drift", "direction": "left", "amount": "subtle"}},
 {"id": "maya", "depth": "foreground", "subject": "MAYA", "purpose": "Maya crosses the room.", "start_ms": 500, "region": "lower", "entrance": "fade", "exit": "fade",
  "pivot": [0.5, 0.92], "parallax": 1.6,
  "keys": [{"at_ms": 500, "x": -0.3, "turn": 1}, {"at_ms": 2500, "x": 0.1, "ease": "smooth"}],
  "actions": [{"kind": "walk", "amount": "moderate", "rate": 1.5}]}]
```

<!-- dpl:fragment scenes[0].shots[0].window -->
```json
[{"at_ms": 0, "x": 0, "scale": 1.2}, {"at_ms": 4000, "x": -0.2, "scale": 1.2, "ease": "in_out"}]
```

### 2.8 Sound

Sound is planned at two levels. **Per scene**, `sound[]` says what speech, music, ambience, effects and authored silence are
*doing*, `hearing` says whose ears the scene takes, and `phrases` says how each heard sound runs through it. **Across the
work**, the top-level `sound` object places everything in film time: the carriers, the spoken lines, which carrier owns the
foreground span by span, how sound crosses a change of picture, and the rules that protect moments such as an authored
quiet.

Here is what the parts of `sound` are for:

- **`carriers`** name each source of sound once (a voice, a song, a room tone, a planned silence). Everything else refers
  to a carrier by id.
- **`speech`** is the fixed spoken words, placed in time. The plan never changes the words.
- **`beat_functions`** mark what the words are doing over film time, across scene boundaries (a claim from a source, a place,
  a new act). Each names a span; `speech` refers to existing line ids, `words` is a readable excerpt, and `source` names
  the evidence cited. Beats are in time order and may not overlap: one clear job at a time makes picture suggestions and
  findings unambiguous. A shot's `picture.serves` points back at them. Readiness checks source visibility, act cards,
  presenter gaps, sponsor setting and unserved beats; a scene's `accepts` can record a justified departure. No check
  changes the speech.
- **`beats`** say which carrier owns the foreground, span by span, and which carriers support or are withheld.
- **`bridges`** say how sound crosses a change of picture or world.
- **`rules`** protect moments, with spans, failure conditions and a reviewer's check. A rule that is an authored quiet says
  so in `quiet`: how it begins, how it ends, and whether it must hold against anything breaking in.
- **`events`** are sounds at a moment (a hit, a chime), tied to a picture, to a spoken moment or to a pulse.
- **`pauses`** make room in the speech for an event or a picture join.
- **`intensity`** is the lever for feeling: where a music carrier's drums come forward or sit back while its song plays on.
- **`takeovers`** are a carrier rising on one straight line in dB, with nothing ducking it, not even the voice.

**Sound** (`sound`)

| Member | Type | Meaning |
|---|---|---|
| `carriers` | list of `SoundCarrier` | The sources of sound the film uses: a voice, a song, a room tone, an effect, a planned silence. |
| `speech` | list of `SpeechLine` | The spoken lines, placed in film time. The words are fixed; nothing in the plan changes them. |
| `beat_functions` | list of `BeatFunction` | What the fixed spoken words are doing at each span (a claim from a source, a place, a new act), so pictures can be chosen to serve them. |
| `beats` | list of `SoundBeat` | Which carrier owns the foreground, span by span. |
| `bridges` | list of `SoundBridge` | How sound crosses a change of picture or world. |
| `rules` | list of `SoundRule` | Protections for moments, such as an authored quiet: what to withhold, when, and how a reviewer checks. |
| `events` | list of `SoundEvent` | Sounds that happen at a moment (a hit, a chime, a landing), placed in time like spoken lines. |
| `pauses` | list of `SpeechPause` | Pauses in the speech that give a sound event or a picture join room (the plan's times already include them). |
| `intensity` | list of `IntensityPoint` | The intensity lever: where a music carrier's drums come forward or sit back while its song plays on. |
| `takeovers` | list of `Takeover` | A carrier rising on one straight line in dB over a span, with nothing ducking it, not even the voice. |

**SoundCarrier** (`sound.carriers[]`)

| Member | Type | Meaning |
|---|---|---|
| `id` | **required** text | A short unique id. Beats, phrases, events and removals name carriers by it. |
| `kind` | **required** one of `speech`, `music`, `ambience`, `effect`, `silence` | What it is. |
| `identity` | text | Which voice, song or place it is. |
| `job` | **required** text | What this carrier does for the story. |
| `constraints` | list of text | Limits on how the sound may be made or used (its source, the number of takes, the allowed cues). |
| `pulse` | `Pulse` | A measured beat grid, for music with a steady pulse (explained below). |
| `message` | `CarrierMessage` | What the carrier says, when it speaks (explained below). |

**A pulse** is a measured beat grid for music with a steady pulse. It carries on in the listener's head after the music
stops, so later moments can land on it: a spoken line or an event with `lands_on` starts on a beat or bar of the carrier's
pulse, within 40 ms, and a carrier with no pulse has nothing to land on (`DPL-RHYTHM`). A **message** is for a carrier that
speaks (a talking drum, a sung line in another language): it is part of the story, so its phrases are never cut part-way and
never buried.

**Pulse** (`sound.carriers[].pulse`)

| Member | Type | Meaning |
|---|---|---|
| `period_ms` | **required** whole number | The length of one beat. Above 0. |
| `phase_ms` | **required** whole number | Film time of one beat on the grid (the first downbeat). |
| `beats_per_bar` | whole number; default `4` | Beats in a bar. |

**CarrierMessage** (`sound.carriers[].message`)

| Member | Type | Meaning |
|---|---|---|
| `language` | **required** text | The language of the message. |
| `words` | text | The message as it is said (in its own language), when known. |
| `meaning` | **required** text | What it means. |
| `phrases` | list of `MessagePhrase` | Where each statement of the message falls in the carrier's own recording. |

**MessagePhrase** (`sound.carriers[].message.phrases[]`)

| Member | Type | Meaning |
|---|---|---|
| `in_ms` | **required** whole number | Where the statement starts in the carrier's recording (media time). |
| `out_ms` | **required** whole number | Where it ends. Later than `in_ms`. |

**Speech and anchors.** A spoken line is placed in film time, and, when the recording is measured, in its own recording as
well (`source_ms`, and per-word offsets in `words`). A **speech anchor** names a spoken moment: a line's `onset` or
`offset`, plus a signed offset. A sound event, an overlay, a layer key or a caption's `clear_before` can use one, and the
authored time must agree with it, so the item follows the words when the speech is placed differently
(`docs/realisation/SPEECH_ANCHORS.md`).

**SpeechLine** (`sound.speech[]`)

| Member | Type | Meaning |
|---|---|---|
| `id` | **required** text | A short unique id. Anchors, pauses and bed lifts name lines by it. |
| `carrier` | **required** text | The speech carrier that says it. |
| `text` | **required** text | The words, exactly as they are said. |
| `start_ms` | **required** whole number | When the line starts in film time. |
| `end_ms` | **required** whole number | When it ends. |
| `direction` | text | How it is delivered. |
| `lands_on` | `LandsOn` | The line starts on a beat (or bar) of a carrier's pulse, within 40 ms. |
| `source_ms` | whole number | Where the line starts in its recording, so the recording can be placed line by line and a pause put between lines without losing sync with the face that speaks them. |
| `words` | list of `SpeechWord` | Measured words: one-based, with offsets in this line's recording. Anything anchored to a word needs them (and the line's `source_ms`). |

**SpeechWord** (`sound.speech[].words[]`)

| Member | Type | Meaning |
|---|---|---|
| `index` | **required** whole number | The word's number in the line, from 1. Unique in the line. |
| `start_ms` | **required** whole number | Where the word starts, as an offset from the start of the line, less than the line's length. |
| `stress_ms` | whole number | Where its stress falls, from `start_ms` up to the line's length. |

**SpeechAnchor** (`sound.events[].anchor`, `scenes[].shots[].overlays[].anchor`, `...overlays[].timing.clear_before`, `layers[].keys[].anchor`, `window[].anchor`)

| Member | Type | Meaning |
|---|---|---|
| `line` | **required** text | The id of a line in `sound.speech`. |
| `boundary` | one of `onset`, `offset`; default `"onset"` | Which edge of the line: `onset` or `offset`. |
| `offset_ms` | whole number; default `0` | A signed offset from that edge. |

**LandsOn** (`sound.speech[].lands_on`, `sound.events[].lands_on`)

| Member | Type | Meaning |
|---|---|---|
| `carrier` | **required** text | A sound carrier that has a `pulse`. |
| `unit` | one of `beat`, `bar`; default `"beat"` | Land on a `beat` or a `bar`. |

**BeatFunction** (`sound.beat_functions[]`)

| Member | Type | Meaning |
|---|---|---|
| `id` | **required** text | A short unique id. A shot's `picture.serves` names it. |
| `function` | **required** one of `claim_from_source`, `law_or_official_fact`, `where`, `who`, `unfilmed_event`, `action`, `opinion_or_joke`, `new_act`, `figure_of_speech`, `quote`, `promise_or_question`, `sponsor`, `call_to_action` | The job the words do there. |
| `start_ms` | **required** whole number | When the span starts. Beat functions run in time order and do not overlap. |
| `end_ms` | **required** whole number | When it ends. Inside the work. |
| `speech` | list of text | The ids of the speech lines it covers. Each must overlap the span. |
| `words` | text | A readable excerpt of what is said. |
| `source` | text | The evidence cited, when the words rest on one. |
| `note` | text | Anything else worth saying. |

The `function` of a beat function is one of `claim_from_source`, `law_or_official_fact`, `where`, `who`, `unfilmed_event`,
`action`, `opinion_or_joke`, `new_act`, `figure_of_speech`, `quote`, `promise_or_question`, `sponsor` or
`call_to_action`.

**SoundBeat** (`sound.beats[]`)

| Member | Type | Meaning |
|---|---|---|
| `start_ms` | **required** whole number | When the span starts. Beats run in time order and do not overlap. |
| `end_ms` | **required** whole number | When it ends. |
| `foreground` | text | The carrier in the foreground, or null when nothing owns it. |
| `supporting` | list of text | Carriers heard behind it. |
| `withheld` | list of text | Carriers held back on purpose. A carrier has one role in a beat. |
| `purpose` | **required** text | Why the sound is arranged this way. |
| `note` | text | Anything else. |

**SoundBridge** (`sound.bridges[]`)

| Member | Type | Meaning |
|---|---|---|
| `id` | **required** text | A short unique id. |
| `at_ms` | **required** whole number | When the change of picture or world happens. |
| `from` | text | The shot before the change. |
| `to` | text | The shot after it. |
| `retained` | list of text | Carriers that carry on across it. |
| `exiting` | list of text | Carriers that stop. |
| `entering` | list of text | Carriers that begin. |
| `rule` | **required** text | How the crossing is made. |

**SoundRule** (`sound.rules[]`)

| Member | Type | Meaning |
|---|---|---|
| `id` | **required** text | A short unique id. |
| `rule` | **required** text | The rule, as a sentence. |
| `withhold` | list of text | Carriers to hold back. |
| `allow` | list of text | Carriers that may sound anyway. |
| `spans` | list of `SoundSpan` | When the rule applies. |
| `fails_if` | list of text | What would break the rule, in words. |
| `check` | text | How a reviewer tells whether the rule was kept. |
| `quiet` | `QuietDesign` | Makes the rule an authored quiet: how it begins and ends. |

**SoundSpan** (`sound.rules[].spans[]`)

| Member | Type | Meaning |
|---|---|---|
| `name` | **required** text | A name for the span. |
| `start_ms` | **required** whole number | When it starts. |
| `end_ms` | whole number | When it ends. Open-ended when absent. |

**QuietDesign** (`sound.rules[].quiet`)

| Member | Type | Meaning |
|---|---|---|
| `begins` | **required** one of `hold_back`, `cut_off` | `hold_back` (sounds are held back from starting) or `cut_off`. |
| `ends` | **required** one of `return`, `reveal`, `reset` | `return` (the same sounds come back), `reveal` (something new) or `reset` (the world resets). |
| `must_hold` | true or false; default `false` | True when nothing may break into the quiet. |

**SoundEvent** (`sound.events[]`)

| Member | Type | Meaning |
|---|---|---|
| `id` | **required** text | A short unique id. A pause's `for` names it. |
| `carrier` | **required** text | The carrier it is heard on. |
| `at_ms` | **required** whole number | When it happens, in film time. |
| `what` | **required** text | What is heard and why it happens here. |
| `lands_on` | `LandsOn` | Puts the event on a beat or bar of a pulse. |
| `length_ms` | whole number | How long the sound needs to be heard complete. Needed with `clear_of_speech`. |
| `clear_of_speech` | true or false; default `false` | True when the sound must not overlap any spoken line; it has its own room. |
| `sync_to` | text | The id of the picture it must match: a shot or layer that starts on it (within 80 ms). |
| `lead_ms` | whole number | The sound anticipates its picture: it is already heard building this long before `at_ms`, and peaks on it. Needs `sync_to`. More than 0, at most 4000. |
| `anchor` | `SpeechAnchor` | The spoken moment it belongs to, so it follows the words when the speech is placed differently. `at_ms` must agree with it. |

**SpeechPause** (`sound.pauses[]`)

| Member | Type | Meaning |
|---|---|---|
| `after_line` | **required** text | The id of the spoken line the pause follows. |
| `length_ms` | **required** whole number | How long the pause is. The next line must start at least that long after. |
| `for` | text | The sound event the pause is for. Give `for` or `for_join`. |
| `for_join` | text | The shot id that a picture join enters, when the pause is for the join. A travel join (a climb, a descent) needs silence to move in, with no sound made for it. |
| `why` | text | Why the pause is there. |
| `hides_speaker` | true or false; default `false` | True when the speaker's window fades out for the pause (an interlude: the scene plays, then they return). |

**IntensityPoint** (`sound.intensity[]`)

| Member | Type | Meaning |
|---|---|---|
| `carrier` | **required** text | A music carrier. |
| `at_ms` | **required** whole number | From this moment the drums are at this level. Points run in time order. |
| `level` | **required** one of `rest`, `low`, `mid`, `high`, `peak` | How far forward the drums come: `rest`, `low`, `mid`, `high` or `peak`. |
| `why` | **required** text | Why feeling rises or settles here. |
| `ramp_ms` | whole number; default `1200` | How long they take to reach the level, before `at_ms`. |

**Takeover** (`sound.takeovers[]`)

| Member | Type | Meaning |
|---|---|---|
| `carrier` | **required** text | The carrier that rises. |
| `start_ms` | **required** whole number | When the rise starts. |
| `end_ms` | **required** whole number | When it ends, later than the start. |
| `from_db` | **required** number | The level at the start, relative to the carrier's bound level; 0 dB is the voice's level. |
| `to_db` | **required** number | The level at the end. Above `from_db`. |
| `why` | **required** text | Why the sound takes over here. |

<!-- dpl:fragment sound -->
```json
{"carriers": [
   {"id": "VOICE", "kind": "speech", "job": "The narrator."},
   {"id": "DRUM", "kind": "music", "identity": "a talking drum", "job": "Calls the town.",
    "pulse": {"period_ms": 500, "phase_ms": 0, "beats_per_bar": 4},
    "message": {"language": "Yoruba", "meaning": "Come home.", "phrases": [{"in_ms": 0, "out_ms": 2000}]}}],
 "speech": [
   {"id": "L1", "carrier": "VOICE", "text": "He told me about the money.", "start_ms": 1000, "end_ms": 3000, "source_ms": 0,
    "words": [{"index": 1, "start_ms": 0}, {"index": 5, "start_ms": 1200, "stress_ms": 1300}]},
   {"id": "L2", "carrier": "VOICE", "text": "It's not what you think.", "start_ms": 6000, "end_ms": 7500, "lands_on": {"carrier": "DRUM", "unit": "beat"}}],
 "beat_functions": [{"id": "b1", "function": "claim_from_source", "start_ms": 1000, "end_ms": 3000, "speech": ["L1"], "words": "He told me about the money.", "source": "Tom's brother"}],
 "beats": [{"start_ms": 0, "end_ms": 4000, "foreground": "VOICE", "supporting": ["DRUM"], "purpose": "The voice leads; the drum is under it."}],
 "bridges": [{"id": "br1", "at_ms": 4000, "retained": ["DRUM"], "exiting": [], "entering": ["VOICE"], "rule": "The drum carries over the cut."}],
 "rules": [{"id": "r1", "rule": "Nothing under the confession.", "withhold": ["DRUM"], "spans": [{"name": "confession", "start_ms": 1000, "end_ms": 3000}],
            "quiet": {"begins": "hold_back", "ends": "return", "must_hold": true}}],
 "events": [{"id": "e1", "carrier": "DRUM", "at_ms": 3500, "what": "A single hit as the glass lands.", "length_ms": 800, "clear_of_speech": true,
             "anchor": {"line": "L1", "boundary": "offset", "offset_ms": 500}}],
 "pauses": [{"after_line": "L1", "length_ms": 2000, "for": "e1", "why": "Give the hit room."}],
 "intensity": [{"carrier": "DRUM", "at_ms": 3500, "level": "high", "why": "Feeling rises here.", "ramp_ms": 1000}],
 "takeovers": [{"carrier": "DRUM", "start_ms": 6000, "end_ms": 7500, "from_db": -18, "to_db": 0, "why": "The drum takes the room as he stops speaking."}]}
```

**In the scene.** The per-scene parts tell a reader why the sound is as it is.

**SceneSound** (`scenes[].sound[]`)

| Member | Type | Meaning |
|---|---|---|
| `kind` | **required** one of `speech`, `music`, `ambience`, `effect`, `silence` | Which kind of sound. |
| `intent` | **required** text | What it is doing in the scene. |
| `must_not` | list of text | What it must not do. |

**Hearing** (`scenes[].hearing`)

| Member | Type | Meaning |
|---|---|---|
| `mode` | **required** one of `objective`, `subjective`, `contrast`, `silence` | `objective` (what is in the world), `subjective` (what a character hears), `contrast` (the two against each other) or `silence`. |
| `quality` | **required** one of `open`, `near_body`, `narrowed`, `off_axis`, `muffled`, `expanding`, `silent` | How the world sounds: `open`, `near_body`, `narrowed`, `off_axis`, `muffled`, `expanding` or `silent`. |
| `whose` | text | For subjective hearing: the character or prop whose hearing it is. |
| `why` | **required** text | Why the scene is heard this way. |

**Phrase** (`scenes[].phrases[]`)

| Member | Type | Meaning |
|---|---|---|
| `carrier` | **required** text | The sound carrier whose phrase this is. One phrase per carrier per scene. |
| `move` | **required** one of `enter`, `carry`, `transform`, `reprise`, `exit`, `foreground` | What the phrase does: `enter`, `carry` on across the cut, `transform`, `reprise`, `exit` or `foreground`. |
| `timing` | **required** one of `rhetorical`, `breath`, `musical`, `emphasis` | What its timing follows: `rhetorical`, `breath`, `musical` or `emphasis`. |
| `keeps` | **required** one of `motif`, `tempo`, `harmony`, `motif_tempo_harmony`, `environment`, `deliberate_break` | What stays recognisable: `motif`, `tempo`, `harmony`, all three (`motif_tempo_harmony`), `environment` or a `deliberate_break`. |
| `what` | **required** text | What the phrase is, in words. |
| `register_from` | **required** text | Its register at the start. |
| `register_to` | **required** text | Its register at the end. A `carry` keeps its register (the same text as `register_from`); a changed register is a `transform` (`DPL-PHRASE`). |
| `placement` | one of `with_picture`, `before_picture`, `across_picture`, `after_picture`; default `"with_picture"` | Where it sits against the picture: `with_picture`, `before_picture`, `across_picture` or `after_picture`. |
| `side` | one of `centre`, `left`, `right`, `both` | Where it is heard across the stereo field. `both` is two placements answering each other. |
| `motif` | text | For music: the motif the phrase plays. |
| `shape` | one of `sustain`, `build`, `resolve` | For music: where the cue is heading: `sustain`, `build` or `resolve`. |

The `kind` of a carrier or of a scene's sound is one of `speech`, `music`, `ambience`, `effect` or `silence`.

### 2.9 Transitions

A **transition** records a join that was decided: what the audience learns across it, what stays the same, the simpler
alternatives that were considered and why they were not chosen. A join between shots that is not listed is an unremarked
cut. Join only neighbouring shots: `from` is a shot and `to` is the shot directly after it, including across a scene
boundary.

`kind` is how the two shots join: `cut`, `match_cut`, `jump_cut`, `smash_cut`, `dissolve`, `fade`, `wipe` or `hold`. A `hold`
keeps the subject and framing intent, so the join is felt as a hold. `need` says why the picture must change here
(`existing_sufficient`, `new_world`, `distinct_planes`, `comparison`, `attention_transfer`, `deliberate_break`);
`simpler` is the simplest join that was considered instead (`none`, `hold`, `reframe`, `cut`). `serves` says whether the
change moves the story on (`story`) or only moves attention (`attention`). A join that serves the story needs a `purpose` saying what happens.
If every change in a sequence only moves attention, the validator warns (`DPL-MOMENTUM`); three joins in a row with the
same device is `DPL-VARIETY`.

With all of this direction, converting tools carry the plan's direction into other formats.

**Transition** (`transitions[]`)

| Member | Type | Meaning |
|---|---|---|
| `from` | **required** text | The id of the shot before the join. |
| `to` | **required** text | The id of the shot after it. Must be the shot that directly follows. |
| `kind` | **required** one of `cut`, `match_cut`, `jump_cut`, `smash_cut`, `dissolve`, `fade`, `wipe`, `hold` | How the two join. |
| `detail` | text | How the join is played beyond its kind (for example a cut that then holds). |
| `purpose` | text | What the audience learns across the join. |
| `keeps` | list of text | What stays the same across the join. |
| `considered` | list of `ConsideredAlternative` | Simpler or different joins that were considered, and why each was not taken. |
| `need` | one of `existing_sufficient`, `new_world`, `distinct_planes`, `comparison`, `attention_transfer`, `deliberate_break` | Why the picture must change here. |
| `simpler` | one of `none`, `hold`, `reframe`, `cut` | The simplest join that was considered instead. |
| `persisting` | list of text | Characters, props or locations that stay in view across the join. |
| `style` | text | A named technique that plays the join beyond its kind (for example `analogue_channel_change`, `inset_expand`, `light_point_reveal`), in lower-case words joined by underscores. Realisation decides how to draw it. |
| `duration_ms` | whole number | How long the join takes, when it is not instant. Above 0. A `cut`, `jump_cut` or `smash_cut` is instant, so has none. A spoken line should not start inside the join (`DPL-SPEECH-JOIN`). |
| `serves` | one of `story`, `attention` | Whether the change moves the story on (`story`: something happens, is learned or transformed) or only moves the audience's attention (`attention`). |

**ConsideredAlternative** (`transitions[].considered[]`)

| Member | Type | Meaning |
|---|---|---|
| `choice` | **required** text | The join that was considered. |
| `why_not` | **required** text | Why it was not taken. |

<!-- dpl:fragment transitions -->
```json
[{"from": "K1", "to": "K2", "kind": "dissolve", "purpose": "The room gives way to Maya's face.", "keeps": ["MAYA"], "need": "attention_transfer",
  "simpler": "cut", "considered": [{"choice": "cut", "why_not": "A cut loses the slow turn from room to person."}],
  "persisting": ["MAYA"], "style": "light_point_reveal", "duration_ms": 600, "serves": "story"}]
```

### 2.10 Short-form profile

The short-form profile is for shoots made on a phone, or with a small crew and real cameras, in a few hours: a promo, a
Short, an interview, a two-person scene. It adds what the first market's constraints need: what the crew shoots with, who is
there and when, the light, the shoot day, and where the work is delivered. It is **opt-in**. A plan says
`work.profile: "short"`; a plan that does not (`film` is the default, and what every plan written before the profile is)
reads, validates and is checked exactly as it was, and none of the rules below run on it. Every member below is optional and
additive, and reading is as strict as everywhere else: an unknown member or a misspelt term is an error.

Shoot Plan (the Shoot Plan app's own planner) plans the same kind of shoot for people. The short-form rules are ported from
its rules (planner rules `2026-10-06.r6`) and cite them, so the two agree; `tools/dpl/import/shoot_plan_to_dpl.py` turns a
Shoot Plan plan into this profile (see "From Shoot Plan" below).

A complete short-form plan: one presenter, a phone, one morning. It validates with no diagnostics. The kit, the people, the
location's light, the delivery and the day are what the short-form rules read.

```json
{
  "format": "dpl.plan/1",
  "work": {"id": "first-loaf", "title": "The first loaf", "profile": "short", "target_runtime_ms": 9000},
  "delivery": {"aspect": "9:16", "max_runtime_ms": 30000, "platform": "Instagram Reels"},
  "kit": {
    "cameras": [{"id": "phone", "kind": "phone", "description": "Main phone", "lens_multipliers": [0.5, 1, 3], "frame_rates": [24, 30, 60]}],
    "supports": ["handheld", "tripod"],
    "sound": ["phone_mic", "lav"],
    "lights": ["reflector"]
  },
  "people": [
    {"id": "maya", "name": "Maya", "roles": ["talent"], "character": "MAYA", "available": [{"from": "08:30", "to": "11:00"}]},
    {"id": "sam", "name": "Sam", "roles": ["camera", "sound"]}
  ],
  "characters": [{"id": "MAYA", "description": "Maya, who runs the bakery."}],
  "locations": [{
    "id": "FRONT", "description": "The bakery's front step in the morning.",
    "setting": "outdoor", "usable_light": [{"from": "07:30", "to": "16:30"}],
    "sun": "Low from the south-east until about 10:00", "acoustics": "quiet"
  }],
  "scenes": [{
    "id": "S1", "purpose": "Maya shows the first loaf of the day.", "start_ms": 0, "end_ms": 9000, "location": "FRONT",
    "provenance": {"by": "human"},
    "lines": [{"id": "L1", "speaker": "MAYA", "to": "camera", "start_ms": 500, "end_ms": 6000, "text": "First loaf of the day."}],
    "shots": [
      {
        "id": "S1A", "purpose": "Maya says it to the camera.", "subject": "MAYA", "start_ms": 0, "end_ms": 6000,
        "camera": {"size": "medium", "movement": "hold", "lens_multiplier": 1, "support": "tripod", "device": "phone", "orientation": "portrait"},
        "source": "footage", "characters": ["MAYA"], "coverage": {"role": "single", "priority": "must"}, "setup": "A",
        "filming": {"speech": true},
        "provenance": {"by": "human"}
      },
      {
        "id": "S1B", "purpose": "The loaf, close.", "subject": "The loaf on the board.", "start_ms": 6000, "end_ms": 9000,
        "camera": {"size": "detail", "movement": "hold", "lens_multiplier": 3, "support": "tripod", "device": "phone", "orientation": "portrait"},
        "source": "footage", "coverage": {"role": "insert", "priority": "should"}, "setup": "B",
        "provenance": {"by": "human"}
      }
    ]
  }],
  "schedule": {"days": [{
    "id": "day1", "date": "2026-10-24", "start": "09:00", "end": "10:00", "locations": ["FRONT"],
    "setups": [{"id": "A"}, {"id": "B", "not_before": "09:20"}],
    "tasks": [{"id": "tone-front", "kind": "room_tone", "location": "FRONT"}]
  }]}
}
```

**Clock times** are `"HH:MM"`, 24-hour, with two digits each (`"09:30"`, never `"9:30"` or `"9.30am"`); anything else is
`DPL-CLOCK`. A window (`from`, `to`) is a span of clock time on a shoot day; with a `day` (a day id of the schedule) it applies
on that day, without one on every day.

**Work.** `work.profile` is `film` (the default) or `short`. A plan with short-form members and no `short` profile gets one
`DPL-PROFILE` warning and nothing else: the members are read and kept, and no short-form rule runs.

**Delivery** (`delivery`)

| Member | Type | Meaning |
|---|---|---|
| `aspect` | **required** one of `9:16`, `16:9`, `1:1`, `4:5` | The picture shape the work is delivered in. A vertical frame (`9:16`, `4:5`) is filmed with the camera upright, a `16:9` frame sideways, and a `1:1` frame is cut from either. |
| `max_runtime_ms` | whole number | The longest the finished work may run, in milliseconds (a platform's limit). Must be above 0. |
| `platform` | text | Where it is posted (free text). |

Real crews are served as well as phones: a camera is a phone (lenses as multipliers) or a camera (lenses in millimetres). A kit
that lists nothing for a thing (a camera with no lenses, no supports, no frame rates) says nothing about it, so the check for
that thing does not run, and `dpl missing` asks.

**Kit** (`kit`)

| Member | Type | Meaning |
|---|---|---|
| `cameras` | list of `KitCamera` | The phones and cameras the crew has. |
| `supports` | list of one of `handheld`, `gimbal`, `tripod`, `clamp`, `slider`, `shoulder`, `dolly`, `jib`, `crane`, `steadicam`, `drone` | What can carry the camera. Hands are always there for a handheld shot. `handheld`, `shoulder`, `gimbal` and `steadicam` need an operator holding the camera; the rest stand on their own. |
| `sound` | list of one of `phone_mic`, `lav`, `shotgun`, `recorder` | The sound gear. |
| `lights` | list of one of `none`, `practical`, `led_panel`, `reflector` | The lights. `none` says there are none and cannot be listed with another. |

**KitCamera** (`kit.cameras[]`)

| Member | Type | Meaning |
|---|---|---|
| `id` | **required** text | A short id, unique among the kit's cameras. A shot's `camera.device` names it. |
| `kind` | **required** one of `phone`, `camera` | A `phone` has `lens_multipliers`; a `camera` (mirrorless, DSLR, cinema) has `lenses`. |
| `description` | text | The device in words ("iPhone 15", "Sony FX3"). |
| `lens_multipliers` | list of number | For a phone: its lenses as multipliers of the main camera, for example `[0.5, 1, 3]`. |
| `lenses` | list of `KitLens` | For a camera: its lenses. |
| `crop_factor` | number | For a camera: the sensor's crop factor against full frame (1 full frame, 1.5 APS-C, 2 Micro Four Thirds). Used to say what a focal length frames like. |
| `sensor` | text | For a camera: the sensor in words ("full frame", "Super 35"). |
| `frame_rates` | list of number | The frame rates it records, in frames per second. |

**KitLens** (`kit.cameras[].lenses[]`)

| Member | Type | Meaning |
|---|---|---|
| `mm` | number | A prime lens's focal length in millimetres. |
| `min_mm` | number | A zoom's shortest focal length in millimetres. Give `min_mm` and `max_mm` together. |
| `max_mm` | number | A zoom's longest focal length. |

A lens is a prime (`mm`) or a zoom (`min_mm` and `max_mm`), never both and never neither (`DPL-KIT`).

**Camera terms for the profile.** A shot's `camera` has, besides the DPL terms of section 4: `lens_multiplier` (a phone's lens,
for example `3`) or `focal_mm` (a camera's lens in millimetres), never both; `support` (what carries the camera, from the kit's
supports); `device` (a kit camera's `id`, so the shot is checked against that camera's lenses; any camera that can film it
otherwise); `frame_rate` (frames per second, for a rate that is not the usual one, such as slow motion); and `orientation`
(`portrait`, `landscape` or `square`: how the camera is held, which decides the frame's shape). The industry vocabulary
(section 4) has no term for a support, an orientation or a multiplier. It does describe lenses by focal length (a wide-angle lens
is 18 to 35 mm on the 35 mm frame it measures against), so `dpl shot-list` names a `focal_mm` lens with the nearest MovieLabs
lens term, as an approximate match, after the crop factor; the vocabulary's one other overlap, Gimbal Shot as a synonym of Steadicam
Shot, is why a gimbal is accepted for the `steadicam` move.

People are the cast and crew, each with roles and the times they are there.

**Person** (`people[]`)

| Member | Type | Meaning |
|---|---|---|
| `id` | **required** text | A short id, unique among the people. |
| `name` | text | Their name. |
| `roles` | **required** list of one of `talent`, `camera`, `sound`, `director`, `helper` | What they do on the shoot. At least one. One person may have several. |
| `character` | text | For talent: the id of the character they play, so a shot that shows the character needs this person. |
| `available` | list of `TimeWindow` | When they are on hand. Without any they are on hand all day, every day. |

**TimeWindow** (`people[].available[]`, `locations[].usable_light[]`, `locations[].access[]`)

| Member | Type | Meaning |
|---|---|---|
| `day` | text | The shoot day (a day id of the schedule) it applies to; every day when absent. |
| `from` | **required** text | When it starts (`"HH:MM"`). |
| `to` | **required** text | When it ends (`"HH:MM"`), after `from`. |

**Locations.** The existing `time_of_day` and `light` stay prose for the people reading the plan. The profile adds typed facts
beside them: `setting` (`indoor` or `outdoor`), `usable_light` (windows by clock time when there is light to film in; outdoors
it is the daylight, indoors say it only when daylight is what lights the room), `sun` (where the sun is, in words, if known),
`access` (windows when the crew may be at the place; without any, always), `acoustics` (`quiet`, `controllable`: the noise can be
switched off, or `fixed`: it cannot) and `quieter_position` (true when a speaker may move somewhere quieter).

The schedule is the shoot days. A shot points to its day through its `setup` (the existing grouping of shots filmed from one camera
position): the day lists the set-ups it films, in the order they are filmed. A shot whose set-up is on no day goes to the day
that uses its scene's location. Shot ids and set-up ids are the keys a later take log (`dpl.takes/1`) records takes against, so
none is made from order.

**Schedule** (`schedule`)

| Member | Type | Meaning |
|---|---|---|
| `days` | **required** list of `ShootDay` | The shoot days. At least one. |

**ShootDay** (`schedule.days[]`)

| Member | Type | Meaning |
|---|---|---|
| `id` | **required** text | A short id, unique among the days. A window's `day` names it. |
| `date` | text | The calendar date, `YYYY-MM-DD`, when it is known. |
| `start` | **required** text | When the day starts (`"HH:MM"`). |
| `end` | **required** text | When it ends (`"HH:MM"`), after `start`. |
| `locations` | list of text | The locations the day uses (location ids). |
| `setups` | list of `DaySetup` | The set-ups filmed this day, in the order they are filmed. |
| `moves` | list of `Move` | How long it takes to travel between two locations (either way). |
| `tasks` | list of `CaptureTask` | Filming that makes no picture: room tone, a voiceover. |

**DaySetup** (`schedule.days[].setups[]`)

| Member | Type | Meaning |
|---|---|---|
| `id` | **required** text | A set-up id: the `setup` of the shots filmed from it. A set-up is filmed on one day. |
| `not_before` | text | The set-up cannot begin before this clock time (the place opens, a person arrives). The crew waits. |
| `at` | text | The set-up must begin at this clock time: an event that happens then, ready or not. The day is late if the crew is not. Not with `not_before`. |

**Move** (`schedule.days[].moves[]`)

| Member | Type | Meaning |
|---|---|---|
| `from` | **required** text | A location id. |
| `to` | **required** text | A location id. |
| `minutes` | **required** whole number | How long the move takes. 0 or more. |

**CaptureTask** (`schedule.days[].tasks[]`)

| Member | Type | Meaning |
|---|---|---|
| `id` | **required** text | A short id, unique among the tasks. |
| `kind` | **required** one of `room_tone`, `voiceover` | What is recorded. Room tone goes last before leaving a place. |
| `location` | **required** text | Where it is recorded (a location id). |
| `minutes` | whole number | How long it takes, when not the profile's own figure for this kind. |

`filming` on a shot says how it is filmed, when that is not what its coverage role implies.

**Filming** (`scenes[].shots[].filming`)

| Member | Type | Meaning |
|---|---|---|
| `role` | text | A timing role of the profile's timing file (`contracts/dpl/profiles/short-timing-1.json`): Shoot Plan's own role names, such as `MASTER`, `SINGLE` and `INSERT`. When absent, the shot's `coverage.role` picks one. |
| `takes` | whole number | How many takes are planned, 1 or more. The role's own number when absent; a `once` shot is one. |
| `record_ms` | whole number | The recording that must be usable, in milliseconds, when it is more than the role's minimum. Absent, it is the role's minimum or the shot's length in the film, whichever is longer. |
| `speech` | true or false | The shot records people speaking. When absent it does if a line of its scene is spoken during it. |
| `once` | true or false | The moment happens once and cannot be filmed again, so it is recorded in one go with a margin either side. |
| `preroll_ms` | whole number | For a `once` shot: how long before the moment the recording starts. The profile's figure when absent. |
| `postroll_ms` | whole number | For a `once` shot: how long after it ends the recording runs on. |
| `with` | text | A shot of this plan filmed at the same time, by a second camera. This shot takes no time of its own. |

A scene or a shot may say who or what set it: `provenance` is `{ "by": ..., "ref": ... }` with `by` one of
`human`, `draft_model`, `shoot_plan_planner`, `script_import` or `ingress`, and an optional `ref` naming what it came from (a Shoot
Plan shot id, a script line). It is inert metadata: no rule reads it. (It is not called `source`, which a shot already has: what
the picture is made from.)

**Provenance** (`scenes[].provenance`, `scenes[].shots[].provenance`)

| Member | Type | Meaning |
|---|---|---|
| `by` | **required** one of `human`, `draft_model`, `shoot_plan_planner`, `script_import`, `ingress` | Who or what set the element. |
| `ref` | text | What it came from, when that can be named. |

**How the day is worked out.** The shoot day is added up the way Shoot Plan's scheduler does it, with its own figures (the
timing file, each value citing its Shoot Plan rule and rules version): the day's preparation; then for each visit to a location a
move, a wait for access, the location setup and, before the first speech there, a sound check; each shot takes its role's
working time (its setup, then its takes, each of recording plus a pad either side, and a reset between takes); room tone and
voiceover tasks go last before leaving; and the spare time is the larger of 5 minutes and 15% of the work done (waiting
excluded). The day's committed time is the work plus the spare time, and it must fit between `start` and `end`. The order is the
plan's: set-ups as the day lists them, shots in plan order inside a set-up. Nothing searches for a better order.

**The short-form rules** (`dpl readiness`; each finding says in its message which expectation breaks, and the rules are in
`docs/capability/EDIT_READINESS.md`). They are production-feasibility findings, listed after the story and rhythm findings and
before the eye-trace and space ones. Major means the shot cannot be filmed as planned.

| Code | Severity | What it finds |
|---|---|---|
| `RDY-PEOPLE` | major | A person in the frame is not there for the whole shot; no camera operator is on hand; or one person would hold the camera and be in front of it. |
| `RDY-LIGHT` | major | A shot at an outdoor place (or one with light windows) is filmed outside its usable light. |
| `RDY-KIT` | major | A shot asks for a lens, a frame rate, a support or a camera move the kit does not have. The fix is the nearest alternative. |
| `RDY-TIME` | major, minor | A day does not fit: major when the work alone is over, minor when only the spare time is used; also a shot outside its place's access window, a late fixed start, and a shot on no day. |
| `RDY-DELIVERY` | major, minor | The work runs longer than `delivery.max_runtime_ms` (major), or a shot is held the wrong way for the aspect (minor). |
| `RDY-SHORT-SOUND` | major | Speech where the noise cannot be switched off, with only the camera's microphone and no quieter place. Mirrors Shoot Plan's sound method (test F02). |
| `RDY-SHORT-ONCE` | minor | A moment filmed once does not roll before and after it (Shoot Plan's INV-03), and no second camera has it. |
| `RDY-SHORT-DIALOGUE` | minor | A conversation lacks a wide two-shot or a single for each speaker (Shoot Plan's two-person recipe, rules r2). |
| `RDY-SHORT-TONE` | minor | Speech is filmed at a place with no room tone planned (Shoot Plan's INV-05). |

**Ask the plan what it is missing.** A short-form plan with no `kit`, `delivery`, `people` or `schedule` gets no feasibility
check at all, each rule returning without a word. `dpl missing` asks for them (kinds `kit`, `delivery`, `people`, `schedule`,
`light` and `sound`), with the answer's shape.

**From Shoot Plan.** `tools/dpl/import/shoot_plan_to_dpl.py plan.json [--intake intake.json] --out plan.dpl.plan.json` reads a
Shoot Plan plan (and, if given, the intake it was made from) and writes this profile. Every element carries provenance
`shoot_plan_planner`; each Shoot Plan shot keeps its id; minute 0 of the shoot is put on the clock at `--day-start` (09:00 by
default). What DPL has no place for is listed in an import report beside the plan, never dropped. The converted Shoot Plan examples
are in `tests/corpus/short/`.

<!-- dpl:fragment locations[] -->
```json
{"id": "FRONT", "description": "The bakery's front step in the morning.", "setting": "outdoor",
 "usable_light": [{"from": "07:30", "to": "16:30"}], "access": [{"from": "07:00", "to": "12:00"}],
 "sun": "Low from the south-east until about 10:00", "acoustics": "fixed", "quieter_position": false}
```

<!-- dpl:fragment scenes[0].shots[0].filming -->
```json
{"role": "MASTER", "takes": 1, "record_ms": 40000, "once": true, "preroll_ms": 60000, "postroll_ms": 30000, "speech": true}
```

<!-- dpl:fragment kit.cameras[] -->
```json
{"id": "cam-b", "kind": "camera", "description": "Mirrorless body", "lenses": [{"mm": 35}, {"min_mm": 24, "max_mm": 70}],
 "crop_factor": 1.5, "sensor": "APS-C", "frame_rates": [24, 25, 50]}
```


## 3. Which details make the checks work

The edit-readiness checks (`dpl readiness`) read typed facts, not prose. A check whose facts are absent **returns without a
finding**, which is not the same as passing: `not_assessed` in the findings, and `dpl missing`, say when that happened. So a
plan that wants the checks to speak must give them the details below. The rules themselves, and the corrected table of what
each one needs, are in `docs/capability/EDIT_READINESS.md`.

| Give the plan this | Where | It switches on |
|---|---|---|
| The **line of action** | a scene's `axis`, with the shots' `staging` of its two people | `RDY-AXIS`: a cut across the line. A shot's `coverage` role `neutral`, or `crosses_line`, lets a deliberate crossing pass. |
| **Who speaks to whom** | a scene's `lines`, with `to` on each (a character, or `camera`, `audience` or `group`), and `turn` on the line that turns the scene | `RDY-REACTION` (a turning line's listener is seen as it lands, or in the second after it), `RDY-SPEAKER` (a speaker is seen during their lines, a speaker's first line is not played entirely over shots that do not show them, and a turning line is not played over neither speaker nor listener), `RDY-EYELINE` (two singles in conversation do not look the same way). With an `axis` on the pair, `RDY-EYELINE` steps aside for them. A line said to `camera`, `audience` or `group` has no character listener, so none of the three expects a reaction, a listener or a pair of eyelines for it. |
| **Where people stand** | `staging` of two or more plan characters (`side`, `facing`) | `RDY-GEOGRAPHY`: the audience is shown where everyone is before the singles. `coverage` roles only refine it. It is skipped for an `entry` whose `how` is `return` or `continue`, and overridden when the scene's `attention` disorients or withholds. |
| **Which shots are the same set-up** | `setup` on the shots, or `staging` on the first | `RDY-JUMP`: two shots with the same people in frame (the staged subjects, else `characters`) at the same size from the same set-up, joined by a plain cut. |
| **What each shot is for** | `coverage` `role` and `priority` | `RDY-HOLD` (an insert, reaction, cutaway or neutral held long past its job, relative to the film's pace; `work.house_style.readiness` sets absolute limits), and `dpl replan`, which never drops a `must`. |
| **Which way things travel** | `staging` `moving` on the shots | `RDY-TRAVEL`: screen direction reversed without a neutral shot or a turn seen on screen. A scene's `travel` alone does nothing: it only sets the direction the first moving shot is held to. |
| **How props change** | `states` on shots and/or `state_changes`; at least two facts about one prop | `RDY-STATE`: a prop shown in a state the changes contradict. |
| **A change that must be seen** | a `state_changes` entry with `shown: true`, and the shots' `props` (or `states`) | `RDY-UNSEEN`: a change with no shot of the prop as it happens. |
| **A journey** | `journey` (the plan's or a sequence's), and `journey_stage` on the scenes it governs | `RDY-JOURNEY-UNANCHORED`, `-PROGRESS`, `-JUMP` and `-CARRIED`: each change of stage is a move forward along the axis, and the route arrives. A plan with sequences but no journey is not held to one. |
| **What the narration is doing** | `sound.beat_functions`, picture `kind` and `serves` on the shots | the `RDY-BEAT-*` rules below. All return at once when `beat_functions` is empty. `RDY-BEAT-PRESENTER` also needs `house_style.presenter`. |
| **The house structure** | `work.house_style.structure` | the `RDY-STRUCT-*` rules below. The sponsor and cold-open rules also read `sound.beat_functions`. |
| **A deliberate departure** | a scene's `accepts`, or `attention` with `disorient` or `withhold` | the finding is reported as overridden, with your reason, not as a fault. |

Beat functions and the house structure receive these findings (each scene can accept a code with a `why`):

| Code | Severity | Meaning |
|---|---|---|
| `RDY-BEAT-SOURCE` | major | A sourced claim, official fact or quote has no visible source in its span |
| `RDY-BEAT-ACT` | major | A new act has no chapter card |
| `RDY-BEAT-PRESENTER` | minor | The house-style presenter is absent for longer than `max_gap_ms` (default 120,000), counting from the film start and to its end, or never appears |
| `RDY-BEAT-SPONSOR` | minor | The sponsor span includes a picture outside the sponsor set |
| `RDY-BEAT-UNSERVED` | minor | No overlapping shot explicitly declares the beat id in `picture.serves` |
| `RDY-STRUCT-COLD-OPEN` | minor | Presenter appears before the house-style hold-back time |
| `RDY-STRUCT-SPONSOR-LOOP` | minor | The fixed sponsor words do not immediately follow an open-question beat; advisory only |
| `RDY-STRUCT-SPONSOR-RETURN` | minor | Picture does not change near the first spoken word after the sponsor |
| `RDY-STRUCT-SUBSCRIBE` | minor | Prompt is missing, outside its window, or over presenter/evidence |
| `RDY-STRUCT-END-CARD` | minor | Closing recommendation and subscribe construct does not span the final window |

**Ask the plan what it is missing.** `dpl missing plan.json` says, in the plan's own names, what to ask the person who
knows the film so the checks can read it ("In scene `S1`, which side of the line between MAYA and TOM is the camera on, and
where does each stand?"), with the paths the answer fills and the rules it unlocks. It exits 4 when a question blocks the
assessment of a scene. Do not invent a detail to silence a question. When the honest answer is "none", say so with `none` ("Saying no", 2.6): a scene where nobody speaks declares `spoken_lines`, a scene staged without a line of action declares `line_of_action` with its reason, and a film that goes nowhere declares `journey`. The question is then answered and no longer asked.

## 4. Camera terms and the industry vocabulary

Plans use **DPL's own camera terms** (`Terms.cs`):

| Field | Terms |
|---|---|
| `size` | `extreme_wide`, `wide`, `full`, `medium_wide`, `cowboy`, `medium`, `medium_close_up`, `close_up`, `extreme_close_up`, `detail`, `macro` |
| `angle` | `overhead`, `high`, `eye_level`, `shoulder_level`, `low`, `hip_level`, `knee_level`, `ground_level`, `worms_eye`, `dutch` |
| `movement` | `hold`, `handheld_hold`, `pan`, `whip_pan`, `tilt`, `push_in`, `pull_out`, `track`, `dolly`, `crab`, `arc`, `orbit`, `zoom`, `dolly_zoom`, `vertigo`, `handheld`, `steadicam`, `boom`, `jib`, `crane`, `pedestal`, `roll`, `aerial`, `helicopter`, `drone`, `rise`, `drift`, `descend` |
| `lens` | `fisheye`, `ultra_wide`, `wide`, `medium`, `telephoto`, `super_telephoto`, `anamorphic_ultra_wide`, `anamorphic_wide`, `anamorphic_medium`, `anamorphic_telephoto` |
| `perspective` | `objective`, `subjective`, `witness`, `inspection`, `over_the_shoulder` (DPL only) |

They follow common industry usage, and each is mapped to the **MovieLabs Creative Vocabulary** in a pinned,
versioned table (`IndustryVocabulary/`). The mapping says how close each match is:

- **exact**: the same idea (55 of the 59 DPL camera terms);
- **approximate**: the nearest industry term, with a note saying what differs: `detail` ≈ Extreme Close-Up Shot,
  `rise` and `descend` ≈ Boom Shot, `drift` ≈ Static Handheld Shot;
- **none**: the vocabulary has no term (none today).

MovieLabs' *Establishing Shot* is the one v1.0 term no DPL term maps to: in DPL, establishing is what a shot is
*for* (its purpose, or its `coverage` role), not a size. `perspective` has no MovieLabs category.

**Upgrading.** Plans store DPL terms only, so a new MovieLabs version never invalidates a plan. Adopting one means
adding a mapping class for it, checked by the tests against the published file, and listing it in
`IndustryVocabularies.All`; renderings then use it by default, and earlier versions stay selectable
(`--vocabulary movielabs@1.0`). The v1.0 mapping is checked against the pinned file
(SHA-256 `6bdbbec4…`, from the 7 September 2026 design freeze): hash, version, every slug and every term name.

## 5. Validation

The schema (`contracts/dpl/dpl.plan-1.schema.json`) describes what the reader accepts. Reading is strict: an unknown
member, a misspelt term (`"Hold"`, `"PushIn"`, `"static-shot"`), a missing required member, or a fractional millisecond is
an error, never silently defaulted or ignored.

A plan that reads is then **validated**: ids are unique and every reference resolves, times run forward and stay inside
their scene and the work, required text is not empty, and the editorial and sound rules hold. Each fault is reported with a
`DPL-*` code, an error (the plan is unusable downstream) or a warning (for a director to look at). The full list of codes,
with what each enforces, is the one table in [`contracts/dpl/README.md`](../../contracts/dpl/README.md); it is checked
against the code by a test, so this page does not repeat it.

### Reading diagnostics

Each diagnostic has five parts: the `code` (the rule), the `severity` (`error` or `warning`), the `path` (a JSON path into
the plan, such as `$.scenes[1].shots[0].purpose`), the `message` (what is wrong) and the `fix` (what to change next).
`dpl validate plan.json` prints the first four, one line each, and exits 1 when there is an error. `dpl validate plan.json --format json`
writes all five as `dpl.diagnostics/1` (schema `contracts/dpl/dpl.diagnostics-1.schema.json`):

<!-- dpl:document dpl.diagnostics-1 -->
```json
{
  "schema": "dpl.diagnostics/1",
  "plan_hash": "9f2c1e7ab04d58c3e6a1f20b97d4c85e3a6b1d0f2e8c7945a3b6d1e0f4c2a8b7",
  "valid": false,
  "summary": { "errors": 1, "warnings": 0 },
  "diagnostics": [
    {
      "code": "DPL-DUPLICATE",
      "severity": "error",
      "path": "$.scenes[2].shots[0].id",
      "message": "Shot \"SH01\" appears twice.",
      "fix": "Rename $.scenes[2].shots[0].id to an id no other shot uses, for example \"SH05\"; shot ids are unique across all scenes (first use: $.scenes[0].shots[0])."
    }
  ]
}
```

- **Act on the fix, at the path.** The fix uses the plan's own ids and paths. It names the field to add or change, the
  ids that do exist (`one of: kitchen, hall`), or the terms that are allowed (`one of: extreme_wide, wide, ...`). A
  misspelling is matched to the closest id or term, and a long list is cut to the nearest few, in a fixed order, so the
  same plan always gives the same advice. When no single id stands out, no guess is made.
- **Errors first, then validate again.** Errors make the plan unusable downstream; warnings are for a director to look at
  (the fix says how to clear them). A change can clear several diagnostics at once, or reveal others, so validate after each round.
- **`DPL-JSON` means the plan was not read.** The reader stops at the first fault (an unknown member, a misspelt term, a
  missing member, a fractional millisecond, a syntax error), so it comes alone, with the path of the value and a fix that
  names what was expected there. The one exception is a `null` where a list item or a map value should be
  (`"scenes": [null]`): every such item is reported, at its own path. The validator never sees an unread plan.
- **`plan_hash` is present only when the plan could be read.** It is the SHA-256 of the plan in its standard layout, the same
  revision hash `dpl readiness` and `dpl missing` report, so an answer can be tied to the plan it was about.
- **Messages do not change; fixes may improve.** The `message` text is stable (tests and films depend on it). The `fix` is
  advice and may be reworded; the golden file `tests/contract/VideoEditor.Dpl.Plan.Tests/Golden/diagnostics.golden.json` shows
  the current wording for eleven codes.

The other commands say what to do next in their errors too: `dpl apply` lists the finding ids, or the fix ids on that
finding, when the one you gave does not exist; `dpl replan` says which limit could not be met, by how much, and what to
relax; any input that cannot be read names what was expected there. Exit codes are unchanged.

### Slices

`dpl slice` cuts some scenes of a plan, with everything they refer to, into a smaller plan of its own, so an agent can work on
one scene without holding the whole film (`docs/dpl/DPL_CONTEXT_TOOLS.md`). A slice is an ordinary valid plan; the one thing
that marks it is `slice_of`.

**SliceOf** (`slice_of`)

| Member | Type | Meaning |
|---|---|---|
| `plan_hash` | **required** text | The revision of the whole plan the slice was cut from: SHA-256 of the whole plan in its standard layout, the same hash `dpl readiness` reports for it. |
| `scenes` | **required** list of text | The scenes the slice holds, in the whole plan's ids. |
| `shots` | **required** list of text | The shots the slice holds, in the whole plan's ids. |

<!-- dpl:fragment slice_of -->
```json
{"plan_hash": "9f2c1e7ab04d58c3e6a1f20b97d4c85e3a6b1d0f2e8c7945a3b6d1e0f4c2a8b7", "scenes": ["kitchen"], "shots": ["k1", "k2", "k3"]}
```

A slice keeps the whole film's clock (its first scene may start well after 0). Because a plan with `slice_of` is cut from a longer
film, `dpl validate` does not report the time before its first scene as a `DPL-GAP`, `RDY-JOURNEY-PROGRESS` does not say it fails to
arrive when it holds no scene at the destination, and `RDY-STATE` does not judge a prop against a state it only guessed from its first
shot (`docs/dpl/DPL_CONTEXT_TOOLS.md`). A hand-written plan never needs `slice_of`; leave it out.

## 6. Renderings

Both are derived from the plan and add nothing to it. Neither is produced from a plan with errors.

- **Shot list for people** (`dpl shot-list plan.json`): Markdown, per scene: purpose, change, attention, a shot
  table in industry names (≈ marks an approximate term), decided joins with the alternatives considered, and
  spoken lines, beat functions and picture kinds when declared.
- **Shot list for tools** (`dpl shot-list plan.json --format json`, `dpl.shot-list/1`, schema `contracts/dpl/dpl.shot-list-1.schema.json`): every shot with its DPL
  term and industry term (category, slug, name, match, note) and the vocabulary name, version and file hash; optional beat functions and picture kinds are carried with their times.

## 7. Command line

```bash
dotnet run --project tools/dpl/VideoEditor.Dpl.Cli -- validate plan.json [--format json]   # exit 0 valid, 1 invalid; json: dpl.diagnostics/1 with a fix for each
dotnet run --project tools/dpl/VideoEditor.Dpl.Cli -- shot-list plan.json [--format json] [--vocabulary movielabs@1.0]
dotnet run --project tools/dpl/VideoEditor.Dpl.Cli -- suggest-pictures plan.json [--format json]  # advisory only
dotnet run --project tools/dpl/VideoEditor.Dpl.Cli -- format plan.json         # standard layout
dotnet run --project tools/dpl/VideoEditor.Dpl.Cli -- terms                    # DPL terms and industry names
dotnet run --project tools/dpl/VideoEditor.Dpl.Cli -- readiness plan.json [--format json]   # edit-readiness findings
dotnet run --project tools/dpl/VideoEditor.Dpl.Cli -- apply plan.json findings.json <finding> <fix>
dotnet run --project tools/dpl/VideoEditor.Dpl.Cli -- replan plan.json constraints.json [--out revised.json] [--format json]   # dpl.replan/1
dotnet run --project tools/dpl/VideoEditor.Dpl.Cli -- bench tests/corpus/readiness   # the benchmark (short-form mutants included)
dotnet run --project tools/dpl/VideoEditor.Dpl.Cli -- missing plan.json [--format json]    # what to ask the human (dpl.missing/1); exit 4 when one blocks
dotnet run --project tools/dpl/VideoEditor.Dpl.Cli -- takes validate plan.json takes.json    # the take log (dpl.takes/1) against the plan; see docs/dpl/DPL_TAKES.md
dotnet run --project tools/dpl/VideoEditor.Dpl.Cli -- takes merge a.json b.json [--out merged.json]   # join two take logs by op_id; exit 6 on a conflict
dotnet run --project tools/dpl/VideoEditor.Dpl.Cli -- coverage-status plan.json takes.json [--location L] [--format json]   # "did we get it?" (dpl.coverage-status/1); exit 5 while a must-have shot has no good take
dotnet run --project tools/dpl/VideoEditor.Dpl.Cli -- draft start brief.json [--format json] [--plan-out skeleton.json]   # a valid skeleton plan, the layers to write it in, the first action (dpl.draft/1); see docs/dpl/DPL_DRAFTING.md
dotnet run --project tools/dpl/VideoEditor.Dpl.Cli -- draft next plan.json [--brief brief.json] [--format json]   # the layer reached, what it lacks, the questions, tested fixes, one next action (dpl.draft-next/1)
dotnet run --project tools/dpl/VideoEditor.Dpl.Cli -- draft gate plan.json [--format json]   # ready to shoot? pass or fail with a certificate (dpl.draft-gate/1); exit 7 on a fail
dotnet run --project tools/dpl/VideoEditor.Dpl.Cli -- schema plan                 # JSON Schema: plan | findings | shot-list | constraints | diagnostics | missing | replan | bench | takes | coverage-status | brief | draft | draft-next | draft-gate | draft-grammar | summary | diff | dispositions
dotnet run --project tools/dpl/VideoEditor.Dpl.Cli -- summary plan.json [--findings f.json] [--format json|markdown]   # the plan in about 300 words (dpl.summary/1)
dotnet run --project tools/dpl/VideoEditor.Dpl.Cli -- slice plan.json --scene S1 | --shots a,b | --sequence Q    # a sub-plan with slice_of
dotnet run --project tools/dpl/VideoEditor.Dpl.Cli -- diff old.json new.json [--old-findings f --new-findings g]   # what changed, by id (dpl.diff/1)
dotnet run --project tools/dpl/VideoEditor.Dpl.Cli -- disposition plan.json <finding-id> accepted --reason "..." --by name   # dpl.dispositions/1
dotnet run --project tools/dpl/VideoEditor.Dpl.Cli -- readiness plan.json --dispositions d.json   # remembered decisions hold while the evidence is unchanged
```

`-` reads the plan from standard input. Exit codes: 0 success, 1 invalid plan, 64 usage, 65 not UTF-8 (or JSON that is not what
the command expects, such as a malformed constraints file), 66 cannot read the file. `readiness`, `reconcile` and `bench` use 2,
`replan` uses 3 when the constraints cannot be met, `missing` uses 4 when a question blocks the assessment of a scene, `coverage-status` uses 5 while a must-have shot in scope has no good or circled take, `takes merge` uses 6 when one `op_id` names two different takes, and `draft gate` uses 7 when the plan is not ready.

## 8. Not in this version

- **Other formats.** Converting a plan to other formats is done by separate tools, not by this format.
- **Reading industry terms in.** Plans are written in DPL terms. Accepting MovieLabs terms as input (for example
  from the ingress) is not yet supported.
- **Performance and affect** (the portrayals in the earlier library) and per-shot sound are not yet modelled.
- **More real plans.** Kingdom of God's shot plan
  records movements and carriers but no framing or camera, so it cannot yet be imported without inventing them.
