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:
wuwiandClaude Fable 5 committed 2026-08-10 21:28:37 +02:00
1 parent 15cad26105
commit 1c793f59a4
2 files changed
+20 -5

No files matched your search

+10 -3
View File
@@ -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,