diff --git a/PROJECT.md b/PROJECT.md
index c3668d5..d538d48 100644
--- a/PROJECT.md
+++ b/PROJECT.md
@@ -491,6 +491,98 @@ zombie (now an occasional opponent, `TUNE.racketEvery`, not the every-game fixtu
`RETURNED`, `DOUBLE KILL`) is a footnote to something you already watched happen. Sharing the loud
FACE with the milestone cards is right; sharing their SIZE made every footnote read as a milestone.
+## Session log: THE LOB, AND TWO THINGS THE MULTIPLAYER TEST FOUND (2026-08-22, same day)
+
+### THE LOB IS THE FLICK YOU ALREADY HAVE — option (c), picked by Oliver after a written comparison
+
+Oliver: *"when you're close to the net in a duel with another player there should be a gesture
+allowing to make a ball flying high from below (lupfen)... this likely needs to be discussed."*
+Three candidates were put to him — (a) slow drag vs fast flick, (b) a down-then-up hook, (c) context
+arms it — and he picked (c): *"i think this is the easiest. no additional gesture to learn."* He also
+corrected the premise: *"the opponent should come to the net too — in zombie tennis the zombie often
+comes to the net"*, and he is right (racketAI chases up to `COURT.svcFar`, the classic CPU to z 9.5).
+
+**The rule: a forward flick means "put it deep behind him", and the game picks the trajectory that
+actually does that.** With him on his baseline that is the smash it has always been; with him in, it
+is over his head. `oppAtNet()` in gameplay.js is the whole switch. Four things it rests on:
+
+1. **It reads the BODY, not O.** In the duel and the match the opponent you play is the racket
+ zombie; O is the invisible tennis body the serve machinery is driven off and it only moves
+ between points, so asking O would have answered "on his mark" all rally, in the one mode Oliver
+ plays most. `farBody()` picks the 'r' zombie whenever there is one and is not netplay.
+2. **`TUNE.lobArm` 7.0 m is not taste.** A lob peaking at `lobApex` passes a man 7 m from the net at
+ almost exactly `CONTACT.maxY` (2.3 m, the height above which nothing can be struck) — so the shot
+ arms EXACTLY as far out as it can still clear him. Measured, not guessed; the numbers are in the
+ suite. Move one of the three and re-measure the other two.
+3. **The pace is DERIVED from the height, never chosen.** `vy = sqrt(2g(apex - y))` fixes the hang
+ time and the ground still to cover fixes the speed, so the same arc comes off the baseline (9.5
+ m/s) and off mid-court (5.8 m/s). A fixed speed was tried on paper first and is a fine lob from
+ one place and a flat drive from the other. It also carries NO topspin multiplier — the dive that
+ makes a hard flat ball land in would pull a lob into the net.
+4. **`SHOT_DEPTH.lob` is DEEPER than `deep` (2.0 vs 3.0 m).** At the ordinary deep target the ball
+ passes only a metre or two behind the man it is going over, and at the far edge of the arm band
+ that was 2.05 m — inside his reach. The shot is defined by where it comes DOWN.
+
+Measured, nominal (no scatter), from the baseline: over a man at the far service line it passes at
+**3.25 m**, lands 2 m inside his baseline, 2.17 s of hang. Over one at z 9.5 (a real net-rusher):
+5.4 m. The AI can and does run it down — a lob is not a free point in tennis either — but it pulls
+him off the net, which is what the shot is for. The word `LOB` pops at the contact: (c) buys "no
+gesture to learn" by letting context change what the flick means, and something has to say which
+shot you just asked for.
+
+### "THE OPPONENT SEES THE BALL REALLY FAST — THE PLAYER BARELY SEES THE SPEED INCREASE"
+
+Measured first, because the report has two possible causes and only one of them is a bug.
+**The ball is the same ball on both phones** — `netSendShot` copies vx/vy/vz/g verbatim and the
+receiver mirrors them, no re-simulation. What differs is the CAMERA: it sits behind your baseline,
+so your own ball's screen speed decays into the distance while the same shot expands toward the man
+receiving it. That asymmetry cannot be fixed by changing pace.
+
+The pace ladder, measured: **serve 49.2 plain / 50.8-52.4 with the hard flick** (the +9 m/s
+`serveHard` asks for 56-58, and `serveArc`'s net-clearance loop takes 10% of it back in one cut);
+**rally 19.2 + 14.4×timing, ×1.42 for a smash at flush timing** — so the smash is a real 42% when
+you time it and nothing when you do not, and the hard SERVE is only ever ~3-6%.
+
+Nothing about the pace was changed. Three feedback channels were added instead, all on the striker's
+side, none of them touching a ball's physics:
+- **The ghost streak now reads PACE.** It was three ghosts at every speed, so it said fast/not-fast
+ and nothing else; it is 3 to 8 now, sampled every frame, so a quick ball is both longer and more
+ spread out. A lob stays clean.
+- **The crack is pitched by pace** (`strikeRate`, 0.93 to 1.09 — past a tenth the sample stops
+ sounding like the same racket).
+- **A fast ball kicks the camera** (`strikeKick`, up to ~2.2 px for 0.12 s, YOUR racket only, and
+ never over a shake already running).
+
+**Still open, and it is Oliver's call.** If he wants the hard serve to actually BE harder, the knob
+is `serveArc`'s loft margin: it charges `0.06 + descent×0.04` (about 0.46 m of extra net clearance
+on a fast serve) against a sampling error the sim does not really make any more — the net test in
+`update()` already interpolates the crossing height, and semi-implicit Euler's real lag at the tape
+measures ~2-17 cm. Cutting that margin gives the hard serve its full +9 back (58 vs 49, a difference
+you could see). Not done unasked: a serve into the tape is worse than a slow one, and that margin
+exists because balls were clipping it.
+
+### THE DEATH MATCH: THE BURST IS THE DEATH, AND ONLY BLOOD IS
+
+Oliver: *"each player has 8 blood points but the player explodes for the winner already at 0:40 —
+the winner sees the zombie explode but the player on the other side is not dead yet. this needs to
+be aligned."* Two clocks were running side by side: **a game is four points, a life is eight**, and
+`matchGameWon` burst the friend's body on the game. Both were true on their own phone and they
+disagreed with each other.
+
+- **A won game is only a game now** (netplay branch of `matchGameWon`): the flash, the serve change
+ and the heal it pays, then the next point. No burst, no walk-on, no mop beat — there is no roster
+ body to walk back on in a death match anyway.
+- **The burst moved to the death**, in `netMatchWon`, which fires on the `dead` message the dying
+ phone sends when its own blood runs out. Both sides are then agreeing about the same event.
+- **The summary waits `TUNE.netWinBurst` (1.4 s) for the gore to fly.** `update()` freezes the whole
+ world on `G.done` — no particles — so setting it on the same frame as the burst hangs a still
+ frame of an explosion behind the trophy screen. Ticked in `tickMatchFall`, like every other beat
+ in beats.js that has to outlive the frame that started it.
+- **The friend's bleeding tracks THEIR blood.** `bleedO` read points-won-this-game, which resets
+ every game and ignores the heal — a friend two points from death looked unhurt. Their hp now rides
+ the 10 Hz `pos` stream (one number; a dropped one corrects itself 100 ms later) and the flow reads
+ it. The old count survives as the fallback for a build that sends none.
+
## Session log: THE SERVICE RULES ARRIVE IN THE OTHER MODES (2026-08-22)
Three notes from Oliver, two of them fixed here; the third (the lob) is under discussion in OPEN.
@@ -3941,7 +4033,10 @@ Nothing here is broken; these are known gaps and unverified risks.
from a tin). At 12 rounds/s a per-pellet sample will machine-gun the mixer, not just the wave.
- **boss sounds** — footfall, wind-up, throw, wound, death. Three designs in `BOSS_ART`; decide
whether they share one set or get one each before recording anything.
-14. **THE LOB ("lupfen") — asked for 2026-08-22, UNDER DISCUSSION, do not build it yet.** Oliver:
+14. ~~**THE LOB ("lupfen")**~~ SHIPPED 2026-08-22 as option (c) — see the session log. The rest of
+ this entry is the discussion it came out of, kept because the two rejected options are the
+ argument for the one that shipped.
+ **~~asked for 2026-08-22, UNDER DISCUSSION, do not build it yet.~~** Oliver:
*"when you're close to the net in a duel with another player there should be a gesture allowing
to make a ball flying high from below... I don't know how to implement this gesture wise — this
likely needs to be discussed."* The shot itself is nearly free: `trySwing` already solves every
diff --git a/index.html b/index.html
index 9244102..096559e 100644
--- a/index.html
+++ b/index.html
@@ -664,6 +664,6 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
-
+