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>