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:
1 parent
fdbadce94d
commit
3a789eb2e9
6 files changed
+53
-7
No files matched your search
+19
@@ -967,6 +967,25 @@ config assertions, which is exactly why it came back twice.**
|
|||||||
floor against catastrophic regressions, not a proxy for Oliver's thumb; his playtest stays the
|
floor against catastrophic regressions, not a proxy for Oliver's thumb; his playtest stays the
|
||||||
real gate for "reachable".
|
real gate for "reachable".
|
||||||
|
|
||||||
|
**TWENTIETH PASS — the flight recorder, and the statue with a racket.** Oliver asked to be WATCHED:
|
||||||
|
*"can you watch me play in the browser pane and see why I loose"*. A passive 20 Hz recorder was
|
||||||
|
injected (samples score/ball/player/opponent, keeps the last 1.5 s of trajectory before every lost
|
||||||
|
point, touches nothing) and he played a real match in the pane. Seven losses on record, and the
|
||||||
|
verdict was unambiguous — **he swung in every single lost point (4-5 swing frames each) while the
|
||||||
|
ball died 2-3 m to his side, and his own x never moved all game.** His timing was fine. 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, now bitten by the designer
|
||||||
|
himself 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 (the assist only runs with no manual input).
|
||||||
|
Pinned by a test that plays with `autoAssist` 0 — the desktop condition — and asserts he runs.
|
||||||
|
- **The vignette curve is sqrt(damage)**, not linear: the match pool is 8, so hit one was a 10%
|
||||||
|
alpha nobody can see ("basically invisible"). sqrt front-loads it — first blood reads at ~0.3 and
|
||||||
|
it still deepens to the cap.
|
||||||
|
- **The flight-recorder pattern is worth keeping**: instrument passively, let Oliver play, read the
|
||||||
|
trails. It ended a three-round guessing match in one session — his reports and my autopilot
|
||||||
|
measurements kept disagreeing because the autopilot had legs he did not.
|
||||||
|
|
||||||
**Open, and every one of them is Oliver's call after he plays it:**
|
**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
|
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
|
the slice removal (seventh pass) — a cut ball picking its own next victim was the main way this
|
||||||
|
|||||||
+1
-1
@@ -400,6 +400,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script type="module" src="src/main.js?v=171"></script>
|
<script type="module" src="src/main.js?v=173"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -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
|
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
|
// 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
|
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
|
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
|
// of ~27) — the flight time auto-run needs. THE reachability dial
|
||||||
matchOppPacePer: 0.05,// ...climbing back per rung, capped at full pace
|
matchOppPacePer: 0.05,// ...climbing back per rung, capped at full pace
|
||||||
|
|||||||
+7
-2
@@ -1312,12 +1312,17 @@ export function update(dt) {
|
|||||||
P.x += mx * TUNE.playerSpeed * slow * dt;
|
P.x += mx * TUNE.playerSpeed * slow * dt;
|
||||||
P.z += mz * TUNE.playerSpeed * slow * dt;
|
P.z += mz * TUNE.playerSpeed * slow * dt;
|
||||||
let assist = 0;
|
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
|
// 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.
|
// included. The assist puts you there; the volley timing stays honest.
|
||||||
const chasing = ball.lastHit === 'O' || (G.mode === 'splatter' && !G.interlude && ball.vz > 0);
|
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
|
// 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
|
// Run to the ball — then recover. WHERE you recover to is mode-specific and each rule exists
|
||||||
// for a reason:
|
// for a reason:
|
||||||
// GORE SPLATTER recovers in DEPTH ONLY, holding your lane — recovering to HOME.P's x undid
|
// GORE SPLATTER recovers in DEPTH ONLY, holding your lane — recovering to HOME.P's x undid
|
||||||
|
|||||||
+5
-4
@@ -1971,10 +1971,11 @@ export function render() {
|
|||||||
const dmg = 1 - Math.max(0, G.hp) / (G.hpMax || 1);
|
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;
|
const pulse = G.hurtFlash > 0 ? Math.min(1, G.hurtFlash / 0.3) * 0.3 : 0;
|
||||||
if (dmg > 0 || pulse > 0) {
|
if (dmg > 0 || pulse > 0) {
|
||||||
// dmg * 0.8: at half health the frame is unmistakably bloodied, at 1 hp it closes in hard
|
// SQRT of the damage, not the damage: the match pool is 8, so a linear curve made the first
|
||||||
// ("the vignette can get more pronounced") — the inner radius tightens with damage too, so
|
// hit a 10% alpha nobody can see — "the vignette is basically invisible". sqrt front-loads it:
|
||||||
// deep wounds shrink the clean centre instead of only darkening the rim
|
// hit one on 8 hp reads at ~0.3 alpha, and it still deepens all the way to the cap. The inner
|
||||||
const a = Math.min(0.85, dmg * 0.8 + pulse);
|
// 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),
|
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);
|
W / 2, H / 2, Math.max(W, H) * 0.7);
|
||||||
g.addColorStop(0, 'rgba(120,8,12,0)');
|
g.addColorStop(0, 'rgba(120,8,12,0)');
|
||||||
|
|||||||
@@ -570,6 +570,24 @@ test('THE DEATH LOOP: a fault before a lost point must not arm second-serve fore
|
|||||||
assert.equal(G.serveNum, 1, '...whoever won it');
|
assert.equal(G.serveNum, 1, '...whoever won it');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('the match brings its own legs: auto-run works even where autoAssist is 0', () => {
|
||||||
|
// The desktop pane (pointer: fine) has autoAssist 0, so the match played as a statue with a
|
||||||
|
// racket — the flight recorder showed a swing in every lost point and an x that never moved.
|
||||||
|
const assist = TUNE.autoAssist; TUNE.autoAssist = 0; // the desktop condition
|
||||||
|
try {
|
||||||
|
startMatch();
|
||||||
|
for (let i = 0; i < 2.2 * 60; i++) update(DT);
|
||||||
|
G.phase = 'rally';
|
||||||
|
P.x = 0; P.z = 23;
|
||||||
|
ball.active = true; ball.lastHit = 'O'; ball.x = -3; ball.z = 14; ball.y = 1.4;
|
||||||
|
ball.vx = -1; ball.vy = 2; ball.vz = 5; // incoming, wide of him
|
||||||
|
const x0 = P.x;
|
||||||
|
for (let i = 0; i < 45; i++) update(DT);
|
||||||
|
assert.ok(Math.abs(P.x - x0) > 0.5, 'he RUNS for it with no manual input and no coarse pointer: '
|
||||||
|
+ x0.toFixed(1) + ' -> ' + P.x.toFixed(1));
|
||||||
|
} finally { TUNE.autoAssist = assist; }
|
||||||
|
});
|
||||||
|
|
||||||
test('the match recovers to CENTRE between shots — a wide return must not strand you', () => {
|
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
|
// 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
|
// G.mode 'splatter' — so one wide return pulled you off centre and you STAYED there, and the next
|
||||||
|
|||||||
Reference in new issue
Block a user