camera
The shot list itself is `@user::pad.shots`. This module does the two things that have to happen every frame.
@user::pad.camera — the part of the camera slice that runs
The shot list itself is @user::pad.shots. This module does the two things that have to
happen every frame.
1 · It makes a seek frame a shot
cutscene.seek only reaches a cutscene that is PLAYING. Without this animator, a
film.seekTo(40) in edit mode moved the whole world to t = 40 — melt, front, sea, sound —
and left the camera standing wherever it was. Every capture of a "shot" was then a picture
of the world at the right moment from the wrong place.
So the camera animator evaluates the shot list and flies film_camera to it:
local eye, at, fov = cutscene.evaluate(def.shots, t)
That is the same code path the played cutscene uses, so the edit-mode frame and the played
frame are the same frame, and the same seek twice gives the same picture. It runs whether or
not a cutscene is playing — while one IS playing its own camera outranks film_camera
anyway, and keeping the two in step means capture { camera = "film_camera" } is always
valid too.
2 · It publishes the lens numbers nobody else should re-derive
local cam = require("@user::pad.camera")
cam.poseAt(t) -- eye, aim, fov
cam.aimAt(t) -- the aim point alone
cam.focusAt(t) -- focus distance in metres, INCLUDING the two racks
cam.stations() -- the macro stations, re-exported from the shot list
focusAt is the one the look slice drives depth of field from. Everything focuses on its
own shot's aim point except two:
- shot 6 racks 3.8 m → 25 m as the bead detaches — off the bead, onto the marshmallow flank behind it.
- shot 11 runs 12 m → 60 m inside the rising column, so the droplets passing the lens are sharp and the rest is bokeh.
Notes
build() repairs film_camera if the scene lost it, and sets its near plane to 5 cm — shot
1 sits 29 cm off a 4 cm ridge, which is uncomfortably close to the 10 cm default.
Everything here is a pure function of t. The animator reads the shot list and writes a
transform; it keeps no state between calls and integrates nothing.
Scoped to this part · feeds back into the world's score.