diff --git a/config.js b/config.js index 3446781..a0d71aa 100644 --- a/config.js +++ b/config.js @@ -11,7 +11,14 @@ export const TUNE = { swingWindup: 0.06, // s before contact window opens swingWindow: 0.16, // s contact window length baseShotSpeed: 16, qualityShotSpeed: 12, // horizontal m/s = base + q*quality; perfect timing = real pace - serveSpeed: 21, serveQualitySpeed: 7, + // the slingshot's pace range. Weighted hard toward QUALITY so a full draw is a smash you can + // see: 21 m/s floppy at the minimum pull, 41 m/s at full. (baseShotSpeed above is the rally + // volley and stays put — it's shared with tennis mode and the CPU.) + serveSpeed: 19, serveQualitySpeed: 22, + serveSpin: 1.3, // extra gravity a full-power serve carries (was a fixed 0.35). Without it the + // net-clearance loop ate the whole smash: a flat fast ball can't get over the + // tape AND come down on a target, so it got throttled back to walking pace. + // Heavy topspin lets it leave flat and DIVE, which is what a smash actually is. aimError: 2.4, // m of scatter at quality 0 perfectMs: 45, // |offset from window center| for PERFECT aiQualityMin: 0.45, aiQualityMax: 0.85, aiFlub: 0.09, diff --git a/gameplay.js b/gameplay.js index 8145dc8..b6aeb1f 100644 --- a/gameplay.js +++ b/gameplay.js @@ -151,17 +151,21 @@ function hitServe(sv, quality, aim = null) { const boxZ = toFar ? COURT.svcFar + 1.6 : COURT.svcNear - 1.6; tzs = boxZ + (Math.random() * 2 - 1) * (1 - quality) * 1.5; } - const g = TUNE.gravity * (1 + quality * 0.35); // kick serves dip a little too + const g = TUNE.gravity * (1 + quality * TUNE.serveSpin); // the harder you hit it, the harder it dips // CLEAR THE NET. A shambler standing just the other side of the tape is only reachable on a // loft, so trade pace for arc until the ball actually gets over. doHit has done this since // forever; the serve never did, which is why every shot at a net-hugger died in the tape. // The trade is real: the loftier it goes the slower it arrives, and the walk-lead below was // computed for the original pace — a lob is a genuinely less reliable shot. let v = solveShot(ball.x, ball.y, ball.z, boxX, tzs, s, g); - const margin = 0.06 + 0.22 * quality; for (let i = 0; i < NET_LOFT_TRIES; i++) { const tN = (COURT.netZ - ball.z) / v.vz; const yN = ball.y + v.vy * tN - 0.5 * g * tN * tN; + // The sim only samples the net plane once a frame, so a steeply DIVING ball can test below + // the tape even when the closed form clears it. Charge margin for exactly that sampling + // error (descent rate × the worst frame step) instead of doHit's flat quality cushion — + // that cushion is paid for in pace, and it was taxing precisely the shots meant to hurt. + const margin = 0.06 + Math.abs(v.vy - g * tN) * 0.04; // 0.04 s = main.js's dt clamp if (tN <= 0 || yN >= COURT.netH + margin) break; s *= 0.9; v = solveShot(ball.x, ball.y, ball.z, boxX, tzs, s, g); } diff --git a/index.html b/index.html index 38ad6a5..a01d87d 100644 --- a/index.html +++ b/index.html @@ -52,6 +52,6 @@
TAP TO START
- +