Make the run to the net and back a double tap

Oliver: "das Vorlaufen und Zurücklaufen sollte durch einen Doppeltipp
ausgelöst werden". It replaces v374's flick-length rule: the long
up-flick is only the deep ball again, the long down-flick only the lob.

While his own ball flies there is nothing to hit, so a tap there is not
a swing. Two inside 0.35 s take him in to the net from the back, or back
from the net. "TO THE NET" or "BACK" pops under him. An incoming ball
keeps every tap a swing, so the two meanings never share a moment. After
his own serve it is serve and volley. The short-ball approach stays
automatic.

Measured: the base ladder is unchanged, and following the ball in still
pays against pushers and costs against big hitters. Three tests replace
v374's two; v376.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Opus 5.5 committed 2026-10-14 20:30:38 +02:00
1 parent bb72a01a3e
commit 48d1069e40
7 files changed
+107 -37

No files matched your search

+7 -3
View File
@@ -93,7 +93,7 @@ const med = a => { const s = [...a].sort((x, y) => x - y); return s.length ? s[M
// 120 ms tap), and plays the game a little — a lob or a pass against a net man, the odd drop when
// the opponent hangs back. BOT=dist is the original fixed-distance tapper with random flicks.
const HUMAN = (process.env.BOT || 'human') === 'human';
const ATTACK = Number(process.env.ATTACK || 0); // share of baseline shots played as the attack (long up-flick)
const ATTACK = Number(process.env.ATTACK || 0); // share of baseline shots followed in: a deep flick, then a DOUBLE TAP
const JIT = Number(process.env.JIT ?? 60) / 1000, LAG = 7;
const gauss = () => Math.sqrt(-2 * Math.log(1 - Math.random())) * Math.cos(2 * Math.PI * Math.random());
const netx = { attacks: 0, smash: 0, smashWon: 0, lobbed: 0, open: null, p0: 0 };
@@ -108,7 +108,7 @@ function humanAim() {
const away = rz && rz.x > 0 ? -1 : 1; // the side he is not on
if (oz < TUNE.netFrom) return r < 0.4 ? sw(0, 1, 150) : sw(away, -0.3, 120); // net man: lob him or pass him
if (oz > COURT.netZ + 0.5 && Math.abs(P.z - COURT.netZ) < COURT.netZ - 1.5 && r < 0.15) return sw(0, 1, 50); // drop
if (ATTACK && Math.abs(P.z - COURT.netZ) >= TUNE.netFrom && r < ATTACK) return sw(away * 0.3, -1, 130); // the attack: follow it in
if (ATTACK && Math.abs(P.z - COURT.netZ) >= TUNE.netFrom && Math.random() < ATTACK) { bot.dbl = true; return sw(away * 0.3, -1, 130); } // the attack
if (r < 0.45) return sw(away, -0.2, 110); // to the open side
if (r < 0.65) return sw(0, -1, 80); // deep through the middle
return null; // a plain tap
@@ -162,6 +162,8 @@ for (const rung of RUNGS) {
}
}
if (i === bot.fire) { G.swipe = bot.swipe; keyPressed('Space', bot.swipe ? 0 : LAG / 60, !!bot.swipe); }
if (bot.dbl && ball.lastHit === 'P' && ball.active) { bot.dbl = false; bot.taps = [i + 4, i + 12]; } // follow it in: the double tap
if (bot.taps && bot.taps.includes(i)) keyPressed('Space', 0.1);
}
else if (G.phase === 'rally' && ball.active && ball.lastHit === 'O') {
const d = Math.hypot(ball.x - P.x, (ball.z - P.z) * 0.8);
@@ -178,7 +180,9 @@ for (const rung of RUNGS) {
const px0 = P.x, pz0 = P.z, tg0 = P.tgt;
if (process.env.PROBE && lh === 'O' && bot.land && P.swingT >= 0) bot.land.sw = P.swingT, bot.land.d = Math.hypot(ball.x - P.x, (ball.z - P.z) * 0.8), bot.land.y = ball.y, bot.land.bb = ball.bounces;
update(DT);
if (lh !== 'P' && ball.lastHit === 'P' && ball.active) { if (ball.approach === 'P' && !G.wasServe) netx.attacks++; if (ball.smash) { netx.smash++; netx.open = 'smash'; } }
if (lh !== 'P' && ball.lastHit === 'P' && ball.active && ball.smash) { netx.smash++; netx.open = 'smash'; }
if (ball.lastHit === 'P' && ball.approach === 'P' && !netx.wasIn) netx.attacks++; // a run in (the double tap, or a short ball)
netx.wasIn = ball.lastHit === 'P' && ball.approach === 'P';
if (lh === 'P' && ball.lastHit === 'O' && ball.active && ball.soft && Math.abs(P.z - COURT.netZ) < TUNE.netFrom) netx.lobbed++;
if (netx.open && (G.phase !== 'rally' || !ball.active)) { const d0 = score.pts[0] - netx.p0; if (d0 > 0) netx.smashWon++; netx.open = null; }
if (netx.open === null) netx.p0 = score.pts[0];