diff --git a/frontend/src/components/FightViewer.vue b/frontend/src/components/FightViewer.vue index 9e06981..4c2c680 100644 --- a/frontend/src/components/FightViewer.vue +++ b/frontend/src/components/FightViewer.vue @@ -28,6 +28,7 @@ let scene: FightSceneController | null = null const sceneReady = ref(false) let cleanupTimerHandle: ReturnType | null = null let destroyed = false +let initializingScene = false const contextLost = ref(false) const isReplaying = ref(false) @@ -174,6 +175,8 @@ onUnmounted(() => { async function initScene() { if (!props.fight.botA || !props.fight.botB) return + if (initializingScene || destroyed) return // prevent concurrent init or post-destroy init + initializingScene = true // Destroy previous scene fully sceneReady.value = false if (scene) { scene.destroy(); scene = null } @@ -207,8 +210,10 @@ async function initScene() { sceneReady.value = false }) newCanvas.addEventListener('webglcontextrestored', () => { + // Don't auto-reinit during replay — just mark context as restored + // The contextLost overlay will let users tap to reload if needed contextLost.value = false - initScene() // Re-create the scene with fresh context + if (!isReplaying.value) initScene() }) // Wrap scene creation in try/catch + timeout so a mobile sprite loading failure @@ -230,6 +235,8 @@ async function initScene() { } } catch (err) { console.error('[FightViewer] Scene creation failed — continuing without canvas:', err) + } finally { + initializingScene = false } }