fix: human vs AI fight bugs — CSP for TTS, invisible sprites, fight end sequence
- Allow huggingface.co in CSP connect-src (fixes Kokoro TTS model download) - Add registerSW.js route (fixes PWA service worker 404) - Add _resetPositions() safety after entrance (fixes invisible fighters) - Fight end sequence works without canvas scene (KO/overlays/log always play) - Pre-fight instructions in battle log for human players - NIP-55 visibility sync and cleanup handlers Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
63cc00fcb6
commit
150ce7447d
@@ -170,6 +170,13 @@ async function initLiveScene() {
|
||||
{ type: 'system', round: 0, text: `${data.botA.name} vs ${data.botB.name}`, color: 'text-secondary' },
|
||||
{ type: 'divider', round: 0, text: '', color: '' },
|
||||
)
|
||||
// Pre-fight instructions for human players
|
||||
if (isHumanFight.value) {
|
||||
liveLogItems.value.push(
|
||||
{ type: 'narration', round: 0, text: 'HUMAN VS AI — Answer challenges faster and better than the bot! Tap a choice or type your answer before time runs out.', color: 'neon-yellow' },
|
||||
{ type: 'divider', round: 0, text: '', color: '' },
|
||||
)
|
||||
}
|
||||
scrollLiveLog()
|
||||
}
|
||||
|
||||
@@ -320,7 +327,7 @@ async function handleFightEnd(data: any) {
|
||||
else liveHpA.value = 0
|
||||
}
|
||||
|
||||
if (liveScene && data.winnerId) {
|
||||
if (data.winnerId) {
|
||||
const winningSide = (data.winnerId === fd.botA.id ? 'a' : 'b') as 'a' | 'b'
|
||||
|
||||
sfxDrumRoll()
|
||||
@@ -328,11 +335,21 @@ async function handleFightEnd(data: any) {
|
||||
announceFinishHim()
|
||||
await showLiveOverlay('FINISH IT!', '#ff2d2d', 900)
|
||||
|
||||
if (data.isPerfect) {
|
||||
await liveScene.playPerfect(winningSide, data.winnerName)
|
||||
announceFlawlessVictory()
|
||||
if (liveScene) {
|
||||
try {
|
||||
if (data.isPerfect) {
|
||||
await liveScene.playPerfect(winningSide, data.winnerName)
|
||||
announceFlawlessVictory()
|
||||
} else {
|
||||
await liveScene.playKO(winningSide, data.winnerName)
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('[FightPage] KO animation failed:', err)
|
||||
}
|
||||
} else {
|
||||
await liveScene.playKO(winningSide, data.winnerName)
|
||||
// Scene unavailable — still announce vocally
|
||||
if (data.isPerfect) announceFlawlessVictory()
|
||||
await sleep(800)
|
||||
}
|
||||
|
||||
sfxApplause()
|
||||
@@ -341,7 +358,7 @@ async function handleFightEnd(data: any) {
|
||||
if (data.mode === 'ranked' && data.potSats) {
|
||||
await showLiveOverlay(`WON ${data.potSats} SATS!`, '#00f0ff', 1500)
|
||||
}
|
||||
liveScene.stopMusic()
|
||||
if (liveScene) liveScene.stopMusic()
|
||||
}
|
||||
|
||||
if (isHumanFight.value) {
|
||||
|
||||
@@ -33,6 +33,7 @@ const queueCount = ref(0)
|
||||
const rankedQueueCount = ref(0)
|
||||
const rankedEstWait = ref(15)
|
||||
let pollHandle: ReturnType<typeof setInterval> | null = null
|
||||
let nip55ReturnHandler: (() => void) | null = null
|
||||
|
||||
// Registration form
|
||||
const selectedArchetype = ref('standard')
|
||||
@@ -161,11 +162,13 @@ onMounted(async () => {
|
||||
onBeforeRouteLeave(() => {
|
||||
if (pollHandle) { clearInterval(pollHandle); pollHandle = null }
|
||||
if (rateLimitTimer) { clearInterval(rateLimitTimer); rateLimitTimer = null }
|
||||
if (nip55ReturnHandler) { document.removeEventListener('visibilitychange', nip55ReturnHandler); nip55ReturnHandler = null }
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (pollHandle) clearInterval(pollHandle)
|
||||
if (rateLimitTimer) { clearInterval(rateLimitTimer); rateLimitTimer = null }
|
||||
if (nip55ReturnHandler) { document.removeEventListener('visibilitychange', nip55ReturnHandler); nip55ReturnHandler = null }
|
||||
})
|
||||
|
||||
async function pollQueue() {
|
||||
@@ -212,6 +215,45 @@ async function handleSignerLogin() {
|
||||
// On Android, try NIP-55 intent to open Amber/Primal directly
|
||||
if (hasAndroidSigner.value) {
|
||||
error.value = 'Opening signer app...'
|
||||
|
||||
// Listen for return from signer app (user switches back to browser)
|
||||
if (nip55ReturnHandler) document.removeEventListener('visibilitychange', nip55ReturnHandler)
|
||||
nip55ReturnHandler = async () => {
|
||||
if (document.visibilityState !== 'visible') return
|
||||
document.removeEventListener('visibilitychange', nip55ReturnHandler!)
|
||||
nip55ReturnHandler = null
|
||||
|
||||
// Check if signer redirected with callback params in the URL
|
||||
try {
|
||||
const nip55Result = await processNip55Return()
|
||||
if (nip55Result) {
|
||||
error.value = ''
|
||||
if (nip55Result.bot) {
|
||||
isHumanMode.value = !!nip55Result.bot.isHuman
|
||||
step.value = 'ready'
|
||||
} else {
|
||||
step.value = 'choose-mode'
|
||||
}
|
||||
return
|
||||
}
|
||||
} catch {
|
||||
// Callback params present but auth failed
|
||||
error.value = 'Signer login failed. Try again.'
|
||||
return
|
||||
}
|
||||
|
||||
// No callback params — signer may have injected window.nostr
|
||||
error.value = 'Connecting...'
|
||||
const signerFound = await waitForSigner(2000)
|
||||
if (signerFound) {
|
||||
error.value = ''
|
||||
handleLogin()
|
||||
} else {
|
||||
error.value = 'Signer did not return a signed event. Tap to try again.'
|
||||
}
|
||||
}
|
||||
document.addEventListener('visibilitychange', nip55ReturnHandler)
|
||||
|
||||
initiateNip55Login()
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user