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) <noreply@anthropic.com>
main
name 2026-04-11 19:47:55 -07:00
parent d2630b1b64
commit 184a24f425
2 changed files with 17 additions and 5 deletions

View File

@ -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 && (
<div className="gesture-label">
{GESTURE_LABELS[gesture]}
</div>

View File

@ -60,6 +60,8 @@ export const ArrowGame = forwardRef<ArrowGameHandle, Props>(({ active = true, ch
const audioRef = useRef<HTMLAudioElement | null>(null)
const songStartRef = useRef<number | null>(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<ArrowGameHandle, Props>(({ 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<ArrowGameHandle, Props>(({ 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(() => {