Run the player on four frames instead of two

Two frames is a lurch, and he is on screen the whole run — during a horde he is
the only thing moving. Regenerated the whole cycle in one pass rather than
bolting two new frames onto the old pair, so the four are consistent with each
other, then fitted them per channel onto the mean and sd of the frames that
stay (idle + swing): mean error under 3 of 255, so he does not change colour
when he starts moving.

The pick is frame-count driven, not hardcoded — the opponent has no run3/run4
and stays on two, and the cycle RATE is unchanged either way: more frames
subdivide the stride, they do not speed it up.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Opus 5 committed 2026-07-31 19:02:15 +02:00
1 parent d62478e8b1
commit 94136ddf8c
7 files changed
+9 -5

No files matched your search

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 23 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

+1 -1
View File
@@ -97,6 +97,6 @@
<p id="build"></p>
</div>
</div>
<script type="module" src="main.js?v=56"></script>
<script type="module" src="main.js?v=57"></script>
</body>
</html>
+8 -4
View File
@@ -155,9 +155,9 @@ export function proj(x, y, z) {
// ---------------------------------------------------------------- sprites ----
// assets/<name>.png; game falls back to code-drawn figures until sprites load.
const SPRV = 51; // asset cache-buster — bump with main.js?v=N (see the img.src note below)
const SPRV = 52; // asset cache-buster — bump with main.js?v=N (see the img.src note below)
const SPR = {};
for (const n of ['p_idle1','p_idle2','p_bounce','p_bounce2','p_toss','p_smash','p_run1','p_run2','p_swing1','p_swing2',
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_swing1','p_swing2',
'o_idle1','o_idle2','o_bounce','o_bounce2','o_toss','o_smash','o_run1','o_run2','o_swing1','o_swing2',
'x_ump','x_boyrun1','x_boyrun2','x_boyrun3','x_boyrun4','x_crowd','x_crowd2','x_ads',
'x_boymop1','x_boymop2','x_boymop3','x_boymop4', // the push cycle, used only while he is stopped and working
@@ -434,7 +434,11 @@ function drawChar(ch) {
const rising = Math.sin(w) * Math.cos(w) > 0;
name = pre + (rising ? 'bounce2' : 'bounce'); // hand up while the ball rises, pat down exactly at the apex
}
if (ch.pose === 'run') name = pre + (Math.floor(ch.runT * 9) % 2 ? 'run1' : 'run2');
// Four-frame run where the frames exist (the player), two where they don't (the opponent). Same
// CYCLE rate either way — more frames subdivide the stride, they do not speed it up, exactly as
// ZFRAMES does for the shamblers.
const runN = SPR[pre + 'run4'] ? 4 : 2;
if (ch.pose === 'run') name = pre + 'run' + (Math.floor(ch.runT * 4.5 * runN) % runN + 1);
if (receiverWaiting) name = pre + (sway > 0 ? 'run1' : 'run2'); // split-step shuffle
if (ch.pose === 'swing') name = pre + (ch.poseT < 0.15 ? 'swing1' : 'swing2');
if (ch.pose === 'serve') name = pre + (G.phase === 'toss' ? 'toss' : 'smash');
@@ -442,7 +446,7 @@ function drawChar(ch) {
// Auto-fire: the idle sprite carries a racket at his hip AND drawMgRacket puts one in his hands —
// two rackets. The run frames are the racket-less art, so he jogs on the spot instead.
if (ch.side === 'P' && ch.pose === 'idle' && G.mode === 'splatter' && (G.horde || G.mgT > 0))
name = pre + (Math.floor(G.idleT * 6) % 2 ? 'run1' : 'run2');
name = pre + 'run' + (Math.floor(G.idleT * 3 * runN) % runN + 1);
const img = SPR[name];
drawShadow(p.x, p.y, hpx * 0.22, hpx * 0.06);
if (img) {