# Director
The only thing that advances **Standing Ovation**. One of these sits on the
`Director` entity; every rendered frame it calls `Film.tick()` exactly once,
which moves the film clock on by a fixed 1/24 s and pushes that time at every
registered slice. It ticks in edit mode and in play mode alike, because the film
is authored, watched and recorded in both.
Nothing else may call `Film.tick`. A slice that wants something to happen at a
moment registers with `Film.on` / `Film.at` — see `ovation.film`.
## Fields
| | |
|---|---|
| `autoPlay` | Rewind and start the film as the component wakes. |
| `filmTime` / `filmFrame` / `shot` / `running` | Where the film is, readable from any scope. |
## Methods
```lua
local d = entity.find("Director").component.get("ovation.director")
d:restart() -- rewind to t = 0, firing every reset hook, and play
d:hold() -- freeze where it is, frame still composed
d:goTo(53.0) -- land on a moment without firing the cues in between
```
# ovation.film
The shared spine of **Standing Ovation** — a 90-second cinematic demo set in a
rotting 1,412-seat theatre. Two things live here and nothing else.
## The constants
The Odeon Marisol's geometry (`Film.GEO`, `Film.rakeY`, `Film.stallSeat`,
`Film.balconySeat`), its palette (`Film.PALETTE`), its four light sources
(`Film.LIGHTS`), its acts (`Film.ACTS`) and its shot list (`Film.SHOTS`). Every
slice of the demo reads its colours and dimensions from here rather than
restating them, because two slices disagreeing about where row A stands breaks
the film in a way no screenshot explains.
## The film clock
One deterministic timeline the whole demo runs on. It advances a **fixed 1/24 s
per rendered frame** — it reads no wall time and no real frame delta, so the
same build produces the same 2,160 frames on any machine, under a recorder or
without one.
```lua
local Film = require("ovation.film")
Film.rewind() -- every reset hook fires, every cue re-arms, t = 0
Film.play() -- the Director advances it from the next rendered frame
Film.on("mine", function(t, dt, shot) end) -- called every frame
Film.at(47.0, "cue", function() end) -- once, as the clock crosses 47 s
Film.reset("mine", function() end) -- back to the t = 0 state
```
`Film.stop()` freezes it with the frame still composed. `Film.seek(t)` lands on
a moment for authoring and does not fire the cues in between.
`Film.ramp`, `Film.smooth` and `Film.wave` are the shared shaping helpers: this
film is made of waves crossing 1,412 identical figures.
Only `ovation.director` calls `Film.tick`.
# house
<!-- zero:scaffolded-readme -->
<!-- Replace the line below with what this component provides and who
consumes it, then delete both comment lines. -->
TODO: describe this component.
# filmSound
The soundtrack of *Standing Ovation*, on the film clock. It sits on the `Sound`
anchor.
On wake it builds the whole soundtrack under that anchor as real entities in
real places — the rain in the dome, the water on the proscenium, the ballast at
the worklight, the applause across the stalls and up in the balcony, the
fourteen filaments down the apron — and then registers with the clock:
* every frame, each bed's `gain(t)` from `sound.mix` is a pure function of film
time, so the mix is the same on every playthrough and a rewind puts it back;
* every one-shot is a `Film.at` cue, so it fires once, on its frame, and is
re-armed by `Film.rewind()`.
Everything is heard through the AudioListener on the FilmCamera, which the
camera slice moves shot by shot — so the mix re-balances itself as the lens
changes seats, with no automation written for it at all.
## Fields
`beds`, `emitters`, `cues` — what it built. `loudest` — the bed on top right now.
## Methods
```lua
local s = entity.find("Sound").component.get("filmSound")
s:at(70) -- every bed's gain at that film second
s:rebuild() -- rebuild every emitter and restart the beds
```
## Putting it on the world
```lua
require("camera.mount").all()
```
# figures.troupe
The whole of "Standing Ovation"'s figure slice, on one entity. Put it on the
scene's `Figures` anchor and it owns everything under it:
```lua
entity.find("Figures").component.add(asset.resolve("figures.troupe", "component"))
```
It builds two things and drives both off the film clock (`ovation.film`):
**The performer** is an articulated hierarchy — pelvis, torso, neck, head, two
arms, two legs, and two eyes that move independently of each other. He walks on
from the wings at shot 5, holds still for shot 6's 18-degree macro while his
two eyes blink on two different clocks and one of them tracks the lens, bows at
shot 7 with his head staying up through it, and walks downstage during shot 8
until he is standing exactly where shot 12's camera stands — at which point he
stops being a figure in the film and becomes the one watching it.
**The audience** is 1,412 of him, drawn as four hardware-instanced populations
(torso, head, left hand, right hand). Their world matrices and shader lanes are
written every frame by `crowd_pose.computeShader` from a static seed block, so
the values never touch the CPU: this component sets fourteen params and
dispatches, and 5,648 transforms come out the other side.
Four waves cross the house and the film is the order they arrive in —
**occupancy** back to front (t 41→52), **attention** front to back (53→62),
**rising** front to back (67→73) and **applause** from t 60, converging as the
`unison` param collapses the rate spread and the offset spread onto one clock
across shot 12. Every wave carries a per-member delay and a per-member rate off
the seed block, so it arrives as a gradient rather than a switch thrown on the
whole room at once.
`:rebuild()` tears the slice down and builds it again from source — every mesh,
the generated greasepaint, the performer and the audience's buffers. `audience`
turns the population off, which is what Acts I and II look like.
# lightbox (component)
The lifetime of **Standing Ovation**'s light rig. Add it to the scene's
`Lightbox` anchor and the theatre has its worklight, its fourteen footlights,
its followspot, its chandelier and house coves, its haze, its rain, its dust,
its falling plaster and its post chain, all running on the film clock. Remove
it and every one of them goes.
```lua
entity.find("Lightbox").component.add(
asset.resolve("lightbox.lightbox", "component"), {})
```
The work itself lives in `lightbox.lightbox` (the module). This file is only
its scope — and that is the whole reason it exists. The volumetric march, the
depth of field, the shutter, the exposure meter and every particle emitter are
acquired in whatever resource scope creates them, and a scope that ends takes
them with it. A rig stood up from a one-shot `execute` therefore reads back as
live and draws nothing a second later. A component's scope is its entity's, so
the rig lives as long as the anchor does.
## Methods
| | |
|---|---|
| `:report(t?)` | What every source is doing at film second `t` (default: now) — level per fixture, the followspot's aim, the haze density. The call to make before reading a screenshot when a frame is darker than expected. |
| `:rebuild()` | Tear the rig down and build it again from the module as it stands. |
## Fields
| | |
|---|---|
| `built` | Whether the rig is standing. |
| `fixtures` | How many lights it put in the world. |
## After editing the module
A module hot-reload does not re-run `awake`, so the rig in the world is still
the one the previous revision built. Remove the component and add it again:
```lua
local anchor = entity.find("Lightbox")
anchor.component.remove("lightbox.lightbox")
anchor.component.add(asset.resolve("lightbox.lightbox", "component"), {})
```
That runs `awake` in the component's own scope, which is what the rig needs to
outlive the call.
# filmCamera
The lens of *Standing Ovation*. It sits on the `FilmCamera` entity and is the
only thing that writes that entity's transform or its fov.
It does not tick. It registers with the film clock in `awake()` and is called by
the Director's tick, so the lens runs on the same fixed 1/24 s as everything
else in the film. Every frame it reads the set-up out of `camera.shots`,
evaluates the path at the shot's own `u`, and puts the lens there.
**Cutting is the same act.** The shot the clock names changes on a boundary in
`Film.SHOTS`, so the lens is in the new set-up on the very next frame with
nothing in between — a hard cut, on the frame, thirteen times.
## Fields
`shot`, `fov`, `eyeX`, `eyeY`, `eyeZ` — read-outs of where the lens is now.
## Methods
```lua
entity.find("FilmCamera").component.get("filmCamera"):bookend()
--> shot 13's station, aim and lens against shot 1's
```
## Putting it on the world
```lua
require("camera.mount").all()
```