Files
benilla-octo/crates
gromandClaude Opus 5 8fcab8517b The MasterSoundEffects cut has to ride the play queue, not an observer a frame later
grom, 2026-11-05: *"softspoken was repeating zone readouts over and over unless I manually
removed them from queue."*

1.12 has no verb that stops a playing sound, so every voice-over addon uses the one
primitive there is: `SetCVar("MasterSoundEffects", 0)` then `..., 1)`, whose ENABLING edge
walks the live instance list and stops each node (`0x457500` -> `0x7a6570` -> `0x7a67a0`).
SoftSpoken's `SoundUtils:StopSound` is exactly those two lines, and its `SoundQueue` spends
them twice over: to duck the game's NPC dialogue while it speaks, and — this report — to
stop its OWN clip before replaying one, which `StopKeeping` does and comments on ("it
replays from the start when Advance next reaches it, since the client has no seek").

`bb9eb084` had to exempt `PlaySoundFile` audio from the cut because stopping it silenced
SoftSpoken outright: the addon spends the toggle and THEN plays its own file, so a cut that
reached files took the very line the toggle made room for. Its doc said what that cost —
*"if the overlapping-replay symptom returns, this is why"*. It returned.

The ordering was the thing, not the exemption. `cut_effects_on_reenable` is already
`.after(UiInput).before(drain_ui_sounds)`, which looks like the right gap and is not: the
flag is armed by the `CvarChanged` observer, `CvarChanged` goes out through
`commands.trigger`, and so the observer fires at a command flush a frame AFTER the Lua pass
that wrote it — by which time the drain has started the new clip. The consumer's ordering
cannot fix a producer that is a frame late.

So the cut now rides the sound queue, in call order with the plays:

  * `SoundRequest::CutEffects`, pushed from `script::cvars::store` — the one write path
    every `SetCVar`, `ConsoleExec` and engine write goes through, and the only place that
    holds both the old value and the new, which an edge needs.
  * `drain_ui_sounds` walks the queue in order and spends it where the addon wrote it:
    stop, then play. Its catalog guard moved inside the loop, because the cut needs neither
    a catalog nor assets and a stop dropped for want of one leaves the clip it meant to
    stop playing under the next one.
  * `cut_effects` is now shared by both paths with a `files` flag. In order with the
    addon's calls it stops files, because the stop was written before the play; a frame
    late, from the observer, it still spares them, because by then the only file alive is
    the one the toggle made room for. Both answers were paid for by a report, and
    `stopped_by_reenable`'s doc says which is which.

Verified: `the_master_sound_toggle_queues_its_cut_ahead_of_the_file_that_follows_it` writes
SoftSpoken's own two calls and asserts the queue is `[CutEffects, File(...)]` — the stop
keeping its place in front of the play — with no cut for the disabling edge and none for a
re-assert. `only_the_in_order_cut_reaches_an_addons_own_file` pins both answers.
benilla-ui 2128/0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
2026-11-05 10:37:43 -07:00
..