Stop the opponent shrinking, skating and sprinting on the spot
The "smaller when swinging or tossing" was an anchor bug, not art. _fine.py scales a whole sheet so the --anchor frame is 224 px, and drawChar draws every sprite at img.height/SPRITE_H — so anchoring opponent_play on the TOSS, with arm and ball above the head, made 224 px cover the raised arm and shrank every body on that sheet. o_swing1 was 162 px against a 224 px standing pose. Each opponent batch now leads with a standing `ref` frame, which _fine.py anchors on and _regen.py discards. Correctly anchored the toss is 278 px and the man is full size; the swing is 221. The receiver no longer slides 0.12 m side to side while waiting on a serve — sliding a sprite sideways reads as skating, and the split-step swap to the run frames (removed earlier) had already been the other half of that. The idle bounce ball had one hardcoded offset for both men. They reach to opposite sides of the screen — the player is drawn from behind, the opponent from the front — so the opponent's ball sat out by his racket. dx and apex are per-man now, and o_bounce/o_bounce2 were redrawn with a patting hand to put it under. Also: the opponent wears a RED headband, and a prompt of mine that said "white" had quietly repainted it on six frames. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
cd1c7a0cd8
commit
494bab1b69
17 files changed
+84
-14
No files matched your search
@@ -151,7 +151,7 @@ export function proj(x, y, z) {
|
||||
|
||||
// ---------------------------------------------------------------- sprites ----
|
||||
// assets/<name>.png; game falls back to code-drawn figures until sprites load.
|
||||
const SPRV = 75; // asset cache-buster — bump with main.js?v=N (see the img.src note below)
|
||||
const SPRV = 76; // 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_run3','p_run4','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
|
||||
@@ -470,9 +470,10 @@ function drawPiles(near) { // split at the net like zombies so height occludes c
|
||||
}
|
||||
|
||||
function drawChar(ch) {
|
||||
const receiverWaiting = G.phase === 'pretoss' && ch.side !== score.server && ch.pose === 'idle';
|
||||
const sway = receiverWaiting ? Math.sin(G.idleT * 3.2) : 0;
|
||||
const p = proj(ch.x + sway * 0.12, 0, ch.z);
|
||||
// No split-step. The receiver used to be slid 0.12 m side to side while waiting on a serve, on top
|
||||
// of a swap to the run frames; both are gone. Sliding a sprite sideways is not footwork — it reads
|
||||
// as the man skating — and the idle pair's own weight shift is the whole of what he should be doing.
|
||||
const p = proj(ch.x, 0, ch.z);
|
||||
const hpx = PLAYER_H * p.s;
|
||||
// pick sprite
|
||||
const pre = ch.side === 'P' ? 'p_' : 'o_';
|
||||
@@ -495,7 +496,8 @@ function drawChar(ch) {
|
||||
// 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
|
||||
// (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');
|
||||
if (ch.pose === 'serve') name = pre + (G.phase === 'toss' ? 'toss' : 'smash');
|
||||
if (ch.pose === 'run') p.y -= Math.floor(ch.runT * 18) % 2; // 1px run bob
|
||||
@@ -905,11 +907,14 @@ export function render() {
|
||||
// server idly bouncing the ball while waiting
|
||||
if (G.phase === 'pretoss') {
|
||||
const sv = score.server === 'P' ? P : O;
|
||||
// The ball goes where the sprite reaches for it. The player is right-handed and drawn from
|
||||
// behind, so the racket is on the viewer's right and the hand that pats the ball is his LEFT —
|
||||
// the viewer's left. The opponent is drawn from the front and already reached that way.
|
||||
const bx = sv.x - BOUNCE_IDLE.dx;
|
||||
const by = Math.abs(Math.sin(G.idleT * BOUNCE_IDLE.tempo)) * BOUNCE_IDLE.apex;
|
||||
// The ball goes where the sprite reaches for it, and the two men reach to OPPOSITE SIDES of the
|
||||
// screen. Both are right-handed, so both pat the ball with the left hand — but the player is
|
||||
// drawn from BEHIND (his left hand is on the viewer's left) and the opponent from the FRONT
|
||||
// (his left hand is on the viewer's right). One sign for both put the opponent's ball out by
|
||||
// his racket instead of under his palm.
|
||||
const mine = sv.side === 'P';
|
||||
const bx = sv.x + (mine ? -BOUNCE_IDLE.dx : BOUNCE_IDLE.oDx);
|
||||
const by = Math.abs(Math.sin(G.idleT * BOUNCE_IDLE.tempo)) * (mine ? BOUNCE_IDLE.apex : BOUNCE_IDLE.oApex);
|
||||
drawBall(bx, by, sv.z);
|
||||
}
|
||||
// the match ball (and any triple-serve side balls in flight)
|
||||
|
||||
Reference in new issue
Block a user