diff --git a/gameplay.js b/gameplay.js
index ac0c129..2dbb4cc 100644
--- a/gameplay.js
+++ b/gameplay.js
@@ -33,7 +33,7 @@ export const pullQuality = mag => TUNE.pullQMin + pullFrac(mag) * (1 - TUNE.pull
export function flickTarget(ch, aim) { // exported: render draws the live reticle with it
const q = pullQuality(aim.mag), f = pullFrac(aim.mag);
if (aim.mag < TUNE.pullMin) return { q, f }; // barely drawn: no lane yet, so no target
- const ax = aim.fx, az = aim.fy; // unit vector; input already inverted the pull. screen up = -z
+ const ax = aim.fx, az = aim.fy; // unit vector, already in SHOT direction. screen up = -z
let zt = null, near = 1e9, bestErr = 1e9;
for (const zb of zombies) {
const dx = zb.x - ch.x, dz = zb.z - ch.z;
@@ -588,7 +588,7 @@ function transition(dt) {
// the mechanic changed under you — the one popup that gets an illustration, because it has
// to TEACH the gesture, not just announce it
showModal({ title: 'THE MATCH HAS CHANGED', color: UI.hot, icon: 'x_howto', iconH: 120,
- body: ['PULL BACK TO AIM — THEN LET GO', 'THE FURTHER YOU PULL, THE HARDER IT HITS',
+ body: ['DRAG AT THEM TO AIM — THEN LET GO', 'THE FURTHER YOU DRAG, THE HARDER IT HITS',
'A WEAK SHOT ONLY STAGGERS THEM'] });
});
}
diff --git a/input.js b/input.js
index 077d91b..0f88375 100644
--- a/input.js
+++ b/input.js
@@ -42,7 +42,7 @@ export function takeFlickAim(maxAgeMs = 450) {
}
// the two-stage serve: a drag that STARTS at a serveable pretoss aims a visible reticle
-// (G.aim, drawn by render); release fires the serve there, dragging back home cancels.
+// (G.aim, drawn by render); release fires the serve there, dragging home again cancels.
// EVERY phase of splatter play, so the drag never silently turns into something else. The old
// "drag walks the player" mode between shots was invisible state and read as a bug.
const canAimServe = () =>
@@ -90,10 +90,16 @@ cv.addEventListener('touchmove', e => {
if (t.identifier !== stick.id) continue;
const dx = t.clientX - stick.x0, dy = t.clientY - stick.y0, dist = Math.hypot(dx, dy);
if (dist > DEAD) stick.moved = true;
- if (stick.aimable) { // slingshot: PULL BACK, launch forward — the shot is the inverse of the drag
+ // Drag TOWARD the target, not back from it. It used to be a slingshot (pull back, launch
+ // forward) and that felt better — but every shot then ended with the thumb LOWER than it
+ // started, and the next touch begins where the last one ended. Over a horde the thumb walks
+ // itself into the iOS bottom edge and a pull becomes Reachability. Dragging at them walks it
+ // UP-screen instead, away from the edge. It also matches the flick-swing below, which always
+ // aimed this way.
+ if (stick.aimable) {
if (dist > DEAD) {
- if (!stick.tossed) { serveToss(); stick.tossed = true; } // the pull kicks off the toss animation
- G.aim = { fx: -dx / dist, fy: -dy / dist, mag: dist };
+ if (!stick.tossed) { serveToss(); stick.tossed = true; } // the drag kicks off the toss animation
+ G.aim = { fx: dx / dist, fy: dy / dist, mag: dist };
// the moment the pull is strong enough to actually splatter, it clicks — you can feel
// where the threshold is without watching the reticle
const hot = pullQuality(dist) >= TUNE.zSplatQ;
diff --git a/main.js b/main.js
index 3327dd2..48fd7bd 100644
--- a/main.js
+++ b/main.js
@@ -89,8 +89,8 @@ const TUT = [
"stops being the other fellow.",
"Nobody stops the match. Nobody ever does."] },
{ pose: 2, shot: 'tut_aim', say: ["Now you do not rally. You AIM.",
- "PULL BACK from your man and let go.",
- "The further you pull, the harder it lands.",
+ "DRAG AT THEM from your man and let go.",
+ "The further you drag, the harder it lands.",
"A soft one only makes them cross."] },
{ pose: 2, shot: 'tut_rack', say: ["I bring tins out to the rack down there.",
"BOMBS, TRIPLES, FIRE, and the GUN.",
diff --git a/render.js b/render.js
index 9d437a9..92cee48 100644
--- a/render.js
+++ b/render.js
@@ -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
diff --git a/zombies.js b/zombies.js
index e8f191f..e580aee 100644
--- a/zombies.js
+++ b/zombies.js
@@ -403,7 +403,7 @@ export function spawnRacketZombie() { // one takes up residence on the far court
if (!G.seen.racket) { // it is not a shambler and it does not behave like one — say so, once
G.seen.racket = true;
showModal({ title: 'IT HAS A RACKET', color: UI.hot,
- body: ['IT RETURNS YOUR WEAK SHOTS', 'ONLY A FULL PULL WOUNDS IT'] });
+ body: ['IT RETURNS YOUR WEAK SHOTS', 'ONLY A FULL-LENGTH DRAG WOUNDS IT'] });
}
}
function endHorde() {