A rendering problem does not always live where it appears. After improving
the mesh, camera, texture sampling, and material of an eight-turn
NearRing, one six-second interval still looked nearly dead.
From about 0:51 through 0:56 the object became a quiet gray ring, even
though the soundtrack remained loud.
The rendered surface was only the final witness. The real failure happened earlier, where two independent systems believed they controlled the same visual state.
Audio measurements ruled out silence: one-second windows in the reported
interval stayed around -10 to -14 dB RMS. We then decoded the first minute
to raw 48 kHz stereo PCM and replayed the existing
video/tracks/Aces High/playlist.csv through the 160×100
CPU renderer. This removed the 3D mesh, shader, depth buffer, and camera
from the test.
Frames 0:51 through 0:56 each expanded to exactly one palette color and had zero image entropy. The gray ring was therefore an accurate rendering of an inactive indexed buffer, not a projection or lighting illusion.
The active CSV row selected Wireframe3D. Its virtual camera
sat only 0.6 × width from a ribbon spanning
±0.5 × width. At the nearest rotation angle the
perspective denominator fell to 0.1 × width, allowing
8× magnification. A loud waveform could be projected entirely
outside the simulation buffer.
The revised projection places the camera one full buffer width away, uses
a focal length of 0.75 × width, and scales waveform
height from the buffer height:
let focal = W as f32 * 0.75;
let depth_offset = W as f32;
let amp_scale =
(H as f32 * 0.25 / 128.0)
/ (1 << scale.min(3)) as f32;
The maximum magnification is now 1.5×, so even a full-range sample remains inside the frame. A regression test renders all 316 sampled phases of one complete rotation and requires visible pixels at every phase.
Yet replaying the exact music still produced the six dead seconds. That negative result was decisive: another state change had removed the wave renderer itself.
A video playlist is already a scheduler. Each CSV row defines a base configuration, a duration, and a state count. At each transition the playlist either advances to the next row or deterministically mutates one visual parameter.
The core engine also has an auto-changer for interactive use. Beats,
elapsed time, and a boring-scene detector can trigger
randomize_one or randomize_all. The headless
renderer loaded and advanced the CSV but left this second scheduler
enabled. It could silently replace the selected wave, flame, palette, or
display between authored transitions.
The fix makes ownership explicit:
// The playlist owns deterministic mutations.
engine.locked = playlist.is_some();
Here locked disables only the independent engine
auto-changer. The playlist still advances, applies base configurations,
and performs its requested mutations. Interactive rendering without a
playlist retains the normal beat, timer, and boredom behavior.
The exact CPU replay changed every sampled frame from 0:51 through 0:56 from one color and zero entropy to 231–238 distinct colors and entropy between 0.476 and 0.634. One-second samples across 0:49–0:59 then showed moving multicolored structure on the GPU surface throughout the previously dead interval.
Finally, the existing playlist was preserved and the full video was regenerated through the normal command:
nix develop --command cargo xtask video \
"Aces High" --reuse-playlist
This bug is a useful architecture warning. Determinism is not merely a fixed random seed; it also requires one clear owner for every state transition. A test can prove that an individual renderer never vanishes, while an interval replay proves that the orchestration layer continues to select that renderer in the first place.