Ride the court high enough to actually pull under him

The slingshot never had room. On a 375x812 his feet sat 658 CSS px down an
812 px screen, leaving 112 px of clear court under him — and a full draw is
130. The wind-up physically did not fit, so the thumb was driven onto the iOS
bottom edge every rally, where a swipe down is Reachability and the system
takes the gesture. The gesture was never the problem; the geometry was.

So the portrait horizon is derived from the BOTTOM now: park his feet a full
draw plus the rack plus slack above the screen edge, and let the horizon fall
where it must. What gets shorter is the stand above the court, and that was
only ever crowd. 375x812: horizon 121 -> 51, the clear band under him 112 ->
210 px, and a full pull from his feet now ends 122 px clear of the edge.

Landscape is untouched. `horizon` is still the live knob in the T panel.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Opus 5 committed 2026-08-02 14:39:05 +02:00
1 parent 8ea749f380
commit 0fe8f37a81
1 file changed
+18 -6
+18 -6
View File
@@ -1,7 +1,7 @@
// Camera, viewport, sprites and all drawing. Owns the crowd dots (visual-only state). // Camera, viewport, sprites and all drawing. Owns the crowd dots (visual-only state).
import { cv, ctx } from './dom.js'; 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 { P, O, ball, score, crowd, G, court } from './state.js';
import { predictLanding, flickTarget } from './gameplay.js'; import { predictLanding, flickTarget } from './gameplay.js';
import { zombies, gore, piles, parts, pellets, extras, shots } from './zombies.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 // screen (the home indicator is a slim bar, not opaque UI). Only the interactive RACK is
// lifted clear of it, by SAFE.bottom below. // lifted clear of it, by SAFE.bottom below.
export const SAFE = { top: 0, bottom: 0, topCss: 0 }; 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) { function measureSafe(scale) {
const el = document.getElementById('safe'); const el = document.getElementById('safe');
if (!el) return; if (!el) return;
@@ -116,11 +119,20 @@ export function fitViewport() { // landscape 480x270; portrait 270 wide and as T
TUNE.camZ = portrait ? 48 : 42; TUNE.camZ = portrait ? 48 : 42;
TUNE.camY = portrait ? 11 : 9; TUNE.camY = portrait ? 11 : 9;
TUNE.camF = portrait ? 760 : 406; TUNE.camF = portrait ? 760 : 406;
// horizon RIDES the adaptive height. proj() places the court in ABSOLUTE logical px off this, so // THE COURT RIDES HIGH ENOUGH TO PULL UNDER HIM. A full draw is TUNE.pullFull CSS px of thumb
// leaving it fixed while h grew just dumped every extra pixel into a dead green apron between the // travel STARTING at his feet, and it has to finish clear of the rack and of the iOS bottom
// court and the rack. 0.72 of the extra height goes to pushing the court down (the rest becomes // edge — where a swipe down is Reachability and the system takes the gesture out from under
// more stand above it), which keeps the apron about where it was at the old fixed 480. // you, which no web page can refuse. Positioning the court from the TOP could not promise that:
TUNE.horizon = portrait ? Math.round(45 + (h - 480) * 0.72) : 40; // 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(); buildCrowd();
} }
ctx.imageSmoothingEnabled = false; // resets on canvas resize ctx.imageSmoothingEnabled = false; // resets on canvas resize