Aim by dragging at them, so the thumb stops walking into the iOS edge

The slingshot pulled back, which meant every shot ended with the thumb lower
than it started — and the next touch begins where the last one ended. Over a
horde it walks itself into the bottom edge strip, where a pull down is
Reachability and iOS takes the gesture. A web page cannot refuse that.

So the drag now points AT the target: the thumb ends high and drifts up-screen,
away from the edge. It also matches the flick-swing, which always aimed this way.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Opus 5 committed 2026-08-02 14:05:04 +02:00
1 parent b23a3a8aaf
commit d4bbcfc080
5 files changed
+29 -22

No files matched your search

+14 -13
View File
@@ -647,26 +647,27 @@ function drawCard(title, sub1, sub2, color) {
}
// Sit in the serve stance doing nothing and the gesture demonstrates itself: a ghost thumb slides
// BACK from the player while chevrons grow FORWARD up the court, then it releases and repeats.
// Pulling is the whole game in splatter mode, and a line of text was carrying that alone.
// UP-court away from the player while chevrons run out ahead of it, then it releases and repeats.
// The drag is the whole game in splatter mode, and a line of text was carrying that alone.
function drawPullHint() {
const cyc = 1.9, t = (G.idleHintT - TUNE.pullHintAfter) % cyc;
const draw = Math.min(1, t / 1.15); // the pull itself, then a beat held at full
const draw = Math.min(1, t / 1.15); // the drag itself, then a beat held at full
const gone = t > 1.45; // released — the thumb lifts off
const anchor = proj(P.x, 0, P.z + 0.6);
const ax = Math.round(anchor.x), ay = Math.round(Math.min(anchor.y + 6, METER_Y() - 26));
const ax = Math.round(anchor.x), ay = Math.round(Math.min(anchor.y + 6, METER_Y() - 6));
const len = 30 * draw;
ctx.save();
ctx.globalAlpha = gone ? 0 : 0.85;
// the dotted track the thumb travels, straight down-screen = "pull back"
// the dotted track the thumb travels, straight up-screen = "drag at them"
ctx.fillStyle = UI.dim;
for (let d = 6; d < 34; d += 6) ctx.fillRect(ax - 1, ay + d, 2, 2);
// chevrons growing UP-court as the pull grows — the inverse relationship is the whole lesson
for (let d = 6; d < 34; d += 6) ctx.fillRect(ax - 1, ay - d, 2, 2);
// chevrons running out beyond the thumb as the drag grows — same direction as the shot, which
// is now the whole lesson: you point at what you want hit
ctx.fillStyle = UI.warm;
for (let i = 0; i < 3; i++) {
const u = (i + 1) / 4;
if (draw < u * 0.9) continue;
const cy = ay - 8 - i * 7, s = 4;
const cy = ay - 40 - i * 7, s = 4;
ctx.beginPath();
ctx.moveTo(ax, cy - s); ctx.lineTo(ax + s, cy + s * 0.7); ctx.lineTo(ax - s, cy + s * 0.7);
ctx.fill();
@@ -674,9 +675,9 @@ function drawPullHint() {
// the ghost thumb
ctx.globalAlpha = gone ? 0 : 0.9;
ctx.fillStyle = UI.cream;
ctx.beginPath(); ctx.arc(ax, ay + len, 5, 0, 7); ctx.fill();
ctx.beginPath(); ctx.arc(ax, ay - len, 5, 0, 7); ctx.fill();
ctx.fillStyle = 'rgba(0,0,0,0.45)';
ctx.beginPath(); ctx.arc(ax, ay + len, 2, 0, 7); ctx.fill();
ctx.beginPath(); ctx.arc(ax, ay - len, 2, 0, 7); ctx.fill();
ctx.restore();
}
@@ -1090,13 +1091,13 @@ export function render() {
const touch = IS_TOUCH, switchHint = touch ? 'TAP THE RACK TO SWITCH' : '1-5 TO SWITCH';
const sel = ['PLAIN', 'BOMB', 'TRIPLE', 'FIRE', 'GUN'][G.weapon];
// The DUEL is played under tennis rules — no slingshot, no rack, serve to the box (see the
// !G.interlude guards in solveShot and the aim reticle). It was still being told to pull back
// !G.interlude guards in solveShot and the aim reticle). It was still being told to drag
// to aim, which is the one thing that does nothing there.
const txt = G.mode === 'splatter' && !G.interlude
? (G.weapon === 4 ? (touch ? 'TAP TO UNLOAD THE GUN' : 'SPACE UNLOADS THE GUN')
: G.aim ? 'PULL BACK: FURTHER = HARDER'
: G.aim ? 'FURTHER = HARDER'
: G.weapon > 0 ? `SERVE FIRES: ${sel} · ${switchHint}`
: touch ? 'PULL BACK TO AIM · RELEASE TO FIRE' : 'DRAG BACK TO AIM · SPACE SERVES')
: touch ? 'DRAG AT THEM · RELEASE TO FIRE' : 'DRAG AT THEM · SPACE SERVES')
: G.serveNum === 2 ? (touch ? 'SECOND SERVE — TAP' : 'SECOND SERVE — SPACE')
: (touch ? 'TAP TO TOSS, TAP TO SERVE' : 'SPACE TO TOSS, SPACE TO SERVE');
ctx.fillText(txt, W / 2, METER_Y()); // same slot the draw meter uses