Story after Oliver's first test: metal, a straight prologue, 2x3 chapters

- The gore bed is "Dystopian Survival", made loopable: the last ten
  seconds cross-faded into the first ten, 203.5 s, as MP3 at 128k (the
  decoder strips the encoder's delay, so the seam has no gap). High-pass
  at 40 Hz instead of 800, level matched to the menu theme.
- The prologue no longer drops into gore for a moment: after the turn,
  act I's card and cup take over directly.
- RESET STORY under the build stamp (two taps; test only). It resets
  the story, league, cups and title, keeps the level, and pushes at once
  so the mirror cannot hand the old act back.
- The story screens have sound: a whoosh and a boom for the act card,
  applause and the rack's own click as each weapon lands, applause and
  a pock per arcade door on the act's end.
- Chapters are six games: mid-boss after three, the horde after four,
  the act boss after six.

v384, staging only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Opus 5.5 committed 2026-10-15 15:12:52 +02:00
1 parent 9e6d566c08
commit 16d0532f97
9 files changed
+119 -30

No files matched your search

+9 -4
View File
@@ -2080,7 +2080,7 @@ test('the menus own the theme, and hand the court back its silence', () => {
// 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';
AMB = 'tennis-ambient-loop.wav', ZOM = cfg.GORE_BED.file; // the gore bed (metal since 2026-10-15)
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
@@ -3737,7 +3737,7 @@ test('THE STORY: the prologue is a living match that turns, and then the first a
assert.ok(G.match && G.match.cup && G.story && G.story.part === 'cup', 'act I\'s cup takes the court');
});
test('THE STORY: a chapter has no duel, a mid-boss checkpoint at five and the act boss at ten', () => {
test('THE STORY: a chapter has no duel, a mid-boss checkpoint, a horde between the bosses, and the act boss last', () => {
prof.prof.lg = 1;
intoStory(2, 'chap');
assert.equal(G.runGames, cfg.STORY.base(2));
@@ -3756,7 +3756,12 @@ test('THE STORY: a chapter has no duel, a mid-boss checkpoint at five and the ac
update(DT);
assert.ok(!G.interlude, 'the racket zombie is an ordinary body in the story');
zombies.length = 0;
for (let g = 0; g < cfg.STORY.length - cfg.STORY.midAt; g++) { storyGame(); G.modal = null; }
let horde = false;
for (let g = 0; g < cfg.STORY.length - cfg.STORY.midAt; g++) {
storyGame(); G.modal = null;
if (G.pendingHorde) { horde = true; assert.equal(G.runGames - G.story.base, cfg.STORY.hordeAt, 'the horde comes after game ' + cfg.STORY.hordeAt); G.pendingHorde = false; }
}
assert.ok(horde, 'one horde, between the two bosses');
const boss = zombies.find(z => z.variant === 'B');
assert.ok(G.bossFight && boss, 'the act boss walks on after game ten');
assert.equal(boss.art.name, cfg.storyBossArt(2, 'end').name, 'act II\'s boss is the sponsor mascot');
@@ -3825,7 +3830,7 @@ test('THE STORY: a profile from before the story starts it from the top, keeping
const t = prof.cleanStory({ act: 9, cp: 'nonsense', rack: [{ id: 'bomb', n: 500 }, { n: 2 }], tries: -3 });
assert.deepEqual([t.act, t.cp, t.tries], [5, 'cup', 0]);
assert.deepEqual(t.rack, [{ id: 'bomb', n: 99 }]);
assert.equal(cfg.storyFixture({ act: 1, cp: 'mid' }).line, 'CHAPTER I · FROM GAME 6');
assert.equal(cfg.storyFixture({ act: 1, cp: 'mid' }).line, 'CHAPTER I · FROM GAME ' + (cfg.STORY.midAt + 1));
assert.equal(cfg.storyFixture({ act: 5, cp: 'done' }).title, 'THE STORY IS TOLD');
});