diff --git a/HANDOFF.md b/HANDOFF.md index 3026c7b..50fe4a9 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -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 SECOND playtest of the boss/ranged build. Both playtest +Last updated 2026-07-30, after Oliver's THIRD playtest round (the readability pass). 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.** @@ -150,6 +150,22 @@ zombie (now an occasional opponent, `TUNE.racketEvery`, not the every-game fixtu never a change to drawZombie. Missing/unloaded sprites degrade to design 1 rather than vanishing, which is convenient and also means **a wiring bug looks exactly like working code** (see pipeline note 7). +- **Sprites are 2x (`SPRITE_H` 224, `_slice.py` `IDLE_TARGET_H` 224) and EVERY scale-critical sprite + must share that scale.** `RES` is 3, so the player at the baseline rasterises into ~170 DEVICE + pixels — a 112px source was being UPSCALED 1.5x, which is why near bodies looked soft. Sprites that + go through `drawChar`, `drawBoy`, `drawMachine` or the wound-stage `h *= img.height / SPRITE_H` rule + are all measured against `SPRITE_H`, so a sprite left at the old scale renders at HALF height. + That is why the remaster 2x-upscaled the ~29 frames it could not re-slice rather than leaving them. + Sprites drawn through plain `drawStatic(name, x, z, hMeters)` only care about ASPECT, so their + resolution is free. + **Heights above 224 are deliberate, not bugs**: a smash or toss pose SHOULD draw taller than idle + (`p_smash` is 322), and that ratio is exactly what `SPRITE_H` exists to express. +- **The umpire's asides are SITUATIONAL, never a timer pool.** `aside(name)` in gameplay.js is the only + entry point and enforces one shared cooldown (`TUNE.asideEvery`), so several triggers in the same + moment still yield one remark. The first version drew from a bag on a timer and Oliver's verdict was + "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. - **`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 @@ -313,6 +329,60 @@ Known generator quirks, all still true: --- +# CLOSED — Oliver's third playtest: audio, hit timing, readability (2026-07-30) + +### The umpire talked too much and at random +Rebuilt as situational triggers behind one shared cooldown — see the invariant above. `asideEvery` +26 s -> 55 s. Removed `ump_out_of_order`, `ump_let`, and `ump_no_appeal` (he asked for +"ump_no_deal"; that line does not exist and `ump_no_appeal` is the one he meant — confirm if a +different line is still unwanted). 11 `ump_*` lines remain. + +### "Visually the ball is not yet at the player but somehow the player already is hit" +Real, and measurable. The hostile-ball hit test reused the PARRY's `hypot(dx, dz * 0.8)` — weighting +depth by 0.8 SHRINKS it, so contact fired while the ball was still most of a metre up-court. Measured +at the moment of the hit: `dz = -0.68 m`, and the ball drawn at screen y 314 against a head at 305 — +up around his head, not on his body. Fixed with a separate unweighted test (`TUNE.shotHitR` 0.42, +`shotHitY` 1.75). Re-measured: contact at `dz ≈ -0.25 m`, ball at screen y 325 against head 305 / +feet 362 — squarely on the chest. **The parry keeps its 0.8 weighting on purpose**: a racket sweep +really is wider than it is deep. + +### "Everything is a tad too small" (his wife, and the best note of the project) +Measured before changing anything, because "regenerate the sprites at higher resolution" is only half +right. With `RES` 3, drawn DEVICE pixels vs the 112px source were: +``` +player at baseline 170 px UPSCALED 1.52x <- higher-res art helps +shambler your half 120 px upscaled 1.08x <- helps +shambler mid-court 98 px downscaled <- cannot help; needs to be BIGGER +shambler far 78 px downscaled 0.70x <- cannot help +``` +So near bodies needed resolution and far bodies needed the camera. Both were done. + +**Camera** (portrait only; landscape untouched): `camZ` 42->48, `camF` 580->760, `camY` 9->11, +`horizon` 90->45. Pushing the camera back while lengthening the lens FLATTENS the perspective, so +distant bodies grow relative to near ones instead of everything merely zooming; `camY` rises with it +for a steeper look-down so bodies overlap less at depth, and `horizon` re-centres the court after +both. Result: far shambler 26 -> 30 logical px, mid 33 -> 36, chapter-1 boss 39 -> 45, **player +unchanged at 57**, court band 200..390 (was 199..380). If you retune these four, check all four +outputs: far size, near size, court top, court bottom. + +**Sprite remaster, at zero generation cost.** The original `assets/_sheet_*.png` files are retained at +full generated resolution (~1400px wide), so nothing needed regenerating — `IDLE_TARGET_H` went to 224 +and every sheet was RE-SLICED. The sheet-to-output mapping was never written down, so it was recovered +automatically: slice each sheet to a temp dir, then greedily assign frames to existing assets 1:1 by +LANCZOS-normalised RGBA signature distance. 58 sprites came back from sheets; the 29 with no confident +sheet match were 2x-upscaled instead, which adds no detail but keeps them on the shared scale (see the +`SPRITE_H` invariant — a mismatched sprite renders at half height). `x_crowd`/`x_crowd2` were reverted +to originals: they never touch `SPRITE_H` and doubling them is pure memory. +**Known gap:** `p_idle1`/`p_idle2` and most `o_*` frames were among the 2x-upscaled set, so the two +most-seen poses gained scale but not detail. Re-slicing `_sheet_player2/3` and `_sheet_opp2/3` by hand +(rather than via the matcher) would fix that. + +### Regression net +30 tapped tennis points = 50 hits / 0 whiffs. 45-game soak: no exceptions, all four chapters fired, +`mgT` drains, gore 40 / piles 30 / bodies capped at 34. + +--- + # CLOSED — Oliver's second boss/ranged playtest (2026-07-30) ### The parry was too generous diff --git a/assets/_slice.py b/assets/_slice.py index 0b3457a..278da18 100644 --- a/assets/_slice.py +++ b/assets/_slice.py @@ -4,7 +4,7 @@ import sys from PIL import Image -IDLE_TARGET_H = 112 +IDLE_TARGET_H = 224 # 2x remaster: the player renders at ~170 DEVICE px (RES 3), so 112 was being upscaled KEY_D2 = 60 * 60 # squared RGB distance to the bg color that still counts as background MIN_GAP = 12 # empty columns that separate frames MIN_BLOB = 60 # despeckle: drop opaque islands smaller than this many pixels diff --git a/assets/audio/ump_let.m4a b/assets/audio/ump_let.m4a deleted file mode 100644 index 156bd17..0000000 Binary files a/assets/audio/ump_let.m4a and /dev/null differ diff --git a/assets/audio/ump_no_appeal.m4a b/assets/audio/ump_no_appeal.m4a deleted file mode 100644 index fe7a6f4..0000000 Binary files a/assets/audio/ump_no_appeal.m4a and /dev/null differ diff --git a/assets/audio/ump_out_of_order.m4a b/assets/audio/ump_out_of_order.m4a deleted file mode 100644 index 630a7fa..0000000 Binary files a/assets/audio/ump_out_of_order.m4a and /dev/null differ diff --git a/assets/o_bounce.png b/assets/o_bounce.png index a3dd665..ad21eb5 100644 Binary files a/assets/o_bounce.png and b/assets/o_bounce.png differ diff --git a/assets/o_bounce2.png b/assets/o_bounce2.png index fcaa61a..b6d5ac2 100644 Binary files a/assets/o_bounce2.png and b/assets/o_bounce2.png differ diff --git a/assets/o_idle1.png b/assets/o_idle1.png index 83e73f7..c18ab26 100644 Binary files a/assets/o_idle1.png and b/assets/o_idle1.png differ diff --git a/assets/o_idle2.png b/assets/o_idle2.png index 474175d..01aa589 100644 Binary files a/assets/o_idle2.png and b/assets/o_idle2.png differ diff --git a/assets/o_run1.png b/assets/o_run1.png index 7cb99b5..13e40b3 100644 Binary files a/assets/o_run1.png and b/assets/o_run1.png differ diff --git a/assets/o_run2.png b/assets/o_run2.png index 5399f33..7149196 100644 Binary files a/assets/o_run2.png and b/assets/o_run2.png differ diff --git a/assets/o_smash.png b/assets/o_smash.png index 6074b1c..a4f49c2 100644 Binary files a/assets/o_smash.png and b/assets/o_smash.png differ diff --git a/assets/o_swing1.png b/assets/o_swing1.png index d93a6a9..2fc750c 100644 Binary files a/assets/o_swing1.png and b/assets/o_swing1.png differ diff --git a/assets/o_swing2.png b/assets/o_swing2.png index adea653..1933518 100644 Binary files a/assets/o_swing2.png and b/assets/o_swing2.png differ diff --git a/assets/o_toss.png b/assets/o_toss.png index 35001a8..ff1c9c8 100644 Binary files a/assets/o_toss.png and b/assets/o_toss.png differ diff --git a/assets/p_bounce.png b/assets/p_bounce.png index 805a661..fbaf783 100644 Binary files a/assets/p_bounce.png and b/assets/p_bounce.png differ diff --git a/assets/p_bounce2.png b/assets/p_bounce2.png index b786a58..6a1389e 100644 Binary files a/assets/p_bounce2.png and b/assets/p_bounce2.png differ diff --git a/assets/p_idle1.png b/assets/p_idle1.png index 1dd8428..2e752b0 100644 Binary files a/assets/p_idle1.png and b/assets/p_idle1.png differ diff --git a/assets/p_idle2.png b/assets/p_idle2.png index f968412..24579a7 100644 Binary files a/assets/p_idle2.png and b/assets/p_idle2.png differ diff --git a/assets/p_run1.png b/assets/p_run1.png index dca236f..2014c7e 100644 Binary files a/assets/p_run1.png and b/assets/p_run1.png differ diff --git a/assets/p_run2.png b/assets/p_run2.png index 6d912ef..07ff5dd 100644 Binary files a/assets/p_run2.png and b/assets/p_run2.png differ diff --git a/assets/p_smash.png b/assets/p_smash.png index 6ae4a58..da7b4cc 100644 Binary files a/assets/p_smash.png and b/assets/p_smash.png differ diff --git a/assets/p_swing1.png b/assets/p_swing1.png index 61e818c..b28b172 100644 Binary files a/assets/p_swing1.png and b/assets/p_swing1.png differ diff --git a/assets/p_swing2.png b/assets/p_swing2.png index 0361941..5d15c70 100644 Binary files a/assets/p_swing2.png and b/assets/p_swing2.png differ diff --git a/assets/p_toss.png b/assets/p_toss.png index 94b8573..c70896a 100644 Binary files a/assets/p_toss.png and b/assets/p_toss.png differ diff --git a/assets/x_boycarry1.png b/assets/x_boycarry1.png index 3716d8b..9085724 100644 Binary files a/assets/x_boycarry1.png and b/assets/x_boycarry1.png differ diff --git a/assets/x_boycarry2.png b/assets/x_boycarry2.png index 4c9ad3c..44488f5 100644 Binary files a/assets/x_boycarry2.png and b/assets/x_boycarry2.png differ diff --git a/assets/x_boycarry3.png b/assets/x_boycarry3.png index 876950a..3cafc37 100644 Binary files a/assets/x_boycarry3.png and b/assets/x_boycarry3.png differ diff --git a/assets/x_boycarry4.png b/assets/x_boycarry4.png index 0f1f4b1..9ea4cc0 100644 Binary files a/assets/x_boycarry4.png and b/assets/x_boycarry4.png differ diff --git a/assets/x_boymop1.png b/assets/x_boymop1.png index f7c619d..a46bba2 100644 Binary files a/assets/x_boymop1.png and b/assets/x_boymop1.png differ diff --git a/assets/x_boymop2.png b/assets/x_boymop2.png index cc5db5c..5339ce7 100644 Binary files a/assets/x_boymop2.png and b/assets/x_boymop2.png differ diff --git a/assets/x_boymop3.png b/assets/x_boymop3.png index 0466554..5adbbdf 100644 Binary files a/assets/x_boymop3.png and b/assets/x_boymop3.png differ diff --git a/assets/x_boymop4.png b/assets/x_boymop4.png index 22a0468..a9b2c15 100644 Binary files a/assets/x_boymop4.png and b/assets/x_boymop4.png differ diff --git a/assets/x_boyrun1.png b/assets/x_boyrun1.png index 7fd1637..d2fb68b 100644 Binary files a/assets/x_boyrun1.png and b/assets/x_boyrun1.png differ diff --git a/assets/x_boyrun2.png b/assets/x_boyrun2.png index 6e69454..4572b9f 100644 Binary files a/assets/x_boyrun2.png and b/assets/x_boyrun2.png differ diff --git a/assets/x_boyrun3.png b/assets/x_boyrun3.png index 4e5be4b..c8a85b3 100644 Binary files a/assets/x_boyrun3.png and b/assets/x_boyrun3.png differ diff --git a/assets/x_boyrun4.png b/assets/x_boyrun4.png index 27b5d47..7be9df5 100644 Binary files a/assets/x_boyrun4.png and b/assets/x_boyrun4.png differ diff --git a/assets/x_ump.png b/assets/x_ump.png index 9fad0c5..d46c2ca 100644 Binary files a/assets/x_ump.png and b/assets/x_ump.png differ diff --git a/assets/z_a1.png b/assets/z_a1.png index 89f45e4..d4d59f2 100644 Binary files a/assets/z_a1.png and b/assets/z_a1.png differ diff --git a/assets/z_a2.png b/assets/z_a2.png index 6f6bc59..a09930a 100644 Binary files a/assets/z_a2.png and b/assets/z_a2.png differ diff --git a/assets/z_a3.png b/assets/z_a3.png index 6f9d225..9896ca5 100644 Binary files a/assets/z_a3.png and b/assets/z_a3.png differ diff --git a/assets/z_a4.png b/assets/z_a4.png index 71bdea4..8877041 100644 Binary files a/assets/z_a4.png and b/assets/z_a4.png differ diff --git a/assets/z_aw.png b/assets/z_aw.png index 091ffb6..0ac074a 100644 Binary files a/assets/z_aw.png and b/assets/z_aw.png differ diff --git a/assets/z_b1.png b/assets/z_b1.png index def6fdf..953317c 100644 Binary files a/assets/z_b1.png and b/assets/z_b1.png differ diff --git a/assets/z_b2.png b/assets/z_b2.png index 8acf96c..3646823 100644 Binary files a/assets/z_b2.png and b/assets/z_b2.png differ diff --git a/assets/z_b3.png b/assets/z_b3.png index 4ef90f7..801fdfb 100644 Binary files a/assets/z_b3.png and b/assets/z_b3.png differ diff --git a/assets/z_b4.png b/assets/z_b4.png index e6df9b5..ce937fe 100644 Binary files a/assets/z_b4.png and b/assets/z_b4.png differ diff --git a/assets/z_boss1.png b/assets/z_boss1.png index 9f34ac4..0d103d8 100644 Binary files a/assets/z_boss1.png and b/assets/z_boss1.png differ diff --git a/assets/z_boss2.png b/assets/z_boss2.png index e8b8603..3e9d3d9 100644 Binary files a/assets/z_boss2.png and b/assets/z_boss2.png differ diff --git a/assets/z_bosst0.png b/assets/z_bosst0.png index 5b88a96..cc2dd60 100644 Binary files a/assets/z_bosst0.png and b/assets/z_bosst0.png differ diff --git a/assets/z_bosst1.png b/assets/z_bosst1.png index ea2ccd4..4895e75 100644 Binary files a/assets/z_bosst1.png and b/assets/z_bosst1.png differ diff --git a/assets/z_bosst2.png b/assets/z_bosst2.png index 76500f0..5584a1c 100644 Binary files a/assets/z_bosst2.png and b/assets/z_bosst2.png differ diff --git a/assets/z_bosst3.png b/assets/z_bosst3.png index e959545..915d091 100644 Binary files a/assets/z_bosst3.png and b/assets/z_bosst3.png differ diff --git a/assets/z_bossw1.png b/assets/z_bossw1.png index 7871ad3..b3e0b19 100644 Binary files a/assets/z_bossw1.png and b/assets/z_bossw1.png differ diff --git a/assets/z_bossw2.png b/assets/z_bossw2.png index 8efb835..c4e6c24 100644 Binary files a/assets/z_bossw2.png and b/assets/z_bossw2.png differ diff --git a/assets/z_bossw3.png b/assets/z_bossw3.png index 280b539..b8af233 100644 Binary files a/assets/z_bossw3.png and b/assets/z_bossw3.png differ diff --git a/assets/z_bw.png b/assets/z_bw.png index b60cc56..fca4bac 100644 Binary files a/assets/z_bw.png and b/assets/z_bw.png differ diff --git a/assets/z_c1.png b/assets/z_c1.png index 5bcf710..6b7b853 100644 Binary files a/assets/z_c1.png and b/assets/z_c1.png differ diff --git a/assets/z_c2.png b/assets/z_c2.png index 2b33c02..e8ef971 100644 Binary files a/assets/z_c2.png and b/assets/z_c2.png differ diff --git a/assets/z_chair1.png b/assets/z_chair1.png index 5d1ddfc..834b6d6 100644 Binary files a/assets/z_chair1.png and b/assets/z_chair1.png differ diff --git a/assets/z_chair2.png b/assets/z_chair2.png index a101c98..0793885 100644 Binary files a/assets/z_chair2.png and b/assets/z_chair2.png differ diff --git a/assets/z_chairt.png b/assets/z_chairt.png index 049be71..8755e98 100644 Binary files a/assets/z_chairt.png and b/assets/z_chairt.png differ diff --git a/assets/z_chairw.png b/assets/z_chairw.png index 569646e..6e0de32 100644 Binary files a/assets/z_chairw.png and b/assets/z_chairw.png differ diff --git a/assets/z_chairwt.png b/assets/z_chairwt.png index 3a1f008..08327a0 100644 Binary files a/assets/z_chairwt.png and b/assets/z_chairwt.png differ diff --git a/assets/z_cw1.png b/assets/z_cw1.png index 74b152a..7b17a33 100644 Binary files a/assets/z_cw1.png and b/assets/z_cw1.png differ diff --git a/assets/z_cw2.png b/assets/z_cw2.png index 2f73b56..925e48c 100644 Binary files a/assets/z_cw2.png and b/assets/z_cw2.png differ diff --git a/assets/z_f1.png b/assets/z_f1.png index 6a2dacb..cecf680 100644 Binary files a/assets/z_f1.png and b/assets/z_f1.png differ diff --git a/assets/z_f2.png b/assets/z_f2.png index 660b2dc..b05d404 100644 Binary files a/assets/z_f2.png and b/assets/z_f2.png differ diff --git a/assets/z_f3.png b/assets/z_f3.png index 6c68d21..1c57acc 100644 Binary files a/assets/z_f3.png and b/assets/z_f3.png differ diff --git a/assets/z_f4.png b/assets/z_f4.png index e5ad955..91858d0 100644 Binary files a/assets/z_f4.png and b/assets/z_f4.png differ diff --git a/assets/z_g1.png b/assets/z_g1.png index ceff35f..69f8799 100644 Binary files a/assets/z_g1.png and b/assets/z_g1.png differ diff --git a/assets/z_g2.png b/assets/z_g2.png index 1d81e7b..7336ad9 100644 Binary files a/assets/z_g2.png and b/assets/z_g2.png differ diff --git a/assets/z_gw.png b/assets/z_gw.png index fc9dd34..53a1036 100644 Binary files a/assets/z_gw.png and b/assets/z_gw.png differ diff --git a/assets/z_gw2.png b/assets/z_gw2.png index 5d51036..e17b755 100644 Binary files a/assets/z_gw2.png and b/assets/z_gw2.png differ diff --git a/assets/z_gw2b.png b/assets/z_gw2b.png index c28f3ce..7bde6c9 100644 Binary files a/assets/z_gw2b.png and b/assets/z_gw2b.png differ diff --git a/assets/z_gw3.png b/assets/z_gw3.png index 44bbd6c..6fa08ec 100644 Binary files a/assets/z_gw3.png and b/assets/z_gw3.png differ diff --git a/assets/z_gw3b.png b/assets/z_gw3b.png index 4f9e76f..993c710 100644 Binary files a/assets/z_gw3b.png and b/assets/z_gw3b.png differ diff --git a/assets/z_gw4.png b/assets/z_gw4.png index 6ddd66a..bf9703d 100644 Binary files a/assets/z_gw4.png and b/assets/z_gw4.png differ diff --git a/assets/z_gw4b.png b/assets/z_gw4b.png index 0733592..b1898e5 100644 Binary files a/assets/z_gw4b.png and b/assets/z_gw4b.png differ diff --git a/assets/z_gwb.png b/assets/z_gwb.png index dda1a38..df12dbc 100644 Binary files a/assets/z_gwb.png and b/assets/z_gwb.png differ diff --git a/assets/z_judge1.png b/assets/z_judge1.png index 93cd3a7..b3c41f9 100644 Binary files a/assets/z_judge1.png and b/assets/z_judge1.png differ diff --git a/assets/z_judge2.png b/assets/z_judge2.png index f048ffe..111c530 100644 Binary files a/assets/z_judge2.png and b/assets/z_judge2.png differ diff --git a/assets/z_judget.png b/assets/z_judget.png index 55885df..19e08d9 100644 Binary files a/assets/z_judget.png and b/assets/z_judget.png differ diff --git a/assets/z_judgew.png b/assets/z_judgew.png index bcf6487..8ce5ce6 100644 Binary files a/assets/z_judgew.png and b/assets/z_judgew.png differ diff --git a/assets/z_judgewt.png b/assets/z_judgewt.png index 5355478..24cb272 100644 Binary files a/assets/z_judgewt.png and b/assets/z_judgewt.png differ diff --git a/assets/z_mach1.png b/assets/z_mach1.png index f5fe8e9..839553a 100644 Binary files a/assets/z_mach1.png and b/assets/z_mach1.png differ diff --git a/assets/z_mach2.png b/assets/z_mach2.png index c104f2e..c056b41 100644 Binary files a/assets/z_mach2.png and b/assets/z_mach2.png differ diff --git a/assets/z_s1.png b/assets/z_s1.png index 2bf3d97..0241a6d 100644 Binary files a/assets/z_s1.png and b/assets/z_s1.png differ diff --git a/assets/z_s2.png b/assets/z_s2.png index 414022e..d46b694 100644 Binary files a/assets/z_s2.png and b/assets/z_s2.png differ diff --git a/assets/z_s3.png b/assets/z_s3.png index ea080b1..4863327 100644 Binary files a/assets/z_s3.png and b/assets/z_s3.png differ diff --git a/config.js b/config.js index 950b027..05f3074 100644 --- a/config.js +++ b/config.js @@ -67,6 +67,11 @@ export const TUNE = { machWind: 0.9, // s of TELEGRAPH before each shot — the fairness contract, same idea as shriek() machSpeed: 13, // m/s it fires at machDmg: 1, // player hp a shot costs if you don't return it + shotHitR: 0.42, // m — how close a hostile ball must ACTUALLY be to hit you. Measured: the old test + // (0.55 m with the parry's 0.8 z-weighting) fired while the ball was still 0.68 m + // up-court and drawn up around his head — "visually the ball is not yet at the + // player but somehow the player already is hit". Depth is NOT weighted here. + shotHitY: 1.75, // m — and it has to be at body height, so a lob passing overhead never counts parryPow: 0.85, // shot quality of a RETURNED machine ball — a parry is a real weapon // The parry gets its OWN generous reach and window, not the normal swing's. Returning a shot is the // intended answer, on a phone, with one thumb, against something aimed at you — it has to be @@ -98,7 +103,7 @@ export const TUNE = { bossShotSpeed: 15.5, // m/s a boss throws at (the machine's is machSpeed 13) racketEvery: 3, // games between racket-zombie (plain opponent) spawns — was EVERY won game, which // is why he read as "the boss" and why a real boss would not have stood out - asideEvery: 26, // s between the umpire's sarcastic asides in splatter (jittered +-40%). He never + asideEvery: 55, // s between the umpire's sarcastic asides in splatter (jittered +-40%). He never // acknowledged that the match had changed; now he comments on it, disapprovingly. racketHpPerGame: 0.12, // the racket zombie stiffens as the run goes on (floored into whole hp) // ---- damage. Bodies with hp (racket boss 'r', brute 'g') take damage in these units, so a big @@ -289,4 +294,8 @@ export const POSE_TIME = 0.38; export const BOUNCE_IDLE = { dx: 0.5, apex: 0.55, tempo: 3.6 }; // generated sprite sheets: the idle pose is this many pixels tall; all scales derive from it -export const SPRITE_H = 112; +export const SPRITE_H = 224; // 2x remaster (was 112). RES is 3, so the player at the baseline + // rasterises into ~170 DEVICE pixels — a 112px source was being UPSCALED + // 1.5x, which is why near sprites looked soft and mushy. Every sprite that + // goes through drawChar/drawBoy/the wound-stage height rule must share this + // scale, so unmatched sprites were 2x-upscaled rather than left at 112. diff --git a/gameplay.js b/gameplay.js index 9777e33..f6ac929 100644 --- a/gameplay.js +++ b/gameplay.js @@ -241,16 +241,15 @@ export function dismissModal() { } // The umpire's asides. He has never once acknowledged that the match turned into a massacre, and -// that is the joke — so he keeps officiating it. Fired on a jittered timer, and only when nothing -// else is talking (a modal, a changeover, the duel announcement all own the audio). -const ASIDES = ['ump_dress_code', 'ump_no_appeal', 'ump_hindrance', 'ump_new_balls_dark', - 'ump_still_a_match', 'ump_spectators', 'ump_let', 'ump_scoreline', 'ump_unsportsmanlike', - 'ump_out_of_order']; -function umpireAside(dt) { +// that is the joke — so he keeps officiating it. But each line is tied to a SITUATION that actually +// justifies it, not drawn from a bag on a timer: Oliver's verdict on the first version was "too much +// 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; - if ((G.asideT -= dt) > 0) return; - G.asideT = TUNE.asideEvery * (0.6 + Math.random() * 0.8); - voice(ASIDES[Math.floor(Math.random() * ASIDES.length)], 0.2, 0.95); + if (G.asideT > 0) return; + G.asideT = TUNE.asideEvery; + voice(name, delay, 0.95); } export function cheer(seconds, vol) { @@ -321,6 +320,7 @@ export function splatPoint(side, quiet = false) { // long stretch with no clear skips a chapter rather than queueing one, which is intended voice(side === 'P' ? 'game_set_massacre' : 'game_opponent', 0.5); // game-win lines always speak + if (side === 'O') aside('ump_scoreline', 1.4); // they took a game: he would like that noted if (side === 'P') cheer(2.2, 0.5); else aww(0.85); // your game = applause, theirs = a groan const i = side === 'P' ? 0 : 1; if (score.games[i] >= 6) { G.sets[i]++; score.games = [0, 0]; G.afterClear = 'set'; } // set banked; a horde follows the card @@ -388,6 +388,7 @@ function startChangeover() { // now long enough that an empty court reads as the game having stalled. sendBoy(0, COURT.L * 0.75, true); // near enough the camera to actually watch him work G.flash = { text: 'CHANGEOVER', t: 1.4 }; + aside('ump_still_a_match', 2.6); // the breather is when he reminds you this is a match // no tins here anymore — weapons are won in the duel, never handed out on a schedule } @@ -418,6 +419,8 @@ export function deliverTin() { else G.guns = add(G.guns + TUNE.tinGuns); // machine-gun charges — a burst you pop when you choose, not on arrival if (G.weapon !== 0) G.weapon = w + 1; // equip the new tin — but never yank you off a deliberate PLAIN (banking) sendBoy(); voice('new_balls', 0.6); + aside('ump_new_balls_dark', 2.0); // "...and a new opponent, apparently." + const name = SLOTS[w + 1]; showModal({ title: name, body: WDESC[name], icon: ['w_bomb', 'w_triple', 'w_fire', 'w_gun'][w], iconH: 40, color: UI.warm }); @@ -536,6 +539,7 @@ function startHorde() { P.pose = 'idle'; P.poseT = 0; P.swingT = -1; // clear the triggering shot's serve/swing pose P.z = HOME.P.z; // brace at the baseline — the horde is a hold-the-line stand (finger steers x only) voice('remain_seated', 0.3); + aside('ump_spectators', 2.2); // a horde IS the crowd on the court G.flash = { text: 'THEY KEEP COMING', t: 1.2 }; } @@ -912,7 +916,7 @@ export function update(dt) { } crowd.cheer = Math.max(0, crowd.cheer - dt); G.idleT += dt; - umpireAside(dt); + if (G.asideT > 0) G.asideT -= dt; // the asides' shared cooldown (see aside()) // "Time." belongs to the changeover alone. It used to ride the generic 'over' expiry, so any // other beat that overwrote overT while the breather ran (a melee kill taking a game, say) // had the umpire calling time on a pause that was never a changeover. diff --git a/index.html b/index.html index 78b2ee7..0011e2b 100644 --- a/index.html +++ b/index.html @@ -60,6 +60,6 @@
TAP TO START
- +