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:
1 parent
49d391dc64
commit
ae64daa0fb
6 files changed
+108
-19
No files matched your search
@@ -39,13 +39,22 @@ const hexA = (h, a) => `rgba(${parseInt(h.slice(1, 3), 16)},${parseInt(h.slice(3
|
||||
|
||||
// ---------------------------------------------------------------- camera ----
|
||||
const IS_TOUCH = matchMedia('(pointer: coarse)').matches; // hoisted: a matchMedia call per frame churns GC
|
||||
export const RES = 3; // physical pixels per logical pixel — 3x so phone screens downscale (crisp) instead of upscaling (soft)
|
||||
// Physical pixels per logical pixel. On an iPhone the canvas ends up ~390 CSS px wide = 1170 DEVICE
|
||||
// px, so RES 3 (810) was still being upscaled 1.44x by the browser. 4 gets us to 1080, near 1:1.
|
||||
// THIS IS THE FIRST KNOB TO TURN DOWN if the phone ever stutters: it is the whole fill-rate budget.
|
||||
export const RES = 4;
|
||||
export let W = 480, H = 270;
|
||||
|
||||
export function fitViewport() { // landscape 480x270, portrait 270x480 with a longer lens
|
||||
export function fitViewport() { // landscape 480x270; portrait 270 wide and as TALL as the phone is
|
||||
const portrait = innerHeight > innerWidth;
|
||||
const [w, h] = portrait ? [270, 480] : [480, 270];
|
||||
if (cv.width !== w * RES) {
|
||||
// Portrait height ADAPTS to the device. A fixed 270x480 is 9:16, but a modern iPhone is taller
|
||||
// (~390x844 = 0.46), so width bound the fit and ~150 CSS px of screen went unused at the bottom.
|
||||
// The court is positioned by proj() off TUNE.horizon in ABSOLUTE logical px, and the rack/meter hang
|
||||
// off `H - ...`, so extra height simply widens the apron between the court and the rack — nothing
|
||||
// has to be re-laid-out. Clamped so a freak aspect can't stretch the apron absurdly.
|
||||
const tall = Math.round(270 * Math.max(1.6, Math.min(2.3, innerHeight / Math.max(1, innerWidth))));
|
||||
const [w, h] = portrait ? [270, tall] : [480, 270];
|
||||
if (cv.width !== w * RES || cv.height !== h * RES) {
|
||||
W = w; H = h;
|
||||
cv.width = w * RES; cv.height = h * RES;
|
||||
// flatter lens: camZ pushed back (config 42, was 36) with camF scaled to keep the PLAYER
|
||||
@@ -60,7 +69,11 @@ export function fitViewport() { // landscape 480x270, portrait 270x480 with a lo
|
||||
TUNE.camZ = portrait ? 48 : 42;
|
||||
TUNE.camY = portrait ? 11 : 9;
|
||||
TUNE.camF = portrait ? 760 : 406;
|
||||
TUNE.horizon = portrait ? 45 : 40;
|
||||
// horizon RIDES the adaptive height. proj() places the court in ABSOLUTE logical px off this, so
|
||||
// leaving it fixed while h grew just dumped every extra pixel into a dead green apron between the
|
||||
// court and the rack. 0.72 of the extra height goes to pushing the court down (the rest becomes
|
||||
// more stand above it), which keeps the apron about where it was at the old fixed 480.
|
||||
TUNE.horizon = portrait ? Math.round(45 + (h - 480) * 0.72) : 40;
|
||||
buildCrowd();
|
||||
}
|
||||
ctx.imageSmoothingEnabled = false; // resets on canvas resize
|
||||
@@ -81,7 +94,7 @@ export function proj(x, y, z) {
|
||||
|
||||
// ---------------------------------------------------------------- sprites ----
|
||||
// assets/<name>.png; game falls back to code-drawn figures until sprites load.
|
||||
const SPRV = 43; // asset cache-buster — bump with main.js?v=N (see the img.src note below)
|
||||
const SPRV = 44; // asset cache-buster — bump with main.js?v=N (see the img.src note below)
|
||||
const SPR = {};
|
||||
for (const n of ['p_idle1','p_idle2','p_bounce','p_bounce2','p_toss','p_smash','p_run1','p_run2','p_swing1','p_swing2',
|
||||
'o_idle1','o_idle2','o_bounce','o_bounce2','o_toss','o_smash','o_run1','o_run2','o_swing1','o_swing2',
|
||||
|
||||
Reference in new issue
Block a user