Stop the auto-run shaking while it chases a ball

Oliver: "wenn der Autoplay den Ball versucht zu bekommen, zittert der
Player schnell hin und her". Measured: the run's target flipped by
metres every other frame. intercept samples the flight every 10 ms
against a 16.7 ms frame, so any candidate on a margin (a low bounce
peaking on the 0.45 m floor, a point on the back boundary, a dying drop)
passed one frame and failed the next. Frames with no candidate sent him
home and back. 9-62 reversals inside 200 ms per five minutes.

The chase now latches its plan: the flight is fixed at the strike, so
the first point stays the answer while his legs still make it in time,
net of any time he stood (split-step, heels). Reach is judged from where
the chase began, and the target eases with a start/stop hysteresis.
After: no reversals at any rung, timing unchanged. He wastes no steps,
so he is a little stronger, mostly at the bottom of the ladder. New
test; v375.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Opus 5.5 committed 2026-10-14 20:15:25 +02:00
1 parent 599d5b0506
commit bb72a01a3e
7 files changed
+145 -19

No files matched your search

+14
View File
@@ -6,6 +6,7 @@
// Since step 4: BOT=human (default; JIT, BIAS ms), CLIMB=1, XP, WHY=1, PROBE=1, LOG=1|all, SUMMARY=1,
// JSON=1 — see PROJECT.md "Step 4, measured first". Pool 8 seeds before reading a win rate.
// ATTACK=0.3 (the long up-flick share), NETX=1 (net/smash tally), KILL=1 (put-aways), T_<knob>=<v> sets TUNE.
// JITTER=1 (auto-run shake: reversals, target jumps), =2 prints big jumps, =3 prints fast reversals.
// ponytail: the platform stubs are copied from test.js; fold this into a test once the rework lands.
let _seed = (Number(process.env.SEED) || 20260811) >>> 0;
Math.random = () => { // xorshift32 — small, fast, good enough for jitter
@@ -96,6 +97,7 @@ const ATTACK = Number(process.env.ATTACK || 0); // share of baseline shots playe
const JIT = Number(process.env.JIT ?? 60) / 1000, LAG = 7;
const gauss = () => Math.sqrt(-2 * Math.log(1 - Math.random())) * Math.cos(2 * Math.PI * Math.random());
const netx = { attacks: 0, smash: 0, smashWon: 0, lobbed: 0, open: null, p0: 0 };
const jit = { shown: 0, rev: 0, fastRev: 0, lastRev: -99, last: 0, jumps: [], frames: 0 };
const probe = [], kills = []; let kOpen = null, kpow = 1;
const bot = { armed: false, fire: -1, jit: null, swipe: null };
const sw = (dx, dy, mag) => { const n = Math.hypot(dx, dy) || 1, fx = dx / n, fy = dy / n;
@@ -173,6 +175,7 @@ for (const rung of RUNGS) {
}
}
const lh = ball.lastHit;
const px0 = P.x, pz0 = P.z, tg0 = P.tgt;
if (process.env.PROBE && lh === 'O' && bot.land && P.swingT >= 0) bot.land.sw = P.swingT, bot.land.d = Math.hypot(ball.x - P.x, (ball.z - P.z) * 0.8), bot.land.y = ball.y, bot.land.bb = ball.bounces;
update(DT);
if (lh !== 'P' && ball.lastHit === 'P' && ball.active) { if (ball.approach === 'P' && !G.wasServe) netx.attacks++; if (ball.smash) { netx.smash++; netx.open = 'smash'; } }
@@ -183,6 +186,15 @@ for (const rung of RUNGS) {
if (ball.lastHit !== 'O') G.swipe = null;
// KILL=1: every put-away (a q >= .95 CPU ball straight after a player ball under .5) — where it went,
// how far the player had to go, how long he had, and whether he got it back
// JITTER=1: the auto-run's shake — a direction reversal of the x step inside a chase, and how far the
// target jumps between frames (the two things a "zittert hin und her" can be made of)
if (process.env.JITTER && G.phase === 'rally' && ball.active && ball.lastHit === 'O' && lh === 'O') {
const dx = P.x - px0;
if (Math.abs(dx) > 1e-4) { if (jit.last && Math.sign(dx) !== Math.sign(jit.last)) { jit.rev++; if (i - jit.lastRev < 12) { jit.fastRev++; if (process.env.JITTER === '3' && jit.shown++ < 12) console.log('rev', i, 'dx', dx.toFixed(3), 'prev', jit.last.toFixed(3), 'P', P.x.toFixed(2), P.z.toFixed(2), 'tgt', P.tgt && P.tgt.x.toFixed(2), P.tgt && P.tgt.z.toFixed(2), 'swingT', P.swingT.toFixed(2), 'heels', ball.heels.toFixed(2)); } jit.lastRev = i; } jit.last = dx; }
if (tg0 && P.tgt) { const j = Math.hypot(P.tgt.x - tg0.x, P.tgt.z - tg0.z); jit.jumps.push(j); }
if (process.env.JITTER === '2' && tg0 && P.tgt && Math.hypot(P.tgt.x - tg0.x, P.tgt.z - tg0.z) > 0.5 && jit.shown++ < 25) console.log('jump', i, 'from', tg0.x.toFixed(2), tg0.z.toFixed(2), 'to', P.tgt.x.toFixed(2), P.tgt.z.toFixed(2), 'P', P.x.toFixed(2), P.z.toFixed(2), 'ball', ball.y.toFixed(2), ball.z.toFixed(2), 'b', ball.bounces, JSON.stringify(ball.rx));
jit.frames++;
} else jit.last = 0;
if (process.env.KILL) {
if (lh === 'P' && ball.lastHit === 'O' && kpow < 0.5 && ball.pow >= 0.95) {
const L = gp.predictLanding(); kills.push({ px: +P.x.toFixed(1), pz: +P.z.toFixed(1), lx: L && +L.x.toFixed(1), lz: L && +L.z.toFixed(1), tl: L && +L.t.toFixed(2), v: +Math.hypot(ball.vx, ball.vz).toFixed(1), got: null }); kOpen = kills[kills.length - 1];
@@ -241,6 +253,8 @@ for (const rung of RUNGS) {
}
if (!process.env.SUMMARY) { console.table(rows); console.table(land); }
if (process.env.NETX) console.log(JSON.stringify(netx));
if (process.env.JITTER) { const j = jit.jumps.sort((a, b) => a - b), q = f => +(j[Math.floor(j.length * f)] || 0).toFixed(3);
console.log(JSON.stringify({ chaseFrames: jit.frames, reversals: jit.rev, fastReversals_lt200ms: jit.fastRev, tgtJump_p50: q(0.5), p90: q(0.9), p99: q(0.99), max: q(0.9999), over20cm: j.filter(x => x > 0.2).length })); }
if (process.env.KILL) { console.table(kills.slice(0, 30)); const l = kills.filter(k => k.got === 'LOST').length; console.log('put-aways', kills.length, 'won by CPU', l); }
if (process.env.PROBE) { const l = probe.map(p => p.late).sort((a, b) => a - b); console.log("contact after predicted entry, ms: n", l.length, "mean", (l.reduce((a, b) => a + b, 0) / l.length).toFixed(1), "p10", l[l.length * 0.1 | 0], "p50", l[l.length / 2 | 0], "p90", l[l.length * 0.9 | 0]); }
if (process.env.JSON) { console.log(JSON.stringify(pts)); process.exit(0); }