From 6b139a076a6c4550b6cd5bc0350b11741d4608b3 Mon Sep 17 00:00:00 2001 From: Dorian Date: Sun, 8 Mar 2026 00:46:50 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20KAPLAY=20scene=20init=20robustness=20?= =?UTF-8?q?=E2=80=94=20wait=20for=20layout,=20catch=20errors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - FightViewer: wrap createFightScene in try/catch so scene failures don't crash every subsequent playRound call - FightViewer: wait for container to have non-zero dimensions before creating canvas (prevents 0x0 canvas causing sprite load failures) - FightViewer: graceful log-only fallback when scene init fails - FightPage: same dimension-wait and try/catch for initLiveScene - Fallback canvas dimensions (800x500) when container not yet laid out Co-Authored-By: Claude Opus 4.6 --- frontend/src/components/FightViewer.vue | 9 +++++++-- frontend/src/pages/FightPage.vue | 25 +++++++++++++++++-------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/FightViewer.vue b/frontend/src/components/FightViewer.vue index a8377c1..6dbf639 100644 --- a/frontend/src/components/FightViewer.vue +++ b/frontend/src/components/FightViewer.vue @@ -112,12 +112,17 @@ async function initScene() { const container = canvasContainer.value if (!container) return + // Wait for container to have dimensions (layout may not be complete yet) + for (let i = 0; i < 10 && (!container.clientWidth || !container.clientHeight); i++) { + await new Promise(r => requestAnimationFrame(r)) + } + // Replace canvas element so Kaplay gets a fresh context const oldCanvas = canvasRef.value const newCanvas = document.createElement('canvas') newCanvas.className = 'w-full h-full block' - newCanvas.width = container.clientWidth - newCanvas.height = container.clientHeight + newCanvas.width = container.clientWidth || 800 + newCanvas.height = container.clientHeight || 500 if (oldCanvas) { oldCanvas.replaceWith(newCanvas) } else { diff --git a/frontend/src/pages/FightPage.vue b/frontend/src/pages/FightPage.vue index 266c605..81b3cdd 100644 --- a/frontend/src/pages/FightPage.vue +++ b/frontend/src/pages/FightPage.vue @@ -305,21 +305,30 @@ async function initLiveScene() { const container = liveCanvas.value.parentElement if (container) { + // Wait for container to have dimensions + for (let i = 0; i < 10 && (!container.clientWidth || !container.clientHeight); i++) { + await new Promise(r => requestAnimationFrame(r)) + } const oldCanvas = liveCanvas.value const newCanvas = document.createElement('canvas') newCanvas.className = oldCanvas.className - newCanvas.width = container.clientWidth - newCanvas.height = container.clientHeight + newCanvas.width = container.clientWidth || 800 + newCanvas.height = container.clientHeight || 500 oldCanvas.replaceWith(newCanvas) liveCanvas.value = newCanvas } - liveScene = await createFightScene({ - canvas: liveCanvas.value, - botA: { name: data.botA.name, seed: data.botA.avatarSeed || data.botA.name, tier: data.botA.tier, archetype: data.botA.archetype, customization: data.botA.customization, wins: data.botA.wins, losses: data.botA.losses }, - botB: { name: data.botB.name, seed: data.botB.avatarSeed || data.botB.name, tier: data.botB.tier, archetype: data.botB.archetype, customization: data.botB.customization, wins: data.botB.wins, losses: data.botB.losses }, - arena: data.arena, - }) + try { + liveScene = await createFightScene({ + canvas: liveCanvas.value, + botA: { name: data.botA.name, seed: data.botA.avatarSeed || data.botA.name, tier: data.botA.tier, archetype: data.botA.archetype, customization: data.botA.customization, wins: data.botA.wins, losses: data.botA.losses }, + botB: { name: data.botB.name, seed: data.botB.avatarSeed || data.botB.name, tier: data.botB.tier, archetype: data.botB.archetype, customization: data.botB.customization, wins: data.botB.wins, losses: data.botB.losses }, + arena: data.arena, + }) + } catch (err) { + console.error('[FightPage] createFightScene failed:', err) + liveScene = null + } liveSceneReady.value = true