Fix the freeze; duel intro card + tennis lights; hold-to-aim; instant gun steering
THE FREEZE (root cause found): the game-winning kill fires INSIDE the zombie update loop — killZombie -> splatPoint -> gameOver -> sweepCourt guts the zombies array mid-iteration, the loop reads zombies[i] === undefined, throws, and the un-rescheduled RAF chain dies: a permanent hard freeze. Crowded courts (later games, brutes) make the mid-loop game-winner likelier — hence 'freezes around game 3 when the fat zombies arrive'. Fixed at the root (null guards in every loop whose body can cascade into a sweep: zombie walk loop, meleeSwipe, explode, pellet + extras inner loops) AND made frame() reschedule RAF FIRST so no single bad frame can ever kill the loop again. Aim awareness: the reticle now appears the moment the thumb lands (mag 0 = auto-target the most advanced zombie, red lock immediately). A pure HOLD (>250ms, no drag) releases into the auto-target serve; dragging aims as before; the serve prompt switches to 'RELEASE TO SERVE / RELEASE FIRES THE BOMB - DRAG BACK TO CANCEL' while aiming. Duel presentation: the interlude now opens with a 2.4s intro beat — serving blocked, centered card (SUDDEN DEATH / ONE POINT - THE LINES ARE LIVE / WIN IT FOR THE NEXT TIN), and the palette lerps back to the TENNIS colors for the duration of the duel (duelLerp, 0.8s each way) — order visibly restored, then the rot returns. Machine-gun steering: absolute now — the finger x IS the player x, zero lag (was sign-quantized velocity). Clamped to what the camera can see ((W/2-14)/proj-scale — portrait's world bounds are wider than its viewport), keyboard steering clamped the same during auto-fire. Verified: the exact pre-fix crash scenario (game-winning kill mid-loop, 9 zombies + brute) survives and sweeps clean; intro blocks taps then the duel plays; hold-release fired at the auto-lock and won a tin end-to-end; screenshot shows tennis-green court + centered card + GAME 2; MG jumps P.x to the finger in one frame, clamped; tennis 12/12; no console errors. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8cfe9fdbcd
commit
e75b3888de
6 files changed
+69
-27
No files matched your search
@@ -41,13 +41,21 @@ export function takeFlickAim(maxAgeMs = 450) {
|
||||
// (G.aim, drawn by render); release fires the serve there, dragging back home cancels.
|
||||
const canAimServe = () =>
|
||||
G.mode === 'splatter' && G.phase === 'pretoss' && score.server === 'P'
|
||||
&& !G.horde && !(G.mgT > 0) && !G.changeover;
|
||||
&& !G.horde && !(G.mgT > 0) && !G.changeover && !(G.interlude && G.interlude.introT > 0);
|
||||
|
||||
let steerPX = null; // live absolute finger x (-1..1 across the canvas) — machine-gun steering reads it
|
||||
export const touchPX = () => steerPX;
|
||||
|
||||
cv.addEventListener('touchstart', e => {
|
||||
e.preventDefault(); audio();
|
||||
for (const t of e.changedTouches) {
|
||||
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
|
||||
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 reticle appears the moment the thumb lands: mag 0 = auto-target the most advanced
|
||||
if (stick.aimable) G.aim = { fx: 0, fy: 0, mag: 0 };
|
||||
else { const r = cv.getBoundingClientRect(); steerPX = (t.clientX - r.left) / r.width * 2 - 1; }
|
||||
}
|
||||
else keyPressed('Space');
|
||||
}
|
||||
}, { passive: false });
|
||||
@@ -59,10 +67,12 @@ 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) { // aiming, not steering: the reticle tracks the thumb
|
||||
G.aim = dist > DEAD ? { fx: dx / dist, fy: dy / dist, mag: dist } : null;
|
||||
if (stick.aimable) { // aiming, not steering: the reticle tracks the thumb (home = auto-target)
|
||||
G.aim = dist > DEAD ? { fx: dx / dist, fy: dy / dist, mag: dist } : { fx: 0, fy: 0, mag: 0 };
|
||||
continue;
|
||||
}
|
||||
const r = cv.getBoundingClientRect();
|
||||
steerPX = (t.clientX - r.left) / r.width * 2 - 1;
|
||||
touchAxis.x = Math.abs(dx) > DEAD ? Math.sign(dx) : 0;
|
||||
touchAxis.z = Math.abs(dy) > DEAD ? Math.sign(dy) : 0;
|
||||
}
|
||||
@@ -74,10 +84,11 @@ const endTouch = e => {
|
||||
if (!stick || t.identifier !== stick.id) continue;
|
||||
const dt = performance.now() - stick.t0;
|
||||
const dx = t.clientX - stick.x0, dy = t.clientY - stick.y0, dist = Math.hypot(dx, dy);
|
||||
if (stick.aimable && stick.moved) { // two-stage release
|
||||
const aim = G.aim; G.aim = null;
|
||||
if (aim && dist > DEAD * 1.5) dragServe(aim); // on target — serve
|
||||
// else: dragged back home — cancel, the tin stays in stock
|
||||
const aim = G.aim; G.aim = null; steerPX = null;
|
||||
if (stick.aimable && (stick.moved || dt >= 250)) { // two-stage release
|
||||
// a pure HOLD (never moved) fires at the auto-target; a drag fires at the reticle;
|
||||
// a drag walked back home cancels — the tin stays in stock
|
||||
if (aim && (!stick.moved || dist > DEAD * 1.5)) dragServe(aim);
|
||||
}
|
||||
else if (!stick.moved && dt < 250) keyPressed('Space'); // tap
|
||||
else if (dt < 300 && dist > 28) { // flick = aimed swing
|
||||
|
||||
Reference in new issue
Block a user