paavohtl Claude Opus 4.6 (1M context) commited on
Commit
882d6f3
·
1 Parent(s): 74fd7c8

fix: DSSound channel priority stored wrong value, preventing sound eviction

Browse files

core_play_sound stored flags_and_slot (sound ID) in desc.flags_b instead
of priority (flags). This made all channels appear high-priority (IDs like
55, 121) so new sounds with real priority (1-8) could never evict them.
Channels accumulated until all 8 were permanently occupied, blocking all
subsequent pooled sounds (explosions, sizzle, etc.).

Also fixed stop_channel to unconditionally return pool entries to free list
even when the descriptor was already evicted by allocate_channel. WA does
this; without it, evicted pool entries leak and pool_free_count drops to 0.

Also fixed stop_channel used-list removal to use swap-remove (matching WA)
instead of shift-down.

Added sound_priority replay test.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

crates/openwa-core/src/audio/dssound.rs CHANGED
@@ -518,7 +518,7 @@ unsafe fn core_play_sound(
518
  // Store descriptor state.
519
  let desc = &mut snd.channel_descs[di];
520
  desc.flags_a = -1;
521
- desc.flags_b = flags_and_slot as i32;
522
  desc.channel_freq = channel_freq;
523
  desc.channel_volume = vol_scaled;
524
  desc.ds_buffer = core::mem::transmute_copy(&new_buf);
@@ -710,46 +710,46 @@ pub unsafe extern "thiscall" fn stop_channel(this: *mut DSSound, pool_id: i32) -
710
  }
711
  let snd = &mut *this;
712
  let desc_idx = snd.buffer_pool_shadow[idx];
713
- if desc_idx < 0 || desc_idx as usize >= 8 {
714
- return 0;
715
- }
716
- let di = desc_idx as usize;
717
- let desc = &mut snd.channel_descs[di];
718
 
719
- // Stop and release the buffer.
720
- if let Some(buf) = desc.take_buffer() {
721
- let _ = buf.Stop();
722
- // Release happens when buf drops
723
- }
724
 
725
- // Reset descriptor to free state.
726
- desc.channel_freq = 0;
727
- desc.channel_volume = 0;
728
- desc.flags_b = -1;
729
- desc.flags_a = -1;
 
 
 
 
 
 
730
 
731
  // Mark pool shadow as free.
732
  snd.buffer_pool_shadow[idx] = -1;
733
 
734
- // Return pool index to free list.
 
 
735
  let free_slot = snd.buffer_pool_free_count as usize;
736
  snd.buffer_pool_free[free_slot] = idx as u32;
737
  snd.buffer_pool_free_count += 1;
738
 
739
- // Remove from used list.
 
740
  let used_count = snd.buffer_pool_used_count as usize;
741
  for i in 0..used_count {
742
  if snd.buffer_pool_used[i] == idx as u32 {
743
- // Shift remaining entries down.
744
- for j in i..used_count - 1 {
745
- snd.buffer_pool_used[j] = snd.buffer_pool_used[j + 1];
746
- }
747
  snd.buffer_pool_used_count -= 1;
748
- break;
 
749
  }
750
  }
751
-
752
- 1
753
  }
754
 
755
  /// Slot 12: load_wav — load a WAV file into a DirectSound secondary buffer
 
518
  // Store descriptor state.
519
  let desc = &mut snd.channel_descs[di];
520
  desc.flags_a = -1;
521
+ desc.flags_b = priority;
522
  desc.channel_freq = channel_freq;
523
  desc.channel_volume = vol_scaled;
524
  desc.ds_buffer = core::mem::transmute_copy(&new_buf);
 
710
  }
711
  let snd = &mut *this;
712
  let desc_idx = snd.buffer_pool_shadow[idx];
 
 
 
 
 
713
 
714
+ // If the descriptor is still valid, stop and release the buffer.
715
+ // (desc_idx may be -1 if the channel was evicted by allocate_channel)
716
+ if desc_idx >= 0 && (desc_idx as usize) < 8 {
717
+ let di = desc_idx as usize;
718
+ let desc = &mut snd.channel_descs[di];
719
 
720
+ // Stop and release the buffer.
721
+ if let Some(buf) = desc.take_buffer() {
722
+ let _ = buf.Stop();
723
+ }
724
+
725
+ // Reset descriptor to free state.
726
+ desc.channel_freq = 0;
727
+ desc.channel_volume = 0;
728
+ desc.flags_b = -1;
729
+ desc.flags_a = -1;
730
+ }
731
 
732
  // Mark pool shadow as free.
733
  snd.buffer_pool_shadow[idx] = -1;
734
 
735
+ // ALWAYS return pool index to free list, even if descriptor was already
736
+ // evicted. WA does this unconditionally — without it, evicted pool entries
737
+ // leak and pool_free_count drops to 0, preventing new pooled sounds.
738
  let free_slot = snd.buffer_pool_free_count as usize;
739
  snd.buffer_pool_free[free_slot] = idx as u32;
740
  snd.buffer_pool_free_count += 1;
741
 
742
+ // Remove from used list (swap-remove with last entry, matching WA).
743
+ // Returns 1 if found in used list, 0 if not.
744
  let used_count = snd.buffer_pool_used_count as usize;
745
  for i in 0..used_count {
746
  if snd.buffer_pool_used[i] == idx as u32 {
 
 
 
 
747
  snd.buffer_pool_used_count -= 1;
748
+ snd.buffer_pool_used[i] = snd.buffer_pool_used[snd.buffer_pool_used_count as usize];
749
+ return 1;
750
  }
751
  }
752
+ 0
 
753
  }
754
 
755
  /// Slot 12: load_wav — load a WAV file into a DirectSound secondary buffer
crates/openwa-wormkit/src/replacements/sound.rs CHANGED
@@ -515,7 +515,7 @@ pub fn install() -> Result<(), String> {
515
  hook_play_sound_pooled_direct as *const (),
516
  )?;
517
 
518
- // Patch DSSound vtable: replace trivial slots with Rust implementations.
519
  patch_dssound_vtable()?;
520
 
521
  // Hook CTaskWorm::PlaySound2 (FUN_00515020) — 23 callers in WA
 
515
  hook_play_sound_pooled_direct as *const (),
516
  )?;
517
 
518
+ // Patch DSSound vtable: replace all 24 slots with Rust implementations.
519
  patch_dssound_vtable()?;
520
 
521
  // Hook CTaskWorm::PlaySound2 (FUN_00515020) — 23 callers in WA
testdata/replays/sound_priority.WAgame ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:82df26aa4a414d9798c64e9175b583f8e5fdca234e48378aa6b3fdb4616b2dcb
3
+ size 19943
testdata/replays/sound_priority_expected.log ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:06f587257881df251a020b001a512d50693e84f1516acf2fcf9a20920730eacf
3
+ size 2681