diff --git a/config.js b/config.js index 7e55071..6fd96f6 100644 --- a/config.js +++ b/config.js @@ -259,7 +259,10 @@ export const TUNE = { // How many a fresh tin holds now lives on its WEAPONS row (`tin`), because the rack is no longer // four fixed types — bombMax/tinTriples/tinFlames/tinGuns lived here and nothing read them after // the table landed, which is a value you can tune in the panel all day for no effect. - wCap: 9, // per-type cap on the courtside rack — tins accumulate, but only so many + wCap: 99, // per-type cap on the courtside rack. Effectively NO cap: at 9 a late run spent + // every duel win on a full tin, and the answer to game 70 is a bigger stack, not + // a rationed one. 99 rather than Infinity only because the count is drawn as + // text in a rack slot and '×999' does not fit. rackH: 30, // px height of the bottom weapon rack (feel knob: how much court it covers). // 22 logical px is ~30 CSS px on a phone — under half Apple's 44px target, which // is why switching tins felt fiddly. rackHit also pads the touch band (render.js). diff --git a/gameplay.js b/gameplay.js index 2412d2f..52973f4 100644 --- a/gameplay.js +++ b/gameplay.js @@ -658,21 +658,16 @@ export function deliverTin() { // full, so most late-run duels paid out in literally nothing. Oliver: "later in the game a won // duel does not give you weapons anymore." const stock = id => { const s = slotOf(id); return s < 0 ? 0 : slotStock(s); }; // rack OR the temp slot + // The barrel weapons are RUNGS OF THE SAME LADDER, not a fallback. They used to be paid out only + // once all four tins hit wCap — and now that wCap is effectively no cap that branch would never + // fire again, so a late duel could only ever hand you a fifth bomb. Unlocked ones only, so this + // still cannot hand you a rocket in game three. + const ladder = [...TINS, ...Object.keys(BARREL_WEAPONS).filter(w => G.runGames >= BARREL_WEAPONS[w].from)]; let id = null; - for (let k = 0; k < TINS.length; k++) { - const cand = TINS[(G.deliveries + k) % TINS.length]; + for (let k = 0; k < ladder.length; k++) { + const cand = ladder[(G.deliveries + k) % ladder.length]; if (stock(cand) < TUNE.wCap) { id = cand; G.deliveries += k + 1; break; } } - // ...and when the whole four-rung ladder is capped, PAY OUT OF THE BARREL RACK instead. Past - // about game 20 all four tins sit at wCap and the duel — the one thing in the run you play for — - // handed you nothing at all: "the duel does not always give you new weapons". The barrel weapons - // are the natural next rung, they already exist, and you cannot otherwise win one without - // shooting a barrel. Unlocked ones only, so this can never hand you a rocket in game three. - if (!id) { - const late = Object.keys(BARREL_WEAPONS) - .filter(w => G.runGames >= BARREL_WEAPONS[w].from && stock(w) < TUNE.wCap); - if (late.length) id = late[G.deliveries++ % late.length]; - } // Every tin AND every barrel weapon full is a real state and not a bug, so say so plainly instead // of miming a handover that cannot happen. No boy, no umpire line — nothing was delivered. if (!id) { G.flash = { text: 'RACK FULL', t: 1.1 }; return; } diff --git a/input.js b/input.js index b462a3e..e6b9ec2 100644 --- a/input.js +++ b/input.js @@ -67,8 +67,21 @@ export function resetInput() { stick = flickAim = steerPX = null; } +// A LOST TOUCHEND LOCKS THE GAME, part two. visibilitychange/blur (below) only catches the case +// where the app goes away. The other one: a system edge gesture (Control Center on the right edge — +// exactly where the horde's absolute finger-steering parks your thumb) steals the finger and iOS +// delivers neither touchend nor touchcancel. `stick` then survives forever, so every later +// touchstart falls through to the extra-finger branch (swish, and only swish) while touchmove +// ignores the new finger's id — leaving steerPX frozen and the player welded to the screen edge. +// Any new touch event proves the old finger is gone if it isn't in e.touches. Drop it then. +const dropStale = e => { + if (stick && ![...e.touches].some(t => t.identifier === stick.id)) { + stick = null; touchAxis.x = touchAxis.z = 0; steerPX = null; G.aim = null; + } +}; + cv.addEventListener('touchstart', e => { - e.preventDefault(); audio(); + e.preventDefault(); audio(); dropStale(e); const r = cv.getBoundingClientRect(); for (const t of e.changedTouches) { if (dismissModal()) return; // the popup owns this gesture and nothing else sees it @@ -90,7 +103,7 @@ cv.addEventListener('touchstart', e => { }, { passive: false }); cv.addEventListener('touchmove', e => { - e.preventDefault(); + e.preventDefault(); dropStale(e); // unpins the player as soon as any finger moves; the next touchstart takes the stick if (!stick) return; for (const t of e.changedTouches) { if (t.identifier !== stick.id) continue;