# Code: Starlock's glow banding

Python 3 with numpy for everything the entry's numbers come from; Node 22 and Playwright's
Chromium only for the parts that drive real WebGL (marked "browser" below). Run each from this folder.

| file | what it does | how to run |
|---|---|---|
| `pipeline.py` | A numpy port of the cutscene's glow path: glow shaders, the 8-bit linear target with additive blending, the painterly pass (generalised Kuwahara, taps a pixel ratio apart), three.js's ACES filmic and sRGB. | imported |
| `dithers.py` | The noise fields: the sin hash (with sin correctly rounded, `M1`, or reduced from the fraction of a turn in float32, `M2`), interleaved gradient noise, a blue-noise tile, Roberts' R2, white noise, the triangular remap. | imported |
| `bluenoise.py` | A 64×64 blue-noise tile by Ulichney's void-and-cluster method. | imported |
| `experiment.py` | The scenes, the candidates and the metrics; the full grid. | `python3 experiment.py` (about 4 minutes on 4 cores; writes `results.json`), `--summary` for the entry's table |
| `floor.py` | Predicts the error left with the painterly pass off from the tone curve alone. | `python3 floor.py` |
| `mediump.py` | The hashes evaluated in IEEE half precision. | `python3 mediump.py` |
| `poolsteps.py` | Counts contour pixels in the two crops of the real cutscene in `frames/`. | `python3 poolsteps.py` |
| `independent/` | The fresh check's own copy of the path (`vpipe.py`, from the shaders) and its measures (`banding.py`: a ring profile and a contrast-sensitivity-weighted error), written without our reasoning. | `cd independent && python3 banding.py` (`out/independent.txt`; about a minute) |
| `webgl.mjs.txt` | Browser. Renders glow quads with the game's own three.js and painterly pass in headless Chromium (SwiftShader) and saves the raw pixels. | `node --input-type=module - spec.json out < webgl.mjs.txt` |
| `validate.py` | Browser. The port against real WebGL: the target and the screen, pixel for pixel. | `python3 validate.py` (`out/validate.txt`) |
| `probe.py` | Browser. Reads a hash's noise field back from WebGL at 16 bits a pixel. | `python3 probe.py sin 1290 2796 out.npy` |
| `beforeafter.py` | Browser. The glow and ring shaders as written in two checkouts of the game, drawn in WebGL and measured. | `python3 beforeafter.py <starlock-before> <starlock-after>` (`out/beforeafter.txt`) |
| `cutframe.mjs.txt` | Browser. One beat of the real cutscene (`.tests/cutscene-demo.html`), from a checkout's root. | `node --input-type=module - rift out.png 390 844 1 < …/cutframe.mjs.txt` |
| `film.mjs.txt` | Browser. A stretch of the real cutscene filmed a frame at a time (each frame drawn at its own moment, saved as a lossless PNG), from a checkout's root: the footage's before and after are two checkouts. | `node --input-type=module - <out-dir> 7600 10800 30 1 < …/film.mjs.txt` |
| `footage.py` | The page's footage: two folders of frames side by side, encoded once (VP9), and checked against the lossless frames: the rings kept, the contour pixels added. Needs ffmpeg and Pillow. | `python3 footage.py <before> <after> footage-rift.webm footage-rift.jpg 4 424 844` (`out/footage.txt`) |
| `footage-browser.mjs.txt` | Browser. The footage as Chromium shows it: played at a tenth of the speed, six screenshots, for `python3 footage.py browser <before> <after> <dir> 424 844` (`out/footage-browser.txt`). | `node --input-type=module - footage-rift.webm <dir> < …/footage-browser.mjs.txt` |

The `.mjs.txt` files are JavaScript modules named `.txt` because the site serves this folder as
text; piping one into `node --input-type=module -` runs it as is. Point Playwright at its
install with `PLAYWRIGHT=/path/to/node_modules/playwright` if `require('playwright')` doesn't find it.

`frames/` holds the two crops of the rift beat (390 × 844 at a pixel ratio of 1): `pool-before.png`
from the default branch, `pool-after.png` with the change. `out/` holds the expected output of
each check.

Floating-point note: the sin hash is chaotic in the last bit of its argument, so no two GPUs (or
numpy and a GPU) agree on its pixels; compare its statistics, never its pixels. Everything else here
is deterministic.
