Stop back-dating flicks, and give the half-volley rescue the right deadline
Oliver's v367 log: every 62-64 ms flick landed 76-92 ms early and a 97 ms one whiffed — v366's touch-down timing for flicks was wrong; a flick is timed by its swipe (flickLead now 0, the age is still logged). The early-swing half-volley rescue checked the bounce against the long volley window and then expired on the bounce frame; it now uses the shorter groundstroke window. The log also records where he stood when the opponent struck and where the auto-run was taking him on a miss. v368. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
dfdb699f9d
commit
fe090f2fba
5 files changed
+37
-11
No files matched your search
+19
@@ -5466,6 +5466,25 @@ in the same test.
|
||||
The swing log's HIT line now also says which way the timing missed: `late 40ms` (the ball was already
|
||||
there when the window opened) or `early 40ms`. That was the question his first loss could not answer.
|
||||
|
||||
### His v367 log: the flick back-dating was wrong, and the half-volley rescue used the wrong window (v368)
|
||||
- **v366's flick back-dating made slow flicks EARLY.** Every 62-64 ms flick landed 76-92 ms early (q
|
||||
0.05-0.2). A 97 ms flick whiffed "14 ms after the window closed" and cost a point. The 31-45 ms
|
||||
flicks were fine. A flick is timed by its SWIPE, not by its touch-down. `TUNE.flickLead` is now 0
|
||||
(knob kept). The touch's age is still logged for both gestures, and taps keep their touch-down timing.
|
||||
- **The early-swing half-volley rescue judged its deadline against the swing's current, VOLLEY-length
|
||||
window.** After the bounce the shorter groundstroke window applies, so it decided to wait and then
|
||||
expired on the bounce frame. That is his 8.98 s "waited for the bounce": the Brute had come in and
|
||||
dug his drop out weakly (q 0.3, 15 m/s), so it was a sitter, lost. The deadline is now the
|
||||
post-bounce window, less a frame.
|
||||
- **Point 3 (a 32 m/s topspin return of his serve into the left corner):** he ended up 1.5 m FORWARD and
|
||||
only 1 m left. Rebuilt headless twice (from the baseline, and after his own serve); the auto-run
|
||||
reached the ball both times (5-15 cm). Not reproduced. THEM lines now carry where the player stood
|
||||
(`px,pz`) and MISS lines where the auto-run was taking him (`runTo`), so the next one explains itself.
|
||||
- Observed, not changed: the CPU's "heavy topspin" carries its extra gravity THROUGH the bounce, so it
|
||||
skids low afterwards and nearly every contact in his logs is 0-25 cm off the grass. Real topspin
|
||||
kicks UP. Resetting `ball.g` at the bounce would fix the picture but moves every timing in the
|
||||
rally (all four planners), so it is a decision to put to him.
|
||||
|
||||
# OPEN — leftovers, in rough priority order
|
||||
|
||||
Nothing here is broken; these are known gaps and unverified risks.
|
||||
|
||||
+1
-1
@@ -1140,6 +1140,6 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
</div>
|
||||
</div>
|
||||
<span id="recLamp" hidden>REC</span>
|
||||
<script type="module" src="src/main.js?v=367"></script>
|
||||
<script type="module" src="src/main.js?v=368"></script>
|
||||
</body>
|
||||
</html>
|
||||
+2
-1
@@ -647,7 +647,8 @@ export const TUNE = {
|
||||
// the swing window, at any pace. 0 = the old distance ring (matchRingSweet/Band)
|
||||
ringBand: 0.35, // share of the half-window either side of the sweet moment that draws gold (edge = q 0.65)
|
||||
ringLead: 0.9, // s before contact the timed ring appears
|
||||
flickLead: 0.1, // s: a flick is timed from touch-down too, up to this much (a slow drag keeps a window)
|
||||
flickLead: 0, // s a flick's swing is back-dated toward touch-down (v366 used 0.1 — his next log showed
|
||||
// every 62-64 ms flick landing ~85 ms EARLY: a flick is timed by its swipe, not its touch)
|
||||
// ---- THE READ (2026-08-28, Oliver: past the HEAD COACH "the opponents are very fast and barely
|
||||
// make mistakes any more — we need a twist"). The dead staff LEARN YOUR PATTERNS: land the same
|
||||
// lane readHabit times in a row and the opponent has the habit — an EXPECTED ball is anticipated
|
||||
|
||||
+11
-4
@@ -378,7 +378,7 @@ function doHit(ch, quality, aim) {
|
||||
const kind = soft === null ? (aim && aim.az < 0 ? 'deep' : 'drive') + (ball.spin > 1 ? ' topspin' : ball.spin < 1 ? ' slice' : '')
|
||||
: soft === 0 ? 'drop' : soft >= 1 ? 'lob' : 'moonball';
|
||||
logSwing({ result: 'THEM', reason: `${kind} q=${r2(quality)} · ${Math.round(Math.hypot(ball.vx, ball.vz))}m/s${mark}`
|
||||
+ (ball.approach === ch.side ? ' · coming in' : '') });
|
||||
+ (ball.approach === ch.side ? ' · coming in' : ''), px: r2(P.x), pz: r2(P.z) }); // where HE stood when it left
|
||||
}
|
||||
G.wasServe = false; // a serve volleyed back pre-bounce must not fault-check the return
|
||||
ball.pow = quality; // carried pace: zombies splatter only above zSplatQ
|
||||
@@ -1618,7 +1618,8 @@ function logWhiff() {
|
||||
else reason = 'unclassified — in reach, in window, in height band'; // should not happen
|
||||
}
|
||||
logSwing({ result: 'MISS', reason: reason + ` · ${P.flick ? 'flick' : 'tap'} ${P.tap || 0}ms`, dMin: r2(best.d), y: r2(best.y), tSwing: r2(best.t),
|
||||
ballZ: r2(best.bz), bounces: best.bounces, px: r2(P.x), pz: r2(P.z) });
|
||||
ballZ: r2(best.bz), bounces: best.bounces, px: r2(P.x), pz: r2(P.z),
|
||||
...(P.tgt ? { runTo: r2(P.tgt.x) + ',' + r2(P.tgt.z) } : {}) }); // where the auto-run was taking him
|
||||
}
|
||||
|
||||
function trySwing(ch, isAI, qCap = TUNE.aiQualityMax) {
|
||||
@@ -1647,9 +1648,12 @@ function trySwing(ch, isAI, qCap = TUNE.aiQualityMax) {
|
||||
// the bounce will still land inside this window. If it will not, this is the last chance and the
|
||||
// low ball is dug out as a half-volley (the below-the-tape cap prices it): a swing a touch early
|
||||
// used to WHIFF here, which read as the game eating a good swing (Oliver's log, 2026-10-14).
|
||||
// The deadline is the window AFTER the bounce (the groundstroke one, shorter than the volley window
|
||||
// this swing is running on now) — judged against the long one, it waited and then expired on the
|
||||
// bounce frame (his v367 log, 8.98 s).
|
||||
if (letBounce()) {
|
||||
const gg = ball.g || TUNE.gravity, tb = (ball.vy + Math.sqrt(ball.vy * ball.vy + 2 * gg * ball.y)) / gg;
|
||||
if (t + tb < TUNE.swingWindup + win) return;
|
||||
if (t + tb < TUNE.swingWindup + TUNE.swingWindow * winLevel() - 0.03) return;
|
||||
}
|
||||
// quality: how centered in the window the contact lands
|
||||
const center = TUNE.swingWindup + win / 2;
|
||||
@@ -2027,7 +2031,10 @@ export function keyPressed(code, lead = 0, flick = false) {
|
||||
// gutem Timing nicht mehr bekommen"). input.js hands the touch's age in as `lead`, and the swing
|
||||
// clock starts that far along: the timing is judged from touch-down, the ball still has to be
|
||||
// there for the contact.
|
||||
P.swingT = rallyFeel() ? lead : 0; P.pose = 'swing'; P.poseT = 0; G.swung = true;
|
||||
// A FLICK is NOT back-dated by default (TUNE.flickLead 0): his v367 log showed every 62-64 ms flick
|
||||
// landing 76-92 ms EARLY once v366 timed flicks from touch-down — a flick is timed by its swipe,
|
||||
// not by the finger landing. The touch's age is still logged for both gestures.
|
||||
P.swingT = rallyFeel() ? (flick ? Math.min(lead, TUNE.flickLead) : lead) : 0; P.pose = 'swing'; P.poseT = 0; G.swung = true;
|
||||
P.tap = Math.round(lead * 1000); P.flick = flick; // ...and the log says which gesture, and how long it took to fire
|
||||
}
|
||||
}
|
||||
|
||||
+4
-5
@@ -207,11 +207,10 @@ cv.addEventListener('touchmove', e => {
|
||||
? { ax: Math.abs(dx) > dist * 0.38 ? Math.sign(dx) : 0,
|
||||
az: Math.abs(dy) > dist * 0.38 ? Math.sign(dy) : 0,
|
||||
fx: dx / dist, fy: dy / dist, mag: dist } : null;
|
||||
// ...timed, like the tap, from the moment the finger LANDED (gameplay.keyPressed's `lead`): the
|
||||
// timed gold ring means "touch now", and a flick only swings once it has travelled flickMin.
|
||||
// Capped at flickLead, so a slow drag still has a window to swing into (the left-flick lesson).
|
||||
// The touch's age rides along for the swing log; keyPressed decides how much of it the swing
|
||||
// clock is back-dated by (TUNE.flickLead — 0: a flick is timed by its swipe, his v367 log).
|
||||
if (G.swipe && !stick.swung && G.phase === 'rally') {
|
||||
stick.swung = true; keyPressed('Space', Math.min(TUNE.flickLead, (performance.now() - stick.t0) / 1000), true);
|
||||
stick.swung = true; keyPressed('Space', (performance.now() - stick.t0) / 1000, true);
|
||||
}
|
||||
}
|
||||
}, { passive: false });
|
||||
@@ -240,7 +239,7 @@ const endTouch = e => {
|
||||
az: Math.abs(dy) > dist * 0.38 ? Math.sign(dy) : 0,
|
||||
fx: dx / dist, fy: dy / dist, mag: dist, // raw vector: splatter free-aim
|
||||
t: performance.now() };
|
||||
if (!stick.swung) keyPressed('Space', Math.min(TUNE.flickLead, dt / 1000), true); // the crossing already swung — never twice per gesture
|
||||
if (!stick.swung) keyPressed('Space', dt / 1000, true); // the crossing already swung — never twice per gesture
|
||||
}
|
||||
stick = null; touchAxis.x = 0; touchAxis.z = 0; G.swipe = null;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user