Keep the wounded walking, and make the steward worth his size

A wound skin was a single still, so anything hurt froze on one frame and slid
across the court. Every skin has its …b twin now — the stride swapped, stepped
off the same clock as the healthy walk — for both shamblers, both racket-zombie
stages, and the steward, who had no wounded state at all.

The steward also died like a shambler at 3 hp, which made a bigger body read as
decoration. Five now, with a wound skin to show the second half of that. hpMax
is recorded on every body that has hp, so "hurt = show the wound" is one rule
instead of a per-variant constant to remember.

First pass at the wound frames was rejected on sight and deserved it: the
generator drew small figures with fine pixels on a GRADIENT background, and a
nearest-neighbour downscale to sprite height shredded the outlines. Regenerated
two at a time, drawn large, chunky pixels, flat background, hard black outline —
then fitted per channel onto each frame's own healthy twin.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Opus 5 committed 2026-07-31 19:57:45 +02:00
1 parent faec897d80
commit 1168e9ee5b
13 files changed
+20 -6

No files matched your search

+11 -2
View File
@@ -155,7 +155,7 @@ export function proj(x, y, z) {
// ---------------------------------------------------------------- sprites ----
// assets/<name>.png; game falls back to code-drawn figures until sprites load.
const SPRV = 53; // asset cache-buster — bump with main.js?v=N (see the img.src note below)
const SPRV = 55; // asset cache-buster — bump with main.js?v=N (see the img.src note below)
const SPR = {};
for (const n of ['p_idle1','p_idle2','p_bounce','p_bounce2','p_toss','p_smash','p_run1','p_run2','p_run3','p_run4','p_swing1','p_swing2',
'o_idle1','o_idle2','o_bounce','o_bounce2','o_toss','o_smash','o_run1','o_run2','o_swing1','o_swing2',
@@ -170,6 +170,7 @@ for (const n of ['p_idle1','p_idle2','p_bounce','p_bounce2','p_toss','p_smash','
'z_chair1','z_chair2','z_chairw','z_chairt','z_chairwt', // boss design 3 (chapter 3, 6, ...)
'z_a1','z_a2','z_a3','z_a4','z_b1','z_b2','z_b3','z_b4','z_c1','z_c2',
'z_d1','z_d2','z_d3','z_d4', // the steward: bigger body, hi-vis vest, 4-frame walk
'z_awb','z_bwb','z_cw1b','z_cw2b','z_dw','z_dwb', // ...and every wound skin's second frame: wounded bodies walk too
'z_f1','z_f2','z_f3','z_f4','z_g1','z_g2','z_g3','z_g4','z_gw','z_s1','z_s2','z_s3',
'z_cw1','z_cw2','z_aw','z_bw', // wounded skins for multi-hit enemies (racket boss, brute, and now the classic shamblers)
'w_ball','w_bomb','w_triple','w_fire','w_gun', // weapon-rack icons
@@ -316,7 +317,13 @@ function drawZombie(zb) {
h *= SPR[stage].height / SPRITE_H; // height ALWAYS off the A frame, or the size pops each step
} else if (SPR.z_gw) name = 'z_gw'; // a missing asset degrades to the last skin that loaded
}
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 ('abd'.includes(zb.variant) && zb.hp < (zb.hpMax || 0) && SPR['z_' + zb.variant + 'w']) {
// A wounded body used to freeze on ONE frame and slide across the court — the wound skins were
// single stills. Every skin has its …b twin now (the stride swapped), stepped off the same clock
// as the healthy walk, so losing an arm changes how he walks and not whether he walks.
const w = 'z_' + zb.variant + 'w';
name = SPR[w + 'b'] && stepFrame(zb.walkT * 2.6, 2) ? w + 'b' : w; // gashed shambler, still lurching
}
// The boss's damage is read as a FRACTION of full health — his hp scales with the run
// (TUNE.racketHpPerGame), so the old `hp === 2` / `hp <= 1` thresholds would have left a late-run
// boss looking untouched until the killing blow. Keep these in step with bleed()'s anchors.
@@ -324,6 +331,8 @@ function drawZombie(zb) {
const f = zb.hp / (zb.hpMax || TUNE.racketHp);
if (f <= 0.34 && 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 (f <= 0.67 && SPR.z_cw1) name = 'z_cw1';
const b = name + 'b'; // ...and he limps through his wounds too, instead of gliding
if (name !== 'z_' + v + frame && SPR[b] && stepFrame(zb.walkT * 2.6, 2)) name = b;
}
// The chapter boss comes apart through the SAME fraction-of-hpMax rule as the racket zombie, over
// three stages, so a 34 hp chapter-4 boss visibly degrades all the way down: arm off, then ribs