Harmonograph

Motion & Curves

A harmonograph — the Victorian pendulum-driven drawing machine — running live on a Super Nintendo. Four damped pendulums (two per axis, slightly detuned) each trace a decaying sinusoid; their sum is a Lissajous figure that slowly precesses and spirals inward as the exponential damping bleeds off energy. When a figure settles, the next of four presets blooms. Written in C and compiled with the llvm-mos-based 65816 toolchain (+mos-a16, 16-bit accumulator). Runs automatically.

loading core…

Self-running demo — the curve draws, decays, and the next figure blooms.

Click the screen to focus, then watch. Tab away and it pauses.

What it is

A real harmonograph hangs a pen and the paper from independent pendulums; as they swing and slowly lose energy to friction, the pen traces a Lissajous curve whose amplitude shrinks — a figure that spirals gracefully into its own centre. This is the digital version: four oscillators, two driving the x-coordinate and two the y, each a damped sine

x(t) = A0·sin(f0·t + p0)·e^(−d0·t) + A1·sin(f1·t + p1)·e^(−d1·t)
y(t) = A2·sin(f2·t + p2)·e^(−d2·t) + A3·sin(f3·t + p3)·e^(−d3·t)

The two oscillators on each axis sit at a near-integer frequency ratio with a tiny detune (f1 ≈ f0·(1+ε)), so the figure does not close — it precesses, drawing the characteristic nested, slowly-rotating loops. The exponential envelope e^(−d·t) is computed incrementally as a multiply by a constant just under 1 each sample, so the whole curve decays into a point and the next preset takes over.

Why it stresses the compiler

This is the damped sibling of the spirograph. Where the spirograph plots a constant-amplitude curve, every term here carries a running envelope that is itself state, decayed each sample. The hot loop issues eight 16/32-bit multiplies per point — four amplitude products sin·env and four envelope-decay products env·decay — a sustained fixed-point __mulsi3 + accumulation profile, under +mos-a16 rep/sep brackets, and multiply-only (the damping needs no divide).

Compiler stress-test

ItemWhat it exercises
Sine LUTFour phase accumulators index a 256-entry signed Q8.8 sine table each sample — the same sin/cos-LUT inner loop the spirograph uses, here driving four independent oscillators
Amplitude productEach oscillator’s sample is sin·env — a 16×16→32 __mulsi3 scaling the sine by the pendulum’s current amplitude; four per sample, then summed per axis
Exponential dampingThe new stress vs the spirograph: each amplitude is a running envelope decayed every sample by env = (env·decay) >> 15 — a sustained fixed-point multiply + accumulation, four more __mulsi3
Multiply-onlyEight __mulsi3 per sample and NO divide — the damping is a multiply by a Q15 constant < 1, so the disasm gate asserts zero __udivsi3/__udivmodsi4

The gate folds 256 samples of preset 0 into a rotating-XOR hash and asserts corpus_result (0x0EBB) is identical on host, default 8-bit SNES, +mos-a16, and +mos-xy16. The Verify fidelity button above reproduces that gate live in this tab via the same cycle-accurate bsnes-jg core used in CI.

Written in C with the llvm-mos 65816 toolchain. Hit Verify fidelity to reproduce the build gate's WRAM assert (corpus_result = 0x0EBB) live in this tab. No far pointers — the same logic checked four ways: host == default == +mos-a16 == +mos-xy16, with bsnes-jg as the in-browser confirmation.