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

+3
View File
@@ -578,6 +578,9 @@ export const TUNE = {
// the peer gift; reaching parity earns all of it. Never a penalty.
peerRun: 0.08, // parity adds 8% legs on top of the permanent level bonus
peerWindow: 0.12, // ...and 12% contact window. Skill still wins; grinding opens the margin
legKnee: 6.0, // m/s past which an opponent's legs stop growing at full rate (gameplay.knee) —
// the player's auto-run is ~6.2. Was uncapped: the champion ran 15.7 m/s
legSoft: 0.15, // ...and the share of the growth past the knee that survives (champion ~7.5)
matchSpeedBase: 1.25, // racketAI speed multiplier for opponent ONE. 1.4 was calibrated against
// the broken recovery (the player never walked back to centre, so every
// wide return looked like opponent strength) — with real footwork restored
+19 -4
View File
@@ -1198,7 +1198,15 @@ const matchStep = () => {
const n = G.match.opp - 1;
return n + (!G.net && !G.match.cup && !G.match.train ? 4 * Math.max(0, 1 - n / 7) : 0);
};
const rzChaseSpeed = rz => TUNE.racketSpeed * leaguePace()
// THE LEGS HAVE A KNEE (2026-10-14). The ladder multiplied speed rung on rung with nothing on top:
// 7.3 m/s at the RUNNER, 10.6 at the HEAD COACH, 15.7 at the CHAMPION — a sprinter's top speed and
// 2.5x the player's ~6.2. Under step 1's geometry a late opponent at the net then covered every pass.
// Up to legKnee nothing changes; past it only legSoft of the growth survives, so the order of the
// ladder (and every like-for-like speed test) holds while the champion lands near ~7.5. Wrong-footing,
// the read, placement and consistency are what climb past it now.
const knee = v => v <= TUNE.legKnee ? v : TUNE.legKnee + (v - TUNE.legKnee) * TUNE.legSoft;
const rzChaseSpeed = rz => knee(rzRawSpeed(rz));
const rzRawSpeed = rz => TUNE.racketSpeed * leaguePace()
* (G.match ? (TUNE.matchSpeedBase + matchStep() * TUNE.matchSpeedPer) * ((rz.foe && rz.foe.speed) || 1)
: 1 + TUNE.duelSpeedMax * duelSkill())
* (G.wasServe && ball.lastHit === 'P' ? TUNE.aiReturnDash : 1)
@@ -1750,7 +1758,7 @@ function meleeCooldown() {
TUNE.meleeCool / (1 + Math.max(0, near - 1) * TUNE.meleeCrowd));
}
export function keyPressed(code) {
export function keyPressed(code, lead = 0) {
// THE SPRITE BENCH takes the key before anything else reads it: it is a back-room art test with no
// ball and no scoring, so every other meaning a key has here is meaningless inside it.
if (G.bench) return benchKey(code);
@@ -1840,8 +1848,15 @@ export function keyPressed(code) {
return;
}
if (G.phase === 'rally' && P.swingT < 0) {
recordEvent('swing', { window: TUNE.swingWindow * winLevel(), assist: assistK() });
P.swingT = 0; P.pose = 'swing'; P.poseT = 0; G.swung = true;
recordEvent('swing', { window: TUNE.swingWindow * winLevel(), assist: assistK(), lead });
// A TAP COUNTS FROM THE MOMENT THE FINGER LANDS (rally modes). A tap is only known to BE a tap
// when the finger lifts — it could still turn into a flick — so it used to swing ~100-150 ms
// after the player meant it. Inside step 1's groundstroke window that collapsed the contact
// quality of a tap made exactly on the gold ring from 0.82 to 0.09 (Oliver, 2026-10-14: "trotz
// gutem Timing nicht mehr bekommen"). input.js hands the touch's age in as `lead`, and the swing
// clock starts that far along: the timing is judged from touch-down, the ball still has to be
// there for the contact.
P.swingT = rallyFeel() ? lead : 0; P.pose = 'swing'; P.poseT = 0; G.swung = true;
}
}
+1 -1
View File
@@ -223,7 +223,7 @@ const endTouch = e => {
// (a bare hold used to fire at a free auto-target; that was the whole exploit)
if (aim && dist >= TUNE.pullMin) dragServe(aim);
}
else if (!stick.moved && dt < 250) { if (!stick.nearRack) keyPressed('Space'); } // tap — dead if it was a near-miss at the rack
else if (!stick.moved && dt < 250) { if (!stick.nearRack) keyPressed('Space', dt / 1000); } // tap — dead if it was a near-miss at the rack; timed from touch-down (keyPressed)
else if (!stick.adopted && dist > TUNE.flickMin
&& (stick.swung || dt < FLICK_MS || (rallyFeel() && G.phase === 'rally'))) {
// flick = aimed swing. The FLICK_MS deadline only still applies where the swing could NOT