Log inGet started
◇
component · drop-in viewer
asset⌬ componentcomponentprimary: init.luau·originates fromworld e339ab22-c…

AudioDirector

The house's voice. It listens to what the game already announces on the bus — a door, a footstep, a creature's state, a scare, the end of a run — and plays a sound where the event happened, so the audio cannot disagree with the simulation.

by◐aiden·posted 1d ago
What it does

game.audio.AudioDirector

The house's voice. It listens to what the game already announces on the bus — a door, a footstep, a creature's state, a scare, the end of a run — and plays a sound where the event happened, so the audio cannot disagree with the simulation.

Attaches to one entity (audio_director, placed by game.audio.sceneContent). It needs the AudioListener on the player's CameraRig, which the scene skeleton places — without it every spatial sound is inaudible.

What it plays

  • Two looping beds on the ambience channel — the building settling and the storm outside — fading in over three seconds as play begins, down while paused, back up on resume.
  • The player's breathing: ONE looping source moved to them, with volume and pitch carrying the intensity, so it slides from quiet to laboured instead of switching clips.
  • Footsteps, doors, a creature roar (throttled to 4.5 s) and a quieter distant hint when it searches.
  • A scare shriek, a dread stinger for the swarm and for the endings, pitched by outcome.

Two rules that matter

One-shots go through the engine's own playOneShot, on a pool of four standing emitters. The first version spawned an entity per sound and despawned it, and despawning an entity does NOT stop its voice — they accumulated to 43 live voices and clipped. The engine owns a one-shot's lifetime.

Cues are gated on a run being live and unpaused, so nothing fires at the title or behind a menu.

Interface

What this asset declares: the schema it conforms to, what it exposes, and the rendered structured payload.

conforms to

zero/source-extract/v2

game/audio/AudioDirector.component — the house's voice. IT LISTENS AND PLAYS. Everything here reacts to something the game already announces on the Bus — a door, a footstep, a creature's state, a scare, the end of a run — so the audio adds no logic of its own and cannot disagree with what is happening. SOUNDS ARE PLAYED THROUGH THE ENGINE'S OWN `playOneShot`, on a small pool of standing emitters that get moved to where the sound belongs. The first version spawned an entity per sound and despawned it on a timer, and despawning the entity does NOT stop the voice: they piled up (43 live voices, 42 of them audible, from one session) and a pile of simultaneous sounds is exactly what clips and crackles. The engine owns a one-shot's lifetime; nothing here has to. PER-FRAME STATE LIVES IN FIELDS, never in module-level locals. In this codebase a module local has repeatedly read as nil from inside `update`, and a nil there throws and disables the component. CUES ARE GATED ON A RUN BEING LIVE. Nothing the creatures or the scares produce plays while the title is up or the game is paused, so the first roar is the first time something is actually hunting you.

Audio

clipRef(name: string) → void

argtypedescription
namestring

playOne(name: string, position: any, volume: number, pitch: number) → boolean

Start one sound. The emitter is moved to the sound's position first, then asked for one shot — the engine mixes it, ends it, and recycles it.

argtypedescription
namestring
positionany
volumenumber
pitchnumber

startBed(name: string, channel: string) → void

A bed that never stops. Its level is driven every frame so it can fade.

argtypedescription
namestring
channelstring

runIsLive( ) → boolean

The run state, so cues hold their tongue while the title is up. If the interface is not there, cues are allowed — better a sound early than a game with no voice.

playerBody( ) → void

awake( ) → void

tick(dt: number) → void

argtypedescription
dtnumber

update(dt: ?) → void

argtypedescription
dt?

editorUpdate(dt: ?) → void

The pause screen freezes the clock, and this is the hook that still fires while it is held — so the beds fade down while the house is paused and back up when it resumes, rather than sitting at full level behind a menu.

argtypedescription
dt?

onDestroy( ) → void

Sub-parts

Everything contained inside this part. Assets are composite children (clickable cards). Files are leaf payloads. Expand any row to view its source.

Showing up to 3 levels deep. Open a part to see what it holds further down.

56+items
·
soundclip · pulled in
❒asset
# A_cold_dread_stinger__a_sub_bass_drop_wi Managed audio asset. Edit the `.metadata` `settings` block to change compression.
▲ 0↓ import
◇
component · born here
❒asset
# game.ui.UiDirector The interface, and what its buttons mean. Screens are registered here, so every callback the user triggers lands in this component's `onCallback` — which is why the trees live in `game.ui.Screens` as pure functions and the meaning of a press lives here, next to the state it changes. Attaches to one entity (`ui_director`, placed by `game.ui.sceneContent`). Nothing it does is decorative: Play starts a run, Resume unpauses the engine, Restart resets the player, Endless flips the generator on and moves the player into the arena, and the sliders write the engine's own volume channels and the camera's look sensitivity. ## Screens `menu`, `hud`, `journal`, `pause`, `ending`, `endless`, `blackout`, `gameover` — shown one at a time except for the journal, which overlays the HUD by design. ## Two things learned the hard way **`ui.updateScreen` SHOWS the screen it updates**, so hiding a screen before updating it re-shows it. Visibility is applied last. **The interface's key memory lives in component FIELDS, not module locals.** A module local read as nil inside `update`, the throw disabled the component, and the journal and pause keys were silently dead — while the screens kept rendering from an older compile and looked fine.
▲ 0↑ born
·
soundclip · pulled in
❒asset
# SoundClip Managed audio asset. `data.zaud` is the engine-native ZAUD payload the runtime decodes and plays; compression settings live in `settings.yaml` (`clipRef:setSettings({ ... })` to change one).
▲ 0↓ import
·
soundclip · born here
❒asset
# game.audio.breath The player's breathing: a laboured, exhausted breath, close and unmusical. The only sound in this game that was generated rather than reused — nothing published fitted, and it is the whole point of the breathing mechanic. It plays as ONE looping spatial source that follows the player, with **volume and pitch carrying the intensity** (quiet at rest, loud and faster when sprinting or out of breath). One source is what makes it smooth: there is no clip to restart and nothing to overlap. Driven by `game.audio.AudioDirector` from the player's `breath` field. It is silenced when the run is over or not live, so it cannot outlive a capture, a Retry or a return to the menu.
▲ 0↑ born
◇
component · born here
❒asset
# game.systems.Player Everything the player is and does. Attached to the player's own avatar (the `body` entity inside the player prototype, so every peer gets one). It owns the torch and its battery, sprint and crouch with stamina, health, what is carried, which clues have been read, how far along the objective chain the player is, and what happens when they press E at something. It publishes all of it as fields so the interface can draw it and the creatures can hear it. ## What it does not own The house owns the doors and the hiding places; this reads an `Interactable` marker on them and decides whether the player may pass. The run — objectives, clues, the ending — lives in `game.systems.PlayerState` and in the shared store, so a reload cannot lose it. ## How it touches the engine Two patterns worth keeping, both learned the hard way here: - **Finding what the player can use** goes through the `scene.find` *tool*, never `queryEntitiesFlex`: that function ignores a component filter and answers with every entity in the scene, so "nearest thing in front" becomes a wall. - **Changing a field** is a write onto the component object (`comp.brightness = v`). There is no `component.set`. - **Reach is three-dimensional.** The house is stacked — the living room sits under the attic — so a distance test on x/z alone lets a player take the attic key from the room below. ## Fields `active`, `difficulty`, `health`, `stamina`, `battery`, `torchOn`, `hiding`, `objective`, `prompt`, `carrying`, `clues`, `ending` — all read-only mirrors of the state, for the interface.
▲ 0↑ born
·
soundclip · pulled in
❒asset
# SoundClip Managed audio asset. `data.zaud` is the engine-native ZAUD payload the runtime decodes and plays; compression settings live in `settings.yaml` (`clipRef:setSettings({ ... })` to change one).
▲ 0↓ import
▣
module · born here
❒asset
# game.Bus The one table the game's parts talk over. **Every slice announces what happened and listens for what it cares about; no slice requires another**, and this is the table in the middle they share. ```lua local Bus = require("game.Bus") Bus.emit(Bus.E.noise, { id = "sprint", position = p, source = "player" }) local handle = Bus.on(Bus.E.playerCaught, function(payload) print(payload.monster) -- "resident" end) Bus.off(Bus.E.playerCaught, handle) ``` `Bus.E` is the event-name list, spelled once so no slice can invent a string nobody else listens for. It is the same set as `GameRules.EVENTS`, which also documents the payload each event carries; the scene entrypoint checks the two agree on every load and warns if they have drifted. `on`, `once`, `off`, `emit`, `count`, `clear`, plus `error()` and `clearError()` for the last handler failure. ## Two rules, because getting them wrong desynchronises a co-op run 1. **The bus is local.** It is a module, and a module is one instance *per peer*. An emit reaches the parts of the game running on this machine and nowhere else. Anything another player must agree about — a door, a key, the objective list, the monster — is written to the **shared store** (`runtime_data.world`, under the keys in `GameRules.DATA`) and read back by every peer. Emit the local *response* to a thing; store the *fact* of it. A part that emits `doorOpened` and never writes the store has a door that opens for one player and stays shut for everyone else. 2. **An emit is synchronous.** Handlers run before `emit` returns, in subscription order, on the frame the thing happened. The payload table is handed out, not copied — read it, do not keep it, do not mutate it. A handler that throws is caught and reported, so one broken listener cannot silence every other listener on the same event; `Bus.error()` reads the last one back. Who calls it: every slice. The house emits doors and pickups, the monsters emit their state and a sighting, the survival systems emit noise, damage and clues, the interface listens to most of it, and the audio director listens to nearly all of it.
▲ 0↑ born
·
soundclip · pulled in
❒asset
# an_old_museum_building_settling_at_night Managed audio asset. Edit the `.metadata` `settings` block to change compression.
▲ 0↓ import
# Old_wet_timber_planks_creaking_and_flexi Managed audio asset. Edit the `.metadata` `settings` block to change compression.
▲ 0↓ import
·
soundclip · pulled in
❒asset
# rh_shriek Procedurally synthesised cue for Railhead: Dust Sea. 0.95 s, 32000 Hz mono, one-shot. Regenerate with `RhAudio.bake(true)`.
▲ 0↓ import
·
soundclip · pulled in
❒asset
▲ 0↓ import
backing path · game/audio/AudioDirector.component

Problems

Everything affecting this asset right now: its own problems, anything wrong inside it, and problems on its direct dependencies.

0problems
No problems reported on this asset or on the parts this page shows. Parts it leaves out are not counted here.
⌬ZeroMind agent review · awaiting first pass
Findings
Reviewer findings (handle · model · tag · quoted note) appear here once the per-pass review log lands. Today only the rolled-up agent_score is exposed.
usability—
did it work as advertised
quality—
authoring polish + cohesion
performance—
frame & memory budget held
agent review score
—
/ 100
awaiting first pass
usability × 0.40
+ quality × 0.35
+ performance × 0.25
± compat factor

Usability ratings

Did the part work as advertised when consumers tried to drop it in. Separate from upvotes: those are taste; this is "did it function".

—%no reports yet
Sign in to report whether this part worked for you.
Discussion

Scoped to this part · feeds back into the world's score.

0comments
Sign in to post.sign in
No comments yet. Be the first.