The lob is the flick you already have, and a game stops being a death
Three from Oliver. THE LOB. He picked option (c) from the written comparison — "no additional gesture to learn" — and corrected the premise: the zombie does come to the net (racketAI chases to the far service line, the classic CPU to z 9.5). So the forward flick keeps its one meaning, "put it deep behind him", and the game picks the trajectory that actually does that: a smash while he is back, over his head once he is in. oppAtNet() is the whole switch, and it reads the BODY, not O — in the match O is the invisible tennis body and only moves between points, so it would have answered "on his mark" all rally. lobArm 7.0 m is measured, not taste: a lob peaking at lobApex passes a man 7 m from the net at almost exactly CONTACT.maxY, so the shot arms exactly as far out as it can still clear him. The pace is derived from the height, so the same arc comes off the baseline and off mid-court, and SHOT_DEPTH.lob is deeper than deep — at the ordinary deep target the ball passed 2.05 m over him at the edge of the band, which is inside his reach. PACE, SEEN FROM THE STRIKER. "the opponent sees the ball really fast - the player barely sees the speed increase." Measured: the ball is the same ball on both phones, the camera is not — it compresses your own ball into the distance and expands theirs as it arrives. Pace untouched; three channels added on the striker's side: the ghost streak now reads pace (3-8 ghosts, was 3 at every speed), the crack is pitched by it, and a fast ball kicks the camera. The serve's own ladder is 49.2 plain / 50.8-52.4 hard, because serveArc's loft margin takes 10% of the +9 back — widening that is his call, and PROJECT.md records the knob and the risk. THE DEATH MATCH. "each player has 8 blood points but the player explodes for the winner already at 0:40." Two clocks: a game is four points, a life is eight. A won game is only a game now (flash, serve, heal); the burst moved to the death itself, on the dead message, where both phones agree. The summary waits 1.4 s for the gore, because update() freezes the world on G.done and would have hung a still frame of an explosion behind the trophy screen. And bleedO tracks THEIR blood off the pos stream instead of our points-this-game, which reset every game and ignored the heal. 4 new tests, 113 pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6e07dc74bb
commit
4f4534e272
9 files changed
+355
-38
No files matched your search
+96
-1
@@ -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
|
||||
|
||||
Reference in new issue
Block a user