The flight recorder finds the statue with a racket

"can you watch me play in the browser pane and see why I loose". A passive
20Hz recorder sampled his real play: seven losses, and in every single one he
SWUNG (his timing was fine) while the ball died 2-3m to his side and his own
x never moved all game. He had no legs: autoAssist keys off pointer:coarse,
the pane reports a fine pointer, so auto-run was 0 — the exact trap the
verification recipe has warned about for weeks, bitten in live play.

matchAssist 1.15: the match brings its own legs on every device. It is a
tap-timing game; footwork is the game's job. Held keys still override. Pinned
by a test that plays with autoAssist 0 and asserts he runs.

The vignette curve is sqrt(damage): the match pool is 8, so hit one was a 10%
alpha nobody can see. sqrt front-loads it — first blood reads at ~0.3.

This also explains three rounds of disagreement between his reports and my
measurements: my autopilot always had legs (it set autoAssist by hand), his
pane play never did.

47 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:28:47 +02:00
1 parent fdbadce94d
commit 3a789eb2e9
6 files changed
+53 -7

No files matched your search

+3
View File
@@ -394,6 +394,9 @@ export const TUNE = {
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
matchFlubKeep: 0.82, // ...and it keeps only this much per rung — the ladder now that hp is flat
matchAssist: 1.15, // auto-run floor DURING A MATCH, any device. autoAssist keys off pointer:coarse,
// so desktop had no legs at all — the match is a tap-timing game and footwork
// is the game's job. Held keys still override
matchOppPace: 0.78, // scale on the opponent's return SPEED for opponent one (~21 m/s instead
// of ~27) — the flight time auto-run needs. THE reachability dial
matchOppPacePer: 0.05,// ...climbing back per rung, capped at full pace
+7 -2
View File
@@ -1312,12 +1312,17 @@ export function update(dt) {
P.x += mx * TUNE.playerSpeed * slow * dt;
P.z += mz * TUNE.playerSpeed * slow * dt;
let assist = 0;
if (!mx && !mz && TUNE.autoAssist > 0 && ball.active && !G.horde && !(G.mgT > 0)) { // auto-fire: you aim by hand, no assist
// THE MATCH BRINGS ITS OWN LEGS, on every device. autoAssist keys off pointer:coarse, so the
// desktop Browser pane (pointer: fine) had NO auto-run at all — the flight recorder showed
// Oliver swinging in every lost point while his x never moved: a statue with a racket. The
// match is a tap-timing game; footwork is the game's job. Held keys still override (mx/mz).
const assistK = G.match ? Math.max(TUNE.autoAssist, TUNE.matchAssist) : TUNE.autoAssist;
if (!mx && !mz && assistK > 0 && ball.active && !G.horde && !(G.mgT > 0)) { // auto-fire: you aim by hand, no assist
// splatter: anything travelling back toward you is yours to run down — your own rebound
// included. The assist puts you there; the volley timing stays honest.
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);
const rate = TUNE.playerSpeed * assistK * dt * (chasing ? 1 : 0.55);
// 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
+5 -4
View File
@@ -1971,10 +1971,11 @@ export function render() {
const dmg = 1 - Math.max(0, G.hp) / (G.hpMax || 1);
const pulse = G.hurtFlash > 0 ? Math.min(1, G.hurtFlash / 0.3) * 0.3 : 0;
if (dmg > 0 || pulse > 0) {
// dmg * 0.8: at half health the frame is unmistakably bloodied, at 1 hp it closes in hard
// ("the vignette can get more pronounced") — the inner radius tightens with damage too, so
// deep wounds shrink the clean centre instead of only darkening the rim
const a = Math.min(0.85, dmg * 0.8 + pulse);
// SQRT of the damage, not the damage: the match pool is 8, so a linear curve made the first
// hit a 10% alpha nobody can see — "the vignette is basically invisible". sqrt front-loads it:
// hit one on 8 hp reads at ~0.3 alpha, and it still deepens all the way to the cap. The inner
// radius tightens with damage too, so deep wounds shrink the clean centre.
const a = Math.min(0.85, Math.sqrt(dmg) * 0.8 + pulse);
const g = ctx.createRadialGradient(W / 2, H / 2, Math.min(W, H) * (0.5 - dmg * 0.22),
W / 2, H / 2, Math.max(W, H) * 0.7);
g.addColorStop(0, 'rgba(120,8,12,0)');