diff --git a/gameplay.js b/gameplay.js index 2dbb4cc..ac0c129 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, already in SHOT direction. screen up = -z + const ax = aim.fx, az = aim.fy; // unit vector; input already inverted the pull. 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: ['DRAG AT THEM TO AIM — THEN LET GO', 'THE FURTHER YOU DRAG, THE HARDER IT HITS', + body: ['PULL BACK TO AIM — THEN LET GO', 'THE FURTHER YOU PULL, THE HARDER IT HITS', 'A WEAK SHOT ONLY STAGGERS THEM'] }); }); } diff --git a/input.js b/input.js index 0f88375..077d91b 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 home again cancels. +// (G.aim, drawn by render); release fires the serve there, dragging back home 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,16 +90,10 @@ 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; - // 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 (stick.aimable) { // slingshot: PULL BACK, launch forward — the shot is the inverse of the drag if (dist > DEAD) { - if (!stick.tossed) { serveToss(); stick.tossed = true; } // the drag kicks off the toss animation - G.aim = { fx: dx / dist, fy: dy / dist, mag: dist }; + if (!stick.tossed) { serveToss(); stick.tossed = true; } // the pull 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 48fd7bd..3327dd2 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.", - "DRAG AT THEM from your man and let go.", - "The further you drag, the harder it lands.", + "PULL BACK from your man and let go.", + "The further you pull, 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 92cee48..9d437a9 100644 --- a/render.js +++ b/render.js @@ -647,27 +647,26 @@ function drawCard(title, sub1, sub2, color) { } // Sit in the serve stance doing nothing and the gesture demonstrates itself: a ghost thumb slides -// 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. +// 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. function drawPullHint() { const cyc = 1.9, t = (G.idleHintT - TUNE.pullHintAfter) % cyc; - const draw = Math.min(1, t / 1.15); // the drag itself, then a beat held at full + const draw = Math.min(1, t / 1.15); // the pull 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() - 6)); + const ax = Math.round(anchor.x), ay = Math.round(Math.min(anchor.y + 6, METER_Y() - 26)); const len = 30 * draw; ctx.save(); ctx.globalAlpha = gone ? 0 : 0.85; - // the dotted track the thumb travels, straight up-screen = "drag at them" + // the dotted track the thumb travels, straight down-screen = "pull back" ctx.fillStyle = UI.dim; - 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 + 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 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 - 40 - i * 7, s = 4; + const cy = ay - 8 - 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(); @@ -675,9 +674,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(); } @@ -1091,13 +1090,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 drag + // !G.interlude guards in solveShot and the aim reticle). It was still being told to pull back // 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 ? 'FURTHER = HARDER' + : G.aim ? 'PULL BACK: FURTHER = HARDER' : G.weapon > 0 ? `SERVE FIRES: ${sel} · ${switchHint}` - : touch ? 'DRAG AT THEM · RELEASE TO FIRE' : 'DRAG AT THEM · SPACE SERVES') + : touch ? 'PULL BACK TO AIM · RELEASE TO FIRE' : 'DRAG BACK TO AIM · 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 e580aee..e8f191f 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-LENGTH DRAG WOUNDS IT'] }); + body: ['IT RETURNS YOUR WEAK SHOTS', 'ONLY A FULL PULL WOUNDS IT'] }); } } function endHorde() {