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
@@ -20,7 +20,8 @@ const hexLerp = (a, b, t) => {
|
||||
return `rgb(${Math.round(ar + (br - ar) * t)},${Math.round(ag + (bg - ag) * t)},${Math.round(ab + (bb - ab) * t)})`;
|
||||
};
|
||||
function palette() {
|
||||
const t = G.mode === 'tennis' ? 0 : G.mode === 'splatter' ? 1 : Math.min(1, G.transT / 6);
|
||||
// splatter lerps back toward the tennis palette while a duel runs — order, restored
|
||||
const t = G.mode === 'tennis' ? 0 : G.mode === 'splatter' ? 1 - G.duelLerp : Math.min(1, G.transT / 6);
|
||||
if (t === 0) return PAL.tennis;
|
||||
if (t === 1) return PAL.splatter;
|
||||
const m = {};
|
||||
@@ -302,7 +303,7 @@ export function render() {
|
||||
drawRing(ft.zt.x, ft.zt.z, '#ff5050');
|
||||
const hp = proj(ft.zt.x, 2.2, ft.zt.z);
|
||||
ctx.fillStyle = '#ff5050'; ctx.fillRect(hp.x - 1, hp.y - 4, 3, 3);
|
||||
} else drawRing(ft.tx, ft.tz, '#f8e048');
|
||||
} else if (ft.tx !== undefined) drawRing(ft.tx, ft.tz, '#f8e048');
|
||||
}
|
||||
|
||||
// far-side drawables in z order (far → near), then the net, then the near side
|
||||
@@ -401,7 +402,8 @@ export function render() {
|
||||
const touch = matchMedia('(pointer: coarse)').matches;
|
||||
const stock = G.bombs ? 'BOMB' : G.triples ? 'TRIPLE' : G.flames ? 'FIRE' : null;
|
||||
const txt = G.mode === 'splatter'
|
||||
? (stock ? `AIMED SERVES FIRE THE ${stock} · TAP KEEPS IT`
|
||||
? (G.aim ? (stock ? `RELEASE FIRES THE ${stock} · DRAG BACK TO CANCEL` : 'RELEASE TO SERVE · DRAG BACK TO CANCEL')
|
||||
: stock ? `AIMED SERVES FIRE THE ${stock} · TAP KEEPS IT`
|
||||
: touch ? 'TAP TO SERVE · HOLD + DRAG TO AIM' : 'SPACE SERVES · ARROWS AIM')
|
||||
: G.serveNum === 2 ? (touch ? 'SECOND SERVE — TAP' : 'SECOND SERVE — SPACE')
|
||||
: (touch ? 'TAP TO TOSS, TAP TO SERVE' : 'SPACE TO TOSS, SPACE TO SERVE');
|
||||
@@ -411,6 +413,14 @@ export function render() {
|
||||
ctx.font = 'bold 14px monospace'; ctx.fillStyle = '#ffffff';
|
||||
ctx.fillText(G.flash.text, W / 2, 60 + Math.sin(G.flash.t * 20) * 1);
|
||||
}
|
||||
if (G.interlude && G.interlude.introT > 0) { // the duel card: a real pause, front and center
|
||||
ctx.fillStyle = 'rgba(10,6,8,0.66)'; ctx.fillRect(0, H / 2 - 36, W, 64);
|
||||
ctx.fillStyle = '#ff5050'; ctx.font = 'bold 18px monospace';
|
||||
ctx.fillText('SUDDEN DEATH', W / 2, H / 2 - 26);
|
||||
ctx.fillStyle = '#e8e8dc'; ctx.font = 'bold 8px monospace';
|
||||
ctx.fillText('ONE POINT · THE LINES ARE LIVE', W / 2, H / 2 - 2);
|
||||
ctx.fillText('WIN IT FOR THE NEXT TIN', W / 2, H / 2 + 12);
|
||||
}
|
||||
if (TUNE.missHud && G.lastMiss && G.idleT - G.lastMiss.at < 3.5) { // last miss reason, live
|
||||
ctx.font = 'bold 8px monospace'; ctx.fillStyle = '#ff9060';
|
||||
ctx.fillText(G.lastMiss.text.toUpperCase(), W / 2, H - 28);
|
||||
|
||||
Reference in new issue
Block a user