Randomly selecting a complete visualizer preset produces variety, but it also produces hard cuts. One moment can be a fading waveform on a flat screen; the next can change the flame, wave, palette, translation, and 3D surface simultaneously. The result is energetic, but not always coherent.
Ordinary interpolation is not an answer. There is no useful numeric
midpoint between rings and wire, or between
screen and torus. ShoggothOx therefore treats
smooth playlist generation as a route-planning problem in a discrete
configuration space: choose several curated presets as anchors, then walk
between them by changing exactly one parameter at each step.
Presets begin as command-line arguments, but comparing raw strings would give misleading results. Options can appear in different orders, and an omitted option means its known default rather than an absent visual property. The generator parses every preset into a sorted map and fills in defaults for the seven established dimensions:
flame, wave, border, palette,
table, translate, display
Unrecognized --key value pairs are retained as additional
dimensions. A future option can therefore participate in distance
calculations and survive playlist generation without first being added to
a fixed serializer.
Distance is the number of parameter values that differ: the Hamming distance between two normalized configurations.
distance(a, b) =
count of keys where a[key] != b[key]
Consider two real preset shapes. After inserting the default
table=0 and translate=none where omitted, they
differ in four places: flame, palette, translation, and display.
# Anchor A
--flame fade --wave rings --border amp \
--palette 8rain --display screen
# Anchor B
--flame water --wave rings --border amp \
--palette blengbyg --translate hurricane --display height
The generator first samples state durations from 12 through 15 seconds
until they cover the audio. It always creates at least two states. If that
produces N states, the playlist has exactly
N - 1 boundaries at which a parameter can change.
An anchor route must use that complete budget. For anchors
a0, a1, ..., am, the required condition is:
distance(a0, a1) + ...
+ distance(a[m-1], am) = N - 1
This is not a conventional shortest-path problem. A route that reaches a destination too quickly leaves unused playlist states, while a route that is too long outlives the sampled timeline. The path length must match the budget exactly.
The algorithm builds a matrix containing the distance between every pair
of presets. It aims for about four parameter changes per anchor segment,
so its first choice for the number of segments is
ceil((N - 1) / 4). Other segment counts are tried in order of
proximity to that target.
A small dynamic program answers the important question: from this preset, can the remaining number of transitions be consumed by exactly this many anchor segments?
can_complete(current, remaining, edges_left):
if edges_left == 0:
return remaining == 0
if remaining < edges_left:
return false
if remaining > edges_left * maximum_distance:
return false
return any next preset where:
distance(current, next) > 0
and can_complete(
next,
remaining - distance(current, next),
edges_left - 1,
)
Results are memoized by
(current, remaining, edges_left). Once feasibility is known,
route construction considers only choices that still have a valid
completion. It prefers unused presets, then distances close to the average
remaining segment length. Seeded shuffling supplies deterministic
tie-breaking, so equally good routes do not always favor the first row in
the preset file.
Selecting anchors is only half of the job. For each adjacent pair, the generator collects the differing fields, shuffles their order using the same deterministic seed, and copies them from the target one at a time. A four-parameter anchor edge might expand like this:
| Step | Changed field | Relevant state |
|---|---|---|
| Anchor A | — | fade, 8rain, none, screen |
| 1 | palette | fade, blengbyg, none, screen |
| 2 | display | fade, blengbyg, none, height |
| 3 | flame | water, blengbyg, none, height |
| Anchor B | translate | water, blengbyg, hurricane, height |
Every generated CSV row uses states = 1. The intermediate
states are explicit, so the older playlist behavior of repeatedly mutating
a base configuration cannot introduce an unplanned change. Comments mark
the start, intermediate anchors, and final anchor, making the route easy
to audit by eye.
The initial duration samples may produce a transition count that cannot be expressed as a sum of available preset distances. In that case the generator appends one more 12–15 second state and tries again. This increases the transition budget by one while continuing to cover the complete song.
Retrying is bounded by the maximum distance between any two presets. If no route appears within that window, generation stops with an error rather than weakening the one-parameter guarantee. It also rejects a preset file with fewer than two distinct normalized configurations.
Duration samples, starting anchors, candidate ordering, and field ordering all come from one stable seed derived from the track's ISRC. With the same track, preset collection, and generator version, the same playlist is produced again. That is essential when a rendering defect must be compared before and after a graphics change.
The feature is available through the normal one-at-a-time video command:
nix develop --command cargo xtask video \
--next --smooth-transition
The resulting playlist and description live together under
video/tracks/<Track Title>/. Once a good route exists,
--reuse-playlist preserves it for later renders instead of
running the generator again.
Randomized algorithms benefit from tests that assert invariants instead of one preferred route. ShoggothOx checks that generation is deterministic, covers the complete audio duration, uses only 12–15 second states, and changes exactly one normalized parameter between every adjacent pair of rows. A repository-level test also verifies that the real preset collection can produce a route with multiple anchors.
The model still has deliberate simplifications. Every parameter has equal weight even though changing a 3D display can feel more dramatic than changing a table. Intermediate combinations are structurally valid but are not themselves curated presets. The timeline follows duration rather than beats or musical phrases. The generator's explicit default table must also remain aligned with the runtime configuration schema. Weighted distances, compatibility rules, shared schema metadata, and audio-aware transition timing are natural future extensions.
The useful architectural boundary is already in place: anchor selection decides where the visual journey should go, while edge expansion guarantees how it gets there. That turns a pile of attractive presets into a coherent, inspectable route—one parameter at a time.