From 804485745706c87361d988868319429a3cb2efe0 Mon Sep 17 00:00:00 2001 From: Oliver Rieger Date: Fri, 14 Aug 2026 12:07:59 +0200 Subject: [PATCH] HARD finally outruns the runner MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Oliver benchmarked HARD against the game's own roster: "the runner in zombie mode is way faster than the regular player in hard" — and the numbers agreed. The RUNNER covers 6.15 m/s (racketSpeed x rung x foe.speed) with no reaction gate; HARD's classic CPU did 5.59, only after its 0.22 s read, and ambled back to centre at HALF speed between shots — that recovery jog was the free points. HARD now moves 4.3 x 1.5 = 6.45 m/s, and DIFFS grows `recover`: the between-shots factor, 0.85 on HARD where the shipped 0.5 stays pinned on MEDIUM. Wrong-footing him still works; strolling him side to side no longer does. Co-Authored-By: Claude Fable 5 --- index.html | 2 +- src/config.js | 10 +++++++--- src/gameplay.js | 4 +++- test.js | 2 ++ 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/index.html b/index.html index 293499a..d04581c 100644 --- a/index.html +++ b/index.html @@ -492,6 +492,6 @@ if (location.hostname !== 'splatter-tennis.wuwi.one') { - + diff --git a/src/config.js b/src/config.js index af10f70..e3c5f4e 100644 --- a/src/config.js +++ b/src/config.js @@ -661,10 +661,14 @@ export const MENU = [ // the dial that turns "returns everything" into "plays you". HARD got a second pass after // Oliver's "still too easy": it now reads your shot in 0.22 s, covers court at 1.3x, floors its // quality at 0.70, and hunts the corner you are not standing in four times out of five. +// `recover` scales the between-shots jog (the rally block's 0.5 "slow recovery: wrong-footing wins +// points") — a HARD CPU that ambles back to centre is free points, a recovering one is a wall. +// Speed calibration: the zombie RUNNER covers 2.8 x 1.8 x 1.22 = 6.15 m/s with no reaction gate, +// and Oliver clocked HARD as slower than him — HARD now moves 4.3 x 1.5 = 6.45. export const DIFFS = [ - { id: 'easy', name: 'EASY', flub: 0.30, quality: 0.62, qMin: 0.45, speed: 0.80, reaction: 0.62, reach: 1.45, serve: 0.75, smart: 0 }, - { id: 'medium', name: 'MEDIUM', flub: 0.10, quality: 0.86, qMin: 0.45, speed: 1.0, reaction: 0.45, reach: 1.6, serve: 1.0, smart: 0 }, - { id: 'hard', name: 'HARD', flub: 0.01, quality: 1.0, qMin: 0.70, speed: 1.30, reaction: 0.22, reach: 1.85, serve: 1.15, smart: 0.8 }, + { id: 'easy', name: 'EASY', flub: 0.30, quality: 0.62, qMin: 0.45, speed: 0.80, reaction: 0.62, reach: 1.45, serve: 0.75, smart: 0, recover: 0.4 }, + { id: 'medium', name: 'MEDIUM', flub: 0.10, quality: 0.86, qMin: 0.45, speed: 1.0, reaction: 0.45, reach: 1.6, serve: 1.0, smart: 0, recover: 0.5 }, + { id: 'hard', name: 'HARD', flub: 0.01, quality: 1.0, qMin: 0.70, speed: 1.50, reaction: 0.22, reach: 1.85, serve: 1.15, smart: 0.8, recover: 0.85 }, ]; // THE TOURNAMENT (Oliver's pick over the endless ladder, 2026-08-13): four rounds up the club's diff --git a/src/gameplay.js b/src/gameplay.js index d62a2e6..77293ca 100644 --- a/src/gameplay.js +++ b/src/gameplay.js @@ -1733,7 +1733,9 @@ export function update(dt) { const incoming = ball.active && ball.lastHit === 'P' && G.aiReactT >= (G.diff ? G.diff.reaction : TUNE.aiReaction); const tgt = incoming ? intercept('O') : HOME.O; - const spd = TUNE.aiSpeed * (G.diff ? G.diff.speed : 1) * (incoming ? 1 : 0.5); // slow recovery: wrong-footing wins points + const spd = TUNE.aiSpeed * (G.diff ? G.diff.speed : 1) + * (incoming ? 1 : (G.diff ? G.diff.recover : 0.5)); // slow recovery: wrong-footing wins points + const dx = tgt.x - O.x, dz = tgt.z - O.z, d = Math.hypot(dx, dz); if (d > 0.1) { O.x += dx / d * Math.min(spd * dt, d); diff --git a/test.js b/test.js index c010594..53d8555 100644 --- a/test.js +++ b/test.js @@ -1127,7 +1127,9 @@ test('classic wears the match feel, and the difficulty table is a ladder', () => assert.ok(e.qMin <= m.qMin && m.qMin < h.qMin, 'harder raises the quality floor'); assert.ok(e.serve < m.serve && m.serve < h.serve, 'harder serves better'); assert.ok(e.smart <= m.smart && m.smart < h.smart, 'harder plays the open court'); + assert.ok(e.recover <= m.recover && m.recover < h.recover, 'harder recovers to centre faster'); assert.equal(m.smart, 0, 'MEDIUM aims at random, as the shipped classic did'); + assert.equal(m.recover, 0.5, 'MEDIUM keeps the shipped recovery jog'); assert.equal(m.flub, TUNE.classicFlub, 'MEDIUM flub IS classicFlub'); assert.equal(m.quality, TUNE.classicQualityMax, 'MEDIUM quality IS classicQualityMax'); assert.equal(m.speed, 1.0, 'MEDIUM leaves aiSpeed alone');