Let the horde gun keep up with the horde

The run ended on the second wave every time: the gun fired 6 flat rounds a
second, hit one body each, and every shambler soaked one — while hordeGrow
made each wave longer and denser. Doubled the rate and scaled it with the
wave (14/s at one set banked, 21/s at four), gave a round 0.5m of extra
reach, a wider cone, and enough damage to drop a shambler outright. Raised
the spawn rate to match, so surviving a wave still leaves a standing crowd
on your half — that pressure is the point of it.

Also: he no longer carries a second racket at his hip while holding one
forward (the run frames are the racket-less art), the umpire stopped talking
over himself and saying "remain seated" twice, and a timed freeze now shows
what it is counting down to instead of reading as dead controls.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Opus 5 committed 2026-07-31 08:07:35 +02:00
1 parent 571cd4c399
commit e904dfefa6
5 files changed
+47 -13

No files matched your search

+6 -5
View File
@@ -304,10 +304,11 @@ function splatter(i, fanfare = true) { // the MAIN ball hit a zombie
function spawnHordeZombie() { // all from the far baseline, across the court — never off-screen at the sides
spawnZombie((Math.random() * 2 - 1) * 5, -1.5 - Math.random(), Math.random() < 0.5 ? 'a' : 'b');
}
let mgTick = 0;
function firePellet() {
const a = (Math.random() * 2 - 1) * 4 * Math.PI / 180, s = 26; // ±4° spread, straight up-court (-z)
const a = (Math.random() * 2 - 1) * TUNE.mgSpread * Math.PI / 180, s = 26; // spread cone, straight up-court (-z)
pellets.push({ x: P.x, y: 0.9, z: P.z, vx: Math.sin(a) * s, vz: -Math.cos(a) * s, t: 1.2 });
pock(0.25, 1400); // rapid fire tick; if it clips on device, gate to every 2nd pellet
if (++mgTick % 2 === 0) pock(0.25, 1400); // rapid fire tick, every 2nd round — at 12+/s every round buzzes
}
export function spawnRacketZombie() { // one takes up residence on the far court — an opponent, of sorts
// He stiffens as the run goes on. hpMax is stored because the wound skins and bleed() anchors read
@@ -338,7 +339,7 @@ function tickHorde(dt) {
if (zombies.length < TUNE.zHordeCap) spawnHordeZombie(); // ...but bounded, see TUNE.zHordeCap
h.spawnT = 1 / (h.rate || TUNE.hordeRate);
}
if ((h.mgT -= dt) <= 0) { firePellet(); h.mgT = 1 / TUNE.mgRate; }
if ((h.mgT -= dt) <= 0) { firePellet(); h.mgT = 1 / (h.mgRate || TUNE.mgRate); }
if (h.t <= 0) endHorde();
}
@@ -578,12 +579,12 @@ export function updateZombies(dt) {
for (let j = zombies.length - 1; j >= 0; j--) {
const zb = zombies[j];
if (!zb) continue;
if (Math.hypot(zb.x - pe.x, zb.z - pe.z) < hitR(zb)) {
if (Math.hypot(zb.x - pe.x, zb.z - pe.z) < hitR(zb) + TUNE.mgPelletR) {
// Pellets used to skip the boss entirely, so a GUN burst (and every horde) did literally
// nothing to him with no feedback at all. They CHIP him now: dmgPellet is fractional, so
// three rounds take a point off and the gun is a slow, committed answer rather than a no-op.
if (isBoss(zb)) { if (wound(zb, TUNE.dmgPellet, false)) killZombie(j, true); else pock(0.45, 240); }
else if (zb.hp > 1) { zb.hp--; pock(0.5, 260); } // brutes AND shamblers soak pellets — wound, don't kill
else if ((zb.hp -= TUNE.dmgPelletHp) > 0) pock(0.5, 260); // only brutes soak now; a shambler drops to one round
else killZombie(j, true);
hit = true; break;
}