diff --git a/gameplay.js b/gameplay.js index b1752fd..ac0c129 100644 --- a/gameplay.js +++ b/gameplay.js @@ -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: ['POINT AT THEM, THEN PULL BACK', 'THE FURTHER YOU PULL, 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 158897e..077d91b 100644 --- a/input.js +++ b/input.js @@ -5,9 +5,9 @@ import { cv } from './dom.js'; import { audio, pock } from './audio.js'; import { TUNE } from './config.js'; -import { G, P, score } from './state.js'; +import { G, score } from './state.js'; import { keyPressed, dragServe, selectWeapon, serveToss, pullQuality, dismissModal } from './gameplay.js'; -import { rackHit, proj, W, H } from './render.js'; +import { rackHit } from './render.js'; import { togglePanel } from './panel.js'; const keys = {}; @@ -41,18 +41,8 @@ export function takeFlickAim(maxAgeMs = 450) { return null; } -// POINT, THEN WIND UP. Where the thumb LANDS picks the lane — a ray from your man through the -// touch — and the pull-back after that is power and nothing else, in whatever direction there is -// room for. The lane used to come from the pull's own direction, which meant aiming up-court was -// always a downward drag: every shot ended lower than it began, the next touch started where the -// last one ended, and over a horde the thumb walked itself into the iOS bottom edge, where a pull -// down is Reachability and the system takes the gesture. Now touch-down is dictated by where the -// shamblers are, which is up-court, so the thumb is pulled UP the screen by the game itself. -// The cost, and the thing to feel: the lane locks when the thumb lands. You can no longer sweep -// the aim while drawn. -// // 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, easing back home 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 = () => @@ -64,18 +54,6 @@ const canAimServe = () => let steerPX = null; // live absolute finger x (-1..1 across the canvas) — machine-gun steering reads it export const touchPX = () => steerPX; -// The lane: a unit vector in CANVAS space from your man's chest to the thumb. gameplay reads it as -// the shot direction straight off (screen up = -z), which is the same contract the flick-swing has -// always used — so nothing downstream of G.aim changes. -function laneFrom(t, r) { - const p = proj(P.x, 1.2, P.z); // chest height: where the shot actually leaves him - const dx = (t.clientX - r.left) / r.width * W - p.x; - const dy = (t.clientY - r.top) / r.height * H - p.y; - const d = Math.hypot(dx, dy); - if (d < 6) return { fx: 0, fy: -1 }; // thumb on top of him: no lane to read, so fire up-court - return { fx: dx / d, fy: dy / d }; -} - // Restarting a run no longer reloads the page (see state.resetState), so this module's own state has // to be put back by hand: a key still held, or a half-finished drag, would otherwise be inherited by // the next run's very first frame. @@ -97,9 +75,8 @@ cv.addEventListener('touchstart', e => { if (!stick) { stick = { id: t.identifier, x0: t.clientX, y0: t.clientY, t0: performance.now(), moved: false, aimable: canAimServe() }; // context locked at touch start — a movement drag never morphs into a serve - // the LANE is locked here too, from where the thumb landed. mag 0 aims at nothing — - // pointing costs nothing, only the pull spends anything. - if (stick.aimable) G.aim = { ...(stick.dir = laneFrom(t, r)), mag: 0 }; + // the reticle appears the moment the thumb lands, but mag 0 aims at nothing — you have to pull + if (stick.aimable) G.aim = { fx: 0, fy: 0, mag: 0 }; else steerPX = (t.clientX - r.left) / r.width * 2 - 1; } else keyPressed('Space'); @@ -113,15 +90,15 @@ 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) { // the lane is already locked (see laneFrom) — the drag is PURE POWER now, - if (dist > DEAD) { // so pull back, sideways, wherever there is screen. Only the length counts. + 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 pull kicks off the toss animation - G.aim = { ...stick.dir, mag: dist }; + 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; if (hot !== !!stick.hot) { stick.hot = hot; if (hot) pock(0.45, 1250); } - } else G.aim = { ...stick.dir, mag: 0 }; + } else G.aim = { fx: 0, fy: 0, mag: 0 }; continue; } const r = cv.getBoundingClientRect(); diff --git a/main.js b/main.js index 84bf737..3327dd2 100644 --- a/main.js +++ b/main.js @@ -89,7 +89,7 @@ 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.", - "POINT at them, then PULL BACK and let go.", + "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.", diff --git a/render.js b/render.js index b302407..9d437a9 100644 --- a/render.js +++ b/render.js @@ -646,23 +646,18 @@ function drawCard(title, sub1, sub2, color) { if (sub2) ctx.fillText(sub2, W / 2, H / 2 + 12); } -// Sit in the serve stance doing nothing and the gesture demonstrates itself: a ghost thumb lands -// OUT ON THE COURT — that is the aiming half, and it is why the anchor sits well up-court of him -// rather than at his feet — then slides BACK while chevrons grow FORWARD up the court, and -// releases. Both halves of the gesture in one loop; a line of text was carrying that alone. +// 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. 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 gone = t > 1.45; // released — the thumb lifts off - const anchor = proj(P.x, 0, P.z - 12); - const ax = Math.round(anchor.x), ay = Math.round(Math.min(anchor.y + 6, METER_Y() - 40)); + 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 len = 30 * draw; ctx.save(); ctx.globalAlpha = gone ? 0 : 0.85; - // the ring is where the thumb LANDED — the aiming half, and the half nobody would guess from a - // moving dot alone. It stays put while the thumb slides away from it. - ctx.strokeStyle = UI.dim; ctx.lineWidth = 1; - ctx.beginPath(); ctx.arc(ax, ay, 4, 0, 7); ctx.stroke(); // 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); @@ -1101,7 +1096,7 @@ export function render() { ? (G.weapon === 4 ? (touch ? 'TAP TO UNLOAD THE GUN' : 'SPACE UNLOADS THE GUN') : G.aim ? 'PULL BACK: FURTHER = HARDER' : G.weapon > 0 ? `SERVE FIRES: ${sel} · ${switchHint}` - : touch ? 'POINT AT THEM · PULL BACK TO FIRE' : 'POINT AND DRAG BACK · 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