Phase 1: tighten the splatter shooting loop

Input during the 'over' beat is no longer swallowed: a tap/flick while
G.phase==='over' (splatter only) resets inline and falls through to the
serve logic, so the finger is the only cadence limit. Spent balls
(lastHit 'X' after a stagger bounce) and hopeless balls (out of the
z/x bounds) are retired immediately instead of lingering until they roll
out. New TUNE.splatterOverT (0.1) replaces the hardcoded 0.25 over-timer.

The out-of-bounds z-bounds gate on travel direction so a quick-serve born
behind the baseline (P.z up to 26, heading vz<0 to the far court) isn't
killed on frame one.

Verified via window.T: flick/tap during 'over' serves same frame; spent
ball dies the frame it bounces; serve->serve latency 0 frames with
immediate flicks; tennis 'over' unchanged (2.2s pause, tap ignored);
tennis CPU still returns 11/12 serves; no console errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Opus 4.8 committed 2026-07-11 21:49:50 +02:00
1 parent fe5532b558
commit 795ddeb425
2 files changed
+14 -1

No files matched your search

+13 -1
View File
@@ -354,6 +354,9 @@ function trySwing(ch, isAI) {
export function keyPressed(code) {
if (code !== 'Space') return;
audio(); // unlock on first gesture
// splatter: a tap/flick during the brief 'over' beat arms the next serve immediately —
// reset inline, then fall through to pretoss so the finger is the only cadence limit
if (G.mode === 'splatter' && G.phase === 'over') { ball.active = false; setupPoint(); }
if (G.phase === 'pretoss' && score.server === 'P') {
if (G.mode === 'splatter') {
const flick = takeFlickAim();
@@ -505,11 +508,20 @@ export function update(dt) {
if (ball.active && Math.abs(ball.vy) < 0.8) { // rolled out — dead ball
ball.active = false;
if (G.mode === 'splatter') { // hand the server a fresh one, fast — they're coming
G.phase = 'over'; G.overT = 0.25; sendBoy();
G.phase = 'over'; G.overT = TUNE.splatterOverT; sendBoy();
if (Math.random() < 0.12) voice('new_balls', 0.3);
} else if (ball.bounces === 1) pointTo(ball.lastHit, null);
}
}
// splatter: retire spent or hopeless balls fast so the next serve comes right up.
// z-bounds gate on travel direction — a serve born behind the baseline (P.z up to 26)
// heads to the far court (vz<0), so it must not read as "out the back" on frame one.
if (G.mode === 'splatter' && ball.active &&
((ball.lastHit === 'X' && ball.bounces >= 1)
|| (ball.z < -2 && ball.vz < 0) || (ball.z > 25.5 && ball.vz > 0)
|| Math.abs(ball.x) > 7.5)) {
ball.active = false; G.phase = 'over'; G.overT = TUNE.splatterOverT;
}
if (G.phase === 'toss' && ball.y <= 1.4 && ball.vy < 0) { // uncaught toss → retoss
ball.active = false; G.phase = 'pretoss';
if (score.server === 'O') G.aiServeT = 0.8;