The white flashing was the top fade: the live raisers were not repainting it
Oliver: "what is this white flashing while applause in the upper right corner of the crowd". With the photographers switched off entirely it was still there, so it was never them — it was mine, from this morning. The top fade is baked into the cached stand, and drawRaisers was painting a spectator's box straight over it with only the dim applied. Every time that spectator stood up his box lost the fade and lit up; every time he sat down it came back. Measured on the canvas: a 146-luminance jump, twice a second, in exactly the two places raisers sit near the top of the picture. Both stand treatments now come from one function each and are applied in both places. The gradient is built in user space, so filling a raiser's sub-rect with it picks up the right alpha for free. Probed again after the fix: zero bright pops during applause and during the wave, against the four hot spots before. While chasing it, two real detector faults turned up and are fixed too: a blob may not be a pale block (a re-rolled cream shirt is "new material" by the stand-turned-person test but nothing rises), and it may not cover something already bright (a genuine arm drawn over a white shirt deletes a white block, which flashes just as hard). 94 raisers survive both rules. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
93c4a21be5
commit
ca3e72d0a9
8 files changed
+81
-47
No files matched your search
+19
-10
@@ -235,7 +235,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 = 147;
|
||||
export const SPRV = 148;
|
||||
// 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".
|
||||
@@ -443,11 +443,25 @@ const standDim = pt => Math.min(0.88, (court.dim ?? TUNE.standBack) + 0.26 * pt)
|
||||
// looks pretty artificial").
|
||||
// The sub-rect is drawn at EXACTLY the scale paintBackdrop used for the whole sheet (kx/ky below),
|
||||
// which is what guarantees the raised arms land on the same pixel grid as the body under them.
|
||||
// THE TOP FADE, in ONE place. It is baked into the cached stand, and the live raisers have to paint
|
||||
// it over themselves as well — a spectator whose box was redrawn without it lit up as a bright
|
||||
// rectangle every time he stood, which is what Oliver saw: "what is this white flashing while
|
||||
// applause in the upper right corner of the crowd". Same class of bug as two copies of standDim.
|
||||
// A gradient in user space, so filling ANY sub-rect with it picks up the right alpha for free.
|
||||
function topFade(c, standsBottom) {
|
||||
if (!(TUNE.standTop > 0)) return null;
|
||||
const g = c.createLinearGradient(0, -8, 0, standsBottom * 0.42);
|
||||
g.addColorStop(0, `rgba(6,10,6,${TUNE.standTop})`);
|
||||
g.addColorStop(1, 'rgba(6,10,6,0)');
|
||||
return g;
|
||||
}
|
||||
|
||||
const rnd = i => ((i * 9301 + 49297) % 233280) / 233280; // one stable pseudo-random per spectator
|
||||
function drawRaisers(img, standsBottom, pt, frameOf) {
|
||||
const fh = img.height / FRAMES, crowdH = standsBottom - AD_H + 2;
|
||||
const kx = W / img.width, ky = (crowdH + 2) / fh;
|
||||
const d = standDim(pt), tint = d > 0 ? `rgba(12,8,20,${d})` : null;
|
||||
const fade = topFade(ctx, standsBottom);
|
||||
let open = false;
|
||||
for (let i = 0; i < RAISERS.length; i++) {
|
||||
const r = RAISERS[i], f = frameOf(i, r[0], r[4]);
|
||||
@@ -458,8 +472,9 @@ function drawRaisers(img, standsBottom, pt, frameOf) {
|
||||
}
|
||||
const sx = r[0] * img.width, sy = r[1] * fh, sw = r[2] * img.width, sh = r[3] * fh;
|
||||
ctx.drawImage(img, sx, sy + f * fh, sw, sh, sx * kx, sy * ky, sw * kx + 0.5, sh * ky + 0.5);
|
||||
// re-tinted like the cached stand, so a raised pair of arms cannot pop brighter than the rot
|
||||
// re-tinted AND re-faded exactly like the cached stand underneath, or the box lights up
|
||||
if (tint) { ctx.fillStyle = tint; ctx.fillRect(sx * kx, sy * ky, sw * kx + 0.5, sh * ky + 0.5); }
|
||||
if (fade) { ctx.fillStyle = fade; ctx.fillRect(sx * kx, sy * ky, sw * kx + 0.5, sh * ky + 0.5); }
|
||||
}
|
||||
if (open) ctx.restore();
|
||||
}
|
||||
@@ -514,14 +529,8 @@ function paintBackdrop(c, pal, pt) {
|
||||
// function): the stand is the far edge of the frame, and a hard cut at y = 0 reads as a
|
||||
// photograph that has been cropped rather than as a stadium with a roof somewhere above it.
|
||||
// It also happens to be where the scoreboard sits, which is the second reason it earns its keep.
|
||||
if (TUNE.standTop > 0) {
|
||||
const deep = standsBottom * 0.42;
|
||||
const g = c.createLinearGradient(0, -8, 0, deep);
|
||||
g.addColorStop(0, `rgba(6,10,6,${TUNE.standTop})`);
|
||||
g.addColorStop(1, 'rgba(6,10,6,0)');
|
||||
c.fillStyle = g;
|
||||
c.fillRect(-8, -8, W + 16, deep + 8);
|
||||
}
|
||||
const fade = topFade(c, standsBottom);
|
||||
if (fade) { c.fillStyle = fade; c.fillRect(-8, -8, W + 16, standsBottom * 0.42 + 8); }
|
||||
// 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
|
||||
|
||||
Reference in new issue
Block a user