Splatter: chevrons in perspective, and a smash that actually flies
The chevrons were stepped along the SCREEN line at a fixed size, so they read as a flat sticker over a 3D court. They now step through world space and take their size from their own projection scale — near ones roughly twice the far ones — and they fade IN toward the target so the eye is walked up the lane instead of being handed a uniform stripe. The arrowhead scales with the target's own depth too. (proj is a true projective map, so the screen direction stays constant along the line; only size and alpha needed to vary.) Hard shots now fly hard: serve pace is weighted onto quality (19 base + 22×q, so 21 -> 41 m/s) instead of 21 -> 28. Two things had to give way for it. Serve topspin was a fixed 1 + 0.35q, which meant a fast flat ball could not both clear the tape and land on a target, so the net-loft loop throttled every smash back to walking pace — measured 21.8 m/s whether you drew 60px or 95px. TUNE.serveSpin 1.3 lets it leave flat and DIVE, which is what a smash is. And the loft loop's margin was doHit's quality cushion, which taxed exactly the shots meant to hurt. It's now sized to the real error it exists to cover: the sim samples the net plane once a frame, so a diving ball can test below the tape when the closed form clears it. Margin = descent rate × the dt clamp. A flat drive pays almost nothing, a lob pays what it must. Measured, target 20m out: 24.7 / 32.2 / 41 m/s for weak / mid / full draw. At the net it's 9.6 / 11.2 / 11.6 — still the slow floaty lob, as it should be. Ball-vs-shambler contact gets a crude sweep (radius + half a frame's travel), because a 41 m/s ball steps 0.7m per frame at 60fps and 1.6m at main.js's dt clamp, both wider than a shambler. Verified the full smash connects at both. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f32cac6a70
commit
f3dcf0b16f
5 files changed
+45
-21
No files matched your search
+6
-2
@@ -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);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user