One file owns the court: beats.js, PROJECT.md (né HANDOFF), and tests gate the build
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
05930f0877
commit
c122745390
11 files changed
+362
-231
No files matched your search
@@ -61,7 +61,8 @@ globalThis.getComputedStyle = () => ({ paddingTop: '0' });
|
||||
|
||||
// ---- game modules (real code) ----------------------------------------------
|
||||
const { G, P, score, ball, setupPoint } = await import('./state.js');
|
||||
const { update, startDuel, forceZombies, splatPoint, restartRun, resolveInterlude } = await import('./gameplay.js');
|
||||
const { update, forceZombies, splatPoint, restartRun } = await import('./gameplay.js');
|
||||
const { startDuel, resolveInterlude, startBossFight, endBossFight } = await import('./beats.js');
|
||||
const { zombies, spawnZombie } = await import('./zombies.js');
|
||||
const { TUNE } = await import('./config.js');
|
||||
|
||||
@@ -155,5 +156,66 @@ test('a pending horde defers while a duel runs, then fires after it', () => {
|
||||
assert.ok(G.horde, 'the set still gets its horde, after the duel');
|
||||
});
|
||||
|
||||
// ---- regressions mined from the project log (each of these shipped broken once) ----------------
|
||||
|
||||
test('a pending horde never pours onto a boss stage', () => {
|
||||
// commit 7803c83: at game 30 (set AND boss chapter) the set's horde latch used to cash into the
|
||||
// boss fight. The latch must hold while the boss owns the court, and fire after.
|
||||
startBossFight(3);
|
||||
assert.ok(G.bossFight, 'setup: boss on court');
|
||||
G.pendingHorde = true; G.seen.horde = true;
|
||||
G.phase = 'over'; G.overT = 0.01;
|
||||
update(DT);
|
||||
assert.equal(G.horde, null, 'the latch must hold during the boss fight');
|
||||
assert.ok(G.pendingHorde, 'and stay armed');
|
||||
});
|
||||
|
||||
test('the boss win card is EPIC and survives the set handback (the game-30 clobber)', () => {
|
||||
// endBossFight once ran the parked 'set' handback LAST, so the routine SET card overwrote the
|
||||
// EPIC card before it drew a frame. The handback runs first now and the boss card wins.
|
||||
startBossFight(3);
|
||||
G.afterDuel = 'set'; G.seen.horde = true;
|
||||
zombies.length = 0;
|
||||
endBossFight();
|
||||
assert.equal(G.bossFight, 0, 'fight over');
|
||||
assert.equal(G.card && G.card.title, 'EPIC!', 'the boss card must not be clobbered by the set card');
|
||||
assert.ok(G.pendingHorde, 'the set the boss interrupted still owes its horde');
|
||||
for (let i = 0; i < 200 && !G.horde; i++) { update(0.1); zombies.length = 0; }
|
||||
assert.ok(G.horde, 'and the horde arrives after the boss beat, not during it');
|
||||
});
|
||||
|
||||
test('mgT counts down in every phase and releases the serve lock', () => {
|
||||
// commit e75b388: the burst clock used to tick only during rally, so a beat that moved the phase
|
||||
// mid-burst stranded mgT above zero forever — and mgT > 0 blocks serving. Permanent lock.
|
||||
G.mgT = 0.5; G.gunId = 'gun';
|
||||
assert.equal(G.phase, 'pretoss', 'setup: not in rally');
|
||||
for (let i = 0; i < 60; i++) { update(DT); zombies.length = 0; }
|
||||
assert.ok(G.mgT <= 0, 'the burst clock must run out outside rally');
|
||||
assert.equal(G.phase, 'pretoss', 'and hand back the serve loop');
|
||||
});
|
||||
|
||||
test('a won duel pays out: the tin and the trainer', () => {
|
||||
// commit c75bd5b ("pay out every won duel") + the heal that is THE reason to want a duel.
|
||||
G.hp = G.hpMax - 3;
|
||||
startDuel();
|
||||
G.interlude.introT = 0;
|
||||
const rackBefore = G.rack.length + (G.temp ? 1 : 0);
|
||||
resolveInterlude('P');
|
||||
const rackAfter = G.rack.length + (G.temp ? 1 : 0);
|
||||
assert.ok(rackAfter > rackBefore, 'winning the duel must hand over a weapon');
|
||||
assert.equal(G.hp, G.hpMax - 3 + TUNE.healPerDuel, 'and the trainer heals healPerDuel');
|
||||
});
|
||||
|
||||
test('a lost duel banks their game — and their set — without a horde', () => {
|
||||
score.games[1] = 5;
|
||||
startDuel();
|
||||
G.interlude.introT = 0;
|
||||
resolveInterlude('O');
|
||||
assert.equal(G.sets[1], 1, 'their 6th game banks their set');
|
||||
assert.deepEqual(score.games, [0, 0], 'games reset');
|
||||
assert.ok(zombies.filter(z => z.variant !== 'r').length >= 5, 'the dead lose patience: an instant surge');
|
||||
assert.ok(!G.horde && !G.pendingHorde, 'their duel set carries no horde');
|
||||
});
|
||||
|
||||
console.log(`\n${passed} tests passed`);
|
||||
process.exit(0); // render.js schedules viewport timers at load; don't let them outlive the report
|
||||
Reference in new issue
Block a user