# shots
The camera language of the three staged beats, and the rule that makes one
written shot read on a television and on a phone held upright.
```lua
local Shots = require("game.staging.shots")
local def = Shots.intro({ aspect = 0.75, hx = 0, hy = 1, hz = -16 })
cutscene.play(def)
```
`intro(ctx)`, `firstlight(ctx)` and `resolution(ctx)` each return a cutscene
definition. `ctx` is `{ aspect, hx, hy, hz, tier }` — the live aspect and where
the Henter is standing when the beat begins.
## Fitting
A shot is written once, as an eye path and a look path in world metres, and
then fitted to the live screen by two things:
- **`lensFor(h, v, aspect)`** — the vertical field of view that covers at least
`h` degrees across AND at least `v` degrees down, whatever the aspect is. A
3:4 phone gets a taller lens so the hill still runs from the bottom of the
frame to the sky; a 16:9 monitor gets the shot's own vertical angle and the
extra width for free.
- **`dollyFor(aspect)`** — how far in the eye comes on a narrow frame. 1.0 at
16:9 and wider, closing to 0.76 by 3:4, which is what keeps the subject the
same size on screen once the lens has opened up.
`fit(spec, aspect)` applies both to one shot; `fitAll` to a list.
## The rest
- `BEAT_LAYERS` — what a beat's camera draws (`all !ui !EditorUI !debug`).
- `cairnPoint()` — the summit landmark the crest turns to face, read from the
scene when it is there so a set-dressed cairn moves the shot with it.
# SoundClip
Managed audio asset. `data.zaud` is the engine-native ZAUD payload the runtime decodes and plays; compression settings live in the `.metadata` `settings` block (edit to re-encode).
# core
<!-- zero:scaffolded-readme -->
<!-- Replace the line below with what this module provides and who
consumes it, then delete both comment lines. -->
TODO: describe this module.
# HenterBody
The Henter — the hooded figure the player is — and the lantern they carry.
Sits on the PlayerPrototype's `body`. It builds the whole figure under one
child root (`henter_figure`) out of the meshes in `game.hero.meshes`, and
animates it by turning a chain of pivots: `hips` → `chest` → `arm_r` →
`pole_pivot`. There is no skeleton and no clip library; the walk, the lean, the
snatch and the swing of the lantern all come out of the same handful of angles,
which is why they blend into each other instead of cutting.
The gait is measured from how fast the body is actually travelling, so it is
true however the Henter got moved — walked, lunged, or carried by a cutscene.
## What other slices use
| | |
|---|---|
| entity `lantern` | A direct child of `henter_figure`, carried onto the stave's hook every frame and hanging plumb. Where the glew flies to, and where the lantern's point light lives. Found by a shallow child scan, by `entity.getDescendants`, or by `public:lanternEntity()`. |
| `public:setGlow(t)` | `0..1` — how full the lantern reads. Drives the light's intensity and radius and the size of the flame inside the glass. |
| `public:playHent()` | Plays the snatch: wind up, drive, settle, with the lantern thrown forward on its hook. |
| `public:receiveGlew(x, y, z)` | Flies one mote from there into the lantern along an arc of its own. |
| `public:lanternPosition()` | The world point the glew should aim at — the flame, not the ring. |
| `public:noteGlewCaught()` | "A catch is already in flight." `game.hero.GlewLook` says this when the glew field flies a caught mote itself, so the body does not draw a second one over the top of it. |
It listens for `butt` (the Henter is knocked back on their heels), `glew` (the
lantern's own note), `hent` (the animation, and the fallback flight when
nothing else drew one) and `phase`.
## The sound it owns
Boots in wet heather on each end of the stride, the lantern's ring chinking on
its hook when the swing turns over, iron creaking when a hent throws it
forward, and the small warm note a mote makes dropping into the lantern — all
from `game.hero.heroaudio`, synthesised, so none of it can arrive silent.
# game.audio.sfx
Play a one-shot from anywhere on the hill.
```lua
local Sfx = require("game.audio.sfx")
Sfx.play(clipRef) -- flat, non-spatial
Sfx.play(clipRef, { pos = { x, y, z }, volume = 0.7 }) -- out on the hill
Sfx.play(clipRef, { pitch = 1, jitter = 0.12 }) -- a run that is not a machine
Sfx.ui("confirm") -- the menu's own set
```
It spawns a temporary entity carrying an `Audio` component, lets it sound, and
despawns it after `ttl` seconds. Pass `pos` for a spatial source, heard from
the `AudioListener` on the CameraRig. Resolve your clip through a **literal**
identity so the reference is pinned when this world is installed elsewhere.
## Three things every caller gets
**A voice budget.** Below a reserve of free voices a low-priority one-shot is
dropped rather than stealing the narrator's. `priority` 0 is set dressing, 1
is the default, 2 survives a crowded mix, 3 always plays.
**A collapse window.** Ten motes caught in one frame is one sound, not ten
stacked copies 6 dB louder than anything else in the mix. The same clip asked
for again inside `collapse` seconds (35 ms by default) is dropped. Pass a
longer window for a sound that should never double up, or `priority = 3` to
opt out.
**Pitch jitter.** `jitter` spreads repeats of one clip either side of their
pitch, so a run of them reads as a hill rather than a loop.
## Options
`pos`, `volume`, `pitch`, `jitter`, `maxDistance`, `channel`, `ttl`, `name`,
`priority`, `collapse`, `looping`.
`channel` is one of `sfx` / `ui` / `ambient` / `music` / `voice` — the names
the mix knows. A source naming anything else plays at unity, which is a sound
nobody can turn down, so an unknown name falls back to `sfx`.
## The menu's set
`Sfx.ui(name)` plays one of `click` / `move` / `confirm` / `back` / `deny` —
small struck-glass sounds, because the lantern is glass and brass and so is
the menu. Non-spatial, on the `ui` channel, and high enough priority to get
through a crowded mix.
`Sfx.stopAll()` drops every one-shot still sounding, so a hent that started a
frame before the round ended does not carry into the end card.
# SoundClip
Managed audio asset. `data.zaud` is the engine-native ZAUD payload the runtime decodes and plays; compression settings live in the `.metadata` `settings` block (edit to re-encode).
# caption
The band a staged beat speaks over.
A beat holds the camera and takes the HUD out of the frame, so the caption band
the HUD carries goes with it. This is the band the beats carry themselves: the
speaker, the line, and the prompt that says the beat can be left.
```lua
local Caption = require("game.staging.caption")
Caption.setPrompt("SKIP · SPACE")
Caption.show("The Old Henter", "Lantern up, henter.", 5)
Caption.tick(dt, 0.10) -- the letterbox fraction the beat is running at
Caption.hide()
```
- `show(speaker, text, seconds?)` — put a line up. Without `seconds` it stays
for `readingTime(text)`, long enough to read aloud and floored so a short
line does not flash.
- `tick(dt, letterbox)` — run the line's clock down and refresh the band.
- `setPrompt(text?)` / `hide()` / `clear()` / `isShowing()`.
## Two things it will not do
**It is not laid out in pixels.** The card's measure, its type sizes and its
distance from the bottom edge are fractions of the live `ui.screenSize()`, so
it reads the same on a 3:4 phone and on a 16:9 monitor.
**It never lands under a letterbox band.** The bands and the fade are a
post-process drawn *over* the UI, so a caption inside one is invisible while
looking perfectly correct in the widget tree. The band's bottom inset is
derived from the letterbox fraction the caller passes.
## Its own render layer
The screen draws on `StagingCaption` (`M.LAYER`) rather than on `ui`, which is
what lets a beat's camera leave the whole HUD out of the frame and still carry
these words. `Shots.BEAT_LAYERS` is the matching spec on the cutscene camera.
# SoundClip
Managed audio asset. `data.zaud` is the engine-native ZAUD payload the runtime decodes and plays; compression settings live in the `.metadata` `settings` block (edit to re-encode).