Catchability contract: every ball reachable via auto-run, timing is the skill
- intercept() now picks a point the runner can actually reach in time (travel-time feasibility), falling back to the deepest hittable point - tapping no longer slows the auto-run: anticipating a shot doesn't lose it - assist legs slightly superhuman (1.15x) so positioning is guaranteed - ball boy run frames regenerated with contrasting stride/gather poses (previous pair was near-identical, which read as no animation) Verified: 10/10 CPU shots end inside the honest 1.3m contact radius with zero manual movement. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
769dea8755
commit
f3c2af4ef5
4 files changed
+16
-10
No files matched your search
Binary file not shown.
|
Before Width: | Height: | Size: 486 KiB After Width: | Height: | Size: 452 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 13 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 9.2 KiB |
@@ -18,7 +18,7 @@ const TUNE = {
|
||||
aimError: 2.4, // m of scatter at quality 0
|
||||
perfectMs: 45, // |offset from window center| for PERFECT
|
||||
aiQualityMin: 0.55, aiQualityMax: 0.95, aiFlub: 0.05,
|
||||
autoAssist: matchMedia('(pointer: coarse)').matches ? 1.0 : 0, // auto-run toward the ball (mobile)
|
||||
autoAssist: matchMedia('(pointer: coarse)').matches ? 1.15 : 0, // auto-run speed factor (mobile); slightly superhuman legs
|
||||
};
|
||||
|
||||
const COURT = { L: 23.77, W: 8.23, dblW: 10.97, netZ: 11.885, netH: 0.914, netW: 10.06, serviceD: 6.4 };
|
||||
@@ -367,24 +367,29 @@ function onBounce() {
|
||||
let phaseWasServe = false;
|
||||
|
||||
// ---------------------------------------------------------------- update ----
|
||||
// Where should a player run to? Simulate the ball forward and take the first point
|
||||
// in their half where the ball is at comfortable hitting height.
|
||||
// Where should a player run to? Simulate the ball forward and take the first point in
|
||||
// their half at hitting height that they can actually REACH IN TIME; otherwise chase the
|
||||
// deepest hittable point. This is the "every ball is catchable, timing is the skill" contract.
|
||||
function intercept(side) {
|
||||
const ch = side === 'O' ? O : P;
|
||||
const spd = side === 'O' ? TUNE.aiSpeed : TUNE.playerSpeed * TUNE.autoAssist;
|
||||
let { x, y, z, vx, vy, vz } = ball;
|
||||
const g = TUNE.gravity, dt = 0.01;
|
||||
const inZone = side === 'O' ? zz => zz <= 9.5 : zz => zz >= COURT.L - 9.5;
|
||||
const home = side === 'O' ? { x: 0, z: 2.5 } : { x: 0, z: 22.8 };
|
||||
let fallback = null;
|
||||
for (let i = 0; i < 240; i++) {
|
||||
let fallback = null, deepest = null;
|
||||
for (let i = 0; i < 300; i++) {
|
||||
vy -= g * dt; x += vx * dt; y += vy * dt; z += vz * dt;
|
||||
if (y <= 0 && vy < 0) { y = 0; vy = -vy * TUNE.bounceY; vx *= TUNE.bounceXZ; vz *= TUNE.bounceXZ; }
|
||||
if (i > 5 && inZone(z) && y < 1.6) {
|
||||
if (i > 5 && inZone(z) && y < 1.6 && y > 0.3) {
|
||||
const pt = { x, z: side === 'O' ? Math.max(0.8, z) : Math.min(24.5, z) };
|
||||
if (y > 0.7) return pt; // waist-high contact preferred
|
||||
if (y > 0.3 && !fallback) fallback = pt;
|
||||
const reachable = Math.hypot(pt.x - ch.x, pt.z - ch.z) / spd <= i * dt;
|
||||
if (y > 0.7 && reachable) return pt; // waist-high AND arrivable in time
|
||||
if (reachable && !fallback) fallback = pt; // low but arrivable
|
||||
deepest = pt; // last resort: run at the deepest hittable point
|
||||
}
|
||||
}
|
||||
return fallback || home;
|
||||
return fallback || deepest || home;
|
||||
}
|
||||
|
||||
function trySwing(ch, isAI) {
|
||||
@@ -448,7 +453,8 @@ function update(dt) {
|
||||
let assist = 0;
|
||||
if (!mx && !mz && TUNE.autoAssist > 0 && ball.active) {
|
||||
const chasing = ball.lastHit === 'O';
|
||||
const rate = TUNE.playerSpeed * TUNE.autoAssist * slow * dt * (chasing ? 1 : 0.55);
|
||||
// no `slow` here: tapping must not freeze your legs, or anticipating a shot loses it
|
||||
const rate = TUNE.playerSpeed * TUNE.autoAssist * dt * (chasing ? 1 : 0.55);
|
||||
const tgt = chasing ? intercept('P') : { x: 0, z: 22.8 }; // run to the ball, then recover home
|
||||
const dx = tgt.x - P.x, dz = tgt.z - P.z, d = Math.hypot(dx, dz);
|
||||
if (d > 0.35) { // deadzone: stand ready near the spot instead of dithering over it
|
||||
|
||||
Reference in new issue
Block a user