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

+7 -2
View File
@@ -91,8 +91,13 @@ communicating its own rules. Model A only works if the player can see they are b
only way in. only way in.
6. ~~**Teaching**~~ **DONE** (2026-08-17). The ghost-hand lesson for the PLACEMENT FLICK, in the 6. ~~**Teaching**~~ **DONE** (2026-08-17). The ghost-hand lesson for the PLACEMENT FLICK, in the
same register as PULL DOWN and TAP AS IT ARRIVES: a dotted track out of the player, the hand same register as PULL DOWN and SWIPE TO DODGE — because it is now literally the same demo: the
riding it, SWIPE AS YOU HIT, mimed both ways in turn so it never teaches one corner. apron mime, played in the SERVE STANCE after a beat of standing still, mimed both ways in turn so
it never teaches one corner. **A gesture is read in a pause, not in a point** (Oliver: "it is
displayed mid-play.. please rather show it in one of the pauses"): the first cut rode the
incoming ball, over the shoulder, at the moment the player has the least attention to spare.
The words carry both halves — SWIPE AS YOU HIT TO AIM — because WHEN alone teaches nothing and
WHAT FOR alone gets tried at the wrong moment and read as broken.
It rides the failure it answers — `G.flatStreak`, returns that left with no aim on them It rides the failure it answers — `G.flatStreak`, returns that left with no aim on them
(`flatCount`, reset by the first aimed one) — and only after `TUNE.flickHintAfter` of them, (`flatCount`, reset by the first aimed one) — and only after `TUNE.flickHintAfter` of them,
because hitting flat is a legal shot and not a mistake. **One lesson at a time:** the tap hint because hitting flat is a legal shot and not a mistake. **One lesson at a time:** the tap hint
+1 -1
View File
@@ -659,6 +659,6 @@ if (location.hostname !== 'splatter-tennis.wuwi.one') {
</div> </div>
</div> </div>
</div> </div>
<script type="module" src="src/main.js?v=279"></script> <script type="module" src="src/main.js?v=280"></script>
</body> </body>
</html> </html>
+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 // 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 // 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 // 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 // to learn.
// the same failure it answers (a run of returns hit flat — doHit resets flatStreak the moment one // IT IS TAUGHT IN THE PAUSE, NOT IN THE POINT. It first rode the incoming ball, over the player's
// leaves with an aim on it). It mimes BOTH ways in turn or it teaches one corner. // 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() { function drawFlickHint() {
const cyc = 1.4, t = G.idleT % cyc; const n = ((G.idleHintT - TUNE.pullHintAfter) / 1.9) | 0;
const dir = (((G.idleT / cyc) | 0) % 2) ? -1 : 1; const px = proj(P.x, 0, P.z).x;
const slide = Math.min(1, t / 0.8), gone = t > 0.95; // stroke, held a beat, lifted, repeat // ...and the words carry BOTH halves, because either one alone is useless: WHEN (as you hit —
if (gone) return; // a swipe at any other moment does nothing, and a player who tries one and sees nothing concludes
const p0 = proj(P.x, 0, P.z); // the gesture is not there) and WHAT FOR (it aims). One line, the same imperative as PULL DOWN
const hx = Math.round(p0.x), hy = Math.round(p0.y) - 52; // and TAP AS IT ARRIVES.
ctx.save(); ctx.globalAlpha = 0.9; demoSwipe(px < W * 0.3 ? 1 : px > W * 0.7 ? -1 : (n % 2 ? -1 : 1), 0, 'SWIPE AS YOU HIT TO AIM');
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();
} }
// The modal: drawn LAST, over everything, and the world behind it is frozen (gameplay.update // 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 (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 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 — // the tennis struggle hints, each riding the exact failure it answers.
// teaching the flick to someone who cannot get a racket on the ball is the wrong lesson, so the // The TAP lesson stays on the incoming ball: it is about a moment, and a moment can only be
// tap always wins. // taught while it is happening. The FLICK lesson is about a gesture, and a gesture is read in a
// THE FLICK LESSON BELONGS TO THE RALLY MODES, which is what rallyFeel() already means: CLASSIC // PAUSE — so it waits for the serve stance and mimes itself on the apron there, exactly like the
// and ZOMBIE TENNIS. Not the gore mode, and not gore's OPENER either — that tennis is a prologue // slingshot's. Nothing new is drawn over a live rally.
// you play for one game before the court turns, and placement is not the skill it is asking for. // 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 // (`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.) // 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 // Touch only: on a keyboard the aim is held keys and 'SWIPE' would name a gesture that does not
// exist there. // exist there.
const inRally = G.phase === 'rally' && ball.active && ball.lastHit === 'O'; if (G.mode === 'tennis' && G.phase === 'rally' && G.passStreak >= 2 && ball.active && ball.lastHit === 'O')
if (G.mode === 'tennis' && inRally && G.passStreak >= 2) drawHitHint(); drawHitHint();
else if (IS_TOUCH && inRally && rallyFeel() && G.flatStreak >= TUNE.flickHintAfter) else if (IS_TOUCH && rallyFeel() && G.phase === 'pretoss' && G.idleHintT > TUNE.pullHintAfter
&& G.flatStreak >= TUNE.flickHintAfter)
drawFlickHint(); drawFlickHint();
// The free-floating register: no panel, one fixed slot, shouted. PERFECT, GAME N, DOUBLE KILL, // 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 // RETURNED, CHANGEOVER, weapon labels — everything that used to be plain white 12px text sitting