Make it readable: 2x sprites, a flatter camera, honest hit timing
The umpire is situational again. Each line is tied to the thing that justifies it — a new variant appearing, the court filling, one crossing your baseline, a game lost, a horde, a changeover — behind one shared cooldown, so several triggers at once still yield one remark. The random timer pool is gone, the interval went 26s to 55s, and ump_out_of_order, ump_let and ump_no_appeal are deleted. "Visually the ball is not yet at the player but somehow the player already is hit" was real and measurable. The hostile-ball hit test reused the parry's hypot(dx, dz*0.8), and weighting depth shrinks it: contact fired while the ball was still 0.68 m up-court and drawn up around his head. It has its own unweighted test now, and contact lands at 0.25 m with the ball on his chest. The parry keeps its weighting — a racket sweep really is wider than it is deep. On "everything is a tad too small": measured first, because higher-resolution sprites are only half the answer. At RES 3 the player rasterises into 170 device pixels from a 112px source — upscaled 1.5x — while a far shambler is downscaled to 78. So near bodies needed resolution and far bodies needed the camera. Camera: pushed back and lengthened (camZ 42->48, camF 580->760) to flatten the perspective so distant bodies grow relative to near ones rather than everything zooming, camY raised for a steeper look-down, horizon re-centred. Far shambler 26->30px, boss 39->45px, player unchanged, court in the same band. Sprites: the original sheets were still in the repo at full resolution, so nothing needed regenerating — IDLE_TARGET_H went to 224 and every sheet was re-sliced. The sheet-to-output mapping was never recorded, so it was recovered by matching sliced frames to existing assets by image signature: 58 came back from sheets, the 29 without a confident match were 2x-upscaled to stay on the shared scale, since a sprite off that scale renders at half height. 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:
1 parent
8bc57e9358
commit
49d391dc64
95 files changed
+119
-21
No files matched your search
+71
-1
@@ -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
|
||||
|
||||
Reference in new issue
Block a user