Files
BT411/scratchpad/night8/audiopool.sh
T
Joe DiPrimaandClaude Opus 5 ad9dfade88 #32: POOL the OpenAL sources instead of creating and destroying one per sound
Every 4.11.674 player log is saturated with acquisition failures -- 3031, 4657,
5245, 6275, 6571 across five machines.  In Lynx's largest session the first
failure lands 9.3% in and they continue to 96.8%: once it starts it never
recovers for the rest of the match.

CAUSE: RequestAudioChannels called alGenSources() per sound event and
ReleaseSourceSet called alDeleteSources() on release -- create and destroy per
sound.  OpenAL sources are a scarce driver resource (OpenAL Soft caps a context
at 256) and a combat burst churned straight through the ceiling.

The two counters looked contradictory and were the tell: ACQUIRE FAILED always
printed live=256 while the 30s census printed live=6.  Same global, sampled at
different moments -- sources spike to the cap during a burst and drain back
between them.  Churn, not a steady leak.

FIX: generate sources once, up to a cap, and recycle them through a free list.
Release scrubs and parks instead of deleting.  Steady-state play performs no AL
allocation at all.

The scrub is load-bearing, not hygiene: a recycled source carries whatever the
previous owner set, and the engine sets AL_LOOPING per sound
(L4AUDLVL.cpp:327).  Hand a looping source to a one-shot and it plays forever --
which is the "sound stuck looping" family (#51, #5).  Every reusable property is
reset at the single point where a source changes owner.

VERIFIED (scratchpad/night8/audiopool.sh + the two-node mp_burst.sh):
  solo, sustained fire  : 0 failures, pooled 152, reuses 21998
  two nodes, 4 min      : 0 failures on both, pooled 148/149, reuses ~7000 each
⚠ HONEST LIMIT: the bench does NOT reproduce the field failure -- the PRE-fix
binary also scores 0 on it (peak 49 live), because solo/2-node combat is not
dense enough to reach 256.  So this verifies the pool works and allocates
nothing in steady state; it does NOT by itself prove the field failures are
gone.  The five field logs remain the "before".

Peak demand is set by how many audio COMPONENTS are alive (each reserves a
SourceSet of up to 25 voices and holds them), not by audible sounds: measured
high-water 138 solo, 149 two-node.  That scales with player count, so the cap is
set near the driver ceiling (240) and the pool now logs its high-water mark once
per 25-source band -- so the next playtest sizes this from field data instead of
a guess.  Growth also self-limits: if a driver offers fewer sources than the cap,
alGenSources simply fails, growth stops, and the pool recycles what it has.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 10:29:04 -05:00

42 lines
1.4 KiB
Bash

#!/usr/bin/env bash
# #32 AUDIO SOURCE POOL verification.
#
# BEFORE: sources were alGenSources'd per sound event and alDeleteSources'd on
# release. Every 4.11.674 player log shows thousands of ACQUIRE FAILED lines
# (3031-6571 across five machines), first failure ~10% into the match, still
# failing at 97% -- combat bursts churned through OpenAL's 256-source context
# limit.
#
# AFTER: sources are generated once and recycled. This run reproduces the
# failure mode -- sustained combat, all weapon groups firing, missiles, deaths
# and explosions -- and reads the census.
#
# PASS:
# * ACQUIRE FAILED lines == 0
# * census `pooled` settles well under the 200 cap (steady-state demand)
# * census `reuses` climbs into the thousands (recycling is doing the work)
set -x
. /c/git/bt411/scratchpad/night6/bench_common.sh
cd /c/git/bt411/content || exit 1
taskkill //F //IM btl4.exe > /dev/null 2>&1
sleep 2
sed "s/^map=.*/map=grass/; s/^time=.*/time=day/" MP.EGG > AUD.EGG
LOG=audiopool_${1:-post}.log
rm -f "$LOG"
BT_DMG_LOG=1 \
BT_SPAWN_ENEMY=1 BT_GOTO=enemy BT_GOTO_STOP=2 \
BT_AUTOFIRE=1 BT_AF_MISSILE=1 BT_AF_PERIOD=1 \
bt_launch "$LOG" AUD.EGG 0x03
sleep 200
taskkill //F //IM btl4.exe > /dev/null 2>&1
sleep 2
echo "=== ACQUIRE FAILED (want 0) ==="
grep -c "ACQUIRE FAILED" "$LOG"
echo "=== census progression ==="
grep -oE "\[audio\] source census:.*" "$LOG" | head -3
grep -oE "\[audio\] source census:.*" "$LOG" | tail -3