The aim cloud goes quiet, and a mistimed flick falls short of the line
Hidden behind TUNE.aimCloud rather than deleted: the reading it gives — how much width the swipe is buying, before the ball leaves — is the only teacher the gesture has, and the panel can flip it back live for the next look at it. And "a little harder to hit the out" is a number: flickReach 0.62 -> 0.52, which halves the worst-case rate from about 15% to about 7%, with the test tightened so it cannot drift back. The shape is untouched — a perfect flick still never sails, a badly timed full swipe still can — but a mistimed one now lands nearer the middle than the line, which is the only half of that trade a player who cannot see his own timing can do anything about. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1b16b422df
commit
7ff8671a22
5 files changed
+30
-7
No files matched your search
+11
-1
@@ -502,6 +502,13 @@ swayOf` reads the swipe's length as a continuous 0..1 (`TUNE.flickMin`..`flickFu
|
|||||||
the SERVE already did, and `doHit` spends that instead of `ax`. Keys have no length, so they keep
|
the SERVE already did, and `doHit` spends that instead of `ax`. Keys have no length, so they keep
|
||||||
the full lane, and the AI's aim (no `mag`) is untouched.
|
the full lane, and the AI's aim (no `mag`) is untouched.
|
||||||
|
|
||||||
|
**...and the out was tightened once more** (2026-08-18, *"make it a little harder to hit the out"*):
|
||||||
|
`flickReach` 0.62 -> 0.52, halving the worst-case rate from ~15% to ~7% and pinned by the test. The
|
||||||
|
shape is unchanged — a perfect flick still never sails, a badly timed full swipe still can — but a
|
||||||
|
mistimed one now lands NEARER THE MIDDLE rather than nearer the line, which is the only half of that
|
||||||
|
trade a player who cannot see his own timing can do anything about. **`flickReach` is the out dial**;
|
||||||
|
`aimError` is shared with the opponent's returns and with every aimed shot in the gore modes.
|
||||||
|
|
||||||
**This is also what hands the OUT back to the player.** Oliver: *"the number one reason i die is
|
**This is also what hands the OUT back to the player.** Oliver: *"the number one reason i die is
|
||||||
that i myself keep firing balls into the out."* Going out was never his choice — every aimed shot
|
that i myself keep firing balls into the out."* Going out was never his choice — every aimed shot
|
||||||
took the FULL width and then `err()` on top, so the dice decided. Width follows the swipe now, so
|
took the FULL width and then `err()` on top, so the dice decided. Width follows the swipe now, so
|
||||||
@@ -540,7 +547,10 @@ timing can keep in. Three things were learned making it read, all of them the ha
|
|||||||
obvious ramp showed nothing across the whole safe half of its range. The cool end carries its
|
obvious ramp showed nothing across the whole safe half of its range. The cool end carries its
|
||||||
contrast in LIGHTNESS, which is what makes it read on grass, clay and slab alike.
|
contrast in LIGHTNESS, which is what makes it read on grass, clay and slab alike.
|
||||||
The threshold is not a flip: the colour is already gold before you reach it, so "too hard" is
|
The threshold is not a flip: the colour is already gold before you reach it, so "too hard" is
|
||||||
something you watch coming. Only inside the window where a release would still BE a flick (`FLICK_MS`/`TUNE.flickMin`, the same
|
something you watch coming. **It is OFF as of its first playtest** (*"hide the indicator for now"*)
|
||||||
|
— `TUNE.aimCloud` gates the whole block and the tuning panel can flip it back live. Kept rather than
|
||||||
|
deleted: it is the only thing that shows the gesture's strength before the ball leaves, and the
|
||||||
|
lesson it teaches has no other teacher. Only inside the window where a release would still BE a flick (`FLICK_MS`/`TUNE.flickMin`, the same
|
||||||
gate `endTouch` uses) — past that the gesture is a run and a target hanging off it would be a lie.
|
gate `endTouch` uses) — past that the gesture is a run and a target hanging off it would be a lie.
|
||||||
Rally modes only (`rallyFeel`); gore's flick free-aims at a body, not at a lane. The placement ring
|
Rally modes only (`rallyFeel`); gore's flick free-aims at a body, not at a lane. The placement ring
|
||||||
turns `UI.bad` when the shot really is out, so the two rings together ARE the lesson: what you asked
|
turns `UI.bad` when the shot really is out, so the two rings together ARE the lesson: what you asked
|
||||||
|
|||||||
+1
-1
@@ -659,6 +659,6 @@ if (location.hostname !== 'splatter-tennis.wuwi.one') {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script type="module" src="src/main.js?v=287"></script>
|
<script type="module" src="src/main.js?v=288"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
+14
-4
@@ -69,12 +69,17 @@ export const TUNE = {
|
|||||||
flickWidth: 3.95, // m off centre a PERFECTLY timed flick reaches. The singles line is 4.115,
|
flickWidth: 3.95, // m off centre a PERFECTLY timed flick reaches. The singles line is 4.115,
|
||||||
// so this is the corner with 17 cm to spare. Raising it past the line does
|
// so this is the corner with 17 cm to spare. Raising it past the line does
|
||||||
// not make the flick punchier, it makes a perfect one go out.
|
// not make the flick punchier, it makes a perfect one go out.
|
||||||
flickReach: 0.62, // how much of an aimed shot's width a MISTIMED flick gets (1 = all of it).
|
flickReach: 0.52, // how much of an aimed shot's width a MISTIMED flick gets (1 = all of it).
|
||||||
// 0.42 made going out IMPOSSIBLE, which fixed the 8-in-10 bug and then
|
// 0.42 made going out IMPOSSIBLE, which fixed the 8-in-10 bug and then
|
||||||
// overshot it: "flicking: too easy. i must be able to play out." At 0.62
|
// overshot it: "flicking: too easy. i must be able to play out." At 0.62
|
||||||
// a mistimed flick CAN sail (~15% of the time at quality 0, on the worst
|
// a mistimed flick sailed ~15% of the time at quality 0 and a perfectly
|
||||||
// half of the scatter) and a perfectly timed one never can — the risk is
|
// timed one never could — the risk was back, and timing rather than a
|
||||||
// back and it is timing, not a coin toss, that decides it.
|
// coin toss decided it. 0.52 keeps that shape and halves the worst-case
|
||||||
|
// rate to ~7% ("make it a little harder to hit the out"): a badly timed
|
||||||
|
// flick now lands NEARER THE MIDDLE rather than nearer the line, which
|
||||||
|
// is the only half of the trade a player who cannot see his own timing
|
||||||
|
// can do anything about. THIS is the out dial — aimError is shared with
|
||||||
|
// the opponent's returns and with every aimed shot in the gore modes.
|
||||||
// Reach x quality must stay under the sideline once aimError is added on
|
// Reach x quality must stay under the sideline once aimError is added on
|
||||||
// top, or the flick goes out — which is exactly the bug this fixed:
|
// top, or the flick goes out — which is exactly the bug this fixed:
|
||||||
// 3.3 * 0.45 + 2.4 = 3.89 against a 4.11 m line. Raise it and the flick
|
// 3.3 * 0.45 + 2.4 = 3.89 against a 4.11 m line. Raise it and the flick
|
||||||
@@ -86,6 +91,11 @@ export const TUNE = {
|
|||||||
// of agency, the player can't really tell if there was skill or luck"). The serve already read its
|
// of agency, the player can't really tell if there was skill or luck"). The serve already read its
|
||||||
// flick as a continuous strength; the rally does now too, on the same shape of numbers.
|
// flick as a continuous strength; the rally does now too, on the same shape of numbers.
|
||||||
flickMin: 28, // px before a drag counts as a flick at all — input.js's own threshold
|
flickMin: 28, // px before a drag counts as a flick at all — input.js's own threshold
|
||||||
|
aimCloud: 0, // 1 draws the live aim cloud under the moving thumb (render's G.swipe
|
||||||
|
// block); 0 hides it. OFF after its first playtest — "hide the indicator
|
||||||
|
// for now" — kept as a switch rather than deleted because the reading it
|
||||||
|
// gives (how much width the swipe is buying, before the ball leaves) is
|
||||||
|
// the only teacher the gesture has, and the panel can flip it back live.
|
||||||
flickFull: 130, // ...and px of travel that buys the FULL width. About a third of a phone
|
flickFull: 130, // ...and px of travel that buys the FULL width. About a third of a phone
|
||||||
// screen: a short swipe is a safe half-court ball, and only a committed
|
// screen: a short swipe is a safe half-court ball, and only a committed
|
||||||
// one goes hunting the line, which is what makes going OUT a chosen risk.
|
// one goes hunting the line, which is what makes going OUT a chosen risk.
|
||||||
|
|||||||
+1
-1
@@ -2294,7 +2294,7 @@ export function render() {
|
|||||||
// walks out toward the corner as you swipe further, and turns red once you are asking for a ball
|
// walks out toward the corner as you swipe further, and turns red once you are asking for a ball
|
||||||
// the line cannot hold. Timing still decides how much of it you actually get — that is what the
|
// the line cannot hold. Timing still decides how much of it you actually get — that is what the
|
||||||
// receipt says, and the difference between the two rings is exactly the lesson.
|
// receipt says, and the difference between the two rings is exactly the lesson.
|
||||||
if (G.swipe && G.phase === 'rally') {
|
if (G.swipe && G.phase === 'rally' && TUNE.aimCloud) {
|
||||||
const sway = swayOf(G.swipe);
|
const sway = swayOf(G.swipe);
|
||||||
const x = sway * TUNE.flickWidth;
|
const x = sway * TUNE.flickWidth;
|
||||||
const z = G.swipe.fy < -0.38 ? SHOT_DEPTH.deep : G.swipe.fy > 0.38 ? SHOT_DEPTH.short : SHOT_DEPTH.mid;
|
const z = G.swipe.fy < -0.38 ? SHOT_DEPTH.deep : G.swipe.fy > 0.38 ? SHOT_DEPTH.short : SHOT_DEPTH.mid;
|
||||||
|
|||||||
@@ -807,6 +807,9 @@ test('an aimed return can be played OUT, but only by mistiming it', () => {
|
|||||||
};
|
};
|
||||||
assert.ok(pOut(0) > 0.02, 'a badly timed flick CAN go out: ' + (pOut(0) * 100).toFixed(1) + '%');
|
assert.ok(pOut(0) > 0.02, 'a badly timed flick CAN go out: ' + (pOut(0) * 100).toFixed(1) + '%');
|
||||||
assert.ok(pOut(0) < 0.25, '...but not the coin toss it was (every flick under q 0.66 went out)');
|
assert.ok(pOut(0) < 0.25, '...but not the coin toss it was (every flick under q 0.66 went out)');
|
||||||
|
// ...and "make it a little harder to hit the out" (2026-08-18) is a NUMBER, not a vibe: the
|
||||||
|
// worst-case rate was halved off 15%, and this is what stops flickReach drifting back up.
|
||||||
|
assert.ok(pOut(0) < 0.10, 'a mistimed flick lands nearer the middle than the line: ' + (pOut(0) * 100).toFixed(1) + '%');
|
||||||
assert.equal(pOut(1), 0, 'a perfectly timed flick never goes out');
|
assert.equal(pOut(1), 0, 'a perfectly timed flick never goes out');
|
||||||
for (let q = 0.05; q <= 1.0001; q += 0.05)
|
for (let q = 0.05; q <= 1.0001; q += 0.05)
|
||||||
assert.ok(pOut(q) <= pOut(q - 0.05) + 1e-9, `risk falls as timing improves (q ${q.toFixed(2)})`);
|
assert.ok(pOut(q) <= pOut(q - 0.05) + 1e-9, `risk falls as timing improves (q ${q.toFixed(2)})`);
|
||||||
|
|||||||
Reference in new issue
Block a user