Fourier Epicycles
Motion & Curves
A chain of rotating vectors — epicycles —
whose tip traces a shape. Feed in the Fourier
coefficients of a 5-pointed star and the chain draws the star, one stroke per frame, over
the dim circle each vector sweeps. Runs automatically; no joypad needed. Written in C and compiled with
the llvm-mos-based 65816 toolchain
(+mos-a16, 16-bit accumulator mode).
Self-running demo — the star draws itself, then redraws.
Click the screen to focus, then watch. Tab away and it pauses.
What it is
Any closed outline can be written as a sum of rotating vectors — its Fourier series. Each
vector k has a fixed integer frequency f_k and a complex amplitude
c_k; at time t the tip of the chain is:
P(t) = Σ_k c_k · e^(i·2π·f_k·t) // a sum of rotating vectors
per vector: rx += re·cos(θ) − im·sin(θ) // four 16×16→32 multiplies
ry += re·sin(θ) + im·cos(θ) // (the complex multiply)
tip = (rx, ry) >> 14 // accumulate, then scale to pixels The coefficients here are the Discrete Fourier Transform of a 5-pointed star, computed at build time. On screen the dominant vector's circle is drawn dim as a scaffold, then the bright outline blooms as the chain sweeps one full period — the bottom HUD counts the points plotted.
Compiler stress-test #10 — the many-multiply one
Where the CORDIC demo is deliberately multiply-free, this one is multiply-dense: a sin/cos-LUT lookup and four 32-bit multiplies for every harmonic, every traced point, with no divide anywhere. A different shape of arithmetic for the compiler to get right.
| Item | What it exercises |
|---|---|
| Four __mulsi3 per harmonic | Each rotating vector is a complex multiply c·e^{iθ} = (re·cos − im·sin, re·sin + im·cos) — four 16×16→32 multiplies. The disasm gate asserts __mulsi3 is present (4 distinct products) |
| No divide | Unlike the spirograph (gear-ratio divide) or n-body (1/r²), this is divide-free: the hot loop is multiply + accumulate only. Zero __udivsi3 / __udivmodsi4 in the gate object |
| Baked DFT coefficients | The 8 vectors are the Discrete Fourier Transform of a 5-pointed star outline, generated at build time (tools/gen-epicycles-tables.py) and ordered largest-first, so a prefix is the best low-order approximation |
| Off-axis on purpose | The star is rotated ~23° so every coefficient has a non-zero real AND imaginary part — a vertically-symmetric star gives purely-imaginary c_k, which would fold the real-part multiplies to ×0 and halve the stress |
| rep/sep | Native 16-bit accumulator brackets under +mos-a16; 28 occurrences across the per-harmonic sin/cos-LUT sweep and the 32-bit accumulation (the values route through the Imag16 zero-page pair) |
The epicycle math is a portable C header (examples/65816/epicycles.h) linked into the
SNES ROM and a host oracle alike. The gate asserts corpus_result — a 16-bit rotate-XOR
CRC over the traced tip points spanning one full period — is identical on host and target.
Written in C with the llvm-mos 65816 toolchain. Hit Verify fidelity to
reproduce the build gate's WRAM assert (gate CRC 0x4F6C) live in this tab — the same
hash the host oracle and the cycle-accurate bsnes-jg core agree on, across the default 8-bit, the
+mos-a16, and the +mos-xy16 builds.