Two frames each: the run cycles land where the zombies always were
The live bench session's verdict, in Oliver's words: "this looks better than anything before." Six and eight real phases at speed read as flicker on a far-court body — the zombies never had this problem because their walks are FOUR well-chosen frames at a calm rate, and the fix was to stop out-drawing them. Each opponent now runs a TWO-frame true leg-swap pair — his frames 1+2 of the grid roll, hers 7+8 of the face-transplant cycle, both picked live on the bench with SPR swaps — at TUNE.runHz2 = 1.7 (the old hardcoded 4.5 for short cycles was most of a stride per display frame; that is the strobe). The tuning rig ships too: TUNE.runN (frame-count override, 0 = probe), T.SPR for live frame swaps, and the bench's living slots — the next cadence question gets answered by watching, not rolling. Twelve run sprites become four; the SPR list says why. SPRV 124, main.js v248. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
No files matched your search
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 24 KiB |
@@ -494,6 +494,6 @@ if (location.hostname !== 'splatter-tennis.wuwi.one') {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="src/main.js?v=247"></script>
|
||||
<script type="module" src="src/main.js?v=248"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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).
|
||||
|
||||
@@ -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; } };
|
||||
@@ -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 <img> 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');
|
||||
|
||||