Cinema
The staged frame: two cinema bands and a wash veil.
It is a UI screen, laid out from the live ui.screenSize() — the one place
in the engine welded to the picture. A band expressed as a share of the screen
is exactly that share at 3:4, at 16:9 and at 21:9, with no lens arithmetic and
no frame of lag behind a fast pan.
That was learned the hard way. Hung in front of the lens as geometry — first parented to the camera rig, then placed in world space from the pose the camera had just taken — the bands slid away from the edges they are meant to be welded to whenever the shot moved quickly, because a mesh transform is composed against the camera's from the frame before. On a crane travelling two metres a second that is most of the frame height. In screen space the question does not arise.
Using it
local Cinema = require("game.stage.Cinema")
Cinema.attach() -- once, when the camera wakes
Cinema.bars(0.085, 0.7) -- one band = 8.5% of the frame, over 0.7 s
Cinema.veil(0, 0, 0, 1, 0) -- wash the frame black, instantly
Cinema.clear(0.8) -- bands out and wash off together
Cinema.tick(dt) -- every frame; it paints itself
Cinema.state() -- { bars, veil, shown }
CrowCamera drives all of it, so a beat reaches it through CF.camera.bars /
CF.camera.veil / CF.camera.clearFrame rather than requiring this directly.
Layering
The screen sits at layer 120 — the always-on-top band — so the bands cover the HUD while a moment is staged and stay under the menu depth (1000+) a card lives at. It is registered only while something is staged and unregistered the moment the frame is clear, so nothing of it stands between a player and a button. Each ending's shot lifts its own wash as the end card comes up, so a card is never read through a veil.
Owned by the staging slice. Design: /zero/source/design/spec.md.
Scoped to this part · feeds back into the world's score.