The serve gets stakes: tight apex window, depth rides quality, the strike flick aims
Three mechanical causes of 'no serve agency', measured then fixed: - the 0.12 m perfect snap was ~0.3 s of hang time (now TUNE.apexSnap 0.05, and the gold band reads the same knob); - the fixed mid-box target depth capped every good serve at ~36 m/s in the loft loop — player serve depth now rides quality, flush aims 0.3 m inside the service line (ladder: 49 / 52 hard / 36 mistimed / 30 bad); - the strike flick was splatter-only — in tennis and the duel it now sways placement (horizontal) and buys pace (vertical, serveHard + serveHardSpin, the extra dive IS the clearance), paid for in scatter against the real fault machinery. Tap stays the safe second serve. Phone verdict pending — see the PROJECT.md session log. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c084d53497
commit
de77ccfea5
6 files changed
+112
-20
No files matched your search
@@ -83,7 +83,7 @@ globalThis.getComputedStyle = () => ({ paddingTop: '0' });
|
||||
|
||||
// ---- game modules (real code) ----------------------------------------------
|
||||
const { G, P, O, score, ball, setupPoint } = await import('./src/state.js');
|
||||
const { update, forceZombies, splatPoint, restartRun, flickTarget, keyPressed, classicGame } = await import('./src/gameplay.js');
|
||||
const { update, forceZombies, splatPoint, restartRun, flickTarget, keyPressed, classicGame, serveApexY } = await import('./src/gameplay.js');
|
||||
const { startDuel, resolveInterlude, startBossFight, endBossFight, startDrill: _startDrill, endDrill, startMatch: _startMatch, endMatch, FALL_LEN } = await import('./src/beats.js');
|
||||
// the drill's first-sight modal freezes the world (that is what modals do) — dismiss it the way a
|
||||
// player would, so the tests step a live court
|
||||
@@ -938,6 +938,26 @@ test('classic tennis: a game is not a set, and six with a two-game lead is', ()
|
||||
assert.equal(G.done, null, 'one set is not a match');
|
||||
});
|
||||
|
||||
test('the toss strike has stakes: flush is fast, a miss is slow, the old free window is gone', () => {
|
||||
// the serve-agency pass: apexSnap/apexFall are TUNE knobs and deliberately tight, so timing
|
||||
// must show up in the ball's actual pace (the fault economy then prices the aimed serve)
|
||||
G.mode = 'tennis'; G.classic = true; G.match = null; G.interlude = false; G.done = null;
|
||||
const strike = dy => {
|
||||
G.phase = 'pretoss'; ball.active = false; score.server = 'P'; P.pose = 'idle';
|
||||
keyPressed('Space'); // toss
|
||||
assert.equal(G.phase, 'toss', 'the tap tossed');
|
||||
ball.y = serveApexY() - dy; ball.vy = 0.1; // park the ball dy below the apex
|
||||
keyPressed('Space'); // strike
|
||||
assert.equal(G.phase, 'rally', 'the strike fired');
|
||||
return Math.hypot(ball.vx, ball.vy, ball.vz);
|
||||
};
|
||||
const flush = strike(0);
|
||||
const oldFree = strike(0.1); // inside the old 0.12 snap, outside the new one
|
||||
const missed = strike(0.4);
|
||||
assert.ok(flush > missed * 1.2, `a flush strike is materially faster (${flush.toFixed(1)} vs ${missed.toFixed(1)})`);
|
||||
assert.ok(oldFree < flush - 0.5, 'the 0.12 m free-perfect window no longer exists');
|
||||
});
|
||||
|
||||
test('classic tennis: the second set ends the run, with no corpse', () => {
|
||||
G.mode = 'tennis'; G.classic = true;
|
||||
G.sets = [1, 0]; score.games = [6, 0]; G.runGames = 14;
|
||||
|
||||
Reference in new issue
Block a user