The flick aims relative to the player: a full sweep from where you stand, not from the court's centre
Third round of the flick report, and this was the core all along: 'when the opponent and the player are both standing in the left service box it is virtually impossible to flick the ball into the left out. no matter how hard/good I flick.' The lane was absolute — a full flick asked for flickWidth off the court's CENTRE — so the gesture saturated near a sideline: from x -2 a perfect left flick moved the ball under 2 m and could never pass the line, and the better the timing the firmer the cap, because scatter was the only thing that could cross it. 'Left is impossible, right works' was never left-vs-right: it was toward-the-near-sideline vs across-the-court. TUNE.aimAnchor: the anchor slides from the court centre to the player's own x as the swipe commits (ch.x * |sway| * aimAnchor), so a flat tap is exactly what it always was and a full flick is a full sweep from where you stand — including past the line. Out-by-aim is a choice now, per Oliver's explicit direction; the placement ring already turns red for it. 0 restores the absolute lane, blendable live in the panel. Player rally lanes only; the opponent's lanes and the centre-stated pOut invariants are untouched. Verified in his exact scene: full left flick from the left box lands -5 (the alley), half swipe -3.07, full right +1.93. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
60127436a0
commit
7e5c3ab399
5 files changed
+75
-2
No files matched your search
+18
@@ -597,6 +597,24 @@ The suite pins the direction guarantee and the knob relationship (`aimTrust·aim
|
||||
under a committed swipe's worst-case aim). Whether placement should also BEAT an unwounded body
|
||||
more often (`aiRead` on wide balls — "dial 2") is open, awaiting the phone verdict on this one.
|
||||
|
||||
**...and the flick aims RELATIVE TO THE PLAYER** (2026-08-20, third round, and this was the core
|
||||
all along: *"when the opponent and the player are both standing in the left service box it is
|
||||
virtually impossible to flick the ball into the left out. no matter how hard/good I flick"*). The
|
||||
lane was ABSOLUTE — a full flick asked for `flickWidth` off the court's CENTRE — so the gesture
|
||||
saturated near a sideline: from x −2 a perfect left flick moved the ball under 2 m and could never
|
||||
pass the line (by design, 3.95 vs 4.115 — and the better the timing, the firmer the cap, because
|
||||
scatter was the only thing that could cross). The felt "left is impossible / right works" was
|
||||
never left-vs-right: it was toward-the-near-sideline vs across-the-court. **`TUNE.aimAnchor`**:
|
||||
the aim's anchor slides from the court centre to the player's own x as the swipe commits
|
||||
(`anchor = ch.x · |sway| · aimAnchor`), so a flat tap is exactly what it always was and a full
|
||||
flick is a full ±flickWidth sweep FROM WHERE YOU STAND — including past the line. **Out-by-aim
|
||||
exists now and is a choice** (his explicit direction: *"not about the possibility of the ball
|
||||
going out but rather the aiming itself"*); the placement ring already turns red for it. 0 restores
|
||||
the old absolute lane, blendable, panel-tunable for live A/B. Player rally lanes only — the
|
||||
opponent's lanes and the pOut invariants are stated from centre and unchanged there. Verified in
|
||||
his exact scene (both bodies in their left boxes): full left flick lands −5 (the alley), half
|
||||
swipe −3.07 (hunting the line, in), full right +1.93 (a full sweep measured from him).
|
||||
|
||||
**The umpire and the boy.** `aside()` refused during any interlude and the match runs on a permanent
|
||||
one, so he was silently muted for the entire mode — the one mode that is literally a tennis match he
|
||||
is sitting over (`G.interlude && !G.match` now, the same shape as `sendBoy`). Six dry lines: two on
|
||||
|
||||
+1
-1
@@ -659,6 +659,6 @@ if (location.hostname !== 'splatter-tennis.wuwi.one') {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="src/main.js?v=291"></script>
|
||||
<script type="module" src="src/main.js?v=292"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -64,6 +64,14 @@ export const TUNE = {
|
||||
// send the ball longer — solveShot still lands it on the same spot — it
|
||||
// gets there flatter and sooner, which is what a smash is.
|
||||
aimError: 2.4, // m of scatter at quality 0
|
||||
aimAnchor: 1, // how much the flick's lane follows the PLAYER instead of the court.
|
||||
// 0 = the old absolute lane (a full flick asks for flickWidth off the
|
||||
// court's centre — saturates near a sideline: from x -2 a perfect left
|
||||
// flick moved the ball under 2 m and could never find the line). 1 = the
|
||||
// anchor slides to your own x as the swipe commits, so a full flick is a
|
||||
// full sweep from where you stand — including past the line: out-by-aim
|
||||
// is a choice now, and the ring already turns red for it. Blendable so
|
||||
// the panel can A/B the feel live. Player's rally lanes only.
|
||||
aimTrust: 0.3, // an aimed rally shot TRUSTS its flick: the scatter may still push the
|
||||
// ball further OUT (the chosen out-risk keeps its exact shape) but only
|
||||
// this fraction of an INWARD roll survives, so a mistimed left flick
|
||||
|
||||
+14
-1
@@ -189,7 +189,20 @@ function doHit(ch, quality, aim) {
|
||||
// symmetric scatter is its width mechanic (matchWide clamps it, not this).
|
||||
let e = err();
|
||||
if (ch.side === 'P' && sway && e * Math.sign(sway) < 0) e *= TUNE.aimTrust;
|
||||
tx = sway * (toFar ? TUNE.flickWidth : 2.7) * reach + e;
|
||||
// THE FLICK AIMS RELATIVE TO YOU, not to the court (2026-08-20, third round of the same
|
||||
// report: "when the opponent and the player are both standing in the left service box it is
|
||||
// virtually impossible to flick the ball into the left out. no matter how hard/good I flick").
|
||||
// The lane was ABSOLUTE — a full flick asked for 3.95 m from the court's centre — so standing
|
||||
// near a sideline saturated the gesture: from x -2 a perfect left flick moved the ball under
|
||||
// 2 m and could never pass the line, while the same swipe from centre swept the court. The
|
||||
// anchor now slides from centre to the player's own x as the swipe commits (|sway|), so a
|
||||
// flat tap is exactly what it always was and a full flick is a full sweep FROM WHERE YOU
|
||||
// STAND — including past the line: out-by-aim is his explicit ask ("not about the possibility
|
||||
// of the ball going out but rather the aiming itself"). TUNE.aimAnchor blends it (0 = the old
|
||||
// absolute lane, 1 = fully relative) so the panel can A/B the feel live. Player only; the
|
||||
// opponent's lanes and every pOut invariant are stated from centre and unchanged there.
|
||||
const anchor = ch.side === 'P' ? ch.x * Math.abs(sway) * TUNE.aimAnchor : 0;
|
||||
tx = anchor + sway * (toFar ? TUNE.flickWidth : 2.7) * reach + e;
|
||||
const tzBase = aim.az < 0 ? SHOT_DEPTH.deep : aim.az > 0 ? SHOT_DEPTH.short : SHOT_DEPTH.mid;
|
||||
tz = (toFar ? tzBase : COURT.L - tzBase) + err() * 0.8;
|
||||
// A FORWARD FLICK IS A SMASH. Up-screen is the far court, so flicking INTO the court asked for
|
||||
|
||||
@@ -876,6 +876,40 @@ test('an aimed shot keeps its DIRECTION: scatter may push it wider, never back p
|
||||
'aimTrust*aimError must stay under a committed swipe\'s worst-case aim');
|
||||
});
|
||||
|
||||
test('the flick aims RELATIVE TO THE PLAYER: a full sweep from where you stand, not from centre', () => {
|
||||
// Third round of the service-line report: the lane was absolute, so standing near a sideline
|
||||
// saturated the gesture — from the left box a perfect left flick moved the ball under 2 m and
|
||||
// could never find the line ("no matter how hard/good I flick"). The anchor follows the player
|
||||
// as the swipe commits (TUNE.aimAnchor); a flat tap keeps its old centre default.
|
||||
startMatch();
|
||||
G.modal = null; G.modalQ.length = 0;
|
||||
const arm = (px, swipe) => { // one rally ball, contact at the window's centre
|
||||
G.interlude.introT = 0; G.phase = 'rally';
|
||||
P.x = px; P.z = 22.8; P.swingT = -1; G.placeMark = null;
|
||||
ball.active = true; ball.lastHit = 'O'; ball.bounces = 1;
|
||||
ball.x = px; ball.y = 0.7; ball.z = P.z - 4.15;
|
||||
ball.vx = 0; ball.vy = 2.0; ball.vz = 14; ball.g = TUNE.gravity;
|
||||
G.swipe = swipe;
|
||||
keyPressed('Space');
|
||||
for (let i = 0; i < 30 && ball.lastHit !== 'P'; i++) update(DT);
|
||||
assert.equal(ball.lastHit, 'P', 'contact happened');
|
||||
G.swipe = null;
|
||||
return G.placeMark;
|
||||
};
|
||||
const full = fx => ({ ax: Math.sign(fx), az: 0, fx, fy: 0, mag: TUNE.flickFull });
|
||||
const line = COURT.W / 2;
|
||||
// from the LEFT service box, a committed left flick reaches PAST the line — out is aimable now
|
||||
const left = arm(-2, full(-1));
|
||||
assert.ok(left && left.x < -line, 'full left flick from the left box finds the alley: x=' + left.x.toFixed(2));
|
||||
// ...and a right flick from the same spot lands a full sweep right OF THE PLAYER, near centre-right
|
||||
const right = arm(-2, full(1));
|
||||
assert.ok(right && right.x > 1.2 && right.x < 2.8, 'full right flick sweeps from the player: x=' + right.x.toFixed(2));
|
||||
// an unaimed tap is untouched by where you stand: no mark, and the ball heads for the centre
|
||||
assert.equal(arm(-2, null), null, 'a flat tap from the box stamps no mark, exactly as before');
|
||||
assert.ok(Math.abs(ball.vx * ((3 - ball.z) / ball.vz) + ball.x) < TUNE.aimError + 1,
|
||||
'and its line stays the old centre default, not the player lane');
|
||||
});
|
||||
|
||||
test('the flick aims from the LIVE swipe: contact reads G.swipe, no release needed', () => {
|
||||
// The swing fires the moment the drag crosses flickMin now (input.js), so at contact the finger
|
||||
// is usually still down and no released flickAim exists yet. The aim has to come from the live
|
||||
|
||||
Reference in new issue
Block a user