5 min read
Checking a course against the code it teaches
ShaderPath had already passed an accuracy audit. Writing review questions for it, and checking every answer against the WebGL spec and the installed library source, still turned up wrong claims in twelve of its fourteen tracks.
ShaderPath is a course on three.js, WebGL and shaders: 162 lessons in Vietnamese and English, from vector maths to raymarching. Every theory lesson carries a Common mistakes callout: the wrong intuition people usually bring to the topic, and what actually happens.
In August the whole course went through an accuracy audit, track by track, plus a pass to make sure both languages say the same thing. I considered the content done.
Then I changed how reviews work, and the reviews found what the audit had missed.
Reviews needed questions
The spaced-repetition review used to show a lesson title and four grade buttons. You recognised the title, graded yourself, and never had to recall anything. Now every review asks a question first, and the grades stay disabled until you've seen the answer.
That meant writing two or three cards per lesson. A good card asks for a consequence, not a definition: not "what is a dot product?" but "two unit vectors have a dot product of −1: where do they point?". The richest source of consequences is the mistake callout, since it already names the wrong intuition.
Each batch of cards then went to a separate AI agent whose only job was to check every answer against a source of truth: the WebGL and GLSL ES specs, and the code actually installed in the project (three 0.185.1, React Three Fiber 9.7.0, drei 10.7.8, GSAP 3.15.0, postprocessing 6.39.4).
The cards failed where the callouts were wrong
| Batch | Checked against | Cards kept | Wrong or seriously off |
|---|---|---|---|
| WebGL, GLSL | the WebGL 1.0 and 2.0 specs | 47 | 4 |
| three.js, R3F | installed three, R3F, drei | 45 | 3 |
| GSAP, custom shaders | installed GSAP, three, R3F, drei | 38 | 5 |
| Procedural, raymarching | the maths, GLSL ES 3.00, the demos | 47 | 2 |
| GPGPU, post-processing | installed three, R3F, postprocessing | 49 | 3 |
| PBR, performance | installed three, R3F, React, the Next.js docs | 58 | 1 |
With 33 cards for the maths track, that's 317 in total. Every bad card was fixed or deleted, but the pattern behind them mattered more than the count. In the GSAP batch, all five serious findings were cards copied from a lesson's mistake callout. In the WebGL batch, four of the six lessons behind the worst findings had a callout or example that contradicted the spec, or the lesson's own body.
My guess at why: a mistake callout describes code you are warning against, so nobody runs it. The body next to it gets exercised by the demo; the callout never does.
What was wrong
A few of the claims that were corrected, in both languages:
- Setting a uniform with the wrong program active. The callout said the value "lands on the wrong program". A uniform location belongs to the program that produced it; with another program active, the call is dropped with
INVALID_OPERATIONand the uniform keeps its old value. The lesson body already said so. - Forgetting to clear depth. The lesson promised vanishing objects. On a default canvas (
preserveDrawingBuffer: false) the browser clears depth after every composite, so the bug only shows up in a framebuffer, or on a canvas that preserves its buffer. OrbitControls.update(). The lesson said it yanks the camera back to its old position. The installed source rebuilds its state from the camera's current position and then callslookAt(controls.target), and that is what overwrites a tween.controls.enabled = false, offered as the fix, doesn't stopupdate().- Index keys in R3F. They don't destroy and recreate meshes. React reuses fibers by position, so state follows the slot instead of the data.
- Why Schlick uses a fifth power. The lesson said lower powers climb too slowly. It's the opposite: on [0, 1] a lower power is larger everywhere. At 60°, a squared term gives 0.28 against an exact 0.09, while the fifth power stays within about 0.036 of exact Fresnel for glass (n = 1.5).
My shaders broke my own rule
The GLSL built-ins lesson says, correctly, that smoothstep is undefined when edge0 >= edge1, and one of its cards asks exactly that. A scan of all 164 smoothstep( calls in the project found 30 with reversed edges, in lesson demos and playground presets.
// Undefined in GLSL ES 3.00: edge0 >= edge1
float mask = smoothstep(0.5, 0.4, d);
// The same curve, defined by the spec
float mask = 1.0 - smoothstep(0.4, 0.5, d);Real drivers draw both the same, which is why nothing ever looked wrong. All 30 were rewritten, and the one demo whose sliders could swap the edges at runtime now guards against it.
Check the fixes too
Every batch of corrections went through the same source check before it was committed, and that check kept finding mistakes in the fixes themselves: an overstated "only", a renamed formula that made the next paragraph contradict itself, a Perlin noise detail that belonged to the 2002 version rather than the 1985 one. A correction is new content written quickly, and it deserves the same check as everything else.
Takeaways
- Check behaviour against the version you ship: the spec, and the source in
node_modules, not what you remember from an older release. - The "common mistakes" section is where a course is most likely to be wrong, because it describes code nobody ran.
- Writing questions is proofreading. A question needs an answer precise enough to be wrong.
- Search your own code for the rules your lessons teach.
- Re-check the fixes.