Draw the whole serve right-handed, and let the pull wind it up
The racket kept changing hands because the art disagreed with itself: p_idle, p_bounce and p_toss held it in his LEFT hand while p_smash and p_swing used his right, so every new frame copied whichever reference it was handed. The idle, the bounce pair, the toss and the four wind-up frames are all regenerated with the side named — drawn from behind, his right hand is the right-hand side of the image, and only the racket HEAD crosses behind his back. What separates the four wind-up stages is how far the racket has fallen, and that needed the racket head pinned to a body landmark per frame — above his head, beside his ear, between his shoulder blades, at the back of his thighs. Described as "further into it each time" it drew four near-identical poses twice running. They are wired late in the pull, Oliver's call: frame 1 owns the first half and the three coiled frames are packed into the top half. assets/_derive.py is deleted. Fixing generated art by mangling it afterwards is always worse than regenerating; three guards in _regen.py turn the bad rolls that used to install silently into automatic re-rolls — a portrait reference canvas (the generator answered with a 2x2 grid), a non-magenta background (a green sheet keyed away four racket heads), and a mirrored frame. The bounce pair is one drawing from one sheet now, differing only in the free hand, so BOUNCE_IDLE moved to his left side and was re-measured off the sprite. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
231cd8650a
commit
bcff17812f
17 files changed
+181
-170
No files matched your search
@@ -155,7 +155,7 @@ export function proj(x, y, z) {
|
||||
|
||||
// ---------------------------------------------------------------- sprites ----
|
||||
// assets/<name>.png; game falls back to code-drawn figures until sprites load.
|
||||
const SPRV = 70; // asset cache-buster — bump with main.js?v=N (see the img.src note below)
|
||||
const SPRV = 71; // 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_pull1','p_pull2','p_pull3','p_pull4', // the serve WIND-UP, one frame per quarter of the pull (see drawChar)
|
||||
@@ -479,19 +479,21 @@ function drawChar(ch) {
|
||||
const pre = ch.side === 'P' ? 'p_' : 'o_';
|
||||
let name = pre + (Math.floor(G.idleT * 1.6) % 2 ? 'idle1' : 'idle2'); // idle weight shift
|
||||
if (G.phase === 'pretoss' && ch.side === score.server && ch.pose === 'idle') {
|
||||
// The two bounce frames are ONE drawing whose free hand sits 0.10 m apart (p_bounce2 is sheared
|
||||
// out of p_bounce, see assets/_bendarm.py) — generated as a pair they came back as two different
|
||||
// men and the swap read as a flicker. So the swap can follow the BALL: hand up when the ball is
|
||||
// up at it, hand down when the ball is near the ground. BOUNCE_IDLE.apex is measured off the
|
||||
// raised hand, so the ball meets the palm rather than passing it.
|
||||
// The two bounce frames are the SAME drawing from one sheet — identical stance, identical legs,
|
||||
// and only the free hand's height and reach differ — so the swap cannot read as a flicker. It
|
||||
// follows the BALL: hand up when the ball is up at it, hand out and low when the ball is near
|
||||
// the ground. BOUNCE_IDLE.apex is measured off that palm, so the ball meets it.
|
||||
name = pre + (Math.abs(Math.sin(G.idleT * BOUNCE_IDLE.tempo)) > 0.5 ? 'bounce2' : 'bounce');
|
||||
}
|
||||
// THE PULL IS THE WIND-UP. While the slingshot is drawn, the frame is chosen by how far back the
|
||||
// finger has come: racket above the head -> behind the head -> behind the back -> fully coiled.
|
||||
// Four drawn stages over pullFrac (the same 0..1 that sets the shot's power), so the gesture and
|
||||
// the man are the same movement, and the release snaps to p_smash.
|
||||
if (G.aim && G.aim.mag > 0 && ch.side === 'P' && score.server === 'P' && ch.pose === 'idle')
|
||||
name = 'p_pull' + (1 + Math.min(3, Math.floor(pullFrac(G.aim.mag) * 4)));
|
||||
// finger has come: racket above the head -> behind the head -> behind the back -> fully coiled,
|
||||
// and the release snaps to p_smash. The stages are NOT spread evenly over the pull: frame 1 owns
|
||||
// the whole first half and the three coiled frames are packed into the top half, so the part of
|
||||
// the gesture that costs power is the part that visibly costs effort.
|
||||
if (G.aim && G.aim.mag > 0 && ch.side === 'P' && score.server === 'P' && ch.pose === 'idle') {
|
||||
const f = pullFrac(G.aim.mag);
|
||||
name = 'p_pull' + (f < 0.5 ? 1 : Math.min(4, 2 + Math.floor((f - 0.5) * 6)));
|
||||
}
|
||||
// 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.
|
||||
@@ -899,10 +901,10 @@ 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. Drawn from behind, the player bounces it beside
|
||||
// his RIGHT foot, which is the viewer's right — the old sign put it on his left while the new art
|
||||
// reached the other way, so the ball and the hand disagreed.
|
||||
const bx = sv.x + BOUNCE_IDLE.dx * (sv.side === 'P' ? 1 : -1);
|
||||
// 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;
|
||||
drawBall(bx, by, sv.z);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user