Quick answer: Construct 3 multi-touch handler firing on stale touches after a rapid tap-release sequence? Browser doesn't always emit touchend - debounce or check touch ID against active set.
Twin-stick controls: fast finger lift-and-replant fires double-fire on the gameplay action.
Debounce active touches
Maintain a Set of active touch IDs. On touchstart, add; on touchend/touchcancel, remove. Action triggers on transition from 0 to 1 active.
Listen to touchcancel
Browsers fire touchcancel under load when touchend would have been correct. Treat as touchend.
Use pointer events
Pointer events have cleaner semantics for stale touches. Migrate where the browser supports them.
“Touch events are best-effort. Defensive handling is the contract.”
Test rapid tap on real mobile hardware. The bugs hide in the touch event ordering that desktop browsers fake correctly.