Stop the brute pulsing, and let a boss die where you can see it

The pulse was drawStatic being told a height instead of reading one. _slice.py
common-scales a sheet, so a crouched frame is genuinely shorter — 211 px against
its neighbours' 224 — and forcing every frame to the same on-screen height
stretched it back up, so the body swelled and shrank once per stride. Worst on
the brute because it is the biggest and slowest. The rule already existed for the
wound stages; it now applies to every frame, once, which also settles the
shamblers and the steward (their cycles vary by up to 9%).

A chapter boss now dies in its own beat instead of under the card announcing it:
the world drops to a third speed for 1.2 s, the screen kicks, a shockwave ring
blows out through 90 extra particles and six stains, and the CHAPTER DOWN card
holds off until the pieces have landed — about 1.4 s, measured.

Cards can hold off in general now (`wait`). First cut only delayed the card's
countdown while still drawing it, which is worse than no delay: caught it in a
screenshot before it shipped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Opus 5 committed 2026-07-31 20:22:55 +02:00
1 parent 7d307d90b9
commit b7899f122c
7 files changed
+61 -8

No files matched your search

+21
View File
@@ -181,10 +181,31 @@ function wreckMachine(zb) {
for (let k = 0; k < 26; k++) smokePuff(zb.x, 0.5 + Math.random() * 1.6, zb.z, 0.7); // a column, not a puff
}
// A chapter boss does not just die. It is the thing the whole fight was about, so it comes apart
// in its own beat: the world drops into slow motion, the screen kicks, a ring of blood blows out
// through the crowd's roar, and the card that announces it waits until the pieces have landed.
function bossDeath(zb) {
G.slowT = TUNE.bossSlowT; // real seconds; main.js scales dt while it runs
G.bossBurst = { x: zb.x, z: zb.z, t: 0.75 }; // the shockwave ring render.js draws
G.shake = { t: 0.5, mag: 7 }; G.boomFlash = 0.12;
boom(); splat(1); cheer(4, 1); voice('unbelievable', 0.5);
const h = 1.8 * (zb.scale || 1.5);
for (let k = 0; k < 90; k++) parts.push({ // on top of killZombie's own burst: a full body's worth
x: zb.x, y: h * (0.2 + Math.random() * 0.7), z: zb.z,
vx: (Math.random() * 2 - 1) * 7, vy: 1 + Math.random() * 7, vz: (Math.random() * 2 - 1) * 7,
t: 0.7 + Math.random() * 0.9, dark: Math.random() < 0.4 });
for (let k = 0; k < 6; k++) { // and it leaves a mess nothing else on the court leaves
const a = Math.random() * 6.283, r = Math.random() * 2.4;
gore.push({ x: zb.x + Math.cos(a) * r, z: zb.z + Math.sin(a) * r * 0.6, r: 0.6 + Math.random() * 0.7, a: 0.9 });
if (gore.length > 40) gore.shift();
}
}
function killZombie(i, quiet) {
const zb = zombies[i];
const mul = GORE_MUL[zb.variant] || 1;
zombies.splice(i, 1);
if (zb.variant === 'B') bossDeath(zb); // before endBossFight below, so the card can wait on it
if (zb.variant === 'm') { // furniture, not a body: no blood, no mound, no mop boy
wreckMachine(zb);
if (!G.horde && !G.clearing) splatPoint('P', quiet);