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');