diff --git a/HANDOFF.md b/HANDOFF.md
index dfeb6aa..9ad3c41 100644
--- a/HANDOFF.md
+++ b/HANDOFF.md
@@ -79,6 +79,29 @@ zombie (now an occasional opponent, `TUNE.racketEvery`, not the every-game fixtu
and only then extend, or you have silently nerfed the part of the game that was already fine.
The raw dials do run out around game 25 by design; bosses and ranged attacks (steps 4-5) are what
carry escalation past that, not more bodies.
+- **`woundStage(zb, n)` in config.js is the ONE rule for how damage READS.** 0 = unhurt, 1..n = the
+ skins, spread evenly over `hpMax`. There used to be three rules: the brute's hardcoded stage array
+ indexed by absolute hp, a single skin behind `'abd'.includes(...)`, and the boss's fraction-of-hpMax.
+ The boss's rule reproduces the other two EXACTLY (5 hp over 4 skins is one skin per hit; 2 hp over
+ 1 skin is the shambler) and keeps working when hp is not one-per-skin — which the ball boy (3 hp,
+ 2 skins), bouncer (6/3) and roller man (9/4) all need. So a new multi-hit body is now one row in
+ `config.ZWOUND` (healthiest first, `…b` stride twin looked up automatically) and NO code.
+ `bleed()`'s anchors are read through the same call and are deliberately the same length as that
+ variant's ZWOUND row, so the blood and the drawing it comes out of cannot drift apart — they used
+ to be written by hand against different constants. **'r' and 'B' keep their own skins** (the boss's
+ on its design row) but go through the same `woundStage`.
+- **A chapter boss's MOVEMENT is data on its `BOSS_ART` row** (`move`, plus optional `hpMul`,
+ `speedMul`, `volley`, `gap`, `spread`, `cycle`, `wind`, `shot`), and `bossAI` in zombies.js is the
+ only place it moves. Every field is optional, so a row carrying nothing but art behaves exactly as a
+ boss did before there were any. Three things that are load-bearing and were each a bug first:
+ **(a)** the generic forward step at the top of the body loop SKIPS `'B'` — bossAI owns both axes,
+ and leaving it in advanced the boss twice per frame; **(b)** the stride steps off `zb.dist`
+ (ground covered on both axes), not `zb.z`, or a boss strafing sideways slides with its feet frozen;
+ **(c)** a boss holds a z BAND (`bossZMin..bossZMax`) except while charging — uncapped, a fight ended
+ by the boss simply arriving after about a minute, and every extra hp made that MORE likely, not less.
+ **No style may rely on the player moving to create its own motion**: in splatter he is rooted in
+ `pretoss` for nearly all of a boss fight, so "close on the player's x" arrives, stops, and is static
+ for the rest of the fight. Every style moves under its own power and only BIASES toward him.
- **`wound(zb, amount, fx)` in zombies.js is the ONE place damage is applied to a multi-hit body.**
Fractional amounts are legal (a pellet chips `TUNE.dmgPellet` = 0.34 of a point); they bank on
`zb.chip` and only WHOLE points come off `zb.hp`, because the wound skins and `bleed()` anchors
@@ -384,6 +407,24 @@ Clear the intro first: `document.getElementById('intro')?.remove()`.
survives as a rim on a green court.
12. **Two-row "style reference / redraw this" input does NOT work.** The model returns both rows and
invents a background. Describe the target detail level in words instead.
+12b. **`_regen.py` has THREE style blocks now, and picking the wrong one costs rolls.** The shared
+ block used to be one constant that always said *"keep every pose, every colour EXACTLY as in the
+ reference"* — correct for a remaster, and it fights every word of the subject for anything else.
+ - default (`STYLE`): remastering a sprite that already exists.
+ - `"newart": true` (`NEW_STYLE`): a NEW character drawn in the reference's style. Drops the
+ fidelity clause and says explicitly not to copy the reference's body or outfit.
+ - `"state": true` (`STATE_STYLE`): the SAME character in a new state — wounded, mid-throw. This is
+ the one that is easy to get wrong, because `newart` looks right and isn't: it licenses a repaint,
+ and it got one. Five wound sheets came back with living TAN skin and a redesigned shirt while the
+ healthy frame stayed a grey-green corpse, and the coach's navy tracksuit came back mid-blue
+ (torso RGB blue channel 111 -> 138). `STATE_STYLE` pins colours, clothing and build to the
+ reference and states outright that he is a corpse, never pink or tan.
+ **A brand-new sprite name must exist on disk before its batch runs** — `run()` measures `density()`
+ of the file it is about to replace, and `colour_match` opens it. Seed each new name by copying a
+ body of the right size and facing, and use `check: same`/`loose` so `colour_match` is skipped and
+ the seed's palette is NOT inherited. The trap in that: a batch that silently fails leaves a perfect
+ copy of another character behind, which renders as working art. Assert every new PNG actually
+ differs from its seed before shipping (the scratchpad `check_seeds.py` does exactly this).
13. **`assets/_derive.py` is where a frame that must MATCH another frame is made.** Three of them
live there now: `p_bounce2` (a per-column shear of `p_bounce` — the free arm bends, nothing else
moves), `p_toss` (the drawn-in ball cut out, canvas height KEPT so drawChar keeps his scale) and
@@ -409,6 +450,47 @@ Known generator quirks, all still true:
- **Colour-match any regenerated frame** of an existing pair before shipping: fit a per-channel
linear map onto the original's mean and sd, then verify with a mean-difference number.
+## Session log: the roster grows — moving bosses, six of them, three new bodies (2026-08-03)
+
+Oliver asked for five things. This is the first half: **enemies first, weapons second**, his call, so
+the arsenal could be tuned against the harder game rather than the other way round.
+
+**Bosses move now, and the style is data.** A boss previously matched NO branch in the variant chain
+and fell through to the crowd's aimless ±0.15 m/s wobble — "the boss AI owns its x" was a comment
+describing code that did not exist. Six styles, one per design: `pace` (groundskeeper, and the coach
+at 1.9x), `shadow` (line judge — paces a band centred on YOU), `surge` (chair umpire — rolls up and
+down the whole depth band), `charge` (physio), `bounce` (mascot). Measured over a simulated minute
+each: 50–136 m travelled, none static. **Three earlier drafts of these styles failed the same way and
+it is worth knowing why: they were written as "walk toward the player's x", which in splatter means
+arrive in four seconds and stand still for the rest of the fight, because the player is rooted in
+`pretoss` and never moves.** Measure `xSpan` over 3600 steps before believing any movement change.
+
+**Three new bosses**: THE HEAD COACH (rapid 5-shot drill, 0.14 s apart), THE PHYSIO (charges — it
+withdraws LESS than it charges, so it is a couple of metres nearer after each rush and reaches you at
+about 38 s, which is the fight's clock), THE SPONSOR MASCOT (a `gap: 0` volley — the whole wall of
+five leaves in ONE frame, so it is dodged, not parried; the salvo loop is a `while` for exactly this).
+Harder to kill: `bossHp` 16 -> 20, `bossHpPerChapter` 9 -> 10, plus per-design `hpMul`.
+**Chapter 6 is now 91 hp — the number most likely to want tuning after a real playtest.**
+
+**Three new bodies**: ball boy `'e'` (3 hp, 2 skins, from game 3), bouncer `'h'` (6/3, game 8), roller
+man `'k'` (9/4, game 15). Named "roller man" and not "groundsman" so he cannot be confused with the
+GROUNDSKEEPER boss.
+
+**One real bug found, and it was pre-existing.** `killPlayer()` empties `shots[]` while the shots loop
+is mid-iteration, so every remaining index reads `undefined` and the frame throws. The mascot's
+five-at-once volley makes it easy to hit; the shipped 3-shot volley could always have hit it. Fixed
+with the same `if (!s) continue` guard the body loop above it already carries, and for the same reason.
+
+**Verification that actually caught things** (all via `window.T`, RAF halted):
+- `drawImage` instrumentation per design: all six bosses and all three new bodies draw their OWN
+ frames, both `…w`/`…wb` stride twins included. Nothing silently falls back to design 1.
+- Differential check on the refactor: the brute still draws `z_gw/gw2/gw3/gw4` at hp 4/3/2/1 — the
+ new shared `woundStage` is byte-identical to the array it replaced.
+- Foot-gap spread: ball boy 76% (the shipped shambler is 36%, the brute 39%). Boss stride-pair
+ difference 34–54%, inside the shipped range of 30–83%.
+- 26-game soak: no exceptions, no stall, `mgT` back to 0, gore capped 40, piles capped 30, and
+ `e`/`h`/`k`/`B` all appeared in it.
+
## Session log: court types (2026-08-01)
Three courts, picked on a second start-screen stage. A court is pure DATA — `COURTS` in config.js
@@ -746,11 +828,13 @@ Nothing here is broken; these are known gaps and unverified risks.
scale but gained no detail in the remaster, and they are the two most-seen poses in the game.
Fix by hand-slicing `_sheet_player2/3` and `_sheet_opp2/3` at `IDLE_TARGET_H` 224 rather than going
through the signature matcher, which assigned those frames elsewhere.
-4. **Boss designs are asymmetric.** Design 1 (GROUNDSKEEPER) has 3 wound stages and 4 throw frames;
- designs 2 (LINE JUDGE) and 3 (CHAIR UMPIRE) have 1 wound skin and 2 throw frames each. Legal — the
+4. **Boss designs are asymmetric — still, for two of the six.** Designs 1 (GROUNDSKEEPER), 4 (HEAD
+ COACH), 5 (PHYSIO) and 6 (SPONSOR MASCOT) have 3 wound stages and 4 throw frames each; designs 2
+ (LINE JUDGE) and 3 (CHAIR UMPIRE) still have 1 wound skin and 2 throw frames. Legal — the
`BOSS_ART` indices are clamped — but a chapter-2 boss degrades in one visible step instead of three.
-5. **Only 3 boss designs, cycling.** Chapter 4 repeats the groundskeeper at 43 hp and 3.67 m. A fourth
- design is a sheet plus one row in `BOSS_ART`.
+ Fixing them is now just two batches in the shape of `boss_coach_wounds` / `boss_coach_throw`.
+5. ~~**Only 3 boss designs, cycling.**~~ CLOSED 2026-08-03 — six now, so a chapter does not repeat a
+ design until chapter 7. A seventh is still a sheet plus one row in `BOSS_ART`.
6. **Landscape is under-maintained.** The readability pass (camZ/camY/camF/horizon), the adaptive
height and the safe-area work are all portrait-only; landscape still runs the old camera and a fixed
480x270. Portrait is the stated primary target, but landscape has drifted and is untested.
diff --git a/assets/_regen.py b/assets/_regen.py
index d401c7d..c5d8e04 100644
--- a/assets/_regen.py
+++ b/assets/_regen.py
@@ -21,21 +21,47 @@ SCRATCH = os.environ.get('SPLAT_SCRATCH', '/tmp/splatter-regen')
GEN = os.path.expanduser('~/.claude/skills/generate-image/scripts/generate_image.py')
PY = os.environ.get('SPLAT_PY', sys.executable)
-STYLE = (
+# The style block is in three parts because the middle one is a REDRAW instruction and only a redraw
+# wants it. "Keep every pose and colour exactly as in the reference" is right when the job is
+# remastering a sprite that already exists, and it fights every word of the subject when the job is a
+# NEW character drawn in the reference's style — set "newart": true on the batch to drop it.
+RES = (
"Redraw them as HIGH-RESOLUTION pixel art. The art pixels must be SMALL: build each figure out "
"of roughly 90 to 110 pixels of height instead of the reference's 40, so there is room for real "
"detail, and spend it — a face with teeth, sunken eyes and a jawline; individual fingers; "
"clothing with visible seams, hems, folds and frayed torn edges; laced shoes; four or five "
- "distinct shades per material with fine hatched shading where the light falls away. Keep every "
- "pose, every body proportion, every height, every facing and every wound EXACTLY as in the "
- "reference — only the drawing resolution and the amount of fine detail change. Keep the same "
- "colours. Hard black outline around each figure. The background must be ONE FLAT solid magenta "
+ "distinct shades per material with fine hatched shading where the light falls away. "
+)
+FIDELITY = (
+ "Keep every pose, every body proportion, every height, every facing and every wound EXACTLY as "
+ "in the reference — only the drawing resolution and the amount of fine detail change. Keep the "
+ "same colours. "
+)
+CANVAS = (
+ "Hard black outline around each figure. The background must be ONE FLAT solid magenta "
"#FF00FF across the entire canvas — no scenery, no gradient, no vignette. Keep at least 200 "
"pixels of empty magenta between figures and at least 150 pixels of empty magenta at every edge "
"of the canvas: no figure or limb may touch or run off an edge. No shadow, no ground line, no text. "
"The canvas is a WIDE LANDSCAPE STRIP and every figure stands side by side in ONE SINGLE HORIZONTAL "
"ROW on the same ground line — never a grid, never two rows, never stacked."
)
+STYLE = RES + FIDELITY + CANVAS
+NEW_STYLE = RES + (
+ "Match the reference's DRAWING STYLE, its overall figure height and its facing — but the "
+ "character, the clothes, the colours and the poses are the ones described above, not the "
+ "reference's. Do not copy the reference's body or outfit. "
+) + CANVAS
+# The third case, and the one that is easy to miss: the SAME character in a different state (wounded,
+# mid-throw). "newart" is wrong for it — it licenses the generator to repaint the character, and it
+# did: wound frames came back with living tan skin and a redesigned shirt while the healthy frame
+# stayed a grey-green corpse. Set "state": true for those.
+STATE_STYLE = RES + (
+ "This is the SAME character as the reference, not a new one. His colours, his clothing, his "
+ "build, his proportions and his height must be IDENTICAL to the reference — the only things "
+ "that change are the injuries and the pose described above. His skin stays exactly the same "
+ "dead grey-green as the reference: he is a walking corpse, never pink, never tan, never living "
+ "flesh. Do not redesign or recolour any garment. "
+) + CANVAS
def batches():
with open(os.path.join(HERE, '_regen_batches.json')) as f:
@@ -147,7 +173,7 @@ def run(name, spec, dry=False):
ref = ref_sheet(shown, f'{SCRATCH}/{name}_ref.png', spec.get('scale', 2.0),
frames=len(sprites) + (1 if lead else 0))
sheet = f'{SCRATCH}/{name}_hi.png'
- prompt = f"{spec['subject']} {STYLE}"
+ prompt = f"{spec['subject']} {STATE_STYLE if spec.get('state') else NEW_STYLE if spec.get('newart') else STYLE}"
if dry:
print(f'[{name}] would generate {len(sprites)} frames: {", ".join(sprites)}'); return True
print(f'[{name}] generating {len(sprites)} frames...')
@@ -174,7 +200,13 @@ def run(name, spec, dry=False):
# that already has blood on it starts high, so a genuinely finer wounded body gains less). A
# REPOSE — turning the player around, pointing the runner at the camera, matching a wound between
# two frames — is already fine art and only has to come back no worse.
- bar = {'finer': 1.25, 'same': 0.85, 'loose': 0.7}[spec.get('check', 'finer')]
+ # 'any' turns the detail gate OFF, and there is exactly one job it is for: RE-ROLLING art that is
+ # already fine to fix something the gate cannot see, such as a colour. The bar is measured against
+ # the file being replaced, so once a good sheet installs it RATCHETS — a second roll of equal
+ # quality now has to beat the first, and three colour-fix re-rolls were rejected for being 0.39
+ # against a 0.46 that was itself only there because the first roll happened to be dense. Magenta
+ # and edge checks still apply; verify the thing you were actually fixing by measuring it.
+ bar = {'finer': 1.25, 'same': 0.85, 'loose': 0.7, 'any': 0.0}[spec.get('check', 'finer')]
if sum(after) / len(after) < sum(before) / len(before) * bar:
print(f'[{name}] REJECTED: not finer than what it replaces {before} -> {after}'); return False
if spec.get('palette_ref'):
diff --git a/assets/_regen_batches.json b/assets/_regen_batches.json
index 02f3641..e314e17 100644
--- a/assets/_regen_batches.json
+++ b/assets/_regen_batches.json
@@ -27,7 +27,8 @@
"z_bwb"
],
"subject": "Four frames of two WOUNDED walking zombies, drawn as coarse low-resolution pixel art. The first two are the same grey-shirted zombie whose right arm ends in a ragged bloody stump, in two stages of one stride. The last two are the same brown-coated zombie with an open red gash across his skull, in two stages of one stride. Keep each wound exactly where it is.",
- "anchor": 0
+ "anchor": 0,
+ "state": true
},
"brute_walk": {
"sprites": [
@@ -65,7 +66,8 @@
"z_dwb"
],
"subject": "Two frames of the same badly wounded zombie stadium steward in a torn high-visibility ORANGE vest \u2014 one arm mangled, a deep bloody gash across the chest, head lolling \u2014 walking, two stages of one stride, drawn as coarse low-resolution pixel art.",
- "anchor": 0
+ "anchor": 0,
+ "state": true
},
"cpu_walk": {
"sprites": [
@@ -85,7 +87,8 @@
"z_cw2b"
],
"subject": "Four frames of the same wounded zombie tennis player in a red shirt holding a wooden racket, drawn as coarse low-resolution pixel art. The first two are him wounded and bleeding, in two stages of one stride. The last two are the same character HEADLESS, a ragged red stump where the neck was, still holding the racket, in two stages of one stride.",
- "anchor": 0
+ "anchor": 0,
+ "state": true
},
"player_stance": {
"sprites": [
@@ -268,7 +271,8 @@
],
"anchor": 0,
"subject": "Four frames of the same enormous fat zombie brute, drawn as coarse low-resolution pixel art. The FIRST TWO are the same damage stage \u2014 his LEFT arm torn off at the shoulder leaving a ragged bloody stump, the right arm dragging a rusty pole \u2014 shown as two stages of one stride. The LAST TWO are the next damage stage, BOTH arms gone at the shoulders, again two stages of one stride. Within each pair the injuries must be IDENTICAL and on the same side of the body: only the legs change.",
- "check": "same"
+ "check": "same",
+ "state": true
},
"brute_all_wounds2": {
"sprites": [
@@ -453,5 +457,302 @@
"anchor": 0,
"check": "loose",
"subject": "The reference is ONE young ball boy in a WHITE polo shirt and WHITE shorts with brown hair, seen from the FRONT, holding a floor mop. Draw FOUR frames of that SAME boy \u2014 same face, same brown hair, same white kit, same white shoes, same size, same front-facing view \u2014 with a gormless, dim-witted, slightly cross-eyed expression in every frame. THE FOUR FRAMES ARE FOUR DIFFERENT POSES: (1) WAVING hello with his free hand raised beside his head, big goofy open-mouthed grin, the mop standing upright in his other hand; (2) POINTING away to the side with one arm stuck straight out and his mouth open explaining something, mop upright in the other hand; (3) SHRUGGING with both palms turned up and his shoulders hunched, utterly baffled, eyebrows up, the mop tucked under one arm; (4) TERRIFIED \u2014 eyes wide as saucers, mouth open in a yell, both hands clutching the mop up across his chest like a weapon. Each figure stands upright at the same height with both feet on the ground. The whole boy and the whole mop are fully inside the canvas with clear space around them. The canvas contains EXACTLY FOUR figures and no others."
+ },
+ "ballboy_walk": {
+ "sprites": [
+ "z_e1",
+ "z_e2",
+ "z_e3",
+ "z_e4"
+ ],
+ "ref_only": [
+ "z_a1"
+ ],
+ "anchor": 0,
+ "check": "same",
+ "subject": "The reference is one walking zombie seen from the SIDE and facing LEFT. Draw FOUR frames of a walk cycle, seen from the SIDE and facing LEFT, of a dead teenage TOURNAMENT BALL BOY: short-sleeved white and navy polo shirt with a small sponsor patch, navy shorts, white knee socks and white trainers, a tennis ball still clutched in one hand. Green-grey dead skin, sunken eyes, slack jaw, hair matted flat. An ordinary-sized body, the same height as the reference. The four frames are the four stages of ONE shambling stride: front leg reaching forward with the GAP BETWEEN THE FEET WIDE; then the feet together and passing; then the other leg reaching forward with the gap wide the other way; then the feet together again. The gap between his feet must visibly open and close, and the arms swing opposite the legs. Output exactly four figures in one row, evenly spaced.",
+ "state": true
+ },
+ "ballboy_w1": {
+ "sprites": [
+ "z_ew",
+ "z_ewb"
+ ],
+ "ref_only": [
+ "z_e1"
+ ],
+ "anchor": 0,
+ "check": "same",
+ "subject": "The reference is the dead tournament ball boy in his white and navy polo, seen from the SIDE and facing LEFT. Draw TWO frames of that SAME boy, same size, same clothes, still walking, now WOUNDED: his right arm is torn off at the elbow leaving a ragged bloody stump, the polo shredded and soaked dark down that side, the tennis ball gone. The two frames are the two halves of ONE stride: in the first the front foot is far forward and the GAP BETWEEN THE FEET IS WIDE, in the second the feet are together and passing. Output exactly two figures in one row.",
+ "state": true
+ },
+ "ballboy_w2": {
+ "sprites": [
+ "z_ew2",
+ "z_ew2b"
+ ],
+ "ref_only": [
+ "z_e1"
+ ],
+ "anchor": 0,
+ "check": "same",
+ "subject": "The reference is the dead tournament ball boy in his white and navy polo, seen from the SIDE and facing LEFT. Draw TWO frames of that SAME boy, same size, same clothes, still walking, now HORRIFICALLY WOUNDED: BOTH arms torn off at the shoulders leaving two bloody stumps, and his lower jaw ripped away so the throat is open, the polo soaked black-red to the hem. The two frames are the two halves of ONE stride: in the first the front foot is far forward and the GAP BETWEEN THE FEET IS WIDE, in the second the feet are together and passing. Output exactly two figures in one row.",
+ "state": true
+ },
+ "bouncer_walk": {
+ "sprites": [
+ "z_h1",
+ "z_h2",
+ "z_h3",
+ "z_h4"
+ ],
+ "ref_only": [
+ "z_d1"
+ ],
+ "anchor": 0,
+ "check": "same",
+ "subject": "The reference is one big walking zombie seen from the SIDE and facing LEFT. Draw FOUR frames of a walk cycle, seen from the SIDE and facing LEFT, of a dead SECURITY STEWARD working the tournament gate: a big heavy-set man with a shaven head and a thick neck, wearing an open BLACK bomber jacket over a bright YELLOW high-visibility vest with SECURITY in black block letters, a white shirt collar showing at the throat, a red accreditation lanyard, grey-green cargo trousers with deep pockets and visible seams, a chunky leather belt with a silver buckle, tan boots, tattoos down one bare forearm, and a coiled clear earpiece wire running up to one ear. Green-grey dead skin, jaw hanging, teeth bared. He is broad and slab-shouldered \u2014 noticeably heavier than the reference, but still a normal human silhouette \u2014 and he walks with his arms held out from his body, shoving forward. Draw him at the same overall height as the reference. The four frames are the four stages of ONE shambling stride: front leg reaching forward with the GAP BETWEEN THE FEET WIDE; then the feet together and passing; then the other leg reaching forward with the gap wide the other way; then the feet together again. The gap between his feet must visibly open and close, and the arms swing opposite the legs. Output exactly four figures in one row, evenly spaced.",
+ "state": true
+ },
+ "bouncer_w1": {
+ "sprites": [
+ "z_hw",
+ "z_hwb"
+ ],
+ "ref_only": [
+ "z_h1"
+ ],
+ "anchor": 0,
+ "check": "any",
+ "subject": "The reference is the dead SECURITY steward in his black bomber jacket over a bright yellow hi-vis vest, seen from the SIDE and facing LEFT. Draw TWO frames of that SAME man, the same size and bulk and the same clothes, still walking, now WOUNDED: his left arm is mangled and hanging by a thread of muscle, and the hi-vis vest is torn open across the chest with a deep bloody gash beneath it. Do not slim him down. The two frames are the two halves of ONE stride: in the first the front foot is far forward and the GAP BETWEEN THE FEET IS WIDE, in the second the feet are together and passing. Output exactly two figures in one row.",
+ "state": true
+ },
+ "bouncer_w2": {
+ "sprites": [
+ "z_hw2",
+ "z_hw2b"
+ ],
+ "ref_only": [
+ "z_h1"
+ ],
+ "anchor": 0,
+ "check": "same",
+ "subject": "The reference is the dead SECURITY steward in his black bomber jacket over a bright yellow hi-vis vest, seen from the SIDE and facing LEFT. Draw TWO frames of that SAME man, the same size and bulk and the same clothes, still walking, now WOUNDED: BOTH arms are torn off at the shoulders leaving two bloody stumps, and the hi-vis vest hangs in shredded ribbons over an opened chest. Do not slim him down. The two frames are the two halves of ONE stride: in the first the front foot is far forward and the GAP BETWEEN THE FEET IS WIDE, in the second the feet are together and passing. Output exactly two figures in one row.",
+ "state": true
+ },
+ "bouncer_w3": {
+ "sprites": [
+ "z_hw3",
+ "z_hw3b"
+ ],
+ "ref_only": [
+ "z_h1"
+ ],
+ "anchor": 0,
+ "check": "same",
+ "subject": "The reference is the dead SECURITY steward in his black bomber jacket over a bright yellow hi-vis vest, seen from the SIDE and facing LEFT. Draw TWO frames of that SAME man, the same size and bulk and the same clothes, still walking, now WOUNDED: both arms are gone at the shoulders AND he has no head at all \u2014 the neck ends in a ragged red stump at shoulder height, no skull, no face \u2014 the earpiece wire dangling loose into the wound. Do not slim him down. The two frames are the two halves of ONE stride: in the first the front foot is far forward and the GAP BETWEEN THE FEET IS WIDE, in the second the feet are together and passing. Output exactly two figures in one row.",
+ "state": true
+ },
+ "roller_walk": {
+ "sprites": [
+ "z_k1",
+ "z_k2",
+ "z_k3",
+ "z_k4"
+ ],
+ "ref_only": [
+ "z_g1"
+ ],
+ "anchor": 0,
+ "check": "same",
+ "subject": "The reference is one enormous walking zombie brute seen from the SIDE and facing LEFT. Draw FOUR frames of a heavy walk cycle, seen from the SIDE and facing LEFT, of a dead GROUNDSMAN hauling a heavy court roller: a colossal man in filthy green overalls with the sleeves cut off, a leather tool belt, mud-caked rubber boots, leaning hard into a two-handed steel T-handle. The roller itself is a squat heavy cylinder on a short frame close behind his heels \u2014 it must stay small and entirely INSIDE his own frame, never reaching toward the next figure. Green-grey dead skin, one eye gone, teeth bared with effort. He is even bigger and heavier than the reference, hunched forward under the load. The four frames are the four stages of ONE shambling stride: front leg reaching forward with the GAP BETWEEN THE FEET WIDE; then the feet together and passing; then the other leg reaching forward with the gap wide the other way; then the feet together again. The gap between his feet must visibly open and close, and the arms swing opposite the legs. Output exactly four figures in one row, evenly spaced.",
+ "state": true
+ },
+ "roller_w1": {
+ "sprites": [
+ "z_kw",
+ "z_kwb"
+ ],
+ "ref_only": [
+ "z_k1"
+ ],
+ "anchor": 0,
+ "check": "any",
+ "subject": "The reference is the dead groundsman in filthy green overalls, seen from the SIDE and facing LEFT. Draw TWO frames of that SAME man, the same colossal size and bulk and the same overalls, still walking, now WOUNDED: the arm on the right-hand side of the image is COMPLETELY GONE \u2014 severed at the shoulder joint, no upper arm, no forearm, no hand, nothing below it but a ragged red stump of bone and muscle at the shoulder \u2014 while his other arm still grips the roller handle. He must stay exactly as enormous \u2014 do not slim him down as the damage grows. The two frames are the two halves of ONE stride: in the first the front foot is far forward and the GAP BETWEEN THE FEET IS WIDE, in the second the feet are together and passing. Output exactly two figures in one row.",
+ "state": true
+ },
+ "roller_w2": {
+ "sprites": [
+ "z_kw2",
+ "z_kw2b"
+ ],
+ "ref_only": [
+ "z_k1"
+ ],
+ "anchor": 0,
+ "check": "any",
+ "subject": "The reference is the dead groundsman in filthy green overalls, seen from the SIDE and facing LEFT. Draw TWO frames of that SAME man, the same colossal size and bulk and the same overalls, still walking, now WOUNDED: BOTH arms are torn off at the shoulders leaving two bloody stumps and the roller is gone entirely. He must stay exactly as enormous \u2014 do not slim him down as the damage grows. The two frames are the two halves of ONE stride: in the first the front foot is far forward and the GAP BETWEEN THE FEET IS WIDE, in the second the feet are together and passing. Output exactly two figures in one row.",
+ "state": true
+ },
+ "roller_w3": {
+ "sprites": [
+ "z_kw3",
+ "z_kw3b"
+ ],
+ "ref_only": [
+ "z_k1"
+ ],
+ "anchor": 0,
+ "check": "same",
+ "subject": "The reference is the dead groundsman in filthy green overalls, seen from the SIDE and facing LEFT. Draw TWO frames of that SAME man, the same colossal size and bulk and the same overalls, still walking, now WOUNDED: both arms are gone AND his chest and belly are torn wide open with the ribs showing through, the overalls hanging in bloody strips. He must stay exactly as enormous \u2014 do not slim him down as the damage grows. The two frames are the two halves of ONE stride: in the first the front foot is far forward and the GAP BETWEEN THE FEET IS WIDE, in the second the feet are together and passing. Output exactly two figures in one row.",
+ "state": true
+ },
+ "roller_w4": {
+ "sprites": [
+ "z_kw4",
+ "z_kw4b"
+ ],
+ "ref_only": [
+ "z_k1"
+ ],
+ "anchor": 0,
+ "check": "same",
+ "subject": "The reference is the dead groundsman in filthy green overalls, seen from the SIDE and facing LEFT. Draw TWO frames of that SAME man, the same colossal size and bulk and the same overalls, still walking, now WOUNDED: both arms are gone, the chest is torn open with ribs showing, AND he has no head at all \u2014 the neck ends in a ragged red stump at shoulder height. He must stay exactly as enormous \u2014 do not slim him down as the damage grows. The two frames are the two halves of ONE stride: in the first the front foot is far forward and the GAP BETWEEN THE FEET IS WIDE, in the second the feet are together and passing. Output exactly two figures in one row.",
+ "state": true
+ },
+ "boss_coach_walk": {
+ "sprites": [
+ "z_coach1",
+ "z_coach2"
+ ],
+ "ref_only": [
+ "z_boss1"
+ ],
+ "anchor": 0,
+ "check": "same",
+ "newart": true,
+ "subject": "The reference is a huge chapter-boss zombie drawn FACING THE VIEWER. Draw TWO frames of a DIFFERENT boss in that same front-facing style and at that same enormous size: THE HEAD COACH \u2014 a towering dead tennis coach facing the viewer, in a zipped navy and white tracksuit with a national-team crest on the chest and white piping down the sleeves, a steel whistle on a cord round his neck, wraparound sunglasses pushed up on his forehead, and a wire ball-hopper basket full of tennis balls gripped in one huge fist. Green-grey dead skin, hollow cheeks, teeth bared in a shout. In both frames his chest and face point straight at the viewer. The two frames are two stages of one heavy stride straight toward the viewer, one knee lifted in each. Output exactly two figures in one row."
+ },
+ "boss_coach_wounds": {
+ "sprites": [
+ "z_coachw1",
+ "z_coachw2",
+ "z_coachw3"
+ ],
+ "ref_only": [
+ "z_coach1"
+ ],
+ "anchor": 0,
+ "check": "loose",
+ "subject": "The reference is THE HEAD COACH \u2014 a towering dead tennis coach facing the viewer, in a zipped navy and white tracksuit with a national-team crest on the chest and white piping down the sleeves, a steel whistle on a cord round his neck, wraparound sunglasses pushed up on his forehead, and a wire ball-hopper basket full of tennis balls gripped in one huge fist. Green-grey dead skin, hollow cheeks, teeth bared in a shout, facing the viewer. Draw THREE frames of that SAME boss, the same enormous size and the same costume, standing and facing the viewer, as three worsening stages of damage: (1) the arm on the left-hand side of the image is torn off at the shoulder leaving a ragged bloody stump \u2014 the OTHER arm is untouched and still holding what it held; (2) his chest and belly are torn wide open with the ribs showing through and the clothing hanging in bloody strips \u2014 one arm is still whole and still holding what it held; (3) his chest is torn open with ribs showing AND he has no head at all, the neck ending in a ragged red stump at shoulder height \u2014 one arm is still whole and still holding what it held. He must stay the same enormous size in all three \u2014 do not slim him down as the damage grows. Output exactly three figures in one row.",
+ "state": true
+ },
+ "boss_coach_throw": {
+ "sprites": [
+ "z_coacht0",
+ "z_coacht1",
+ "z_coacht2",
+ "z_coacht3"
+ ],
+ "ref_only": [
+ "z_coach1",
+ "z_coachw1",
+ "z_coachw2",
+ "z_coachw3"
+ ],
+ "anchor": 0,
+ "check": "loose",
+ "subject": "The reference shows the SAME boss in four states: undamaged, then three worsening stages of damage. Draw all FOUR of those exact states again, in the same order, same size, same costume, same wounds \u2014 but each one caught MID-THROW: the whole body wound up and twisted, the surviving arm drawn back high over the shoulder about to hurl a tennis ball straight at the viewer, weight on the back foot. The wounds must stay clearly visible in every frame. Output exactly four figures in one row.",
+ "state": true
+ },
+ "boss_physio_walk": {
+ "sprites": [
+ "z_physio1",
+ "z_physio2"
+ ],
+ "ref_only": [
+ "z_boss1"
+ ],
+ "anchor": 0,
+ "check": "same",
+ "newart": true,
+ "subject": "The reference is a huge chapter-boss zombie drawn FACING THE VIEWER. Draw TWO frames of a DIFFERENT boss in that same front-facing style and at that same enormous size: THE PHYSIO \u2014 a towering dead sports physiotherapist facing the viewer, in a white medical polo with a red cross on the breast and blood to the elbows, torn blue latex gloves, a bandolier of rolled strapping tape across the chest, a folding massage table strapped flat to his back, an ice pack gripped in one huge fist. Green-grey dead skin, wire-framed glasses cracked across one lens, jaw slack. In both frames his chest and face point straight at the viewer. The two frames are two stages of one heavy stride straight toward the viewer, one knee lifted in each. Output exactly two figures in one row."
+ },
+ "boss_physio_wounds": {
+ "sprites": [
+ "z_physiow1",
+ "z_physiow2",
+ "z_physiow3"
+ ],
+ "ref_only": [
+ "z_physio1"
+ ],
+ "anchor": 0,
+ "check": "loose",
+ "subject": "The reference is THE PHYSIO \u2014 a towering dead sports physiotherapist facing the viewer, in a white medical polo with a red cross on the breast and blood to the elbows, torn blue latex gloves, a bandolier of rolled strapping tape across the chest, a folding massage table strapped flat to his back, an ice pack gripped in one huge fist. Green-grey dead skin, wire-framed glasses cracked across one lens, jaw slack, facing the viewer. Draw THREE frames of that SAME boss, the same enormous size and the same costume, standing and facing the viewer, as three worsening stages of damage: (1) the arm on the left-hand side of the image is torn off at the shoulder leaving a ragged bloody stump \u2014 the OTHER arm is untouched and still holding what it held; (2) his chest and belly are torn wide open with the ribs showing through and the clothing hanging in bloody strips \u2014 one arm is still whole and still holding what it held; (3) his chest is torn open with ribs showing AND he has no head at all, the neck ending in a ragged red stump at shoulder height \u2014 one arm is still whole and still holding what it held. He must stay the same enormous size in all three \u2014 do not slim him down as the damage grows. Output exactly three figures in one row.",
+ "state": true
+ },
+ "boss_physio_throw": {
+ "sprites": [
+ "z_physiot0",
+ "z_physiot1",
+ "z_physiot2",
+ "z_physiot3"
+ ],
+ "ref_only": [
+ "z_physio1",
+ "z_physiow1",
+ "z_physiow2",
+ "z_physiow3"
+ ],
+ "anchor": 0,
+ "check": "loose",
+ "subject": "The reference shows the SAME boss in four states: undamaged, then three worsening stages of damage. Draw all FOUR of those exact states again, in the same order, same size, same costume, same wounds \u2014 but each one caught MID-THROW: the whole body wound up and twisted, the surviving arm drawn back high over the shoulder about to hurl a tennis ball straight at the viewer, weight on the back foot. The wounds must stay clearly visible in every frame. Output exactly four figures in one row.",
+ "state": true
+ },
+ "boss_mascot_walk": {
+ "sprites": [
+ "z_mascot1",
+ "z_mascot2"
+ ],
+ "ref_only": [
+ "z_boss1"
+ ],
+ "anchor": 0,
+ "check": "same",
+ "newart": true,
+ "subject": "The reference is a huge chapter-boss zombie drawn FACING THE VIEWER. Draw TWO frames of a DIFFERENT boss in that same front-facing style and at that same enormous size: THE SPONSOR MASCOT \u2014 a towering dead tournament mascot facing the viewer, wearing a giant fuzzy tennis-ball costume: an enormous yellow-green foam sphere for a head and body with the white curved seam painted across it, stubby green sleeves and leggings, oversized cartoon shoes, white gloves. Through a dark mesh mouth-hole in the ball a rotting green face is just visible. He grips a tennis ball in one glove. In both frames his chest and face point straight at the viewer. The two frames are two stages of one heavy stride straight toward the viewer, one knee lifted in each. Output exactly two figures in one row."
+ },
+ "boss_mascot_wounds": {
+ "sprites": [
+ "z_mascotw1",
+ "z_mascotw2",
+ "z_mascotw3"
+ ],
+ "ref_only": [
+ "z_mascot1"
+ ],
+ "anchor": 0,
+ "check": "loose",
+ "subject": "The reference is THE SPONSOR MASCOT \u2014 a towering dead tournament mascot facing the viewer, wearing a giant fuzzy tennis-ball costume: an enormous yellow-green foam sphere for a head and body with the white curved seam painted across it, stubby green sleeves and leggings, oversized cartoon shoes, white gloves. Through a dark mesh mouth-hole in the ball a rotting green face is just visible. He grips a tennis ball in one glove, facing the viewer. Draw THREE frames of that SAME boss, the same enormous size and the same costume, standing and facing the viewer, as three worsening stages of damage: (1) the arm on the left-hand side of the image is torn off at the shoulder leaving a ragged bloody stump \u2014 the OTHER arm is untouched and still holding what it held; (2) his chest and belly are torn wide open with the ribs showing through and the clothing hanging in bloody strips \u2014 one arm is still whole and still holding what it held; (3) his chest is torn open with ribs showing AND he has no head at all, the neck ending in a ragged red stump at shoulder height \u2014 one arm is still whole and still holding what it held. He must stay the same enormous size in all three \u2014 do not slim him down as the damage grows. Output exactly three figures in one row.",
+ "state": true
+ },
+ "boss_mascot_throw": {
+ "sprites": [
+ "z_mascott0",
+ "z_mascott1",
+ "z_mascott2",
+ "z_mascott3"
+ ],
+ "ref_only": [
+ "z_mascot1",
+ "z_mascotw1",
+ "z_mascotw2",
+ "z_mascotw3"
+ ],
+ "anchor": 0,
+ "check": "loose",
+ "subject": "The reference shows the SAME boss in four states: undamaged, then three worsening stages of damage. Draw all FOUR of those exact states again, in the same order, same size, same costume, same wounds \u2014 but each one caught MID-THROW: the whole body wound up and twisted, the surviving arm drawn back high over the shoulder about to hurl a tennis ball straight at the viewer, weight on the back foot. The wounds must stay clearly visible in every frame. Output exactly four figures in one row.",
+ "state": true
}
}
\ No newline at end of file
diff --git a/assets/z_coach1.png b/assets/z_coach1.png
new file mode 100644
index 0000000..e7e213f
Binary files /dev/null and b/assets/z_coach1.png differ
diff --git a/assets/z_coach2.png b/assets/z_coach2.png
new file mode 100644
index 0000000..7976260
Binary files /dev/null and b/assets/z_coach2.png differ
diff --git a/assets/z_coacht0.png b/assets/z_coacht0.png
new file mode 100644
index 0000000..3eed7c7
Binary files /dev/null and b/assets/z_coacht0.png differ
diff --git a/assets/z_coacht1.png b/assets/z_coacht1.png
new file mode 100644
index 0000000..7c87027
Binary files /dev/null and b/assets/z_coacht1.png differ
diff --git a/assets/z_coacht2.png b/assets/z_coacht2.png
new file mode 100644
index 0000000..0d6f169
Binary files /dev/null and b/assets/z_coacht2.png differ
diff --git a/assets/z_coacht3.png b/assets/z_coacht3.png
new file mode 100644
index 0000000..ae7f220
Binary files /dev/null and b/assets/z_coacht3.png differ
diff --git a/assets/z_coachw1.png b/assets/z_coachw1.png
new file mode 100644
index 0000000..c3accac
Binary files /dev/null and b/assets/z_coachw1.png differ
diff --git a/assets/z_coachw2.png b/assets/z_coachw2.png
new file mode 100644
index 0000000..3ee180f
Binary files /dev/null and b/assets/z_coachw2.png differ
diff --git a/assets/z_coachw3.png b/assets/z_coachw3.png
new file mode 100644
index 0000000..c303ac2
Binary files /dev/null and b/assets/z_coachw3.png differ
diff --git a/assets/z_e1.png b/assets/z_e1.png
new file mode 100644
index 0000000..1de4f50
Binary files /dev/null and b/assets/z_e1.png differ
diff --git a/assets/z_e2.png b/assets/z_e2.png
new file mode 100644
index 0000000..b1ffd12
Binary files /dev/null and b/assets/z_e2.png differ
diff --git a/assets/z_e3.png b/assets/z_e3.png
new file mode 100644
index 0000000..d815674
Binary files /dev/null and b/assets/z_e3.png differ
diff --git a/assets/z_e4.png b/assets/z_e4.png
new file mode 100644
index 0000000..604df8b
Binary files /dev/null and b/assets/z_e4.png differ
diff --git a/assets/z_ew.png b/assets/z_ew.png
new file mode 100644
index 0000000..2247e21
Binary files /dev/null and b/assets/z_ew.png differ
diff --git a/assets/z_ew2.png b/assets/z_ew2.png
new file mode 100644
index 0000000..51ba9d5
Binary files /dev/null and b/assets/z_ew2.png differ
diff --git a/assets/z_ew2b.png b/assets/z_ew2b.png
new file mode 100644
index 0000000..a5285bf
Binary files /dev/null and b/assets/z_ew2b.png differ
diff --git a/assets/z_ewb.png b/assets/z_ewb.png
new file mode 100644
index 0000000..ccca404
Binary files /dev/null and b/assets/z_ewb.png differ
diff --git a/assets/z_h1.png b/assets/z_h1.png
new file mode 100644
index 0000000..8fd384d
Binary files /dev/null and b/assets/z_h1.png differ
diff --git a/assets/z_h2.png b/assets/z_h2.png
new file mode 100644
index 0000000..2a6f450
Binary files /dev/null and b/assets/z_h2.png differ
diff --git a/assets/z_h3.png b/assets/z_h3.png
new file mode 100644
index 0000000..cb9b83d
Binary files /dev/null and b/assets/z_h3.png differ
diff --git a/assets/z_h4.png b/assets/z_h4.png
new file mode 100644
index 0000000..21369fe
Binary files /dev/null and b/assets/z_h4.png differ
diff --git a/assets/z_hw.png b/assets/z_hw.png
new file mode 100644
index 0000000..10c6e4f
Binary files /dev/null and b/assets/z_hw.png differ
diff --git a/assets/z_hw2.png b/assets/z_hw2.png
new file mode 100644
index 0000000..3145798
Binary files /dev/null and b/assets/z_hw2.png differ
diff --git a/assets/z_hw2b.png b/assets/z_hw2b.png
new file mode 100644
index 0000000..35ae6fd
Binary files /dev/null and b/assets/z_hw2b.png differ
diff --git a/assets/z_hw3.png b/assets/z_hw3.png
new file mode 100644
index 0000000..e7e29bf
Binary files /dev/null and b/assets/z_hw3.png differ
diff --git a/assets/z_hw3b.png b/assets/z_hw3b.png
new file mode 100644
index 0000000..d89530a
Binary files /dev/null and b/assets/z_hw3b.png differ
diff --git a/assets/z_hwb.png b/assets/z_hwb.png
new file mode 100644
index 0000000..cf44cd8
Binary files /dev/null and b/assets/z_hwb.png differ
diff --git a/assets/z_k1.png b/assets/z_k1.png
new file mode 100644
index 0000000..50e4dc5
Binary files /dev/null and b/assets/z_k1.png differ
diff --git a/assets/z_k2.png b/assets/z_k2.png
new file mode 100644
index 0000000..f6ca527
Binary files /dev/null and b/assets/z_k2.png differ
diff --git a/assets/z_k3.png b/assets/z_k3.png
new file mode 100644
index 0000000..e5ab3b4
Binary files /dev/null and b/assets/z_k3.png differ
diff --git a/assets/z_k4.png b/assets/z_k4.png
new file mode 100644
index 0000000..a4d3f55
Binary files /dev/null and b/assets/z_k4.png differ
diff --git a/assets/z_kw.png b/assets/z_kw.png
new file mode 100644
index 0000000..f20ff23
Binary files /dev/null and b/assets/z_kw.png differ
diff --git a/assets/z_kw2.png b/assets/z_kw2.png
new file mode 100644
index 0000000..aa8f3eb
Binary files /dev/null and b/assets/z_kw2.png differ
diff --git a/assets/z_kw2b.png b/assets/z_kw2b.png
new file mode 100644
index 0000000..d7db048
Binary files /dev/null and b/assets/z_kw2b.png differ
diff --git a/assets/z_kw3.png b/assets/z_kw3.png
new file mode 100644
index 0000000..2374279
Binary files /dev/null and b/assets/z_kw3.png differ
diff --git a/assets/z_kw3b.png b/assets/z_kw3b.png
new file mode 100644
index 0000000..93f639c
Binary files /dev/null and b/assets/z_kw3b.png differ
diff --git a/assets/z_kw4.png b/assets/z_kw4.png
new file mode 100644
index 0000000..79240f5
Binary files /dev/null and b/assets/z_kw4.png differ
diff --git a/assets/z_kw4b.png b/assets/z_kw4b.png
new file mode 100644
index 0000000..5266416
Binary files /dev/null and b/assets/z_kw4b.png differ
diff --git a/assets/z_kwb.png b/assets/z_kwb.png
new file mode 100644
index 0000000..79c48c5
Binary files /dev/null and b/assets/z_kwb.png differ
diff --git a/assets/z_mascot1.png b/assets/z_mascot1.png
new file mode 100644
index 0000000..8fb4450
Binary files /dev/null and b/assets/z_mascot1.png differ
diff --git a/assets/z_mascot2.png b/assets/z_mascot2.png
new file mode 100644
index 0000000..dd3b424
Binary files /dev/null and b/assets/z_mascot2.png differ
diff --git a/assets/z_mascott0.png b/assets/z_mascott0.png
new file mode 100644
index 0000000..f9d39ec
Binary files /dev/null and b/assets/z_mascott0.png differ
diff --git a/assets/z_mascott1.png b/assets/z_mascott1.png
new file mode 100644
index 0000000..b7487cf
Binary files /dev/null and b/assets/z_mascott1.png differ
diff --git a/assets/z_mascott2.png b/assets/z_mascott2.png
new file mode 100644
index 0000000..2530cdd
Binary files /dev/null and b/assets/z_mascott2.png differ
diff --git a/assets/z_mascott3.png b/assets/z_mascott3.png
new file mode 100644
index 0000000..c583c5b
Binary files /dev/null and b/assets/z_mascott3.png differ
diff --git a/assets/z_mascotw1.png b/assets/z_mascotw1.png
new file mode 100644
index 0000000..6682ad4
Binary files /dev/null and b/assets/z_mascotw1.png differ
diff --git a/assets/z_mascotw2.png b/assets/z_mascotw2.png
new file mode 100644
index 0000000..b6890e9
Binary files /dev/null and b/assets/z_mascotw2.png differ
diff --git a/assets/z_mascotw3.png b/assets/z_mascotw3.png
new file mode 100644
index 0000000..aac85b5
Binary files /dev/null and b/assets/z_mascotw3.png differ
diff --git a/assets/z_physio1.png b/assets/z_physio1.png
new file mode 100644
index 0000000..530451b
Binary files /dev/null and b/assets/z_physio1.png differ
diff --git a/assets/z_physio2.png b/assets/z_physio2.png
new file mode 100644
index 0000000..6c26177
Binary files /dev/null and b/assets/z_physio2.png differ
diff --git a/assets/z_physiot0.png b/assets/z_physiot0.png
new file mode 100644
index 0000000..e469168
Binary files /dev/null and b/assets/z_physiot0.png differ
diff --git a/assets/z_physiot1.png b/assets/z_physiot1.png
new file mode 100644
index 0000000..8cd6d98
Binary files /dev/null and b/assets/z_physiot1.png differ
diff --git a/assets/z_physiot2.png b/assets/z_physiot2.png
new file mode 100644
index 0000000..6c7c031
Binary files /dev/null and b/assets/z_physiot2.png differ
diff --git a/assets/z_physiot3.png b/assets/z_physiot3.png
new file mode 100644
index 0000000..03518f5
Binary files /dev/null and b/assets/z_physiot3.png differ
diff --git a/assets/z_physiow1.png b/assets/z_physiow1.png
new file mode 100644
index 0000000..c8e28b9
Binary files /dev/null and b/assets/z_physiow1.png differ
diff --git a/assets/z_physiow2.png b/assets/z_physiow2.png
new file mode 100644
index 0000000..2ea7a8b
Binary files /dev/null and b/assets/z_physiow2.png differ
diff --git a/assets/z_physiow3.png b/assets/z_physiow3.png
new file mode 100644
index 0000000..2ff8dc8
Binary files /dev/null and b/assets/z_physiow3.png differ
diff --git a/config.js b/config.js
index 28df04c..f1e5188 100644
--- a/config.js
+++ b/config.js
@@ -101,9 +101,11 @@ export const TUNE = {
// ---- chapter bosses: one every bossEvery games, escalating. The racket zombie is demoted to an
// occasional opponent (racketEvery) so the boss is the only thing that reads as a wall.
bossEvery: 10, // games between chapter bosses
- bossHp: 16, // hp of the FIRST one; each later chapter adds bossHpPerChapter. 10 was far too
- // soft — three banked flame balls took chapter 1 down (2 impact + 2.4 burn each)
- bossHpPerChapter: 9,
+ bossHp: 20, // hp of the FIRST one; each later chapter adds bossHpPerChapter. 10 was far too
+ // soft — three banked flame balls took chapter 1 down (2 impact + 2.4 burn each).
+ // 16 -> 20 alongside the movement below: a boss that holds its ground and dodges
+ // is only harder if it also lives long enough for the dodging to matter.
+ bossHpPerChapter: 10,
bossScale: 1.5, // body height multiplier at chapter 1 (a shambler is 1.8 m)
bossScalePer: 0.18, // ...growing per chapter, so chapter 4 towers
bossSpeed: 0.42, // m/s it advances — slow, deliberate, unstoppable
@@ -121,6 +123,20 @@ export const TUNE = {
bossSlowT: 1.2, // s of REAL time the world runs slow while a boss comes apart
bossSlowMo: 0.35, // ...and how slow
bossShotSpeed: 15.5, // m/s a boss throws at (the machine's is machSpeed 13)
+ // ---- boss MOVEMENT. A boss used to have no AI at all: it fell through to the crowd's aimless
+ // ±0.15 m/s wobble and crawled forward until it simply arrived on top of you. Each design now owns
+ // a movement style (BOSS_ART.move) and these are the shared numbers it spends.
+ bossStrafe: 1.7, // m/s sideways — bossSpeed above is only the FORWARD crawl
+ bossPaceX: 3.4, // m either side of the centre line a pacing boss turns at
+ bossShadowX: 1.7, // m either side of YOUR lane a 'shadow' boss turns at — its beat follows you
+ bossZMin: -1.0, // the band a boss holds. Without a clamp a fight ends by the boss walking into
+ bossZMax: 5.6, // you (~57 s at bossSpeed) rather than by you killing it — and every extra hp
+ // above just made that MORE likely, not less. Only 'charge' breaks the band.
+ bossChargeV: 4.2, // m/s the physio rushes at once it commits
+ bossChargeT: 1.5, // s a charge lasts before it withdraws
+ bossChargeRest: 2.2, // s of held ground between charges — the window you get to hit it
+ bossHopT: 0.55, // s per mascot hop (it moves in arcs, not in a slide)
+ bossHopX: 2.0, // m of ground one hop covers (the eased arc lands it at about 2/pi of this)
racketEvery: 3, // games between racket-zombie (plain opponent) spawns — was EVERY won game, which
// is why he read as "the boss" and why a real boss would not have stood out
asideEvery: 55, // s between the umpire's sarcastic asides in splatter (jittered +-40%). He never
@@ -223,6 +239,14 @@ export const TUNE = {
// as decoration; at 5 he is a brute's worth of hp on a faster, ordinary-looking
// walker, and he has a wound skin (z_dw) so the second half of that shows
hordeSteward: 0.2, // share of horde spawns that come out as stewards (from game 4)
+ // ---- the late roster. Three bodies that arrive as the run goes on, filling the gap between the
+ // 2 hp shamblers and the 5 hp brute and then extending past it. Each has one wound skin per hit it
+ // can still take, so the damage is readable without a bar (the rule the boss's missing bar set).
+ ballboyHp: 3, // the ball boy ('e') — a stock-sized body that takes three, with two wound skins
+ bouncerHp: 6, // the bouncer ('h') — 2.4 m, shoves through the crowd instead of shambling round it
+ rollerHp: 9, // the roller man ('k') — 3.2 m, hauling a court roller. The heaviest thing on court
+ rollerStride: 0.62, // m he covers per lurch frame — longer than the brute's 0.5: a bigger body,
+ // a longer stride, or his feet slide the way the brute's used to
// fire: the flame ball no longer just kills what it touches, it starts an epidemic.
burnTime: 3.0, // s a body burns before the fire goes out
burnDps: 2.0, // hp lost per second while burning — burnTime*burnDps must exceed bruteHp or tanks walk out of the fire
@@ -324,16 +348,66 @@ export const PLAYER_H = 1.88;
// throws: the same states mid-throw, used while windT > 0. Index is clamped, so a design with
// fewer throw frames than wound stages simply reuses its last one.
// Every name must be ALL LOWERCASE and a whole word — see the case-collision note in the pipeline.
+//
+// BEHAVIOUR is data too, and every field below is OPTIONAL — anything left out falls back to the
+// shared TUNE.boss* number, so a row with only art in it behaves exactly like a boss did before:
+// move 'pace' | 'mirror' | 'advance' | 'charge' | 'bounce' (spawnChapterBoss -> bossAI)
+// hpMul / speedMul scale this design's hp and its forward crawl
+// volley shots per attack · gap s between them (0 = the whole volley leaves in ONE frame,
+// which is a WALL to dodge rather than a rhythm to parry) · spread m of scatter
+// cycle s between attacks · wind s of telegraph · shot m/s it throws at
export const BOSS_ART = [
{ name: 'THE GROUNDSKEEPER', walk: ['z_boss1', 'z_boss2'],
wounds: ['z_bossw1', 'z_bossw2', 'z_bossw3'],
- throws: ['z_bosst0', 'z_bosst1', 'z_bosst2', 'z_bosst3'] },
+ throws: ['z_bosst0', 'z_bosst1', 'z_bosst2', 'z_bosst3'],
+ move: 'pace', speedMul: 0.7 },
{ name: 'THE LINE JUDGE', walk: ['z_judge1', 'z_judge2'],
- wounds: ['z_judgew'], throws: ['z_judget', 'z_judgewt'] },
+ wounds: ['z_judgew'], throws: ['z_judget', 'z_judgewt'],
+ move: 'shadow' }, // he walks the line, and the line is wherever you are
{ name: 'THE CHAIR UMPIRE', walk: ['z_chair1', 'z_chair2'],
- wounds: ['z_chairw'], throws: ['z_chairt', 'z_chairwt'] },
+ wounds: ['z_chairw'], throws: ['z_chairt', 'z_chairwt'],
+ move: 'surge', hpMul: 1.1 }, // the immovable one — he just keeps coming, and coming back
+ { name: 'THE HEAD COACH', walk: ['z_coach1', 'z_coach2'],
+ wounds: ['z_coachw1', 'z_coachw2', 'z_coachw3'],
+ throws: ['z_coacht0', 'z_coacht1', 'z_coacht2', 'z_coacht3'],
+ move: 'pace', paceMul: 1.9, hpMul: 0.9, // never where he was: drills you while running the line
+ volley: 5, gap: 0.14, cycle: 2.2, wind: 0.4, shot: 19 },
+ { name: 'THE PHYSIO', walk: ['z_physio1', 'z_physio2'],
+ wounds: ['z_physiow1', 'z_physiow2', 'z_physiow3'],
+ throws: ['z_physiot0', 'z_physiot1', 'z_physiot2', 'z_physiot3'],
+ move: 'charge', hpMul: 0.8, // fast and fragile: the BODY is the weapon, and contact
+ volley: 1, cycle: 3.4, wind: 0.35, shot: 21, spread: 0.15 }, // is still instant death
+ { name: 'THE SPONSOR MASCOT', walk: ['z_mascot1', 'z_mascot2'],
+ wounds: ['z_mascotw1', 'z_mascotw2', 'z_mascotw3'],
+ throws: ['z_mascott0', 'z_mascott1', 'z_mascott2', 'z_mascott3'],
+ move: 'bounce', hpMul: 1.3, speedMul: 0.6, // soaks, and answers with a WALL: five at once, so
+ volley: 5, gap: 0, spread: 1.6, cycle: 3.2, wind: 0.65, shot: 14 }, // there is nothing to parry
];
+// Which wound stage a body is in: 0 = unhurt, 1..n = the skins, spread evenly over its full health.
+// This is the rule the chapter boss already used, and it turns out to reproduce the brute's old
+// hardcoded thresholds EXACTLY (5 hp over 4 skins = one skin per hit) and the shamblers' single skin
+// too — so there is now one rule for damage readout instead of three, and it is the one that keeps
+// working when a body's hp is not one-per-skin. Every caller passes its own stage count.
+export const woundStage = (zb, n) => {
+ const f = zb.hp / (zb.hpMax || 1);
+ return f >= 1 ? 0 : Math.min(n, Math.max(1, Math.ceil((1 - f) * n)));
+};
+// Wound skins by variant, HEALTHIEST FIRST — the same ordering BOSS_ART.wounds uses, indexed by
+// woundStage above. This one table replaced two branches in drawZombie that did the same job against
+// different constants (the brute had a hardcoded stage array, everything else a single skin behind
+// `'abd'.includes(...)`), which is why a new multi-stage body is a row here and no code at all.
+// Each name's `…b` twin — the stride swapped — is looked up automatically, so a wounded body keeps
+// WALKING instead of freezing and sliding. 'r' and 'B' carry their skins elsewhere ('B' on its
+// design row) but read them through the very same woundStage call.
+export const ZWOUND = {
+ a: ['z_aw'], b: ['z_bw'], d: ['z_dw'],
+ e: ['z_ew', 'z_ew2'],
+ h: ['z_hw', 'z_hw2', 'z_hw3'],
+ g: ['z_gw', 'z_gw2', 'z_gw3', 'z_gw4'],
+ k: ['z_kw', 'z_kw2', 'z_kw3', 'z_kw4'],
+};
+
export const MACH_H = 1.55; // was 1.15 — waist high read as a prop you could ignore. Still furniture
// (a body is PLAYER_H 1.88), but big enough to be a thing on the court.
@@ -341,6 +415,7 @@ export const MACH_H = 1.55; // was 1.15 — waist high read as a prop you could
// fraction of it, so the two cannot drift — a shot at a brute has to arrive at the brute's chest,
// not at the height a shambler's chest happens to be.
export const zombieH = zb => zb.variant === 'g' ? 2.7 : zb.variant === 'd' ? 2.15
+ : zb.variant === 'k' ? 3.2 : zb.variant === 'h' ? 2.4
: zb.variant === 'r' ? 1.85 : zb.variant === 'm' ? MACH_H
: zb.variant === 'B' ? 1.8 * (zb.scale || 1.5) : 1.8;
// Where on that body the ball is aimed, and where the aim mark is drawn. Solving the shot to the
diff --git a/index.html b/index.html
index 55f9d45..42ed132 100644
--- a/index.html
+++ b/index.html
@@ -179,6 +179,6 @@
-
+