A match opponent is a FAMILY now, so a generated variant is one row and not dead art
--variant produces 31 frames; nothing could draw them. drawZombie hardcoded the `z_r` prefix, the preloader spelled all 31 filenames out by hand, and the sprite bench built its names from the same literal. A new zombie would have sat on disk. config.ZFAMS = ['r'] the families that exist config.famFrames(f) the 31 names derived from a key Everything that needs those names now derives them: the preloader spreads ZFAMS.flatMap(famFrames), drawZombie reads `zb.fam` (unset = 'r', so the club pro is untouched) and builds its wound lookups off it, and the bench walks families on up/down. Adding an opponent is ONE ROW — which also retires 31 hand-typed filenames from the preload list, each of which was a chance to typo one and have a body silently vanish for a frame. That is the oldest bug in render.js and it is now impossible in this corner of it. And the link that makes it worth anything: a LADDER RUNG can name a family. `fam: 'br'` on a row in LADDER_TRAITS and that rung walks on as a different body, all the way down its own wound ladder. So the whole path is now: _master.py --variant br "…" -> generate 31 frames ZFAMS += 'br' -> the game loads them fam: 'br' on a ladder row -> that opponent is in the match Verified: the bench still walks all 33 cells pulling the right derived name at every wound stage, and a real match still opens on THE GROUNDSKEEPER wearing family 'r' and drawing z_r1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4bec0cb09c
commit
e2487b784a
4 files changed
+52
-24
No files matched your search
+9
-3
@@ -7,7 +7,7 @@
|
||||
// (beats <-> gameplay and beats <-> zombies are runtime-only import cycles, same pattern as
|
||||
// input <-> gameplay: the imported bindings are only touched inside functions, never at load.)
|
||||
|
||||
import { TUNE, UI, bossArt, matchRung, BENCH, COURT, HOME } from './config.js';
|
||||
import { TUNE, UI, bossArt, matchRung, BENCH, ZFAMS, COURT, HOME } from './config.js';
|
||||
import { G, P, O, ball, score, setupPoint, sendBoy } from './state.js';
|
||||
import { voice, zombieDuck, aww } from './audio.js';
|
||||
import { zombies, pellets, extras, shots, spawnRacketZombie, spawnChapterBoss, spawnZombie, explode, killPlayer } from './zombies.js';
|
||||
@@ -171,7 +171,7 @@ const BENCH_HP = 12; // a deep pool so a wound stage is se
|
||||
|
||||
export function startBench() {
|
||||
if (G.interlude || G.horde || G.bossFight || G.drill || G.match || G.bench) return false;
|
||||
G.bench = { i: 0, stage: 0, dir: 1 };
|
||||
G.bench = { i: 0, stage: 0, fam: 0, dir: 1 };
|
||||
zombies.length = 0; pellets.length = 0; extras.length = 0; shots.length = 0;
|
||||
// ...and DISARM the ordinary schedule, exactly as the drill does: a pending horde or an owed
|
||||
// chapter is a latch that keeps trying to fire behind a mode that has no bodies for it.
|
||||
@@ -203,8 +203,10 @@ function benchApply() {
|
||||
// ...and the named states take the wound stage with them: z_r<suffix> at full health,
|
||||
// z_rw1<suffix> / z_rw2<suffix> below it. render falls back to the healthy frame if a stage has
|
||||
// not been drawn yet, and the HUD says so — a bench must never imply art that does not exist.
|
||||
const f = ZFAMS[G.bench.fam] || 'r';
|
||||
zb.fam = f;
|
||||
const suf = BENCH[G.bench.i].spr;
|
||||
zb.showFrame = suf ? (G.bench.stage ? 'z_rw' + G.bench.stage : 'z_r') + suf : null;
|
||||
zb.showFrame = suf ? 'z_' + f + (G.bench.stage ? 'w' + G.bench.stage : '') + suf : null;
|
||||
}
|
||||
|
||||
// the bench's own controls, taken before anything else reads the key: SPACE / a tap steps to the
|
||||
@@ -214,6 +216,9 @@ export function benchKey(code) {
|
||||
const b = G.bench;
|
||||
if (code === 'ArrowLeft' || code === 'KeyA') b.stage = (b.stage + 2) % 3;
|
||||
else if (code === 'ArrowRight' || code === 'KeyD') b.stage = (b.stage + 1) % 3;
|
||||
// up/down walks the OPPONENT FAMILIES — one today, one row in config.ZFAMS per variant after that
|
||||
else if (code === 'ArrowUp' || code === 'KeyW') b.fam = (b.fam + ZFAMS.length - 1) % ZFAMS.length;
|
||||
else if (code === 'ArrowDown' || code === 'KeyS') b.fam = (b.fam + 1) % ZFAMS.length;
|
||||
else b.i = (b.i + 1) % BENCH.length;
|
||||
benchApply();
|
||||
}
|
||||
@@ -343,6 +348,7 @@ function matchOpponent() {
|
||||
// pure data, read where the dials are already read (see matchRung in config). It rides on the
|
||||
// BODY so that killZombie can file the kill under it without asking G what rung we were on.
|
||||
zb.rung = matchRung(G.match.opp);
|
||||
zb.fam = zb.rung.fam; // ...and the body that rung wears (config.ZFAMS)
|
||||
G.card = { title: zb.rung.name, sub1: zb.rung.line, color: UI.hot, t: 2.4 };
|
||||
zb.hp = zb.hpMax = TUNE.matchOppHp; // a FLAT pool: with regicide gone the body is pure
|
||||
// scoreboard (a quarter per point), so a deeper pool would
|
||||
|
||||
Reference in new issue
Block a user