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
+9
-3
@@ -6,7 +6,7 @@
|
||||
import { TUNE, UI, COURT, VOLLEY, CONTACT, BOSS_ART } from './config.js';
|
||||
import { G, P, ball, sendBoy, setupPoint } from './state.js';
|
||||
import { splat, moan, pock, voice, boom, swish, shriek, aww, sfx } from './audio.js';
|
||||
import { splatPoint, cheer, spentBall, resolveInterlude, showModal, endBossFight } from './gameplay.js';
|
||||
import { splatPoint, cheer, spentBall, resolveInterlude, showModal, endBossFight, aside } from './gameplay.js';
|
||||
|
||||
export const zombies = []; // {x, z, v, walkT, variant: 'a'|'b'|'c'}
|
||||
export const gore = []; // lawn decals {x, z, r, a} — the flat ground layer
|
||||
@@ -34,6 +34,7 @@ export function spawnZombie(x, z, variant) {
|
||||
if (variant === 'a' || variant === 'b') zb.hp = TUNE.shamblerHp; // classic shamblers take a wound before they drop — only the runner is one-shot glass
|
||||
if (variant === 'g') { zb.v *= 0.55; zb.hp = TUNE.bruteHp; } // slow, but soaks hits
|
||||
if (variant === 's') zb.shieldT = 0; // chair up; > 0 = seconds of stagger left
|
||||
if ('fgs'.includes(variant) && !G.seen['v' + variant]) { G.seen['v' + variant] = true; aside('ump_dress_code', 0.8); }
|
||||
if (variant === 'm') { // the rogue ball machine: furniture, not a body. It does not walk at all.
|
||||
zb.v = 0; zb.hp = TUNE.machHp; zb.fireT = TUNE.machCycle; zb.windT = 0;
|
||||
}
|
||||
@@ -431,6 +432,7 @@ export function updateZombies(dt) {
|
||||
// the game literally stopped changing. The gap is hyperbolic now so it keeps tightening on its
|
||||
// own instead of running into a floor; zSpawnMin is only a safety stop.
|
||||
const cap = Math.min(TUNE.zMaxCap, TUNE.zMax + Math.floor(G.runGames * TUNE.zMaxPerGame));
|
||||
if (zombies.length >= cap - 1) aside('ump_hindrance', 0.6); // the court is genuinely full
|
||||
if (spawnT <= 0 && zombies.length < cap) {
|
||||
spawnZombie((Math.random() * 2 - 1) * 4.8, -1.5 - Math.random(), pickVariant());
|
||||
spawnT = Math.max(TUNE.zSpawnMin, TUNE.zSpawnEvery / (1 + G.runGames * TUNE.zSpawnAccel));
|
||||
@@ -566,7 +568,7 @@ export function updateZombies(dt) {
|
||||
ball.vx *= 0.6; ball.vz *= 0.6; ball.lastHit = 'X'; // spent — one stagger per ball
|
||||
}
|
||||
}
|
||||
if (zb.z >= COURT.L + 0.7) { zombies.splice(i, 1); if (G.mode !== 'eaten' && !G.horde && !G.clearing) splatPoint('O'); } // it reached your baseline (during a mop-up, a straggler that reaches the line just despawns — no point for them)
|
||||
if (zb.z >= COURT.L + 0.7) { aside('ump_unsportsmanlike', 0.5); zombies.splice(i, 1); if (G.mode !== 'eaten' && !G.horde && !G.clearing) splatPoint('O'); } // it reached your baseline (during a mop-up, a straggler that reaches the line just despawns — no point for them)
|
||||
}
|
||||
|
||||
for (let i = pellets.length - 1; i >= 0; i--) { // machine-gun rounds: flat, fast, one-shot kills
|
||||
@@ -597,7 +599,11 @@ export function updateZombies(dt) {
|
||||
s.t -= dt; s.vy -= s.g * dt;
|
||||
s.x += s.vx * dt; s.y += s.vy * dt; s.z += s.vz * dt;
|
||||
let done = s.t <= 0 || s.y < -0.25 || Math.abs(s.x) > 8 || s.z > COURT.L + 2;
|
||||
// The PARRY keeps the 0.8 depth weighting — a racket sweep really is wider than it is deep.
|
||||
// The HIT does not: weighting depth shrinks it, so contact registered while the ball was still
|
||||
// most of a metre up-court. That mismatch between what you saw and what hit you was the bug.
|
||||
const dP = Math.hypot(s.x - P.x, (s.z - P.z) * 0.8);
|
||||
const dHit = Math.hypot(s.x - P.x, s.z - P.z);
|
||||
if (!done && P.swingT >= 0 && P.swingT <= TUNE.parryWindow
|
||||
&& dP < TUNE.parryReach && s.y < CONTACT.maxY) {
|
||||
// the parry: it becomes an `extras` clone — that array is already ballistic and already kills
|
||||
@@ -607,7 +613,7 @@ export function updateZombies(dt) {
|
||||
pock(0.85, 1300); G.flash = { text: 'RETURNED', t: 0.6 }; cheer(0.8, 0.3);
|
||||
done = true;
|
||||
}
|
||||
if (!done && dP < 0.55 && s.y < 2.0) { hurtPlayer(TUNE.machDmg); done = true; }
|
||||
if (!done && dHit < TUNE.shotHitR && s.y < TUNE.shotHitY) { hurtPlayer(TUNE.machDmg); done = true; }
|
||||
if (done) shots.splice(i, 1);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user