An aimed shot keeps its direction: the scatter can push it wider, never back past centre

Second round on the service-line flick report. The crossing-fired swing
alone did not move the feel, and Oliver's sharpened observation — the
flick only lands left when the opponent already stands left — pointed at
a magnet that does not exist: measured with the body teleported at
contact, the aim never tracks it. The real mechanism: at rushed quality
the symmetric scatter (aimError x (1-q)) outweighs the quality-shrunk
aim, so a mistimed left flick landed anywhere in a ~4 m blob around
'slightly left' — usually centre — and the eye anchors that on the
opponent. Its position was a proxy for preparation time all along.

TUNE.aimTrust: an outward scatter roll is untouched — the committed
swipe's chance of sailing keeps its exact tuned shape, flickReach stays
the only out dial — but an inward roll keeps only 0.3 of itself. Bad
timing costs width, never direction. Player lane shots only; the
opponent's symmetric scatter is its width mechanic. Verified at
worst-case q~0.08: 14/14 rushed left flicks landed left, 9/9 right
flicks right, where before it was a coin toss. The suite pins the
direction guarantee and the aimTrust/aimError/flickReach relationship.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Fable 5 committed 2026-08-20 14:03:57 +02:00
1 parent 6f9dea4ffc
commit 60127436a0
5 files changed
+63 -2

No files matched your search

+18
View File
@@ -579,6 +579,24 @@ serve gesture, the duel and gore are untouched; keys unchanged. Verified with re
held-swipe contact aims exactly `swayOf`, a 360 ms swipe (previously dead) swings and aims, and a
release-before-arrival still aims through `takeFlickAim`.
**...and an aimed shot keeps its DIRECTION** (2026-08-20, same report, second round — the crossing
fire alone "did not feel much different", and his sharpened observation was that the flick only
seemed to work when the opponent already stood where he aimed). Measured with the opponent
teleported at contact: the aim never tracked the body — no magnet exists — but at rushed quality
the SYMMETRIC scatter (±aimError·(1-q)) outweighed the quality-shrunk aim, so a mistimed left flick
landed anywhere in a ~4 m blob around "slightly left": usually centre, sometimes right, and the eye
anchors that on wherever the opponent stands. The opponent's position was a proxy for preparation
time (a body parked wide = its return flew a long slow diagonal = time = quality). The dial:
**`TUNE.aimTrust` — an OUTWARD scatter roll is untouched** (a committed mistimed swipe still sails
at the exact tuned rate; `flickReach` stays the only out dial) **but an inward roll keeps only
`aimTrust` (0.3) of itself**, so bad timing costs width, never the direction that was asked for.
Player lane shots only: the opponent's symmetric scatter is its width mechanic, and gore's
free-aim never used lanes. Verified in real rallies at worst-case q≈0.08: 14/14 rushed left flicks
landed left (−0.8..−3.5), 9/9 right flicks right — previously the same condition was a coin toss.
The suite pins the direction guarantee and the knob relationship (`aimTrust·aimError` must stay
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.
**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
View File
@@ -659,6 +659,6 @@ if (location.hostname !== 'splatter-tennis.wuwi.one') {
</div>
</div>
</div>
<script type="module" src="src/main.js?v=290"></script>
<script type="module" src="src/main.js?v=291"></script>
</body>
</html>
+8
View File
@@ -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
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
// lands SHORTER LEFT rather than center-or-right "wherever the dice
// said". Player's lane shots only — the opponent's symmetric scatter is
// its width mechanic, and the gore free-aim never used lanes. Must stay
// under 0.5*flickWidth*flickReach/aimError (~0.43) or a committed
// mistimed swipe can cross back past centre (pinned by the suite).
// NOT `aimReach` — that name is taken further down by the slingshot's aim-ray LENGTH (22 m), and
// the duplicate key silently won: every aimed shot reached 22x its width until a test caught it.
flickWidth: 3.95, // m off centre a PERFECTLY timed flick reaches. The singles line is 4.115,
+12 -1
View File
@@ -178,7 +178,18 @@ function doHit(ch, quality, aim) {
// it just stops being a coin toss you are punished for calling.
const reach = ch.side === 'P' ? TUNE.flickReach + (1 - TUNE.flickReach) * quality : 1;
sway = swayOf(aim); // -1..1, continuous on touch — see swayOf
tx = sway * (toFar ? TUNE.flickWidth : 2.7) * reach + err();
// AN AIMED SHOT KEEPS ITS DIRECTION. The scatter used to be symmetric, and at rushed quality
// it was BIGGER than the quality-shrunk aim — a mistimed left flick landed anywhere in a 4 m
// blob around "slightly left", i.e. usually centre and sometimes right. It read as the ball
// landing wherever the opponent stood (2026-08-20, "trying to move the approaching ball with
// a flick to the left is almost impossible"). An OUTWARD roll is untouched — the committed
// swipe's chance of sailing wide is a chosen risk with a tuned shape (flickReach) and this
// must not soften it — but an inward roll now keeps only TUNE.aimTrust of itself, so bad
// timing costs WIDTH, never the direction that was asked for. Player only: the opponent's
// 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;
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
+24
View File
@@ -852,6 +852,30 @@ test('a gentle flick cannot be played out — only a committed one can', () => {
assert.ok(safe > 0.4 && safe < 1, 'the ring turns hot inside the swipe range, not at either end');
});
test('an aimed shot keeps its DIRECTION: scatter may push it wider, never back past centre', () => {
// Dial 1 (2026-08-20). At rushed quality the symmetric scatter outweighed the quality-shrunk
// aim, so a mistimed left flick landed "wherever" — usually centre, read as the ball following
// the opponent. The model mirrors doHit's lane path: outward rolls untouched (the chosen
// out-risk keeps its exact tuned shape), inward rolls keep only aimTrust of themselves.
const land = (s, q, u) => { // u in [-1,1]: the raw scatter roll
const reach = TUNE.flickReach + (1 - TUNE.flickReach) * q;
let e = u * TUNE.aimError * (1 - q);
if (s && e * Math.sign(s) < 0) e *= TUNE.aimTrust;
return s * TUNE.flickWidth * reach + e;
};
const line = COURT.W / 2;
for (let q = 0; q <= 1; q += 0.1) {
assert.ok(land(0.5, q, -1) > 0, 'a committed right swipe stays right at q=' + q.toFixed(1)
+ ': ' + land(0.5, q, -1).toFixed(2));
assert.ok(land(-0.5, q, 1) < 0, '...and a committed left swipe stays left, mirrored');
}
assert.ok(land(1, 0, 1) > line, 'the outward tail is untouched: a mistimed full swipe can still sail');
assert.ok(land(1, 1, 1) < line, 'and a perfectly timed one still never does');
// the knob relationship the direction guarantee rests on — retuning must keep it
assert.ok(TUNE.aimTrust * TUNE.aimError < 0.5 * TUNE.flickWidth * TUNE.flickReach,
'aimTrust*aimError must stay under a committed swipe\'s worst-case aim');
});
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