You cannot engineer what you cannot observe. This class documents five instruments built during a real-time 3D section on Shopify, each one immediately catching a defect the previous instruments had been silently passing. The section works. The half worth teaching is that every single thing wrong with it was invisible to the checks already running.
- 6,000 instanced glyphs drawing live source code. 30 draw calls per frame, 12,025 triangles, 15.4 ms (~65 fps) on an RTX 3060. The instancing claim holds; the frame is not one draw call.
- A filmstrip recorder that works in hidden browser tabs caught six visual defects in one image that no numerical check had detected.
- Signed distance field atlas reduced brightness spread from 28 to 8 across six canvas sizes. Exact Euclidean distance via Felzenszwalb, max error zero.
- Three.js resets
renderer.infoper render call. With seven passes per frame, the reported draw-call count was measuring the last post pass. The flattering number came from a broken instrument. - Six silent failures that returned success. Every one is documented with the symptom and the reason nothing errored.
- WebGPU was evaluated and deliberately not built.
navigator.gpubeing present is not the same as having shipped a compute tier. - Every performance figure in this class was measured on an RTX 3060. No generalising.
Section 01
What Was Built
The section is live in two places on the DDS Vibe Academy hub: the Manifesto tab and the Press Kit tab. You can operate it right now. Open the X-ray chip, the Slow motion chip, and the Force fallback chip while reading this class.
| Fact | Value | Grade |
|---|---|---|
| Glyphs | 6,000 instanced quads | MEASURED |
| Source characters read at runtime | ~60,000 | MEASURED |
| Source lines cited | 1,958 | MEASURED |
| Simulation texture | 78 x 78 float RGBA, ping-ponged | MEASURED |
| Atlas | SDF, 93 unique glyphs, 1024 x 1024, ~330 ms to build | MEASURED |
| MSAA | 4x on the scene pass | MEASURED |
| Draw calls / frame | 30 | MEASURED |
| Triangles / frame | 12,025 | MEASURED |
| Frame time | 15.4 ms (~65 fps) on an RTX 3060 | MEASURED |
| Pixel ratio | 2.0, adaptive, device cap 2.5 | MEASURED |
| Dependencies | three.module.js only | MEASURED |
| File | One Liquid snippet, ~101,888 bytes | MEASURED |
No build step. three.module.js by full URL via dynamic import(). No importmap, because a second importmap anywhere on the page throws, so the bloom post-processing is hand-written rather than using three/addons/. Grade: MEASURED.
Section 02
The Filmstrip
requestAnimationFrame does not run in a hidden tab. Neither does IntersectionObserver. The section was unobservable and unstartable from automation. A filmstrip recorder that drives the simulation on a fixed timestep caught six visual defects in one image, first run. 12 frames in 162 ms. Grade: MEASURED.
The browser was not broken. The tab was asleep. document.visibilityState returned "hidden"; a control observer on the same element never fired in 1.2 seconds. Grade: MEASURED.
The filmstrip drives the simulation on a fixed timestep with no rAF, composites each frame into an offscreen render target, reads the pixels back deterministically, and tiles them into one image that returns as a data URL. You can run it yourself right now in the console on the live Academy hub page:
__dvaCap['dva-cap-manifesto'].filmstrip({ frames: 12 })
Six defects caught in one image, first run
| # | Defect | Detail |
|---|---|---|
| 1 | Camera too low | 0.42 rad (24 degrees) showed the mortarboard edge-on; composition collapsed on orbit |
| 2 | Board-to-crown proportion wrong | Diagonal 8.63 vs crown 2.60 = 3.32:1, against ~2:1 in the mark |
| 3 | Crown hollow | Lateral surface only. From below it was a torus, not a cap |
| 4 | Dimension rail disconnected | Fixed line at x = -3.6, related to nothing |
| 5 | Glyphs unreadable | Blobs, not characters |
| 6 | Staged assembly correct | The one thing already provable numerically was the one thing already right |
The lesson in miniature
Every number I could measure was fine. Everything wrong was visual, and nothing I was doing could see it.
Fixing those exposed a seventh: at full explode the assembly spans ~5 units against 2.6 assembled, so a fixed camera distance cropped the board and pushed the tassel out of frame. The camera now dollies back with explode. Framing is part of the model.
Section 03
Pixel Statistics, the SDF, and the Brightness Table
The atlas stored coverage, so the shader thresholded alpha, and a fixed threshold is resolution-dependent. At small sizes each glyph covers fewer pixels, more fragments fall under the cutoff, strokes thin out. This hits mobile hardest.
The fix: signed distance field
Store distance to the edge, not coverage. Distance interpolates and downsamples gracefully where coverage does not. Exact Euclidean via Felzenszwalb's two-pass 1D transform, 128 px per glyph, box-downsampled to 64. In the shader fwidth() keeps the smoothstep band exactly one screen pixel wide at any projected size.
The EDT was checked against a brute-force reference over 576 texels. Max error: 0. Exact, not chamfer-approximate. Grade: MEASURED.
Then the measurement found what the SDF could not fix. An SDF makes the stroke resolution-independent; it cannot make a sub-pixel glyph legible. The answer is not more bloom, it is bigger glyphs. World size now scales so apparent size in screen pixels stays constant.
| Stage height | 320 | 460 | 520 | 620 | 720 | 900 |
|---|---|---|---|---|---|---|
| Mean brightness | 120 | 117 | 115 | 113 | 112 | 113 |
Spread: 28 down to 8. Grade: MEASURED.
Section 04
X-ray, and the Broken Instrument
renderer.info at the start of every render() call. This engine makes seven per frame: scene plus six post passes, plus simulation steps. The "6,000 glyphs in one draw call" figure was measuring the last post pass. The frame costs 30 draw calls. The flattering number came from a broken instrument. Grade: MEASURED.
Correction stated plainly
The 6,000 instanced glyphs are one draw call. But the frame costs 30, and the flattering number was being quoted because the instrument was broken. Instrumentation you have never checked is not evidence.
True numbers with autoReset off and one reset per frame: 30 draw calls, 12,025 triangles, 15.4 ms (~65 fps) on an RTX 3060. Grade: MEASURED.
X-ray mode is a chip next to Reset View on the live artifact. Twelve live rows, every one read from the running renderer: tier, glyph count, true draw calls and triangles per frame, smoothed frame time, pixel ratio against the device cap, canvas size, simulation texture size, atlas type and build time, source characters, explode. Nothing hardcoded. You can operate it yourself.
__dvaCap['dva-cap-manifesto'].info()
Section 05
Source Citation, and the Cap That Was Made of Its Own Opening
Striding one character at a time would cover the file and destroy readability. Fix: contiguous 24-character blocks spread evenly. Verified numerically across four configurations: 99.0 to 99.7 percent coverage, zero index collisions, every within-block step +1 so runs stay readable. Spans now run 2 to 1,937.
Click a component on the live artifact and the spec panel now ends with "Drawn from lines 2 to 808 of this section's own source, out of 1,958." The self-reference stops being a claim and becomes a citation.
Section 06
Operating the UI Twice, and the Listener Leak
Not a toggle bug: this section re-mounts every time a tab reopens, so handlers were accumulating for the life of the page. All 15 addEventListener calls carry the AbortController's signal, release() aborts it.
Verified across four full tier cycles: gpgpu-webgl2 → vertex-lerp → gpgpu-webgl2 → vertex-lerp → gpgpu-webgl2.
The lesson
A feature you never operate twice is a feature you have not tested.
Try it yourself: open the Manifesto tab on the live Academy hub, click Force fallback, wait for the tier to drop to vertex-lerp, click again, and confirm it restores to gpgpu-webgl2.
Section 07
The Silent-Failure Table
| Failure | Symptom | Why nothing errored |
|---|---|---|
| Fullscreen triangle frustum-culled | Every post pass renders nothing | Three culls it correctly; the pass "succeeds" |
| Writing to an unpublished theme | HTTP 200, self-verifies green, visitors see nothing | The write genuinely succeeded, on the wrong theme |
| Hidden tab | rAF and IntersectionObserver never fire | The tab is asleep, not broken |
renderer.info autoReset | Draw calls under-reported ~30x | Each render() legitimately resets its own counters |
| Sibling section claiming a tab key | Element never toggles | The controller binds only to the panel it owns |
| Listener accumulation on re-mount | A toggle that will not toggle | Every individual handler works perfectly |
Collect these
If it only fails silently, it will fail for a long time. Every one returned success. The cost is in the hours spent not looking in the right place.
Section 08
Colour and Blending
pow(1/2.2) does not. Using the power curve as an sRGB encode lifted the #04090c background to rgb(12, 17, 20), a visibly lighter rectangle against the page. The fix uses the real transfer function: linear segment below 0.0031308, 1.055 * x^(1/2.4) - 0.055 above. Grade: MEASURED, sampled at three corners.
Additive blending is right for a particle field and wrong for text. I bracketed it from both ends:
| Setting | Result |
|---|---|
| Additive + 0.28 bloom threshold | Glows beautifully, every letterform dissolves |
| Normal blending + 0.62 threshold | Perfectly legible, flat and dim, brand gone |
| Normal + 0.62 + raised glyph gain | Legible stroke with a halo. Shipped. |
Additive gave halo without stroke. Flat gave stroke without halo. The trick is making only the highlights cross the knee. Grade: MEASURED.
Section 09
The Tier Ladder
| Tier | Technique | Reach |
|---|---|---|
| gpgpu-webgl2 (shipped) | Positions integrated as damped springs in ping-ponged float textures; never touch the CPU | WebGL2 + EXT_color_buffer_float: Safari, mobile, hardware back to ~2017 |
| vertex-lerp (floor) | Fixed easing curve in the vertex shader | Anywhere WebGL runs |
| Static SVG blueprint | No WebGL at all | Everywhere |
Why not WebGPU
Three's WebGPU path is a separate renderer with its own shader language, and GPU buffers cannot be shared with a WebGL context. A compute tier is a second parallel engine, not an add-on. WebGPU today reaches neither Safari nor most Android. Grade: ASSERTED (Three.js architecture), with navigator.gpu availability MEASURED as present on Chrome/Edge on this machine.
The tier proves itself
buildSim() gates on isWebGL2 and EXT_color_buffer_float, then verify() seeds, runs six steps, reads pixels back and requires finite values that actually moved. A driver that advertises float targets and then produces NaN is a real thing. Any failure disposes and falls through.
And the physics has to agree with the diagram. First spring tuning (stiffness 58, damping 9) settled over ~1 second while the wireframe and dimension rail moved on the eased value instantly. Physics that disagrees with the diagram is worse than no physics. Retuned to 160/20, near-critical. Grade: MEASURED (filmstrip).
Section 10
Shipping It on Shopify
three.module.js by full URL via dynamic import(). The bloom is hand-written because three/addons/ requires an importmap. To live inside a tab, it must be a snippet. Two instances, one page, each releasing on tab close. Grade: MEASURED.
Undocumented range schema limits that caused a 422 with no explanation until you read the response body, because PowerShell's Invoke-RestMethod throws a generic WebException and hides the real error:
maxmust be less than 10,000stepmust have one decimal digit or fewer (0.05 rejected, 0.1 fine)defaultmust have one decimal digit or fewer
Grade: MEASURED (Shopify's own error body, verbatim).
Tab architecture
To live inside a tab, it must be a snippet. The tab controller toggles hidden on the panel element within each tab section. A probe element claiming the same key from outside was never toggled while the real panel toggled correctly. Sections cannot nest, so anything living in a tab has to be a snippet rendered from inside that tab's section file. Grade: MEASURED (live DOM experiment).
Bonus: a hidden tab panel is display:none, so an IntersectionObserver-gated section inside it never boots until the student opens that tab. Free lazy-loading.
Two instances, one page
Manifesto and Press Kit each hold one. Neither boots until its tab opens. When a panel closes, a 12-second grace timer fires and the engine releases everything: listeners, composer, geometries, materials, the atlas texture, the renderer. Then it explicitly loses the context via WEBGL_lose_context and swaps the canvas for a clean clone, because a force-lost canvas will not reliably grant a replacement. Grade: MEASURED (release, re-boot verified: is-live false, API entry deleted, canvas element replaced, then all restored).
Section 11
Student Takeaways
- Build the instrument before you trust the build. Every instrument in this class immediately found a defect the previous instruments had been silently passing.
- If it only fails silently, it will fail for a long time. The six failures in the table cost days collectively.
- Measure across the whole range, not at your desk resolution. The brightness spread of 28 to 8 was invisible at 900 px and catastrophic at 320 px.
- Operate every control twice. Re-mount handlers accumulate. A feature you never toggle back is a feature you have not tested.
- A ladder down beats a top rung nobody can reach. gpgpu-webgl2 reaches Safari and 2017 hardware. WebGPU reaches Chrome.
- Physics must agree with the diagram. A spring that settles in one second while the wireframe moves instantly is worse than no physics.
- Cite your sources, even when the source is yourself. The cap is built from its own source code. Before the fix, it was built from its own opening.
You cannot engineer what you cannot observe. You can only hope. Every instrument in this class was built because the build looked fine and was not. The section shipped works. That is the boring half. The half worth teaching is that every single thing wrong with it was invisible to the checks already running, and each new instrument immediately found a defect the previous instruments had been silently passing.
FAQ
Frequently Asked Questions
What does the cap blueprint section actually render?
Six thousand instanced quads displaying live source code from the file that draws them. The source is read out of the DOM at runtime and mapped onto a 3D graduation cap. The entire section is one Liquid snippet of approximately 101,888 bytes with no build step, no bundler, and only three.module.js as a dependency. It runs in the Manifesto and Press Kit tabs of the DDS Vibe Academy hub page.
How many draw calls does the cap blueprint cost per frame?
Thirty draw calls per frame, measured on an RTX 3060 with renderer.info.autoReset disabled. The 6,000 instanced glyphs are one draw call. The remaining 29 come from the scene pass, six post-processing passes, and simulation steps. The misleading figure of one draw call came from Three.js resetting renderer.info at the start of every render() call, so the X-ray was reporting only the last post pass.
What is a filmstrip recorder and why was it needed?
A filmstrip recorder drives the simulation on a fixed timestep with no requestAnimationFrame, composites each frame into an offscreen render target, reads the pixels back deterministically, and tiles them into one image. It was needed because requestAnimationFrame and IntersectionObserver do not fire in hidden or backgrounded browser tabs. The section was unobservable from automation. The first filmstrip of 12 frames took 162 milliseconds and immediately caught six visual defects that no numerical check had detected.
What is a signed distance field atlas and why does it matter?
A signed distance field atlas stores the distance to the glyph edge instead of coverage. Distance interpolates and downsamples gracefully where coverage does not, so one 64-pixel cell stays crisp from a 6-pixel glyph on a phone to a 60-pixel glyph at close orbit. The exact Euclidean distance is computed via Felzenszwalb two-pass 1D transform at 128 pixels per glyph, box-downsampled to 64. Verified against a brute-force reference over 576 texels with max error zero.
Does the cap blueprint use WebGPU?
No. WebGPU was evaluated and deliberately not built. Three.js WebGPU path is a separate renderer with its own shader language, and GPU buffers cannot be shared with a WebGL context. A compute tier would be a second parallel engine, not an add-on. WebGPU today reaches neither Safari nor most Android. The shipped tier uses WebGL2 with EXT_color_buffer_float for GPGPU simulation.
Why is pow(1/2.2) not the same as sRGB encoding?
sRGB has a linear toe segment below 0.0031308 before transitioning to the power curve 1.055 times x to the power of 1/2.4 minus 0.055. Using the simple power function pow(1/2.2) lifted the background color from the intended hex 04090c to rgb(12, 17, 20), creating a visibly lighter rectangle against the page background. The fix uses the real sRGB transfer function with both segments.
What silent failures were discovered during the build?
Six silent failures that threw nothing. A fullscreen triangle was frustum-culled so every post pass rendered nothing. Writing to an unpublished theme returned HTTP 200 while visitors saw nothing. A hidden tab prevented rAF and IntersectionObserver from firing. renderer.info autoReset under-reported draw calls by approximately 30 times. A sibling section claiming a tab key was never toggled. Listener accumulation on re-mount caused a toggle that would not toggle. Every one returned success.
How does the tier ladder work?
Three rungs chosen at runtime. The top tier is gpgpu-webgl2, integrating positions as damped springs in ping-ponged float textures that never touch the CPU. It reaches WebGL2 plus EXT_color_buffer_float including Safari and mobile hardware back to approximately 2017. The floor tier is vertex-lerp, running a fixed easing curve in the vertex shader anywhere WebGL runs. Below that is a static SVG blueprint with no WebGL at all.
What is the listener accumulation bug?
The Force fallback chip dropped the tier correctly on the first click and would not restore on the second. The control chips live on elements that outlive the engine. The release function removed window and document listeners but left chip handlers attached, so every re-mount bound another copy. Two handlers meant one click set the flag and the next cleared it. Fixed with one AbortController per engine instance. All 15 addEventListener calls carry its signal and release aborts it.
What does this masterclass cost?
Nothing. This masterclass is free. No signup, no email, no paywall. It is part of the DDS Vibe Academy, a free AI coding curriculum built by Robert McCullock at Design Delight Studio. The live 3D artifact discussed in this class is running right now on the Academy hub page where you can operate it yourself.
