diff --git a/assets/o_run1_f.png b/assets/o_run1_f.png index ff12c73..f785af1 100644 Binary files a/assets/o_run1_f.png and b/assets/o_run1_f.png differ diff --git a/assets/o_run2_f.png b/assets/o_run2_f.png index 68176b9..bb19f99 100644 Binary files a/assets/o_run2_f.png and b/assets/o_run2_f.png differ diff --git a/assets/o_run3.png b/assets/o_run3.png deleted file mode 100644 index cb421e2..0000000 Binary files a/assets/o_run3.png and /dev/null differ diff --git a/assets/o_run3_f.png b/assets/o_run3_f.png deleted file mode 100644 index 8e461b8..0000000 Binary files a/assets/o_run3_f.png and /dev/null differ diff --git a/assets/o_run4.png b/assets/o_run4.png deleted file mode 100644 index b4dbea6..0000000 Binary files a/assets/o_run4.png and /dev/null differ diff --git a/assets/o_run4_f.png b/assets/o_run4_f.png deleted file mode 100644 index a7d1dad..0000000 Binary files a/assets/o_run4_f.png and /dev/null differ diff --git a/assets/o_run5.png b/assets/o_run5.png deleted file mode 100644 index 2e6803a..0000000 Binary files a/assets/o_run5.png and /dev/null differ diff --git a/assets/o_run5_f.png b/assets/o_run5_f.png deleted file mode 100644 index 239ac12..0000000 Binary files a/assets/o_run5_f.png and /dev/null differ diff --git a/assets/o_run6.png b/assets/o_run6.png deleted file mode 100644 index 8da1ef6..0000000 Binary files a/assets/o_run6.png and /dev/null differ diff --git a/assets/o_run6_f.png b/assets/o_run6_f.png deleted file mode 100644 index 08168fd..0000000 Binary files a/assets/o_run6_f.png and /dev/null differ diff --git a/assets/o_run7_f.png b/assets/o_run7_f.png deleted file mode 100644 index f785af1..0000000 Binary files a/assets/o_run7_f.png and /dev/null differ diff --git a/assets/o_run8_f.png b/assets/o_run8_f.png deleted file mode 100644 index bb19f99..0000000 Binary files a/assets/o_run8_f.png and /dev/null differ diff --git a/index.html b/index.html index 0a9f01c..47da6da 100644 --- a/index.html +++ b/index.html @@ -494,6 +494,6 @@ if (location.hostname !== 'splatter-tennis.wuwi.one') { - + diff --git a/src/config.js b/src/config.js index bfbed97..c306f3b 100644 --- a/src/config.js +++ b/src/config.js @@ -41,6 +41,9 @@ export const TUNE = { // the dive is what buys the pace its net clearance serveHardErr: 1.6,// how much a hard serve multiplies the timing scatter: pace is paid for in // faults, which is what makes tap = safe second serve a real decision + runN: 0, // frame-count override for run cycles (0 = probe per character sheet) — bench tuning knob + runHz2: 1.7, // stride cycles/s for TWO-frame pairs (the opponents). Tuned live on the bench + // with Oliver 2026-08-14 — the old hardcoded 4.5 was strobe; 1.7 reads as running. runHz: 2.4, // stride cycles/s for the rolled 6/8-frame run sets. The old hardcoded 4.5 was // tuned on 2-4 near-duplicate frames; on eight real phases it strobed faceFlip: 0.13, // s an auto-steered direction must persist before the body mirrors (earnFlip). diff --git a/src/main.js b/src/main.js index a4b2ede..8d56a2a 100644 --- a/src/main.js +++ b/src/main.js @@ -7,7 +7,7 @@ import { TUNE, COURTS, TOURS, MODES, MENU, DIFFS, EATEN_RESTART, DONE_RESTART, W import { P, O, ball, score, G, setupPoint, selectCourt, selectTour } from './state.js'; import { update, restartRun, keyPressed, solveShot, inServeBox, inCourt, forceZombies, splatPoint, deliverTin, selectWeapon, grantWeapon, showModal, dismissModal, flickTarget, dragServe, toggleTestMode } from './gameplay.js'; import { startDuel, resolveInterlude, startBossFight, endBossFight, startDrill, endDrill, startMatch, endMatch, startBench, endBench, FALL, benchFall } from './beats.js'; -import { fitViewport, render, proj, RES, SPRV, LOAD } from './render.js'; +import { fitViewport, render, proj, RES, SPRV, LOAD, SPR } from './render.js'; import { voice, ambient, menuMusic, menuDuck } from './audio.js'; import { zombies, spawnZombie, spawnRacketZombie, spawnMachine, spawnChapterBoss, piles, gore, parts, pellets, extras, shots, pops, explode, hurtPlayer, killPlayer , spawnBarrel } from './zombies.js'; import { cv } from './dom.js'; @@ -556,6 +556,6 @@ if (location.hash === '#bench') setTimeout(() => { start(null); intoSplatter(); requestAnimationFrame(frame); // exposed for in-browser sanity checks and simulated playtests -window.T = { TUNE, ball, P, O, score, G, zombies, keyPressed, laserScene, laserFire, pause: v => { paused = v !== false; }, spawnZombie, spawnRacketZombie, spawnMachine, spawnChapterBoss, spawnBarrel, grantWeapon, toggleTestMode, piles, gore, parts, pellets, extras, shots, pops, explode, hurtPlayer, killPlayer, splatPoint, solveShot, inServeBox, inCourt, proj, setupPoint, forceZombies, deliverTin, selectWeapon, showModal, dismissModal, dragServe, startDuel, resolveInterlude, flickTarget, startBossFight, endBossFight, startDrill, endDrill, startMatch, endMatch, +window.T = { TUNE, ball, P, O, score, G, zombies, keyPressed, SPR, laserScene, laserFire, pause: v => { paused = v !== false; }, spawnZombie, spawnRacketZombie, spawnMachine, spawnChapterBoss, spawnBarrel, grantWeapon, toggleTestMode, piles, gore, parts, pellets, extras, shots, pops, explode, hurtPlayer, killPlayer, splatPoint, solveShot, inServeBox, inCourt, proj, setupPoint, forceZombies, deliverTin, selectWeapon, showModal, dismissModal, dragServe, startDuel, resolveInterlude, flickTarget, startBossFight, endBossFight, startDrill, endDrill, startMatch, endMatch, stats, step: dt => update(dt), render, press: keyPressed, FALL, benchFall, get phase() { return G.phase; }, set phase(v) { G.phase = v; } }; diff --git a/src/render.js b/src/render.js index fe5dea2..1f41e4e 100644 --- a/src/render.js +++ b/src/render.js @@ -221,16 +221,16 @@ function farB(z) { // Asset cache-buster — bump with main.js?v=N (see the img.src note below). EXPORTED because the // menu screens hang tags on the same files, and two copies of this number is one copy too // many: the one that does not get bumped is the one that serves a cached 404. -export const SPRV = 123; +export const SPRV = 124; // The boot bar in main.js watches this. Nothing here CHANGES when things load — every sprite was // already requested at import — it just makes the wait visible and holds the menu back until the // last sheet has landed. See the onerror note below for the other half of "sometimes missing". export const LOAD = { done: 0, total: 0 }; -const SPR = {}; +export const SPR = {}; for (const n of ['p_idle1','p_idle2','p_bounce','p_bounce2','p_toss','p_smash','p_run1','p_run2','p_run3','p_run4','p_run5','p_run6','p_swing1','p_swing2','p_die1','p_die2','p_die3','p_die4','p_die5', 'p_pull', // the serve wind-up: the racket dropping behind him while the pull is drawn 'o_idle1','o_idle2','o_bounce','o_bounce2','o_toss','o_smash','o_swing1','o_swing2', - 'o_run1','o_run2','o_run3','o_run4','o_run5','o_run6', // the rolled 6-frame cycle (a 2x6 grid roll is TWO cycles — one row IS the cycle, never stitch the halves) + 'o_run1','o_run2', // TWO frames, a true leg-swap pair — the whole 2026-08-14 tuning session's verdict: fewer, better-chosen frames at a calm rate beat every 6/8-frame roll ('this looks better than anything before') 'x_ump','x_boyrun1','x_boyrun2','x_boyrun3','x_boyrun4', 'x_crowd','x_crowd2','x_ads', // CENTRE COURT (grass) — also the fallback set 'x_crowd_c','x_crowd_c2','x_ads_c', // TERRE ROUGE (clay) @@ -290,7 +290,7 @@ for (const n of ['p_idle1','p_idle2','p_bounce','p_bounce2','p_toss','p_smash',' // could actually see was blue popping in after three frames of white. 'p_die1_f','p_die2_f','p_die3_f','p_die4_f','p_die5_f', 'o_idle1_f','o_idle2_f','o_bounce_f','o_bounce2_f','o_toss_f','o_smash_f', - 'o_run1_f','o_run2_f','o_run3_f','o_run4_f','o_run5_f','o_run6_f','o_run7_f','o_run8_f','o_swing1_f','o_swing2_f', + 'o_run1_f','o_run2_f','o_swing1_f','o_swing2_f', // her pair likewise (frames 7+8 of the face-transplant cycle) 'z_c1_f','z_c2_f','z_c3_f','z_c4_f','z_cw1_f','z_cw2_f','z_cw1b_f','z_cw2b_f', 'z_r1_f','z_r2_f','z_r3_f','z_r4_f', 'z_rw1_f','z_rw1b_f','z_rw1s_f','z_rw2_f','z_rw2b_f','z_rw2s_f', @@ -1488,11 +1488,12 @@ function drawChar(ch) { // The run cycle at whatever count the character's sheet carries: the _runroll.py cycles are 6 // (one call, one row, one character — see assets/_runroll.py for why the old per-frame art // flickered); un-rolled characters fall back to 4 or 2. - const runN = SPR[pre + 'run8'] ? 8 : SPR[pre + 'run6'] ? 6 : SPR[pre + 'run4'] ? 4 : 2; + // TUNE.runN: live override for the bench's tuning sessions (0 = probe the sheet as always) + const runN = TUNE.runN || (SPR[pre + 'run8'] ? 8 : SPR[pre + 'run6'] ? 6 : SPR[pre + 'run4'] ? 4 : 2); // The rolled 6/8-frame cycles run at their own STRIDE RATE (TUNE.runHz, cycles/s). The old 4.5 // was tuned on 2-4 near-duplicate frames, where it read as vigour — on eight real phases it was // a strobe ("very flashy and unnatural"), most of a full stride every display frame. - if (ch.pose === 'run') name = pre + 'run' + (Math.floor(ch.runT * (runN >= 6 ? TUNE.runHz : 4.5) * runN) % runN + 1); + if (ch.pose === 'run') name = pre + 'run' + (Math.floor(ch.runT * (runN >= 6 ? TUNE.runHz : TUNE.runHz2) * runN) % runN + 1); // (the receiver used to flip to run1/run2 here as a "split-step" — it read as a man sprinting on // the spot, because the run frames are a full leaning stride, not a ready-hop. He idles now.) if (ch.pose === 'swing') name = pre + (ch.poseT < 0.15 ? 'swing1' : 'swing2');