2ced93a425
The request was a mask's EXPOSURE losing hue, and the mask pass has not done that since6d60d45: measured today through a page the service worker controls, with an ellipse over the whole frame and +1 EV, the pixels inside move exactly as the frame's own knob moves them — identical to the byte (mask+1 vs frame+1 over four codes: 619 pixels of 1,709,450, all of them on the rim), and the hue each one leaves behind is the same distribution to a hundredth of a degree over the 370,606 pixels that carry a hue in both reads (mean 2.07°, p99 15.31° for the mask against 15.32° for the frame; on the vivid pixels, mean 0.83° at +1 EV). The one place that still says otherwise is a doc on the Android branch, whose §3.2 quotes the old line. But the symptom is real, and the build that produces it is the one from before that commit, where maskAdjust multiplied the three channels by the stop: c = c * half(pow(2.0, a.x)); Three channels clip by three different amounts, so the differences between them stop being scaled together and the hue goes with them. That bundle could still be what a visitor runs, because of two files the deploy never took away: - index.html was the only document the server handed over with no Cache-Control at all (the .mjs, /assets, /wasm and /models locations all name their policy, sw.js and the manifest both opted out). With no header the browser is free to guess a freshness window out of Last-Modified — a tenth of the file's age — and answer a navigation from its own cache for hours after a deploy. The page it answers with names the previous build's hashed bundle, so the previous shader is what runs, and a hard reload is the only way out. The SPA fallback lands on the same file (an internal redirect re-matches locations), so /app and /library were covered by the same guess. - sw.js is the second place the pin lived. Its navigations are network-first, but a plain fetch is not the network: it can be answered by the browser's cache, so the network never came first — and the old shell's hashed bundle, once fetched, is a STATIC path that the cache-first rule serves forever. So: nginx names the policy for the shell, with the isolation pair restated because an add_header in a location drops every inherited one and index.html is the document that needs them — the wasm renderer's SharedArrayBuffer is behind that pair. The worker reads its navigations past the browser's cache, precaches the shell the same way (a cache.add of '/' consults that cache like any other fetch, so a shell read inside a stale window would be stored as the offline shell of the build that replaced it), and VERSION goes to v2, whose activate drops the cache the old worker pinned — the old shell and the old bundle with it. The root fix is still6d60d45: this commit is what lets it reach the browser. Verified: nginx -t on the shipped config, and a container of this config against a copy of index.html hands / and /app `Cache-Control: no-cache` with all five original headers intact, while /assets/index-abc.js keeps `public, immutable` (30 days) — the exact location does not shadow the hashed bundle. node --check on sw.js. The measurements above come from the live 8090 build inside a persistent profile whose page is controlled by the worker (controlled true, crossOriginIsolated true, bundle index-CKOd57MG.js), the same session that pinned the build the fix is about. Co-authored-by: PenguinHarness <noreply@penguin.local>