docs(audio): D207 M-78b — user Bunker repro; player-gun double-buffer lead

User repro: player gunshots specifically go permanently muted mid-firefight
and stay muted after the alarm ends; all other SFX fine. Not generic pool
starvation — a stuck-state bug in the gunfire.c:3178 two-slot double buffer
(audioHandle / field_A48). Leading mechanism: asynchronous sndDeactivate
posts to the 64-slot SFX event queue; under firefight+alarm load the queue
saturates, alEvtqPostEvent drops the deactivate, the state never disposes,
the slot never clears — once both slots stick, no player gunshot can play.
All three pool sizes are byte-match stock GE => port timing bug, not a value
to bump. Next: read the user's audiotrace.log to confirm evtq saturation +
uncleared slots.

Claude-Session: https://claude.ai/code/session_01U7gyYC3pcatXHhD1kPCgsR
This commit is contained in:
James Dansereau committed 2026-09-08 07:56:02 -04:00
1 parent 2f503f007d
commit 0f7fbcecdf
1 file changed
+48
+48
View File
@@ -8183,3 +8183,51 @@ listening pass on a real playthrough is still owed before calling it closed.
`powershell -Command "& bash.exe script.sh"`. Every `GE_*` probe silently
read as unset, which looks exactly like "the probe is broken". `cmd.exe`
inherits normally, so `audiodebug.ps1` generates a `.bat` and runs that.
### M-78b — user repro on Bunker: symptom is specific and permanent
Natural Bunker alarm trigger (spawn, go forward into the room ahead, shoot
one of the two guards → the other trips the alarm), then sustained firefight.
User by-ear result:
- Most SFX keep playing during the klaxon.
- **Eventually the *player's* gunshots stop being emitted — and stay muted
even after the alarm ends.** Enemy gunfire and all other SFX continue fine
during and after.
This is NOT generic pool starvation (that would drop random sounds
transiently). It is specific to the player-weapon fire sound and it is
*permanent* — a stuck-state bug, not a transient voice shortage.
**Structural lead (static, unconfirmed — needs the capture).** The player
fire sound is played through a **two-slot double buffer** in
`bondfirefunc` / `gunfire.c:3178-3202`: `handptr->audioHandle` and
`handptr->field_A48`. Each trigger interval: `sndDeactivate` whichever slot
is currently *playing*, then `sndPlaySfx(..., &slot)` into whichever slot is
*NULL* (`audioHandle` first, else `field_A48`). If **both** slots are ever
left non-NULL-but-not-playing at once, the `else if (field_A48 == 0)` guard
fails and **no further player gunshot can play, ever** — matches the symptom
exactly.
A slot is cleared only by `sndUnlinkClearSound` (via `sndDisposeSound`)
following the state's back-link `state->state->link.next`. `sndDeactivate`
is **asynchronous** — it posts `AL_SNDP_DEACTIVATE_EVT` to the 64-slot SFX
event queue (`MUSIC_SFX_SEQ_CONFIG_MAX_EVENTS = 0x40`). If that queue is
saturated (heavy firefight + the alarm's self-retrigger posts + every
ricochet/impact/yelp posting PLAY/PLAY_EVT/STOP/DEACTIVATE), `alEvtqPostEvent`
**drops the event silently** → the deactivate never runs → the state never
disposes → the slot never clears. Once both slots are stuck, permanent mute.
The `[VOICES] ... evtq=N/64` probe already in `snd.c` will show queue
saturation; `[SLOTWRITE]` + `[VOICE+]/[VOICE-]` + `sndPlaySfx t=` trace the
two player-gun slots. All three counts (8 voices / 64 states / 64 events) are
byte-match stock GE — so if this is the mechanism it is a port *timing* bug
(the SFX event queue drained too infrequently vs N64's RCP cadence, D204
family), not a value to bump.
**Next:** read the user's `build-pc/audiotrace.log` — confirm (1) `evtq`
approaches 64, (2) a `[AUDIOTRACE] sndDeactivate: state=X` with no following
`[VOICE-] state=X` / dispose, (3) two `[SLOTWRITE] slot=<audioHandle>` and
`slot=<field_A48>` with no clear afterwards. Then the fix is port-side: drain
the SFX event queue more often, or (cheaper, D202-family-consistent) a
bounded self-heal in the double-buffer — if a slot has pointed at a
non-playing state for > N ms, force-clear it.