Boids Flock

Physics & Simulation

Reynolds boids flocking, computed live on a Super Nintendo — and built entirely on a vec2 { int16_t x, y } struct that the steering kernel passes and returns by value. On the 16-bit 65816 a 4-byte aggregate return forces the compiler's aggregate-return ABI — a small-struct register pair versus an sret hidden pointer — and the three rules (separation, alignment, cohesion) chain those returns O(N²) times a frame. A correct ABI paints a coherent flock: dots clump, align into streams (each boid coloured by its heading, so aligned birds share a hue) and dodge each other. Get the returned struct wrong and the flock scatters into noise. The picture is the proof — and it is bit-for-bit identical to host x86. Runs automatically; no joypad needed. Written in C and compiled with the llvm-mos-based 65816 toolchain (+mos-a16).

loading core…

Self-running demo — the flock swirls and the heading colours flow. (A title card shows first; the flock fades in after it.)

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

What it is

Each dot is a boid — a simple agent steered by three rules over its neighbours: separation (don't crowd), alignment (match the flock's heading) and cohesion (drift toward the flock's centre). Run them on a few dozen agents and a lifelike flock emerges. The twist here is purely about the compiler: the velocity and position of every boid is a vec2 struct, and every steering helper takes and returns that struct by value:

typedef struct { int16_t x, y; } vec2;            // a 4-byte aggregate

vec2 v2_add(vec2 a, vec2 b);                  // by-value in, by-value out
vec2 separation(const Boid *f, ...);          // each rule RETURNS a vec2
vec2 acc = v2_add(v2_add(sep, ali), coh);     // chained aggregate returns

On a register-rich CPU that is free. On the 65816 a returned vec2 must travel by the ABI's aggregate-return path — a register pair if it fits, otherwise an sret hidden pointer the caller allocates — and the steering composition hits that path thousands of times per frame. The flock lives in bank-0 WRAM (no far pointers), so the same kernel compiles and runs five ways, all asserted equal.

Compiler stress-test #26 — struct by value

A Round 2 demo — each Round-2 entry targets a codegen corner the first twenty never execute. Here it is the aggregate / by-value struct ABI: structs exist in earlier demos (a heap, arrays) but are always reached through pointers. This is the first to pass and return a struct by value, on a hot path, so the register-pair-vs-sret return convention is genuinely exercised.

ItemWhat it exercises
Struct passed/returned by valueThe whole kernel is a vec2 {int16_t x,y} VALUE type. v2_add/v2_sub/v2_scale and the three Reynolds rules (separation/alignment/cohesion) each TAKE and RETURN a vec2 by value, and the composition chains those returns: v2_add(v2_add(sep, ali), coh). No other demo on the site passes an aggregate by value
The aggregate-return ABIA 4-byte struct return forces the compiler to pick its aggregate-return convention — a small-struct register pair vs. an sret hidden pointer — and the steering composition exercises it O(N²) times per frame. That ABI path is otherwise untested by the battery
noinline so the ABI survives -OsThe vec functions are __attribute__((noinline)) on purpose: at -Os the optimiser would inline the whole kernel and the ABI would never run. The disasm gate asserts the by-value calls are still there (497 call sites in the slice)
Bit-exact differentialAll math is integer fixed-point (Q12.4; int16_t components, products widened to int32_t → __mulsi3, neighbour-count divides → __divsi3). Exact integer ops ⇒ host x86 == console bit-for-bit. Verified host == default-8bit == +mos-a16 == +mos-xy16 == gate CRC 0xA8AB — any returned-struct miscompile diverges it
The picture IS the proofA correct ABI yields a coherent flock — dots clump, align into streams (each boid coloured by its heading octant, so aligned birds share a hue) and avoid collisions. A returned-struct miscompile would scatter the flock into noise or freeze the gate CRC

The flocking kernel is a portable C header (examples/65816/boids.h) linked into the SNES ROM, a host oracle, and the corpus differential slice alike. The gate runs a small seeded flock a fixed number of steps through the by-value kernel and folds every boid's position and velocity into corpus_result. Because the math is exact integer fixed-point, that 16-bit hash must be identical on host x86 and on the console — and it is, 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 0xA8AB) 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: by-value calls = 497, __mulsi3 = 6, __divsi3 = 4, rep/sep = 103 — the struct-return ABI in native-16 mode, the aggregate-return path surviving -Os exactly as intended.