diff --git a/game.js b/game.js index a93488e..ff75aa7 100644 --- a/game.js +++ b/game.js @@ -579,14 +579,12 @@ function update(dt) { if (phase === 'over') { overT -= dt; if (overT <= 0) setupPoint(); } } -function predictLanding() { // first-bounce point of the ball's current trajectory - let { x, y, z, vx, vy, vz } = ball; - const g = ball.g || TUNE.gravity, dt = 0.01; - for (let i = 0; i < 300; i++) { - vy -= g * dt; x += vx * dt; y += vy * dt; z += vz * dt; - if (y <= 0) return { x, z }; - } - return null; +function predictLanding() { // first-bounce point, closed form — exact, so the ring never jitters + const g = ball.g || TUNE.gravity; + const disc = ball.vy * ball.vy + 2 * g * ball.y; + if (disc < 0) return null; + const t = (ball.vy + Math.sqrt(disc)) / g; // time until y = 0 + return { x: ball.x + ball.vx * t, z: ball.z + ball.vz * t }; } // ---------------------------------------------------------------- render ---- @@ -739,7 +737,7 @@ function render() { // server idly bouncing the ball while waiting if (phase === 'pretoss') { const sv = score.server === 'P' ? P : O; - const bx = sv.x - 0.5 * (sv.side === 'P' ? 1 : -1), by = Math.abs(Math.sin(idleT * 3.6)) * 0.7; // floor to the reaching hand + const bx = sv.x - 0.5 * (sv.side === 'P' ? 1 : -1), by = Math.abs(Math.sin(idleT * 3.6)) * 0.55; // floor to the reaching hand const sh = proj(bx, 0, sv.z); ctx.fillStyle = 'rgba(0,0,0,0.3)'; ctx.beginPath(); ctx.ellipse(sh.x, sh.y, Math.max(1.5, sh.s * 0.08), Math.max(1, sh.s * 0.035), 0, 0, 7); ctx.fill();