diff --git a/assets/_crowdpass.py b/assets/_crowdpass.py index 97833ae..a746b25 100644 --- a/assets/_crowdpass.py +++ b/assets/_crowdpass.py @@ -55,7 +55,12 @@ PAL = 128 # colours a sheet is quantised to on save # HEAD, so re-running the tool after installing its own output cannot drift the palettes. GRADE_REF = '76ea1d0' -COURTS = [('x_crowd', 'x_crowd2'), ('x_crowd_c', 'x_crowd_c2'), ('x_crowd_h', 'x_crowd_h2')] +# sheet -> how much its grade TARGET is lifted before the histogram match (a gamma; 1.0 is the old +# sheet as it was). Centre court's old crowd sat at a mean luminance of 39 against clay's 96 — dark +# enough that the detail in the new roll was being thrown away, and then dimmed on top of that: +# Oliver, "it is too dim to see anything on the centre court and the dark court". The night court +# borrows the hard-court sheet and does its own dimming in `COURTS.dim`, so it is fixed there. +COURTS = [('x_crowd', 0.72), ('x_crowd_c', 1.0), ('x_crowd_h', 1.0)] MIN_AREA, MAX_AREA = 150, 3000 # an arm blob: one pair of raised arms, or one arm ARM_H, ARM_W = (10, 70), (8, 90) # ...and its shape, in px of the 1408x768 sheet @@ -199,8 +204,11 @@ def main(review): print(f'{len(rows)} spectators can move, {both} of them with both poses (a real clap)') stack = np.concatenate(frames, 0) - for a_name, _ in COURTS: - lut = grade(A, old_sheet(a_name)) # the LOOK is judged on the seated frame... + for a_name, lift in COURTS: + ref = old_sheet(a_name) + if lift != 1.0: + ref = (255 * (ref / 255.0) ** lift).astype(np.uint8) + lut = grade(A, ref) # the LOOK is judged on the seated frame... pa = os.path.join(REFCHECK if review else HERE, a_name + ('_new.png' if review else '.png')) # ...and applied to the whole stack at once, so the frames cannot drift apart in colour Image.fromarray(lut(stack)).quantize(colors=PAL, method=Image.MEDIANCUT).save(pa, optimize=True) diff --git a/assets/x_crowd.png b/assets/x_crowd.png index f3bddce..4525db7 100644 Binary files a/assets/x_crowd.png and b/assets/x_crowd.png differ diff --git a/index.html b/index.html index b299884..741a4c9 100644 --- a/index.html +++ b/index.html @@ -661,6 +661,6 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat - + diff --git a/src/config.js b/src/config.js index 5016283..f2c0750 100644 --- a/src/config.js +++ b/src/config.js @@ -297,10 +297,13 @@ export const TUNE = { // cheered (render's drawClapColumns). Between points the crowd used to be a photograph. This is // deliberately slow and single-column: a stand that fidgets constantly is a stand you look AT. fidgetEvery: 4.5, - // HOW FAR BACK THE STAND SITS. A flat dim over the crowd and the boards, on every court, before - // the night court's own `dim` and the splatter rot are added on top. Oliver: "all of the art could - // be colored a little more dim as it is perspective wise farther in the background and should not - // steal too much attention." The crowd is the only thing on screen that is 30 m away. + // HOW FAR BACK THE STAND SITS: a flat dim over the crowd and the boards, before the splatter rot + // is added on top. Oliver: "all of the art could be colored a little more dim as it is perspective + // wise farther in the background and should not steal too much attention." The crowd is the only + // thing on screen that is 30 m away. + // THIS IS THE DEFAULT, NOT A FLOOR. A court with its own `dim` (centre court's already-dark crowd, + // the night court) replaces it rather than adding to it — stacking the two is what made those two + // unreadable: "it is too dim to see anything on the centre court and the dark court". standBack: 0.22, // ---- damage. Bodies with hp (racket boss 'r', brute 'g') take damage in these units, so a big // boss is just a bigger hp number and every weapon stays proportional. Fractions are legal: they @@ -694,7 +697,9 @@ export const UI = { hot: '#d9705f', warm: '#e0cc72', pick: '#cbd06a', cream: '#e // sits up and drags, acrylic is the middle it always was. export const COURTS = [ { id: 'grass', name: 'CENTRE COURT', sub: 'GRASS · fast, low skid', tile: 'court_grass', - ads: 'x_ads', crowd: 'x_crowd', stripes: true, line: '#e8e8dc', border: '#e8e8dc', + // dim 0.06: centre court's crowd is a night-lit navy sheet and is dark to begin with, so it + // takes almost none of the standBack default (see TUNE.standBack). + ads: 'x_ads', crowd: 'x_crowd', stripes: true, line: '#e8e8dc', border: '#e8e8dc', dim: 0.06, bounceY: 0.50, bounceXZ: 0.84, tennis: { stands: '#232e42', tierA: '#2a3750', tierB: '#26324a', board: '#0e5c2e', out: '#295c2d', lawn: '#4cae52', stripe: '#46a54c' }, @@ -726,7 +731,7 @@ export const COURTS = [ // court and the whole picture stops being a night. Nothing else in COURTS carries it, so it is 0 // everywhere else and reads as absent. { id: 'gloom', name: 'THE NIGHT COURT', sub: 'AFTER DARK · TRUE BOUNCE, NO LIGHT', tile: 'court_gloom', - ads: 'x_ads_h', crowd: 'x_crowd_h', stripes: false, line: '#dff6ff', border: '#c8dbe6', dim: 0.62, + ads: 'x_ads_h', crowd: 'x_crowd_h', stripes: false, line: '#dff6ff', border: '#c8dbe6', dim: 0.5, bounceY: 0.58, bounceXZ: 0.80, tennis: { stands: '#0a0d15', tierA: '#0f131e', tierB: '#0c1019', board: '#131a2b', out: '#0f1a15', lawn: '#16261e', stripe: '#16261e' }, diff --git a/src/render.js b/src/render.js index bce94c2..ee781d8 100644 --- a/src/render.js +++ b/src/render.js @@ -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 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 = 145; +export const SPRV = 146; // 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". @@ -420,14 +420,15 @@ function tickFlashes(standsBottom) { ctx.globalAlpha = 1; } -// How dark the STAND is: `TUNE.standBack` (every court, always — the stand is scenery a long way -// behind the court and should not compete with it) plus 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 +// How dark the STAND is: the court's own `dim` if it names one (centre court's crowd sheet is +// already night-dark, the night court is darker still), otherwise `TUNE.standBack` — the stand is +// scenery thirty metres back and should not compete with the court — plus the rot the splatter +// palette brings with it. ?? not ||: a court that says 0 means 0. 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.88, TUNE.standBack + (court.dim || 0) + 0.26 * pt); +const standDim = pt => Math.min(0.88, (court.dim ?? TUNE.standBack) + 0.26 * pt); // THE STAND MOVES IN COLUMNS, and the sheets are cut to match: `assets/_crowdpass.py` guarantees // the clap frame is pixel-identical to the seated one EXCEPT inside blobs that each sit inside ONE