game.camera.shots
The six moments the camera takes itself off the crane.
local Shots = require("game.camera.shots")
local ctx = Shots.context() -- the live world, read once
local pose = Shots.eval("ring", 0.72, ctx) -- { px, py, pz, ax, ay, az, fov, roll }
| Beat | Seconds | What it does |
|---|---|---|
opening | 6.2 | Starts under the fog sea, turned nearly a radian off the crane's side of the tower, and climbs through the fog line past the rooftops onto exactly the station the follow takes over. The aim lands a beat before the position, so the last stretch reads as arriving rather than as being swung into place. |
hoist | 4.6 | An orbit that leaves and returns — turn, radius and lift are all one half-sine, so it ends on the follow station exactly. The aim is on the bell itself, read live, so the shot carries the bell up the outside of the tower and in over the last course. |
ring | 2.6 | The shot the whole game is pointed at. Swings round to the side the sun is not on and pushes in along the sun's own bearing, so the bell is between the lens and the light. Holds. |
won | 7.0 | Rises and pulls back until the shot is no longer the tower but the place: the aim comes down onto the roofs, the lens widens, and the town the warning was for arrives under the new light. Holds. |
fell | 5.0 | Does not cut away. Falls with the courses, ends low on the stump, held a few degrees off level. Holds. |
sunrise | 5.0 | Turns off the unfinished tower and onto the light: round to the sun's bearing, down toward the fog, the stump dark against a sky that has already won. Holds. |
Pure functions of a clock
Every beat is a function of a normalised clock and a Ctx of live readings.
Nothing here reads the world during a beat, holds state or writes an entity —
game.camera.director runs the clock and applies the pose. Two things follow,
and both are why it is written this way: a hot reload cannot lose a beat
mid-play, and any frame of any beat can be looked at without playing it,
which is how the framing was judged.
local p = Shots.eval("opening", 0.35, Shots.context())
-- capture { source = "position", position = {p.px,p.py,p.pz},
-- lookAt = {p.ax,p.ay,p.az}, fov = p.fov }
Every beat is written in the terms game.camera.framing hands back — a turn
around the tower, a scale on the solved radius, a lift off the solved height —
never in absolute metres, so a beat stays framed on a phone held upright and on
a television alike.
The bell and the sun in one frame
ring is the one beat that has to hold two things at once, and only one of them
can be moved to, because the sun is at infinity. What decides the frame is the
ANGLE between them, and the only free variable is how far above or below the
bell the camera stands — so the shot is solved from the sun's own elevation,
read live off the directional light, rather than from a height that happens to
work at one hour. The bell lands at 0.33 up the frame and the sun at 0.80,
whatever elevation the dawn has reached. Where the sun sits so high that holding
both would put the camera under the fog, the bell keeps its place and the sun
rides up the frame.
Handing the camera back
Every beat ends on the follow pose, or on a pose the follow can damp out of in
a few tenths, so the hand-off is a continuation rather than a cut. ring and
the three ending beats are the exception: they HOLD their last frame. The ring
push-in has to stay on the bell while the player waits for the top of the
swing, and an end card wants a composed frame behind it rather than a camera
drifting back to a tower nobody is building any more.
Scoped to this part · feeds back into the world's score.