lines
The Keeper's script — every word she says in *First Light: Lucina*, and the recording of each beside it.
game.staging.lines
The Keeper's script — every word she says in First Light: Lucina, and the recording of each beside it.
One speaker. A woman past sixty, dry and unhurried, heard over the speaking tube from the watch room below. The apprentice never answers, which is why there is one voice and no cast: the game is one side of a conversation.
local Lines = require("game.staging.lines")
local l = Lines.get("opening_a")
l.text -- what goes on screen
l.speaker -- "The Keeper"
Lines.seconds("opening_a") -- how long it is heard for, read off the clip
Lines.clip("opening_a") -- the .soundClip, or nil if it never landed
The text is the asset; the audio is a performance of it
Every line is written here first. A line whose clip never generated, or whose
player has the sound off, or who is in a noisy room, is a quiet line rather
than a lost one — the words go on screen either way, and Lines.seconds falls
back to a reading estimate (2.6 words a second plus a breath) so a beat built
without the recordings is still the right length and still ends.
The script
| id | When it is said |
|---|---|
opening_a opening_b opening_c | the opening beat, over the speaking tube, on the first plate of a run |
dose_warn | the dose crosses its warning mark, over live play |
crest | first light crests the window |
pass / fail | the plate is scored |
relight_a relight_b | the lens takes the light |
lost | the night ends |
The recordings
game/staging/voice/keeper_<id>.soundClip, one per line, generated once with
audio_gen.speech and cached as an asset so a hot reload never re-buys the
script. They are resolved one literal at a time rather than by a computed
identity: a computed reference cannot be pinned, and a world that pulled this
module in would arrive with the words and none of the voice.
Lines.info(id) reads a clip's sampleRate / channels / frames /
durationMs off the ZAUD header without decoding it, and Lines.report() is
the whole script with whether each line is voiced and how long it runs — which
is how "is the voice actually there" is answered without listening:
for _, row in Lines.report() do
print(row.id, row.voiced, string.format("%.2fs", row.seconds))
end
Scoped to this part · feeds back into the world's score.