Stadium pass: real crowd, sponsor boards, umpire visible in portrait
- Umpire chair moved courtside (UMP_X 5.6, was 7.1 — which projected to x=272 on a 270px-wide portrait canvas: literally one pixel off-screen). Tap-cheat coords follow the constant. - Generated pixel-art night crowd (x_crowd.png) replaces the tier stripes + random dots; drawn in 16 columns that bounce independently while the crowd cheers. Old procedural crowd kept as the loading fallback. - Generated courtside sponsor boards (x_ads.png): ACEADE, TOPSPIN & CO, VOLLEX, NETTO GRIP, SLAMWICH — tiled at native aspect so lettering is never half-cut at any viewport width. Replaces the 'SPLATTER OPEN' text. - One rot tint over crowd + boards driven by the shared paletteT() (new helper), so the stadium dims into splatter and brightens back for duels. Verified: fresh origin; tennis + splatter screenshots (boards tile cleanly, crowd reads, tint dims, umpire on-screen beside the court); umpire triple-tap works at the new chair position; tennis serve benchmark 33/36 across two runs (first 12 was flub noise); no console errors. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e948e8b36e
commit
1b97905217
5 files changed
+37
-23
No files matched your search
@@ -1,7 +1,7 @@
|
||||
// Camera, viewport, sprites and all drawing. Owns the crowd dots (visual-only state).
|
||||
|
||||
import { cv, ctx } from './dom.js';
|
||||
import { TUNE, COURT, PLAYER_H, BOY_H, UMP_H, BOUNCE_IDLE, SPRITE_H } from './config.js';
|
||||
import { TUNE, COURT, PLAYER_H, BOY_H, UMP_H, UMP_X, BOUNCE_IDLE, SPRITE_H } from './config.js';
|
||||
import { P, O, ball, score, crowd, G } from './state.js';
|
||||
import { predictLanding, flickTarget } from './gameplay.js';
|
||||
import { zombies, gore, piles, parts, pellets, extras } from './zombies.js';
|
||||
@@ -19,9 +19,12 @@ const hexLerp = (a, b, t) => {
|
||||
const [ar, ag, ab] = c(a), [br, bg, bb] = c(b);
|
||||
return `rgb(${Math.round(ar + (br - ar) * t)},${Math.round(ag + (bg - ag) * t)},${Math.round(ab + (bb - ab) * t)})`;
|
||||
};
|
||||
// how far into the rot we are, 0 (tennis) .. 1 (splatter) — duels lerp it back home
|
||||
function paletteT() {
|
||||
return G.mode === 'tennis' ? 0 : G.mode === 'splatter' ? 1 - G.duelLerp : Math.min(1, G.transT / 6);
|
||||
}
|
||||
function palette() {
|
||||
// splatter lerps back toward the tennis palette while a duel runs — order, restored
|
||||
const t = G.mode === 'tennis' ? 0 : G.mode === 'splatter' ? 1 - G.duelLerp : Math.min(1, G.transT / 6);
|
||||
const t = paletteT();
|
||||
if (t === 0) return PAL.tennis;
|
||||
if (t === 1) return PAL.splatter;
|
||||
const m = {};
|
||||
@@ -61,7 +64,7 @@ export function proj(x, y, z) {
|
||||
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_ump','x_boyrun1','x_boyrun2','x_boyrun3','x_boyrun4','x_crowd','x_ads',
|
||||
'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_s1','z_s2','z_s3']) {
|
||||
const img = new Image();
|
||||
@@ -254,24 +257,34 @@ export function render() {
|
||||
ctx.translate((Math.random() * 2 - 1) * m, (Math.random() * 2 - 1) * m);
|
||||
}
|
||||
|
||||
const pal = palette();
|
||||
// stands: tiered crowd from top of screen down to just above the far court
|
||||
const pal = palette(), pt = paletteT();
|
||||
// stands: the generated crowd, drawn in columns so it can BOUNCE when it cheers
|
||||
const standsBottom = proj(0, 0, 0).y - 12;
|
||||
ctx.fillStyle = pal.stands; ctx.fillRect(0, 0, W, standsBottom);
|
||||
for (let r = 0; r < 16; r++) {
|
||||
ctx.fillStyle = r % 2 ? pal.tierA : pal.tierB;
|
||||
ctx.fillRect(0, 6 + r * 5.5, W, 5.5);
|
||||
const adH = 14, crowdH = standsBottom - adH + 2;
|
||||
const crowdImg = SPR['x_crowd'];
|
||||
if (crowdImg) {
|
||||
const cols = 16, sw = crowdImg.width / cols, dw = W / cols;
|
||||
for (let c = 0; c < cols; c++) {
|
||||
const dy = crowd.cheer > 0 ? -Math.random() * 2 : 0;
|
||||
ctx.drawImage(crowdImg, c * sw, 0, sw, crowdImg.height, c * dw, dy, dw + 0.5, crowdH + 2);
|
||||
}
|
||||
} else { // fallback until the image loads: the old tier stripes + dots
|
||||
ctx.fillStyle = pal.stands; ctx.fillRect(0, 0, W, standsBottom);
|
||||
for (let r = 0; r < 16; r++) { ctx.fillStyle = r % 2 ? pal.tierA : pal.tierB; ctx.fillRect(0, 6 + r * 5.5, W, 5.5); }
|
||||
for (const d of crowd.dots) {
|
||||
const jx = crowd.cheer > 0 ? (Math.random() * 2 - 1) : 0;
|
||||
const jy = crowd.cheer > 0 ? -Math.random() * 1.5 : 0;
|
||||
ctx.fillStyle = d.c; ctx.fillRect(d.x + jx | 0, d.y + jy | 0, 2, 2);
|
||||
}
|
||||
}
|
||||
for (const d of crowd.dots) {
|
||||
const jx = crowd.cheer > 0 ? (Math.random() * 2 - 1) : 0;
|
||||
const jy = crowd.cheer > 0 ? -Math.random() * 1.5 : 0;
|
||||
ctx.fillStyle = d.c; ctx.fillRect(d.x + jx | 0, d.y + jy | 0, 2, 2);
|
||||
}
|
||||
// courtside billboard wall
|
||||
ctx.fillStyle = pal.board; ctx.fillRect(0, standsBottom - 10, W, 10);
|
||||
ctx.fillStyle = '#dfe8d0'; ctx.font = 'bold 7px monospace'; ctx.textBaseline = 'top'; ctx.textAlign = 'center';
|
||||
ctx.fillText(W > 400 ? '· SPLATTER OPEN · NEW BALLS PLEASE · SPLATTER OPEN ·' : '· SPLATTER OPEN ·', W / 2, standsBottom - 9);
|
||||
ctx.textAlign = 'left';
|
||||
// courtside advertising: tiled sponsor panels at native aspect (never half-cut text)
|
||||
const ads = SPR['x_ads'];
|
||||
if (ads) {
|
||||
const tw = ads.width * (adH / ads.height);
|
||||
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); }
|
||||
|
||||
// ground + court (Super Tennis lawn); everything outside the doubles court is OUT
|
||||
const dw = COURT.dblW / 2;
|
||||
@@ -314,7 +327,7 @@ export function render() {
|
||||
|
||||
// far-side drawables in z order (far → near), then the net, then the near side
|
||||
const farSide = [
|
||||
{ z: COURT.netZ - 0.01, f: () => drawStatic('x_ump', 7.1, COURT.netZ, UMP_H) },
|
||||
{ z: COURT.netZ - 0.01, f: () => drawStatic('x_ump', UMP_X, COURT.netZ, UMP_H) },
|
||||
];
|
||||
if (!G.hideO) farSide.push({ z: O.z, f: () => drawChar(O) });
|
||||
if (G.boy && G.boy.z <= COURT.netZ) farSide.push({ z: G.boy.z, f: () => drawBoy(G.boy) });
|
||||
|
||||
Reference in new issue
Block a user