Strengthen final-league placement and returns
This commit is contained in:
1 parent
bfc70d7388
commit
e3faeb0a3a
5 files changed
+101
-8
No files matched your search
@@ -904,6 +904,68 @@ test('the opponent card is a RAMP: it starts easy and only gets harder', () => {
|
||||
assert.equal(expectLo, LADDER + 1, 'the five bands cover the ladder exactly');
|
||||
});
|
||||
|
||||
test('league 5 has no speed dip below league 4, including serve and wrong-footed chases', () => {
|
||||
startMatch(); G.interlude.introT = 0;
|
||||
const zb = oppOf();
|
||||
const measure = (n, serve, surprised) => {
|
||||
G.match.opp = n; G.match.surprised = surprised; G.match.expected = false;
|
||||
zb.foe = cfg.matchFoe(n); zb.x = -3; zb.z = 2; zb.swingCool = 999;
|
||||
G.phase = 'rally'; G.wasServe = serve;
|
||||
Object.assign(ball, { active: true, lastHit: 'P', bounces: 0,
|
||||
x: 3, z: 5, y: 2, vx: 0, vy: 4, vz: -8 });
|
||||
update(DT);
|
||||
return Math.hypot(zb.vx, zb.vz);
|
||||
};
|
||||
for (const serve of [false, true]) for (const surprised of [false, true]) {
|
||||
const fourth = cfg.leagueOf(4).band, fifth = cfg.leagueOf(5).band;
|
||||
let floor = 0;
|
||||
for (let n = fourth[0]; n <= fourth[1]; n++) floor = Math.max(floor, measure(n, serve, surprised));
|
||||
assert.ok(floor > 7, 'the rig measures a real full-speed chase');
|
||||
for (let n = fifth[0]; n <= fifth[1]; n++) {
|
||||
const speed = measure(n, serve, surprised);
|
||||
assert.ok(speed >= floor, `${cfg.matchFoe(n).short}: ${speed} must clear ${floor} (serve ${serve}, surprised ${surprised})`);
|
||||
}
|
||||
}
|
||||
endMatch();
|
||||
});
|
||||
|
||||
test('league 5 returns hunt the open court and use their own quality caps; 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');
|
||||
}
|
||||
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]]) {
|
||||
G.match.opp = n; zb.foe = cfg.matchFoe(n);
|
||||
let hits = 0, open = 0, maxQ = 0;
|
||||
for (let i = 0; i < 1200; i++) {
|
||||
G.phase = 'rally'; G.wasServe = false;
|
||||
G.match.surprised = G.match.expected = false;
|
||||
P.x = i % 2 ? 3 : -3; 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; // actual flubs remain part of the opponent
|
||||
hits++;
|
||||
assert.ok(ball.pow >= TUNE.aiQualityMin && ball.pow <= cap, `${n}: quality ${ball.pow} within its band`);
|
||||
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;
|
||||
// At q >= .45 the corner's inward scatter stays beyond 1.35; centre scatter stays inside.
|
||||
if (landingX * Math.sign(P.x) < -1.35) open++;
|
||||
ball.active = false;
|
||||
}
|
||||
assert.ok(hits > 1100, `${n}: enough actual returns (${hits})`);
|
||||
assert.ok(maxQ > cap - 0.002, `${n}: the live return reaches its cap (${maxQ})`);
|
||||
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}`);
|
||||
}
|
||||
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.
|
||||
|
||||
Reference in new issue
Block a user