The leagues, phase 2: five bands, pace tiers, locked courts, the members' ground, the promotion ceremony
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9133c0d351
commit
16bf5b9fed
12 files changed
+406
-71
No files matched your search
@@ -89,7 +89,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, startGore, splatPoint, restartRun, flickTarget, flatCount, swayOf, keyPressed, classicGame, serveApexY, assistK, rallyFeel, barrelGap, machCap, hitServe, cheer, inCourt } = await import('./src/gameplay.js');
|
||||
const { update, forceZombies, startGore, splatPoint, restartRun, leaguePace, flickTarget, flatCount, swayOf, keyPressed, classicGame, serveApexY, assistK, rallyFeel, barrelGap, machCap, hitServe, cheer, inCourt } = 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
|
||||
@@ -110,6 +110,9 @@ const { read: readStats, check: statsCheck } = statsMod;
|
||||
const DT = 1 / 60;
|
||||
function freshSplatter() {
|
||||
restartRun();
|
||||
// THE LEAGUES: a won cup PROMOTES the profile (by design), which would leak league 2 into every
|
||||
// test after the tournament walkthrough — each test opens in league 1 with no ceremony pending
|
||||
prof.prof.lg = 1; prof.prof.ch = 0; prof.takePromo();
|
||||
G.mode = 'splatter';
|
||||
G.everTransitioned = true;
|
||||
setupPoint();
|
||||
@@ -877,12 +880,23 @@ test('the opponent card is a RAMP: it starts easy and only gets harder', () => {
|
||||
for (let n = 1; n <= LADDER; n++)
|
||||
assert.ok(cfg.ZFAMS.includes(matchFoe(n).fam), `rung ${n}'s family is registered in ZFAMS`);
|
||||
assert.ok(matchFoe(LADDER).h > 1.85, 'the champion stands taller than a stock body');
|
||||
// the cup's final draws the top rung and nothing else: a band pointing past the ladder would
|
||||
// wrap through matchFoe's modulo and quietly deal the shambler as THE CHAMPION
|
||||
const fin = cfg.CUP_ROUNDS[cfg.CUP_ROUNDS.length - 1];
|
||||
assert.deepEqual(fin.band, [LADDER], 'the final is the top of the ladder');
|
||||
for (const r of cfg.CUP_ROUNDS) for (const rung of r.band)
|
||||
assert.ok(rung >= 1 && rung <= LADDER, `${r.name} draws a rung that exists (${rung})`);
|
||||
// THE LEAGUES: every league's cup draws only from its own band, its final is the band's top
|
||||
// rung and nothing else, and the five bands tile the whole ladder with no gap and no overlap
|
||||
let expectLo = 1;
|
||||
for (const l of cfg.LEAGUES) {
|
||||
const [lo, hi] = l.band;
|
||||
assert.equal(lo, expectLo, `${l.name} starts where the league below ended`);
|
||||
expectLo = hi + 1;
|
||||
const R = cfg.cupRoundsFor(l.id);
|
||||
assert.deepEqual(R[R.length - 1].band, [hi], `${l.name}'s final is its top rung`);
|
||||
assert.equal(R.length, hi - lo + 1, `${l.name} plays one round per rung`);
|
||||
for (const r of R) for (const rung of r.band)
|
||||
assert.ok(rung >= lo && rung <= hi, `${l.name} ${r.name} stays in its band (${rung})`);
|
||||
for (const r of R.slice(0, -1))
|
||||
assert.ok(!r.band.includes(hi), `${l.name}'s top rung is only ever the final`);
|
||||
assert.ok(cfg.COURTS.some(c => c.id === l.court), `${l.name}'s court exists`);
|
||||
}
|
||||
assert.equal(expectLo, LADDER + 1, 'the five bands cover the ladder exactly');
|
||||
});
|
||||
|
||||
test('the first opponent cannot paint lines: its returns are width-clamped', () => {
|
||||
@@ -1185,13 +1199,16 @@ test('the gore economy: barrels stop being income, and the late court keeps gett
|
||||
});
|
||||
|
||||
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;
|
||||
G.mode = 'splatter';
|
||||
const d = G.match;
|
||||
assert.ok(d.cup, 'the cup rides the match machinery');
|
||||
const CUP_ROUNDS = d.cup.rounds; // THE LEAGUES: the run carries its own rounds
|
||||
const dealt = [];
|
||||
for (let round = 0; round < CUP_ROUNDS.length; round++) {
|
||||
const R = CUP_ROUNDS[round];
|
||||
assert.ok(!dealt.includes(d.opp), R.name + ' never re-deals a fallen body');
|
||||
dealt.push(d.opp);
|
||||
assert.equal(d.cup.round, round, 'on ' + R.name);
|
||||
assert.ok(R.band.includes(d.opp), R.name + ' drew from its band (rung ' + d.opp + ')');
|
||||
const zb = oppOf();
|
||||
@@ -1209,7 +1226,11 @@ test('THE TOURNAMENT: bands draw, bodies deepen, rounds hold their body, the cha
|
||||
if (round < CUP_ROUNDS.length - 1) passMopBeat(); // the boy, then the next draw walks on
|
||||
}
|
||||
assert.ok(G.done && G.done.cup && G.done.won, 'the champion fell: the trophy screen holds');
|
||||
assert.equal(G.match.felled, CUP_ROUNDS.length, 'four bodies felled, banked as a match run');
|
||||
assert.equal(G.match.felled, CUP_ROUNDS.length, 'every round felled a body, banked as a match run');
|
||||
// THE LEAGUES: this cup was league one's, so winning it IS the promotion
|
||||
assert.equal(prof.prof.lg, 2, 'the cup is the promotion gate');
|
||||
const pr = prof.takePromo();
|
||||
assert.ok(pr && pr.to === 2, 'the ceremony is parked for the menu to play');
|
||||
});
|
||||
|
||||
test('THE DRAW: a bracket settled on screen is the bracket that is played', () => {
|
||||
@@ -1217,20 +1238,21 @@ test('THE DRAW: a bracket settled on screen is the bracket that is played', () =
|
||||
// showed you settling (main.buildBracket hands its rungs straight to startMatch). The guard is
|
||||
// the other half: a rung that is not in the round's band falls back to an honest roll rather
|
||||
// than dealing THE CHAMPION in round one.
|
||||
const { CUP_ROUNDS, LADDER } = cfg;
|
||||
const drawn = CUP_ROUNDS.map(r => r.band[r.band.length - 1]);
|
||||
const { LADDER } = cfg;
|
||||
const R = cfg.cupRoundsFor(1); // a fresh profile's cup is league one's
|
||||
const drawn = [3, 1, 2, 4]; // a legal shuffle of the sub-band, the final settled
|
||||
_startMatch(true, drawn); G.modal = null; G.modalQ.length = 0;
|
||||
assert.equal(G.match.opp, drawn[0], 'round one walks on the body the screen landed on');
|
||||
assert.deepEqual(G.match.cup.draw, drawn, 'and the rest of the draw rides with the run');
|
||||
endMatch();
|
||||
// a nonsense draw is refused per round, not trusted
|
||||
_startMatch(true, [LADDER, LADDER, LADDER, LADDER]); G.modal = null; G.modalQ.length = 0;
|
||||
assert.ok(CUP_ROUNDS[0].band.includes(G.match.opp),
|
||||
assert.ok(R[0].band.includes(G.match.opp),
|
||||
'a rung round one cannot contain is rolled instead of dealt');
|
||||
endMatch();
|
||||
// ...and no draw at all is the old behaviour, which the #cup back door still uses
|
||||
_startMatch(true); G.modal = null; G.modalQ.length = 0;
|
||||
assert.ok(CUP_ROUNDS[0].band.includes(G.match.opp), 'no draw: the round rolls its own');
|
||||
assert.ok(R[0].band.includes(G.match.opp), 'no draw: the round rolls its own');
|
||||
endMatch();
|
||||
});
|
||||
|
||||
@@ -1240,7 +1262,6 @@ test('cup rallies never strand the latch: no-crossing points, the fall, and the
|
||||
// no-fall path re-armed synchronously, and the after-set latch went stale ("basically like
|
||||
// freeze from before"). The latch is set BEFORE the resolve now (the swallow exempts the
|
||||
// resolution in progress via G.matchEarly), and willFall mirrors the cup divisor exactly.
|
||||
const { CUP_ROUNDS } = cfg;
|
||||
_startMatch(true); G.modal = null; G.modalQ.length = 0;
|
||||
G.mode = 'splatter';
|
||||
const d = G.match;
|
||||
@@ -2193,5 +2214,59 @@ test('PROFILE: zombie names fit the board and its charset, and the profile round
|
||||
prof.prof.lg = 1; prof.save();
|
||||
});
|
||||
|
||||
test('THE LEAGUES: the arcade opens at its band, climbs inside it, and counts felled bodies', () => {
|
||||
prof.prof.lg = 2;
|
||||
assert.ok(_startMatch(), 'the arcade starts');
|
||||
G.modal = null; G.modalQ.length = 0;
|
||||
G.mode = 'splatter';
|
||||
assert.equal(G.league, 2, 'the run wears the profile league');
|
||||
const d = G.match;
|
||||
assert.equal(d.opp, 5, "league two opens at its band's bottom rung");
|
||||
for (let b = 0; b < 6; b++) {
|
||||
G.interlude.introT = 0;
|
||||
for (let k = 0; k < 4; k++) { resolveInterlude('P'); G.modal = null; G.modalQ.length = 0; }
|
||||
for (let i = 0; i < 6 * 60 && G.matchFall; i++) { G.modal = null; G.modalQ.length = 0; update(DT); }
|
||||
passMopBeat();
|
||||
}
|
||||
assert.equal(d.opp, 8, 'the ladder clamps at the top of the band — the cup is the only way up');
|
||||
assert.equal(d.felled, 6, 'every felled body counts, clamp or no clamp');
|
||||
endMatch();
|
||||
});
|
||||
|
||||
test('THE LEAGUES: the top league crowns a champion instead of promoting', () => {
|
||||
prof.prof.lg = 5;
|
||||
_startMatch(true); G.modal = null; G.modalQ.length = 0;
|
||||
G.mode = 'splatter';
|
||||
const d = G.match;
|
||||
assert.equal(d.cup.rounds.length, 3, 'a three-rung league plays three rounds');
|
||||
for (let round = 0; round < d.cup.rounds.length; round++) {
|
||||
const R = d.cup.rounds[round];
|
||||
G.interlude.introT = 0;
|
||||
for (let g = 1; g <= R.need; g++)
|
||||
for (let k = 0; k < 4; k++) { resolveInterlude('P'); G.modal = null; G.modalQ.length = 0; }
|
||||
for (let i = 0; i < 6 * 60 && G.matchFall; i++) { G.modal = null; G.modalQ.length = 0; update(DT); }
|
||||
if (round < d.cup.rounds.length - 1) passMopBeat();
|
||||
}
|
||||
assert.ok(G.done && G.done.cup, 'the trophy screen holds');
|
||||
assert.equal(prof.prof.lg, 5, 'there is no league six');
|
||||
assert.equal(prof.prof.ch, 1, 'the profile is crowned');
|
||||
const pr = prof.takePromo();
|
||||
assert.ok(pr && pr.champion, 'the ceremony parked is the champion one');
|
||||
});
|
||||
|
||||
test('THE LEAGUES: the pace tier scales the solo match world and nothing else', () => {
|
||||
G.match = { opp: 1 }; G.league = 1;
|
||||
assert.equal(leaguePace(), 1, 'league one IS the shipped game');
|
||||
G.league = 5;
|
||||
assert.ok(leaguePace() > 1.2, 'the top league is a visibly faster world');
|
||||
const climbs = [1, 2, 3, 4, 5].map(l => { G.league = l; return leaguePace(); });
|
||||
for (let i = 1; i < climbs.length; i++) assert.ok(climbs[i] > climbs[i - 1], 'each league is faster');
|
||||
G.net = { on: true };
|
||||
assert.equal(leaguePace(), 1, 'netplay is a human and exempt');
|
||||
G.net = null; G.match = null; G.league = 5;
|
||||
assert.equal(leaguePace(), 1, 'outside the match modes the factor is 1 — gore keeps its own director');
|
||||
G.league = 1;
|
||||
});
|
||||
|
||||
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