# FilmDirector
The one component that makes DEADLIGHT play. It sits on the `Director`
entity in `scenes.main`.
Every RENDERED frame while the film is playing it advances `deadlight.film` by
exactly 1/24 s — which fires every registered slice's `shot` and `tick`
handlers — and then places `FilmCamera` for that instant. Never wall time: a
slow frame makes the film take longer in wall seconds without changing a
single frame of it, which is what lets the recording phase run it twice and cut
the same picture.
Framing is not its business. It reads `deadlight.cut.shots`, an array indexed
by shot number holding the engine's cutscene shot fields minus `dur`, and
merges each with the duration from `Film.SHOTS`. An anchor naming an entity
nobody has spawned yet is substituted with its contracted fallback from
`Film.ANCHORS`, so the whole 94 seconds plays end to end while four slices are
still building the things it frames.
| field | |
|---|---|
| `autoplay` | start the film when the scene enters play |
| `driveCamera` | write `FilmCamera`'s pose and lens; turn off to fly the editor camera through a held moment |
Methods take no `self` — call them with `.`:
```luau
local d = entity.find("Director").component.get("FilmDirector")
d.play() -- from the top
d.hold(41.5) -- seek there deterministically, place the camera, stop
d.status()
```
# film
The shared constants and the film clock for DEADLIGHT. Every slice of the
demo reads this module and nothing restates its numbers.
It holds three things:
- The timing spine — the 14 shots, their durations and their windows.
`Film.SHOTS`, `Film.shotAt(t)`, `Film.window(n)`, `Film.movementAt(t)`.
Durations live here and nowhere else.
- The world contract — the shared coordinate frame (`Film.WORLD`), the
named entities each slice owns (`Film.NAMES`), the submersible's contracted
path (`Film.SUB_PATH` / `Film.subPoseAt(t)`), the anchor fallbacks that let
the film play while it is still being built (`Film.ANCHORS`), the palette
(`Film.PALETTE`) and the water's optics (`Film.WATER`).
- The clock — a fixed 1/24 s step advanced once per RENDERED frame by
`deadlight.FilmDirector`, never from wall time. 2,256 rendered frames is
exactly the 94-second film, every time.
```luau
local Film = require("deadlight.film")
Film.restart() -- t := 0, every slice resets, playing := true
Film.seek(48.0) -- deterministic scrub to a moment
Film.status() -- where the film is and who has joined it
Film.register("abyss", { -- join from your own component's awake()
reset = function() end,
tick = function(t, dt) end,
shot = function(n, tLocal) end,
})
```
Handlers run inside `pcall`, so one slice erroring cannot stop the film for
the others. Registering a name twice replaces its handlers.
# Abyss
The medium and the place DEADLIGHT is filmed in, as one component on one
entity. It owns the whole abyss slice and joins the film's clock in `awake()`:
- **the absence of a sky and of a sun** — the engine renders both with no entity
in the scene for either, and this film has neither;
- **the water's optics** — per-channel extinction and the colour of the column,
written once per film frame from one number;
- **the seafloor** — a silt plain carrying the long dunes, with every ripple
mark computed per pixel;
- **everything suspended in the water** — marine snow, the silt the vehicle
lifts, the amphipod boil, the corrosion dust;
- **the water column of movement I** — the sea's underside, the sunball through
it, the caustic net and the shafts;
- **the life** — a shoal, a siphonophore, a comb jelly, a sea cucumber, a
rattail, a squat lobster, a shrimp, and the bioluminescent flashes.
Everything it builds is spawned `temporary`, so none of it enters `scene.json`
and none of it collides with the other three slices' pending edits; the
component rebuilds it on every awake instead.
`buildOnWake` off inspects what is already there without paying for a rebuild.
`rebuild()` tears the slice down and puts it back — the call to make after
editing a shader or a curve. `observe()` reports what the slice is holding: the
water's optics at the current instant, every emitter's population, the plain's
triangle count and how many bodies are on screen.
## The scrub problem
`Film.seek(t)` runs the clock from zero to `t` at the fixed step inside ONE
rendered frame, so a slice's `tick` can fire two thousand times before the GPU
draws anything, and an emitter stepped that many times queues two thousand
dispatches and wedges the engine. The component counts rendered frames of its
own and tells the emitters whether the film frame they are being handed is a
real one. During a scrub they are left alone; the frame after it lands, they are
caught up over a bounded run of real steps.
# SubRig
The submersible slice of DEADLIGHT. One entity in `scenes.main` carries this
component; it builds the vehicle *Nautile* and drives everything the vehicle
does for the whole 94 seconds.
```luau
local rig = entity.find("SubRig").component.get("SubRig")
rig.status() -- where the vehicle is, what it is doing, what is alive
rig.rebuildRig() -- tear the vehicle down and build it again
```
## What it owns
| name | what it is |
|---|---|
| `Nautile` | the vehicle. Rides `Film.subPoseAt(t)` exactly. |
| `NautileLightPort` / `NautileLightStbd` | the two 6,000 K HMI floods |
| `NautileSpot` | the 4,300 K forward spot |
| `NautileArm` | the manipulator base; its joints are `NautileArmShoulder`, `NautileArmElbow`, `NautileArmWrist` |
Everything under `Nautile` is spawned temporary, so a rebuild leaves nothing
behind and the shared commit gate never sees seventy-odd hull parts.
## How it moves
Position is `Film.subPoseAt(t)` and is never adjusted — the camera is framed
against those keys. The performance is entirely in rotation: heading from the
path (level and slowly turning through movement I, because a ballasted descent
hangs level rather than flying), bank from the turn rate, trim wobble on top.
`deadlight.sub.drive` holds all of it as pure functions of film time.
## The lamps are the film's lighting model
Below 200 m there is no other light. Each lamp is a `SpotLight` on a pan/tilt
pivot plus a particle **beam cone** — the suspended matter the light scatters
off, drawn rather than marched. Through the shots that are about what the
light finds, the pivots are aimed at a world point that moves, which is what
"the two cones slide over the bridge front like a finger reading a page" is.
Those shots also narrow the cone and open the throw, so a flood that works at
four metres reads as a searchlight at forty.
On the way down only the forward spot runs: that is the ONE hard cone raking
the void in shot 4. The floods come up as the floor arrives.
## Its clock is the film's
The five emitters (three beam cones, the thruster wash, the ballast bubbles)
are stepped BY HAND, one `Film.DT` per film frame, from `update` /
`editorUpdate`. Nothing here runs on wall time, and a held frame is the same
picture twice. A seek runs thousands of film frames inside one rendered frame,
so the emitter debt is capped at `CATCH_UP` frames — catching up is worth about
two particle lifetimes and dispatching the full backlog wedges the frame loop.
## Fields
| field | what it does |
|---|---|
| `floodBeamRate` | particles/second in one HMI flood's cone |
| `spotBeamRate` | particles/second in the forward spot's cone |
# Wreck
The liner on the bottom, and the things on it that move. One component on one
entity; it is the whole `wreck` slice of DEADLIGHT.
On `awake` it derives every surface, builds the ship, and spawns the five
anchors `Film.NAMES` promises — `Bow`, `PortAnchor`, `Forecastle`,
`BridgeFront`, `DebrisField` — each carrying its own geometry, so a shot framed
against a name frames the real thing rather than a contracted fallback.
Then it joins the film clock through `Film.register("wreck", ...)`. Everything
the wreck DOES is a function of `Film.t` and nothing else: the rusticles sway
down the whole side, one of them parts and falls its whole length at 41 s for
shot 7, corrosion flakes lift off the forecastle for shot 8 and rust dust falls
past the bridge for shot 11. Nothing here reads wall time, so two runs of the
film are the same film.
## Using it
```luau
local c = entity.find("Wreck").component.get("Wreck")
c.status() -- { built, clumps, flakes, t }
c.at(48.0) -- put the wreck where it stands at 48 s, without running the clock
c.rebuild() -- re-derive every texture and mesh after editing a module
```
Component methods take no `self` — call them with `.`, not `:`.
## Fields
- `generation` — bump it to force a rebuild.
## What it spawns
Every entity it makes is TEMPORARY: the wreck is a function of this code, not a
hundred records in the scene, and a slice that leaves un-marked entities behind
blocks the shared commit gate for the other three. It also puts every piece on
the `wreck` render layer as well as `default`, so a diagnostic capture can ask
for the ship alone (`capture { renderLayers = "wreck" }`) without disturbing the
other slices.
## Where the parts live
`deadlight.wreck.geom` builds triangles, `…surfaces` makes the materials,
`…hull` is the shape of the ship, `…fittings` is everything bolted to it,
`…growth` is the rusticles and `…debris` is the field aft of the break.