After adding a depth buffer, removing texture seams, and fixing sphere poles, one ring-shaped surface in a deterministic music-video render still looked rough. It filled or crossed the frame, its outline was visibly polygonal, and some camera angles reduced it to a giant rotating slab. Two remaining shader defects looked suspicious, but neither explained the shape.
The object was not the ordinary Torus display mode. At 45 seconds the
playlist entered NearRing, an eight-turn spiral tube whose
coils overlap closely enough to read as a torus from a distance. We have
a separate geometric tour of that
surface; this post is about how tessellation and camera assumptions made
it fail on screen.
The HeightField vertex shader had an out-of-bounds fetch at
uv = 1, but NearRing sets the height-displacement uniform to
zero, so that branch never executes here. The palette lookup also shared
the simulation buffer's linear sampler. Giving the palette its required
nearest sampler removed invented colors at index boundaries, but a
sampling filter cannot add missing mesh vertices or change a camera's
field of view.
Both were real defects worth fixing. A frame-for-frame comparison simply showed that they were not the causes of this geometry problem. See One Texture, Two Kinds of Smoothing for that part of the work.
The original mesh used 128 longitudinal segments. That sounds adequate
until the parameterization is considered: the centerline completes eight
revolutions while u moves from zero to one. Each visible
revolution therefore received only
128 / 8 = 16 segments. A face-on outline was literally a
16-sided polygon.
const NEAR_RING_TURNS: u16 = 8;
const NEAR_RING_SEGMENTS_PER_TURN: u16 = 64;
const NEAR_RING_TUBE_SEGMENTS: u16 = 64;
let longitudinal_segments =
NEAR_RING_TURNS * NEAR_RING_SEGMENTS_PER_TURN;
The revised mesh uses 512 longitudinal segments, or 64 per revolution, and doubles the tube cross-section from 32 to 64 segments. This yields 33,345 vertices and 65,536 triangles: inexpensive for contemporary GPUs but dense enough that the largest silhouette has sub-pixel faceting at 720p.
The renderer uses 16-bit mesh indices, so increasing density has a hard safety boundary. The maximum vertex index is now 33,344, comfortably below 65,535. A regression test locks together the segment counts, vertex count, index count, and maximum referenced vertex so a later quality increase cannot silently overflow the index format.
Most bounded ShoggothOx meshes reach roughly 0.8 to 1.0 units from the origin. NearRing reaches 1.5 units and its tube extends half a unit toward the camera. Nevertheless every display used the same orbit radius of 3.0 and a 45-degree vertical field of view.
At distance 3, that field of view covers only
3 tan(22.5°) ≈ 1.24 units above and below the
center plane. A 1.5-unit face-on ring cannot fit even before perspective
makes the nearer tube surface larger. The more varied camera orbit merely
made the old sizing assumption visible.
NearRing now selects an orbit radius of 4.5. Other modes retain radius 3.0, so their established framing and deterministic output do not change. The display-mode match is exhaustive: adding a new surface requires an explicit framing decision at compile time rather than inheriting an accidental default.
The active configuration uses the hippy palette, whose index
zero is intentionally (252, 252, 252). Large quiet regions of
the simulation buffer contain zero, so the first corrected render still
produced a mostly white surface with colored activity around its edges.
That observation was accurate but the conclusion was incomplete: a palette
lookup supplies color, not a complete 3D material model. The subsequent
surface-material and texture-coverage investigation adds lighting, a
palette-derived base coat, and one texture cycle per spiral turn.
The original video was preserved, then the same existing playlist was rendered with the repository's normal command:
nix develop --command cargo xtask video \
"Aces High" --reuse-playlist
The playlist file remained unchanged. Focused tests cover camera selection and 16-bit mesh-index safety, while matching 0:45 and 0:48.5 frames verify the visible result. The comparison also reinforces a useful debugging lesson: a renderer can contain several genuine bugs, but reachability, geometry budgets, and projection math still determine which one explains a particular frame.