Phase 11: enemy roster wired — runner, brute, shield zombie

- Spawn mix (pickVariant): roster grows one type per set — runner from set 1
  game 3+ (12% slot, at most one alive, shriek on spawn = the fairness contract),
  brute from set 2 (15%), shield from set 3 (15%); classics fill the rest
- Runner ('f'): 3.2x shamble speed, faster leg cycle; new shriek() synth in audio
- Brute ('g'): hp 2 vs splat-quality balls AND pellets, 0.55x speed, 0.75m target,
  pushback-immune, 0.9m eaten radius, dies to double gore + size-2 pile, towers
  at 1.5x height
- Shield ('s'): blocks front balls with the chair (clank, ball spent), the block
  drops the chair for shieldDown 1.5s — any splat-quality ball kills in the
  window (z_s3 staggered pose renders); chair recovers after; always dies to
  bombs, melee, pellets
- TUNE: runnerSpeed 3.2, bruteHp 2, shieldDown 1.5

Verified in browser: per-variant stats; brute soft-ball immunity, 2-hit kill,
2-pellet kill, size-2 pile; shield block->window kill->chair recovery->blocks
again; melee kills f/s but not the racket boss; tier-2 spawn mix over 1500
spawns = 14/15/8/63%; all five sprites render with correct scale+occlusion;
tennis regression 11/12; no console errors.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Fable 5 committed 2026-07-12 10:39:10 +02:00
1 parent 811fe3f91f
commit 7bc26aec1c
4 files changed
+76 -14

No files matched your search

+6 -3
View File
@@ -59,7 +59,8 @@ const SPR = {};
for (const n of ['p_idle1','p_idle2','p_bounce','p_bounce2','p_toss','p_smash','p_run1','p_run2','p_swing1','p_swing2',
'o_idle1','o_idle2','o_bounce','o_bounce2','o_toss','o_smash','o_run1','o_run2','o_swing1','o_swing2',
'x_ump','x_boyrun1','x_boyrun2','x_boyrun3','x_boyrun4',
'z_a1','z_a2','z_b1','z_b2','z_c1','z_c2']) {
'z_a1','z_a2','z_b1','z_b2','z_c1','z_c2',
'z_f1','z_f2','z_g1','z_g2','z_s1','z_s2','z_s3']) {
const img = new Image();
img.onload = () => SPR[n] = img;
img.src = 'assets/' + n + '.png';
@@ -125,8 +126,10 @@ function drawBoy(b) {
function drawZombie(zb) {
const rk = zb.variant === 'r';
const v = rk ? 'c' : zb.variant; // the racket boss reuses the turned-CPU sprite (already holds a racket)
const frame = rk && zb.pose === 'swing' ? 2 : Math.floor(zb.walkT * 2.6) % 2 + 1;
drawStatic('z_' + v + frame, zb.x, zb.z, rk ? 1.85 : 1.8, Math.floor(zb.walkT * 5.2) % 2);
let frame = rk && zb.pose === 'swing' ? 2 : Math.floor(zb.walkT * (zb.variant === 'f' ? 6 : 2.6)) % 2 + 1;
if (zb.variant === 's' && zb.shieldT > 0) frame = 3; // chair dropped — hit it NOW
const h = zb.variant === 'g' ? 2.7 : rk ? 1.85 : 1.8; // the brute towers half again over the crowd
drawStatic('z_' + v + frame, zb.x, zb.z, h, Math.floor(zb.walkT * 5.2) % 2);
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;
ctx.fillStyle = '#300'; ctx.fillRect(x, p.y, w, 3);