The dead club doubles its staff, and the draw is made where you can see it
Six new opponent families (head coach, stringer, strawberry seller, chair umpire, spectator, roller man) through the famroll/zsheet route, placed by hierarchy into an 18-rung ladder; the champion steps up behind them. THE DRAW: picking the tournament now rolls its own bracket in front of you — only the final was ever settled — and what lands is what walks on. Netplay, off Oliver's cellular report: the mid-flight early-decide was missing its !G.net gate, so the striker judged its own outbound flight and the two scoreboards drifted a point apart into a court nobody could serve on. The host's court now rides the room, its scoreboard rides the pretoss stream, and a called ball rolls to its own halt instead of freezing where the call caught it. Plus the night court, and somebody in the stand is taking pictures. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4c47295a73
commit
9cc3ac54b3
207 files changed
+632
-81
No files matched your search
+53
-6
@@ -221,7 +221,7 @@ function farB(z) {
|
||||
// Asset cache-buster — bump with main.js?v=N (see the img.src note below). EXPORTED because the
|
||||
// menu screens hang <img> tags on the same files, and two copies of this number is one copy too
|
||||
// many: the one that does not get bumped is the one that serves a cached 404.
|
||||
export const SPRV = 125;
|
||||
export const SPRV = 129;
|
||||
// The boot bar in main.js watches this. Nothing here CHANGES when things load — every sprite was
|
||||
// already requested at import — it just makes the wait visible and holds the menu back until the
|
||||
// last sheet has landed. See the onerror note below for the other half of "sometimes missing".
|
||||
@@ -273,9 +273,10 @@ for (const n of ['p_idle1','p_idle2','p_bounce','p_bounce2','p_toss','p_smash','
|
||||
// filenames typed out here (each of which is a chance to typo one and have a body
|
||||
// vanish for a frame — the oldest bug in this file).
|
||||
...ZFAMS.flatMap(famFrames),
|
||||
// the card's 4K-cut head icons (assets/_heads.py). 'r' has no fam sheet — his
|
||||
// card keeps the HEAD_CROP fallback, so no file and no 404 for him.
|
||||
...ZFAMS.filter(k => k !== 'r').map(k => 'z_' + k + 'head'),
|
||||
// the card's head icons (assets/_heads.py), 'r' included since 2026-08-15: the
|
||||
// club pro's is cut from z_c1, the turned opponent, who IS him. The HEAD_CROP
|
||||
// fallback below him stays as the net for a family that has no head file yet.
|
||||
...ZFAMS.map(k => 'z_' + k + 'head'),
|
||||
'w_ball','w_bomb','w_triple','w_fire','w_gun', // weapon-rack icons
|
||||
'w_beacon','w_laser','w_rocket','w_flamer', // ...and the four that come out of a barrel
|
||||
'z_barrel1','z_barrel2', // the weapon barrel, two frames so it ROLLS
|
||||
@@ -348,6 +349,50 @@ function buildCrowd() {
|
||||
});
|
||||
}
|
||||
|
||||
// CAMERA FLASHES IN THE STAND (Oliver, 2026-08-15: "random sporadic subtle flashes like someone is
|
||||
// photographing, slightly increasing during applause"). AN OVERLAY, never backdrop content: the
|
||||
// backdrop is one cached blit and anything that moves in it would repaint the whole scene every
|
||||
// frame (see drawBackdrop's key). Clocked off G.idleT rather than dt for the same reason the clap
|
||||
// ripple is — this function has no dt, and idleT is the one clock that never stops.
|
||||
// Each flash is a two-frame pop: a hot core, a soft bloom, gone in ~TUNE.flashLife. Rate doubles
|
||||
// while the crowd is on its feet, which is the "slightly increase during applause" — a stand that
|
||||
// went white during a point would be a different, much louder feature.
|
||||
const flashes = []; // {x, y, r, t0}
|
||||
let flashNext = 0;
|
||||
function tickFlashes(standsBottom) {
|
||||
const now = G.idleT, top = Math.max(4, standsBottom - AD_H - 4);
|
||||
if (now >= flashNext) {
|
||||
// the gap is exponential-ish so they never look metronomic; cheer halves the mean wait
|
||||
const mean = TUNE.flashEvery / (crowd.cheer > 0 ? 2.2 : 1);
|
||||
flashNext = now + mean * (0.35 + Math.random() * 1.6);
|
||||
flashes.push({ x: 6 + Math.random() * (W - 12), y: 6 + Math.random() * (top - 6),
|
||||
// small. The first cut was r 2.2-4.2 and its bloom drew a 6 px disc that read as
|
||||
// a LAMP in the stand rather than a shutter going off half a second ago
|
||||
r: 0.9 + Math.random() * 1.1, t0: now });
|
||||
if (flashes.length > 12) flashes.shift();
|
||||
}
|
||||
for (let i = flashes.length - 1; i >= 0; i--) {
|
||||
const f = flashes[i], a = 1 - (now - f.t0) / TUNE.flashLife;
|
||||
if (a <= 0) { flashes.splice(i, 1); continue; }
|
||||
ctx.save();
|
||||
ctx.globalCompositeOperation = 'lighter';
|
||||
ctx.globalAlpha = a * a * 0.30; ctx.fillStyle = '#8fa6d8'; // the bloom, cold and dim
|
||||
ctx.beginPath(); ctx.arc(f.x, f.y, f.r * 2.4, 0, 7); ctx.fill();
|
||||
ctx.globalAlpha = Math.min(1, a * 1.5); ctx.fillStyle = '#fdfdff'; // the bulb itself
|
||||
ctx.beginPath(); ctx.arc(f.x, f.y, f.r * 0.8, 0, 7); ctx.fill();
|
||||
ctx.restore();
|
||||
}
|
||||
ctx.globalAlpha = 1;
|
||||
}
|
||||
|
||||
// How dark the STAND is: the court's own night (`dim` on its COURTS row — 0 everywhere but the
|
||||
// night court) plus the rot the splatter palette brings with it. ONE function because it is applied
|
||||
// in two places and they must agree exactly — the cached sheet and the live clap columns drawn over
|
||||
// it. Two copies of this number is a stand whose clapping columns pop brighter than the rest of it,
|
||||
// which is the same class of bug as the crowd frames drifting apart. Capped: past ~0.85 the stand
|
||||
// is a black rectangle and the applause stops reading at all.
|
||||
const standDim = pt => Math.min(0.85, (court.dim || 0) + 0.26 * pt);
|
||||
|
||||
// ---------------------------------------------------------------- backdrop ----
|
||||
// THE STATIC SCENE, PRE-COMPOSED. Stands, ads, rot tint, out area, court, lines and the run-off
|
||||
// apron are pixel-identical frame to frame except while a palette lerps (the 6 s transition, the
|
||||
@@ -389,7 +434,8 @@ function paintBackdrop(c, pal, pt) {
|
||||
// the rot dims the stadium: one tint over crowd + boards, fading home during duels. 0.26, not
|
||||
// 0.5: on an already-dark crowd sheet 0.5 buried the stands; 0.26 still reads as "the lights
|
||||
// went wrong".
|
||||
if (pt > 0) { c.fillStyle = `rgba(12,8,20,${0.26 * pt})`; c.fillRect(0, 0, W, standsBottom); }
|
||||
const dim = standDim(pt);
|
||||
if (dim > 0) { c.fillStyle = `rgba(12,8,20,${dim})`; c.fillRect(0, 0, W, standsBottom); }
|
||||
// ground + court (Super Tennis lawn); everything outside the doubles court is OUT
|
||||
const dw = COURT.dblW / 2;
|
||||
quadXZ(-14, -6, 14, 30, pal.out); // out area. NEVER let z approach
|
||||
@@ -460,7 +506,7 @@ function drawBackdrop(pal, pt) {
|
||||
if (crowd.cheer > 0 && crowdImg && clapImg) {
|
||||
const crowdH = standsBottom - AD_H + 2;
|
||||
const cols = 16, sw = clapImg.width / cols, cw = W / cols;
|
||||
const tint = pt > 0 ? `rgba(12,8,20,${0.26 * pt})` : null;
|
||||
const d = standDim(pt), tint = d > 0 ? `rgba(12,8,20,${d})` : null;
|
||||
ctx.save(); ctx.beginPath(); ctx.rect(0, 0, W, standsBottom - AD_H); ctx.clip();
|
||||
for (let i = 0; i < cols; i++) {
|
||||
if (!(Math.floor(G.idleT * 9 + i * 0.7) % 2)) continue;
|
||||
@@ -469,6 +515,7 @@ function drawBackdrop(pal, pt) {
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
tickFlashes(standsBottom); // ...and somebody in there is taking pictures
|
||||
return standsBottom;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user