Pool Fountain
Games & Classics
A sparking fountain on a Super Nintendo, drawn by a hand-rolled
free-list pool allocator — no
malloc. Forty-eight fixed particle slots are recycled through a singly-linked
LIFO free list threaded through the slots themselves: a birth pops the free
head, a death pushes the slot back. Continuous alloc/free churn every frame. 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 — sparks are born and die as the pool recycles slots.
Click the screen to focus, then watch. Tab away and it pauses.
What it is
A pool allocator hands out slots from a fixed array. To know which slots are free without
scanning, it keeps a free list: a linked stack whose links live inside the free
slots. Each free slot stores, in its own next field, the index of the next
free one; a single head points at the top of the stack.
alloc(): i = head; head = slot[head].next; // pop the free head free(i): slot[i].next = head; head = i; // push it back (LIFO) // spawn a particle → alloc(); particle dies → free(i)
Allocation pops the head; freeing pushes the slot back on top — two pointer writes, no search, no heap. Here the pool feeds a fountain: three sparks are born per frame, gravity brings each down, and death recycles the slot for the next birth. The whole birth/death cycle stays bit-exact across every codegen mode.
Compiler stress-test
| Item | What it exercises |
|---|---|
| free list | A singly-linked stack of unused slots threaded through the slots themselves — each free slot stores the index of the next free slot in its own next field. No separate bookkeeping array, no malloc: recycling 48 fixed slots is two pointer writes. |
| pop / push | Spawning a particle pops the head off the free list (alloc); a particle dying pushes its slot back (free). Distinct from a bump/arena pool that only ever grows then resets — here individual slots come and go every frame. |
| indexed slot chase | On the 65816 the link walk head = slot[head].next becomes an indexed struct-array load (ldy-indexed). The differential proves that recycling — across default-8-bit, +mos-a16 and +mos-xy16 — is bit-exact, with zero arithmetic libcalls (pure pointer/index math). |
| fountain | The pool drives a particle fountain: three births per frame from the nozzle, gravity pulling each spark down, death (life expiry or off-screen) recycling the slot. When births outrun the 48 slots the pool saturates and drops births — the exhaustion path is exercised too. |
Written in C with the llvm-mos 65816 toolchain and verified against bsnes-jg
(and MAME where the SPC700 IPL is present). Hit Verify fidelity to reproduce the build
gate's WRAM assert (gate hash 0x2B9B — a fold of the full pool state over 100
simulated frames) live in this tab. No far pointers — the same source passes every way:
host == default == +mos-a16 == +mos-xy16 == bsnes-jg,
-verify clean.