One playing sound node: K THREE.PositionalAudio voices (spatial) or one
THREE.Audio (non-spatial), its buffer decoded once, fades on the node gain,
and the audible ⇄ silent edges the slab rule produces.
Edge rules (SOUND_SPEC.md §3.1 / §4.1), per voice:
rising + continuous → loop, start after delay_ms, ramp 0 → gain over
fade_in_ms;
rising + once → start once after delay_ms, ramp in, report the end;
falling (either) → ramp to 0 over fade_out_ms, then stop;
a rising edge during a fade-out restarts the voice.
on_depart / on_arrive nodes ignore the slab's RISING edge: the engine
calls SoundNode.triggerFromWaypoint when the waypoint driver reports
the event, for the rows that belong to that waypoint (§4.3). An on_arrive
clip still fades out on the slab's falling edge (the visitor moved on, the
next story's narration must not overlap); an on_depart clip plays out —
by definition it starts when its own row has just stopped being audible.
A trigger that lands while the gate is closed, or before the clip decoded,
is kept pending and fires when either arrives.
attach_to moves every voice to the named node's bounding-box centre (in
this node's local frame) instead of its rows' displayed x/y/z; the centre is
re-resolved on every evaluation, so a node that loads later is picked up.
While the autoplay gate is closed, edges are still tracked but nothing starts;
opening the gate re-runs the rising edges — once nodes included, so the
opening story's narration is not lost to the tap (a deliberate reading of
§4.4, which only names continuous).
Three's Audio wires its gain node straight to the listener input in its
constructor; play() only reconnects the SOURCE side. So the bus routing here
(gain.disconnect(); gain.connect(bus)) is done once and survives every
start. PositionalAudio hard-codes panningModel = 'HRTF'; the engine's
choice overwrites it.
One playing sound node: K
THREE.PositionalAudiovoices (spatial) or oneTHREE.Audio(non-spatial), its buffer decoded once, fades on the node gain, and the audible ⇄ silent edges the slab rule produces.Edge rules (
SOUND_SPEC.md§3.1 / §4.1), per voice:continuous→ loop, start afterdelay_ms, ramp 0 →gainoverfade_in_ms;once→ start once afterdelay_ms, ramp in, report the end;fade_out_ms, then stop;on_depart/on_arrivenodes ignore the slab's RISING edge: the engine calls SoundNode.triggerFromWaypoint when the waypoint driver reports the event, for the rows that belong to that waypoint (§4.3). Anon_arriveclip still fades out on the slab's falling edge (the visitor moved on, the next story's narration must not overlap); anon_departclip plays out — by definition it starts when its own row has just stopped being audible. A trigger that lands while the gate is closed, or before the clip decoded, is kept pending and fires when either arrives.attach_tomoves every voice to the named node's bounding-box centre (in this node's local frame) instead of its rows' displayed x/y/z; the centre is re-resolved on every evaluation, so a node that loads later is picked up.While the autoplay gate is closed, edges are still tracked but nothing starts; opening the gate re-runs the rising edges —
oncenodes included, so the opening story's narration is not lost to the tap (a deliberate reading of §4.4, which only namescontinuous).Three's
Audiowires its gain node straight to the listener input in its constructor;play()only reconnects the SOURCE side. So the bus routing here (gain.disconnect(); gain.connect(bus)) is done once and survives every start.PositionalAudiohard-codespanningModel = 'HRTF'; the engine's choice overwrites it.