バトル中断後 ホーム画面に戻ると ホーム画面voiceアサーションエラー(内部)し、BGMが脈絡なく停止する問題
Description
1-2 で起きた。
1-2 バトル開始し、バトル中断 リザルトの後、ホームに戻って mayaya の音声が アサーション失敗したら、 BGMが 何の脈絡もなく、停止しました。
以下ログです。
ーーーーーーー
ーーー
[Error] アサーション失敗: No sound matching alias 'sounds/se/battle/normal_mayaya.m4a'.
(anonymous関数) (index-BVxTR0NS.js:16:2024)
exists (index-BVxTR0NS.js:3408:44293)
find (index-BVxTR0NS.js:3408:44446)
_resolveSEPlayOptions (index-BVxTR0NS.js:3620:22852)
playSE (index-BVxTR0NS.js:3620:23143)
(anonymous関数) (index-BVxTR0NS.js:4215:209462)
(anonymous関数) (index-BVxTR0NS.js:3445:2675)
emit (index-BVxTR0NS.js:27:66793)
update (index-BVxTR0NS.js:27:69218)
(anonymous関数) (index-BVxTR0NS.js:27:67571)
n (index-BVxTR0NS.js:16:5857)
[Error] TypeError: null is not an object (evaluating 't.addressModeU') — index-BVxTR0NS.js:53950
(anonymous関数) (index-BVxTR0NS.js:16:2024)
c (index-BVxTR0NS.js:3620:226573)
(anonymous関数) (user-script:2:878)
[Error] TypeError: null is not an object (evaluating 't.addressModeU')
n (index-BVxTR0NS.js:16:6010)
[Error] (2)
TypeError: undefined is not an object (evaluating 'this._texturePool[n].push') — index-BVxTR0NS.js:22387{componentStack: ""}Object
(anonymous関数) (index-BVxTR0NS.js:16:2024)
c (index-BVxTR0NS.js:3620:226573)
Mm (index-BVxTR0NS.js:3420:33190)
(anonymous関数) (index-BVxTR0NS.js:3420:33522)
Jt (index-BVxTR0NS.js:3420:12327)
fa (index-BVxTR0NS.js:3420:12420)
gt (index-BVxTR0NS.js:3420:65617)
tr (index-BVxTR0NS.js:3420:86227)
Rr (index-BVxTR0NS.js:3420:85531)
Nf (index-BVxTR0NS.js:3420:80961)
fg (index-BVxTR0NS.js:3420:80626)
Ge (index-BVxTR0NS.js:3420:8803)
Be (index-BVxTR0NS.js:3420:7568)
tr (index-BVxTR0NS.js:3420:86579)
Rr (index-BVxTR0NS.js:3420:85531)
Nf (index-BVxTR0NS.js:3420:80961)
fg (index-BVxTR0NS.js:3420:80626)
st (index-BVxTR0NS.js:3420:8682)
$ (index-BVxTR0NS.js:3413:37004)
[Error] Failed to load resource: the server responded with a status of 413 (Request Entity Too Large) (envelope, line 0)
Comments (4)
厳密に分析した結果、再現性がなかなか限定的であり、高速で画面遷移させれば生じる問題であるようだ。再現性は限定的であることから一旦、解析 修正は、後回しにする。
クロスフェード FadeIn FadeOut系のVolume制御の問題かもしれない。
なお中断は、バトル中だけでなく、ステージ選択画面での中断でも同様の問題が生じた。
リザルト画面から高速でホームへ遷移しようとすると本件バグが生じやすいことまでわかった。
アサーションエラーでは、ないが、バトル中断後 BGM停止する問題が起きる前と起きた時の比較で、instVol が、正常時 0.25 異常時1.0 ということがわかった。
結果、異常時に以下 診断スニペットを Safariで実行することで問題解析をおこなった。
({
// 既存
curAlias: __sm._currentPlayingAlias,
hasInst: !!__sm._bgmInstance,
instVol: __sm._bgmInstance?.volume,
final: __sm._bgmFinalVolume(),
// ★ dead pointer 判定の本命
aliasIsPlaying: __pixiSound.find(__sm._currentPlayingAlias)?.isPlaying,
aliasIsLoaded: __pixiSound.find(__sm._currentPlayingAlias)?.isLoaded,
aliasInRegistry: __pixiSound.exists(__sm._currentPlayingAlias),
perSoundVol: __pixiSound.volume(__sm._currentPlayingAlias),
// instance 側の内部状態
instProgress: __sm._bgmInstance?.progress, // 0〜1 で進行中なら増える
instPaused: __sm._bgmInstance?.paused,
instLoop: __sm._bgmInstance?.loop,
// 並走 instance 数(pixi-sound 内部)
instCount: (() => {
const s = __pixiSound.find(__sm._currentPlayingAlias);
return s?.instances?.length;
})(),
instSame: (() => {
const s = __pixiSound.find(__sm._currentPlayingAlias);
return s?.instances?.includes(__sm._bgmInstance);
})(),
// transition token (二重 playBGM の証拠)
token: __sm._bgmTransitionToken,
})
結果以下となり、問題コードが絞り込めた。
aliasInRegistry: true
aliasIsLoaded: true
aliasIsPlaying: false
curAlias: "https://utsaqkhxqllqkazxzhow.supabase.co/storage/v1/object/public/sounds/sounds/e105a489-0ab7-4dfa-bb93-db494318d6c2/6c10b55c-59fd-4b95-ad9f…"
final: 0.25
hasInst: true
instCount: 0
instLoop: false
instPaused: false
instProgress: 1
instSame: false
instVol: 1
perSoundVol: 1
token: 36
診断結果まとめ
hasInst: true _bgmInstance は参照保持
instSame: false ★ pixi-sound 内部の instances リストに居ない(detached)
instCount: 0 ★ alias 上に生きてる instance はゼロ
aliasIsPlaying: false ★ 鳴っていない("聞こえない" の正体)
instProgress: 1 このインスタンスは「終端まで到達した」状態
instLoop: false ★★ BGM なのに loop:false(playBGM 経路では必ず loop:true のはず)
aliasInRegistry: true
perSoundVol: 1 per-sound 層は正常
dead pointer 確定。INS-815 と同型の罠です。_bgmInstance は生きているように見えるが、pixi-sound 側ではそのインスタンスは既に detach されていて、別のどこかで stop された状態。
ーーー
最も怪しい原因
instLoop: false が決定打。BGM 経路は sound.ts:1294 で loop: true をハードコード。なのに loop: false のインスタンスが _bgmInstance に入っている。
これは playBGM の race ではなく、playOneShot(voice) または何か別の sound.play 経路で生成された instance が _bgmInstance に紛れ込んだ可能性が高い、と思いきや、_bgmInstance への代入は _ensureMainPlaying の line 1324 一箇所しかないので、それは無い。
より妥当な仮説:
- BGM playBGM が走る
- 同じ alias の
sound.playを内部で再利用するため、pixi-sound が 既存のインスタンスを返してくる ケースがある(特に Luna BGM がsingleInstance相当の設定で add されていた場合や、直前に何らかの sound.play が同 alias で走っていた場合) - その既存インスタンスはすでに「loop:false で再生終了済み」の orphan
_bgmInstance = playInstanceでこの orphan を保持してしまう- その後の fadeIn timer は
_bgmInstance.volume = ...を書き込むが、orphan なので音は出ない → 最終 volume 1.0 (= fadeIn 完了時に_bgmFinalVolume()を書いた値? いや 0.25 のはず…)
instVol: 1 の値の出どころが完全には説明できていないので、もう一段絞りたい。
ーーーー
Home BGM (Luna 選択時) で playBGM が二重発火 + singleInstance: true の組合せで dead pointer 化
決定的な証拠:
addSoundIfNeededはsingleInstance: trueで alias を登録する (preload.ts:103)- Home マウント時、playBGM(LunaURL) を 2系統が並走 で叩く:
usePreloader.ts:86— preload 完了直後のplayBGM(initialBgm.alias, ...)Home.tsx:523—isLoading/queryData依存の useEffect (ポーリング後 playBGM)
instLoop: falseが示すとおり、_bgmInstanceは loop:true で開始されたはずの BGM instance ではなく、stop された後の orphan を握っている
Raceの流れ
Home BGM (Luna 選択時) で playBGM が二重発火 + singleInstance: true の組合せで dead pointer 化
決定的な証拠:
addSoundIfNeeded は singleInstance: true で alias を登録する (preload.ts:103)
Home マウント時、playBGM(LunaURL) を 2系統が並走 で叩く:
usePreloader.ts:86 — preload 完了直後の playBGM(initialBgm.alias, ...)
Home.tsx:523 — isLoading/queryData 依存の useEffect (ポーリング後 playBGM)
instLoop: false が示すとおり、_bgmInstance は loop:true で開始されたはずの BGM instance ではなく、stop された後の orphan を握っている
「voice 喋り出しが大きい時に出る」のは、voice の playOneShot がメインスレッド/iOS XPC を一瞬詰まらせて A と B のタイミングがズレ、上記 race が綺麗に成立する条件になるためと思われます(早い遷移で再現する事象とも整合)。
なぜ「正常時」と「不具合時」が分かれるか
- 正常時: A と B の到着間隔が十分離れる、または順序がうまく揃って
_currentPlayingAlias === alias && isPlayingの短絡 (sound.ts:1178-1184) で B が no-op になる - 不具合時: B が A の await 中(isPlaying がまだ false)に到着 → 短絡が外れて二重 sound.play → 上記 race
ーーーー
一旦、メモリリークの問題と合わせて解決した。