diff --git a/HANDOFF-MATCH.md b/HANDOFF-MATCH.md index 14b95cb..8f95619 100644 --- a/HANDOFF-MATCH.md +++ b/HANDOFF-MATCH.md @@ -202,6 +202,37 @@ The placement ring carries `q` now and reads as the flick's receipt: **tight and earned the full width, wide and dim when you barely got a racket on it.** Without it the new rule is invisible — the shot simply goes less wide and nothing says why. +## THE CARD — eight bodies, one ladder (2026-08-12) + +`config.OPPONENTS` is the narrative AND the difficulty curve, and they are the same list read twice: +the further you get, the further up the club's hierarchy the corpse across the net used to be. +SHAMBLER, BALL BOY, GROUNDSKEEPER, STEWARD, RUNNER, LINE JUDGE, CLUB PRO, THE BULK, DIRECTOR, +CHAMPION. Six of those wear NEW sprite families generated through the one-sheet pipeline +(`sh bb st ru gk lj`), the CLUB PRO is the original `z_r*` body and THE BULK is the brute. + +**The ramp is asserted, not assumed** — a test walks all ten and requires the whiff rate to fall every +single rung (1.35 -> 0.5 on the trait, on top of `matchFlubKeep`), the names/`short` to be complete +and to fit the board's 8-char slot, and speed never to step meaningfully backwards. Speed is allowed +to ZIGZAG by character (5 is the RUNNER, 6 paints lines instead of sprinting); the whiff rate is the +dial the ramp actually rides on. + +Per-rung growth was steepened at the same time (`matchSpeedPer` .08->.11, `matchOppPacePer` +.035->.05, `matchFlubKeep` .82->.76): "in general this is too easy now". The flick fix means the +player no longer hands over free points by sailing returns out, so the ladder had to earn them back. +The OPENING is deliberately softer than before — the shambler starts at 0.72 where the old first +opponent was 0.85. Both ends moved. + +**The umpire finally speaks in this mode.** `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. Same shape as `sendBoy`, fixed the same way (`G.interlude && +!G.match`). Six new dry lines through the existing `say -v Daniel -r 168` pipeline: two on taking a +limb, two on your blood, one when your blood is nearly gone, one on felling the thing. The shared +`asideEvery` cooldown still holds, which is what keeps him situational rather than chatty. + +**And the mop boy comes out between games**, to the FAR court (`sendBoy(0, 4.5, true)` from +`matchGameWon`) — that is where one body has been bleeding through four wound stages, and it keeps +him out of the near court where you are about to serve. Between games only; never mid-point. + ## Traps this mode has already sprung (do not re-learn these) - **`autoAssist` is 0 on `pointer: fine`.** The desktop pane has no auto-run. The match floors it diff --git a/assets/audio/ump_felled.m4a b/assets/audio/ump_felled.m4a new file mode 100644 index 0000000..0b96c66 Binary files /dev/null and b/assets/audio/ump_felled.m4a differ diff --git a/assets/audio/ump_low_blood.m4a b/assets/audio/ump_low_blood.m4a new file mode 100644 index 0000000..d55784e Binary files /dev/null and b/assets/audio/ump_low_blood.m4a differ diff --git a/assets/audio/ump_wound_opp.m4a b/assets/audio/ump_wound_opp.m4a new file mode 100644 index 0000000..9724726 Binary files /dev/null and b/assets/audio/ump_wound_opp.m4a differ diff --git a/assets/audio/ump_wound_opp2.m4a b/assets/audio/ump_wound_opp2.m4a new file mode 100644 index 0000000..caa92fd Binary files /dev/null and b/assets/audio/ump_wound_opp2.m4a differ diff --git a/assets/audio/ump_wound_you.m4a b/assets/audio/ump_wound_you.m4a new file mode 100644 index 0000000..7de6ca0 Binary files /dev/null and b/assets/audio/ump_wound_you.m4a differ diff --git a/assets/audio/ump_wound_you2.m4a b/assets/audio/ump_wound_you2.m4a new file mode 100644 index 0000000..1ebc423 Binary files /dev/null and b/assets/audio/ump_wound_you2.m4a differ diff --git a/assets/z_bb1.png b/assets/z_bb1.png new file mode 100644 index 0000000..82ff0ce Binary files /dev/null and b/assets/z_bb1.png differ diff --git a/assets/z_bb2.png b/assets/z_bb2.png new file mode 100644 index 0000000..6791349 Binary files /dev/null and b/assets/z_bb2.png differ diff --git a/assets/z_bb3.png b/assets/z_bb3.png new file mode 100644 index 0000000..810b265 Binary files /dev/null and b/assets/z_bb3.png differ diff --git a/assets/z_bb4.png b/assets/z_bb4.png new file mode 100644 index 0000000..82543ab Binary files /dev/null and b/assets/z_bb4.png differ diff --git a/assets/z_bbbounce.png b/assets/z_bbbounce.png new file mode 100644 index 0000000..4794fcd Binary files /dev/null and b/assets/z_bbbounce.png differ diff --git a/assets/z_bbfall1.png b/assets/z_bbfall1.png new file mode 100644 index 0000000..f5efda3 Binary files /dev/null and b/assets/z_bbfall1.png differ diff --git a/assets/z_bbfall2.png b/assets/z_bbfall2.png new file mode 100644 index 0000000..fda1630 Binary files /dev/null and b/assets/z_bbfall2.png differ diff --git a/assets/z_bbfall3.png b/assets/z_bbfall3.png new file mode 100644 index 0000000..8520012 Binary files /dev/null and b/assets/z_bbfall3.png differ diff --git a/assets/z_bbrise.png b/assets/z_bbrise.png new file mode 100644 index 0000000..fc7a23d Binary files /dev/null and b/assets/z_bbrise.png differ diff --git a/assets/z_bbserve.png b/assets/z_bbserve.png new file mode 100644 index 0000000..125f066 Binary files /dev/null and b/assets/z_bbserve.png differ diff --git a/assets/z_bbtoss.png b/assets/z_bbtoss.png new file mode 100644 index 0000000..0c6ae38 Binary files /dev/null and b/assets/z_bbtoss.png differ diff --git a/assets/z_bbw1.png b/assets/z_bbw1.png new file mode 100644 index 0000000..a0b3dde Binary files /dev/null and b/assets/z_bbw1.png differ diff --git a/assets/z_bbw1b.png b/assets/z_bbw1b.png new file mode 100644 index 0000000..fc4b448 Binary files /dev/null and b/assets/z_bbw1b.png differ diff --git a/assets/z_bbw1bounce.png b/assets/z_bbw1bounce.png new file mode 100644 index 0000000..a829826 Binary files /dev/null and b/assets/z_bbw1bounce.png differ diff --git a/assets/z_bbw1fall1.png b/assets/z_bbw1fall1.png new file mode 100644 index 0000000..d89cca6 Binary files /dev/null and b/assets/z_bbw1fall1.png differ diff --git a/assets/z_bbw1fall2.png b/assets/z_bbw1fall2.png new file mode 100644 index 0000000..ba788c0 Binary files /dev/null and b/assets/z_bbw1fall2.png differ diff --git a/assets/z_bbw1fall3.png b/assets/z_bbw1fall3.png new file mode 100644 index 0000000..6bc1b16 Binary files /dev/null and b/assets/z_bbw1fall3.png differ diff --git a/assets/z_bbw1rise.png b/assets/z_bbw1rise.png new file mode 100644 index 0000000..b99236c Binary files /dev/null and b/assets/z_bbw1rise.png differ diff --git a/assets/z_bbw1s.png b/assets/z_bbw1s.png new file mode 100644 index 0000000..3c56eed Binary files /dev/null and b/assets/z_bbw1s.png differ diff --git a/assets/z_bbw1serve.png b/assets/z_bbw1serve.png new file mode 100644 index 0000000..eba6782 Binary files /dev/null and b/assets/z_bbw1serve.png differ diff --git a/assets/z_bbw1toss.png b/assets/z_bbw1toss.png new file mode 100644 index 0000000..1fbdec7 Binary files /dev/null and b/assets/z_bbw1toss.png differ diff --git a/assets/z_bbw2.png b/assets/z_bbw2.png new file mode 100644 index 0000000..df6551d Binary files /dev/null and b/assets/z_bbw2.png differ diff --git a/assets/z_bbw2b.png b/assets/z_bbw2b.png new file mode 100644 index 0000000..75db99a Binary files /dev/null and b/assets/z_bbw2b.png differ diff --git a/assets/z_bbw2bounce.png b/assets/z_bbw2bounce.png new file mode 100644 index 0000000..12bc221 Binary files /dev/null and b/assets/z_bbw2bounce.png differ diff --git a/assets/z_bbw2fall1.png b/assets/z_bbw2fall1.png new file mode 100644 index 0000000..9f08f96 Binary files /dev/null and b/assets/z_bbw2fall1.png differ diff --git a/assets/z_bbw2fall2.png b/assets/z_bbw2fall2.png new file mode 100644 index 0000000..24274eb Binary files /dev/null and b/assets/z_bbw2fall2.png differ diff --git a/assets/z_bbw2fall3.png b/assets/z_bbw2fall3.png new file mode 100644 index 0000000..4d6dfa9 Binary files /dev/null and b/assets/z_bbw2fall3.png differ diff --git a/assets/z_bbw2rise.png b/assets/z_bbw2rise.png new file mode 100644 index 0000000..03e324d Binary files /dev/null and b/assets/z_bbw2rise.png differ diff --git a/assets/z_bbw2s.png b/assets/z_bbw2s.png new file mode 100644 index 0000000..2b3fcab Binary files /dev/null and b/assets/z_bbw2s.png differ diff --git a/assets/z_bbw2serve.png b/assets/z_bbw2serve.png new file mode 100644 index 0000000..6860d11 Binary files /dev/null and b/assets/z_bbw2serve.png differ diff --git a/assets/z_bbw2toss.png b/assets/z_bbw2toss.png new file mode 100644 index 0000000..ac46a20 Binary files /dev/null and b/assets/z_bbw2toss.png differ diff --git a/assets/z_gk1.png b/assets/z_gk1.png new file mode 100644 index 0000000..6325577 Binary files /dev/null and b/assets/z_gk1.png differ diff --git a/assets/z_gk2.png b/assets/z_gk2.png new file mode 100644 index 0000000..f2035aa Binary files /dev/null and b/assets/z_gk2.png differ diff --git a/assets/z_gk3.png b/assets/z_gk3.png new file mode 100644 index 0000000..9ccf864 Binary files /dev/null and b/assets/z_gk3.png differ diff --git a/assets/z_gk4.png b/assets/z_gk4.png new file mode 100644 index 0000000..17a5ec4 Binary files /dev/null and b/assets/z_gk4.png differ diff --git a/assets/z_gkbounce.png b/assets/z_gkbounce.png new file mode 100644 index 0000000..2bd1ca6 Binary files /dev/null and b/assets/z_gkbounce.png differ diff --git a/assets/z_gkfall1.png b/assets/z_gkfall1.png new file mode 100644 index 0000000..298f371 Binary files /dev/null and b/assets/z_gkfall1.png differ diff --git a/assets/z_gkfall2.png b/assets/z_gkfall2.png new file mode 100644 index 0000000..7dd4b9f Binary files /dev/null and b/assets/z_gkfall2.png differ diff --git a/assets/z_gkfall3.png b/assets/z_gkfall3.png new file mode 100644 index 0000000..e90f0f1 Binary files /dev/null and b/assets/z_gkfall3.png differ diff --git a/assets/z_gkrise.png b/assets/z_gkrise.png new file mode 100644 index 0000000..efa9208 Binary files /dev/null and b/assets/z_gkrise.png differ diff --git a/assets/z_gkserve.png b/assets/z_gkserve.png new file mode 100644 index 0000000..25646ff Binary files /dev/null and b/assets/z_gkserve.png differ diff --git a/assets/z_gktoss.png b/assets/z_gktoss.png new file mode 100644 index 0000000..f801a57 Binary files /dev/null and b/assets/z_gktoss.png differ diff --git a/assets/z_gkw1.png b/assets/z_gkw1.png new file mode 100644 index 0000000..9351bce Binary files /dev/null and b/assets/z_gkw1.png differ diff --git a/assets/z_gkw1b.png b/assets/z_gkw1b.png new file mode 100644 index 0000000..6df94da Binary files /dev/null and b/assets/z_gkw1b.png differ diff --git a/assets/z_gkw1bounce.png b/assets/z_gkw1bounce.png new file mode 100644 index 0000000..92dab8e Binary files /dev/null and b/assets/z_gkw1bounce.png differ diff --git a/assets/z_gkw1fall1.png b/assets/z_gkw1fall1.png new file mode 100644 index 0000000..dd0e984 Binary files /dev/null and b/assets/z_gkw1fall1.png differ diff --git a/assets/z_gkw1fall2.png b/assets/z_gkw1fall2.png new file mode 100644 index 0000000..3cf0f8c Binary files /dev/null and b/assets/z_gkw1fall2.png differ diff --git a/assets/z_gkw1fall3.png b/assets/z_gkw1fall3.png new file mode 100644 index 0000000..f0afd57 Binary files /dev/null and b/assets/z_gkw1fall3.png differ diff --git a/assets/z_gkw1rise.png b/assets/z_gkw1rise.png new file mode 100644 index 0000000..f701018 Binary files /dev/null and b/assets/z_gkw1rise.png differ diff --git a/assets/z_gkw1s.png b/assets/z_gkw1s.png new file mode 100644 index 0000000..5f88644 Binary files /dev/null and b/assets/z_gkw1s.png differ diff --git a/assets/z_gkw1serve.png b/assets/z_gkw1serve.png new file mode 100644 index 0000000..3a777b3 Binary files /dev/null and b/assets/z_gkw1serve.png differ diff --git a/assets/z_gkw1toss.png b/assets/z_gkw1toss.png new file mode 100644 index 0000000..268273e Binary files /dev/null and b/assets/z_gkw1toss.png differ diff --git a/assets/z_gkw2.png b/assets/z_gkw2.png new file mode 100644 index 0000000..778e929 Binary files /dev/null and b/assets/z_gkw2.png differ diff --git a/assets/z_gkw2b.png b/assets/z_gkw2b.png new file mode 100644 index 0000000..db42bd0 Binary files /dev/null and b/assets/z_gkw2b.png differ diff --git a/assets/z_gkw2bounce.png b/assets/z_gkw2bounce.png new file mode 100644 index 0000000..44e2864 Binary files /dev/null and b/assets/z_gkw2bounce.png differ diff --git a/assets/z_gkw2fall1.png b/assets/z_gkw2fall1.png new file mode 100644 index 0000000..9181e94 Binary files /dev/null and b/assets/z_gkw2fall1.png differ diff --git a/assets/z_gkw2fall2.png b/assets/z_gkw2fall2.png new file mode 100644 index 0000000..6734e64 Binary files /dev/null and b/assets/z_gkw2fall2.png differ diff --git a/assets/z_gkw2fall3.png b/assets/z_gkw2fall3.png new file mode 100644 index 0000000..f3a2add Binary files /dev/null and b/assets/z_gkw2fall3.png differ diff --git a/assets/z_gkw2rise.png b/assets/z_gkw2rise.png new file mode 100644 index 0000000..771c207 Binary files /dev/null and b/assets/z_gkw2rise.png differ diff --git a/assets/z_gkw2s.png b/assets/z_gkw2s.png new file mode 100644 index 0000000..261f8bc Binary files /dev/null and b/assets/z_gkw2s.png differ diff --git a/assets/z_gkw2serve.png b/assets/z_gkw2serve.png new file mode 100644 index 0000000..94006d2 Binary files /dev/null and b/assets/z_gkw2serve.png differ diff --git a/assets/z_gkw2toss.png b/assets/z_gkw2toss.png new file mode 100644 index 0000000..27b8f50 Binary files /dev/null and b/assets/z_gkw2toss.png differ diff --git a/assets/z_lj1.png b/assets/z_lj1.png new file mode 100644 index 0000000..bb97e42 Binary files /dev/null and b/assets/z_lj1.png differ diff --git a/assets/z_lj2.png b/assets/z_lj2.png new file mode 100644 index 0000000..15c3263 Binary files /dev/null and b/assets/z_lj2.png differ diff --git a/assets/z_lj3.png b/assets/z_lj3.png new file mode 100644 index 0000000..c027e39 Binary files /dev/null and b/assets/z_lj3.png differ diff --git a/assets/z_lj4.png b/assets/z_lj4.png new file mode 100644 index 0000000..e95c31b Binary files /dev/null and b/assets/z_lj4.png differ diff --git a/assets/z_ljbounce.png b/assets/z_ljbounce.png new file mode 100644 index 0000000..1a77460 Binary files /dev/null and b/assets/z_ljbounce.png differ diff --git a/assets/z_ljfall1.png b/assets/z_ljfall1.png new file mode 100644 index 0000000..9f333c9 Binary files /dev/null and b/assets/z_ljfall1.png differ diff --git a/assets/z_ljfall2.png b/assets/z_ljfall2.png new file mode 100644 index 0000000..c2d3503 Binary files /dev/null and b/assets/z_ljfall2.png differ diff --git a/assets/z_ljfall3.png b/assets/z_ljfall3.png new file mode 100644 index 0000000..12bf269 Binary files /dev/null and b/assets/z_ljfall3.png differ diff --git a/assets/z_ljrise.png b/assets/z_ljrise.png new file mode 100644 index 0000000..0d24646 Binary files /dev/null and b/assets/z_ljrise.png differ diff --git a/assets/z_ljserve.png b/assets/z_ljserve.png new file mode 100644 index 0000000..c6c9931 Binary files /dev/null and b/assets/z_ljserve.png differ diff --git a/assets/z_ljtoss.png b/assets/z_ljtoss.png new file mode 100644 index 0000000..4552fee Binary files /dev/null and b/assets/z_ljtoss.png differ diff --git a/assets/z_ljw1.png b/assets/z_ljw1.png new file mode 100644 index 0000000..9435c4e Binary files /dev/null and b/assets/z_ljw1.png differ diff --git a/assets/z_ljw1b.png b/assets/z_ljw1b.png new file mode 100644 index 0000000..297762b Binary files /dev/null and b/assets/z_ljw1b.png differ diff --git a/assets/z_ljw1bounce.png b/assets/z_ljw1bounce.png new file mode 100644 index 0000000..c11466f Binary files /dev/null and b/assets/z_ljw1bounce.png differ diff --git a/assets/z_ljw1fall1.png b/assets/z_ljw1fall1.png new file mode 100644 index 0000000..6fb715e Binary files /dev/null and b/assets/z_ljw1fall1.png differ diff --git a/assets/z_ljw1fall2.png b/assets/z_ljw1fall2.png new file mode 100644 index 0000000..4dc8f2a Binary files /dev/null and b/assets/z_ljw1fall2.png differ diff --git a/assets/z_ljw1fall3.png b/assets/z_ljw1fall3.png new file mode 100644 index 0000000..fec6d33 Binary files /dev/null and b/assets/z_ljw1fall3.png differ diff --git a/assets/z_ljw1rise.png b/assets/z_ljw1rise.png new file mode 100644 index 0000000..be0af32 Binary files /dev/null and b/assets/z_ljw1rise.png differ diff --git a/assets/z_ljw1s.png b/assets/z_ljw1s.png new file mode 100644 index 0000000..438ba7a Binary files /dev/null and b/assets/z_ljw1s.png differ diff --git a/assets/z_ljw1serve.png b/assets/z_ljw1serve.png new file mode 100644 index 0000000..dcaa5cf Binary files /dev/null and b/assets/z_ljw1serve.png differ diff --git a/assets/z_ljw1toss.png b/assets/z_ljw1toss.png new file mode 100644 index 0000000..f241b49 Binary files /dev/null and b/assets/z_ljw1toss.png differ diff --git a/assets/z_ljw2.png b/assets/z_ljw2.png new file mode 100644 index 0000000..17b973c Binary files /dev/null and b/assets/z_ljw2.png differ diff --git a/assets/z_ljw2b.png b/assets/z_ljw2b.png new file mode 100644 index 0000000..bb1ab79 Binary files /dev/null and b/assets/z_ljw2b.png differ diff --git a/assets/z_ljw2bounce.png b/assets/z_ljw2bounce.png new file mode 100644 index 0000000..cd30427 Binary files /dev/null and b/assets/z_ljw2bounce.png differ diff --git a/assets/z_ljw2fall1.png b/assets/z_ljw2fall1.png new file mode 100644 index 0000000..78e337f Binary files /dev/null and b/assets/z_ljw2fall1.png differ diff --git a/assets/z_ljw2fall2.png b/assets/z_ljw2fall2.png new file mode 100644 index 0000000..b114d49 Binary files /dev/null and b/assets/z_ljw2fall2.png differ diff --git a/assets/z_ljw2fall3.png b/assets/z_ljw2fall3.png new file mode 100644 index 0000000..7a7975b Binary files /dev/null and b/assets/z_ljw2fall3.png differ diff --git a/assets/z_ljw2rise.png b/assets/z_ljw2rise.png new file mode 100644 index 0000000..1d386f2 Binary files /dev/null and b/assets/z_ljw2rise.png differ diff --git a/assets/z_ljw2s.png b/assets/z_ljw2s.png new file mode 100644 index 0000000..8d2ac1b Binary files /dev/null and b/assets/z_ljw2s.png differ diff --git a/assets/z_ljw2serve.png b/assets/z_ljw2serve.png new file mode 100644 index 0000000..0651cdc Binary files /dev/null and b/assets/z_ljw2serve.png differ diff --git a/assets/z_ljw2toss.png b/assets/z_ljw2toss.png new file mode 100644 index 0000000..bee6187 Binary files /dev/null and b/assets/z_ljw2toss.png differ diff --git a/assets/z_ru1.png b/assets/z_ru1.png new file mode 100644 index 0000000..4fd8f78 Binary files /dev/null and b/assets/z_ru1.png differ diff --git a/assets/z_ru2.png b/assets/z_ru2.png new file mode 100644 index 0000000..cf65115 Binary files /dev/null and b/assets/z_ru2.png differ diff --git a/assets/z_ru3.png b/assets/z_ru3.png new file mode 100644 index 0000000..41da348 Binary files /dev/null and b/assets/z_ru3.png differ diff --git a/assets/z_ru4.png b/assets/z_ru4.png new file mode 100644 index 0000000..4125d16 Binary files /dev/null and b/assets/z_ru4.png differ diff --git a/assets/z_rubounce.png b/assets/z_rubounce.png new file mode 100644 index 0000000..739c741 Binary files /dev/null and b/assets/z_rubounce.png differ diff --git a/assets/z_rufall1.png b/assets/z_rufall1.png new file mode 100644 index 0000000..6ac6a5a Binary files /dev/null and b/assets/z_rufall1.png differ diff --git a/assets/z_rufall2.png b/assets/z_rufall2.png new file mode 100644 index 0000000..9657e85 Binary files /dev/null and b/assets/z_rufall2.png differ diff --git a/assets/z_rufall3.png b/assets/z_rufall3.png new file mode 100644 index 0000000..888b492 Binary files /dev/null and b/assets/z_rufall3.png differ diff --git a/assets/z_rurise.png b/assets/z_rurise.png new file mode 100644 index 0000000..51c0f11 Binary files /dev/null and b/assets/z_rurise.png differ diff --git a/assets/z_ruserve.png b/assets/z_ruserve.png new file mode 100644 index 0000000..682d781 Binary files /dev/null and b/assets/z_ruserve.png differ diff --git a/assets/z_rutoss.png b/assets/z_rutoss.png new file mode 100644 index 0000000..b91f424 Binary files /dev/null and b/assets/z_rutoss.png differ diff --git a/assets/z_ruw1.png b/assets/z_ruw1.png new file mode 100644 index 0000000..686c44a Binary files /dev/null and b/assets/z_ruw1.png differ diff --git a/assets/z_ruw1b.png b/assets/z_ruw1b.png new file mode 100644 index 0000000..fad770a Binary files /dev/null and b/assets/z_ruw1b.png differ diff --git a/assets/z_ruw1bounce.png b/assets/z_ruw1bounce.png new file mode 100644 index 0000000..95ae0f0 Binary files /dev/null and b/assets/z_ruw1bounce.png differ diff --git a/assets/z_ruw1fall1.png b/assets/z_ruw1fall1.png new file mode 100644 index 0000000..3eaf451 Binary files /dev/null and b/assets/z_ruw1fall1.png differ diff --git a/assets/z_ruw1fall2.png b/assets/z_ruw1fall2.png new file mode 100644 index 0000000..915c0fc Binary files /dev/null and b/assets/z_ruw1fall2.png differ diff --git a/assets/z_ruw1fall3.png b/assets/z_ruw1fall3.png new file mode 100644 index 0000000..0e7c216 Binary files /dev/null and b/assets/z_ruw1fall3.png differ diff --git a/assets/z_ruw1rise.png b/assets/z_ruw1rise.png new file mode 100644 index 0000000..a633090 Binary files /dev/null and b/assets/z_ruw1rise.png differ diff --git a/assets/z_ruw1s.png b/assets/z_ruw1s.png new file mode 100644 index 0000000..9666cbd Binary files /dev/null and b/assets/z_ruw1s.png differ diff --git a/assets/z_ruw1serve.png b/assets/z_ruw1serve.png new file mode 100644 index 0000000..a62b6cf Binary files /dev/null and b/assets/z_ruw1serve.png differ diff --git a/assets/z_ruw1toss.png b/assets/z_ruw1toss.png new file mode 100644 index 0000000..04adf5b Binary files /dev/null and b/assets/z_ruw1toss.png differ diff --git a/assets/z_ruw2.png b/assets/z_ruw2.png new file mode 100644 index 0000000..27a2287 Binary files /dev/null and b/assets/z_ruw2.png differ diff --git a/assets/z_ruw2b.png b/assets/z_ruw2b.png new file mode 100644 index 0000000..1b75323 Binary files /dev/null and b/assets/z_ruw2b.png differ diff --git a/assets/z_ruw2bounce.png b/assets/z_ruw2bounce.png new file mode 100644 index 0000000..0d8bf15 Binary files /dev/null and b/assets/z_ruw2bounce.png differ diff --git a/assets/z_ruw2fall1.png b/assets/z_ruw2fall1.png new file mode 100644 index 0000000..d39aff2 Binary files /dev/null and b/assets/z_ruw2fall1.png differ diff --git a/assets/z_ruw2fall2.png b/assets/z_ruw2fall2.png new file mode 100644 index 0000000..1f8c30f Binary files /dev/null and b/assets/z_ruw2fall2.png differ diff --git a/assets/z_ruw2fall3.png b/assets/z_ruw2fall3.png new file mode 100644 index 0000000..e49f5a1 Binary files /dev/null and b/assets/z_ruw2fall3.png differ diff --git a/assets/z_ruw2rise.png b/assets/z_ruw2rise.png new file mode 100644 index 0000000..12fe379 Binary files /dev/null and b/assets/z_ruw2rise.png differ diff --git a/assets/z_ruw2s.png b/assets/z_ruw2s.png new file mode 100644 index 0000000..9d22ae7 Binary files /dev/null and b/assets/z_ruw2s.png differ diff --git a/assets/z_ruw2serve.png b/assets/z_ruw2serve.png new file mode 100644 index 0000000..ed2edc0 Binary files /dev/null and b/assets/z_ruw2serve.png differ diff --git a/assets/z_ruw2toss.png b/assets/z_ruw2toss.png new file mode 100644 index 0000000..ee456e8 Binary files /dev/null and b/assets/z_ruw2toss.png differ diff --git a/assets/z_sh1.png b/assets/z_sh1.png new file mode 100644 index 0000000..711e5c2 Binary files /dev/null and b/assets/z_sh1.png differ diff --git a/assets/z_sh2.png b/assets/z_sh2.png new file mode 100644 index 0000000..8a5c7be Binary files /dev/null and b/assets/z_sh2.png differ diff --git a/assets/z_sh3.png b/assets/z_sh3.png new file mode 100644 index 0000000..f919322 Binary files /dev/null and b/assets/z_sh3.png differ diff --git a/assets/z_sh4.png b/assets/z_sh4.png new file mode 100644 index 0000000..c4aadbb Binary files /dev/null and b/assets/z_sh4.png differ diff --git a/assets/z_shbounce.png b/assets/z_shbounce.png new file mode 100644 index 0000000..fad0f2c Binary files /dev/null and b/assets/z_shbounce.png differ diff --git a/assets/z_shfall1.png b/assets/z_shfall1.png new file mode 100644 index 0000000..8cd41e2 Binary files /dev/null and b/assets/z_shfall1.png differ diff --git a/assets/z_shfall2.png b/assets/z_shfall2.png new file mode 100644 index 0000000..ab0a999 Binary files /dev/null and b/assets/z_shfall2.png differ diff --git a/assets/z_shfall3.png b/assets/z_shfall3.png new file mode 100644 index 0000000..17cf9ae Binary files /dev/null and b/assets/z_shfall3.png differ diff --git a/assets/z_shrise.png b/assets/z_shrise.png new file mode 100644 index 0000000..b40f914 Binary files /dev/null and b/assets/z_shrise.png differ diff --git a/assets/z_shserve.png b/assets/z_shserve.png new file mode 100644 index 0000000..a5ce398 Binary files /dev/null and b/assets/z_shserve.png differ diff --git a/assets/z_shtoss.png b/assets/z_shtoss.png new file mode 100644 index 0000000..f9f266d Binary files /dev/null and b/assets/z_shtoss.png differ diff --git a/assets/z_shw1.png b/assets/z_shw1.png new file mode 100644 index 0000000..281ae8c Binary files /dev/null and b/assets/z_shw1.png differ diff --git a/assets/z_shw1b.png b/assets/z_shw1b.png new file mode 100644 index 0000000..b5583bc Binary files /dev/null and b/assets/z_shw1b.png differ diff --git a/assets/z_shw1bounce.png b/assets/z_shw1bounce.png new file mode 100644 index 0000000..645f5a3 Binary files /dev/null and b/assets/z_shw1bounce.png differ diff --git a/assets/z_shw1fall1.png b/assets/z_shw1fall1.png new file mode 100644 index 0000000..cfe1bb0 Binary files /dev/null and b/assets/z_shw1fall1.png differ diff --git a/assets/z_shw1fall2.png b/assets/z_shw1fall2.png new file mode 100644 index 0000000..186f3e1 Binary files /dev/null and b/assets/z_shw1fall2.png differ diff --git a/assets/z_shw1fall3.png b/assets/z_shw1fall3.png new file mode 100644 index 0000000..71d06d8 Binary files /dev/null and b/assets/z_shw1fall3.png differ diff --git a/assets/z_shw1rise.png b/assets/z_shw1rise.png new file mode 100644 index 0000000..3ef94fa Binary files /dev/null and b/assets/z_shw1rise.png differ diff --git a/assets/z_shw1s.png b/assets/z_shw1s.png new file mode 100644 index 0000000..7bd843e Binary files /dev/null and b/assets/z_shw1s.png differ diff --git a/assets/z_shw1serve.png b/assets/z_shw1serve.png new file mode 100644 index 0000000..7f3f95d Binary files /dev/null and b/assets/z_shw1serve.png differ diff --git a/assets/z_shw1toss.png b/assets/z_shw1toss.png new file mode 100644 index 0000000..31cd151 Binary files /dev/null and b/assets/z_shw1toss.png differ diff --git a/assets/z_shw2.png b/assets/z_shw2.png new file mode 100644 index 0000000..d1eafe7 Binary files /dev/null and b/assets/z_shw2.png differ diff --git a/assets/z_shw2b.png b/assets/z_shw2b.png new file mode 100644 index 0000000..7459d48 Binary files /dev/null and b/assets/z_shw2b.png differ diff --git a/assets/z_shw2bounce.png b/assets/z_shw2bounce.png new file mode 100644 index 0000000..1a983ab Binary files /dev/null and b/assets/z_shw2bounce.png differ diff --git a/assets/z_shw2fall1.png b/assets/z_shw2fall1.png new file mode 100644 index 0000000..c30ca9a Binary files /dev/null and b/assets/z_shw2fall1.png differ diff --git a/assets/z_shw2fall2.png b/assets/z_shw2fall2.png new file mode 100644 index 0000000..18a6d12 Binary files /dev/null and b/assets/z_shw2fall2.png differ diff --git a/assets/z_shw2fall3.png b/assets/z_shw2fall3.png new file mode 100644 index 0000000..3c9d6ea Binary files /dev/null and b/assets/z_shw2fall3.png differ diff --git a/assets/z_shw2rise.png b/assets/z_shw2rise.png new file mode 100644 index 0000000..59fd9a4 Binary files /dev/null and b/assets/z_shw2rise.png differ diff --git a/assets/z_shw2s.png b/assets/z_shw2s.png new file mode 100644 index 0000000..0424812 Binary files /dev/null and b/assets/z_shw2s.png differ diff --git a/assets/z_shw2serve.png b/assets/z_shw2serve.png new file mode 100644 index 0000000..aa9bc87 Binary files /dev/null and b/assets/z_shw2serve.png differ diff --git a/assets/z_shw2toss.png b/assets/z_shw2toss.png new file mode 100644 index 0000000..f0355c4 Binary files /dev/null and b/assets/z_shw2toss.png differ diff --git a/assets/z_st1.png b/assets/z_st1.png new file mode 100644 index 0000000..f65a0a9 Binary files /dev/null and b/assets/z_st1.png differ diff --git a/assets/z_st2.png b/assets/z_st2.png new file mode 100644 index 0000000..489f9b3 Binary files /dev/null and b/assets/z_st2.png differ diff --git a/assets/z_st3.png b/assets/z_st3.png new file mode 100644 index 0000000..4a344b6 Binary files /dev/null and b/assets/z_st3.png differ diff --git a/assets/z_st4.png b/assets/z_st4.png new file mode 100644 index 0000000..cd11d69 Binary files /dev/null and b/assets/z_st4.png differ diff --git a/assets/z_stbounce.png b/assets/z_stbounce.png new file mode 100644 index 0000000..3398654 Binary files /dev/null and b/assets/z_stbounce.png differ diff --git a/assets/z_stfall1.png b/assets/z_stfall1.png new file mode 100644 index 0000000..3351177 Binary files /dev/null and b/assets/z_stfall1.png differ diff --git a/assets/z_stfall2.png b/assets/z_stfall2.png new file mode 100644 index 0000000..67e3342 Binary files /dev/null and b/assets/z_stfall2.png differ diff --git a/assets/z_stfall3.png b/assets/z_stfall3.png new file mode 100644 index 0000000..73dc188 Binary files /dev/null and b/assets/z_stfall3.png differ diff --git a/assets/z_strise.png b/assets/z_strise.png new file mode 100644 index 0000000..4a0dcb8 Binary files /dev/null and b/assets/z_strise.png differ diff --git a/assets/z_stserve.png b/assets/z_stserve.png new file mode 100644 index 0000000..0b15554 Binary files /dev/null and b/assets/z_stserve.png differ diff --git a/assets/z_sttoss.png b/assets/z_sttoss.png new file mode 100644 index 0000000..32c8079 Binary files /dev/null and b/assets/z_sttoss.png differ diff --git a/assets/z_stw1.png b/assets/z_stw1.png new file mode 100644 index 0000000..92ea01e Binary files /dev/null and b/assets/z_stw1.png differ diff --git a/assets/z_stw1b.png b/assets/z_stw1b.png new file mode 100644 index 0000000..42e489a Binary files /dev/null and b/assets/z_stw1b.png differ diff --git a/assets/z_stw1bounce.png b/assets/z_stw1bounce.png new file mode 100644 index 0000000..5c9ad01 Binary files /dev/null and b/assets/z_stw1bounce.png differ diff --git a/assets/z_stw1fall1.png b/assets/z_stw1fall1.png new file mode 100644 index 0000000..bd11494 Binary files /dev/null and b/assets/z_stw1fall1.png differ diff --git a/assets/z_stw1fall2.png b/assets/z_stw1fall2.png new file mode 100644 index 0000000..c130d0d Binary files /dev/null and b/assets/z_stw1fall2.png differ diff --git a/assets/z_stw1fall3.png b/assets/z_stw1fall3.png new file mode 100644 index 0000000..1094171 Binary files /dev/null and b/assets/z_stw1fall3.png differ diff --git a/assets/z_stw1rise.png b/assets/z_stw1rise.png new file mode 100644 index 0000000..28b0356 Binary files /dev/null and b/assets/z_stw1rise.png differ diff --git a/assets/z_stw1s.png b/assets/z_stw1s.png new file mode 100644 index 0000000..67a49c8 Binary files /dev/null and b/assets/z_stw1s.png differ diff --git a/assets/z_stw1serve.png b/assets/z_stw1serve.png new file mode 100644 index 0000000..e6ebb04 Binary files /dev/null and b/assets/z_stw1serve.png differ diff --git a/assets/z_stw1toss.png b/assets/z_stw1toss.png new file mode 100644 index 0000000..a107adb Binary files /dev/null and b/assets/z_stw1toss.png differ diff --git a/assets/z_stw2.png b/assets/z_stw2.png new file mode 100644 index 0000000..debf2b8 Binary files /dev/null and b/assets/z_stw2.png differ diff --git a/assets/z_stw2b.png b/assets/z_stw2b.png new file mode 100644 index 0000000..f420770 Binary files /dev/null and b/assets/z_stw2b.png differ diff --git a/assets/z_stw2bounce.png b/assets/z_stw2bounce.png new file mode 100644 index 0000000..c5de52b Binary files /dev/null and b/assets/z_stw2bounce.png differ diff --git a/assets/z_stw2fall1.png b/assets/z_stw2fall1.png new file mode 100644 index 0000000..041662d Binary files /dev/null and b/assets/z_stw2fall1.png differ diff --git a/assets/z_stw2fall2.png b/assets/z_stw2fall2.png new file mode 100644 index 0000000..cced90c Binary files /dev/null and b/assets/z_stw2fall2.png differ diff --git a/assets/z_stw2fall3.png b/assets/z_stw2fall3.png new file mode 100644 index 0000000..7b46460 Binary files /dev/null and b/assets/z_stw2fall3.png differ diff --git a/assets/z_stw2rise.png b/assets/z_stw2rise.png new file mode 100644 index 0000000..5a5e431 Binary files /dev/null and b/assets/z_stw2rise.png differ diff --git a/assets/z_stw2s.png b/assets/z_stw2s.png new file mode 100644 index 0000000..c90d197 Binary files /dev/null and b/assets/z_stw2s.png differ diff --git a/assets/z_stw2serve.png b/assets/z_stw2serve.png new file mode 100644 index 0000000..ba3913e Binary files /dev/null and b/assets/z_stw2serve.png differ diff --git a/assets/z_stw2toss.png b/assets/z_stw2toss.png new file mode 100644 index 0000000..be98a7b Binary files /dev/null and b/assets/z_stw2toss.png differ diff --git a/index.html b/index.html index 2b65eae..59ee12a 100644 --- a/index.html +++ b/index.html @@ -431,6 +431,6 @@ if (location.hostname !== 'splatter-tennis.wuwi.one') { - + diff --git a/src/beats.js b/src/beats.js index b378fbe..bc4e04e 100644 --- a/src/beats.js +++ b/src/beats.js @@ -361,7 +361,10 @@ function matchReady() { // section about, and the horde's warning is on its own clock for the same reason. The 'over' beat is // only there to keep the court quiet, and it is set to outlive the fall so it cannot expire into // setupPoint() mid-collapse and hand the player a serve over a body that is still on the ground. -const FALL = [['fall1', 0.16], ['fall2', 0.18], ['fall3', 0.30], ['rise', 0.34]]; +// fall3 is the one that HOLDS: "the opponent should fall and then lie there for a quick moment, the +// player stands up too quick." A corpse that touches the ground and is instantly upright reads as a +// stumble; the beat only lands if it is down long enough to look beaten. +const FALL = [['fall1', 0.14], ['fall2', 0.16], ['fall3', 0.62], ['rise', 0.36]]; export const FALL_LEN = FALL.reduce((s, f) => s + f[1], 0); function startFall(from) { @@ -463,13 +466,22 @@ function matchPoint(side) { if (side === 'O') { G.hp--; G.hurtT = TUNE.hurtGrace; G.hurtFlash = 0.6; aww(0.8); + // ...and the umpire notices, drily. Situational, never a pool on a timer: the LOW line only + // exists once the blood is nearly gone, so it lands as a warning rather than as commentary. + aside(G.hp <= 2 ? 'ump_low_blood' : (score.pts[1] % 2 ? 'ump_wound_you2' : 'ump_wound_you'), 0.7); if (G.hp <= 0) return killPlayer(); // eaten — the run summary, as ever } const over = advancePoint(side); // banks the game and resets the points itself if (over) return side === 'P' ? matchGameWon(zb, true) : matchGameLost(); voice(scoreKey(), 0.4); if (side === 'P') cheer(0.8, 0.3); - if (fellFrom >= 0) return startFall(fellFrom); // it goes down, and gets up short of a limb + if (fellFrom >= 0) { + // A LIMB IS WORTH A REMARK. Two lines, alternating on the stage taken, so the same sentence + // never lands twice in one game — a repeated aside reads as a stuck HUD, the same reason the + // praise words cycle rather than roll. + aside(fellFrom ? 'ump_wound_opp2' : 'ump_wound_opp', 0.9); + return startFall(fellFrom); // it goes down, and gets up short of a limb + } matchReady(); // next point, same opponent, both on their marks } @@ -483,10 +495,15 @@ function matchGameWon(zb, banked = false) { G.runGames++; // the ladder metric, same as every mode G.hp = Math.min(G.hpMax, G.hp + TUNE.matchHeal); // a clean game is how you last voice('game_player', 0.3); cheer(2.4, 0.6); + aside('ump_felled', 1.5); // ...over the applause, as he would // GAME is the FLASH and the next opponent's name plate is the CARD. It used to be the other way // round and matchOpponent's plate landed on the same slot a frame later, so 'GAME.' never drew // once — and the plate is the better card anyway: it announces the win by replacing the loser. G.flash = { text: 'GAME.', t: 1.4 }; + // ...and the boy comes out to deal with what four wound stages left on the far court. Deliberately + // the FAR side: that is where the body has been standing and bleeding, and it keeps him out of the + // near court where the player is about to serve. Between games only — see sendBoy's match note. + sendBoy(0, 4.5, true); d.opp++; // ALTERNATING SERVICE, as the sport has it. The match calls advancePoint directly, which banks // the game but never touches the server — that flip lives in tennis's own pointTo, which the diff --git a/src/config.js b/src/config.js index d5bff38..3c3cdcf 100644 --- a/src/config.js +++ b/src/config.js @@ -22,10 +22,16 @@ export const TUNE = { // net-clearance loop ate the whole smash: a flat fast ball can't get over the // tape AND come down on a target, so it got throttled back to walking pace. // Heavy topspin lets it leave flat and DIVE, which is what a smash actually is. + smashBoost: 0.42, // extra pace on a FORWARD flick (aim.az < 0), scaled by timing. It does not + // 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 // 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. - flickReach: 0.45, // how much of an aimed shot's width a MISTIMED flick gets (1 = all of it). + 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 + // not make the flick punchier, it makes a perfect one go out. + flickReach: 0.42, // how much of an aimed shot's width a MISTIMED flick gets (1 = all of it). // 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: // 3.3 * 0.45 + 2.4 = 3.89 against a 4.11 m line. Raise it and the flick @@ -403,12 +409,16 @@ export const TUNE = { // the broken recovery (the player never walked back to centre, so every // wide return looked like opponent strength) — with real footwork restored // it starts gentler and the ladder still climbs - matchSpeedPer: 0.08, // ...growing this much per opponent felled. Softened once: each rung moves + // STEEPER PER RUNG (2026-08-12): "in general this is too easy now". The flick fix means an aimed + // return can no longer sail out, so the player stopped handing over free points and the ladder had + // to earn them back. The OPENING is deliberately softer than before (the shambler starts at 0.72 + // where the old first opponent was 0.85) — the ramp got steeper at both ends, not just harder. + matchSpeedPer: 0.11, // ...growing this much per opponent felled. Softened once: each rung moves // THREE dials at once (speed, flub, width, pace), and the recorder showed // opponent two as a wall — he beat one and lost every gap to two by ~0.3 m matchFlub: 0.08, // the match's own whiff base — the duel's 0.2 gives away a fifth of all // returns, which across a game is an opponent that beats itself - matchFlubKeep: 0.82, // ...and it keeps only this much per rung — the ladder now that hp is flat + matchFlubKeep: 0.76, // ...and it keeps only this much per rung — the ladder now that hp is flat matchReach: 1.35, // contact reach multiplier in the MATCH. The recorder showed swings missing by // centimetres point after point — the assist parks you near the ball, not inside // a 1.3 m reach. A rally mode lives on contact feeling fair. Match-only @@ -417,7 +427,7 @@ export const TUNE = { // is the game's job. Held keys still override matchOppPace: 0.78, // scale on the opponent's return SPEED for opponent one (~21 m/s instead // of ~27) — the flight time auto-run needs. THE reachability dial - matchOppPacePer: 0.035,// ...climbing back per rung, capped at full pace (softened with speedPer) + matchOppPacePer: 0.05,// ...climbing back per rung, capped at full pace (softened with speedPer) matchWideBase: 2.1, // m either side of centre the FIRST opponent's returns may land — the width an // auto-run player can actually cover. Its scatter painted lines from game one // ("still a LOT of unreachable balls"); now line-painting is the late ladder @@ -690,18 +700,38 @@ export const bossArt = ch => ch % 10 === 0 ? BOSS_ART[BOSS_ART.length - 1] // name it here, and that opponent walks on as it — through all three wound stages. // short 8 characters, because that is the board's name slot — the match board names // the thing you are playing instead of calling it THEM +// THE CARD, in order. It is a ladder of STAFF: the further you get, the further up the club's +// hierarchy the thing across the net used to be. That is the narrative, and the difficulty ramp is +// the same list read as numbers — every dial climbs monotonically, so "start easy and get harder" +// is a property of the table rather than something the code has to arrange. +// 1-2 the ones who fetch and sweep. Slow, wild, forgiving. +// 3-4 the ones who keep order. They start covering ground. +// 5-6 the ones who judge. Width arrives: they begin painting lines. +// 7-8 the ones who actually played. The club pro's own body, and the brute behind him. +// 9-10 management, and then the man whose name is on the honours board. const OPPONENTS = [ - { short: 'KEEPER', line: 'HE MOWED THIS LAWN FOR THIRTY YEARS.', speed: 0.85, pace: 0.9, wide: 0.85, fam: 'br' }, - { short: 'JUDGE', line: 'EVERY BALL OF YOURS IS OUT.', wide: 1.25, flub: 0.8 }, - { short: 'UMPIRE', line: 'HE WILL NOT BE OVERRULED.', speed: 1.1, flub: 0.7 }, - { short: 'COACH', line: 'HE HAS SEEN YOUR BACKHAND.', pace: 1.15, wide: 1.1 }, - { short: 'PHYSIO', line: 'HE IS HERE TO TREAT YOU.', speed: 1.2, pace: 1.1 }, - { short: 'MASCOT', line: 'THE CROWD LOVES HIM.', flub: 1.2, wide: 1.3 }, - { short: 'STRINGER', line: 'HE RESTRUNG THIS RACKET HIMSELF.', pace: 1.2, flub: 0.75 }, - { short: 'DIRECTOR', line: 'HE CAN HAVE YOU REMOVED.', speed: 1.15, wide: 1.2 }, - { short: 'SELLER', line: 'WITH CREAM.', pace: 1.1, speed: 1.1, wide: 1.15 }, - { short: 'CHAMPION', line: 'HE RETURNS EVERYTHING.', pace: 1.25, speed: 1.3, wide: 1.25, flub: 0.5 }, + { short: 'SHAMBLER', name: 'THE SHAMBLER', fam: 'sh', + line: 'HE WAS HERE BEFORE ANY OF THIS.', speed: 0.72, pace: 0.72, wide: 0.75, flub: 1.35 }, + { short: 'BALL BOY', name: 'THE BALL BOY', fam: 'bb', + line: 'HE STILL FETCHES. HE JUST BITES NOW.', speed: 0.82, pace: 0.8, wide: 0.82, flub: 1.2 }, + { short: 'KEEPER', name: 'THE GROUNDSKEEPER', fam: 'gk', + line: 'HE MOWED THIS LAWN FOR THIRTY YEARS.', speed: 0.9, pace: 0.88, wide: 0.9, flub: 1.05 }, + { short: 'STEWARD', name: 'THE STEWARD', fam: 'st', + line: 'HE IS ASKING YOU TO LEAVE.', speed: 0.98, pace: 0.95, wide: 1.0 }, + { short: 'RUNNER', name: 'THE RUNNER', fam: 'ru', + line: 'HE COVERS THE COURT. ALL OF IT.', speed: 1.22, pace: 1.0, wide: 1.05, flub: 0.95 }, + { short: 'JUDGE', name: 'THE LINE JUDGE', fam: 'lj', + line: 'EVERY BALL OF YOURS IS OUT.', speed: 1.05, pace: 1.05, wide: 1.28, flub: 0.85 }, + { short: 'CLUB PRO', name: 'THE CLUB PRO', + line: 'HE GAVE LESSONS ON THIS COURT.', speed: 1.12, pace: 1.15, wide: 1.15, flub: 0.8 }, + { short: 'THE BULK', name: 'THE BULK', fam: 'br', + line: 'NOTHING GETS PAST HIM TWICE.', speed: 1.0, pace: 1.3, wide: 1.2, flub: 0.75 }, + { short: 'DIRECTOR', name: 'THE DIRECTOR', + line: 'HE CAN HAVE YOU REMOVED.', speed: 1.22, pace: 1.22, wide: 1.25, flub: 0.65 }, + { short: 'CHAMPION', name: 'THE CHAMPION', + line: 'HE RETURNS EVERYTHING.', speed: 1.35, pace: 1.3, wide: 1.3, flub: 0.5 }, ]; + // Who opponent N is. The chapter roster's own order is reused wholesale (bossArt), so the champion // lands on opponent 10 exactly as he lands on chapter 10 and the two rosters can never drift apart. export const matchFoe = n => { @@ -741,7 +771,7 @@ export const BENCH_STAGES = ['HEALTHY', 'ARM OFF', 'HEADLESS']; // that (15 to 48 away from z_br1, where a good frame scores under 8.5) and refuses them, which is // the gate working, not the gate being wrong. Re-registering it is one entry here; see the note at // the top of _master.py for the fix its next attempt needs. -export const ZFAMS = ['r', 'br']; +export const ZFAMS = ['r', 'br', 'sh', 'bb', 'st', 'ru', 'gk', 'lj']; // THE RACKET FAMILY'S THREE STAGES, as a FRACTION of the pool — never against literal hp, the rule // this whole file already holds (see woundStage). It lived inline in drawZombie; the FALL BEAT needs diff --git a/src/gameplay.js b/src/gameplay.js index f227a11..c3b6d72 100644 --- a/src/gameplay.js +++ b/src/gameplay.js @@ -152,9 +152,15 @@ function doHit(ch, quality, aim) { // timed flick is the full 3.3 with no scatter left on it at all. Placement is still earned; // it just stops being a coin toss you are punished for calling. const reach = ch.side === 'P' ? TUNE.flickReach + (1 - TUNE.flickReach) * quality : 1; - tx = aim.ax * (toFar ? 3.3 : 2.7) * reach + err(); + tx = aim.ax * (toFar ? TUNE.flickWidth : 2.7) * reach + err(); 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 + // depth and nothing else — the gesture had a direction with no consequence behind it. It buys + // PACE now, scaled by timing like the width is. Note what it deliberately does not do: solveShot + // still lands the ball on the same tz, so extra speed makes the shot flatter and earlier rather + // than longer. A smash you can hit out on a whim would just be the bug above wearing a hat. + if (ch.side === 'P' && aim.az < 0) s *= 1 + TUNE.smashBoost * quality; aimedLane = true; } tx = Math.max(-5, Math.min(5, tx)); @@ -468,7 +474,13 @@ export function dismissModal() { // and not situation aware.. they seem random". `aside()` is the single entry point and it enforces // one shared cooldown (TUNE.asideEvery), so several triggers firing at once still yields one remark. export function aside(name, delay = 0.2) { - if (G.mode !== 'splatter' || G.modal || G.changeover || G.interlude || G.card) return; + // THE MATCH IS THE EXCEPTION to the interlude bar, exactly as sendBoy is: the match runs on a + // PERMANENT interlude, so this line silently muted the umpire for an entire mode — the one mode + // that is literally a tennis match he is sitting over. The shared asideEvery cooldown still holds, + // which is the rule that keeps him situational rather than chatty ("too much and not situation + // aware.. they seem random"). Cards still block him: a name plate is already the mode talking. + if (G.mode !== 'splatter' || G.modal || G.changeover || G.card) return; + if (G.interlude && !G.match) return; if (G.asideT > 0) return; G.asideT = TUNE.asideEvery; voice(name, delay, 0.95); diff --git a/src/render.js b/src/render.js index a2818b7..7abaef0 100644 --- a/src/render.js +++ b/src/render.js @@ -195,7 +195,7 @@ export function proj(x, y, z) { // Asset cache-buster — bump with main.js?v=N (see the img.src note below). EXPORTED because the // menu screens hang tags on the same files, and two copies of this number is one copy too // many: the one that does not get bumped is the one that serves a cached 404. -export const SPRV = 102; +export const SPRV = 103; // The boot bar in main.js watches this. Nothing here CHANGES when things load — every sprite was // already requested at import — it just makes the wait visible and holds the menu back until the // last sheet has landed. See the onerror note below for the other half of "sometimes missing". diff --git a/src/state.js b/src/state.js index 5d1269f..b7c0f92 100644 --- a/src/state.js +++ b/src/state.js @@ -201,7 +201,12 @@ export function setupPoint() { const mopWorthIt = () => gore.some(d => d.a >= 0.15 && d.z >= COURT.netZ - 2); export function sendBoy(tx = ball.x, tz = ball.z, mop = G.mode === 'splatter') { - if (G.interlude) return; // the sudden-death duel is clean tennis: nobody mops through it + // The sudden-death duel is clean tennis: nobody mops through it. THE MATCH IS THE EXCEPTION — it + // runs on a PERMANENT interlude, so this guard silently barred the boy from an entire mode, and a + // match is where the gore actually accumulates: one body bleeds through four wound stages in the + // same spot, game after game. He is let out between games only (matchGameWon calls him), never + // mid-point, which is the same "guaranteed calm court" rule the modals live under. + if (G.interlude && !G.match) return; if (G.drill) return; // ...and nobody walks onto a drill. He is one more body crossing the // lanes you are reading colours down, and unlike everything else out // there he is not part of the puzzle OR the threat — pure noise. diff --git a/test.js b/test.js index f022727..8c97100 100644 --- a/test.js +++ b/test.js @@ -90,7 +90,8 @@ const startDrill = (...a) => { const r = _startDrill(...a); G.modal = null; G.mo const { zombies, spawnZombie, pellets } = await import('./src/zombies.js'); const { killPlayer } = await import('./src/zombies.js'); const { drillQueue, drillLegal, arrival, drillMult, DRILL_TYPES } = await import('./src/drill.js'); -const { TUNE, bossArt, COURT, racketStage } = await import('./src/config.js'); +const cfg = await import('./src/config.js'); +const { TUNE, bossArt, COURT, racketStage } = cfg; const { read: readStats, check: statsCheck } = await import('./src/stats.js'); const DT = 1 / 60; @@ -635,6 +636,31 @@ test('the match recovers to CENTRE between shots — a wide return must not stra } finally { TUNE.autoAssist = assist; } }); +test('the opponent card is a RAMP: it starts easy and only gets harder', () => { + // "the players should start easy and get harder over time." That is a property of the table, not + // of any code, so the table is what gets asserted — a row edited into the wrong slot is otherwise + // invisible until someone plays ten opponents deep. + const { matchFoe, TUNE } = cfg; + let lastSpeed = 0, lastWhiff = 1e9; + for (let n = 1; n <= 10; n++) { + const f = matchFoe(n); + const speed = (TUNE.matchSpeedBase + (n - 1) * TUNE.matchSpeedPer) * (f.speed ?? 1); + const whiff = TUNE.matchFlub * Math.pow(TUNE.matchFlubKeep, n - 1) * (f.flub ?? 1); + assert.ok(whiff < lastWhiff, `opponent ${n} (${f.short}) must whiff less than the one before`); + lastWhiff = whiff; + // Speed is allowed to ZIGZAG by character — opponent 5 is the RUNNER and opponent 6 paints + // lines instead of sprinting — so this bans a real regression, not a personality. The whiff + // rate above is the dial that must fall every single rung; it is the one the ramp rides on. + if (n > 1) assert.ok(speed > lastSpeed * 0.88, + `opponent ${n} (${f.short}) must not be a big step BACKWARDS in speed`); + lastSpeed = speed; + assert.ok(f.name && f.short && f.line, `opponent ${n} is fully named`); + assert.ok(f.short.length <= 8, `${f.short} fits the board's 8-char slot`); + } + // ...and the opening must actually be gentle: opponent one is the softest thing on the card. + assert.ok((matchFoe(1).speed ?? 1) < (matchFoe(10).speed ?? 1) * 0.6, 'the shambler is no champion'); +}); + test('the first opponent cannot paint lines: its returns are width-clamped', () => { // aimError x (1 - 0.55 quality) scattered returns to +-3.5 m, which an auto-run player cannot // cover — "still a LOT of unreachable balls". Width is the ladder's third dial now. @@ -649,7 +675,7 @@ test('an AIMED return cannot be sent out by its own aim, at any timing', () => { // Flicking, the mode's one act of skill, was what lost the point. Reach scales with the same // quality the scatter shrinks with, so the two can never sum past the line — that sum is the // whole rule and it is arithmetic, so assert the arithmetic rather than a sampled rally. - const line = COURT.W / 2, aimW = 3.3; // doHit's far-court aim width + const line = COURT.W / 2, aimW = TUNE.flickWidth; // doHit's far-court aim width for (let q = 0; q <= 1.0001; q += 0.05) { const reach = TUNE.flickReach + (1 - TUNE.flickReach) * q; const worst = aimW * reach + TUNE.aimError * (1 - q); @@ -680,11 +706,16 @@ test('a wounding point knocks the body down and it gets up MINUS the limb', () = update(DT); assert.match(zb.showFrame, /fall1$/, 'first frame is the buckle'); assert.ok(!/w\d/.test(zb.showFrame), 'and it is the UNWOUNDED buckle'); - // ...run to the rise, which must wear the NEW stage - for (let i = 0; i < 0.75 * 60; i++) update(DT); + // ...and it must LIE THERE a moment before it rises: a corpse that touches the ground and is + // instantly upright reads as a stumble, not as something the point did to it. + let downFrames = 0; + for (let i = 0; i < FALL_LEN * 60 && G.matchFall && !/rise$/.test(zb.showFrame || ''); i++) { + update(DT); if (/fall3$/.test(zb.showFrame || '')) downFrames++; + } + assert.ok(downFrames > 0.3 * 60, 'it stays down for a beat: ' + (downFrames / 60).toFixed(2) + 's'); assert.match(zb.showFrame, /w1rise$/, 'it drags itself up as the one-armed body'); // ...and the point comes back only once it is standing - for (let i = 0; i < 0.6 * 60; i++) update(DT); + for (let i = 0; i < FALL_LEN * 60 && G.matchFall; i++) update(DT); assert.equal(G.matchFall, null, 'the fall ends on its own clock'); assert.equal(zb.showFrame, null, 'and hands the sprite back to drawZombie'); assert.equal(G.serveNum, 1, 'matchReady ran: the next point is armed');