Audio: fix stuck weapon charge/sustain/loading loops -- maintain GaugeAlarm54 oldState (task #50)

WeaponState -> weaponAlarm (mechweap.cpp:149), and the weapon's looping audio
(LaserACharge/LaserASustain/MissileLoading, all SF2 sampleModes=1) is STARTED by an
AudioStateWatcher on the firing/charging state and STOPPED by an inverse
AudioStateTrigger that keys on old_state (== the state being LEFT).

The subsystem-state commit fired GaugeAlarm54 watchers on SetLevel but deliberately
left levelB (the oldState slot @0x10) untouched to avoid disturbing the weapon's
LevelCountB read.  Consequence: the audio watcher's StateChanged(oldState,newState)
saw a stale oldState, so the inverse/STOP triggers never matched -> the charge/
sustain/loading loops played forever.

Fix: SetLevel now sets levelB := level (oldState := currentState) before the change,
exactly like StateIndicator::SetState.  The stop triggers now match on leaving the
state and StopNote the loop.  This is also authentic: in the binary the 0x54 alarm
+0x10 IS oldState, and the weapon's LevelCountB()/weaponIdle read of +0x10 is that
same oldState (the old static-count reading was the reconstruction's accident).
weaponIdle is only consumed in the MP replicant record-apply path, so single-player
firing is unaffected; the value is now the authentic oldState-based one.

Subsystem state audio unaffected (0 skips). Diagnostics: BT_AUDIO_LOG now traces
SetupPatch src/file/loop + StopNote.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-15 22:11:49 -05:00
co-authored by Claude Opus 4.8
parent 41c8ac120d
commit c27af4800b
2 changed files with 10 additions and 3 deletions
+3 -2
View File
@@ -141,7 +141,7 @@ void
if (sourceState == AL_INITIAL)
{
alSourcei(sourceSet.sources[i],AL_BUFFER,AL_getBuffer(info.bufferIndex));
{ static int s_a=0; if (getenv("BT_AUDIO_LOG") && s_a++<8) DEBUG_STREAM << "[audio] SetupPatch attach bank=" << (int)bankID << " patch=" << (int)patchID << " buf=" << info.bufferIndex << "\n" << std::flush; }
{ static int s_a=0; if (getenv("BT_AUDIO_LOG") && s_a++<24) DEBUG_STREAM << "[audio] SetupPatch src=" << sourceSet.sources[i] << " file=" << (info.file?info.file:"?") << " loop=" << (info.loop==ForceStatic?0:1) << "\n" << std::flush; }
alSourcei(sourceSet.sources[i],AL_SOURCE_RELATIVE,AL_TRUE);
AudioRenderer *render = application->GetAudioRenderer();
@@ -190,7 +190,7 @@ void PatchLevelOfDetail::PlayNote(SourceSet sourceSet)
if (state != AL_PLAYING)
{
alSourcePlay(sourceSet.sources[i]);
{ static int s_p=0; if (getenv("BT_AUDIO_LOG") && s_p++<8) DEBUG_STREAM << "[audio] PlayNote alSourcePlay src=" << sourceSet.sources[i] << "\n" << std::flush; }
{ static int s_p=0; if (getenv("BT_AUDIO_LOG") && s_p++<30) DEBUG_STREAM << "[audio] PlayNote alSourcePlay src=" << sourceSet.sources[i] << "\n" << std::flush; }
}
ALenum error = alGetError();
@@ -205,6 +205,7 @@ void PatchLevelOfDetail::PlayNote(SourceSet sourceSet)
void PatchLevelOfDetail::StopNote(SourceSet sourceSet)
{
{ static int s_s=0; if (getenv("BT_AUDIO_LOG") && s_s++<30) DEBUG_STREAM << "[audio] StopNote count=" << sourceSet.count << " src0=" << (sourceSet.count>0?sourceSet.sources[0]:0) << "\n" << std::flush; }
if (sourceSet.count >0 && sourceSet.sources != NULL)
{
alSourceStopv(sourceSet.count, sourceSet.sources);
+7 -1
View File
@@ -97,7 +97,13 @@ public:
: audioWatcherSocket(0), videoWatcherSocket(0), gaugeWatcherSocket(0)
{ levelA = levelB = levels; level = 0; }
void Initialize(int levels) { levelA = levelB = levels; level = 0; }
void SetLevel(int n) { if (n != level) { level = n; NotifyWatchers(); } }
// StateIndicator::SetState semantics: oldState (levelB@0x10) := currentState
// BEFORE the change, so the audio watcher's StateChanged(oldState,newState) is
// correct -- this is what the inverse/STOP AudioStateTriggers key on (old_state ==
// firing/charging state) to StopNote a looping charge/sustain/loading sample when
// the weapon leaves that state. (In the binary levelB@0x10 IS oldState; the
// weapon's LevelCountB() reading @0x10 is that same authentic oldState.)
void SetLevel(int n) { levelB = level; if (n != level) { level = n; NotifyWatchers(); } }
int GetLevel() const { return level; }
int Level() const { return level; }
int LevelCountB() const { return levelB; } // +0x10 (the weapon update-record "reset" source)