Sweep the dead code off the court
EATEN_GRAB (write-only marker), ball.firstLand (written five places, read nowhere), zombies' unused VOLLEY import, three scoreboard palette fields from the pre-PNG board, the x_tin.png nothing references, and a dozen export keywords on functions no module ever imported. Kept on purpose: o_run3/o_run4.png — the SPR list excludes them so both tours run on two frames (the women's set has no 3/4 twins), per the comment at the preload list. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e07196f247
commit
d847c2e4a8
7 files changed
+23
-24
No files matched your search
Binary file not shown.
|
Before Width: | Height: | Size: 8.3 KiB |
@@ -138,7 +138,7 @@ export const zombieDuck = out => fadeBed('zombie-mode.m4a', out); // key must ma
|
||||
export const menuDuck = out => fadeBed('background-music.m4a', out);
|
||||
|
||||
let sysVoice = null;
|
||||
export function say(text, delay = 0) { // fallback when a recorded umpire line is missing
|
||||
function say(text, delay = 0) { // fallback when a recorded umpire line is missing
|
||||
try {
|
||||
if (!sysVoice) sysVoice = speechSynthesis.getVoices().find(v => v.lang.startsWith('en-GB')) || null;
|
||||
const u = new SpeechSynthesisUtterance(text);
|
||||
|
||||
@@ -362,7 +362,7 @@ export const APPLAUSE_T = 4.2;
|
||||
// and what is left is the mound · C (EATEN_SUMMARY): the summary fades in over them still feeding.
|
||||
// It is deliberately long. Oliver: "the movies surround and bite them, blood spills, and then the
|
||||
// person basically drowns in the zombies" — that cannot happen in half a second.
|
||||
export const EATEN_GRAB = 0.9, EATEN_BURST = 2.3, EATEN_SUMMARY = 3.6, EATEN_RESTART = 4.6;
|
||||
export const EATEN_BURST = 2.3, EATEN_SUMMARY = 3.6, EATEN_RESTART = 4.6;
|
||||
export const EATEN_FEED_R = 1.15; // m — how tight the feeding ring packs around the body
|
||||
|
||||
// UI accent colours, one place so the whole HUD can be re-tuned at once. Deliberately
|
||||
|
||||
+10
-11
@@ -29,11 +29,11 @@ export function predictLanding() { // first-bounce point, closed form — exact,
|
||||
// further, which is the whole fix: a shambler at the far baseline costs the same short pull as
|
||||
// one on top of you, so the gesture always fits on the screen. The first body standing in the
|
||||
// lane is the target; the cone widens with range so distant lanes stay pickable.
|
||||
export const pullFrac = mag => Math.max(0, Math.min(1, (mag - TUNE.pullMin) / (TUNE.pullFull - TUNE.pullMin)));
|
||||
const pullFrac = mag => Math.max(0, Math.min(1, (mag - TUNE.pullMin) / (TUNE.pullFull - TUNE.pullMin)));
|
||||
export const pullQuality = mag => TUNE.pullQMin + pullFrac(mag) * (1 - TUNE.pullQMin);
|
||||
// pullQuality inverted: hitServe is handed a quality, not a drag, and the band it leaves behind is
|
||||
// drawn at the same weight the live preview had.
|
||||
export const qualityFrac = q => Math.max(0, Math.min(1, (q - TUNE.pullQMin) / (1 - TUNE.pullQMin)));
|
||||
const qualityFrac = q => Math.max(0, Math.min(1, (q - TUNE.pullQMin) / (1 - TUNE.pullQMin)));
|
||||
// A pull that STOPS in the band at the top of the draw, rather than merely reaching it. See
|
||||
// TUNE.perfectBand for why the old "quality == 1" test praised almost every serve.
|
||||
export const pullPerfect = mag => mag >= TUNE.pullFull && mag <= TUNE.pullFull * TUNE.perfectBand;
|
||||
@@ -139,7 +139,7 @@ function doHit(ch, quality, aim) {
|
||||
}
|
||||
ball.g = g;
|
||||
ball.vx = v.vx; ball.vy = v.vy; ball.vz = v.vz;
|
||||
ball.lastHit = ch.side; ball.bounces = 0; ball.firstLand = null;
|
||||
ball.lastHit = ch.side; ball.bounces = 0;
|
||||
G.wasServe = false; // a serve volleyed back pre-bounce must not fault-check the return
|
||||
ball.pow = quality; // carried pace: zombies splatter only above zSplatQ
|
||||
ball.kills = 0; // fresh shot: multi-kill streak restarts
|
||||
@@ -162,7 +162,7 @@ function doHit(ch, quality, aim) {
|
||||
|
||||
// ----------------------------------------------------------------- serve ----
|
||||
function tossBall(sv) {
|
||||
ball.active = true; ball.bounces = 0; ball.firstLand = null; ball.collected = false;
|
||||
ball.active = true; ball.bounces = 0; ball.collected = false;
|
||||
// The player is right-handed: he tosses with his LEFT hand (viewer's left) and hits with the
|
||||
// racket on the viewer's right, so the ball goes up almost over his head — a big offset either way
|
||||
// lands it on the wrong side of one of those two frames. The opponent is drawn from the front and
|
||||
@@ -171,7 +171,7 @@ function tossBall(sv) {
|
||||
ball.vx = 0; ball.vz = 0; ball.vy = TOSS.vy; ball.g = TUNE.gravity;
|
||||
G.phase = 'toss';
|
||||
}
|
||||
export function serveApexY() { return TOSS.y0 + TOSS.vy * TOSS.vy / (2 * TUNE.gravity); } // ~2.95
|
||||
function serveApexY() { return TOSS.y0 + TOSS.vy * TOSS.vy / (2 * TUNE.gravity); } // ~2.95
|
||||
|
||||
// how many 10%-pace cuts a shot may take to get over the net. 8 wasn't enough for a target
|
||||
// sitting right behind the tape — that needs a near-vertical drop, so ~10 cuts (pace × 0.35).
|
||||
@@ -522,10 +522,10 @@ function startChangeover() {
|
||||
// that ("[0,1,2,3,4].find(...)"), so the moment the rack grew past five, a burst weapon sitting in
|
||||
// slot 5..9 could never be found and never spent: beacon, flamer and rocket sat at their pickup
|
||||
// count forever. One accessor, one place that knows the range.
|
||||
export const slotEntry = i => (i === 0 ? G.temp : G.rack[i - 1]) || null;
|
||||
const slotEntry = i => (i === 0 ? G.temp : G.rack[i - 1]) || null;
|
||||
export const slotId = i => slotEntry(i)?.id || (i === 0 ? 'ball' : null);
|
||||
export const slotStock = i => { const e = slotEntry(i); return e ? e.n : (i === 0 ? Infinity : 0); };
|
||||
export const slotOf = id => { // -1 if you are not carrying it
|
||||
const slotOf = id => { // -1 if you are not carrying it
|
||||
for (let i = 0; i <= RACK_MAX; i++) if (slotId(i) === id) return i;
|
||||
return -1;
|
||||
};
|
||||
@@ -549,7 +549,7 @@ export function grantWeapon(id, n = WEAPONS[id].tin) {
|
||||
|
||||
// Spend one from a slot, and hand you back plain balls when it runs dry. The temp weapon does not
|
||||
// leave an empty dimmed slot behind the way a rack entry does — slot 0 simply becomes BALL again.
|
||||
export function spendSlot(i) {
|
||||
function spendSlot(i) {
|
||||
const e = slotEntry(i);
|
||||
if (!e) return;
|
||||
if (--e.n > 0) return;
|
||||
@@ -646,7 +646,7 @@ function stockTestRack() {
|
||||
else if (G.rack.length < RACK_MAX) G.rack.push({ id, n: WEAPONS[id].tin });
|
||||
}
|
||||
}
|
||||
export function tickTestMode(dt) {
|
||||
function tickTestMode(dt) {
|
||||
if (!G.testMode) return;
|
||||
stockTestRack(); // no barrels: the rack is already full of everything they would hand you, and
|
||||
} // one rolling across the sandbox was just something else to look at
|
||||
@@ -934,7 +934,6 @@ function onBounce() {
|
||||
}
|
||||
if (G.mode !== 'tennis') return; // no lines, no faults — the umpire has other concerns
|
||||
if (ball.bounces === 1) {
|
||||
ball.firstLand = { x: ball.x, z: ball.z };
|
||||
const wasServe = G.wasServe; G.wasServe = false;
|
||||
if (wasServe) {
|
||||
if (!inServeBox(ball.x, ball.z)) { ball.active = false; fault(); }
|
||||
@@ -1156,7 +1155,7 @@ export function dragServe(aim) {
|
||||
// walked the player around between shots was the confusing part, not the fire rate.
|
||||
if (ball.active && G.phase !== 'toss') // the old ball flies on as a ballistic clone
|
||||
extras.push({ x: ball.x, y: ball.y, z: ball.z, vx: ball.vx, vy: ball.vy, vz: ball.vz, pow: ball.pow || 0.5, t: 2.5, g: ball.g, wid: ball.wid });
|
||||
ball.active = true; ball.collected = false; ball.bounces = 0; ball.firstLand = null;
|
||||
ball.active = true; ball.collected = false; ball.bounces = 0;
|
||||
ball.x = P.x + 0.12; ball.z = P.z; ball.y = 2.2; ball.g = TUNE.gravity; // over his head, see tossBall
|
||||
// a full pull is quality 1.0, but only a pull that STOPS at the top of the draw is praised
|
||||
hitServe(P, pullQuality(aim.mag), aim, pullPerfect(aim.mag));
|
||||
|
||||
@@ -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, P_BOUNDS, PLAYER_H, BOY_H, UMP_H, UMP_X, MACH_H, BOSS_ART, ZWOUND, woundStage, BOUNCE_IDLE, SPRITE_H, zombieH , WEAPONS, RACK_ROW } from './config.js';
|
||||
import { TUNE, UI, EATEN_BURST, EATEN_SUMMARY, EATEN_RESTART, COURT, P_BOUNDS, PLAYER_H, BOY_H, UMP_H, UMP_X, MACH_H, BOSS_ART, ZWOUND, woundStage, BOUNCE_IDLE, SPRITE_H, zombieH , WEAPONS, RACK_ROW } from './config.js';
|
||||
import { P, O, ball, score, crowd, G, court, tour } from './state.js';
|
||||
import { predictLanding, previewServe, slotId, slotStock } from './gameplay.js';
|
||||
import { zombies, gore, piles, parts, pellets, extras, shots } from './zombies.js';
|
||||
@@ -67,7 +67,7 @@ export let W = 480, H = 270;
|
||||
// BOTTOM — the canvas deliberately runs to the PHYSICAL bottom edge so the court art fills the
|
||||
// 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 };
|
||||
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;
|
||||
@@ -263,7 +263,7 @@ for (const n of ['p_idle1','p_idle2','p_bounce','p_bounce2','p_toss','p_smash','
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ crowd ----
|
||||
export function buildCrowd() {
|
||||
function buildCrowd() {
|
||||
crowd.dots.length = 0;
|
||||
const rows = Math.floor((proj(0, 0, 0).y - 26) / 5.5), cols = Math.ceil(W / 8);
|
||||
for (let row = 0; row < rows; row++) for (let i = 0; i < cols; i++) crowd.dots.push({
|
||||
@@ -1095,8 +1095,8 @@ const RACK_GRAB = 7; // px the TOUCH band extends above the drawn rack (below,
|
||||
// UPWARD instead, and only once you are actually carrying more than five: the common case is still
|
||||
// one row on the bottom edge, exactly as before.
|
||||
const rackRows = () => (G.rack.length + 1 > RACK_ROW ? 2 : 1);
|
||||
export const rackTop = () => H - TUNE.rackH * rackRows() - RACK_BOT - SAFE.bottom;
|
||||
export const METER_Y = () => rackTop() - 17;
|
||||
const rackTop = () => H - TUNE.rackH * rackRows() - RACK_BOT - SAFE.bottom;
|
||||
const METER_Y = () => rackTop() - 17;
|
||||
function rackGeom() {
|
||||
const h = TUNE.rackH, tw = Math.min(64, (W - 4) / RACK_ROW), rw = tw * RACK_ROW;
|
||||
const rows = rackRows();
|
||||
@@ -1539,8 +1539,8 @@ export function render() {
|
||||
// sunk into a recessed slot — purple for the server, gold digits. Splatter adds a recessed
|
||||
// status well (kills-to-next-point pips + weapon stock). Hard edges, no radius = retro.
|
||||
const splat = G.mode !== 'tennis';
|
||||
const bd = { face: '#1e5b3a', hi: '#3f9560', lo: '#0e3624', slot: '#0b2b1c', slotSvr: '#2b1a46',
|
||||
spine: '#472a70', spineHi: '#5f3d94', cream: '#f2ead6', dim: '#89a08f', gold: UI.pick, engHi: '#2c7a4f' };
|
||||
const bd = { face: '#1e5b3a', hi: '#3f9560', lo: '#0e3624',
|
||||
spine: '#472a70', spineHi: '#5f3d94', cream: '#f2ead6', dim: '#89a08f', gold: UI.pick };
|
||||
const R = (x, y, w, h, c) => { ctx.fillStyle = c; ctx.fillRect(x, y, w, h); };
|
||||
const ptTxt = i => score.pts[0] >= 3 && score.pts[1] >= 3
|
||||
? (score.pts[i] === score.pts[1 - i] ? '40' : score.pts[i] > score.pts[1 - i] ? 'AD' : '40')
|
||||
|
||||
@@ -7,7 +7,7 @@ import { gore } from './zombies.js'; // runtime-only cycle (zombies imports stat
|
||||
export const P = { x: 1.3, z: 22.8, side: 'P', pose: 'idle', poseT: 0, swingT: -1, facing: 1, runT: 0 };
|
||||
export const O = { x: -1.3, z: 1.0, side: 'O', pose: 'idle', poseT: 0, swingT: -1, facing: 1, runT: 0 };
|
||||
export const ball = { x: 0, y: 1, z: 20, vx: 0, vy: 0, vz: 0, g: 9.2,
|
||||
active: false, lastHit: 'P', bounces: 0, firstLand: null, collected: false, kills: 0, bomb: false, flame: false,
|
||||
active: false, lastHit: 'P', bounces: 0, collected: false, kills: 0, bomb: false, flame: false,
|
||||
// which weapon launched the ball in flight — the stats screen files a kill under it long after
|
||||
// the WEAPONS row itself is out of scope. Lives on the ball so PRISTINE resets it for free.
|
||||
wid: 'ball' };
|
||||
@@ -147,7 +147,7 @@ export const other = s => s === 'P' ? 'O' : 'P';
|
||||
export const deuceSide = () => (score.pts[0] + score.pts[1]) % 2 === 0; // even points → deuce (right) side
|
||||
|
||||
export function setupPoint() {
|
||||
G.phase = 'pretoss'; ball.active = false; ball.bounces = 0; ball.firstLand = null;
|
||||
G.phase = 'pretoss'; ball.active = false; ball.bounces = 0;
|
||||
G.swingSnaps = null; G.rallyNear = null; G.swung = false;
|
||||
if (G.mode === 'splatter') { // always your serve, from wherever you're standing
|
||||
score.server = 'P';
|
||||
|
||||
+2
-2
@@ -3,7 +3,7 @@
|
||||
// your baseline — one crossing it is a point for THEM.
|
||||
// (zombies <-> gameplay is a runtime-only import cycle, same pattern as input.)
|
||||
|
||||
import { TUNE, UI, COURT, VOLLEY, CONTACT, BOSS_ART, WEAPONS, BARREL_WEAPONS, woundStage, zombieH, EATEN_BURST, EATEN_SUMMARY, EATEN_FEED_R } from './config.js';
|
||||
import { TUNE, UI, COURT, CONTACT, BOSS_ART, WEAPONS, BARREL_WEAPONS, woundStage, zombieH, EATEN_BURST, EATEN_SUMMARY, EATEN_FEED_R } from './config.js';
|
||||
import { G, P, ball, sendBoy, setupPoint } from './state.js';
|
||||
import { splat, moan, pock, thud, zap, voice, boom, swish, shriek, aww, sfx , whoosh } from './audio.js';
|
||||
import { splatPoint, cheer, spentBall, resolveInterlude, showModal, endBossFight, aside, grantWeapon, weaponModal, endBurst } from './gameplay.js';
|
||||
@@ -461,7 +461,7 @@ function killZombie(i, quiet) {
|
||||
// ---- fire. The flame ball doesn't just kill what it touches, it starts an epidemic: the kill
|
||||
// ignites its neighbours, and every burning body keeps igniting whatever it shambles past.
|
||||
// Read-only over zombies[] (it only sets burnT), so it is safe to call from inside a kill loop.
|
||||
export function ignite(x, z, r) {
|
||||
function ignite(x, z, r) {
|
||||
for (const zb of zombies) {
|
||||
if (!zb || zb.burnT > 0) continue; // the boss DOES catch now — just at TUNE.bossBurnMul the rate,
|
||||
// so fire is a real answer to a big body instead of a no-op
|
||||
|
||||
Reference in new issue
Block a user