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
+21
-19
@@ -1,7 +1,7 @@
|
||||
// Camera, viewport, sprites and all drawing. Owns the crowd dots (visual-only state).
|
||||
|
||||
import { cv, ctx } from './dom.js';
|
||||
import { BENCH, BENCH_STAGES, TUNE, UI, EATEN_BURST, EATEN_SUMMARY, EATEN_RESTART, DONE_RESTART, COURT, P_BOUNDS, PLAYER_H, BOY_H, UMP_H, UMP_X, MACH_H, BOSS_ART, ZWOUND, woundStage, BOUNCE_IDLE, SPRITE_H, zombieH , WEAPONS, RACK_ROW, TOSS } from './config.js';
|
||||
import { BENCH, BENCH_STAGES, ZFAMS, famFrames, TUNE, UI, EATEN_BURST, EATEN_SUMMARY, EATEN_RESTART, DONE_RESTART, COURT, P_BOUNDS, PLAYER_H, BOY_H, UMP_H, UMP_X, MACH_H, BOSS_ART, ZWOUND, woundStage, BOUNCE_IDLE, SPRITE_H, zombieH , WEAPONS, RACK_ROW, TOSS } from './config.js';
|
||||
import { P, O, ball, score, crowd, G, court, tour } from './state.js';
|
||||
import { predictLanding, previewServe, slotId, slotStock, serveApexY } from './gameplay.js';
|
||||
import { drillQueue, drillLegal, drillMult } from './drill.js';
|
||||
@@ -195,7 +195,7 @@ export function proj(x, y, z) {
|
||||
// Asset cache-buster — bump with main.js?v=N (see the img.src note below). EXPORTED because the
|
||||
// menu screens hang <img> tags on the same files, and two copies of this number is one copy too
|
||||
// many: the one that does not get bumped is the one that serves a cached 404.
|
||||
export const SPRV = 100;
|
||||
export const SPRV = 101;
|
||||
// The boot bar in main.js watches this. Nothing here CHANGES when things load — every sprite was
|
||||
// already requested at import — it just makes the wait visible and holds the menu back until the
|
||||
// last sheet has landed. See the onerror note below for the other half of "sometimes missing".
|
||||
@@ -241,17 +241,11 @@ for (const n of ['p_idle1','p_idle2','p_bounce','p_bounce2','p_toss','p_smash','
|
||||
// THE MATCH OPPONENT, drawn to play tennis rather than to shamble: ready stance,
|
||||
// shuffle pair, forehand — and the same three poses again at each wound stage
|
||||
// (…s is the swing). Its own family, so the turned CPU above keeps its shamble.
|
||||
'z_r1','z_r2','z_r3','z_r4',
|
||||
'z_rw1','z_rw1b','z_rw1s','z_rw2','z_rw2b','z_rw2s',
|
||||
// ...and the states the MODE still needs before it is a real tennis match: the
|
||||
// serve (slice 7, alternating service) and the fall between points (slice 4).
|
||||
// Loaded now so the sprite bench can show them the day they are drawn.
|
||||
'z_rbounce','z_rtoss','z_rserve','z_rfall1','z_rfall2','z_rfall3','z_rrise',
|
||||
// ...and the same seven at each wound stage: a one-armed opponent still has to serve
|
||||
// and still has to go down, and reverting to the healthy body to do it would undo the
|
||||
// damage the score just did
|
||||
'z_rw1bounce','z_rw1toss','z_rw1serve','z_rw1fall1','z_rw1fall2','z_rw1fall3','z_rw1rise',
|
||||
'z_rw2bounce','z_rw2toss','z_rw2serve','z_rw2fall1','z_rw2fall2','z_rw2fall3','z_rw2rise',
|
||||
// EVERY MATCH-OPPONENT FAMILY, derived from config.ZFAMS — 31 names each, so a new
|
||||
// opponent generated by _master.py --variant is ONE row in that table and not 31
|
||||
// filenames typed out here (each of which is a chance to typo one and have a body
|
||||
// vanish for a frame — the oldest bug in this file).
|
||||
...ZFAMS.flatMap(famFrames),
|
||||
'w_ball','w_bomb','w_triple','w_fire','w_gun', // weapon-rack icons
|
||||
'w_beacon','w_laser','w_rocket','w_flamer', // ...and the four that come out of a barrel
|
||||
'z_barrel1','z_barrel2', // the weapon barrel, two frames so it ROLLS
|
||||
@@ -582,8 +576,13 @@ function drawZombie(zb) {
|
||||
// tennis points against you. The 'r' family is drawn off the LIVING opponent's poses instead (a
|
||||
// ready stance, a shuffle pair and a real forehand, at every wound stage). 'c' keeps its shamble
|
||||
// untouched, because in the gore mode shambling is exactly what the turned CPU should be doing.
|
||||
const v = rk ? (SPR.z_r1 ? 'r' : 'c') : zb.variant; // 'c' until the new sheets have landed
|
||||
const rNew = rk && v === 'r';
|
||||
// WHICH FAMILY THIS BODY WEARS. `fam` rides on the zombie (spawnRacketZombie leaves it unset, so
|
||||
// the club pro is 'r'), which is what lets a generated variant be a different opponent rather than
|
||||
// a different game: every name below is built from it. Falls back to the turned CPU's shamble
|
||||
// while a family's sheets have not landed.
|
||||
const fam = zb.fam || 'r';
|
||||
const v = rk ? (SPR['z_' + fam + '1'] ? fam : 'c') : zb.variant;
|
||||
const rNew = rk && v !== 'c';
|
||||
let N = ZFRAMES[v] || 2;
|
||||
if (!SPR['z_' + v + N]) N = 2; // frames still loading (or sheet rejected): 2-frame fallback
|
||||
// same CYCLE frequency as before — more frames subdivide the stride, they don't speed it up
|
||||
@@ -643,7 +642,7 @@ function drawZombie(zb) {
|
||||
// ...stepped off GROUND COVERED, the same clock the healthy cycle two blocks up uses. It was
|
||||
// walkT — a wall clock — so a wounded opponent's legs cycled at a fixed cadence however fast
|
||||
// it was actually moving, which is the foot-slide this project banned for the heavy bodies.
|
||||
const base = 'z_rw' + st;
|
||||
const base = 'z_' + fam + 'w' + st;
|
||||
const want = zb.pose === 'swing' ? base + 's'
|
||||
: zb.pose === 'run' && SPR[base + 'b'] && stepFrame((zb.dist || 0) / TUNE.racketStride, 2) ? base + 'b'
|
||||
: base;
|
||||
@@ -695,7 +694,7 @@ function drawZombie(zb) {
|
||||
if (zb.showFrame) {
|
||||
// a wound stage that has not been drawn yet degrades to the HEALTHY frame rather than to the
|
||||
// walk cycle — the bench prints what it actually drew, so the substitution is visible
|
||||
const heal = zb.showFrame.replace(/^z_rw\d/, 'z_r');
|
||||
const heal = zb.showFrame.replace(new RegExp('^z_' + fam + 'w\\d'), 'z_' + fam);
|
||||
if (SPR[zb.showFrame]) name = zb.showFrame;
|
||||
else if (SPR[heal]) name = heal;
|
||||
}
|
||||
@@ -793,7 +792,8 @@ function drawBenchHud() {
|
||||
const y = CARD_TOP + 4;
|
||||
ctx.textAlign = 'center';
|
||||
ctx.font = B(20);
|
||||
shadowText(BENCH[b.i].name, W / 2, y, UI.cream);
|
||||
shadowText((ZFAMS.length > 1 ? ZFAMS[b.fam].toUpperCase() + ' · ' : '') + BENCH[b.i].name,
|
||||
W / 2, y, UI.cream);
|
||||
ctx.font = F(8);
|
||||
// ...and say so when the stop has no wound variant. The serve and the fall exist for the HEALTHY
|
||||
// body only so far, so a stage label there would be claiming art that has not been drawn — and the
|
||||
@@ -809,7 +809,9 @@ function drawBenchHud() {
|
||||
shadowText(zb ? zb.drawn || '—' : 'NO BODY', W / 2, y + 40, UI.pick);
|
||||
if (zb) shadowText('hp ' + zb.hp + '/' + zb.hpMax, W / 2, y + 54, UI.dim);
|
||||
ctx.font = F(7, 0);
|
||||
shadowText(IS_TOUCH ? 'TAP: NEXT STATE' : 'SPACE: NEXT STATE ←/→: WOUND STAGE', W / 2, H - 26, UI.dim);
|
||||
shadowText(IS_TOUCH ? 'TAP: NEXT STATE'
|
||||
: 'SPACE: NEXT STATE ←/→: WOUND STAGE' + (ZFAMS.length > 1 ? ' ↑/↓: OPPONENT' : ''),
|
||||
W / 2, H - 26, UI.dim);
|
||||
}
|
||||
|
||||
function drawDrillHud() {
|
||||
|
||||
Reference in new issue
Block a user