fix: mobile fight playback — prevent sprite loading hangs, TTS stalls, unlock audio from gesture

- Unified speakAsync/speakAsyncWithRate into _speakAsyncCore with:
  - 500ms startup check: bail immediately if speech won't start (mobile/no gesture)
  - 8s safety timeout (down from 15s) to prevent blocking
  - iOS-safe keepalive: only do Chrome pause/resume workaround on desktop Chrome
  - Immediate bail when no voices loaded
- Sprite loading: 5s timeout per sprite prevents mobile hangs from stuck Image decodes
- Scene creation: 10s timeout in FightViewer so overlay/voice flow continues even if
  canvas fails on mobile
- playRound: bail gracefully if fighter sprites missing instead of crashing
- Global audio unlock: first touch/click on site unlocks AudioContext + SpeechSynthesis
- Practice button pre-unlocks audio while still in user gesture context

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-08 15:54:58 +00:00
co-authored by Claude Opus 4.6
parent 9287fd1783
commit 6561ef5d15
5 changed files with 97 additions and 65 deletions
+3
View File
@@ -4,6 +4,7 @@ import { useRouter } from 'vue-router'
import { useNostr } from '../composables/useNostr'
import { useWallet } from '../composables/useWallet'
import SpritePreview from '../components/SpritePreview.vue'
import { ensureAudioContext } from '../game/sounds'
import HumanPreview from '../components/HumanPreview.vue'
import WalletConnect from '../components/WalletConnect.vue'
@@ -384,6 +385,8 @@ async function practice() {
if (!bot.value || isJoiningPractice.value) return
isJoiningPractice.value = true
error.value = ''
// Unlock audio/speech NOW while we're in user gesture context (lost after async navigation)
ensureAudioContext()
try {
const res = await fetch(`/api/fights/practice/${bot.value.id}`, { method: 'POST' })
if (res.ok) {