SAURON's "coolant flush sound glitched and perma" is one instance of a systemic
defect. SetupPatch (L4AUDLVL.cpp) decided looping from the SAMPLE flag alone:
AL_LOOPING = (info.loop != ForceStatic)
That armed every non-ForceStatic sample -- 224 of the 603 authored zones,
including unmistakable one-shots -- as an OpenAL source that never ends on its
own. Any such sound whose note-off never arrives plays forever. Measured 1946
LoopAtWill x Transient arming events in one short session.
The sample flag expresses PERMISSION; the SOURCE decides. Three facts [T1]:
* the enum's own comments (L4AUDLVL.h:14-19) -- LoopAtWill = "will play once
OR LOOP AS DESIRED", ForceStatic = "plays only once EVEN IF LOOPED" (a
veto), LoopAlways = "ramp up and then down";
* LoopAtWill is enum value 0, i.e. the DEFAULT an unauthored sample receives
(WTPresets.cpp:37) -- it cannot mean "loop forever";
* LoopAlways, the real always-loop, is used by ZERO shipped samples.
"As desired" is the source's authored AudioRenderType (Transient=one-shot vs
Sustained=held), streamed from AUDIO*.RES (AUDLVL.cpp:28) and already consulted
by the renderer (L4AUDRND.cpp:567, AUDREND.cpp:184). SetupPatch now takes it,
threaded from all three L4AudioSource call sites (Direct/Dynamic3D/Static3D).
New rule: LoopAlways->1, ForceStatic->0, LoopAtWill->the source's render type.
MEASURED before changing behaviour (BT_LOOP_AUDIT=1, scratchpad/loopaudit.py):
LoopAtWill x Transient -> loop=0 (was 1) 1946 events
LoopAtWill x Sustained -> loop=1 unchanged 65 events
ForceStatic x Transient -> loop=0 unchanged 292 events
The engine loops (EngineAccel07_z0..z2, EngineMotor01, EnginePower01) are all
LoopAtWill/Sustained and PRESERVED -- no regression there. All 24 reclassified
samples are genuine one-shots: laser charge/fire/explosion/loaded, missile
loading, engine shift, coolant pressure inc/dec.
A/B PROOF (scratchpad/loopab.py, same session both arms, only BT_LOOP_LEGACY
differs), asking the engine's own BT_AUDIO_DUMP what is still playing with
loop=1 long after every release:
[legacy] EnginePower01, CoolantPresInc03_z2, CoolantPresDcr03_z2
[fixed] EnginePower01
The two stuck coolant sources are the reported symptom. They were eventually
reclaimed when a later trigger reused the source, which is why the bug was
intermittent -- the "perma" case is when nothing else retriggers it. The fix
removes the mechanism.
BT_LOOP_LEGACY=1 restores the old rule for a field A/B without a rebuild. The
layers to listen to are the beam sustains: LaserA/CSustain* are authored
LoopAtWill on a TRANSIENT source, so they now end with the sample instead of
looping until note-off. The render type is T1 authored data; the interpretation
that LoopAtWill defers to it is a strong reading of the enum + defaults rather
than a disassembled statement, and is flagged as such in the KB.
Plausibly bears on #32 (audio cutting in/out late in a match): a stuck looping
source holds its pool slot for the rest of the round.
Rigs: scratchpad/flushsnd.py (flush start/stop pairing), flushsnd2.py (stuck-
source hunt), loopaudit.py (the classification matrix), loopab.py (the A/B).
KB: context/wintesla-port.md audio section, context/decomp-reference.md env
table (BT_LOOP_AUDIT / BT_LOOP_LEGACY / BT_AUDIO_DUMP / BT_AUD_TAIL),
docs/AUDIO_FIDELITY.md F38. checkctx.py CLEAN.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Field report: audio cutting in and out toward the end of the match.
Three structural defects in the port's source-pool lifecycle:
1. SourceSet.sources[] was uninitialized heap garbage until first
acquisition -- AL names are small ints, so a recycled-heap slot could
alias a LIVE source owned by another sound; alIsSource() skipped
generation, two sounds shared one source, and whichever released
first deleted the other's mid-play. SourceSet ctor now zero-inits.
2. ReleaseSourceSet's bulk alDeleteSources(count, sources) is ATOMIC on
invalid names (AL spec) -- one empty/-1 slot and NOTHING deleted;
after the first pool exhaustion every partial release leaked and the
pool never recovered. Now per-slot guarded delete, slots parked at 0.
3. Failed acquisitions stranded partial sets forever (dropped transients
are never released by anything). ReleaseChannels() handback at both
failure sites (StartRequest + dormant-resume) + a dtor backstop.
Verified: 2-node MP smoke (mission + lobby-loop relaunch clean, census
live/acquireFails 0/0) + 100s solo combat smoke (52 audio triggers,
zero AL errors). Awaiting live playtest confirmation at scale.
KB: context/wintesla-port.md audio-dropout section.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PARALLAX (#16): the pick/fire ray was anchored at mech.y+5.0 (a port
improvisation) while the sight line ran from the eyepoint (y=6.23) --
two parallel rays whose constant offset grew into the reported low-miss
as range closed (measured ry +0.072 @50u -> +1.54 @2.7u). The decomp's
sight and pick share the eye origin (HudSimulation @4b7830 chain). Fix:
the viewpoint mech's cockpit eye owns the aim-camera publish in BOTH
views, origin = its own eye translation; leveling + deliberate elevation
untouched; chase view now converges to cockpit ballistics (V cannot
change where shots land). After: pick pinned to the crosshair (ry <=
5e-6) from 50u to point-blank; 26 laser + 8 missile center-mass hits at
3/4-screen. Awaiting the reporters' approach-test.
VERDICT INSTRUMENTATION (#4 closed authentic, #5 verdict posted):
BT_RANGE_LOG per-frame pick tracing + BTGroundRayHitExact analytic
cross-check (0/8000 arena fall-throughs; cavern 6/8400 single-frame
grazes -- the 'crazy sliding' is the authentic world-pick + 500 m/s
slide over depth discontinuities); BT_AUD_TAIL StopNote/fade timing +
BT_FIRE_PULSE single-shot driver (the energy 'buzz' is the AUTHORED
charging loop: crescendo through recharge, 1.309s authored release,
measured within one frame). CAVERN.EGG: solo cavern test egg.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- decomp-reference.md: the binary ATTRIBUTE-TABLE section -- 16-byte
{id,name,off+1,0} row format (alignment warning), the walker technique,
and the three recovered tables (Mech ids 21-56 incl the corrected
EyepointRotation@0x360 / RearFiring@0x410 / DistanceToMissile@0x400
labels; HeatSink 3-12; Torso 3-15; the weapon RearFiring 'b' marker).
- combat-damage.md: the REAR-FIRE + look-view system (roster survey: the
Blackhawk's ERMLaser_2/3 are the game's only rear weapons; every missile
rack forward), the missile mount-frame launch truth, and the BANKED
full Missile flight-model decode (three performances, proximity fuse).
- wintesla-port.md: post-Phase-4 closures (instability model live, F14
static filter baked, the footstep warm-up bug + its trace-cap lesson);
deferred list trimmed to F21/HRTF.
- AUDIO_FIDELITY.md: status block -- F14 FIXED, warm-up bug noted.
- open-questions.md: "which mechs used rear fire" answered from data;
remaining look-view key bindings noted.
checkctx: CLEAN.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Correct the audio digest in wintesla-port.md + project-overview.md: the "no
sound ever" root cause was that BOTH engine/lib backend DLLs were no-op stubs
(libsndfile ordinal-only sf_open->NULL; OpenAL32 imports only KERNEL32). Now
real OpenAL Soft + LoadWavPCM; soundbank cracked (241 samples). Only remaining
gap = game triggering (AudioEntities).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
User reported never hearing any audio. Root-caused: the KB "Audio: DONE" was wrong.
The OpenAL playback chain IS fully implemented (device/buffers/sources; PlayNote
really calls alSourcePlay), but three gaps kept it silent:
1. GATED OFF (the "no sound" root cause): BTL4Application::MakeAudioRenderer returned
NULL unless the pod's AWE_FRONT/AWE_REAR AWE32-card env vars were set -- authentic
1995 pod behavior, dead on modern hardware, so the renderer was NEVER created.
FIXED: default audio ON (BT_NO_AUDIO=1 restores silence; AWE vars still force-on).
Verified: the OpenAL device now opens with no env vars ([audio] device OPENED).
2. Soundbank STUB: allPresets[2][100] (btstubs.cpp) is zero-init -> PRESET_isImplemented
false -> 0 buffers load. Sample data exists (AUDIO1/2.RES: EnginePower/LaserAFire/...)
but the event->sample map is gone (not in the decomp). STILL OPEN.
3. No triggering: the reconstructed game never creates AudioEntities on events. STILL OPEN.
So the device opens but nothing loads/plays yet -- enabling real sound needs the
soundbank reconstructed + the game triggers wired (a proper audio wave). Added a
BT_AUDIO_LOG trace harness across L4AUDRND/L4AUDRES/L4AUDLVL. KB corrected.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>