# Scenes

HTML scenes and canvas scenes, the meta, the frame rule, and why every frame renders the same.

A scene is one shot: an id, a duration, an entry, cues, and something that draws as a pure function of time. Any frame re-renders exactly, frames render in any order, and one scene renders in parallel chunks across Chrome pages. Authoring contract: [writing scenes](/docs/writing-scenes).

## Two kinds

- **HTML scenes**: one `.html` file of HTML, CSS, and [pinned libraries](/docs/shots#the-other-libraries), run in headless Chrome under rapid's virtual-time runtime. Every `scenes/<id>.html` in a project is one; the planner builds only these.
- **Canvas scenes**: a `.js`, `.mjs`, or `.ts` module whose default export has a `meta` and a `draw(g, t, d)` that paints the whole frame from `t`. `rapid render` takes one by its extension.

## The meta

`SceneMeta` and `HtmlSceneMeta` in `contracts/src/scene.ts`:

| Field | Type | Notes |
|---|---|---|
| `id` | id | `a-z`, `0-9`, `-`, up to 64 |
| `kind` | `html` or `canvas` | `html` by default in an HTML scene |
| `dur` | seconds | from the scene's own timings, never padded |
| `enter` | `{ kind, dur, params? }` | `cut`, `flood`, `mark`, `blob`, `slide`, or `fade`; default `{ "kind": "cut", "dur": 0 }` |
| `place`, `clock` | strings, up to 80 and 20 | drawn over the scene, never by it |
| `who` | string, up to 80 | optional |
| `duck` | array of `[from, to]` | spans where the scene's full-frame type owns the clock's corner; default `[]` |
| `width`, `height`, `fps` | HTML only | the frame it draws, from its format; required |
| `fonts` | HTML only, array of strings | families drawn only on a `<canvas>`, which must load; default `[]` |
| `key` | HTML only, seconds | the key frame: the hero, built and approved before the motion |
| `holds` | HTML only, array of `[from, to]` | spans held still on purpose; the `dead` check skips them; default `[]` |
| `room` | HTML only, 0–1 | the share of the key frame that may be empty field; the `empty` check allows up to it (default 0.4) |

## The frame rule

One function (`contracts/src/frames.ts`) says how many frames a duration has and which frame a time is in; every renderer, check, and sheet uses it.

- Frame `k` starts at `k / fps`.
- `d` seconds is `ceil(d × fps)` frames, at least one.
- A time falls in the frame it is in (rounding down), clamped to the scene.
- **The poster** is the start of the last frame: the node's thumbnail.

At 30 fps a 3.2 s scene has 96 frames; its poster is at 3.1667 s.

## Determinism

The runtime loads before the scene's scripts and owns every clock the page can read: `Date`, `performance.now`, `requestAnimationFrame`, `setTimeout`, `setInterval`, `Math.random`, `crypto.getRandomValues`. The clock starts at `2026-01-01T00:00:00Z` and moves only when the renderer seeks.

Same in any seek order:

- code in `rapid.frame(fn)` that computes from its `t`;
- GSAP timelines built at load;
- CSS animations and transitions;
- Web Animations made at load;
- `rapid.random()` inside a frame callback (reseeded from the frame's time);
- the [pinned libraries](/docs/shots).

Same only in a straight play: state a script keeps (a timer chain, a counter bumped per frame). Timers fire in virtual-time order as seeks move forward; a backwards seek rewinds the clocks but never un-fires a timer. A render seeks out of order across chunks, so compute from `t`.

## Problems fail the render

The runtime collects uncaught errors, unhandled rejections, a throwing frame callback or timer, assets that fail to load, images still loading after 10 s, more than 10,000 callbacks in one seek, and fonts that didn't load, each with the time it first happened. A scene with any problem renders nothing; `rapid render` exits 1 naming it:

```text
rapid: callback at 1.033s: frame: Error: oops
```

## No network

A scene reaches nothing outside its folder (a project's scene: the project). An absolute address in its HTML or CSS is refused before the page loads; the page's content security policy blocks anything a script builds at run time. Relative addresses resolve against the scene's folder; `rapid:style/<path>` is the linked style's file.

## What a render writes

Beside each MP4:

- `<name>.boxes.json`: every `data-el` element on every frame: id, box in frame pixels, role, text, and for type its line height and font size. The checks read it.
- `<name>.cues.json`: every cue the scene emitted, deduplicated by key. The mix plays them; review times are picked at them.
