From 184a24f4250793ddc74ce3733a2f740d5812e066 Mon Sep 17 00:00:00 2001 From: name Date: Sat, 11 Apr 2026 19:47:55 -0700 Subject: [PATCH] fix arrow spawning and suppress step labels during calibration ArrowGame: use ref for onSongEnd to avoid unstable deps causing the audio effect to re-run and lose songStartRef. Reset arrows, combo, and spawn index when the game starts. Log audio play failures instead of swallowing them. GestureOverlay: only show 'arms-out' label during calibration, not step labels which are confusing before the game starts. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/components/GestureOverlay.tsx | 4 +++- src/game/ArrowGame.tsx | 18 ++++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/components/GestureOverlay.tsx b/src/components/GestureOverlay.tsx index 4e48aa1..08a9ff9 100644 --- a/src/components/GestureOverlay.tsx +++ b/src/components/GestureOverlay.tsx @@ -17,9 +17,11 @@ interface Props { export function GestureOverlay({ gesture, calibrationStatus, framingHint }: Props) { if (calibrationStatus === 'ready') return null + const showGesture = gesture === 'arms-out' + return ( <> - {gesture && ( + {showGesture && (
{GESTURE_LABELS[gesture]}
diff --git a/src/game/ArrowGame.tsx b/src/game/ArrowGame.tsx index 51fcb06..d3c511e 100644 --- a/src/game/ArrowGame.tsx +++ b/src/game/ArrowGame.tsx @@ -60,6 +60,8 @@ export const ArrowGame = forwardRef(({ active = true, ch const audioRef = useRef(null) const songStartRef = useRef(null) const spawnedUpToRef = useRef(0) + const onSongEndRef = useRef(onSongEnd) + onSongEndRef.current = onSongEnd const showJudgment = useCallback((text: string) => { if (judgmentTimer.current) clearTimeout(judgmentTimer.current) @@ -140,16 +142,24 @@ export const ArrowGame = forwardRef(({ active = true, ch useEffect(() => { if (!active || !chartNotes || !audioUrl) return + // Reset state for fresh start + arrowsRef.current = [] + setArrows([]) + spawnedUpToRef.current = 0 + comboRef.current = 0 + setScore({ perfect: 0, good: 0, miss: 0, combo: 0 }) + const audio = new Audio(audioUrl) audioRef.current = audio songStartRef.current = null - spawnedUpToRef.current = 0 audio.play().then(() => { songStartRef.current = performance.now() - }).catch(() => {}) + }).catch((e) => { + console.warn('Audio play failed:', e) + }) - audio.addEventListener('ended', () => onSongEnd?.()) + audio.addEventListener('ended', () => onSongEndRef.current?.()) return () => { audio.pause() @@ -157,7 +167,7 @@ export const ArrowGame = forwardRef(({ active = true, ch audioRef.current = null songStartRef.current = null } - }, [active, chartNotes, audioUrl, onSongEnd]) + }, [active, chartNotes, audioUrl]) // eslint-disable-line react-hooks/exhaustive-deps // Chart mode: spawn arrows based on song time useEffect(() => {