The level is you, in the league: quicker legs, a wider window, and a curve that pays on the way up

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Fable 5 committed 2026-08-30 13:28:24 +02:00
1 parent 7dc72009eb
commit 7e2649c888
5 files changed
+82 -16

No files matched your search

+14 -4
View File
@@ -1315,7 +1315,16 @@ function onBounce() {
// them, so the same swing whiffed centimetres short there. Gore's opener keeps the old feel: its
// tennis is a prologue, not the mode.
export const rallyFeel = () => !!(G.match || G.classic);
export const assistK = () => rallyFeel() ? Math.max(TUNE.autoAssist, TUNE.matchAssist) : TUNE.autoAssist;
// THE LEVEL IS YOU, in the league (2026-08-29): the player's legs and contact window grow subtly
// with the level — LEAGUE modes only (the climb, the cup, the training court). Classic keeps its
// own difficulty picker, netplay is a fair human match, gore is untouched (its trio lives in
// profile.bonus too, read elsewhere). Level 1 is factor 1: the shipped baseline is the floor.
export const legLevel = () => G.match && !G.net ? bonus().run : 1;
export const winLevel = () => G.match && !G.net ? bonus().window : 1;
// legLevel rides INSIDE assistK, so the mover's legs and intercept's reachability can never
// disagree about what those legs can get to (the rzChaseSpeed rule, player-side).
export const assistK = () => (rallyFeel() ? Math.max(TUNE.autoAssist, TUNE.matchAssist) : TUNE.autoAssist)
* legLevel();
function intercept(side) {
const ch = side === 'O' ? O : P;
@@ -1409,7 +1418,7 @@ function trySwing(ch, isAI, qCap = TUNE.aiQualityMax) {
// dies on contact feeling fair, and the gore modes are untouched.
const reach = isAI ? (G.diff ? G.diff.reach : TUNE.aiReach)
: TUNE.reach * (volley ? VOLLEY.reach : 1) * (rallyFeel() ? TUNE.matchReach : 1);
const win = TUNE.swingWindow * (volley ? VOLLEY.window : 1);
const win = TUNE.swingWindow * (volley ? VOLLEY.window : 1) * (isAI ? 1 : winLevel());
const t = ch.swingT;
if (t < TUNE.swingWindup || t > TUNE.swingWindup + win) return;
if (!ball.active || G.phase !== 'rally') return;
@@ -1986,12 +1995,13 @@ export function update(dt) {
const volley = ball.bounces === 0;
(G.swingSnaps ||= []).push({ t: P.swingT, d: Math.hypot(ball.x - P.x, (ball.z - P.z) * 0.8), y: ball.y,
bz: ball.z, bounces: ball.bounces,
r: TUNE.reach * (volley ? VOLLEY.reach : 1), w: TUNE.swingWindow * (volley ? VOLLEY.window : 1) });
r: TUNE.reach * (volley ? VOLLEY.reach : 1), w: TUNE.swingWindow * (volley ? VOLLEY.window : 1) * winLevel() });
}
trySwing(P, false); // a contact resets swingT to -1 inside trySwing
// volley-length window while a hostile shot is in the air too, or the swing clock expires at
// 0.24 s and closes TUNE.parryWindow (0.34 s) before it has had its chance
const win = TUNE.swingWindow * ((shots.length || (ball.active && ball.bounces === 0)) ? VOLLEY.window : 1);
const win = TUNE.swingWindow * ((shots.length || (ball.active && ball.bounces === 0)) ? VOLLEY.window : 1)
* winLevel(); // the clock must outlive the widened window or the level's gift is dead time
if (P.swingT > TUNE.swingWindup + win + 0.02) { P.swingT = -1; if (G.phase === 'rally') swish(); logWhiff(); } // log even if the point ended mid-swing
}
expirePose(P, dt);
+15 -7
View File
@@ -89,14 +89,22 @@ export function award(kind, n = 1) {
}
// ---------------------------------------------------------------- the level is you ----
// Phase 4's defensive buffs, through existing dials only: level 1 is the shipped game (factor 1)
// and the cap stays honest — a maxed profile survives volleys better, it does not stop dying.
// Read at the dial sites (meleeCooldown's floor, the parry's reach/arm, hurtPlayer's grace).
// The level's buffs, through existing dials only: level 1 is the shipped game (factor 1, the
// untouchable baseline) and the caps stay honest. FRONT-LOADED (sqrt of the level fraction,
// 2026-08-29): Oliver plays at level ~20 and the buffs must be felt on the way up, not only at
// the cap — the xp curve itself is fast-early for the same reason. At level 20 the fraction is
// ~0.62 of the cap value; linear gave 0.39.
// Read at the dial sites: meleeCooldown's floor, the parry's reach/arm, hurtPlayer's grace (the
// gore trio), and — THE LEAGUE PAIR (his league-4 report: "autorun schlicht zu langsam, timing
// zu tight") — gameplay.assistK's legs and trySwing's contact window, league modes only.
export function bonus() {
const f = (levelAt(prof.xp) - 1) / (LEVEL_CAP - 1);
return { melee: 1 - 0.25 * f, // the swish floor drops to 75% — panic scales with mastery
parry: 1 + 0.25 * f, // the parry reaches a quarter further
grace: 1 + 0.35 * f }; // a volley needs a third longer to land twice
const f = Math.sqrt((levelAt(prof.xp) - 1) / (LEVEL_CAP - 1));
return { melee: 1 - 0.25 * f, // the swish floor drops to 75% — panic scales with mastery
parry: 1 + 0.25 * f, // the parry reaches a quarter further
grace: 1 + 0.35 * f, // a volley needs a third longer to land twice
run: 1 + 0.15 * f, // the league autorun's legs, up to +15%
window: 1 + 0.20 * f }; // the league contact window, up to +20% — cleaner timing too:
// quality is centre-normalised, so the same ms error scores higher
}
// ---------------------------------------------------------------- the promotion ----