A gesture is read in a pause, not in a point

The flick lesson rode the incoming ball, over the player's shoulder — the one
moment he has no attention to spare for reading anything. It moves into the
room this game already keeps for teaching gestures: the serve stance, standing
still, mimed on the apron by the very demo the slingshot lesson plays. Same
clock, same hand, same words slot — one teacher, three lessons, and nothing new
drawn over a live rally.

The words carry both halves now, SWIPE AS YOU HIT TO AIM: WHEN alone teaches
nothing, and WHAT FOR alone gets tried at the wrong moment and read as a
gesture that does not work.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Opus 5 committed 2026-08-17 08:45:23 +02:00
1 parent 67cb0b74d7
commit 553b765846
3 files changed
+35 -31

No files matched your search

+27 -28
View File
@@ -2140,26 +2140,22 @@ function drawHitHint() {
// goes, and wrong-footing the body across the net is how points are actually won. It was taught
// nowhere on touch: the placement ring shows the result of a flick, which is a receipt, not a
// lesson — if you never happen to swipe you never get one, so you never learn there was anything
// to learn. Same ghost hand as every other gesture in this game, over the same shoulder, riding
// the same failure it answers (a run of returns hit flat — doHit resets flatStreak the moment one
// leaves with an aim on it). It mimes BOTH ways in turn or it teaches one corner.
// to learn.
// IT IS TAUGHT IN THE PAUSE, NOT IN THE POINT. It first rode the incoming ball, over the player's
// shoulder — which is the moment he has the least attention to spare for reading anything: "it is
// displayed mid-play.. please rather show it in one of the pauses" (Oliver, 2026-08-17). So it
// moved into the room this game already keeps for teaching gestures: standing still in the serve
// stance, on the apron, in the very demo the slingshot lesson plays. Same clock, same mime, same
// words slot — one teacher, three lessons, and nothing new drawn over live play.
// Both ways in turn, or it teaches one corner, and away from whichever edge he is standing against.
function drawFlickHint() {
const cyc = 1.4, t = G.idleT % cyc;
const dir = (((G.idleT / cyc) | 0) % 2) ? -1 : 1;
const slide = Math.min(1, t / 0.8), gone = t > 0.95; // stroke, held a beat, lifted, repeat
if (gone) return;
const p0 = proj(P.x, 0, P.z);
const hx = Math.round(p0.x), hy = Math.round(p0.y) - 52;
ctx.save(); ctx.globalAlpha = 0.9;
ctx.fillStyle = UI.dim; // the track the thumb travels
for (let d = 10; d <= 38; d += 7) ctx.fillRect(hx + dir * d - 1, hy, 2, 2);
drawGhostHand(hx + dir * (10 + 28 * slide), hy, 1); // starts clear of his own sprite
ctx.font = F(10); ctx.textAlign = 'center'; ctx.textBaseline = 'top';
// ...and the words sit ABOVE the stroke and OPPOSITE it: below is the player's own feet and the
// BASELINE, and 10px type across a bright white line is the one place on this court it cannot be
// read. Clamped off the screen edges too — this rides the player and he stands in the corners.
shadowText('SWIPE AS YOU HIT', Math.max(52, Math.min(W - 52, hx - dir * 22)), hy - 26, UI.warm);
ctx.restore();
const n = ((G.idleHintT - TUNE.pullHintAfter) / 1.9) | 0;
const px = proj(P.x, 0, P.z).x;
// ...and the words carry BOTH halves, because either one alone is useless: WHEN (as you hit —
// a swipe at any other moment does nothing, and a player who tries one and sees nothing concludes
// the gesture is not there) and WHAT FOR (it aims). One line, the same imperative as PULL DOWN
// and TAP AS IT ARRIVES.
demoSwipe(px < W * 0.3 ? 1 : px > W * 0.7 ? -1 : (n % 2 ? -1 : 1), 0, 'SWIPE AS YOU HIT TO AIM');
}
// The modal: drawn LAST, over everything, and the world behind it is frozen (gameplay.update
@@ -2729,19 +2725,22 @@ export function render() {
if (txt) shadowText(txt, W / 2, METER_Y(), UI.warm); // same slot the draw meter uses
if (demo) drawPullHint(); // show the gesture instead of only naming it
}
// the tennis struggle hints: both ride the exact failure they answer, and only one at a time —
// teaching the flick to someone who cannot get a racket on the ball is the wrong lesson, so the
// tap always wins.
// THE FLICK LESSON BELONGS TO THE RALLY MODES, which is what rallyFeel() already means: CLASSIC
// and ZOMBIE TENNIS. Not the gore mode, and not gore's OPENER either — that tennis is a prologue
// you play for one game before the court turns, and placement is not the skill it is asking for.
// the tennis struggle hints, each riding the exact failure it answers.
// The TAP lesson stays on the incoming ball: it is about a moment, and a moment can only be
// taught while it is happening. The FLICK lesson is about a gesture, and a gesture is read in a
// PAUSE — so it waits for the serve stance and mimes itself on the apron there, exactly like the
// slingshot's. Nothing new is drawn over a live rally.
// It belongs to the RALLY MODES, which is what rallyFeel() already means: CLASSIC and ZOMBIE
// TENNIS. Not the gore mode, and not gore's OPENER either — that tennis is a prologue you play
// for one game before the court turns, and placement is not the skill it is asking for.
// (`G.mode === 'tennis'` covered classic AND that prologue, which is exactly the wrong seam; the
// match itself is not mode 'tennis' at all, it plays under splatter's permanent interlude.)
// Touch only: on a keyboard the aim is held keys and 'SWIPE' would name a gesture that does not
// exist there.
const inRally = G.phase === 'rally' && ball.active && ball.lastHit === 'O';
if (G.mode === 'tennis' && inRally && G.passStreak >= 2) drawHitHint();
else if (IS_TOUCH && inRally && rallyFeel() && G.flatStreak >= TUNE.flickHintAfter)
if (G.mode === 'tennis' && G.phase === 'rally' && G.passStreak >= 2 && ball.active && ball.lastHit === 'O')
drawHitHint();
else if (IS_TOUCH && rallyFeel() && G.phase === 'pretoss' && G.idleHintT > TUNE.pullHintAfter
&& G.flatStreak >= TUNE.flickHintAfter)
drawFlickHint();
// The free-floating register: no panel, one fixed slot, shouted. PERFECT, GAME N, DOUBLE KILL,
// RETURNED, CHANGEOVER, weapon labels — everything that used to be plain white 12px text sitting