Sharpen league and Climb difficulty and give Gore a front door

This commit is contained in:
wuwi committed 2026-09-07 09:53:49 +02:00
1 parent e3faeb0a3a
commit ede1f48152
6 files changed
+151 -51

No files matched your search

+65 -4
View File
@@ -929,16 +929,17 @@ test('league 5 has no speed dip below league 4, including serve and wrong-footed
endMatch();
});
test('league 5 returns hunt the open court and use their own quality caps; league 4 stays unchanged', () => {
test('league 5 sustains strong returns and hunts the open court; league 4 stays unchanged', () => {
startMatch(); G.interlude.introT = 0;
const zb = oppOf();
for (let n = 1; n <= cfg.leagueOf(4).band[1]; n++) {
assert.equal(cfg.matchFoe(n).smart, undefined, 'no new aim bonus in earlier leagues');
assert.equal(cfg.matchFoe(n).qMax, undefined, 'no new quality bonus in earlier leagues');
assert.equal(cfg.matchFoe(n).qMin, undefined, 'no new quality floor in earlier leagues');
}
for (const [n, smart, cap] of [[15, 0, 0.55], [16, 0.2, 0.60], [17, 0.4, 0.65], [18, 0.6, 0.70]]) {
for (const [n, smart, floor, cap] of [[15, 0, 0.45, 0.55], [16, 0.5, 0.62, 0.76], [17, 0.7, 0.68, 0.83], [18, 0.85, 0.74, 0.90]]) {
G.match.opp = n; zb.foe = cfg.matchFoe(n);
let hits = 0, open = 0, maxQ = 0;
let hits = 0, open = 0, maxQ = 0, sumQ = 0;
for (let i = 0; i < 1200; i++) {
G.phase = 'rally'; G.wasServe = false;
G.match.surprised = G.match.expected = false;
@@ -949,7 +950,8 @@ test('league 5 returns hunt the open court and use their own quality caps; leagu
update(DT);
if (ball.lastHit !== 'O') continue; // actual flubs remain part of the opponent
hits++;
assert.ok(ball.pow >= TUNE.aiQualityMin && ball.pow <= cap, `${n}: quality ${ball.pow} within its band`);
assert.ok(ball.pow >= floor && ball.pow <= cap, `${n}: quality ${ball.pow} within its band`);
sumQ += ball.pow;
maxQ = Math.max(maxQ, ball.pow);
const t = (ball.vy + Math.sqrt(ball.vy * ball.vy + 2 * ball.g * ball.y)) / ball.g;
const landingX = ball.x + ball.vx * t;
@@ -959,6 +961,7 @@ test('league 5 returns hunt the open court and use their own quality caps; leagu
}
assert.ok(hits > 1100, `${n}: enough actual returns (${hits})`);
assert.ok(maxQ > cap - 0.002, `${n}: the live return reaches its cap (${maxQ})`);
assert.ok(Math.abs(sumQ / hits - (floor + cap) / 2) < 0.01, `${n}: normal returns sustain the stronger quality`);
const expected = 1 / 3 + 2 / 3 * smart;
assert.ok(Math.abs(open / hits - expected) < 0.05,
`${n}: open-court share ${open / hits} should be near ${expected}`);
@@ -966,6 +969,64 @@ test('league 5 returns hunt the open court and use their own quality caps; leagu
endMatch();
});
test('The Climb opens briskly, converges at opponent eight, and leaves cup and training intact', () => {
startMatch(); G.interlude.introT = 0;
const zb = oppOf();
const measure = (n, mode) => {
G.match.opp = n; G.match.train = mode === 'train';
G.match.cup = mode === 'cup' ? { round: 0, rounds: [{ need: 1 }] } : null;
G.match.surprised = G.match.expected = false;
zb.foe = cfg.matchFoe(n); zb.x = -3; zb.z = 2; zb.swingCool = 999;
G.phase = 'rally'; G.wasServe = false;
Object.assign(ball, { active: true, lastHit: 'P', bounces: 0,
x: 3, z: 5, y: 2, vx: 0, vy: 4, vz: -8 });
update(DT);
const chase = Math.hypot(zb.vx, zb.vz);
let hits = 0, pace = 0, width = 0;
const seed = _seed;
_seed = 9173; // repeatable samples; cosmetic timers may still consume different rolls
for (let i = 0; i < 1200; i++) {
G.phase = 'rally'; G.wasServe = false;
P.x = 0; P.z = 23; P.swingT = -1;
zb.x = 0; zb.z = 2; zb.swingT = -1; zb.swingCool = 0; zb.serveHold = 0;
Object.assign(ball, { active: true, lastHit: 'P', bounces: 1,
x: 0, z: 2.4, y: 1.2, vx: 0, vy: 0, vz: -1 });
update(DT);
if (ball.lastHit !== 'O') continue;
hits++; pace += Math.hypot(ball.vx, ball.vz);
const t = (ball.vy + Math.sqrt(ball.vy ** 2 + 2 * ball.g * ball.y)) / ball.g;
width = Math.max(width, Math.abs(ball.x + ball.vx * t));
ball.active = false;
}
_seed = seed;
return { chase, hits, pace: pace / hits, width };
};
let previous = 0;
for (let n = 1; n <= 8; n++) {
const cup = measure(n, 'cup'), train = measure(n, 'train'), climb = measure(n, 'climb');
const same = (a, b, why) => {
assert.equal(a.chase, b.chase, why + ': chase');
assert.ok(Math.abs(a.pace / b.pace - 1) < 0.005, why + ': return pace');
assert.ok(Math.abs(a.width - b.width) < 0.12, why + ': width ' + a.width + ' / ' + b.width);
assert.ok(Math.abs(a.hits - b.hits) < 40, why + ': whiff rate');
};
same(train, cup, `${n}: the training rep matches its cup opponent`);
assert.ok(climb.chase > previous, `${n}: the arcade's early chase curve keeps rising`);
previous = climb.chase;
if (n < 8) assert.ok(climb.chase > cup.chase, `${n}: less opening assistance in arcade`);
else same(climb, cup, 'opponent eight rejoins the original curve');
if (n === 1) {
assert.ok(climb.chase / cup.chase > 1.3, 'the opening opponent covers materially more ground');
assert.ok(climb.pace / cup.pace > 1.2, 'its actual returns arrive faster');
assert.ok(climb.width / cup.width > 1.4, 'its actual returns use more of the court');
assert.ok(climb.hits > cup.hits + 40, 'it gives away fewer returns');
assert.ok(climb.width < 2.5, 'opening returns still leave a reachable margin');
}
}
G.match.cup = null; G.match.train = false;
endMatch();
});
test('the first opponent cannot paint lines: its returns are width-clamped', () => {
// aimError x (1 - 0.55 quality) scattered returns to +-3.5 m, which an auto-run player cannot
// cover — "still a LOT of unreachable balls". Width is the ladder's third dial now.