Nothing arrives unannounced: the horde gets its breather, the boss gets a name
THEY KEEP COMING is a 2.4s 'over' beat again (warnHorde in beats.js) instead of a flash raised on the same frame the wave lands, and the chapter boss walks on behind an overlay — his name in red over ENTERED THE COURT — held for 3s by the new self-dismissing modal clock (m.hold) rather than announced on a routine-set card. startHorde returns a bool: if the court has been taken when the warning expires the pendingHorde latch is re-armed rather than the set losing the horde it paid for. The warning ticks on its own clock, not on the 'over' beat, so a beat that moves the phase underneath it can't strand it (the mgT lock, again). Three tests, 15 total. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1f636530b9
commit
5ea84f8511
8 files changed
+132
-16
No files matched your search
+20
-4
@@ -6,7 +6,7 @@ import { audio, pock, swish, whoosh, applause, voice, sfx, aww, zombieMusic, zom
|
||||
import { axisX, axisZ, takeFlickAim, touchPX, resetInput } from './input.js';
|
||||
import { W } from './render.js'; // runtime-only cycle (render imports gameplay), same pattern as input/zombies
|
||||
import { updateZombies, updateParts, spawnZombie, spawnRacketZombie, spawnMachine, spawnChapterBoss, zombies, gore, explode, meleeSwipe, extras, parts, pellets, shots, playerGore, eatenBleed, resetZombies , firePellet , spawnBarrel , fireBeam , pops, popText } from './zombies.js';
|
||||
import { proceedAfterClear, startDuel, startHorde, resolveInterlude, watchdog } from './beats.js'; // runtime-only cycle: beats calls back into cheer/aside/endBurst/deliverTin/showModal/logSwing
|
||||
import { proceedAfterClear, startDuel, startHorde, warnHorde, resolveInterlude, watchdog } from './beats.js'; // runtime-only cycle: beats calls back into cheer/aside/endBurst/deliverTin/showModal/logSwing
|
||||
|
||||
// ------------------------------------------------------------- ballistics ----
|
||||
// Launch ball from (x0,y0,z0) so it arrives at (tx, tz) with horizontal speed s under gravity g.
|
||||
@@ -1187,7 +1187,14 @@ export function update(dt) {
|
||||
if (G.modal) {
|
||||
G.idleT += dt;
|
||||
const m = G.modal; // the entrance is the ONE thing that animates behind the freeze
|
||||
if (m.in < 1 && (m.in = Math.min(1, m.in + dt / TUNE.modalIn)) >= 1) pock(0.4, 620); // it lands with a thud
|
||||
if (m.in < 1) {
|
||||
if ((m.in = Math.min(1, m.in + dt / TUNE.modalIn)) >= 1) pock(0.4, 620); // it lands with a thud
|
||||
}
|
||||
// ...and a modal may carry its OWN clock (`hold`): it announces, holds the frozen court for a
|
||||
// couple of seconds and hands it back without being tapped. The boss walk-on is the case — an
|
||||
// announcement the player must be PREPARED by, where waiting for a tap would be the control
|
||||
// steal the popup policy forbids. Only ticks once it has landed; a tap still dismisses early.
|
||||
else if (m.hold > 0 && (m.hold -= dt) <= 0) dismissModal();
|
||||
return;
|
||||
}
|
||||
// The flash is FROZEN behind a popup too, and it has to be: it used to tick above the return, so
|
||||
@@ -1466,6 +1473,15 @@ export function update(dt) {
|
||||
const boy = G.boy;
|
||||
if (boy) boy.mop ? mopRound(boy, dt) : fetchRound(boy, dt);
|
||||
|
||||
// The announced breather runs on its OWN clock, not on the 'over' beat it also sets: any beat that
|
||||
// moves the phase underneath it (a duel taking the calm court, say) would otherwise strand the
|
||||
// latch forever — the mgT lock, exactly. If the court is taken when it expires, the latch is
|
||||
// re-armed and the set gets its horde later rather than losing it. Above the 'over' block so the
|
||||
// wave lands before that block can put the player back in the serve stance for one frame.
|
||||
if (G.hordeWarn > 0 && (G.hordeWarn -= dt) <= 0) {
|
||||
G.hordeWarn = 0;
|
||||
if (!startHorde()) G.pendingHorde = true;
|
||||
}
|
||||
if (G.phase === 'over') {
|
||||
G.overT -= dt;
|
||||
if (G.overT <= 0) {
|
||||
@@ -1479,8 +1495,8 @@ export function update(dt) {
|
||||
G.seen.horde = true;
|
||||
showModal({ title: 'THEY KEEP COMING', color: UI.hot,
|
||||
body: ['THE RACKET FIRES ON ITS OWN', 'SLIDE TO AIM · HOLD THE BASELINE'],
|
||||
onDismiss: startHorde });
|
||||
} else startHorde(); // ...and every later set starts on the flash alone. Re-showing the
|
||||
onDismiss: warnHorde });
|
||||
} else warnHorde(); // ...and every later set starts on the flash alone. Re-showing the
|
||||
// same popup at the hottest moment of every set was one of the
|
||||
// "popups get me killed" reports — you know the rules by set two.
|
||||
return;
|
||||
|
||||
Reference in new issue
Block a user