Julia Set Explorer
Fractals
A Julia set — z² + c iterated to
escape — computed live on a Super Nintendo in Q5.10 fixed point. The constant
c orbits the classic 0.7885·eiθ path, so the fractal
continuously morphs between connected blobs and scattered dust, while Mode 7 spins and
zoom-breathes it and the palette cycles. 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 set morphs and spins automatically. (Boot takes a few seconds to grind the first frame.)
Click the screen to focus, then watch. Tab away and it pauses.
What it is
A Julia set is the escape-time picture of iterating z ← z² + c for a
fixed complex constant c, colouring each starting point by how fast it
flies to infinity. It is the close cousin of the Mandelbrot set: Mandelbrot fixes
z₀ = 0 and varies c per pixel; Julia fixes c for the whole
image and lets the pixel be z₀. Slide c and the set transforms
— that slide is the animation here:
z = (zr, zi) = pixel // Q5.10 fixed point, 1.0 == 1024
for n in 0..maxiter:
zr2 = (zr*zr) >> 10 ; zi2 = (zi*zi) >> 10 // two __mulsi3
if zr2 + zi2 > 4.0: break // escaped
zi = 2*((zr*zi) >> 10) + ci // one __mulsi3
zr = zr2 - zi2 + cr
// escape count n = pixel colour ; c sweeps 0.7885·e^iθ
Each frame the 65816 grinds that loop over the image and far-stores every pixel into a
high-WRAM buffer at $7E2000 — memory only reachable through 24-bit addressing, the
#320 far-pointer path
(sta [dp]) — then far-loads it back into Mode 7 character VRAM. Because a full
recompute is seconds of fixed-point multiply, the set is re-ground a row at a time in the
background while the Mode 7
affine matrix spins and zoom-breathes it at 60 fps: the multiply is the morph, the hardware is
the motion.
Compiler stress-test #1 — complex multiply + the far framebuffer
Every iteration issues three 32-bit multiplies and the whole image lives in far high WRAM, so
this demo leans on exactly the two things +mos-a16 exists for: the
__mulsi3 16×16→32 multiply in native-16 mode, and the 24-bit far store/load path.
| Item | What it exercises |
|---|---|
| Q5.10 complex multiply | The hot loop is z = z² + c in signed Q5.10 fixed point — three 16×16→32 multiplies per iteration (zr², zi², zr·zi), each a __mulsi3 libcall, with zr/zi held live across the loop |
| Far high-WRAM framebuffer | The 64×56 escape image lives at $7E2000 — high WRAM reachable only by 24-bit addressing. Filling it is the #320 far-store path (sta [dp]); reading it back to VRAM is far loads (lda [dp]). a16-only |
| rep/sep | Native 16-bit accumulator brackets under +mos-a16: the int16 values route through the Imag16 zero-page pair, entered/left with rep #$20 / sep #$20 around each native-16 op (46 in the gate object) |
| z₀ = pixel, c = constant | The Julia twist on the same kernel as the Mandelbrot demo: there c is per-pixel and z₀=0; here z₀ is the pixel and c is one constant per frame, swept along the 0.7885·e^{iθ} orbit so the set morphs |
| Grind is the morph, matrix is the motion | A full 64×56 escape-time recompute is ~22 s on the 65816, so the set is re-ground one coarse row per spin frame in the background; Mode 7's hardware affine transform does the rotation/zoom at 60 fps |
The Julia kernel is a portable C header (examples/65816/julia.h) linked into the
SNES ROM, a host oracle, and the corpus differential slice alike. The gate folds the escape
buffers of four keyframe c values into corpus_result, a 16-bit
rotate-XOR CRC that must be identical on host (32-bit int) and target (16-bit
int) — which holds only because every product is forced through
int32_t and every narrowing is an explicit fixed-width cast.
Written in C with the llvm-mos 65816 toolchain. Hit Verify fidelity to
reproduce the build gate's WRAM assert (gate CRC 0x3490) live in this tab — the
same hash the host oracle and the cycle-accurate bsnes-jg core agree on. The disasm gate proves
the shape: __mulsi3 = 3 (the three Q5.10 products), rep/sep = 46 —
native 16-bit complex multiply.