Time a tap from touch-down, and give the opponents' legs a knee

Oliver after step 1: much harder, stuck at the Steward, "trotz gutem
Timing nicht mehr bekommen". A tap only proves it is a tap when the
finger lifts, so the swing started 100-150 ms after he meant it; the old
volley window hid that, step 1's groundstrokes did not. A tap made on the
gold ring fell from q 0.82 to 0.09 (and a third were missed at 150 ms),
murky balls landed short and the opponent came in. The swing clock now
starts at the touch's age (rally modes only); flicks keep their crossing.

The opponents' legs had no ceiling: 7.3 m/s at the Runner, 15.7 at the
Champion against the player's ~6.2. Past legKnee (6 m/s) only 15% of the
growth survives, monotone, so the ladder order holds; champion ~7.5. v362.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Opus 5.5 committed 2026-10-14 11:06:08 +02:00
1 parent 4e7c816a25
commit f9b0dd2efb
6 files changed
+82 -7

No files matched your search

+34 -1
View File
@@ -920,7 +920,8 @@ test('league 5 has no speed dip below league 4, including serve and wrong-footed
const fourth = cfg.leagueOf(4).band, fifth = cfg.leagueOf(5).band;
let floor = 0;
for (let n = fourth[0]; n <= fourth[1]; n++) floor = Math.max(floor, measure(n, serve, surprised));
assert.ok(floor > 7, 'the rig measures a real full-speed chase');
// since the legs have a knee (gameplay.knee) the late ladder runs at ~6-7.5 m/s, not 7-15
assert.ok(floor > TUNE.legKnee * 0.8, 'the rig measures a real full-speed chase: ' + floor.toFixed(2));
for (let n = fifth[0]; n <= fifth[1]; n++) {
const speed = measure(n, serve, surprised);
assert.ok(speed >= floor, `${cfg.matchFoe(n).short}: ${speed} must clear ${floor} (serve ${serve}, surprised ${surprised})`);
@@ -1136,6 +1137,38 @@ test('a SHORT ball is an approach, a deep one keeps the baseliner back, and nobo
} finally { TUNE.matchFlub = flub; }
});
test('a TAP is timed from touch-down: a normal 120 ms tap on the gold ring still returns cleanly', () => {
// Oliver, 2026-10-14: "manchmal scheine ich den Ball trotz gutem Timing nicht mehr zu bekommen".
// A tap only proves it is a tap when the finger LIFTS, so it swung ~100-150 ms late — inside the
// groundstroke window that took a tap made exactly on the gold ring from q 0.82 to 0.09, and at
// 150 ms a third of the balls were simply missed. keyPressed's `lead` starts the swing clock at
// touch-down; this is the ring-tapping player, with a real finger's 120 ms of tap on top.
const assist = TUNE.autoAssist; TUNE.autoAssist = 1.15;
try {
startMatch(); G.interlude.introT = 0; G.match.opp = 6;
for (let i = 0; i < 2.2 * 60; i++) update(DT);
const LAG = 7; // frames: ~120 ms between the finger landing and it lifting
let fire = -1, armed = false, incoming = 0, returned = 0, sumQ = 0;
for (let i = 0; i < 90 * 60; i++) {
G.modal = null; G.modalQ.length = 0; G.hp = G.hpMax; G.match.opp = 6;
if (G.phase === 'pretoss' && !(G.interlude && G.interlude.introT > 0)) { if (i % 40 === 0) keyPressed('Space'); }
else if (G.phase === 'toss') { if (ball.vy < 0.4 && ball.y > 2.4) keyPressed('Space'); }
else if (G.phase === 'rally' && ball.active && ball.lastHit === 'O' && ball.vz > 0 && !armed) {
const d = Math.hypot(ball.x - P.x, (ball.z - P.z) * 0.8);
if (Math.abs(d - TUNE.matchRingSweet) < TUNE.matchRingBand) { armed = true; incoming++; fire = i + LAG; }
}
if (i === fire) keyPressed('Space', LAG / 60); // the lift: the touch's age rides along
const before = ball.lastHit;
update(DT);
if (armed && before === 'O' && ball.lastHit === 'P') { returned++; sumQ += ball.pow; armed = false; }
else if (armed && (!ball.active || G.phase !== 'rally')) armed = false;
}
assert.ok(incoming >= 30, 'a real sample: ' + incoming);
assert.ok(returned / incoming >= 0.9, `a gold-ring tap returns the ball: ${returned}/${incoming}`);
assert.ok(sumQ / returned >= 0.6, 'and returns it cleanly: mean q ' + (sumQ / returned).toFixed(2));
} finally { TUNE.autoAssist = assist; }
});
test('rally depth: deep by default, mistiming lands it SHORTER, never longer', () => {
// the only out a rally ball may find is wide — depth must never put one past the baseline
startMatch(); G.interlude.introT = 0;