The match walks back to centre — "unreachable" was the player, not the CPU

"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 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Opus 5 committed 2026-08-11 23:02:14 +02:00
1 parent 24be613c61
commit d6df582cd4
5 files changed
+55 -11

No files matched your search

+19
View File
@@ -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
+1 -1
View File
@@ -400,6 +400,6 @@
</div>
</div>
</div>
<script type="module" src="src/main.js?v=168"></script>
<script type="module" src="src/main.js?v=169"></script>
</body>
</html>
+4 -3
View File
@@ -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
+12 -7
View File
@@ -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
+19
View File
@@ -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