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

+3
View File
@@ -153,6 +153,9 @@ export const TUNE = {
// screen: a short swipe is a safe half-court ball, and only a committed
// one goes hunting the line, which is what makes going OUT a chosen risk.
perfectMs: 45, // |offset from window center| for PERFECT
qSeamless: 1, // rally modes: past perfectMs, q falls from 1 to the window edge with no step.
// 0 = the old curve, 1 - off/half, which dropped a groundstroke 1 ms past
// PERFECT straight to q 0.43 (step 4, 2026-10-14 — see gameplay.trySwing)
// The tennis CPU only exists in the OPENING match (after the transition, tennis never comes
// back — duels use racket* dials), so these three are the "how long until the first game is won"
// knobs. Testers needed several games to get past him: softer cap, twice the whiffs, and
+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
+5 -1
View File
@@ -2883,7 +2883,11 @@ export function render() {
ctx.save();
ctx.globalAlpha = Math.min(1, f * 3); // holds, then a quick fade at the end
ctx.font = B(13); ctx.textAlign = 'center'; ctx.textBaseline = 'top';
shadowText(p.text, Math.round(pr.x), Math.round(pr.y - (p.still ? 0 : (1 - f) * 14)), p.col || '#ffffff');
// kept on screen: a word under a player out wide (the rally's praise, v371) is centred on his feet,
// and "SHOT OF THE DAY" off a man near the left tramline lost its first letters on the iPhone
const hw = ctx.measureText(p.text).width / 2 + 3;
const x = hw * 2 < W ? Math.min(W - hw, Math.max(hw, pr.x)) : W / 2;
shadowText(p.text, Math.round(x), Math.round(pr.y - (p.still ? 0 : (1 - f) * 14)), p.col || '#ffffff');
ctx.restore();
}
// (a PERFECT-contact spark ring lived here for one build — Oliver's verdict: too much. The