Fill the screen, and let the swish keep up with a late horde

The portrait canvas is adaptive now. A fixed 270x480 is 9:16 while a modern
iPhone is taller, so width bound the fit and ~150 CSS px of screen went unused
at the bottom. Height is derived from the device aspect, and TUNE.horizon rides
with it — proj() places the court in absolute pixels, so leaving horizon fixed
just grew a dead green band between the court and the rack instead of moving
the court down. RES 3 to 4 for the extra pixels: an iPhone gives ~1125 device
px across and we were rendering 810. That is the first knob to turn back down
if the phone stutters.

The swish cooldown scales with how swarmed you are — 1.00s at one body, 0.47 at
four, 0.22 from fourteen. Oliver died on every game-20 horde because the wave
grows with hordeGrow while the panic button stayed a flat second; he chose
scaling the melee over shrinking the horde. The first version measured the
crowd after meleeSwipe had already killed it, so it counted survivors and
barely moved.

Winning a duel while wounded lost the weapon popup entirely: showModal never
clobbers an open modal, so the heal modal opened first and the tin's was
dropped. One popup carries both now.

And the mop boy stays off the court unless a stain actually passes the test
mopRound uses to pick a target. He was walking on, finding nothing reachable,
and walking straight off again.

Tennis untouched: 50 hits / 0 whiffs. 45-game soak clean, all four chapters.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Opus 5 committed 2026-07-30 17:43:48 +02:00
1 parent 49d391dc64
commit ae64daa0fb
6 files changed
+108 -19

No files matched your search

+48 -1
View File
@@ -5,7 +5,7 @@ everything they specified is shipped, and what still matters from them is record
long history, `~/.claude/projects/.../memory/splatter-tennis-history.md` is auto-loaded; for the
detail of any individual change, read the git log.
Last updated 2026-07-30, after Oliver's THIRD playtest round (the readability pass). All playtest
Last updated 2026-07-30, after Oliver's FOURTH playtest round (viewport, horde survivability). All playtest
rounds are closed at the bottom. Step 6 (crowd turning, ball-boy catcher) stays PARKED by his
decision. **Awaiting his next phone playtest.**
@@ -166,6 +166,22 @@ zombie (now an occasional opponent, `TUNE.racketEvery`, not the every-game fixtu
"too much and not situation aware.. they seem random". Each line is now tied to the thing that
justifies it — a new variant appearing, the court filling up, one crossing your baseline, a game lost,
a tin delivered, a horde, a changeover, a machine, a boss, the trainer, taking a hit.
- **The portrait viewport ADAPTS to the device, and `TUNE.horizon` must ride with it.** `fitViewport`
picks `h = 270 * clamp(innerHeight/innerWidth, 1.6, 2.3)` — a fixed 270x480 is 9:16 while a modern
iPhone is ~0.46, so width bound the fit and ~150 CSS px of screen went unused. `proj()` places the
court in ABSOLUTE logical px off `horizon`, and the rack/meter hang off `H - ...`, so extra height
lands entirely in the apron between them: leaving `horizon` fixed just grew a dead green band.
It is computed as `45 + (h - 480) * 0.72` for that reason. `RES` is 4 (was 3) so an iPhone's ~1125
device px across is nearly 1:1 with our 1080 — **`RES` is the first knob to turn down if the phone
ever stutters**, it is the whole fill-rate budget.
Note `#hint` (the desktop key legend) is already `display:none` on `pointer: coarse`; it only
overlaps the rack in the desktop test pane, which is not a real bug.
- **The swish cooldown scales with how swarmed you are** (`meleeCooldown()` in gameplay.js):
`meleeCool / (1 + (near-1) * meleeCrowd)`, floored at `meleeCoolMin`, counting bodies within
`meleeCrowdR`. 1 body 1.00 s, 4 → 0.47, 8 → 0.27, 14+ → 0.22. Oliver died on every game-20 horde
because the wave grows with `hordeGrow` while the panic button was a flat 1 s; he chose scaling the
melee over shrinking the horde. **Measure the crowd BEFORE calling `meleeSwipe`** — it kills
everything in reach, so counting after counts the survivors and the cooldown barely moves.
- **`UI` in config.js is the accent palette** (hot/warm/pick/cream/dim/bad/ball). Every HUD colour
goes through it. Gore reds are deliberately NOT in it — the splatter stays loud.
- **`F(size, bold)` in render.js builds every canvas font string.** Bare `monospace` resolves to
@@ -329,6 +345,37 @@ Known generator quirks, all still true:
---
# CLOSED — Oliver's fourth playtest (2026-07-30)
### The game did not use the full height of his iPhone
Fixed by making the portrait logical height adaptive and letting `horizon` ride with it — see the
invariant above. On a 375x812 pane: logical 270x585 (was 270x480), canvas CSS 375x812, **100% of both
axes**. `RES` 3 -> 4 for the "extra pixels" he asked about.
### Dying on every game-20 horde
The swish cooldown now scales with crowding — see the invariant. Note the ordering trap recorded
there; the first implementation measured the crowd after the swipe had already killed it, so the
cooldown only moved once ~14 bodies were present.
### The weapon message was lost when the heal popup fired
`showModal` deliberately never clobbers an open modal, so winning a duel while wounded opened the
heal modal and the weapon modal was silently dropped. `deliverTin(healLine)` now folds the trainer
into the WEAPON popup as an extra line, so there is one popup carrying both. An unwounded duel win
still shows the plain weapon popup.
### The mop boy walked out with nothing to mop
`sendBoy` now returns early when `mop` is set and no stain passes the same test `mopRound` uses to
pick a target (`a >= 0.15 && z >= COURT.netZ - 2`) — he was walking on, finding nothing reachable,
and walking straight off. This needs `state.js` to read `gore`, which adds a **third runtime-only
import cycle** (state <-> zombies) alongside input<->gameplay and zombies<->gameplay; it is safe for
the same reason, `gore` is only touched inside `sendBoy` at runtime, never during module evaluation.
### Regression net
30 tapped tennis points = 50 hits / 0 whiffs. 45-game soak: no exceptions, all four chapters,
`mgT` drains, gore 40 / piles 30 / bodies capped at 34.
---
# CLOSED — Oliver's third playtest: audio, hit timing, readability (2026-07-30)
### The umpire talked too much and at random