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
+24
-10
@@ -411,7 +411,7 @@ const WDESC = { // what the tin you just won actually does — one line, shown o
|
||||
FIRE: ['KILLS ON ANY TOUCH', 'AND SETS THE OTHERS ALIGHT'],
|
||||
GUN: ['HOLD THE LINE AND SPRAY', 'A BURST, NOT A SERVE'],
|
||||
};
|
||||
export function deliverTin() {
|
||||
export function deliverTin(healLine = null) {
|
||||
const w = G.deliveries++ % 4, add = k => Math.min(TUNE.wCap, k);
|
||||
if (w === 0) G.bombs = add(G.bombs + TUNE.bombMax);
|
||||
else if (w === 1) G.triples = add(G.triples + TUNE.tinTriples);
|
||||
@@ -422,8 +422,11 @@ export function deliverTin() {
|
||||
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 });
|
||||
// healLine folds the trainer's visit INTO this popup. `showModal` deliberately never clobbers an
|
||||
// open modal (a kill cascade can try to open two in one frame), so when both happened the heal
|
||||
// modal opened first and the weapon one was lost entirely — Oliver: "the weapon message is lost".
|
||||
showModal({ title: name, body: healLine ? [...WDESC[name], '', healLine] : WDESC[name],
|
||||
icon: ['w_bomb', 'w_triple', 'w_fire', 'w_gun'][w], iconH: 40, color: UI.warm });
|
||||
}
|
||||
|
||||
// ---- the interlude: order is restored, one sudden-death point vs the racket zombie.
|
||||
@@ -496,17 +499,14 @@ export function resolveInterlude(side) {
|
||||
cheer(3, 0.7);
|
||||
// winning the duel patches you up. THE reason to want a duel rather than merely tolerate it —
|
||||
// it is the only healing in the game, and blood is the only thing you cannot buy back otherwise.
|
||||
let healLine = null;
|
||||
if (G.hp < G.hpMax) {
|
||||
const before = G.hp;
|
||||
G.hp = Math.min(G.hpMax, G.hp + TUNE.healPerDuel);
|
||||
// a real popup, not a flash: getting patched up is the only way blood comes back, so it deserves
|
||||
// to stop the world for a beat and say so plainly. The trainer came on. It's still tennis.
|
||||
voice('ump_trainer', 0.2, 0.95);
|
||||
showModal({ title: 'WOUNDS TREATED', color: UI.pick, icon: 'x_cross', iconH: 34,
|
||||
body: ['THE TRAINER PATCHED YOU UP',
|
||||
(G.hp - before) + ' OF ' + G.hpMax + ' BACK · NOW ' + G.hp + '/' + G.hpMax] });
|
||||
healLine = '+ WOUNDS TREATED · NOW ' + G.hp + '/' + G.hpMax;
|
||||
}
|
||||
deliverTin(); // win the point, win the next tin — on the spot
|
||||
deliverTin(healLine); // win the point, win the next tin — and the trainer, in ONE popup
|
||||
} else {
|
||||
voice('game_opponent', 0.4); aww(0.85); // lost the sudden-death duel
|
||||
score.games[1]++; G.runGames++; // it goes on the scoreboard
|
||||
@@ -793,12 +793,26 @@ export function dragServe(aim) {
|
||||
hitServe(P, pullQuality(aim.mag), aim); // a full pull is quality 1.0 — PERFECT, hitstop and all
|
||||
}
|
||||
|
||||
// The swish cooldown, scaled by how crowded you are. Oliver died on every game-20 horde: the wave
|
||||
// grows with hordeGrow but the panic button was a flat 1 s, so past a certain density it simply could
|
||||
// not keep up. He picked "melee fires more frequently with horde density" over shrinking the horde.
|
||||
function meleeCooldown() {
|
||||
let near = 0;
|
||||
for (const zb of zombies) if (zb && Math.hypot(zb.x - P.x, zb.z - P.z) < TUNE.meleeCrowdR) near++;
|
||||
return Math.max(TUNE.meleeCoolMin, TUNE.meleeCool / (1 + Math.max(0, near - 1) * TUNE.meleeCrowd));
|
||||
}
|
||||
|
||||
export function keyPressed(code) {
|
||||
if (code !== 'Space') return;
|
||||
audio(); // unlock on first gesture
|
||||
// a swish cleaves adjacent shamblers — but it's on a cooldown now: the panic button, not the
|
||||
// default attack. A whiff costs nothing (nothing was in range), a connect locks it for meleeCool.
|
||||
if (G.mode === 'splatter' && G.meleeT <= 0 && meleeSwipe(P.x, P.z, TUNE.meleeRange)) G.meleeT = TUNE.meleeCool;
|
||||
if (G.mode === 'splatter' && G.meleeT <= 0) {
|
||||
// measure the crowd BEFORE the swipe — it kills everything in reach, so counting afterwards
|
||||
// counts the SURVIVORS and the cooldown barely moved no matter how swarmed you were
|
||||
const cool = meleeCooldown();
|
||||
if (meleeSwipe(P.x, P.z, TUNE.meleeRange)) G.meleeT = cool;
|
||||
}
|
||||
// AN INCOMING SHOT OWNS THE TAP. The parry used to live behind the `phase === 'rally'` branch at the
|
||||
// bottom of this function — and in splatter you are in `pretoss` almost all of the time, so a tap
|
||||
// aimed at a machine's shot tossed the serve ball instead and the return was, in practice,
|
||||
|
||||
Reference in new issue
Block a user