From d6df582cd45db96eaad4ec147410b93e8c6e5e86 Mon Sep 17 00:00:00 2001 From: Oliver Rieger Date: Tue, 11 Aug 2026 23:02:14 +0200 Subject: [PATCH] =?UTF-8?q?The=20match=20walks=20back=20to=20centre=20?= =?UTF-8?q?=E2=80=94=20"unreachable"=20was=20the=20player,=20not=20the=20C?= =?UTF-8?q?PU?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "every second ball is basically unreachable by an auto-run player.. are there some hidden gestures I don't know about or is it just a very strong cpu?" Neither. The match inherited gore-splatter's depth-only recovery — the sidestep-protection rule — by being G.mode 'splatter'. One wide return pulled you off centre and you stayed there, so the next ball to the opposite corner was lost before it was struck. A player who never walks back experiences every rally as unreachable, and it reads as CPU strength because the balls keep landing where he isn't. The match recovers to centre now, like tennis: real footwork, and in a rally there is no artillery lane to protect. Gore splatter keeps depth-only — the sidestep it protects has no role inside a rally. matchSpeedBase 1.4 -> 1.25: it was calibrated against a player who couldn't move. The full attacking vocabulary is recorded in PROJECT.md — nothing else is hidden. The placement flick (aim during the swing) is taught nowhere on touch; flagged as a ghost-hand lesson if the mode ships. 42 tests, 12 seeds clean. Co-Authored-By: Claude Opus 5 --- PROJECT.md | 19 +++++++++++++++++++ index.html | 2 +- src/config.js | 7 ++++--- src/gameplay.js | 19 ++++++++++++------- test.js | 19 +++++++++++++++++++ 5 files changed, 55 insertions(+), 11 deletions(-) diff --git a/PROJECT.md b/PROJECT.md index cfa56df..d77782b 100644 --- a/PROJECT.md +++ b/PROJECT.md @@ -912,6 +912,25 @@ re-diagnosing.** The serveNum reset was real and stays, but it was found by read reproducing the report, and the actual loop needed the exact field conditions (a zombie touch, the new serve position, panic-speed taps) to show itself. +**SEVENTEENTH PASS — "every second ball is unreachable" was the player, not the CPU.** Oliver: +*"the opponent is really hard to beat now.. every second ball is basically unreachable by an +auto-run player.. are there some hidden gestures I don't know about or is it just a very strong +cpu?"* Neither: **the match had inherited gore-splatter's depth-only recovery** (the fifteenth-pass +sidestep-protection rule) by virtue of being `G.mode 'splatter'` — so one wide return pulled you off +centre and you STAYED there, and the next ball to the opposite corner was lost before it was struck. +A player who never walks back to centre experiences every rally as "unreachable"; it read as CPU +strength because the balls kept landing where he wasn't. +- The match recovers to CENTRE now, like tennis — real footwork, and in a rally there is no + artillery lane to protect. Gore splatter keeps the depth-only rule; the sidestep it protects has + no role inside a rally. +- `matchSpeedBase` 1.4 -> 1.25: it was calibrated against the broken recovery, i.e. against a + player who couldn't move. With footwork restored the ladder starts gentler and still climbs. +- For the record, the full attacking vocabulary — nothing else is hidden: timing quality (the ring + and the band), PLACEMENT by flicking/holding a direction during the swing (`doHit`'s aim — deep, + short, wide; wrong-footing is how points are won, the AI recovers slowly on purpose), VOLLEYS + (stepping in to take the ball pre-bounce carries reach x1.4 / window x1.8), and serve pace off the + apex. The placement flick is taught nowhere on touch — worth a ghost-hand lesson if the mode ships. + **Open, and every one of them is Oliver's call after he plays it:** 1. ~~**A ball that carries through the lane can complete two steps at once.**~~ Largely CLOSED by the slice removal (seventh pass) — a cut ball picking its own next victim was the main way this diff --git a/index.html b/index.html index aa575a5..a0a7c19 100644 --- a/index.html +++ b/index.html @@ -400,6 +400,6 @@ - + diff --git a/src/config.js b/src/config.js index 2f41a56..4ed6a92 100644 --- a/src/config.js +++ b/src/config.js @@ -386,9 +386,10 @@ export const TUNE = { matchPlayerHp: 8, // your pool for the MATCH — richer than the gore mode's 4, because a real game // has rallies you deserve to lose without the run ending ("the player is too // weak in this mode"). Their point still costs 1; matchHeal is how you last - matchSpeedBase: 1.4, // racketAI speed multiplier for opponent ONE — the duel's 2.8 m/s lumber was - // tuned for a single sudden-death point and read as "very weak and slow" - // across a whole game (~3.9 m/s now, rising per rung) + 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 + // it starts gentler and the ladder still climbs matchSpeedPer: 0.12, // ...growing this much per opponent felled matchFlub: 0.08, // the match's own whiff base — the duel's 0.2 gives away a fifth of all // returns, which across a game is an opponent that beats itself diff --git a/src/gameplay.js b/src/gameplay.js index fbf71c0..44ffb0e 100644 --- a/src/gameplay.js +++ b/src/gameplay.js @@ -1296,14 +1296,19 @@ export function update(dt) { const chasing = ball.lastHit === 'O' || (G.mode === 'splatter' && !G.interlude && ball.vz > 0); // no `slow` here: tapping must not freeze your legs, or anticipating a shot loses it const rate = TUNE.playerSpeed * TUNE.autoAssist * dt * (chasing ? 1 : 0.55); - // Run to the ball — then recover in DEPTH ONLY, holding whatever lane you are standing in. - // Recovering to HOME.P's x undid the sidestep: measured, a step to 1.94 m was dragged back to - // 0.77 m over one ball's flight, so firing gave back most of a dodge you had spent a cooldown - // on. That is the "welded in place" complaint the sidestep exists to answer, arriving slowly - // instead of all at once. TENNIS keeps the full recovery — recovering to centre is the real - // sport's footwork and there is no sidestep there to protect. + // Run to the ball — then recover. WHERE you recover to is mode-specific and each rule exists + // for a reason: + // GORE SPLATTER recovers in DEPTH ONLY, holding your lane — recovering to HOME.P's x undid + // the sidestep (measured: a step to 1.94 m dragged back to 0.77 m over one ball's flight), + // which is the "welded in place" complaint the sidestep answers, arriving slowly. + // TENNIS and THE MATCH recover to CENTRE — that is the real sport's footwork, and in a + // rally there is no artillery lane to protect. The match inherited the gore rule by being + // G.mode 'splatter', and the cost was exactly Oliver's "every second ball is basically + // unreachable": one wide return pulled you off centre and you STAYED there, so the next + // ball to the opposite corner was lost before it was struck. Not a strong CPU — a player + // who never walked back. const tgt = chasing ? intercept('P') - : G.mode === 'splatter' ? { x: P.x, z: HOME.P.z } + : G.mode === 'splatter' && !G.match ? { x: P.x, z: HOME.P.z } : HOME.P; const dx = tgt.x - P.x, dz = tgt.z - P.z, d = Math.hypot(dx, dz); if (d > 0.35) { // deadzone: stand ready near the spot instead of dithering over it diff --git a/test.js b/test.js index 766771a..46a88eb 100644 --- a/test.js +++ b/test.js @@ -570,6 +570,25 @@ test('THE DEATH LOOP: a fault before a lost point must not arm second-serve fore assert.equal(G.serveNum, 1, '...whoever won it'); }); +test('the match recovers to CENTRE between shots — a wide return must not strand you', () => { + // The match inherited gore-splatter's depth-only recovery (a sidestep-protection rule) by being + // G.mode 'splatter' — so one wide return pulled you off centre and you STAYED there, and the next + // ball to the opposite corner was unreachable before it was struck. "Every second ball is + // basically unreachable" was this, not CPU strength. + const assist = TUNE.autoAssist; TUNE.autoAssist = 1.15; // headless matchMedia reports desktop: assist off + try { + startMatch(); + for (let i = 0; i < 2.0 * 60; i++) update(DT); + G.phase = 'rally'; + P.x = 4.2; P.z = 22.8; // stranded wide by the last return + ball.active = true; ball.lastHit = 'P'; ball.vz = -8; ball.z = 8; ball.y = 1; // your ball, outbound: not chasing + const x0 = P.x; + for (let i = 0; i < 60; i++) update(DT); + assert.ok(Math.abs(P.x) < Math.abs(x0) - 0.5, + 'auto-run walks back toward centre: ' + x0.toFixed(1) + ' -> ' + P.x.toFixed(1)); + } finally { TUNE.autoAssist = assist; } +}); + test('THE REAL DEATH LOOP: a toss after their rally touch is not their passing shot', () => { // tossBall left ball.lastHit stale. After any rally the zombie touched last, the fresh toss still // said lastHit 'O' — and the match serves from BEHIND the baseline (z 24.3), past the passing-shot