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
+8
-6
@@ -80,8 +80,9 @@ export function sweepCourt() {
|
||||
export function meleeSwipe(x, z, range) {
|
||||
let hit = 0;
|
||||
for (let i = zombies.length - 1; i >= 0; i--) {
|
||||
if (zombies[i].variant === 'r') continue;
|
||||
if (Math.hypot(zombies[i].x - x, zombies[i].z - z) < range) { killZombie(i, !!G.horde); hit++; }
|
||||
const zb = zombies[i];
|
||||
if (!zb || zb.variant === 'r') continue; // !zb: killZombie can sweep the court mid-loop
|
||||
if (Math.hypot(zb.x - x, zb.z - z) < range) { killZombie(i, !!G.horde); hit++; }
|
||||
}
|
||||
if (hit) swish(0.35);
|
||||
return hit;
|
||||
@@ -128,8 +129,8 @@ function tickHorde(dt) {
|
||||
// bomb ball: everything inside bombRadius splatters at once (each kill still ++ball.kills so
|
||||
// the Phase-2 fanfare escalates), plus a crater — big stain, size-3 pile, extra particles, boom.
|
||||
export function explode(x, z) {
|
||||
for (let i = zombies.length - 1; i >= 0; i--) // backwards: splatter() splices the array
|
||||
if (Math.hypot(zombies[i].x - x, zombies[i].z - z) < TUNE.bombRadius) splatter(i);
|
||||
for (let i = zombies.length - 1; i >= 0; i--) // backwards: splatter() splices (and can sweep) the array
|
||||
if (zombies[i] && Math.hypot(zombies[i].x - x, zombies[i].z - z) < TUNE.bombRadius) splatter(i);
|
||||
gore.push({ x, z, r: 1.6, a: 1, scorch: true }); if (gore.length > 40) gore.shift(); // the crater — near-permanent
|
||||
gore.push({ x, z, r: 1.4, a: 0.9 }); if (gore.length > 40) gore.shift();
|
||||
const pl = piles.find(p => Math.hypot(p.x - x, p.z - z) < 0.6);
|
||||
@@ -170,6 +171,7 @@ export function updateZombies(dt) {
|
||||
|
||||
for (let i = zombies.length - 1; i >= 0; i--) {
|
||||
const zb = zombies[i];
|
||||
if (!zb) continue; // a kill can cascade into sweepCourt (game won mid-loop) and gut the array under us
|
||||
zb.walkT += dt;
|
||||
zb.z += zb.v * dt;
|
||||
if (zb.shieldT > 0) zb.shieldT -= dt; // staggered chair comes back up when this runs out
|
||||
@@ -250,7 +252,7 @@ export function updateZombies(dt) {
|
||||
let hit = false;
|
||||
for (let j = zombies.length - 1; j >= 0; j--) {
|
||||
const zb = zombies[j];
|
||||
if (zb.variant === 'r') continue;
|
||||
if (!zb || zb.variant === 'r') continue;
|
||||
if (Math.hypot(zb.x - pe.x, zb.z - pe.z) < (zb.variant === 'g' ? 0.75 : 0.55)) {
|
||||
if (zb.variant === 'g' && --zb.hp > 0) pock(0.5, 260); // the brute soaks pellets too
|
||||
else killZombie(j, true);
|
||||
@@ -268,7 +270,7 @@ export function updateZombies(dt) {
|
||||
let dead = e.t <= 0 || Math.abs(e.x) > 7.5 || e.z < -2 || e.z > 25.5;
|
||||
if (!dead && e.y < 1.9) for (let j = zombies.length - 1; j >= 0; j--) {
|
||||
const zb = zombies[j];
|
||||
if (zb.variant === 'r') continue; // side balls pass the boss — only the match ball duels it
|
||||
if (!zb || zb.variant === 'r') continue; // side balls pass the boss; !zb: kills can sweep mid-loop
|
||||
if (Math.hypot(zb.x - e.x, zb.z - e.z) < (zb.variant === 'g' ? 0.75 : 0.55)) {
|
||||
if (zb.variant === 's' && e.vz < 0 && !(zb.shieldT > 0)) { zb.shieldT = TUNE.shieldDown; pock(0.9, 1800); } // chair blocks these too
|
||||
else if (e.pow >= TUNE.zSplatQ) {
|
||||
|
||||
Reference in new issue
Block a user