The effect cut must spare an addon's own PlaySoundFile — it silenced SoftSpoken outright

My last commit made the `MasterSoundEffects` 0→1 toggle stop every playing effect, as
the reference does. It stopped too much: grom's next session carries ten
`MasterSoundEffects re-enabled` lines and he heard **none** of the ten voice lines that
produced them.

The mechanism is not subtle once stated. Every 1.12 voice-over addon spends the toggle
*and then plays its own file* — SoftSpoken's `SoundQueue:PlaySound` is literally

    if Version.IsLegacyVanilla then
        self:MuteGameDialogue(channel, clip.cutsGameDialogue)   -- SetCVar 0; SetCVar 1
    end
    local willPlay = SoundUtils:PlaySound(clip, channel)        -- PlaySoundFile(clip.path)

so a cut that reaches file playback takes the very thing the toggle was spent to make
room for. `stopped_by_reenable` now excludes `kit == 0`, which is `play_file`'s own
marker for the `PlaySoundFile` path.

The two exclusions have different standing and the doc says so. Music and ambience are
**derived** — the reference's `flags & 0x4000008` class, muted rather than stopped
(`0x7a65a0`). `kit == 0` is **measured**: which flag class a streamed file falls in is
unreversed, so this is the player's own observation standing in for a fact I do not have.

What it costs is the other half of SoftSpoken's use of the toggle: `StopKeeping` spends it
to stop its own clip before replaying one, and that does nothing again, so the
overlapping-replay symptom may return. That is a worse-to-better trade, not a fix, and it
is written where it lives.

The stop now logs the kits it cut and how many file channels it spared, because a bare
count cannot separate "the bark died, as intended" from "we cut the line the addon was
about to speak" — which is exactly the distinction this commit got wrong.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
This commit is contained in:
2026-10-30 16:29:02 -06:00
co-authored by Claude Opus 5
parent a811f50e86
commit bb9eb08438
+51 -14
View File
@@ -1000,11 +1000,30 @@ pub(super) fn arms_effect_cut(was: bool, now: bool) -> bool {
now && !was
}
/// Which categories the enabling edge stops. `Sfx` alone: music and ambience are the reference's
/// `flags & 0x4000008` class, muted rather than stopped (`0x7a65a0`), so a toggle must not cost
/// the player their music — the point of the toggle is to cut a bark, not to restart the score.
fn stopped_by_reenable(category: SoundCategory) -> bool {
matches!(category, SoundCategory::Sfx)
/// Which channels the enabling edge stops: a kit-played `Sfx` channel, and nothing else.
///
/// Two exclusions, and they have different standing.
///
/// Music and ambience are **derived**: they are the reference's `flags & 0x4000008` class, muted
/// rather than stopped (`0x7a65a0`), so a toggle must not cost the player their music — the point
/// of the toggle is to cut a bark, not to restart the score.
///
/// `kit == 0`, the `PlaySoundFile` path ([`play_file`], whose doc records that id as its own
/// marker), is **measured, not derived**, and the distinction is worth keeping honest. Stopping
/// those too is what the first revision of this did, and it silenced SoftSpoken completely:
/// grom's session carries ten `MasterSoundEffects re-enabled` lines and he heard none of the ten
/// voice lines that produced them. It has to be excluded, because **every 1.12 voice-over addon
/// toggles this CVar and then plays its own file** — a cut that reaches file playback cuts the
/// very thing the toggle was spent to make room for.
///
/// Which of the reference's two flag classes a streamed file falls in is not settled here;
/// `0x4000008`'s meaning is unreversed, so this is a player's measurement standing in for a fact
/// we do not have. What it costs is the other half of SoftSpoken's use of the toggle:
/// `SoundQueue.StopKeeping` spends it to stop its *own* clip before replaying one, and that does
/// nothing again. If the overlapping-replay symptom returns, this is why — and the per-kit line
/// the stop now logs is what will say so.
fn stopped_by_reenable(kit: u32, category: SoundCategory) -> bool {
matches!(category, SoundCategory::Sfx) && kit != 0
}
/// Spend an armed `MasterSoundEffects` enabling edge: stop every playing effect, as the
@@ -1042,17 +1061,25 @@ pub(super) fn cut_effects_on_reenable(
return;
}
let before = out.channels.len();
// Named, not merely counted: which kits a toggle cuts is the only evidence separating "the
// bark died, as intended" from "we cut the line the addon was about to speak".
let mut stopped: Vec<u32> = Vec::new();
let mut spared_files = 0usize;
out.channels.retain_mut(|c| {
if !stopped_by_reenable(c.category) {
if !stopped_by_reenable(c.kit, c.category) {
spared_files += usize::from(c.kit == 0 && c.category == SoundCategory::Sfx);
return true;
}
stopped.push(c.kit);
c.handle.stop(mixer::declick());
false
});
let stopped = before - out.channels.len();
if stopped > 0 {
if !stopped.is_empty() || spared_files > 0 {
info!(
"sound: MasterSoundEffects re-enabled — {stopped} effect channel(s) stopped (0x7a67a0)"
"sound: MasterSoundEffects re-enabled — stopped {} of {before} channel(s), kits {:?}; \
spared {spared_files} PlaySoundFile channel(s) (0x7a67a0)",
stopped.len(),
stopped
);
}
}
@@ -1130,15 +1157,25 @@ mod tests {
assert!(enabled, "and leave sound effects on");
}
/// The toggle cuts effects and leaves the score alone.
/// The toggle cuts barks, leaves the score alone, and — the one grom's silence paid for —
/// never touches an addon's own `PlaySoundFile` audio.
#[test]
fn the_reenabling_edge_spares_music_and_ambience() {
assert!(stopped_by_reenable(SoundCategory::Sfx));
fn the_reenabling_edge_spares_music_ambience_and_addon_files() {
assert!(
!stopped_by_reenable(SoundCategory::Music),
stopped_by_reenable(1, SoundCategory::Sfx),
"a kit-played effect is the bark the toggle exists to cut"
);
assert!(
!stopped_by_reenable(1, SoundCategory::Music),
"music is the reference's mute-not-stop class (`flags & 0x4000008`, `0x7a65a0`)"
);
assert!(!stopped_by_reenable(SoundCategory::Ambience));
assert!(!stopped_by_reenable(1, SoundCategory::Ambience));
assert!(
!stopped_by_reenable(0, SoundCategory::Sfx),
"kit 0 is `play_file`'s marker, so this is an addon's own `PlaySoundFile`. Cutting \
it silenced SoftSpoken outright: every 1.12 voice-over addon toggles this CVar and \
then plays its own file, so the cut would take the line the toggle made room for"
);
}
/// Pinned as the 13 dwords at `.data` `0x87ce60`.