Make the double tap a stance that holds the net

Oliver on v376: "wenn man doppeltippt, geht er minimal vor". His own
ball's flight covered 4-9 m of the 10 to the net. His next ball was met
from mid-court, outside the net zone, and the recovery after that volley
walked him straight back to the baseline.

The double tap now sets a stance (P.post) that lasts until the next
double tap, a lob he has to run down, or the end of the point. Every
recovery heads for the net at full pace, and every incoming ball is
planned as a net man's. The 'back' stance clears once he is behind the
net zone, so short balls still pull him in.

Measured: contacts while committed are ~35% at the net and ~40% around
the service line, and he no longer falls back. The base ladder is
unchanged. New test; v377.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Opus 5.5 committed 2026-10-14 20:40:56 +02:00
1 parent 48d1069e40
commit ba8d13a867
6 files changed
+72 -11

No files matched your search

+15 -5
View File
@@ -1478,7 +1478,10 @@ const letBounce = () => ball.bounces === 0 && ball.y < COURT.netH && ball.rx &&
// ...and where he recovers to is where he IS: at the net he holds the net, otherwise he goes back
// behind his baseline. Read live — recovery always moves him away from netFrom, so it cannot dither.
// The one exception is the APPROACH: having taken a short ball, he follows it in.
const rallyHome = ch => ({ x: 0, z: (Math.abs(ch.z - COURT.netZ) < TUNE.netFrom && ball.retreat !== ch.side) || ball.approach === ch.side
// The PLAYER's double tap overrides all of that with a STANCE (P.post) that lasts until he changes it.
const atNetHome = ch => ch.side === 'P' && P.post ? P.post === 'net'
: (Math.abs(ch.z - COURT.netZ) < TUNE.netFrom && ball.retreat !== ch.side) || ball.approach === ch.side;
const rallyHome = ch => ({ x: 0, z: atNetHome(ch)
? COURT.netZ + (ch.side === 'P' ? TUNE.netStand : -TUNE.netStand)
: ch.side === 'P' ? COURT.L + TUNE.rallyBack : -TUNE.rallyBack });
// Where a baseliner meets this ball: after the bounce, falling through rallyHit, or at the top of a
@@ -1574,7 +1577,7 @@ function intercept(side) {
deepest = pt; // last resort: run at the deepest hittable point
}
}
if (smashing) plan.over = null; // no overhead in reach: he runs it down
if (smashing) { plan.over = null; if (P.post === 'net') P.post = 'base'; } // no overhead in reach: he runs it down, and stays back
// ...and a frame that finds NOTHING mid-chase holds the plan it had: a dying drop's bounce sits on the
// 0.35 m floor, and every other frame without a candidate sent him home, 10 m away, and back again
const hold = side === 'P' && P.chase && P.goal ? P.goal : null;
@@ -2162,8 +2165,13 @@ export function keyPressed(code, lead = 0, flick = false) {
// In to the net, or back from it: the double tap's one meaning is "the other end of my half". At the net
// (or already running in) it is the way back; anywhere else it is the way in. rallyHome reads the flags
// while his ball flies, the run goes at full pace, and his reply is planned from wherever he has got to.
// It is a STANCE, not one ball's run (Oliver on v376: "wenn man doppeltippt, geht er minimal vor"): his own
// ball's flight covers 4-9 m of the 10 to the net, his next ball was met from mid-court, outside the net
// zone, and the recovery after it walked him straight back to the baseline. P.post holds until the next
// double tap, a lob he has to run down, or the point's end.
function moveCall() {
const goingIn = ball.approach === 'P' || (Math.abs(P.z - COURT.netZ) < TUNE.netFrom && ball.retreat !== 'P');
const goingIn = atNetHome(P);
P.post = goingIn ? 'base' : 'net';
ball.approach = goingIn ? null : 'P';
ball.retreat = goingIn ? 'P' : null;
popText(P.x, 0, P.z + TUNE.praiseBehind, goingIn ? 'BACK' : 'TO THE NET', UI.warm, true);
@@ -2367,8 +2375,10 @@ export function update(dt) {
// ...and lobbed at the net he backpedals, exactly as slowly as the CPU does (TUNE.lobTurn)
// HIS OWN WAY IN AND OUT runs at full pace (the attack's follow-in, the lob's retreat), and while
// his attack flies he is RUSHING: the reply is planned as a net man's from wherever he has got to
if (ball.lastHit === 'P') P.rushing = ball.approach === 'P'; // a double tap behind his serve is serve and volley
const ownRun = !chasing && rallyFeel() && ball.lastHit === 'P' && (ball.approach === 'P' || ball.retreat === 'P');
// committed to the net he is a net man for every ball until he reaches it (a double tap behind his serve is serve and volley)
if (P.post === 'base' && Math.abs(P.z - COURT.netZ) > TUNE.netFrom + 0.5) P.post = null; // back: the old rules again (a short ball still pulls him in)
P.rushing = P.post ? P.post === 'net' : ball.lastHit === 'P' ? ball.approach === 'P' : P.rushing;
const ownRun = !chasing && rallyFeel() && (P.post || ball.lastHit === 'P' && (ball.approach === 'P' || ball.retreat === 'P'));
let rate = TUNE.playerSpeed * aK * dt * (chasing || ownRun ? 1 : 0.55) * (chasing && ball.rx && ball.rx.lob ? TUNE.lobTurn : 1);
// ON HIS HEELS: a put-away of his own sitter is read late (step 4 (b)). Without this the auto-run
// started on the strike's frame with a perfect read and covered the whole court — no put-away ever