Keep the praise word on screen, and take the cliff out of the groundstroke

Oliver on v371: the praise word is cut off on the left on the iPhone. It
is centred under the player's feet, so out wide it ran past the edge;
every pop is now clamped to the canvas by its measured width.

Step 4, measured first. The rally sim gets a human bot (taps on the timed
ring with a timing spread), a Climb mode and a lost-point breakdown. At
level 26 the ladder falls over six rungs and then goes flat: nearly every
lost point is the player's own early whiff, the CPU barely ever misses,
and the auto-run reaches everything, so no opponent dial decides points.
A quality ramp, a follow-through and CPU put-aways were each measured
and reverted; the options go to Oliver in PROJECT.md.

Shipped: in the rally modes q no longer drops from 1 to 0.43 one ms past
PERFECT; it falls smoothly to the window's edge (TUNE.qSeamless). A tap
30 ms early is q 0.86 instead of 0.61. Same window, same whiff edge. v372.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Opus 5.5 committed 2026-10-14 18:01:24 +02:00
1 parent d561050af8
commit 5dd5d71c33
7 files changed
+170 -9

No files matched your search

+9
View File
@@ -1687,6 +1687,15 @@ function trySwing(ch, isAI, qCap = TUNE.aiQualityMax) {
const center = TUNE.swingWindup + win / 2;
const off = Math.abs(t - center) / (win / 2);
let q = Math.max(0.05, 1 - off);
// ...WITHOUT A CLIFF in the rally modes (step 4, 2026-10-14). The line above runs from the centre, so
// q was 1 inside perfectMs and 0.43 a millisecond outside it: a groundstroke's half-window is ~80 ms
// and PERFECT owns 45 of them. The volley's 1.8x window used to hide that (q 0.69 at the edge); step
// 1 made the rally groundstrokes, and a 30 ms early tap came back at q 0.6 (0.89 now). Same window,
// same PERFECT, same whiff edge — only the step between them is gone. It does NOT move the ring bot's
// win rate: points are decided by whiffs, not weak balls (see PROJECT.md, step 4's measurement).
const p = TUNE.perfectMs / 1000, half = win / 2;
if (rallyFeel() && !isAI && TUNE.qSeamless && half > p)
q = Math.max(0.05, 1 - (Math.abs(t - center) - p) / (half - p));
if (Math.abs(t - center) * 1000 < TUNE.perfectMs) q = 1;
ch.facing = dx >= 0 ? 1 : -1; // forehand right (righty), backhand left → mirrored sprite
// the LIVE swipe outranks the released one: since the crossing fires the swing (input.js), the