Skip to content

Fix Samus charge whoosh persisting after pause (hotfix)#130

Merged
JRickey merged 1 commit intomainfrom
agent/samus-charge-pause-pr
May 4, 2026
Merged

Fix Samus charge whoosh persisting after pause (hotfix)#130
JRickey merged 1 commit intomainfrom
agent/samus-charge-pause-pr

Conversation

@JRickey
Copy link
Copy Markdown
Owner

@JRickey JRickey commented May 4, 2026

Summary

  • Pause-init now force-stops every active FGM voice via the new portAudioPurgeFGMs in n_env.c, hooked from ftParamStopAllFightersLoopSFX in the existing pause path. Walks unk_alsound_0x3C directly and calls n_alSynStopVoice + n_alSynFreeVoice on every voice, then NULLs every siz34's unk_0x28.
  • This is a hotfix, not a root-cause fix. See the bug doc for the deeper IDO/LP64 divergence (audio thread isn't gated by an SP-task-done handshake on the port, so the FGM bytecode keeps cycling through pause).

Root cause vs hotfix

On IDO/N64 the audio thread is gated each frame by osRecvMesg(&sSYAudioSPTaskMesgQueue); while the pause menu owns the SP, the message stops landing, the FGM bytecode tick stops advancing, and looping voices wind down naturally because the wavetable loop region stops getting re-armed by case 1 ticks. The port's audio thread runs on the VI tick alone — no SP, no handshake — so the bytecode keeps cycling and the voice keeps playing.

The polite per-fighter ftParamStopLoopSFX(fp) route doesn't work on the port: siz34->unk_0x28 (the EE0C ptr cached when the bytecode allocated the current note's voice) goes stale across the bytecode's natural case-2 → 0 → free cycle. Per-frame trace logged on_0x3C=0 for every audio frame after pause — the EE0C the polite path wrote unk2A=2 to was already freed and the audible voice was on a different EE0C entirely.

Real root-cause fixes (out of scope here):

  • (a) Gate n_alAudioFrame on gSCManagerBattleState->game_status != Pause. Emulates SP starvation. Side effect: short SFX in their decay tail at pause cut off.
  • (b) Lock siz34->unk_0x28 reads/writes between game thread and audio thread. Lets the polite path work as designed. Invasive across the audio engine.

Either would replace portAudioPurgeFGMs and the ftParamStopAllFightersLoopSFX hook. Until then the queue-wide purge is the working hotfix.

Submodule pointer bump

Decomp pushed to JRickey/ssb-decomp-re#port-patches at 5cf15364c. The four-file diff under decomp/src/ rides along via this submodule update.

Test plan

  • Pre-refactor build (vendored src/, RelWithDebInfo Windows): whoosh cuts on pause, pause beep plays, resume continues without re-arming. User confirmed "Fixed".
  • Post-refactor build (decomp/ submodule, RelWithDebInfo Windows): not verified by me — the refactored origin/main has pre-existing MSVC build breaks unrelated to this fix:
    • decomp/include/stdarg.h:34 — re-defines va_list __builtin_va_list on MSVC where that intrinsic doesn't exist (C2061). Tried to fix with #ifndef _MSC_VER guard but reverted to keep this PR scoped.
    • decomp/src/mn/mnmaps/mnmaps.c:1073 (and ~28 other sites) — #ifdef PORT directives inside function-like macro arguments. Even with /Zc:preprocessor, MSVC's conforming preprocessor rejects these as undefined behavior (C2059); this is a pre-existing decomp-source-level issue.
  • Manual verification required: once the MSVC build break above is resolved (or on Linux/macOS where it's not an issue), repro: VS match → Samus → hold B until whoosh audible → START → confirm whoosh cuts on pause.

The fix code itself is mechanically identical to the version that tested working pre-refactor — it's just the mechanical path migration (src/decomp/src/).

🤖 Generated with Claude Code

The IDO/N64 audio thread blocks on osRecvMesg(&sSYAudioSPTaskMesgQueue)
each frame; while the pause menu owns the SP, that message stops landing,
the FGM bytecode interpreter stops ticking, and looping FGM voices wind
down naturally. The port's audio thread runs on the VI tick alone, no SP
gate, so the bytecode keeps cycling and the voice keeps playing.

Per-fighter ftParamStopLoopSFX doesn't work on the port because
siz34->unk_0x28 (the cached EE0C ptr) goes stale across the bytecode's
natural case-2 → 0 → free cycle. Diagnosed via per-frame trace that logged
on_0x3C=0 for every audio frame after pause — the EE0C we marked was
already freed and the audible voice was on a different EE0C.

Fix lives in the decomp submodule (port-patches branch):
- portAudioPurgeFGMs in n_env.c walks unk_alsound_0x3C directly and
  calls n_alSynStopVoice + n_alSynFreeVoice on every voice
  (synthesizer-level kill, bypasses the envelope-mixer fade-to-zero
  path), then NULLs every siz34->unk_0x28.
- ftParamStopAllFightersLoopSFX clears each fighter's p_loop_sfx
  cache and delegates the actual kill to portAudioPurgeFGMs.
- Hooked into ifCommonBattlePauseInitInterface before the existing
  func_80026594_27194 call. Pause beep is allocated AFTER the purge
  on a fresh slot; BGM lives on the CSPlayer pipeline, untouched.

This is a hotfix at pause-init only — proper root cause is "port audio
thread doesn't deschedule like SP-gated N64 thread"; real fixes (gate
n_alAudioFrame on game state, or lock siz34->unk_0x28) have wider blast
radius and are deferred.

Verified pre-refactor (vendored src/) on RelWithDebInfo Windows build:
whoosh cuts on pause, pause beep plays, resume continues without the
loop re-arming; user reported "Fixed".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@JRickey JRickey force-pushed the agent/samus-charge-pause-pr branch from 4d1626a to cf8fa5a Compare May 4, 2026 01:00
@JRickey JRickey merged commit 8dabe06 into main May 4, 2026
@JRickey JRickey deleted the agent/samus-charge-pause-pr branch May 4, 2026 01:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant