The leagues, phase 4: the duel learns, tin tiers by league, level buffs, the board wears the badge
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
76d88030e5
commit
787877bce6
10 files changed
+175
-24
No files matched your search
@@ -89,13 +89,13 @@ 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, leaguePace, 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, duelSkill, tinLadder, deliverTin, 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
|
||||
// player would, so the tests step a live court
|
||||
const startDrill = (...a) => { const r = _startDrill(...a); G.modal = null; G.modalQ.length = 0; return r; };
|
||||
const { zombies, spawnZombie, pellets, parts, explode } = await import('./src/zombies.js');
|
||||
const { zombies, spawnZombie, pellets, parts, explode, hurtPlayer } = await import('./src/zombies.js');
|
||||
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');
|
||||
@@ -2268,5 +2268,67 @@ test('THE LEAGUES: the pace tier scales the solo match world and nothing else',
|
||||
G.league = 1;
|
||||
});
|
||||
|
||||
test('THE DUEL LEARNS: its skill ramps with the run, and game zero is the shipped duel', () => {
|
||||
G.match = null;
|
||||
G.runGames = 0;
|
||||
assert.equal(duelSkill(), 0, 'a fresh run duels the shipped opponent');
|
||||
G.runGames = Math.round(TUNE.duelRampGames / 2);
|
||||
const mid = duelSkill();
|
||||
G.runGames = TUNE.duelRampGames + 10;
|
||||
assert.ok(mid > 0 && mid < 1, 'the ramp is a ramp');
|
||||
assert.equal(duelSkill(), 1, '...and it caps');
|
||||
G.match = { opp: 5 };
|
||||
assert.equal(duelSkill(), 0, 'the match opponent has its own ladder — the ramp is duel-only');
|
||||
G.match = null;
|
||||
// the dial relationships the ramp promises: a full-skill duel still misses and still misreads
|
||||
assert.ok(TUNE.racketFlub * (1 - TUNE.duelFlubCut) > 0.05, 'a maxed duel still whiffs sometimes');
|
||||
assert.ok(TUNE.duelReadMax < 1, 'a maxed duel can still be wrong-footed');
|
||||
assert.ok(TUNE.racketQMax + TUNE.duelQBonus < 1, 'its returns never reach perfect quality');
|
||||
});
|
||||
|
||||
test('THE TIN TIERS: the league deepens the duel pot, and league 5 pays double', () => {
|
||||
G.runGames = 0;
|
||||
prof.prof.lg = 1;
|
||||
assert.ok(!tinLadder().includes('laser') && !tinLadder().includes('rocket'),
|
||||
'league one, game zero: the shipped pot');
|
||||
prof.prof.lg = 3;
|
||||
assert.ok(tinLadder().includes('laser') && !tinLadder().includes('rocket'),
|
||||
'league three adds the laser, not the rocket');
|
||||
prof.prof.lg = 4;
|
||||
assert.ok(tinLadder().includes('rocket'), 'league four adds the rocket');
|
||||
prof.prof.lg = 1;
|
||||
G.runGames = 12;
|
||||
assert.ok(tinLadder().includes('laser'), 'the run\'s own barrel gates still work without a league');
|
||||
G.runGames = 0;
|
||||
// the double tin: an empty rack, one delivery, twice the stock at league five
|
||||
const stockOf = id => { const i = G.rack.findIndex(r => r.id === id); return i < 0 ? 0 : G.rack[i].n; };
|
||||
G.rack.length = 0; G.temp = null; G.deliveries = 0;
|
||||
prof.prof.lg = 5;
|
||||
deliverTin();
|
||||
const first = G.rack[0];
|
||||
assert.equal(first.n, cfg.WEAPONS[first.id].tin * 2, 'a league-five duel pays a double tin');
|
||||
G.rack.length = 0; G.temp = null; G.deliveries = 0;
|
||||
prof.prof.lg = 1;
|
||||
deliverTin();
|
||||
assert.equal(G.rack[0].n, cfg.WEAPONS[G.rack[0].id].tin, 'league one pays the shipped tin');
|
||||
});
|
||||
|
||||
test('THE LEVEL IS YOU: the defensive buffs scale with level and level one is the shipped game', () => {
|
||||
const xp0 = prof.prof.xp;
|
||||
prof.prof.xp = 0;
|
||||
const b1 = prof.bonus();
|
||||
assert.deepEqual(b1, { melee: 1, parry: 1, grace: 1 }, 'level one changes nothing');
|
||||
prof.prof.xp = prof.xpFor(prof.LEVEL_CAP);
|
||||
const bMax = prof.bonus();
|
||||
assert.ok(bMax.melee < 1 && bMax.melee >= 0.7, 'the swish floor drops, boundedly');
|
||||
assert.ok(bMax.parry > 1 && bMax.parry <= 1.3, 'the parry reaches further, boundedly');
|
||||
assert.ok(bMax.grace > 1 && bMax.grace <= 1.4, 'the grace stretches, boundedly');
|
||||
// ...and the grace actually lands on the player: hurtPlayer writes the scaled window
|
||||
G.mode = 'splatter'; G.hurtT = 0; G.dodgeI = 0; G.hp = 4;
|
||||
hurtPlayer(1);
|
||||
assert.ok(G.hurtT > TUNE.hurtGrace * 1.3, 'a maxed profile\'s grace window is visibly longer');
|
||||
prof.prof.xp = xp0;
|
||||
});
|
||||
|
||||
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