The dead club doubles its staff, and the draw is made where you can see it
Six new opponent families (head coach, stringer, strawberry seller, chair umpire, spectator, roller man) through the famroll/zsheet route, placed by hierarchy into an 18-rung ladder; the champion steps up behind them. THE DRAW: picking the tournament now rolls its own bracket in front of you — only the final was ever settled — and what lands is what walks on. Netplay, off Oliver's cellular report: the mid-flight early-decide was missing its !G.net gate, so the striker judged its own outbound flight and the two scoreboards drifted a point apart into a court nobody could serve on. The host's court now rides the room, its scoreboard rides the pretoss stream, and a called ball rolls to its own halt instead of freezing where the call caught it. Plus the night court, and somebody in the stand is taking pictures. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4c47295a73
commit
9cc3ac54b3
207 files changed
+632
-81
No files matched your search
+86
-25
@@ -233,6 +233,13 @@ export const TUNE = {
|
||||
asideEvery: 55, // s between the umpire's sarcastic asides in splatter (jittered +-40%). He never
|
||||
// acknowledged that the match had changed; now he comments on it, disapprovingly.
|
||||
racketHpPerGame: 0.12, // the racket zombie stiffens as the run goes on (floored into whole hp)
|
||||
// CAMERA FLASHES IN THE STAND (render.tickFlashes). MEAN seconds between them — the actual gap is
|
||||
// jittered 0.35x..1.95x so they never tick, and halved again while the crowd is cheering. Turn
|
||||
// flashEvery UP to quieten them; it is the one knob. Life is deliberately shorter than a frame
|
||||
// pair at 60 fps would need: a flash you can look AT is a light, a flash you only catch is a
|
||||
// photograph being taken.
|
||||
flashEvery: 5.0,
|
||||
flashLife: 0.16,
|
||||
// ---- damage. Bodies with hp (racket boss 'r', brute 'g') take damage in these units, so a big
|
||||
// boss is just a bigger hp number and every weapon stays proportional. Fractions are legal: they
|
||||
// bank on zb.chip and only whole points come off hp (the wound SKINS switch on hp thresholds).
|
||||
@@ -624,6 +631,24 @@ export const COURTS = [
|
||||
out: '#2c7a52', lawn: '#1f5c93', stripe: '#1f5c93' },
|
||||
splatter: { stands: '#141a26', tierA: '#1a2130', tierB: '#161d2a', board: '#3a1212',
|
||||
out: '#25452f', lawn: '#1c3e5c', stripe: '#1c3e5c' } },
|
||||
// THE NIGHT COURT (Oliver, 2026-08-15: "a doomy gloomy court, the lines are bright"). The only
|
||||
// court whose TENNIS palette is already darker than everything else's SPLATTER one — which is
|
||||
// the whole idea: it starts where the others end up, and the rot then takes it further still.
|
||||
// The lines need no new knob to be bright: paintBackdrop paints every line in UI.cream, so on a
|
||||
// near-black surface they read as chalk under floodlights on their own. The ball is UI.ball and
|
||||
// is bright for the same reason. It borrows the hard court's night-blue stands and boards rather
|
||||
// than growing two more sheets — the stands are meant to be a wall of dark up there.
|
||||
// `dim` is the one new knob: how far down the STAND is taken before the rot even starts (render's
|
||||
// standDim). Without it the borrowed hard-court crowd sits floodlit and blue above a near-black
|
||||
// court and the whole picture stops being a night. Nothing else in COURTS carries it, so it is 0
|
||||
// everywhere else and reads as absent.
|
||||
{ id: 'gloom', name: 'THE NIGHT COURT', sub: 'AFTER DARK · TRUE BOUNCE, NO LIGHT', tile: 'court_gloom',
|
||||
ads: 'x_ads_h', crowd: 'x_crowd_h', stripes: false, line: '#dff6ff', border: '#c8dbe6', dim: 0.62,
|
||||
bounceY: 0.58, bounceXZ: 0.80,
|
||||
tennis: { stands: '#0a0d15', tierA: '#0f131e', tierB: '#0c1019', board: '#131a2b',
|
||||
out: '#0f1a15', lawn: '#16261e', stripe: '#16261e' },
|
||||
splatter: { stands: '#06050b', tierA: '#0b0813', tierB: '#08060f', board: '#2c0e0e',
|
||||
out: '#0a120d', lawn: '#101c15', stripe: '#101c15' } },
|
||||
];
|
||||
|
||||
// The tour, picked on the same screen as the court. Only the two TENNIS PLAYERS and the turned CPU
|
||||
@@ -692,11 +717,15 @@ export const DIFFS = [
|
||||
// across the whole round instead of being spent in game one. Blood carries between rounds; the
|
||||
// trainer's heal-per-game is what makes the distance survivable. Deathmatch (id 'zombie') stays
|
||||
// beside it as the arcade run.
|
||||
// The bands widened with the ladder (12 rungs -> 18, 2026-08-15): the shape is unchanged — the
|
||||
// bottom third opens, the middle third is the quarter, everything up to management is the semi,
|
||||
// and the final is the honours board — but every round now draws from a deeper hat, which is
|
||||
// exactly what the bracket screen's shuffle is showing you.
|
||||
export const CUP_ROUNDS = [
|
||||
{ name: 'ROUND ONE', band: [1, 2, 3], need: 1 },
|
||||
{ name: 'QUARTER-FINAL', band: [4, 5, 6], need: 2 },
|
||||
{ name: 'SEMI-FINAL', band: [7, 8, 9, 10, 11], need: 2 },
|
||||
{ name: 'THE FINAL', band: [12], need: 2 },
|
||||
{ name: 'ROUND ONE', band: [1, 2, 3, 4, 5], need: 1 },
|
||||
{ name: 'QUARTER-FINAL', band: [6, 7, 8, 9, 10], need: 2 },
|
||||
{ name: 'SEMI-FINAL', band: [11, 12, 13, 14, 15, 16, 17], need: 2 },
|
||||
{ name: 'THE FINAL', band: [18], need: 2 },
|
||||
];
|
||||
|
||||
export const TOURS = [
|
||||
@@ -826,41 +855,62 @@ export const bossArt = ch => ch % 10 === 0 ? BOSS_ART[BOSS_ART.length - 1]
|
||||
// hierarchy the thing across the net used to be. That is the narrative, and the difficulty ramp is
|
||||
// the same list read as numbers — every dial climbs monotonically, so "start easy and get harder"
|
||||
// is a property of the table rather than something the code has to arrange.
|
||||
// 1-2 the ones who fetch and sweep. Slow, wild, forgiving.
|
||||
// 3-4 the ones who keep order. They start covering ground.
|
||||
// 5 the entertainment. The costume still fits.
|
||||
// 6-8 the ones who cover and judge. Width arrives: they begin painting lines.
|
||||
// 9-10 the ones who actually played. The club pro's own body, and the brute behind him.
|
||||
// 11-12 management, and then the man whose name is on the honours board.
|
||||
// 1-3 the ones who fetch, sweep and watch. Slow, wild, forgiving.
|
||||
// 4-6 the ground staff and the ones who keep order. They start covering ground.
|
||||
// 7-8 the entertainment and the legs.
|
||||
// 9-12 the trades and the officials. Width arrives: they begin painting lines.
|
||||
// 13-16 the ones who actually worked the players, and then played themselves.
|
||||
// 17-18 management, and then the man whose name is on the honours board.
|
||||
// EIGHTEEN RUNGS since 2026-08-15 (Oliver: expand the roster, "the champion can then be even a
|
||||
// little bit harder to beat"). The six new ones are interleaved rather than stacked on the end —
|
||||
// a ladder is a hierarchy, not an arrival order, so the spectator belongs at the bottom with the
|
||||
// ball boy and the head coach belongs up beside the club pro. Every new rung wears a family rolled
|
||||
// off the body it already has in the gore mode, same rule as the twelve before it.
|
||||
const OPPONENTS = [
|
||||
{ short: 'SHAMBLER', name: 'THE SHAMBLER', fam: 'sh',
|
||||
line: 'HE WAS HERE BEFORE ANY OF THIS.', speed: 0.72, pace: 0.72, wide: 0.75, flub: 1.35 },
|
||||
{ short: 'BALL BOY', name: 'THE BALL BOY', fam: 'bb',
|
||||
line: 'HE STILL FETCHES. HE JUST BITES NOW.', speed: 0.82, pace: 0.8, wide: 0.82, flub: 1.2 },
|
||||
line: 'HE STILL FETCHES. HE JUST BITES NOW.', speed: 0.80, pace: 0.78, wide: 0.82, flub: 1.25 },
|
||||
{ short: 'PUNTER', name: 'THE SPECTATOR', fam: 'sp',
|
||||
line: 'HE PAID FOR THE WHOLE SEAT.', speed: 0.86, pace: 0.82, wide: 0.86, flub: 1.18 },
|
||||
{ short: 'KEEPER', name: 'THE GROUNDSKEEPER', fam: 'gk',
|
||||
line: 'HE MOWED THIS LAWN FOR THIRTY YEARS.', speed: 0.9, pace: 0.88, wide: 0.9, flub: 1.05 },
|
||||
// 5: the heavy of the ground staff. Slow legs, but everything he touches comes back flat and hard
|
||||
{ short: 'ROLLER', name: 'THE ROLLER MAN', fam: 'rm', h: 1.95,
|
||||
line: 'HE FLATTENED THIS COURT. THEN THE REST.', speed: 0.88, pace: 0.98, wide: 0.92, flub: 1.0 },
|
||||
{ short: 'STEWARD', name: 'THE STEWARD', fam: 'st',
|
||||
line: 'HE IS ASKING YOU TO LEAVE.', speed: 0.98, pace: 0.95, wide: 1.0 },
|
||||
// 5-6: the entertainment, and then width arrives with the ones who judge
|
||||
line: 'HE IS ASKING YOU TO LEAVE.', speed: 0.98, pace: 0.95, wide: 1.0, flub: 0.98 },
|
||||
// 7-8: the entertainment, and then the legs
|
||||
{ short: 'MASCOT', name: 'THE MASCOT', fam: 'ma',
|
||||
line: 'THE CROWD STILL LOVES HIM.', speed: 1.02, pace: 0.98, wide: 1.05, flub: 1.0 },
|
||||
line: 'THE CROWD STILL LOVES HIM.', speed: 1.02, pace: 0.98, wide: 1.05, flub: 0.95 },
|
||||
{ short: 'RUNNER', name: 'THE RUNNER', fam: 'ru',
|
||||
line: 'HE COVERS THE COURT. ALL OF IT.', speed: 1.22, pace: 1.0, wide: 1.05, flub: 0.95 },
|
||||
line: 'HE COVERS THE COURT. ALL OF IT.', speed: 1.22, pace: 1.0, wide: 1.05, flub: 0.92 },
|
||||
// 9-12: the trades and the officials — width arrives here
|
||||
{ short: 'SELLER', name: 'THE STRAWBERRY SELLER', fam: 'sb',
|
||||
line: 'CREAM WITH THAT?', speed: 1.04, pace: 1.06, wide: 1.10, flub: 0.90 },
|
||||
{ short: 'STRINGER', name: 'THE STRINGER', fam: 'sg',
|
||||
line: 'HE STRUNG YOUR RACKET. HE KNOWS ITS LIMIT.', speed: 1.06, pace: 1.12, wide: 1.14, flub: 0.86 },
|
||||
{ short: 'JUDGE', name: 'THE LINE JUDGE', fam: 'lj',
|
||||
line: 'EVERY BALL OF YOURS IS OUT.', speed: 1.05, pace: 1.05, wide: 1.28, flub: 0.85 },
|
||||
line: 'EVERY BALL OF YOURS IS OUT.', speed: 1.05, pace: 1.08, wide: 1.28, flub: 0.84 },
|
||||
{ short: 'UMPIRE', name: 'THE CHAIR UMPIRE', fam: 'cu',
|
||||
line: 'HIS CALL WAS ALWAYS FINAL.', speed: 1.08, pace: 1.14, wide: 1.26, flub: 0.80 },
|
||||
{ short: 'PHYSIO', name: 'THE PHYSIO', fam: 'ph',
|
||||
line: 'HE KNOWS EXACTLY WHERE IT HURTS.', speed: 1.1, pace: 1.1, wide: 1.22, flub: 0.82 },
|
||||
line: 'HE KNOWS EXACTLY WHERE IT HURTS.', speed: 1.12, pace: 1.16, wide: 1.24, flub: 0.78 },
|
||||
{ short: 'COACH', name: 'THE HEAD COACH', fam: 'hc',
|
||||
line: 'HE TAUGHT YOU EVERY SHOT YOU HAVE.', speed: 1.20, pace: 1.20, wide: 1.22, flub: 0.72 },
|
||||
{ short: 'CLUB PRO', name: 'THE CLUB PRO',
|
||||
line: 'HE GAVE LESSONS ON THIS COURT.', speed: 1.12, pace: 1.15, wide: 1.15, flub: 0.8 },
|
||||
line: 'HE GAVE LESSONS ON THIS COURT.', speed: 1.16, pace: 1.22, wide: 1.20, flub: 0.70 },
|
||||
{ short: 'THE BULK', name: 'THE BULK', fam: 'br', h: 2.0,
|
||||
line: 'NOTHING GETS PAST HIM TWICE.', speed: 1.0, pace: 1.3, wide: 1.2, flub: 0.75 },
|
||||
line: 'NOTHING GETS PAST HIM TWICE.', speed: 1.02, pace: 1.34, wide: 1.24, flub: 0.66 },
|
||||
{ short: 'DIRECTOR', name: 'THE DIRECTOR', fam: 'dr',
|
||||
line: 'HE CAN HAVE YOU REMOVED.', speed: 1.22, pace: 1.22, wide: 1.25, flub: 0.65 },
|
||||
// 12: the man whose name is on the honours board. `h` is WORLD height (zombieH reads it, so the
|
||||
line: 'HE CAN HAVE YOU REMOVED.', speed: 1.26, pace: 1.28, wide: 1.28, flub: 0.60 },
|
||||
// 18: the man whose name is on the honours board. `h` is WORLD height (zombieH reads it, so the
|
||||
// serve aims at the body actually drawn): four family rolls could not make the art itself bulky
|
||||
// against the pose sheet — see _famroll.py's pair-reference note — so his size is stature.
|
||||
// Six new rungs stand between him and the start now, so he took the step up Oliver asked for:
|
||||
// he reads the court faster, hits harder, paints wider, and misses half as often as he did.
|
||||
{ short: 'CHAMPION', name: 'THE CHAMPION', fam: 'ch', h: 2.1,
|
||||
line: 'HE RETURNS EVERYTHING.', speed: 1.35, pace: 1.3, wide: 1.3, flub: 0.5 },
|
||||
line: 'HE RETURNS EVERYTHING.', speed: 1.45, pace: 1.42, wide: 1.36, flub: 0.38 },
|
||||
];
|
||||
|
||||
// Who opponent N is. This used to reuse the chapter roster's cycling (bossArt) so the two lists
|
||||
@@ -872,6 +922,10 @@ export const matchFoe = n => {
|
||||
const i = (n - 1) % OPPONENTS.length;
|
||||
return { key: 'L' + i, fam: 'r', ...OPPONENTS[i] };
|
||||
};
|
||||
// How many rungs there ARE, for everything that has to know the shape of the ladder rather than
|
||||
// one rung of it: the ramp test, and the bracket screen's shuffle. OPPONENTS stays private —
|
||||
// exporting the length is the whole of what anyone outside needs.
|
||||
export const LADDER = OPPONENTS.length;
|
||||
|
||||
// ---- THE SPRITE BENCH's stops (back room only, see beats.startBench). Each is either an ANIMATED
|
||||
// state or ONE NAMED FRAME, so frames the game cannot reach yet — the serve, the fall — can still be
|
||||
@@ -905,7 +959,8 @@ export const BENCH_STAGES = ['HEALTHY', 'ARM OFF', 'HEADLESS'];
|
||||
// that (15 to 48 away from z_br1, where a good frame scores under 8.5) and refuses them, which is
|
||||
// the gate working, not the gate being wrong. Re-registering it is one entry here; see the note at
|
||||
// the top of _master.py for the fix its next attempt needs.
|
||||
export const ZFAMS = ['r', 'br', 'sh', 'bb', 'st', 'ru', 'gk', 'lj', 'dr', 'ch', 'ph', 'ma'];
|
||||
export const ZFAMS = ['r', 'br', 'sh', 'bb', 'st', 'ru', 'gk', 'lj', 'dr', 'ch', 'ph', 'ma',
|
||||
'hc', 'sg', 'sb', 'cu', 'sp', 'rm'];
|
||||
|
||||
// HEAD CROPS for the opponent intro card — a straight source-pixel crop out of each family's own
|
||||
// healthy frame (z_<fam>1.png), so the card's icon is the body on court, never separate art.
|
||||
@@ -977,8 +1032,14 @@ for (let i = 0; i < BOSS_ART.length; i++) // the chapter bosses, named wher
|
||||
// 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', sub: OPPONENTS[i].line };
|
||||
// ...and it reads OPPONENTS, not BOSS_ART. It used to take the NAME off the boss roster and the
|
||||
// line off this one — which was the same answer only while the two lists were the same list. They
|
||||
// were decoupled 2026-08-13 and the screen has been mislabelling rungs ever since (L4 printed THE
|
||||
// PHYSIO over the mascot's line); at 18 rungs against 10 designs it would also have left the top
|
||||
// six with no row at all. One list, its own names, its own bodies.
|
||||
for (let i = 0; i < OPPONENTS.length; i++)
|
||||
ENEMIES['L' + i] = { name: OPPONENTS[i].name, sub: OPPONENTS[i].line,
|
||||
spr: 'z_' + (OPPONENTS[i].fam || 'r') + '1' };
|
||||
|
||||
// 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
|
||||
|
||||
Reference in new issue
Block a user