Trade the tutorial for a screen that counts the dead

The menus were placeholders next to the art. Three screens now: a title over
an empty, dimmed, blood-stained court; a MATCH SETUP screen where each court
wears meters read straight off its own bounce numbers; and BODY COUNT, which
needed the game to start measuring anything at all.

stats.js keeps one capped array of per-run records — all time, last match and
seven days are three filters over it rather than three counters that can drift
apart. Kills file under the body AND the weapon: killZombie only ever knew an
index, so every damage loop stamps the cause on its way past.

The tutorial is gone. Marvin explained what the in-game modals already teach,
and his stage is where the stats live now.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Opus 5 committed 2026-08-04 16:01:59 +02:00
1 parent 411bff8724
commit f3b0b791d6
21 files changed
+619 -210

No files matched your search

+27
View File
@@ -473,6 +473,29 @@ export const BOSS_ART = [
volley: 5, gap: 0, spread: 1.6, cycle: 3.2, wind: 0.65, shot: 14 }, // there is nothing to parry
];
// The roster, as the BODY COUNT screen reads it. Until now a variant's NAME lived only in the
// comment above its hp value — fine while nothing ever had to print one. The key is the `variant`
// char the zombie already carries, except that 'a' and 'b' fold together (they are one shambler in
// two shirts, and two identical rows on the stats list would just look like a bug) and a chapter
// boss files under 'B0'..'B5' by BOSS_ART index, so the screen can name the design that got you.
// `spr` is the frame the row draws — the first walk frame, which every body has.
// The barrel ('o') is deliberately absent: it is loot, not an opponent, and killZombie scores it
// as nothing. Keep that agreement — a barrel row would put a crate in a list of the dead.
export const ENEMIES = {
a: { name: 'SHAMBLER', spr: 'z_a1', sub: 'THE ORDINARY DEAD' },
d: { name: 'THE STEWARD', spr: 'z_d1', sub: 'HI-VIS · ONE HIT TOUGHER' },
e: { name: 'THE BALL BOY', spr: 'z_e1', sub: 'STILL FETCHING' },
f: { name: 'THE RUNNER', spr: 'z_f1', sub: 'ONE HIT · BUT HE SPRINTS' },
g: { name: 'THE BRUTE', spr: 'z_g1', sub: 'SLOW · DRAGS A POLE' },
h: { name: 'THE BOUNCER', spr: 'z_h1', sub: 'BUILT LIKE A DOOR' },
k: { name: 'THE ROLLER MAN', spr: 'z_k1', sub: 'HAULS THE COURT ROLLER' },
s: { name: 'THE SPECTATOR', spr: 'z_s1', sub: 'BLOCKS WITH HIS CHAIR' },
r: { name: 'THE CLUB PRO', spr: 'z_c1', sub: 'STILL HAS A BACKHAND' },
m: { name: 'THE BALL MACHINE', spr: 'z_mach1', sub: 'FIRES BACK · FURNITURE' },
};
for (let i = 0; i < BOSS_ART.length; i++) // the six chapter bosses, named where they already are
ENEMIES['B' + i] = { name: BOSS_ART[i].name, spr: BOSS_ART[i].walk[0], sub: 'CHAPTER BOSS', boss: true };
// 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
@@ -551,6 +574,10 @@ export const WEAPONS = {
col: '#ffd050', glow: 1.8,
desc: ['IT LAUNCHES ITSELF', 'AND EVERY ONE IS A BOMB'] },
};
// Every row knows its own key. The rack has always looked weapons up BY key and passed the row
// around after that, so nothing needed the id — the stats screen does, because a kill has to be
// filed under the weapon that made it long after the row itself is out of scope.
for (const k in WEAPONS) WEAPONS[k].id = k;
// What a pink barrel can be carrying — the four above, which you cannot win any other way. NOT a
// flat 1-in-4: on that, Oliver "got the laser almost every time" and never once saw the beacon.
// Weighted, gated, and it never hands you the same thing twice running (see pickBarrelWeapon).