Stop calling three different things "the ladder"

Oliver: "the talk about ladder and rung is extremely confusing." It was, and it
was my vocabulary, not his reading. "Ladder" had come to mean three unrelated
things in this codebase — the sequence of opponents in zombie tennis, the
sequence of WOUND STAGES on one body, and the name of a stats screen — and
"rung" meant a position in whichever of those you happened to be reading about.

One word per thing now:

  the OPPONENTS   who you play, in order, one game each (config.OPPONENTS,
                  matchFoe(n), zb.foe). Never "rungs".
  WOUND STAGES    healthy -> arm off -> headless, on one body. Unchanged, and
                  now the only thing allowed to be called a progression.
  a FAMILY        the sprite set a body wears (config.ZFAMS).

Renames: LADDER_TRAITS -> OPPONENTS, matchRung -> matchFoe, zb.rung -> zb.foe,
and the stats screen THE LADDER -> OPPONENTS FELLED, which is what it lists.

The 'L0'..'L9' record keys are deliberately NOT renamed: they are written into
saved run records, and renaming them would orphan every match anyone has
played. A comment says so where they are built.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Opus 5 committed 2026-08-12 13:11:55 +02:00
1 parent e2487b784a
commit df454c5d92
6 files changed
+40 -37

No files matched your search

+22 -19
View File
@@ -667,22 +667,23 @@ export const BOSS_ART = [
export const bossArt = ch => ch % 10 === 0 ? BOSS_ART[BOSS_ART.length - 1]
: BOSS_ART[(ch - 1 - Math.floor(ch / 10)) % (BOSS_ART.length - 1)];
// ---- THE MATCH LADDER. Zombie tennis walks the SAME dead club staff onto the court as the gore
// mode's chapter bosses — the roster is the joke, and it is worth telling twice: in gore they come
// for you, here they play you at tennis. So rung N takes design N's NAME (bossArt, champion on
// ten) and a PERSONALITY, and the body stays the racket zombie's, which is the one drawn to play.
// ---- THE MATCH OPPONENTS. In zombie tennis you play a SEQUENCE of opponents, one game each:
// opponent 1, opponent 2, and so on. This is that list. It walks the SAME dead club staff onto the
// court as the gore mode's chapter bosses — the roster is the joke and it is worth telling twice:
// in gore they come for you, here they play you at tennis. So opponent N takes design N's NAME
// (bossArt, the champion on ten) and a PERSONALITY.
//
// A personality is DIALS, not new mechanics: every field below multiplies a number racketAI and the
// return shaping already read, so a rung is a data row and never a branch. All optional.
// return shaping already read, so an opponent is a data row and never a branch. All optional.
// pace scale on its returns' speed wide scale on how far off centre they may land
// flub scale on its whiff chance speed scale on how fast it covers the ground
// line the personality, said once, on the name plate as it walks on
// fam which SPRITE FAMILY this rung wears (config.ZFAMS). Absent = the club pro's body, 'r'.
// This is the payoff of _master.py --variant: generate an opponent, add its key to ZFAMS,
// name it on a rung, and that rung is a different body all the way down its wound ladder.
// fam which SPRITE FAMILY this opponent wears (config.ZFAMS). Absent = the club pro's body.
// This is the payoff of _master.py --variant: generate a body, add its key to ZFAMS,
// name it here, and that opponent walks on as it — through all three wound stages.
// short 8 characters, because that is the board's name slot — the match board names
// the thing you are playing instead of calling it THEM
const LADDER_TRAITS = [
const OPPONENTS = [
{ short: 'KEEPER', line: 'HE MOWED THIS LAWN FOR THIRTY YEARS.', speed: 0.85, pace: 0.9, wide: 0.85 },
{ short: 'JUDGE', line: 'EVERY BALL OF YOURS IS OUT.', wide: 1.25, flub: 0.8 },
{ short: 'UMPIRE', line: 'HE WILL NOT BE OVERRULED.', speed: 1.1, flub: 0.7 },
@@ -694,11 +695,11 @@ const LADDER_TRAITS = [
{ short: 'SELLER', line: 'WITH CREAM.', pace: 1.1, speed: 1.1, wide: 1.15 },
{ short: 'CHAMPION', line: 'HE RETURNS EVERYTHING.', pace: 1.25, speed: 1.3, wide: 1.25, flub: 0.5 },
];
// Rung N's identity. The chapter roster's own walk is reused wholesale (bossArt), so the champion
// lands on rung 10 exactly as he lands on chapter 10 and the two rosters can never drift apart.
export const matchRung = n => {
// Who opponent N is. The chapter roster's own order is reused wholesale (bossArt), so the champion
// lands on opponent 10 exactly as he lands on chapter 10 and the two rosters can never drift apart.
export const matchFoe = n => {
const art = bossArt(n), i = BOSS_ART.indexOf(art);
return { name: art.name, key: 'L' + i, fam: 'r', ...LADDER_TRAITS[i] };
return { name: art.name, key: 'L' + i, fam: 'r', ...OPPONENTS[i] };
};
// ---- THE SPRITE BENCH's stops (back room only, see beats.startBench). Each is either an ANIMATED
@@ -727,7 +728,9 @@ export const BENCH_STAGES = ['HEALTHY', 'ARM OFF', 'HEADLESS'];
// needs the names — the preloader, drawZombie's wound lookup, the sprite bench — derives them from
// this table rather than spelling them out. That is the whole point: 31 hand-written filenames in a
// preload list is 31 chances to typo one and have a body silently vanish for a frame.
export const ZFAMS = ['r'];
// 'br' is THE BRUTE, generated with _master.py --variant off the master sheets — the same creature
// the gore mode's 'g' shambler is, now playing tennis: twice the width, a racket lost in one fist.
export const ZFAMS = ['r', 'br'];
const POSES = ['bounce', 'toss', 'serve', 'fall1', 'fall2', 'fall3', 'rise'];
export const famFrames = f => [
`z_${f}1`, `z_${f}2`, `z_${f}3`, `z_${f}4`, // stance, shuffle A, shuffle B, forehand
@@ -765,12 +768,12 @@ export const ENEMIES = {
for (let i = 0; i < BOSS_ART.length; i++) // the chapter bosses, named where they already are
ENEMIES['B' + i] = { name: BOSS_ART[i].name, spr: BOSS_ART[i].walk[0], boss: true,
sub: i === BOSS_ART.length - 1 ? 'THE FINAL · EVERY TENTH CHAPTER' : 'CHAPTER BOSS' };
// ...and the same roster again as MATCH opponents ('L0'..), because that is who walks on in zombie
// tennis. Separate keys, not shared ones: the two are different fights with different bodies, they
// are counted on different screens now (the mode split in stats.js), and one row cannot mean both.
// ...and the same roster again as MATCH opponents, because that is who walks on in zombie tennis.
// Separate keys, not shared ones: the two are different fights with different bodies, they are
// counted on different screens (the mode split in stats.js), and one row cannot mean both. The 'L'
// prefix is kept as-is because it is written into saved run records — renaming it would orphan them.
for (let i = 0; i < BOSS_ART.length; i++)
ENEMIES['L' + i] = { name: BOSS_ART[i].name, spr: 'z_r1', ladder: true,
sub: LADDER_TRAITS[i].line };
ENEMIES['L' + i] = { name: BOSS_ART[i].name, spr: 'z_r1', sub: OPPONENTS[i].line };
// 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