open-standards/wai/extensions/direction.md
Transaction Science 8e64d7484f
All checks were successful
joule-moq-room room service / joule-moq-room · does this change touch it (pull_request) Successful in 5s
joule-moq-web browser client / joule-moq-web · does this change touch it (pull_request) Successful in 5s
wai-web reach builds / wai-web reach · does this change touch it (pull_request) Successful in 5s
wai-web reach builds / wai-web reach builds · the package of record, every output as recorded (pull_request) Successful in 2m33s
wai-web reach builds / wai-web reach builds · this tree's wai-web, every rung on its engine line (pull_request) Successful in 4m6s
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 5s
WAI conformance / does this change touch it (pull_request) Successful in 5s
WAI energy receipts / does this change touch it (pull_request) Successful in 4s
wai-ops-gpu integer operators on GPUs / does this change touch it (pull_request) Successful in 4s
wai-pack / wai-pack · does this change touch it (pull_request) Successful in 4s
wai-quantum studio and desktop / does this change touch it (pull_request) Successful in 5s
wai-web wasm32 / does this change touch it (pull_request) Successful in 4s
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 46s
joule-moq-room room service / joule-moq-room, every target (pull_request) Successful in 1m0s
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Successful in 2m2s
WAI conformance / cross-arch byte-equality · linux (pull_request) Successful in 25s
WAI energy receipts / energy receipts · linux (pull_request) Successful in 10m10s
wai-ops-gpu integer operators on GPUs / builds · no adapter is not a pass (linux) (pull_request) Successful in 1m50s
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 18m59s
wai-pack / wai-pack · linux (pull_request) Successful in 1m19s
wai-quantum studio and desktop / studio · browser (pull_request) Successful in 2m44s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Successful in 4m18s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (pull_request) Successful in 7m56s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 4m53s
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Successful in 24s
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Successful in 10s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 30s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 1m52s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 14m38s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Successful in 24m37s
wai-pack / wai-pack · linux-arm64 (pull_request) Successful in 1m27s
wai-pack / wai-pack · the floor through a media prober (pull_request) Successful in 1m2s
wai-pack / wai-pack · dependency licences (pull_request) Successful in 1m52s
wai-pack / wai-pack · independent verifier (Python) (pull_request) Successful in 6m20s
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Successful in 40m17s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 8m2s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 14m35s
WAI conformance / cross-arch byte-equality · macos (pull_request) Successful in 20s
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Successful in 46s
WAI energy receipts / energy receipts · macos (pull_request) Successful in 5m4s
wai-ops-gpu integer operators on GPUs / Metal GPU and browser WebGPU (macos) (pull_request) Successful in 2m23s
wai-pack / wai-pack · macos (pull_request) Successful in 1m6s
wai-quantum studio and desktop / desktop app · macos (pull_request) Successful in 2m16s
direction.md: the exploration record's pictures as layers and references
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-21 16:12:23 -04:00

774 lines
45 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# WAI Extension: Direction Records
> Status: Draft. Registers the record `wai.media.direction`: a person's direction of generated
> or edited video, as signed evidence bound to the output. A director, a person named by an
> Ed25519 key (as a `did:key`, the same key), signs **direction records** at four **levels** of
> control: a **storyboard** fixes the shots, each with its duration, its subjects by reference
> picture and its framing (the **shot** level), and may fix their order (the **sequence**
> level); **keyframes** fix, at stated times of a shot, a subject's box on screen, its size
> class, its screen position and its angle, relative to the subject, and a camera window, with a
> pinned interpolation between keys (the **frame** level), and **regions** hold samples of
> stated frames to a reference picture (the **pixel** level); a **sequence** fixes the edit:
> which shots, which part of each, in which order, cut or dissolved (the sequence level). Each
> record states the levels it sets. A tool that makes a video under direction signs a
> **directed output** record naming the direction records, the layout of every frame of every
> shot, the timeline of the edit and the output's frames by digest. Where the output was made
> by the pinned render of this extension from layers that `wai.generate.spectral` regenerates
> or that the verifier holds, every frame recomputes, and the checks are **exact**: each cut
> falls at its directed frame, each duration and the order are the directed ones, each subject
> is where the keyframes and their interpolation put it under a pinned integer **framing
> measure**, and each held region holds. Where a model made the frames in floating point, the
> same checks run on the layout the tool states, and the outcome is **attested** with the class
> of what stated it, never exact. Every directed element **governs** the output frames that show
> what it directed, and the record states them: the per-frame map shows which person directed
> each frame's framing, timing, held samples and place in the sequence, whoever made its
> samples, and each element maps onto a `direct` step of the contribution record and onto a
> C2PA action. WAI decides no legal question. No new transport. The reference implementation is
> the `direction` module of `wai-rs` (feature `direction`); the corpus is
> [`live-conformance/direction/`](../live-conformance/direction/), and its second
> implementation is `live-conformance/direction_verify.py`.
> Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174.
>
> Texts read for this revision, each pinned to the version named: the C2PA Technical
> Specification, version 2.4, §6.4, §18.2 (§18.2.2.1, §18.2.2.4 and §18.2.3 among it), §18.15.1,
> §18.15.2, §18.15.4.1 and §18.15.4.6; the Creator Assertions Working Group's Identity
> Assertion, version 1.2, §5.1, §5.1.2 and §6.1; the serialized schemas of OpenTimelineIO
> release 0.18.1 (`Timeline.1`, `Stack.1`, `Track.1`, `Clip.2`, `Transition.1`,
> `RationalTime.1`, `TimeRange.1`) and its transition types; the abstract of arXiv 2606.01900;
> and *Copyright and Artificial Intelligence, Part 2: Copyrightability*, a report of the United
> States Copyright Office (January 2025), its Part II.F and its Part V. A section number of one
> of those texts in this extension is that version's.
## 1. Scope
### 1.1 Why
Direction of generated video works at four levels: the pixel, the frame, the shot and the
sequence. A director decides what each shot shows and how it is framed, where a subject sits on
screen and how large, how the camera moves, which samples stay fixed, where each cut falls and
in which order the shots run. arXiv 2606.01900 states framing as functions of the subject's pose
and motion (shot size, angle and composition) and directs a virtual camera with sparse keyframes
deterministically interpolated into a trajectory. A generator then makes the samples.
The cited report concludes that outputs made with generative AI may be protected in whole or in
part where a person was able to determine the expressive elements they contain, that prompts
alone are unlikely to suffice at this stage, and that the questions are decided case by case
(its Part V); and that a person may select, coordinate and arrange generated material, or modify
it, in a way that contributes authorship (its Part II.F). Whether a director's choices are such
a contribution is a question for a registration authority or a court, under its own rule.
This extension makes the director's choices signed facts, bound to the output they governed, and
checks them: exactly, frame by frame, where the output recomputes; as the tool's statement, with
its class, where it does not. It states, for every frame, who directed what. It does not say what
any of that makes anyone.
### 1.2 In scope and out of scope
In scope: the records and their encodings (§2); the levels and the elements of direction (§3);
the storyboard (§4); keyframes, their interpolation and held regions (§5); the sequence and the
timeline arithmetic (§6); the framing measure (§7); the directed output (§8); the render (§9);
governance, the per-frame map and the contribution record's `direct` act (§10); the mappings
onto C2PA actions, identity assertions and an open timeline interchange format (§11);
verification (§12).
Out of scope:
- **Legal questions.** WAI decides none. A record states which person directed which frames at
which level, and how much of that a verifier checked; a reader applies its own rule to those
facts. Registering a work, and contacting any authority about it, are its owner's decisions.
- **How a generator follows direction.** A model conditioned on a storyboard, a keyframe or a
pose is the tool's; this extension checks what came out.
- **Measuring a subject in arbitrary pixels.** Where the subject is a known layer, the render of
§9 places it and its box is exact. A detector or tracker that finds a subject in a generated
frame is a method of the tool's, and what it states is attested with its class (§8.1).
- **Audio** and its direction, and direction of a live room's composition, which
[live-compose](live-compose.md) lays out from the room's events.
- **Camera paths in world space.** The camera of this revision is a window over a plate (§5.1);
a path through a scene is a later revision's.
### 1.3 Relationships
Relationship to the core spec: `wai.media.direction` is a **record** ([SPEC](../SPEC.md) §5):
signed records a WAI tool emits and verifies. It names no payload format and is not
dispatchable.
Relationship to `wai.generate.spectral`: a layer of a directed output may be a picture that
generator makes; a verifier holding the model file regenerates it (SPEC §5, *Payload of
`wai.generate.spectral`*).
Relationship to [deriving-edge](deriving-edge.md): the render of §9 places layers as that
extension's `wai.derive.overlay_composition` does, opaquely at integer offsets, and resamples by
the nearest sample, the reference rule of its `wai.derive.scaling` (§4.2 there), which this
extension pins for its own render.
Relationship to [synthesis](synthesis.md): a directed output's origin claim is that extension's;
direction changes no class. A video whose layers or frames a model generated is synthesized,
however closely a person directed it.
Relationship to the contribution record, `wai.media.contribution`: that record reserves the act
`direct` for this extension. §10.3 defines the step, its parameters and its dependency rule.
Relationship to the exploration record, `wai.media.exploration`: a subject's view, a shot's
reference or a plate may be a picture an exploration archive holds, chosen by a pick; this
extension names it by its samples digest, and the archive and the pick are that record's.
## 2. Records
Every record of this extension is a JSON object (RFC 8259) under the encodings of
[jwp-receipts](jwp-receipts.md) §2.4.3, read as synthesis §2 reads a record: strict JSON, no
member named twice, at most 32 levels of nesting, byte strings in lowercase hexadecimal,
integers unsigned and at most 2^53 − 1, no other number, no boolean, **terms** and
**date-times** as defined there. Each record has exactly the members its section lists, no
member missing and none added, at any level, and a `kind` naming it; one that does not is
`record_malformed`. So is a member outside the values or ranges its section gives, or one that
breaks a rule its section states of the record alone.
Every record carries `signer_pubkey` (32 bytes, an Ed25519 key of RFC 8032) and `sig` (64
bytes):
```
sig = Ed25519(signer_key, "wai:direction\x01" || JCS(record without "sig"))
link_hash = BLAKE3("wai:direction-link\x01" || JCS(record))
```
JCS is RFC 8785. The signature is verified strictly ([SPEC](../SPEC.md) §7 *Signatures*): a key
that fails step 1 or 2 is `bad_key`, and a signature that fails a later step `bad_signature`. A
record is named by its link hash wherever another names it. `issued_at` holds the instant its
signer signed it, self-asserted.
Values used throughout:
- A **signed integer** is a string: `0`, or an optional `-` followed by one to seven of the
digits `0`–`9` of which the first is not `0` (no `+`, no space, no `-0`), with a value from
−1 048 576 to 1 048 576. The encodings admit no negative number, and a subject's box may
begin left of or above the frame.
- A **box** is `{"x", "y", "w", "h"}`: `x` and `y` signed integers, the box's left and top edge
in pixels from the frame's top-left corner; `w` and `h` integers from 1 to 1 048 576. It may
extend past any edge of the frame.
- A **rect** is `{"x", "y", "w", "h"}`, four integers, `w` and `h` at least 1: a rectangle of
pixels within a picture, `x` + `w` at most its width and `y` + `h` at most its height, checked
where the picture is known (§8.5, §12.2 step 2).
- A **picture** is `width` × `height` pixels as the `RGB8` buffer of
[reconstruction-binding](reconstruction-binding.md) §2 (row-major, three bytes a pixel, no row
padding), each side from 1 to 4096 and `width` · `height` at most 2^23, the sizes
`wai.generate.spectral` makes. Its **samples digest** is SHA-256 (FIPS 180-4) over the
buffer. A **picture reference** is `{"samples", "width", "height"}`: a samples digest and the
size it is of. A pixel is addressed (*x*, *y*), *x* to the right and *y* down from the
top-left.
- A **frame range** is a pair `[a, b]` of integers, *a* < *b*: the frames *a* to *b* − 1. A
**frame set** is an array of frame ranges in ascending order, each beginning after the one
before it ends and not where it ends (`b` of one below `a` of the next): its one encoding.
- ⌊*a* / *b*⌋, for *b* above 0, rounds toward negative infinity. Every quantity of this
extension is an integer; none needs more than 127 bits with its sign.
## 3. Levels and elements
A **level** is one of `pixel`, `frame`, `shot` and `sequence`, in that order. Each direction
record has a member `levels`: the levels it sets, in that order, none twice. A record whose
`levels` is not exactly the levels its members set (§4, §5, §6) is `record_malformed`.
An **element** is one directed choice of a record, named by a **path**:
| level | element | path | what it fixes | its record |
|---|---|---|---|---|
| `shot` | a shot | `["shot", i]` | the shot's duration, its subjects and the framing it opens on | storyboard (§4) |
| `frame` | a shot's keys | `["keyframes", e]` | each subject's box and classes, and the camera, at each key and between keys | keyframes (§5) |
| `pixel` | a held region | `["region", j]` | the samples inside rects of stated frames | keyframes (§5.3) |
| `sequence` | the order and cuts | `["sequence"]` | which shots in which order, which part of each, each cut and dissolve | storyboard (§4) or sequence (§6) |
*i* is a shot's position in the storyboard, *e* an entry's and *j* a region's position in their
keyframes record. A record's **elements** are, in this order: a storyboard's shots by position,
then `["sequence"]` where it sets that level; a keyframes record's entries by position, then its
regions by position; a sequence record's `["sequence"]`.
## 4. The storyboard
A director signs a **storyboard**, `kind` `"wai-storyboard"`:
| member | type | meaning |
|---|---|---|
| `kind` | string | `"wai-storyboard"` |
| `levels` | array | `["shot"]`, or `["shot", "sequence"]` when `order` is not `null` |
| `width`, `height` | integer | every frame's size: a picture's sides (§2) |
| `rate` | object | `{"num", "den"}`, each from 1 to 1 048 576: frames per second, as *num* / *den* |
| `subjects` | array | 0 to 16 subjects (§4.1) |
| `shots` | array | 1 to 256 shots (§4.2) |
| `order` | array or `null` | 1 to 1024 shot positions, in the order the shots run, each a shot whole, joined by cuts; `null` where the storyboard does not fix an order |
| `issued_at` | date-time | when the director signed it |
| `signer_pubkey`, `sig` | | §2: the director's key |
### 4.1 Subjects
A **subject** is `{"views"}`: 1 to 8 **views**, each `{"angle", "samples", "width", "height"}`,
a term naming the angle the subject is seen from and a picture reference: the subject's
reference picture as seen from that angle, its whole extent edge to edge. No angle appears twice
in one subject. Angles are the director's terms; `front`, `three-quarter`, `profile`, `back`,
`high` and `low` are examples, not a list. A subject is named by its position.
### 4.2 Shots
A **shot** is `{"duration", "subjects", "framing", "reference"}`:
- `duration`: its number of frames, from 1 to 65 536;
- `subjects`: 0 to 16 subject positions, none twice, in drawing order, the first drawn first;
- `framing`: one entry per subject of `subjects`, in its order: `{"size", "position",
"angle"}`, each `null` or a class: `size` and `position` the classes of §7, `angle` an angle
of that subject's views. It is the framing the shot opens on: the measure of its first frame
(§7);
- `reference`: a picture reference (§2), a reference picture for the shot, or `null`.
A position in `subjects` or `order` that is not a subject's or a shot's, or an `angle` that is
not one of the subject's views', is `record_malformed`.
## 5. Keyframes
A director signs a **keyframes** record, `kind` `"wai-keyframes"`:
| member | type | meaning |
|---|---|---|
| `kind` | string | `"wai-keyframes"` |
| `storyboard` | 32 B hex | the link hash of the storyboard it directs |
| `levels` | array | `["frame"]` when only `keyframes` is not `null`, `["pixel"]` when only `regions` is not, `["pixel", "frame"]` when neither is `null` |
| `keyframes` | array or `null` | 1 to 256 entries, each `{"shot", "keys"}`: a shot's position and 1 to 1024 keys (§5.1); no shot twice |
| `regions` | array or `null` | 1 to 256 held regions (§5.3) |
| `issued_at` | date-time | when the director signed it |
| `signer_pubkey`, `sig` | | §2: the director's key |
A record whose `keyframes` and `regions` are both `null` is `record_malformed`.
### 5.1 Keys
A **key** is `{"t", "interp", "camera", "subjects"}`:
- `t`: the frame of the shot it is at, from 0; the keys of an entry in strictly ascending `t`;
- `interp`: `hold`, `linear` or `ease`: how its values move to the next key's (§5.2);
- `camera`: `null`, or a rect: the **camera window**, the part of the shot's plate the frame
shows, scaled to the frame (§9.1);
- `subjects`: one entry per subject of the shot, in the shot's order, each `{"box", "size",
"position", "angle"}`: `box` `null` or a box (§2), the subject's whole extent on screen, actor
relative: where the subject is and how large; `size`, `position` and `angle` each `null` or a
class (§7).
Within an entry, every key's `subjects` has one length, and for each subject either every key
gives a box or none does; likewise the camera. Otherwise `record_malformed`.
A subject's boxes, and the cameras, are **tracks**. A track begins at the entry's first key.
The classes of a key are checked at its `t` alone.
### 5.2 Interpolation
A track's value at frame *t* of the shot, from the first key's `t` to the shot's last frame, is
computed member by member (`x`, `y`, `w`, `h`): for *t* from a key *k* (at *t*ₖ, value *v*ₖ)
up to the next key (at *t*ₖ₊₁, value *v*ₖ₊₁), with *n* = *t* − *t*ₖ and *D* = *t*ₖ₊₁ −
*t*ₖ, by key *k*'s `interp`:
| `interp` | value |
|---|---|
| `hold` | *v*ₖ |
| `linear` | *v*ₖ + ⌊(*v*ₖ₊₁ − *v*ₖ) · *n* / *D*⌋ |
| `ease` | *v*ₖ + ⌊(*v*ₖ₊₁ − *v*ₖ) · *n*² · (3*D* − 2*n*) / *D*³⌋ |
From the last key to the shot's end the value is the last key's. `ease` is the cubic that
starts and ends with zero speed, *s*²(3 − 2*s*) of *s* = *n* / *D*, in integers. A value lies
between its two keys' values, so `w` and `h` stay at least 1 and a camera window between two
windows inside the plate stays inside it.
### 5.3 Held regions
A **held region** is `{"shot", "from", "to", "rects", "source"}`: a shot's position; its frames
`from` up to, not including, `to` (`from` < `to`); 1 to 64 rects within the frame; and a picture
reference of the frame's size, the **source**. In those frames, every pixel inside a rect is the
source's pixel at the same position. A region holds a reference picture, a drawn matte, a fixed
element or a locked background to exact samples, whatever made the rest of the frame.
## 6. The sequence
A director (an editor directs the sequence) signs a **sequence**, `kind` `"wai-sequence"`:
| member | type | meaning |
|---|---|---|
| `kind` | string | `"wai-sequence"` |
| `storyboard` | 32 B hex | the link hash of the storyboard it edits |
| `levels` | array | `["sequence"]` |
| `events` | array | 1 to 1024 events (§6.1) |
| `issued_at` | date-time | when the director signed it |
| `signer_pubkey`, `sig` | | §2: the director's key |
### 6.1 Events
An **event** is `{"shot", "in", "out", "transition"}`: a shot's position; the frames of the shot
it shows, `in` up to, not including, `out` (`in` < `out` ≤ 65 536), its **length** *L* = `out`
− `in`; and the transition into it: `{"kind": "cut"}`, or `{"kind": "dissolve", "frames"}`, a
dissolve over `frames` ≥ 1 frames. With *d*ᵢ the dissolve frames into event *i* (0 for a cut):
the first event's transition is a cut, and *d*ᵢ + *d*ᵢ₊₁ ≤ *L*ᵢ for every event *i* (with
*d* = 0 past the last), so no frame shows more than two events. Otherwise `record_malformed`.
A storyboard's `order` is the events `{"shot": i, "in": 0, "out": duration of i, "transition":
{"kind": "cut"}}`, one for each position *i* of `order`, in its order.
### 6.2 The timeline
Events run in order. Event *i* **starts** at output frame *S*ᵢ: *S*₀ = 0 and *S*ᵢ = *S*ᵢ₋₁ +
*L*ᵢ₋₁ − *d*ᵢ, and occupies the output frames *S*ᵢ up to, not including, *S*ᵢ + *L*ᵢ. The
output has *N* = *S*ₘ₋₁ + *L*ₘ₋₁ frames, *m* the number of events. A dissolve into event *i*
overlaps the last *d*ᵢ frames of event *i* − 1 with the first *d*ᵢ of event *i*.
Output frame *f* **shows** frame `in` + (*f* − *S*ᵢ) of event *i*'s shot for each event *i* that
occupies it: one event, or two in a dissolve. The **cuts** are *S*₁, …, *S*ₘ₋₁: the frames where
each event after the first begins to show, cut or dissolved.
## 7. The framing measure
The **framing measure** of a subject in a frame of `width` *W* and `height` *H* is computed from
its box (*x*, *y*, *w*, *h*) and the angle of the view it is drawn from:
- **size**: with *q* = ⌊1000 · *h* / *H*⌋, the subject's height in thousandths of the frame's:
| class | *q* |
|---|---|
| `extreme_wide` | below 250 |
| `wide` | 250 to 699 |
| `full` | 700 to 1099 |
| `medium_wide` | 1100 to 1499 |
| `medium` | 1500 to 2199 |
| `medium_close` | 2200 to 3199 |
| `close` | 3200 to 4999 |
| `extreme_close` | 5000 or more |
The box is the subject's whole extent, so a close shot's box runs far past the frame. The
bounds are this measure's: a director's vocabulary maps onto them, and they define no
craft's terms.
- **position**: the cell of a three-by-three grid holding the box's centre: column *c* = min(2,
max(0, ⌊3 · (2*x* + *w*) / (2*W*)⌋)) and row *r* = min(2, max(0, ⌊3 · (2*y* + *h*) /
(2*H*)⌋)), named `R-C` with *R* `top`, `middle` or `bottom` for *r* 0, 1, 2 and *C* `left`,
`centre` or `right` for *c* 0, 1, 2: `top-left` … `middle-centre` … `bottom-right`.
- **angle**: the angle of the view.
A directed class **matches** when the measure gives it. A directed box matches when it equals the
subject's box member for member; a directed camera window when it equals the frame's window.
## 8. The directed output
### 8.1 The record
The tool that made the output signs the **directed output**, `kind` `"wai-directed"`:
| member | type | meaning |
|---|---|---|
| `kind` | string | `"wai-directed"` |
| `direction` | array | 1 to 64 link hashes of the direction records it was made under, the storyboard first; none twice |
| `directors` | array | 1 to 64 keys (32 B hex), the people who directed it; none twice, none the tool's key |
| `check` | string | `replicate` or `attest` |
| `attestation` | object or `null` | `null` for `replicate`; for `attest`, `{"generator", "class"}`: a term naming what made the frames, and a term naming the class of method that stated the layout (a tracker, a detector, the generator's own conditioning) |
| `layers` | array | 0 to 256 layers (§8.2) |
| `clips` | array | 1 to 256 clips (§8.3) |
| `timeline` | array | 1 to 1024 events (§6.1): the edit as made |
| `output` | object | `{"width", "height", "frames", "digest", "content_hash"}`: the output's frame size, its number of frames (1 to 2^20), the digest of its frames (§8.4), and the content hash (jwp-receipts §2.1) of the object it was published as, or `null` |
| `governs` | array | 0 to 1024 entries (§10.1) |
| `issued_at` | date-time | when the tool signed it |
| `signer_pubkey`, `sig` | | §2: the **tool's key** |
`replicate` states that every frame is the render of §9 over the layers; `attest` that something
else made the frames and the layout is the tool's statement, of the class it names. The record
reads only if its timeline does (§6.1).
### 8.2 Layers
A **layer** is a picture reference with its source, `{"samples", "width", "height",
"source"}`; no samples digest twice. Its `source` is one of:
- `{"generator", "model", "prompt", "seed", "steps"}` with `generator`
`"wai.generate.spectral"`: the picture that generator makes with the model file whose SHA-256
is `model` (32 B hex), the prompt (0 to 256 bytes, each from 0x20 to 0x7E other than `"` and
`\`), the seed (0 to 2^32 − 1) and the steps (1 to 64), at the layer's size: SPEC §5,
*Payload of `wai.generate.spectral`*, `generate(model, prompt, seed, steps, height, width)`;
- `{"generator", "model"}`, `generator` any other term: a picture another generator made, with
the model whose digest is `model`;
- `null`: a picture given as it is, such as a person's drawing.
A layer is named by its samples digest wherever a clip or a direction record names a picture.
### 8.3 Clips
A **clip** is the layout of every frame of one shot as made, `{"frames", "plate", "camera",
"subjects", "holds"}`; the clips are the storyboard's shots, in order:
- `frames`: its number of frames, from 1 to 65 536;
- `plate`: the samples digest of the picture the frames are drawn over, or `null` (§8.5);
- `camera`: `null`, or one rect per frame: the camera window over the plate;
- `subjects`: 0 to 16 entries, in drawing order, each `{"subject", "views", "boxes"}`: a
subject's position; one view position per frame (the view drawn); and one box per frame;
- `holds`: 0 to 64 entries `{"from", "to", "rects", "source"}`: frames `from` up to, not
including, `to` (`from` < `to` ≤ `frames`), 1 to 64 rects and a samples digest: the regions
held, as made.
A `camera`, `views` or `boxes` whose length is not `frames` is `record_malformed`.
### 8.4 The frames' digest
```
digest = BLAKE3("wai:direction-frames\x01" || D_0 || D_1 || … || D_{N−1})
```
with *D*ₖ the samples digest of output frame *k*, `width` × `height` `RGB8` (§2), in order. A
frame is named by its samples digest, as a frame of a `wai.video.int_motion` clip is checked by
its state.
### 8.5 Layout rules
Against the storyboard, a directed output whose layout breaks one of these is `layout_invalid`,
or `layer_missing` where a picture it needs is not one of its layers:
1. There is one clip per shot of the storyboard.
2. Each clip's subjects are subjects of the storyboard, and each view position is one of that
subject's views.
3. For `replicate`: each clip's `plate` is not `null`, and is a layer (`layer_missing`); with no
camera, the plate
is the frame's size; each camera window is a rect within the plate; each view drawn is a
layer of the size the storyboard states for that view (`layer_missing` where it is not a
layer). For `attest`: each `plate` is `null`.
4. Each hold's rects are within the frame; for `replicate` its source is a layer
(`layer_missing`) of the frame's size.
5. Each timeline event names a shot of the storyboard and `out` is at most its clip's
`frames`.
6. The output's `width` and `height` are the storyboard's, and its `frames` is the timeline's
*N* (§6.2).
## 9. The render
The render is pinned: every byte of every frame it makes is a pure function of the layers and
the record, integer throughout.
### 9.1 A clip's frame
Frame *t* of a clip, `width` *W* by `height` *H*, is drawn in this order:
1. **The plate.** With no camera, out(*X*, *Y*) = plate(*X*, *Y*). With the camera window
(*cx*, *cy*, *cw*, *ch*) of frame *t*, out(*X*, *Y*) = plate(*cx* + ⌊*X* · *cw* / *W*⌋, *cy* +
⌊*Y* · *ch* / *H*⌋): the window resampled to the frame by the nearest sample.
2. **The subjects**, in the clip's order. With the box (*bx*, *by*, *bw*, *bh*) of frame *t* and
the view *V* of frame *t*, *vw* × *vh*: every pixel (*X*, *Y*) of the frame with *bx* ≤ *X*
< *bx* + *bw* and *by* ≤ *Y* < *by* + *bh* is *V*(⌊(*X* − *bx*) · *vw* / *bw*⌋, ⌊(*Y* −
*by*) · *vh* / *bh*⌋): the view scaled to its box by the nearest sample and drawn opaquely,
the part off the frame dropped.
3. **The holds**, in order: for each hold whose frames include *t*, every pixel inside each of
its rects is the source's pixel at the same position.
### 9.2 An output frame
Output frame *f* that shows one event is that event's clip frame (§6.2). One that shows events
*i* − 1 and *i*, in a dissolve of *d* = *d*ᵢ frames, is a blend of their clip frames *A* and *B*:
with *j* = *f* − *S*ᵢ + 1 (1 to *d*), each byte is
```
out = ⌊(A · (d + 1 − j) + B · j + ⌊(d + 1) / 2⌋) / (d + 1)⌋
```
so *B*'s weight rises by 1/(*d* + 1) each frame and neither picture is ever shown alone inside
the dissolve.
## 10. Governance
### 10.1 What an element governs
An element (§3) **governs** the output frames that show what it directed, on the timeline the
directed output states:
| element | the output frames it governs |
|---|---|
| `["shot", i]` | each frame that shows any frame of shot *i* |
| `["keyframes", e]` | each frame that shows a frame *t* of the entry's shot with *t* in its **keyed frames**: from its first key's `t` to the shot's end where it has a track, and otherwise the `t` of each key that gives a class |
| `["region", j]` | each frame that shows a frame of the region's shot from `from` up to, not including, `to` |
| `["sequence"]` | every output frame |
Within those frames, a region governs the samples inside its rects, and every other element every
sample. A frame that shows two events is governed by what governs either.
The directed output states this in `governs`: one entry for each element of each record it
names, in the order of `direction` and then of each record's elements (§3), each `{"record",
"element", "frames"}`: the record's position in `direction`, the element's path and the frame
set (§2) it governs, `[]` for none. An entry that is not that element's, or not its frame set, is
`governance_mismatch`. So a tool cannot claim for a director frames the director's choice did not
govern, and cannot leave out frames it did.
### 10.2 The per-frame map
The **frame map** gives each output frame: the frames it shows, `[shot, t]` for each event
occupying it (§6.2); and `directed_by`, an object of four members, `pixel`, `frame`, `shot` and
`sequence`, each the positions in `directors` of the signers of the elements of that level that
govern it, ascending, none twice. It shows, frame by frame, which person directed the framing,
the timing, the held samples and the order, beside whatever made the frame's samples: the
layers' sources (§8.2) for `replicate`, the attested generator for `attest`.
### 10.3 The contribution record's `direct` act
The contribution record, `wai.media.contribution`, reserves the act `direct` for this
extension. Each element that governs at least one frame becomes one step of that record's session:
| member of the step | value |
|---|---|
| `party` | the director: a `human` party whose key is the element's record's signer |
| `act` | `direct` |
| `inputs` | none |
| `output`, `region` | `null` |
| `params` | `{"record", "element", "frames"}`: the direction record's link hash, the element's path, and the frame set it governs (§10.1) |
| `check` | `replicate` when the directed output's `check` is `replicate`, so every check of §12.2 the element has ran on recomputed frames; `attest` otherwise |
| signer | the director |
Its dependency rule: the step introduces no sample and changes none; it adds the director to
`directed_by` of every sample the element governs (§10.1), in every frame of `frames`, and
leaves `made_by` as it is. A verifier of the contribution record holding the direction records and
the directed output recomputes `frames` by §10.1 and refuses a step whose `frames` differ, as
§12.2 refuses a `governs` entry. So the authorship map shows, per frame, that a person directed
framing and timing where a model made the samples. The contribution record's revision of
2026-10-21 maps still pictures only; the step applies, sample by sample, in the revision that maps
video frame by frame, and to a still picture as an output of one frame.
## 11. Mappings
### 11.1 C2PA actions
For each director with at least one element that governs a frame, an accepted case gives one
C2PA statement, `{"actions": A}`, written in JCS: `A` is an actions assertion's actions (C2PA 2.4
§18.15.1), one for each of that director's elements that governs a frame, in the order of §10.1:
```
{"action": "c2pa.edited", "changes": C, "description": T}
```
- `T`, the action's description (C2PA 2.4 §18.15.4.1), by level: `"directed shot: framing,
duration and subjects"`, `"directed keyframes: framing over time"`, `"directed region: samples
held to a reference"` and `"directed sequence: order, cuts and transitions"`.
- `C`, its `changes` (C2PA 2.4 §18.15.4.6): one region map (§18.2) for each frame range of the
element's frame set, in order, each `{"region": [{"type": "frame", "frame": {"start": a,
"end": b − 1}}]}`, a frame range of §18.2.2.4, whose end is inclusive. For a held region, one
region map for each frame range and each rect, by range and then by rect, whose `region`
holds that frame range and then `{"type": "spatial", "shape": {"type": "rectangle", "unit":
"pixel", "origin": {"x": X, "y": Y}, "width": W, "height": H}}`, the rect, the coordinates as
integers (a manifest in CBOR writes them as the floats §18.2.3 types them).
C2PA 2.4 §18.2.2.4 recommends temporal ranges for media over time. Direction is exact in frames,
and a frame's time at a rate such as 24000/1001 has no exact decimal form, so the statement uses
frame ranges; a manifest that also wants times derives them from `rate`.
Each director's statement is one actions assertion of the output's manifest, labelled by C2PA
2.4 §6.4's rule for several assertions of one type. It is never the first actions assertion,
which begins with the manifest's `c2pa.created` or `c2pa.opened` action (C2PA 2.4 §18.15.2):
the generation's, or the contribution record's statement where there is one. Embedding the
statements in a manifest, and signing it, is C2PA's.
### 11.2 Identity assertions (informative)
A director may give the output's manifest an identity assertion (Identity Assertion 1.2 §5.1,
label `cawg.identity`) whose referenced assertions include the actions assertion of §11.1 that
holds that director's actions, which §6.1 there gives as an example of an assertion that lets an
actor describe its relationship to the content. A role of §5.1.2 there is the director's own
statement; this extension derives none. The director's key in the records is an Ed25519 key, and
the relation between it and the assertion's credential is the director's to state.
### 11.3 An open timeline interchange format (informative)
A sequence (§6) maps onto OpenTimelineIO's serialized schemas, release 0.18.1: a `Timeline.1`
whose `tracks` is a `Stack.1` holding one `Track.1` of `kind` `"Video"`, whose children are, for
each event *i* in order, a `Clip.2` whose `source_range` is a `TimeRange.1` from `start_time`
`in` to a `duration` of *L*ᵢ − *d*ᵢ₊₁ frames, each a `RationalTime.1` at `rate` *num* / *den*,
naming the shot's clip as its media; and, before each event entered by a dissolve, a
`Transition.1` of `transition_type` `"SMPTE_Dissolve"` with `in_offset` 0 and `out_offset` *d*ᵢ
frames. The cut point is then *S*ᵢ, the outgoing clip's range ends there, and the dissolve
draws its last *d*ᵢ frames from past that range, as this extension's timeline shows them. An edit
decision list that records each event's source in and out points and its record in point (*S*ᵢ),
with a cut or a dissolve of a duration, carries the same values. Neither format carries the
framing of §5 or the governance of §10, and neither is signed: they are the edit's interchange,
and the direction records are its evidence.
## 12. Verification
### 12.1 Inputs
A verifier is given: the directed output record; the direction records it holds, in a list; the
model files it holds, each named by its SHA-256; the pictures it holds, each a buffer named by
its samples digest; and a **policy** of one member:
| member | values | default | meaning |
|---|---|---|---|
| `require` | `attested`, `exact` | `attested` | the lowest status it accepts (`not_exact`) |
### 12.2 Steps
The verifier runs these steps and stops at the first refusal, which it reports with its reason
and its place: `["output"]`, `["record", i]`, `["direction", k]`, `["layer", i]`, `["clip", i]`,
`["clip", i, t]`, `["timeline"]`, `["timeline", i]`, `["governs", i]` or `["policy"]`, each *i*
a position in its list (a record's among the records given, a direction record's *k* among
`direction`), and *t* a frame of a clip.
1. **Reading** (§2, §4–§6, §8): the directed output at `["output"]`, then each record given at
`["record", i]`: `record_malformed`, `bad_key`, `bad_signature`.
2. **The direction**, each link of `direction` in order at `["direction", k]`:
1. a record given has that link hash (`record_unknown`);
2. the first is a storyboard and no other is; each keyframes or sequence record's
`storyboard` is the first's link hash (`storyboard_mismatch`);
3. its signer is one of `directors` (`signer_not_party`);
4. no second record sets the sequence level, and no keyframes entry directs a shot an
earlier record's entry directs (`direction_conflict`);
5. against the storyboard, every shot, subject and angle it names exists; every key's `t` is
below its shot's duration; every key has one subject entry per subject of its shot; every
region's `to` is at most its shot's duration, its rects are within the frame and its
source is the frame's size; every event's `out` is at most its shot's duration
(`direction_inconsistent`).
3. **The check**, at `["output"]`: a `replicate` record with a layer of another generator
than `wai.generate.spectral` (§8.2) is `check_mismatch`: a float generator's picture is
never claimed exact.
4. **The layout** (§8.5): rule 1 at `["output"]`; rules 2 to 4 clip by clip at `["clip", i]`;
rule 5 event by event at `["timeline", i]`; rule 6 at `["output"]`: `layout_invalid`,
`layer_missing`.
5. **The layers**, for `replicate`, each in order at `["layer", i]`: a generated layer whose
model file the verifier does not hold, or a given layer whose buffer it does not hold, is
`layer_missing`; a held buffer that is not `width` · `height` · 3 bytes is
`samples_mismatch`; a generated layer whose picture's samples digest is not `samples` is
`regeneration_mismatch`.
6. **The frames**, for `replicate`, at `["output"]`: the verifier renders every clip frame (§9.1)
and every output frame (§9.2) and computes the digest of §8.4; another digest than
`output.digest` is `frame_mismatch`.
7. **The direction against the layout**: each element of each direction record, in the order of
§10.1, against the clips and the timeline the directed output states:
- `["shot", i]`: clip *i*'s `frames` is the shot's duration (`duration_mismatch`, at
`["clip", i]`); its subjects are the shot's, in its order (`subject_mismatch`, at
`["clip", i]`); each class of the shot's `framing` matches the measure (§7) of clip frame
0 (`framing_mismatch`, at `["clip", i, 0]`);
- `["keyframes", e]`: for each keyed frame *t* of the entry's shot in ascending order, each
subject's box is its track's value (§5.2), the camera window is the camera track's, and,
at a key, each class it gives matches the measure (`framing_mismatch`, at `["clip", s,
t]`, *s* the shot);
- `["region", j]`: the shot's clip has a hold with the region's `from`, `to`, rects and source
(`region_mismatch`, at `["clip", s]`);
- `["sequence"]`: with the directed events (§6.1), the timeline holds the same shots in the
same order (`order_mismatch`, at `["timeline"]`); then, event by event, each starts at its
directed frame (`cut_mismatch`, at `["timeline", i]`); then each has its directed
transition (`transition_mismatch`); then its directed `in` and `out`
(`duration_mismatch`).
8. **Governance** (§10.1), each entry in order at `["governs", i]`, and a missing entry at the
position it would have: `governance_mismatch`.
9. **Policy**: an `attest` record under `require` `exact` is `not_exact`, at `["policy"]`.
A sequence level no record sets is not checked: the order is then the tool's own, and no element
governs it.
### 12.3 Status
The outcome's **status** is `exact` for `replicate`: the frames recompute, so the layout the
checks of step 7 read is what the frames hold, cut for cut and pixel for pixel. It is `attested`
for `attest`, with the attestation's generator and class: the same checks ran on what the tool
stated, which nothing recomputed. An `attest` record is never reported exact.
### 12.4 What an accepted case states
- `output`: the directed output's link hash;
- `status`, `generator` and `class`: §12.3, the last two `null` when exact;
- `records`: each record of `direction`, in order, its `kind`, its link hash, its signer's
position in `directors` and its `levels`;
- `elements`: each element in the order of §10.1, its `record`, `element`, `level`, `director`
and governed `frames`;
- `cuts`: the cuts of the stated timeline (§6.2);
- `frames`: the frame map (§10.2), one entry per output frame;
- `frame_sha256`: for `exact`, the samples digest of each output frame, in order; `null`
otherwise;
- `layers`: each layer's status, in order: `regenerated` or `held` for `exact`, `stated` for
`attested`;
- `c2pa`: the statements of §11.1, each `{"director", "statement"}`, by director.
## 13. Reasons
The codes this extension adds, each with where it is raised:
| reason | raised at |
|---|---|
| `record_malformed` | §2, §3, §4, §5, §6.1, §8, §12.2 step 1 |
| `bad_key` | §2 |
| `bad_signature` | §2 |
| `record_unknown` | §12.2 step 2 |
| `storyboard_mismatch` | §12.2 step 2 |
| `signer_not_party` | §12.2 step 2 |
| `direction_conflict` | §12.2 step 2 |
| `direction_inconsistent` | §12.2 step 2 |
| `check_mismatch` | §8.1, §12.2 step 3 |
| `layout_invalid` | §8.5 |
| `layer_missing` | §8.5, §12.2 step 5 |
| `samples_mismatch` | §12.2 step 5 |
| `regeneration_mismatch` | §8.2, §12.2 step 5 |
| `frame_mismatch` | §8.4, §12.2 step 6 |
| `duration_mismatch` | §4.2, §6.1, §12.2 step 7 |
| `subject_mismatch` | §4.2, §12.2 step 7 |
| `framing_mismatch` | §5, §7, §12.2 step 7 |
| `region_mismatch` | §5.3, §12.2 step 7 |
| `order_mismatch` | §6, §12.2 step 7 |
| `cut_mismatch` | §6.2, §12.2 step 7 |
| `transition_mismatch` | §6.1, §12.2 step 7 |
| `governance_mismatch` | §10.1, §12.2 step 8 |
| `not_exact` | §12.1, §12.3 |
## 14. Conformance
[`live-conformance/direction/`](../live-conformance/direction/) holds the corpus, in one file,
`vectors.json`, written from fixed seeds by `wai_direction_vectors gen`. It holds the model file
of `wai.generate.spectral` its layers are generated with, the pictures given as they are, the
**measures** (the framing measure of §7 alone: boxes at both sides of every size bound and every
column and row bound, past every edge and at the limits of a signed integer, each with the classes
it measures), the **interpolations** (§5.2 alone: each mode, rising and falling, at the keys,
between them and with floors below zero, each with its value), and the **vectors**: each a case
of §12.1, its outcome and, accepted, everything §12.4 states; refused, its reason and its place.
Its frames are small (64 × 36 pixels at 24 frames a second), so every one recomputes. It has:
- **Accepted, `exact`**: a storyboard of three shots and two subjects, a cinematographer's
keyframes (a camera that eases across a plate in one shot, a push in from a medium to a close
framing in another) and a held region of a drawn reference, and an editor's sequence that
reorders the shots, trims one and dissolves into it, every frame recomputed; the same
storyboard fixing its own order, with no other record; a storyboard and keyframes with no
sequence level, the order then the tool's own; and the first under a policy that requires
exact.
- **Accepted, `attested`**: the first direction with frames a float generator made, the layout
stated by a tracker.
- **Refused**: each reason of §13 at least once, each a control of one defect: among them a cut
one frame from its directed frame, a keyframe box one pixel from its track, a size class the
box does not measure, a direction record signed by a key that is not a director's, the edit's
shots in another order, a float generator's plate claimed exact, frames that do not recompute,
a governance entry claiming frames its element does not govern, and a missing held region.
A conforming verifier gives every vector the outcome, the reason, the place and the statements
the corpus states. `wai_direction_vectors verify` and the test `tests/direction.rs` check that
the file regenerates byte for byte, that `wai-rs` gives every vector its outcome, that the corpus
refuses with every reason of §13, and that §13 lists exactly the reference's codes.
`live-conformance/direction_verify.py` is the second implementation, written from this text: it
shares no code with `wai-rs`; it runs the generator in its own arithmetic and checks it first
against the generator's conformance vector (`wai-rs/tests/vectors/generate/sha256.json`),
renders every frame, interpolates every track and measures every box itself, writes every
statement and C2PA statement itself, reads records with synthesis's second implementation's
JSON reader, verifies every signature with WAI's strict rule (`claim-conformance/_ed25519.py`)
and every link hash with BLAKE3 written from its specification (`claim-conformance/_blake3.py`);
it mutates copies of the corpus and requires every copy to fail; and it drops each of its own
checks in turn and requires the corpus to catch the weakened verifier.
## 15. Security and privacy considerations
- **What is checked and what is stated.** A direction record is its director's signed
statement of a choice; WAI checks the output against it, not whether the director made the
choice unaided. Under `replicate` the frames recompute, so the layout the checks read is what
the frames hold: a cut, a box, a held sample is exact. Under `attest` the layout is the tool's
statement, of the class it names, and the outcome says so.
- **No enrolment without a signature.** A person is credited with direction only for elements of
records that person signed, and a directed output names its directors; a record by any other
key is refused. A tool cannot credit or blame a director for frames the director's elements do
not govern (§10.1).
- **Direction is not making.** Directing a frame does not make its samples. The frame map keeps
the two apart: `directed_by` per level beside the layers' or the generator's making, never one
merged figure, and an origin claim keeps its class ([synthesis](synthesis.md) §3.2).
- **Not a judgment.** The records state facts frame by frame; they rank no contribution. What
any of it means for authorship is decided elsewhere, by others, under their own rules.
- **Privacy.** Records name directors by keys, never by name; subjects by reference pictures,
which a director discloses as it chooses. A layer's prompt is in the clear in the directed
output; a tool that keeps prompts private names the layer as given (`source` `null`) and
commits the prompt in a contribution record instead. This extension sets no consent
requirement for a service's own mechanics: rendering under a director's records, measuring a
frame, stating governance. Using a distributor's WAI service is the person's agreement to how
that service works. Where a regime asks for a lawful basis for processing a director's key or
a subject's reference picture, the service records it in a `lawful-basis` claim
([jwp-receipts](jwp-receipts.md) §2.4.6).