L-System Plant
Fractals
A Lindenmayer-system fractal plant grown
live on a Super Nintendo. Five generations of string rewriting — in-place
memmove + memcpy + strlen over a 1 700-byte buffer
— then a turtle interpreter walks the string with a [/]
bracket push/pop stack, drawing branch by branch. The picture is the proof: a correct
string-rewrite + interpreter grows a coherent fractal; a miscompile scrambles the symbols
into noise. Bit-for-bit identical to host x86. Self-running; no joypad needed.
Written in C and compiled with the llvm-mos
65816 toolchain (+mos-a16).
Self-running demo — after the title card the plant draws itself stroke by stroke (trunk first, then branch by branch). (Rewriting 2 388 symbols takes a moment.)
Click the screen to focus. Tab away and it pauses.
What it is
The axiom "X" is rewritten 6 generations by two rules:
X → "F-[[X]+X]+F[+FX]-X" // branch into two sub-fronds F → "FF" // elongate each segment
After 6 generations the string is 2 388 symbols long. Each F moves the turtle
forward one step; +/- turn 25°; [ pushes the full
turtle state (position, heading, depth) and ] restores it. Colour is chosen by
bracket depth — dark brown trunk, two greens for inner and outer fronds.
The rewriting runs entirely in a single 2 400-byte bank-0 WRAM buffer (in place —
no second buffer). Each symbol expansion shifts the buffer tail right with
memmove, writes the production with memcpy, and measures its
length with strlen.
Compiler stress-test #23 — string libcalls + bracket stack
A Round 2 entry targeting codegen corners the first twenty-two demos never exercise.
| Item | What it exercises |
|---|---|
| String rewriting — the libcall corner | Each rewrite generation walks the buffer symbol by symbol; for every symbol that has a rule it shifts the tail right with memmove (overlapping copy), writes the production with memcpy, and measures the production with strlen. These are the three string libcalls no other demo in the battery calls — the stress is building a string dynamically on a 16-bit machine with no heap |
| In-place memmove | The buffer is rewritten IN PLACE (single buffer, no ping-pong). Each production insertion shifts the tail right before writing, so source and destination overlap — a genuine memmove, not a memcpy. Every other demo that copies memory uses memcpy over disjoint regions |
| Bracket push/pop stack | The turtle's [ saves the full turtle state (position, heading, depth) and ] restores it, so each frond returns to its branch point. This is the battery's save/restore stack member — the push/pop path no fractal or simulation demo runs |
| Bit-exact differential | String rewriting and turtle interpretation are exact byte/integer operations (position Q8.8 in int32, heading 0..255 indexing the shared SINCOS LUT). Exact ops reproduce bit-for-bit on host x86 and on the console — a memmove or strlen miscompile corrupts the string, a bracket stack bug scrambles the path, and the gate CRC diverges immediately. Verified default == +mos-a16 == +mos-xy16 == 0x8073 |
| Found a compiler bug | The gate caught a +mos-xy16 miscompile: SEP #$10 (inserted by the REP/SEP scheduler for an intervening ldy) zeroed the high byte of the 16-bit index register between the ldx that loaded i and the lda abs,X that read buf[i]. For i ≥ 256 the wrong byte was read. Fixed in MOSInsertREPSEP by cloning the last X-writer instruction after the repair REP |
| Far-pointer reveal (high WRAM) | After the string is rewritten, the interpreter records every line segment into a FAR buffer at $7E2000 in the SNES's 128 KiB WRAM — exercising the +mos-a16 24-bit far-pointer STORE path. The main loop then replays a few segments per frame, reading them back from far memory (the far LOAD path), so the plant draws itself stroke by stroke. The far path is display-only (the differential gate stays far-pointer-free), but it adds the high-memory codegen coverage the low-WRAM-only version never touched — and the picture proves it: a far store/load miscompile would scramble the replayed plant |
The L-system logic lives in a portable C header (examples/65816/lsystem.h)
shared by the SNES ROM, a host oracle, and the corpus differential slice. The gate rewrite
+ interpretation folds every turtle segment (endpoints + depth colour) into a rotate-XOR
CRC16 stored in corpus_result. Because the ops are exact, that 16-bit hash must
agree on host x86 and on the console — and it does, across every target codegen
mode (default 8-bit, +mos-a16, +mos-xy16).
Written in C with the llvm-mos 65816 toolchain. Hit Verify fidelity
to reproduce the build gate's WRAM assert (gate CRC 0x8073) live in this tab —
the same hash the host oracle and the cycle-accurate bsnes-jg core agree on, bit for bit.
The disasm gate proves the shape: memcpy/memmove = 2, strlen = 1,
rep/sep = 87 — string libcalls in native-16 mode.