stagecraft
The camera of *The Acceding*. It drives the one camera the scene already carries and never adds another, because the world is always multiplayer and a second rig beside the PlayerPrototype's passes single-peer testing and breaks the moment anyone joins.
STAGECRAFT — the camera, and the three moments
The camera of The Acceding. It drives the one camera the scene already carries and never adds another, because the world is always multiplayer and a second rig beside the PlayerPrototype's passes single-peer testing and breaks the moment anyone joins.
What it does
The standing shot. A station per gate, from A.cameraStationForTier(n),
with an eased travel between them. A locked shot with nothing moving in it
reads as a still, so the frame breathes — three incommensurate periods on the
eye, the look target and the lens — and the breath gets faster and wider as
the Engine's patience runs out. A refused litany knocks the frame; a flinch
shakes it hard.
THE ACCEDING. A gate opens and the camera HOLDS on the flare for 0.62 s with the lens pushing in three and a half degrees, then the whole frame RISES one tier over 1.85 s. In a frame three wide and four tall the camera move is the reward.
THE DESCENT. The opening. Thirty metres, three shots and a settle, on a slow arc around the machine so it turns as it is fallen past. Skippable, and the shot the trailer opens on.
It runs on RITE's brief phase and lasts exactly BRIEF_SECONDS, so the
camera comes to rest at the porch as the first gate takes the frame. RITE
speaks the Verger over it and LITANY writes what he says, so the descent keeps
the ui layer and fires no cues of its own — it takes the camera and nothing
else. Skipping it ends the brief with it.
THE REFUSAL. Shoved back against the wall of the well, then sinking toward the standing water while the lens opens and the machine grows over the top of the frame, until the dark closes.
THE CROWN. Hold on the ninth gate's flare, rise above the machine, and turn to look back DOWN the whole climb — then fade to firelight. It bookends the descent: the game opens falling down the column and ends looking back down it.
What other slices can call
M.install(ctx) / M.tick(dt) / M.onCallback(id, data) | the driver calls these |
M.play(name) | "descent" · "crown" · "refusal" — play a moment on its own, for a capture or a trailer take |
M.skip() | end the moment that is running |
M.isCinematic() | true while a moment owns the camera |
M.moment() | which one, or nil |
M.descentDef() / M.crownDef() / M.refusalDef() | the shot lists, for cutscene.evaluate |
M.TUNING | every number |
stg-skip is the callback id for a skip affordance on screen.
Ask M.isCinematic() through the function, never as a field. A field is
read off whichever module table the caller happens to hold, and after a hot
reload that is not the one running the game.
The HUD does not have to hide itself during a moment: each moment draws with
renderLayers = "all !ui !EditorUI !debug", so the ui layer is already out
of the frame. isCinematic() is there for anything that wants to know anyway.
What it puts in the world
Under the Stagecraft anchor, all of it temporary, all rebuilt on install:
stg_key— a warm point light that rides the descent, so the brass the camera falls past has something to catch.stg_crown— a point light over the machine's upper drums, lit only during the crown.stg_caption/stg_speaker— the cinematic caption, in the game's own faces, wrapped to the width a 3:4 frame has.
Three things this slice learned the hard way
The room is a well. Walls at x = ±8.05 and z = ±8.05, floor at 0, a lid at
y = 36.5, and the ninth gate at y = 26.6. A pull-back to z = −13 fills the
frame with one flat brick slab; a climb to y = 42 comes back with every pixel
black. ROOM_Z is the furthest back a shot may stand. Capture a shot list —
do not reason about it.
camera.active(), not camera.main(), and not layers.active.camera.
Only one of the three asks the question this slice needs answered — which
camera is the frame actually drawn from. layers.active.camera returned the
PROTOTYPE's rig in play (it reports ready = true and renders nothing) and the
editor fly-camera in edit. camera.main() answered correctly once and, later
the same session, named the prototype while camera.list() showed the clone as
the one with rendering = true. Both failures look identical from inside: every
write succeeds and the picture does not change. camera.active() is the
on-screen camera by definition; it is the editor's in edit, so refuse that one
and fall back to camera.main().
Nothing mutable lives on the module table. AccedingDriver captures the
module once and ticks that table forever; the bus holds closures over whichever
table was current when each install ran; a fresh require returns the newest.
A hot reload splits all three apart. So the live state and the unsubscribe list
both hang off A.bus, which survives — otherwise a moment plays with
cinematic reading false, the camera freezes mid-shot, and each reload leaves
one more handler on the bus writing camera stations nobody asked for.
One thing it costs the session
Driving the camera writes the CameraRig's transform. When a player's body is in
the world that is the clone and nothing is touched; when there is none, it is
the prototype the scene owns, and the session ends up with one tuned entity
("Tuned in play: CameraRig") to accept or reject before the next mode flip.
tools.use("sceneAuthoring", "changes") then rejectChanges is the whole of
settling it.
Scoped to this part · feeds back into the world's score.