game.sound
Lanternwake's score, ambience, sound-effect library and mix.
It listens on the shared bus and never calls another slice:
| event | what it does |
|---|---|
sfx (key, worldPos?) | plays a library key, positionally when a position comes with it |
speak (key, text) | plays the cached voice line and ducks the bed and score under it |
lanternToggled (up) | the shutter opening or snapping shut |
clueFound / sigilSolved / cryptOpen | paper, the amber bell, the slab |
vicarSpotted / vicarLost | the sting, and the score crossfading between its two cues |
playerCaught | the catch, and the score going out under it |
phaseChanged | which cue is up at all |
The fourteen library keys
ui_click · ui_back · lantern_open · lantern_hood · clue_read · sigil_claim · door_heavy · footstep_stone · footstep_mud · heartbeat · sting_spotted · sting_lost · crypt_open · caught
Anything may emit them: Core.emit("sfx", "door_heavy", { x, y, z }). A key that
arrives twice inside its own hold window is played once, so a slice emitting
lantern_hood on the same frame this module hears lanternToggled produces one
sound rather than a flam.
The mix
Sound.master scales everything — the one knob a trailer is balanced on.
Loud: the spotted sting, a spoken line, the catch. Under them: the score at
0.30 exploring and 0.54 hunted, the bed at 0.42, footsteps at 0.30. A line
ducks the bed to 55% and the score to 34% for as long as it takes to say.
The heartbeat is a sustained loop whose level and pitch ride Core.state.fear
and nothing else. Footsteps are metered by distance travelled — and shut off
permanently the first time another slice emits a footstep of its own, so the two
can never double up.
Two shapes of source, and they are not interchangeable
- Sustained — the bed, the two score decks, the heartbeat. Built with their
clip and
autoplay = trueat creation, never started or stopped again; the mix moves them byvolumealone. Each score deck owns one cue for the life of the session, so a cue change is two level ramps rather than a clip swap. - One-shot — every library key and every spoken line, through
playOneShot. The sfx, voice and spatial buses carry no clip of their own at all.
A multi-megabyte PCM clip takes several seconds from being referenced to being
audible, which is why every sustained clip is bound in build().
The clips
Baked assets under /zero/source/audio/, made by audio.clipforge. The bed and
the score are codec = "pcm" and seamless end to end — the bed's loop point is
a smaller sample-to-sample step than the clip's own average, so it does not
click. One-shots are Opus and stay small.
require("game.sound").debug() reports every level, which cue is up, and any
library key whose clip is missing.
Scoped to this part · feeds back into the world's score.