Gore opens in the gore, the theme comes back, and every mode gets a board
Splatter gore no longer makes you win a game of tennis first — the mode you pick is the mode you get, with the man you came to play already turned. The transition itself is untouched and still reachable; it just isn't a toll booth. The menu theme could only ever be started, never brought back, so the first run of a session silenced it for good while the court's ambience followed you into the menu. Starting a bed now un-ducks it too. Three world boards instead of one: kills, opponents felled, and games taken off the CPU, each with its own name prompt and its own row per player. ...and the gore economy stops paying a barrel for every game won past game 16, while the shamblers, the mix and the ball machines keep escalating past the point they all used to stop. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9cc3ac54b3
commit
3cf3e029a4
11 files changed
+440
-88
No files matched your search
@@ -84,7 +84,7 @@ globalThis.getComputedStyle = () => ({ paddingTop: '0' });
|
||||
// ---- game modules (real code) ----------------------------------------------
|
||||
const state = await import('./src/state.js');
|
||||
const { G, P, O, score, ball, setupPoint } = state;
|
||||
const { update, forceZombies, splatPoint, restartRun, flickTarget, keyPressed, classicGame, serveApexY, assistK, rallyFeel } = await import('./src/gameplay.js');
|
||||
const { update, forceZombies, startGore, splatPoint, restartRun, flickTarget, keyPressed, classicGame, serveApexY, assistK, rallyFeel, barrelGap, machCap } = await import('./src/gameplay.js');
|
||||
const { startDuel, resolveInterlude, startBossFight, endBossFight, startDrill: _startDrill, endDrill, startMatch: _startMatch, endMatch, FALL_LEN, startNetVs, endNetVs } = await import('./src/beats.js');
|
||||
const { mirror, netMsg, netTest, netTick } = await import('./src/net.js');
|
||||
// the drill's first-sight modal freezes the world (that is what modals do) — dismiss it the way a
|
||||
@@ -95,7 +95,9 @@ const { killPlayer } = await import('./src/zombies.js');
|
||||
const { drillQueue, drillLegal, arrival, drillMult, DRILL_TYPES } = await import('./src/drill.js');
|
||||
const cfg = await import('./src/config.js');
|
||||
const { TUNE, bossArt, COURT, racketStage } = cfg;
|
||||
const { read: readStats, check: statsCheck } = await import('./src/stats.js');
|
||||
const statsMod = await import('./src/stats.js');
|
||||
const audio = await import('./src/audio.js');
|
||||
const { read: readStats, check: statsCheck } = statsMod;
|
||||
|
||||
const DT = 1 / 60;
|
||||
function freshSplatter() {
|
||||
@@ -849,6 +851,66 @@ test('an ordinary far winner keeps its ordinary ending, and the match plays on (
|
||||
assert.equal(score.pts[0], pts + 2, 'the match plays on');
|
||||
});
|
||||
|
||||
test('the menus own the theme, and hand the court back its silence', () => {
|
||||
// Oliver, 2026-08-15: "after some games — or returning to the main menu via the close button —
|
||||
// the background music does not play any more, but you sometimes still hear the court background
|
||||
// noise." loopBed no-ops once a file is playing, so menuMusic() could only ever START the theme,
|
||||
// never bring it BACK from the duck start() put it under — and nothing ducked the court's own
|
||||
// beds at all. The menu was silent and noisy at once, permanently, from the first run onward.
|
||||
const d = audio.bedTest.ducked, THEME = 'background-music.m4a',
|
||||
AMB = 'tennis-ambient-loop.wav', ZOM = 'zombie-mode.m4a';
|
||||
audio.menuMusic(); // on the menu
|
||||
assert.ok(!d.has(THEME), 'the theme plays on the menu');
|
||||
audio.menuDuck(true); audio.ambient(); audio.zombieMusic(); // ...a run starts
|
||||
assert.ok(d.has(THEME), 'the theme hands the court over');
|
||||
assert.ok(!d.has(AMB) && !d.has(ZOM), 'and the court gets its own beds');
|
||||
audio.menuMusic(); // ...and the run ends, however it ended
|
||||
assert.ok(!d.has(THEME), 'the theme is BACK — this is the bug');
|
||||
assert.ok(d.has(AMB) && d.has(ZOM), '...and the court does not follow you into the menu');
|
||||
audio.ambient(); audio.zombieMusic(); // ...and the NEXT run is not played in silence
|
||||
assert.ok(!d.has(AMB) && !d.has(ZOM), 'a second run sounds like the first');
|
||||
audio.menuMusic();
|
||||
});
|
||||
|
||||
test('SPLATTER GORE opens in the gore — no tennis prologue to win first', () => {
|
||||
// Oliver, 2026-08-15: "it should not have the normal tennis intro anymore, we should just start
|
||||
// right in splatter mode." The transition is not deleted (forceZombies still plays it) — what is
|
||||
// gone is it standing between the menu and the mode.
|
||||
restartRun();
|
||||
assert.equal(G.mode, 'tennis', 'a fresh run starts where every run starts');
|
||||
startGore();
|
||||
assert.equal(G.mode, 'splatter', 'the mode you picked is the mode you get');
|
||||
assert.ok(G.everTransitioned, '...and the prologue can never fire behind it');
|
||||
assert.ok(zombies.some(z => z.variant === 'c'), 'the man you came to play is already one of them');
|
||||
assert.ok(G.hideO, 'so the tennis opponent is gone from the far court');
|
||||
assert.equal(score.server, 'P', 'and the serve is yours');
|
||||
startGore();
|
||||
assert.equal(zombies.filter(z => z.variant === 'c').length, 1, 'a second call is a no-op, not a second corpse');
|
||||
});
|
||||
|
||||
test('the gore economy: barrels stop being income, and the late court keeps getting worse', () => {
|
||||
// "in splatter gore it now gets too easy to just play through... maybe we should reduce the
|
||||
// barrels again — pretty early in the game every won game gives a barrel." At 3/8 the gap hit ONE
|
||||
// at game 16 and stayed, so the back half of a run paid a weapon per game won. The floor is the
|
||||
// fix, and it is the property worth pinning: a barrel is a reward, never an income.
|
||||
const gapAt = g => { G.runGames = g; return barrelGap(); };
|
||||
for (const g of [0, 5, 12, 20, 40, 120])
|
||||
assert.ok(gapAt(g) >= 2, `game ${g}: a barrel is never one-per-game (gap ${gapAt(g)})`);
|
||||
assert.ok(gapAt(40) <= gapAt(0), 'the late game still earns them faster than the early one');
|
||||
assert.equal(gapAt(400), TUNE.barrelFloor, '...down to the floor and no further');
|
||||
// ...and the pressure keeps climbing where the barrels stop
|
||||
const capAt = g => { G.runGames = g; return machCap(); };
|
||||
assert.equal(capAt(0), TUNE.machMax, 'game one gets the old two guns');
|
||||
assert.ok(capAt(40) > capAt(0), 'a long run faces more of them');
|
||||
assert.equal(capAt(400), TUNE.machMaxCap, '...to a ceiling, because the court still has to read');
|
||||
// the two ramps that used to stop dead around game 25 now run past it
|
||||
assert.ok(1 + Math.min(TUNE.zSpeedMax, 30 * TUNE.zSpeedPerGame)
|
||||
> 1 + Math.min(TUNE.zSpeedMax, 22 * TUNE.zSpeedPerGame), 'shamblers keep gaining speed past game 22');
|
||||
assert.equal(Math.min(TUNE.nastyMax, 40 * TUNE.nastyPerGame), 1,
|
||||
'and by the end every body that walks out is off the advanced roster');
|
||||
G.runGames = 0;
|
||||
});
|
||||
|
||||
test('THE TOURNAMENT: bands draw, bodies deepen, rounds hold their body, the champion ends it', () => {
|
||||
const { CUP_ROUNDS } = cfg;
|
||||
_startMatch(true); G.modal = null; G.modalQ.length = 0;
|
||||
@@ -1148,7 +1210,7 @@ test('the toss strike has stakes: flush is fast, a miss is slow, the old free wi
|
||||
|
||||
test('classic tennis: the second set ends the run, with no corpse', () => {
|
||||
G.mode = 'tennis'; G.classic = true;
|
||||
G.sets = [1, 0]; score.games = [6, 0]; G.runGames = 14;
|
||||
G.sets = [1, 0]; score.games = [6, 0]; G.runGames = 14; G.classicWon = 12;
|
||||
assert.equal(classicGame('P'), true, 'the match is over');
|
||||
assert.ok(G.done && G.done.won, 'and you won it');
|
||||
assert.notEqual(G.mode, 'eaten', 'nobody died — this is the other ending');
|
||||
@@ -1156,7 +1218,10 @@ test('classic tennis: the second set ends the run, with no corpse', () => {
|
||||
const r = runs[runs.length - 1];
|
||||
assert.equal(r.m, 'c', 'banked as a classic run');
|
||||
assert.equal(r.dead, 0, 'a match WON is not a death — that is how wins are counted');
|
||||
assert.equal(r.s, 2, 'sets won ride in the second headline slot');
|
||||
// GAMES YOU WON, not sets: it is what classic's world board ranks, because every win is two sets
|
||||
// and a board of nothing but 2s is a wall of ties. Nothing ever displayed the old value.
|
||||
assert.equal(r.s, 12, 'games won ride in the second headline slot');
|
||||
assert.equal(statsMod.BOARDS.c.score(r), 12, "...and that is the classic board's number");
|
||||
});
|
||||
|
||||
test('classic tennis: losing the match ends it the same way, and counts as a loss', () => {
|
||||
|
||||
Reference in new issue
Block a user