diff --git a/render.js b/render.js index 9d437a9..42f2dca 100644 --- a/render.js +++ b/render.js @@ -1,7 +1,7 @@ // Camera, viewport, sprites and all drawing. Owns the crowd dots (visual-only state). import { cv, ctx } from './dom.js'; -import { TUNE, UI, EATEN_GRAB, EATEN_BURST, EATEN_SUMMARY, EATEN_RESTART, COURT, PLAYER_H, BOY_H, UMP_H, UMP_X, MACH_H, BOSS_ART, BOUNCE_IDLE, SPRITE_H } from './config.js'; +import { TUNE, UI, EATEN_GRAB, EATEN_BURST, EATEN_SUMMARY, EATEN_RESTART, COURT, P_BOUNDS, PLAYER_H, BOY_H, UMP_H, UMP_X, MACH_H, BOSS_ART, BOUNCE_IDLE, SPRITE_H } from './config.js'; import { P, O, ball, score, crowd, G, court } from './state.js'; import { predictLanding, flickTarget } from './gameplay.js'; import { zombies, gore, piles, parts, pellets, extras, shots } from './zombies.js'; @@ -48,6 +48,9 @@ export let W = 480, H = 270; // screen (the home indicator is a slim bar, not opaque UI). Only the interactive RACK is // lifted clear of it, by SAFE.bottom below. export const SAFE = { top: 0, bottom: 0, topCss: 0 }; +// Logical px of slack under a full draw, on top of the rack: the home indicator (~34 CSS px) plus +// enough that a pull ENDING near the edge is still nowhere near STARTING there. See fitViewport. +const PULL_ROOM = 30; function measureSafe(scale) { const el = document.getElementById('safe'); if (!el) return; @@ -116,11 +119,20 @@ export function fitViewport() { // landscape 480x270; portrait 270 wide and as T TUNE.camZ = portrait ? 48 : 42; TUNE.camY = portrait ? 11 : 9; TUNE.camF = portrait ? 760 : 406; - // 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; + // THE COURT RIDES HIGH ENOUGH TO PULL UNDER HIM. A full draw is TUNE.pullFull CSS px of thumb + // travel STARTING at his feet, and it has to finish clear of the rack and of the iOS bottom + // edge — where a swipe down is Reachability and the system takes the gesture out from under + // you, which no web page can refuse. Positioning the court from the TOP could not promise that: + // on a 375x812 it left 112 CSS px under him for a 130 px pull, so the wind-up physically did + // not fit and the thumb was driven onto the edge every single rally. + // So park his feet a full draw above the rack and let the horizon fall wherever it must. What + // gets shorter is the stand above the court, and that was only ever crowd. + // (`horizon` is the live knob — it survives in the T panel until the viewport actually changes.) + const perCss = h / Math.max(1, bh); // logical px per CSS px (bh is 0 in a hidden tab) + const apron = TUNE.pullFull * perCss + TUNE.rackH + PULL_ROOM; + // his scale at his DEEPEST, so the room holds wherever he is standing when you draw + const feetY = TUNE.camY * TUNE.camF / (TUNE.camZ - P_BOUNDS.zMax); + TUNE.horizon = portrait ? Math.round(h - apron - feetY) : 40; buildCrowd(); } ctx.imageSmoothingEnabled = false; // resets on canvas resize