Feedback round: fire actually spreads, brutes lurch, crowd stops flickering
- Fire never spread in play. The flame ball's full pierce wiped its own lane, so
the bodies that would have caught were already gone. It now ignites everything
it FLIES PAST (TUNE.burnBallR), with a wider ignition and spread radius. One
flame serve into a scattered 10-wave kills 9, nine burning at once.
- Wounded brutes slid along like decals: every wound stage is a single frame and
the brute now spends most of its life wounded. They get a procedural lumber
instead of eight more sprites — translate + squash, no rotation, so the pixels
stay on the grid, and the amplitudes scale with the projection or the whole
thing goes sub-pixel at range.
- The "scoreboard flicker" was the crowd: the cheer bounce was per-column
Math.random() every frame, i.e. TV static, showing through the scoreboard art's
transparent ornate edges. Now a smooth per-column sine on idleT.
- Ball boys: mopping is the default in splatter mode now, and the boy is half
again as tall — at 1.2m he read as a distant speck. The running fetch-boy
survives for tennis, where there is nothing on the grass but a ball.
- Changeover 7s -> 5s, with the mop crossing timed to fill it.
- Save-to-homescreen: a bloody tennis ball, wired up as apple-touch-icon +
favicon + webmanifest. The Dockerfile now ships the manifest and gives it its
own MIME type via an exact-match location (a bare `types {}` block would have
replaced nginx's whole map and broken .js and .png with it).
Benchmarks re-run: CPU returns 67/72 serves (93.1%, above the 11/12 bar — a
20/24 sample in between was flub noise), 30 human-tapped points = 50 hits /
0 whiffs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a09a5d7559
commit
ff77922e85
14 files changed
+92
-19
No files matched your search
@@ -177,7 +177,22 @@ function drawZombie(zb) {
|
||||
else if ((zb.variant === 'a' || zb.variant === 'b') && zb.hp < TUNE.shamblerHp && SPR['z_' + zb.variant + 'w']) name = 'z_' + zb.variant + 'w'; // gashed shambler
|
||||
else if (rk && zb.hp <= 1 && SPR.z_cw2) { name = 'z_cw2'; h = 1.52; } // headless: a head shorter, so the shoulders land where a whole zombie's do
|
||||
else if (rk && zb.hp === 2 && SPR.z_cw1) name = 'z_cw1';
|
||||
drawStatic(name, zb.x, zb.z, h, Math.floor(zb.walkT * 5.2) % 2);
|
||||
// Every wound stage is a SINGLE frame, and the brute now spends most of its life wounded — so
|
||||
// without this it slid toward you like a decal. Rather than eight more sprites, the wrecked
|
||||
// stages get a procedural lumber: it heaves sideways, sags onto each step and drags the trailing
|
||||
// foot. Pure translate + squash (no rotation), so the pixels stay on the grid.
|
||||
const wrecked = zb.variant === 'g' && zb.hp < TUNE.bruteHp && name !== 'z_g' + frame;
|
||||
if (wrecked) {
|
||||
const p = proj(zb.x, 0, zb.z), w = zb.walkT * 2.4; // slow and heavy — it is barely holding together
|
||||
ctx.save();
|
||||
// amplitudes are in PROJECTED px, so they have to scale with p.s or they vanish at range —
|
||||
// at 0.035 the whole lurch was sub-pixel out at the far baseline and read as no animation
|
||||
ctx.translate(p.x + Math.sin(w) * p.s * 0.13, p.y);
|
||||
ctx.scale(1 + 0.07 * Math.sin(w), 1 - 0.09 * Math.abs(Math.sin(w * 2)));
|
||||
ctx.translate(-p.x, -p.y);
|
||||
}
|
||||
drawStatic(name, zb.x, zb.z, h, wrecked ? Math.max(0, Math.sin(zb.walkT * 4.8)) * 3 : Math.floor(zb.walkT * 5.2) % 2);
|
||||
if (wrecked) ctx.restore();
|
||||
if (zb.burnT > 0) drawFlames(zb, h);
|
||||
if (rk) { // HP bar floating above the mini-boss
|
||||
const p = proj(zb.x, 2.05, zb.z), w = Math.max(10, p.s * 0.5), x = p.x - w / 2;
|
||||
@@ -412,15 +427,18 @@ export function render() {
|
||||
if (crowdImg) {
|
||||
const cols = 16, sw = crowdImg.width / cols, dw = W / cols;
|
||||
for (let c = 0; c < cols; c++) {
|
||||
const dy = crowd.cheer > 0 ? -Math.random() * 2 : 0;
|
||||
// A smooth per-column wave, NOT per-frame noise: random jitter here read as TV static
|
||||
// through the scoreboard art's transparent ornate edges, which looked like the HUD flickering.
|
||||
const dy = crowd.cheer > 0 ? -1.6 * (0.5 + 0.5 * Math.sin(G.idleT * 12 + c * 1.7)) : 0;
|
||||
ctx.drawImage(crowdImg, c * sw, 0, sw, crowdImg.height, c * dw, dy, dw + 0.5, crowdH + 2);
|
||||
}
|
||||
} else { // fallback until the image loads: the old tier stripes + dots
|
||||
ctx.fillStyle = pal.stands; ctx.fillRect(0, 0, W, standsBottom);
|
||||
for (let r = 0; r < 16; r++) { ctx.fillStyle = r % 2 ? pal.tierA : pal.tierB; ctx.fillRect(0, 6 + r * 5.5, W, 5.5); }
|
||||
for (const d of crowd.dots) {
|
||||
const jx = crowd.cheer > 0 ? (Math.random() * 2 - 1) : 0;
|
||||
const jy = crowd.cheer > 0 ? -Math.random() * 1.5 : 0;
|
||||
const w = G.idleT * 12 + d.x * 0.4; // same smooth wave for the pre-sprite dot fallback
|
||||
const jx = crowd.cheer > 0 ? Math.sin(w * 0.7) : 0;
|
||||
const jy = crowd.cheer > 0 ? -1.2 * (0.5 + 0.5 * Math.sin(w)) : 0;
|
||||
ctx.fillStyle = d.c; ctx.fillRect(d.x + jx | 0, d.y + jy | 0, 2, 2);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user