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:
1 parent
bb72a01a3e
commit
48d1069e40
7 files changed
+107
-37
No files matched your search
@@ -1521,11 +1521,11 @@ test('LOBBED at the net, the player backpedals too, and only blocks it back', ()
|
||||
} finally { TUNE.autoAssist = assist; }
|
||||
});
|
||||
|
||||
// ---- THE PLAYER'S WAY TO THE NET AND BACK, AND THE SMASH (2026-10-14, Oliver: "Steuerung ans Netz gehen"
|
||||
// ---- THE PLAYER'S WAY TO THE NET AND BACK (the double tap), AND THE SMASH (2026-10-14, Oliver: "Steuerung ans Netz gehen"
|
||||
// ... "wenn man einen Lob vom Gegner als Volley annimmt, soll man derb schmettern") ---------------------
|
||||
// One player shot with a given swipe from (x 0, z pz), then `secs` of flight with the opponent parked out
|
||||
// of play. Returns the shot's flags and where the player's own run took him.
|
||||
function ownShot(swipe, pz, secs = 1.2) {
|
||||
function ownShot(swipe, pz, secs = 1.2, taps = []) {
|
||||
freshSplatter(); startMatch(); G.interlude.introT = 0;
|
||||
const assist = TUNE.autoAssist; TUNE.autoAssist = 1.15;
|
||||
try {
|
||||
@@ -1538,30 +1538,55 @@ function ownShot(swipe, pz, secs = 1.2) {
|
||||
G.swipe = swipe;
|
||||
update(DT); G.swipe = null;
|
||||
assert.equal(ball.lastHit, 'P', 'the swing connects');
|
||||
const out = { approach: ball.approach, retreat: ball.retreat };
|
||||
for (let i = 0; i < secs * 60 && ball.active; i++) { rz.swingCool = 999; update(DT); }
|
||||
out.fromNet = Math.abs(P.z - COURT.netZ);
|
||||
return out;
|
||||
let swung = false;
|
||||
for (let i = 0; i < secs * 60 && ball.active; i++) {
|
||||
rz.swingCool = 999;
|
||||
if (taps.includes(i)) { keyPressed('Space', 0.1); swung ||= P.swingT >= 0; }
|
||||
update(DT);
|
||||
}
|
||||
return { approach: ball.approach, retreat: ball.retreat, fromNet: Math.abs(P.z - COURT.netZ), swung };
|
||||
} finally { TUNE.autoAssist = assist; }
|
||||
}
|
||||
|
||||
test('THE ATTACK: a long up-flick follows the ball to the net, a short one stays back', () => {
|
||||
const up = mag => ({ ax: 0, az: -1, fx: 0, fy: -1, mag });
|
||||
const attack = ownShot(up(TUNE.attackMin + 30), 24), deep = ownShot(up(TUNE.attackMin - 30), 24);
|
||||
assert.equal(attack.approach, 'P', 'the long up-flick is the attack');
|
||||
assert.ok(attack.fromNet < TUNE.netFrom, `...and he is at the net by the time it gets there: ${attack.fromNet.toFixed(1)} m`);
|
||||
assert.equal(deep.approach, null, 'the short one is the deep ball');
|
||||
assert.ok(deep.fromNet > TUNE.netFrom + 3, `...and he stays back: ${deep.fromNet.toFixed(1)} m`);
|
||||
test('THE DOUBLE TAP is his legs: from the back it takes him to the net, a single tap does nothing', () => {
|
||||
// Oliver, 2026-10-14: "das Vorlaufen und Zurücklaufen sollte durch einen Doppeltipp ausgelöst werden".
|
||||
// While his own ball flies there is nothing to hit, so taps there are the move, never a swing.
|
||||
const deep = { ax: 0, az: -1, fx: 0, fy: -1, mag: TUNE.flickFull + 20 };
|
||||
const plain = ownShot(deep, 24), one = ownShot(deep, 24, 1.2, [6]), two = ownShot(deep, 24, 1.2, [6, 6 + 10]);
|
||||
assert.equal(plain.approach, null, 'a long up-flick is only a shot now: no run');
|
||||
assert.ok(plain.fromNet > TUNE.netFrom + 3, `...he stays back: ${plain.fromNet.toFixed(1)} m`);
|
||||
assert.ok(!one.swung && one.approach === null && one.fromNet > TUNE.netFrom + 3, 'one tap while his ball flies: nothing at all');
|
||||
assert.equal(two.approach, 'P', 'the double tap is the way in');
|
||||
assert.ok(!two.swung, '...and neither tap swung');
|
||||
assert.ok(two.fromNet < TUNE.netFrom, `...and he is at the net by the time the ball gets there: ${two.fromNet.toFixed(1)} m`);
|
||||
const slow = ownShot(deep, 24, 1.2, [6, 6 + Math.ceil(TUNE.dblTap * 60) + 3]);
|
||||
assert.equal(slow.approach, null, 'two taps further apart than dblTap are not a double tap');
|
||||
});
|
||||
|
||||
test('THE WAY BACK: a long down-flick from the net is the lob, and he goes back behind it; a drop keeps the net', () => {
|
||||
const down = mag => ({ ax: 0, az: 1, fx: 0, fy: 1, mag });
|
||||
const lob = ownShot(down(TUNE.flickFull + 20), COURT.netZ + TUNE.netStand, 2);
|
||||
const drop = ownShot(down(TUNE.dropMax - 30), COURT.netZ + TUNE.netStand, 1);
|
||||
assert.equal(lob.retreat, 'P', 'the lob is the way back');
|
||||
assert.ok(lob.fromNet > TUNE.netFrom, `...and he is back behind the net zone: ${lob.fromNet.toFixed(1)} m`);
|
||||
assert.equal(drop.retreat, null, 'a drop volley is not a retreat');
|
||||
assert.ok(drop.fromNet < TUNE.netFrom, `...he holds the net: ${drop.fromNet.toFixed(1)} m`);
|
||||
test('THE DOUBLE TAP at the net is the way back; a long down-flick is only the lob', () => {
|
||||
const down = { ax: 0, az: 1, fx: 0, fy: 1, mag: TUNE.flickFull + 20 };
|
||||
const lob = ownShot(down, COURT.netZ + TUNE.netStand, 1.5);
|
||||
assert.equal(lob.retreat, null, 'the lob alone does not take him back');
|
||||
assert.ok(lob.fromNet < TUNE.netFrom, `...he holds the net: ${lob.fromNet.toFixed(1)} m`);
|
||||
const back = ownShot(down, COURT.netZ + TUNE.netStand, 1.5, [6, 14]);
|
||||
assert.equal(back.retreat, 'P', 'the double tap at the net is the way back');
|
||||
assert.ok(back.fromNet > TUNE.netFrom, `...and he is back behind the net zone: ${back.fromNet.toFixed(1)} m`);
|
||||
});
|
||||
|
||||
test('an INCOMING ball keeps every tap a swing: the double tap never eats a shot', () => {
|
||||
freshSplatter(); startMatch(); G.interlude.introT = 0;
|
||||
oppOf().swingCool = 999;
|
||||
G.phase = 'rally'; G.wasServe = false;
|
||||
const v = solveShot(0, 1, 2, 0.5, 21, 20);
|
||||
Object.assign(ball, { active: true, scored: false, lastHit: 'O', bounces: 0, rx: null, spin: 1, soft: false,
|
||||
x: 0, y: 1, z: 2, vx: v.vx, vy: v.vy, vz: v.vz, g: TUNE.gravity });
|
||||
P.swingT = -1;
|
||||
keyPressed('Space', 0.1);
|
||||
assert.ok(P.swingT >= 0, 'the first tap swings');
|
||||
P.swingT = -1;
|
||||
keyPressed('Space', 0.1);
|
||||
assert.ok(P.swingT >= 0, 'and so does the second');
|
||||
assert.equal(ball.approach, null, 'no run was called');
|
||||
});
|
||||
|
||||
test('THE SMASH: a lob taken out of the air is struck hard to the open court; a deep lob still beats him', () => {
|
||||
|
||||
Reference in new issue
Block a user