Strengthen final-league placement and returns

This commit is contained in:
wuwi committed 2026-09-04 09:11:12 +02:00
1 parent bfc70d7388
commit e3faeb0a3a
5 files changed
+101 -8

No files matched your search

+5 -3
View File
@@ -1250,17 +1250,19 @@ const OPPONENTS = [
line: 'HE GAVE LESSONS ON THIS COURT.', speed: 1.16, pace: 1.22, wide: 1.20, flub: 0.70 },
// THE BRUTE, not "THE BULK": the gore mode's stats screen has called this body THE BRUTE since it
// was the pole-dragger (ENEMIES.g below), and one character may not carry two names.
// Final-league returns hunt the open court (smart) and have individual quality caps. Earlier
// opponents keep random lanes and racketQMax. Brute's legs must clear even league 4's fastest.
{ short: 'BRUTE', name: 'THE BRUTE', fam: 'br', h: 2.0,
line: 'NOTHING GETS PAST HIM TWICE.', speed: 1.02, pace: 1.34, wide: 1.24, flub: 0.66 },
line: 'NOTHING GETS PAST HIM TWICE.', speed: 1.12, pace: 1.34, wide: 1.24, flub: 0.66, smart: 0.20, qMax: 0.60 },
{ short: 'DIRECTOR', name: 'THE DIRECTOR', fam: 'dr',
line: 'HE CAN HAVE YOU REMOVED.', speed: 1.26, pace: 1.28, wide: 1.28, flub: 0.60 },
line: 'HE CAN HAVE YOU REMOVED.', speed: 1.26, pace: 1.28, wide: 1.28, flub: 0.60, smart: 0.40, qMax: 0.65 },
// 18: the man whose name is on the honours board. `h` is WORLD height (zombieH reads it, so the
// serve aims at the body actually drawn): four family rolls could not make the art itself bulky
// against the pose sheet — see _famroll.py's pair-reference note — so his size is stature.
// Six new rungs stand between him and the start now, so he took the step up Oliver asked for:
// he reads the court faster, hits harder, paints wider, and misses half as often as he did.
{ short: 'CHAMPION', name: 'THE CHAMPION', fam: 'ch', h: 2.1,
line: 'HE RETURNS EVERYTHING.', speed: 1.45, pace: 1.42, wide: 1.36, flub: 0.38 },
line: 'HE RETURNS EVERYTHING.', speed: 1.45, pace: 1.42, wide: 1.36, flub: 0.38, smart: 0.60, qMax: 0.70 },
];
// Who opponent N is. This used to reuse the chapter roster's cycling (bossArt) so the two lists
+10 -4
View File
@@ -1444,11 +1444,13 @@ function trySwing(ch, isAI, qCap = TUNE.aiQualityMax) {
// finger is usually still down at contact, and the vector under it NOW is the aim the player
// means — the released flickAim only carries it when the ball arrives after the finger lifted.
const flick = isAI ? null : (G.swipe || takeFlickAim());
// SMART AIM (G.diff.smart, classic difficulty): with this probability the CPU plays the OPEN
// SMART AIM (classic difficulty or the solo match foe): with this probability the CPU plays the OPEN
// COURT — the corner away from where you stand — instead of rolling a random lane. Random aim
// sends a third of everything straight back to your racket; hunting the open corner is what
// makes a hard CPU feel like it is PLAYING you. ax is an absolute court lane (see doHit).
const smart = isAI && G.diff && Math.random() < (G.diff.smart || 0);
const smartChance = G.diff ? (G.diff.smart || 0) : G.match && !G.net ? (ch.foe?.smart || 0) : 0;
// Keep the old random sequence for classic and for foes without smart aim.
const smart = isAI && (G.diff || smartChance) && Math.random() < smartChance;
const aim = isAI
? { ax: smart ? (P.x > 0.6 ? -1 : P.x < -0.6 ? 1 : (Math.random() < 0.5 ? -1 : 1))
: [ -1, 0, 1 ][Math.floor(Math.random() * 3)],
@@ -1545,8 +1547,12 @@ function racketAI(rz, dt) {
// body had actually learned. The champion's near-zero flub is exactly what this opens.
+ (G.match && G.match.surprised ? TUNE.surpFlub * learnK(rz) : 0);
if (Math.random() < flub) rz.swingCool = 0.5; // whiff — it hesitates (rarer per rung of the ladder)
else { rz.swingT = TUNE.swingWindup + TUNE.swingWindow / 2;
trySwing(rz, true, TUNE.racketQMax + TUNE.duelQBonus * duelSkill()); rz.swingT = -1; rz.swingCool = 0.3; }
else {
const qCap = G.match && !G.net ? (rz.foe?.qMax ?? TUNE.racketQMax)
: TUNE.racketQMax + TUNE.duelQBonus * duelSkill();
rz.swingT = TUNE.swingWindup + TUNE.swingWindow / 2;
trySwing(rz, true, qCap); rz.swingT = -1; rz.swingCool = 0.3;
}
// the lean is SPENT at the racket, either way — matchReady clears it when a point ends unmet
if (G.match) G.match.surprised = G.match.expected = false;
}