Fix the black screen: CRT shader did not compile, plus a test suite that can actually see it #1

Open
kevinhermes wants to merge 3 commits from fix/crt-shader-and-real-tests into main
pull from: fix/crt-shader-and-real-tests
Owner

What was wrong

The 3D canvas rendered pure black — one colour, 0 of 64,000 pixels lit. Cause is js/retro.js:29:

gl_Position = vec4(position, 0.0, 1.0);

In three.js position is a vec3, so this is vec4(vec3, float, float) — five components into a four-component vector. Invalid GLSL ES on both WebGL 1 and WebGL 2:

THREE.WebGLProgram: Shader Error 0 - VALIDATE_STATUS false
Program Info Log: Vertex shader is not compiled.
ERROR: 0:71: 'constructor' : too many arguments

The whole CRT post-processing pass failed, so nothing drew. This is not a driver quirk: glslangValidator (Khronos' reference compiler) rejects the same line with the same message, and accepts the fix.

The fix

One line: vec4(position.xy, 0.0, 1.0). position.xy is the vec2 a fullscreen quad actually needs.

Why the old tests missed it

npm test was scripts/check-syntax.js, which checks each file is >10 characters and that { and } counts match. Braces balanced perfectly. It passed on a build that could not draw anything.

What this branch adds

  • tests/shaders.test.js — compiles the game's real shaders with glslangValidator under a three.js-style attribute prefix. Includes a canary asserting a known-bad vec4 construction is rejected, so the suite cannot pass vacuously.
  • tests/terrain.test.js — heightfield determinism (the "persistent world" claim), airport flattening inside the 200 m radius with no leakage outside it, out-of-bounds behaviour, plausible height range, vertex colour counts.
  • scripts/headless-check.mjs (npm run test:render) — serves dist/, drives the game over raw CDP with SwiftShader software WebGL, and fails if the canvas is black, the shaders fail to compile, or the scene does not change after input. No new dependencies (Node 22 built-in fetch/WebSocket), 120 s watchdog so it cannot hang a runner.
  • npm test → node --test "tests/*.test.js". The old brace counter survives as npm run lint:syntax.
  • CI installs glslang-tools and runs the real tests. The render job is continue-on-error because not every runner has a Chrome binary.

Evidence

With the fix (fresh clone of this branch, npm ci → build → tests):

Gate Result
npm test 10/10 pass, 0 skipped
npm run build clean, 565 ms
npm run lint:syntax pass
npm run test:render PASSED — 55–143 distinct colours, 63,750/64,000 lit pixels, no shader errors, scene hash changes after throttle + pitch

With the shader deliberately re-broken (proving the tests are not decorative):

  • npm test → fails 2/4 with glslang's own 'constructor' : too many arguments
  • npm run test:render → fails four ways: 1 distinct colour, 0/64,000 lit pixels, shader compile error, scene did not change after input

Honest caveats

  1. Rendering was verified in software only. This box has no GPU (no /dev/dri, no /dev/nvidia*), so all the pixel evidence above is SwiftShader CPU rasterisation. It proves the shaders compile and geometry draws. It does not prove the CRT effect looks right — that needs your eyes.
  2. CI has never validated this repo. This instance has no registered Actions runners (/actions/runners → total_count: 0), and upstream's only run (id 36) ended cancelled, not success. Do not read a missing CI tick as a pass.
  3. The flight model is still broken (TODO T1): full throttle gives a brief hop to ~105 ft, then the aircraft stays grounded and accelerates to 2,543 knots. Deliberately left out of this PR so a verified one-line fix isn't tangled with judgement-heavy physics. That's the next branch.

How to test it

git clone -b fix/crt-shader-and-real-tests https://repos.retroweb.dev/kevinhermes/retro-flight-sim.git
cd retro-flight-sim
npm ci
npm test
npm run build && npm run preview   # http://localhost:4173

You should see the terrain, runway and aircraft instead of a black rectangle. Takeoff will still not work — that's T1, not this PR.

## What was wrong The 3D canvas rendered **pure black** — one colour, 0 of 64,000 pixels lit. Cause is `js/retro.js:29`: ```glsl gl_Position = vec4(position, 0.0, 1.0); ``` In three.js `position` is a **vec3**, so this is `vec4(vec3, float, float)` — five components into a four-component vector. Invalid GLSL ES on both WebGL 1 and WebGL 2: ``` THREE.WebGLProgram: Shader Error 0 - VALIDATE_STATUS false Program Info Log: Vertex shader is not compiled. ERROR: 0:71: 'constructor' : too many arguments ``` The whole CRT post-processing pass failed, so nothing drew. This is not a driver quirk: `glslangValidator` (Khronos' reference compiler) rejects the same line with the same message, and accepts the fix. ## The fix One line: `vec4(position.xy, 0.0, 1.0)`. `position.xy` is the vec2 a fullscreen quad actually needs. ## Why the old tests missed it `npm test` was `scripts/check-syntax.js`, which checks each file is >10 characters and that `{` and `}` counts match. Braces balanced perfectly. It passed on a build that could not draw anything. ## What this branch adds - **`tests/shaders.test.js`** — compiles the game's *real* shaders with `glslangValidator` under a three.js-style attribute prefix. Includes a canary asserting a known-bad `vec4` construction is **rejected**, so the suite cannot pass vacuously. - **`tests/terrain.test.js`** — heightfield determinism (the "persistent world" claim), airport flattening inside the 200 m radius with no leakage outside it, out-of-bounds behaviour, plausible height range, vertex colour counts. - **`scripts/headless-check.mjs`** (`npm run test:render`) — serves `dist/`, drives the game over raw CDP with SwiftShader software WebGL, and fails if the canvas is black, the shaders fail to compile, or the scene does not change after input. No new dependencies (Node 22 built-in `fetch`/`WebSocket`), 120 s watchdog so it cannot hang a runner. - `npm test` → `node --test "tests/*.test.js"`. The old brace counter survives as `npm run lint:syntax`. - CI installs `glslang-tools` and runs the real tests. The render job is `continue-on-error` because not every runner has a Chrome binary. ## Evidence **With the fix** (fresh clone of this branch, `npm ci` → build → tests): | Gate | Result | |---|---| | `npm test` | 10/10 pass, 0 skipped | | `npm run build` | clean, 565 ms | | `npm run lint:syntax` | pass | | `npm run test:render` | **PASSED** — 55–143 distinct colours, 63,750/64,000 lit pixels, no shader errors, scene hash changes after throttle + pitch | **With the shader deliberately re-broken** (proving the tests are not decorative): - `npm test` → fails 2/4 with glslang's own `'constructor' : too many arguments` - `npm run test:render` → fails four ways: *1 distinct colour*, *0/64,000 lit pixels*, *shader compile error*, *scene did not change after input* ## Honest caveats 1. **Rendering was verified in software only.** This box has no GPU (no `/dev/dri`, no `/dev/nvidia*`), so all the pixel evidence above is SwiftShader CPU rasterisation. It proves the shaders compile and geometry draws. It does not prove the CRT effect looks right — that needs your eyes. 2. **CI has never validated this repo.** This instance has no registered Actions runners (`/actions/runners` → `total_count: 0`), and upstream's only run (id 36) ended `cancelled`, not `success`. Do not read a missing CI tick as a pass. 3. **The flight model is still broken** (TODO T1): full throttle gives a brief hop to ~105 ft, then the aircraft stays grounded and accelerates to 2,543 knots. Deliberately left out of this PR so a verified one-line fix isn't tangled with judgement-heavy physics. That's the next branch. ## How to test it ```bash git clone -b fix/crt-shader-and-real-tests https://repos.retroweb.dev/kevinhermes/retro-flight-sim.git cd retro-flight-sim npm ci npm test npm run build && npm run preview # http://localhost:4173 ``` You should see the terrain, runway and aircraft instead of a black rectangle. Takeoff will still not work — that's T1, not this PR.
kevinhermes added 3 commits 2026-10-07 14:33:41 +01:00
vec4(position, 0.0, 1.0) passes a vec3 plus two floats into a vec4 - five
components into four. Invalid GLSL ES on both WebGL 1 and 2:

  THREE.WebGLProgram: Shader Error 0 - VALIDATE_STATUS false
  ERROR: 0:71: 'constructor' : too many arguments

The whole post-processing pass failed and the game canvas read back as one
colour, 0/64000 non-black pixels. position.xy is the vec2 the fullscreen quad
needs.

Verified headlessly with SwiftShader: 55-143 distinct colours, 63750/64000
lit pixels, no shader errors, scene hash changes after throttle + pitch.
npm test was a brace counter: it checked each file was >10 chars and that {
and } counts matched. It passed on a build whose shader would not compile.

- tests/shaders.test.js compiles the game's real GLSL with glslangValidator
  (Khronos' reference compiler) under a three.js-style attribute prefix, and
  includes a canary that asserts a known-bad vec4 construction is REJECTED, so
  the suite cannot pass vacuously.
- tests/terrain.test.js covers heightfield determinism (the "persistent world"
  claim), airport flattening inside the 200 m radius with no leakage outside it,
  out-of-bounds behaviour, plausible height range, and vertex colour counts.
- scripts/headless-check.mjs (npm run test:render) serves dist/, drives the
  game over raw CDP with SwiftShader software WebGL, and fails if the canvas is
  black, the shaders fail to compile, or the scene does not change after input.
  No new dependencies: Node 22 built-in fetch/WebSocket. 120 s watchdog so it
  cannot hang a runner.
- npm test -> node --test "tests/*.test.js"; old checker kept as npm run lint:syntax.
- CI installs glslang-tools and runs the real tests; render job is
  continue-on-error because not every runner has a Chrome binary.

Verified non-vacuous: re-breaking the shader makes the unit tests fail 2/4 with
glslang's own "constructor: too many arguments" and makes the render check fail
four ways (1 distinct colour, 0/64000 lit pixels, shader error, frozen scene).
Without dist/ it reported 'no webgl context captured', which points at WebGL
when the actual problem was that there was no page to load.
You are not authorized to merge this pull request.
This pull request can be merged automatically.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin fix/crt-shader-and-real-tests:fix/crt-shader-and-real-tests
git checkout fix/crt-shader-and-real-tests
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: kevinhermes/retro-flight-sim#1