staging.Frame
How THE EIGHTH KETYL is framed, at any screen shape.
A shot is authored here as a world box it has to hold — so many units tall, so many units wide — and the distance and the lens are solved for the screen as it is right now. Nothing in this module is a pixel and nothing in it is a number picked while looking at one viewport.
The game films at 1080x1440, so the vertical extent usually decides the shot: a
wider screen holding the same vertical box simply gains margin at the sides. On
a screen TALLER than 3:4 the horizontal extent takes over, and past
DISTANCE_CAP the camera opens the lens rather than backing out through the
glass wall of the still.
local Frame = require("staging.Frame")
local shot = Frame.play(ketylRadius) -- dist, fov, orbitLimit, aspect, portrait
local R = Frame.eyeRadius(r, shot.dist, 0) -- where the eye stands, from the axis
local p = Frame.ring(angle, y, 0.85) -- a point just inside the glass
The gameplay box
PLAY_V | 12.0 | World units of height the playing shot holds — the plate you are on to the underside of the plate above. |
PLAY_H | 9.4 | World units of width — the room the ketyl is read moving in, not the whole plate. |
FOV_BASE / FOV_MAX | 58° / 78° | The lens it prefers, and how wide it may open on a very tall screen. |
AIM_RISE / EYE_RISE | 2.3 / 3.3 | The aim point above the plate floor, and the eye above that — a look down of about seventeen degrees. |
EYE_MAX_RADIUS | COLUMN_RADIUS - 0.4 | The eye never leaves the still. |
Solving the orbit
Frame.play(r) returns orbitLimit, the angle the eye may be swung off
"directly across the column" before its solved radius would reach the wall:
R(d) = r·cos d + sqrt(dist² − r²·sin² d) ≤ Rmax
⟺ cos d ≤ (Rmax² − dist² + r²) / (2·Rmax·r)
Frame.eyeRadius(r, dist, delta) is that solve, clamped inside the still. The
cinematics in staging.Beats place every one of their stations with it, so a
beat filmed on a phone and the same beat on a television are the same shot.
Scoped to this part · feeds back into the world's score.