Brighter stands, a serve hint that shows the gesture, and a boy who walks
- The crowd sat in the dark, worst of all in splatter mode where you spend the game. Both crowd frames get an identical gamma lift (means 30/14/39 -> 42/22/56) and the rot tint drops 0.5 -> 0.26. The curve MUST stay identical across the two frames or the clap swap flashes brightness; _sheet_crowd1.png keeps the pre-lift original. - The splatter hint led with the tap when pulling is the whole gesture. It now reads PULL BACK TO AIM - RELEASE TO FIRE, and after 1.5s of sitting in the serve stance untouched a ghost thumb slides back while chevrons grow up-court on a loop. Pull back, shoot forward is an inverse relationship that a line of text cannot carry on its own. - The mop boy has three sheets instead of two: the push cycle now plays ONLY while he is stopped and working, a new carry sheet walks him upright with the mop shouldered, and the run sheet stays for the tennis-mode ball fetch. He was mopping thin air the entire way to the stain. Sheet note: frames 2 and 3 of the carry sheet merged (a mop handle bridging the gap), so it is cut at the true column gaps plus the thinnest column inside the merged pair, then common-scaled — same technique as the brute stride sheet. 26-game soak clean. Serve returns 133/150 = 88.7% +/-2.6%, 30 human-tapped points = 50 hits / 0 whiffs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
21b9dd53fa
commit
7dde5874ab
14 files changed
+67
-5
No files matched your search
@@ -76,7 +76,8 @@ 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',
|
||||
'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','x_howto', // the mopping boy, and the slingshot tutorial panel
|
||||
'x_boymop1','x_boymop2','x_boymop3','x_boymop4', // the push cycle, used only while he is stopped and working
|
||||
'x_boycarry1','x_boycarry2','x_boycarry3','x_boycarry4','x_howto', // walking with the mop shouldered, and the slingshot tutorial panel
|
||||
'z_gw2','z_gw3','z_gw4','z_gwb','z_gw2b','z_gw3b','z_gw4b', // the brute comes apart in stages (4/5 hp = z_gw .. 1/5 = z_gw4), each with a 2-frame lurch (…b)
|
||||
'z_a1','z_a2','z_a3','z_a4','z_b1','z_b2','z_b3','z_b4','z_c1','z_c2',
|
||||
'z_f1','z_f2','z_f3','z_f4','z_g1','z_g2','z_gw','z_s1','z_s2','z_s3',
|
||||
@@ -143,11 +144,15 @@ function drawStatic(name, x, z, hMeters, bob = 0) {
|
||||
function drawBoy(b) {
|
||||
// mopping is not sprinting: its own sheet and a slower push cycle. Falls back to the run
|
||||
// frames until the mop PNGs have loaded.
|
||||
// Three sheets, because a boy WALKING to the next stain is not a boy mopping: x_boymop is the
|
||||
// push cycle (used only while he's stopped and working), x_boycarry is him walking upright with
|
||||
// the mop shouldered, x_boyrun is the plain tennis-mode ball fetch.
|
||||
const mop = b.mop && SPR.x_boymop1;
|
||||
const sheet = !mop ? 'x_boyrun' : b.mopT > 0 ? 'x_boymop' : SPR.x_boycarry1 ? 'x_boycarry' : 'x_boymop';
|
||||
// Frames step off DISTANCE COVERED, not elapsed time: a time-driven cycle made him moonwalk
|
||||
// whenever his speed and the frame rate disagreed. Now the stride owns the ground he covers.
|
||||
const stride = TUNE.boyStride * (mop ? 1 : 2.1); // a sprint eats more ground per frame than a push
|
||||
const img = SPR[(mop ? 'x_boymop' : 'x_boyrun') + (Math.floor((b.dist || 0) / stride) % 4 + 1)]; if (!img) return;
|
||||
const img = SPR[sheet + (Math.floor((b.dist || 0) / stride) % 4 + 1)]; if (!img) return;
|
||||
const p = proj(b.x, 0, b.z);
|
||||
// scale off the frame's OWN pixel height, not a fixed BOY_H: the sheets are common-scaled per
|
||||
// sheet, so a deep mopping lean is genuinely shorter and must not be stretched back upright
|
||||
@@ -379,6 +384,40 @@ function drawCard(title, sub1, sub2, color) {
|
||||
if (sub2) ctx.fillText(sub2, W / 2, H / 2 + 12);
|
||||
}
|
||||
|
||||
// Sit in the serve stance doing nothing and the gesture demonstrates itself: a ghost thumb slides
|
||||
// BACK from the player while chevrons grow FORWARD up the court, then it releases and repeats.
|
||||
// Pulling is the whole game in splatter mode, and a line of text was carrying that alone.
|
||||
function drawPullHint() {
|
||||
const cyc = 1.9, t = (G.idleHintT - TUNE.pullHintAfter) % cyc;
|
||||
const draw = Math.min(1, t / 1.15); // the pull itself, then a beat held at full
|
||||
const gone = t > 1.45; // released — the thumb lifts off
|
||||
const anchor = proj(P.x, 0, P.z + 0.6);
|
||||
const ax = Math.round(anchor.x), ay = Math.round(Math.min(anchor.y + 6, METER_Y() - 26));
|
||||
const len = 30 * draw;
|
||||
ctx.save();
|
||||
ctx.globalAlpha = gone ? 0 : 0.85;
|
||||
// the dotted track the thumb travels, straight down-screen = "pull back"
|
||||
ctx.fillStyle = UI.dim;
|
||||
for (let d = 6; d < 34; d += 6) ctx.fillRect(ax - 1, ay + d, 2, 2);
|
||||
// chevrons growing UP-court as the pull grows — the inverse relationship is the whole lesson
|
||||
ctx.fillStyle = UI.warm;
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const u = (i + 1) / 4;
|
||||
if (draw < u * 0.9) continue;
|
||||
const cy = ay - 8 - i * 7, s = 4;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(ax, cy - s); ctx.lineTo(ax + s, cy + s * 0.7); ctx.lineTo(ax - s, cy + s * 0.7);
|
||||
ctx.fill();
|
||||
}
|
||||
// the ghost thumb
|
||||
ctx.globalAlpha = gone ? 0 : 0.9;
|
||||
ctx.fillStyle = UI.cream;
|
||||
ctx.beginPath(); ctx.arc(ax, ay + len, 5, 0, 7); ctx.fill();
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.45)';
|
||||
ctx.beginPath(); ctx.arc(ax, ay + len, 2, 0, 7); ctx.fill();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
// The modal: drawn LAST, over everything, and the world behind it is frozen (gameplay.update
|
||||
// early-returns). Unlike drawCard this is a wall — nothing moves until it is tapped away.
|
||||
function drawModal(m) {
|
||||
@@ -455,7 +494,9 @@ export function render() {
|
||||
for (let x = 0; x < W; x += tw) ctx.drawImage(ads, x, standsBottom - adH, tw, adH);
|
||||
} else { ctx.fillStyle = pal.board; ctx.fillRect(0, standsBottom - adH, W, adH); }
|
||||
// the rot dims the stadium: one tint over crowd + boards, fading home during duels
|
||||
if (pt > 0) { ctx.fillStyle = `rgba(12,8,20,${0.5 * pt})`; ctx.fillRect(0, 0, W, standsBottom); }
|
||||
// the rot dims the stadium — but 0.5 on top of an already-dark crowd sheet buried the stands in
|
||||
// splatter mode, where you spend most of the game. 0.26 still reads as "the lights went wrong".
|
||||
if (pt > 0) { ctx.fillStyle = `rgba(12,8,20,${0.26 * pt})`; ctx.fillRect(0, 0, W, standsBottom); }
|
||||
|
||||
// ground + court (Super Tennis lawn); everything outside the doubles court is OUT
|
||||
const dw = COURT.dblW / 2;
|
||||
@@ -680,10 +721,12 @@ export function render() {
|
||||
? (G.weapon === 4 ? (touch ? 'TAP TO UNLOAD THE GUN' : 'SPACE UNLOADS THE GUN')
|
||||
: G.aim ? 'PULL BACK: FURTHER = HARDER'
|
||||
: G.weapon > 0 ? `SERVE FIRES: ${sel} · ${switchHint}`
|
||||
: touch ? 'TAP TO SERVE · PULL BACK TO AIM' : 'SPACE SERVES · ARROWS AIM')
|
||||
: touch ? 'PULL BACK TO AIM · RELEASE TO FIRE' : 'DRAG BACK TO AIM · SPACE SERVES')
|
||||
: G.serveNum === 2 ? (touch ? 'SECOND SERVE — TAP' : 'SECOND SERVE — SPACE')
|
||||
: (touch ? 'TAP TO TOSS, TAP TO SERVE' : 'SPACE TO TOSS, SPACE TO SERVE');
|
||||
ctx.fillText(txt, W / 2, METER_Y()); // same slot the draw meter uses
|
||||
// and after a beat of inaction, show the gesture instead of only naming it
|
||||
if (G.mode === 'splatter' && !G.interlude && G.weapon !== 4 && G.idleHintT > TUNE.pullHintAfter) drawPullHint();
|
||||
}
|
||||
if (G.flash) {
|
||||
ctx.font = F(12); ctx.fillStyle = '#ffffff';
|
||||
|
||||
Reference in new issue
Block a user