diff --git a/PROJECT.md b/PROJECT.md
index fd06493..48bac38 100644
--- a/PROJECT.md
+++ b/PROJECT.md
@@ -493,6 +493,69 @@ it in one of the pauses") — the first cut rode the incoming ball, at the momen
least attention to spare. Touch only, rally modes only, and the tap lesson wins if both qualify:
teaching placement to someone who cannot reach the ball is the wrong lesson.
+**...and its LENGTH is its strength** (2026-08-18). Oliver: *"the flick to aim sometimes is applied
+very strong, sometimes it barely does anything. this leads to a lack of agency, the player can't
+really tell if there was skill or luck."* He was right and it was the gesture reader, not the feel:
+the rally flick was quantised to THREE LANES (`ax`, a swipe past 0.38 of its own length sideways),
+so two neighbouring gestures differed by the whole court and nothing lived in between. `gameplay.
+swayOf` reads the swipe's length as a continuous 0..1 (`TUNE.flickMin`..`flickFull`, px) exactly as
+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.
+
+**...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
+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
+the risk band has a floor the player can stop short of: **below `(line - aimError) / (flickWidth *
+flickReach)` ≈ 0.7 of a full swipe, the worst mistiming in the game cannot put the ball out**, and
+above it the old risk curve is untouched. The tested contract from the pass before this one still
+holds — a perfectly timed flick never sails, a badly timed full one can.
+
+**THE AUTO-RUN OWNS THE LEGS, so the drag does not** (2026-08-18, Oliver: *"could it be that when
+playing touch on mobile zombie tennis and tennis i am still able to manually move by dragging? in
+autorun this must be disabled"* — it could, and it was). It was not a harmless second option: a
+manual axis SUPPRESSES the assist (the `!mx && !mz` gate in the movement block), so holding a drag
+traded a full-speed run to the intercept point for a `0.35x` shuffle aimed at wherever the thumb
+went rather than at where the ball will be. Walking was strictly worse than standing still, which is
+most of *"i tend to not be able to reach the balls"*. `input.js` stops writing `touchAxis` under
+`rallyFeel()`; the drag keeps its one rally meaning (release it quickly and it is the aimed swing),
+keys stay a deliberate desktop override, and GORE keeps its drag-walk — there the assist recovers
+depth only and holding your own artillery lane is the point.
+
+**THE AIM IS DRAWN WHILE THE THUMB IS STILL MOVING.** Oliver asked to *"visualize the strength of my
+flick at flick-time"*, and he was asking for the half the placement ring cannot give: the ring is a
+RECEIPT, it arrives with the ball already gone. `G.swipe` (set by input.js during the drag, cleared
+on release) marks where the ball is being ASKED to land.
+
+**It is a cloud, not a diagram.** The first cut was a ring on a ray from the player's feet and it
+was rejected on sight (*"pretty bare bones... the line from the player to the indicator must go
+away, it is distracting"*): a line drawn across the court is a HUD element laid over the game.
+What is there now is a soft stain on the ground in the same procedural register as the dust and the
+blood — it GROWS with the length of the swipe and it WARMS as that ask approaches the width no
+timing can keep in. Three things were learned making it read, all of them the hard way:
+* **metres, not pixels** — sized off `p.s` so perspective shrinks it with distance. A pixel radius
+ looked fine at the baseline and was an 8-px smudge at the far corner, which is where it lives.
+* **a stain, not a glow** — `globalCompositeOperation = 'lighter'` turned the red end YELLOW over
+ green grass, and the hue is the entire message.
+* **the ramp is pale lime -> gold -> red, not green -> red** — green on green is invisible, so the
+ 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.
+The threshold is not a flip: the colour is already gold before you reach it, so "too hard" is
+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.
+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
+for, what your timing gave you, and the difference between them.
+
**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
diff --git a/index.html b/index.html
index 525b60e..a51f555 100644
--- a/index.html
+++ b/index.html
@@ -659,6 +659,6 @@ if (location.hostname !== 'splatter-tennis.wuwi.one') {
-
+