A miss at the rack is a miss, not a shot
Tapping a tile and landing a thumb-width off it returned -1 — free court — and the tap became Space, which instantly armed whatever burst weapon was selected. Reaching for the flamer and missing by 10px fired the gun: 22px tiles, a second row the thumb hasn't learned, and a selection that moves under you (a won duel re-equips the tin it tops up). rackHit now reports a dead margin around the band (-2), and only the TAP dies there, decided at touchend — a drag or hold starting in the same spot still steers and still serves, so burst steering parked low keeps working. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
15cad26105
commit
1c793f59a4
2 files changed
+20
-5
No files matched your search
@@ -101,21 +101,28 @@ cv.addEventListener('touchstart', e => {
|
||||
const r = cv.getBoundingClientRect();
|
||||
for (const t of e.changedTouches) {
|
||||
if (dismissModal()) return; // the popup owns this gesture and nothing else sees it
|
||||
// A TAP that lands NEAR the rack but on no tile is a miss at a tile, not a shot — Space here
|
||||
// instantly armed whatever burst weapon was selected ("selecting the flamer activated the
|
||||
// gun"). Only the TAP dies, decided at touchend: a drag or hold starting in the same spot is
|
||||
// a deliberate gesture (burst steering parks the thumb low, serve pulls start anywhere) and
|
||||
// must keep working, so the stick is still created.
|
||||
let nearRack = false;
|
||||
if (!G.horde) { // rack tap selects a tin — a burst can be aborted out of, a horde cannot (selectWeapon)
|
||||
const slot = rackHit((t.clientX - r.left) / r.width, (t.clientY - r.top) / r.height);
|
||||
if (slot >= 0) { selectWeapon(slot); continue; }
|
||||
nearRack = slot === -2;
|
||||
}
|
||||
if (!stick) {
|
||||
// `r` rides along: the canvas is position: fixed, so its rect only moves on viewport changes
|
||||
// — re-measuring it per touchmove (60-120 Hz on iOS) was layout work for a constant answer
|
||||
stick = { id: t.identifier, x0: t.clientX, y0: t.clientY, t0: performance.now(), r,
|
||||
seen: performance.now(),
|
||||
seen: performance.now(), nearRack,
|
||||
moved: false, aimable: canAimServe() }; // context locked at touch start — a movement drag never morphs into a serve
|
||||
// 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 { spare = { id: t.identifier, x0: t.clientX, y0: t.clientY }; keyPressed('Space'); }
|
||||
else { spare = { id: t.identifier, x0: t.clientX, y0: t.clientY }; if (!nearRack) keyPressed('Space'); }
|
||||
}
|
||||
}, { passive: false });
|
||||
|
||||
@@ -173,7 +180,7 @@ const endTouch = e => {
|
||||
// (a bare hold used to fire at a free auto-target; that was the whole exploit)
|
||||
if (aim && dist >= TUNE.pullMin) dragServe(aim);
|
||||
}
|
||||
else if (!stick.moved && dt < 250) keyPressed('Space'); // tap
|
||||
else if (!stick.moved && dt < 250) { if (!stick.nearRack) keyPressed('Space'); } // tap — dead if it was a near-miss at the rack
|
||||
else if (!stick.adopted && dt < 300 && dist > 28) { // flick = aimed swing — a rescue drag is never a swing
|
||||
flickAim = { ax: Math.abs(dx) > dist * 0.38 ? Math.sign(dx) : 0,
|
||||
az: Math.abs(dy) > dist * 0.38 ? Math.sign(dy) : 0,
|
||||
|
||||
@@ -1183,13 +1183,21 @@ function rackGeom() {
|
||||
const rows = rackRows();
|
||||
return { x0: Math.round((W - rw) / 2), y0: rackTop(), tw, h, rows, n: rows * RACK_ROW, rw };
|
||||
}
|
||||
export function rackHit(nx, ny) { // normalized [0,1] canvas coords -> slot index, or -1
|
||||
// A NEAR-MISS AT THE RACK IS DEAD, NOT A SHOT. A tap that lands a thumb-width off a tile used to
|
||||
// return -1 — free court — and input turned it into Space, which INSTANTLY armed whatever burst
|
||||
// weapon was selected. So reaching for the flamer and missing by 10px fired the gun: the exact
|
||||
// "selecting X activated Y" report, made likely by 22px tiles, a second row the thumb hasn't
|
||||
// learned yet, and a selection that can move under you (a won duel re-equips the tin it tops up).
|
||||
const RACK_MISS = 18; // px of dead zone around the band: no selection, no swing, no burst
|
||||
export function rackHit(nx, ny) { // normalized [0,1] canvas coords -> slot index; -1 free court, -2 dead near-miss
|
||||
if (G.mode !== 'splatter' || G.interlude) return -1; // no rack during the tennis-rules duel
|
||||
const g = rackGeom(), cx = nx * W, cy = ny * H;
|
||||
// The touch band is deliberately BIGGER than the art: up by RACK_GRAB and all the way down to
|
||||
// the screen edge, reclaiming the dead margin below. At 22px tall the tiles were ~30 CSS px on a
|
||||
// phone — well under the 44px everyone's thumb expects — which is why switching tins felt fiddly.
|
||||
if (cy < g.y0 - RACK_GRAB || cx < g.x0 || cx >= g.x0 + g.rw) return -1;
|
||||
if (cy < g.y0 - RACK_GRAB || cx < g.x0 || cx >= g.x0 + g.rw)
|
||||
return cy >= g.y0 - RACK_GRAB - RACK_MISS && cx >= g.x0 - RACK_MISS && cx < g.x0 + g.rw + RACK_MISS
|
||||
? -2 : -1;
|
||||
// The FIRST five never move: they stay welded to the bottom edge and the second five stack above,
|
||||
// so growing the rack doesn't slide the ball out from under a thumb that already knows where it is.
|
||||
// Clamped rather than rejected — the RACK_GRAB overhang sits above the top row.
|
||||
|
||||
Reference in new issue
Block a user