diff --git a/frontend/src/pages/FightPage.vue b/frontend/src/pages/FightPage.vue index b47a242..16714d7 100644 --- a/frontend/src/pages/FightPage.vue +++ b/frontend/src/pages/FightPage.vue @@ -199,13 +199,17 @@ function wireSSE() { } }, onRoundStart(data) { - liveLogItems.value.push({ - type: 'challenge', - round: data.round, - text: `${data.challenge.label}: ${data.challenge.prompt}`, - color: 'neon-green', - }) - scrollLiveLog() + // For human fights, don't preview the question in the log — it spoils the + // challenge before the input area shows it. The round_end handler adds the full result. + if (!isHumanFight.value) { + liveLogItems.value.push({ + type: 'challenge', + round: data.round, + text: `${data.challenge.label}: ${data.challenge.prompt}`, + color: 'neon-green', + }) + scrollLiveLog() + } }, onHumanChallenge(sseData) { // SSE now includes choices directly — no secondary fetch needed @@ -454,16 +458,9 @@ watch(() => route.params.fightId, async (newId) => { if (isLive.value) { startPolling({ onFinished: () => stopHumanPolling(), onTimeout: () => stopHumanPolling(), keepLive: isHumanFight.value }) wireSSE() - if (isHumanFight.value) { - startHumanPolling() - await nextTick() - await nextTick() - if (liveFightData.value) await initLiveScene() - } else { - if (!liveFightData.value) await loadFight() - await nextTick() - await nextTick() - if (liveFightData.value) await initLiveScene() + // Scene init handled by liveFightData watcher to avoid double init + if (!isHumanFight.value && !liveFightData.value) { + await loadFight() } } }) @@ -526,17 +523,8 @@ onMounted(async () => { startPolling({ onFinished: () => stopHumanPolling(), onTimeout: () => stopHumanPolling(), keepLive: isHumanFight.value }) if (isHumanFight.value) { wireSSE() - // Init scene first, then start challenge polling — ensures entrance - // plays before the first question appears (characters invisible otherwise) - if (liveFightData.value) { - await nextTick() - await nextTick() - await initLiveScene() - } - // Only start polling after scene is ready (or watcher will start it when liveFightData arrives) - if (liveSceneReady.value) { - startHumanPolling() - } + // Scene init + human polling are handled by the liveFightData watcher + // (avoids double init if loadFight already set liveFightData) } } }) @@ -586,15 +574,13 @@ async function fightAgain(botId: string) { startPolling({ onFinished: () => stopHumanPolling(), onTimeout: () => stopHumanPolling(), keepLive: isHumanFight.value }) if (isHumanFight.value) { wireSSE() + // Poll until fight data arrives — the liveFightData watcher handles scene init + // (don't call initLiveScene directly, it causes double kaplay init + double TTS) for (let i = 0; i < 20; i++) { await loadFight() if (liveFightData.value) break await sleep(300) } - await nextTick() - await nextTick() - if (liveFightData.value) await initLiveScene() - startHumanPolling() } } else { fightError.value = `Failed to start fight (${res.status})`