Fmin/Fmax Speed Cap
Physics & Simulation
12 float-velocity particles attracted to a center, each clamped by a
fminf(fmaxf(v, −MAX_V), MAX_V) speed governor — the LLVM
backend's G_FMINNUM/G_FMAXNUM nodes, which
lower to fminf/fmaxf libcalls on the 65816.
Every 180 frames one particle gets a quiet NaN injected into its
velocity; fmaxf(NaN, −8.0f) = −8.0f (NaN-quieting)
recovers it. Compiler stress-test #82.
Self-running — teal = normal · orange = speed-capped · red = NaN-recovered
Click the screen to focus, then watch. Tab away and it pauses.
What it is
Each of the 12 particles starts on a circle of radius 48 centred at the canvas and is
pulled toward the center by a constant acceleration proportional to distance. As speed
builds, the velocity is clamped each frame by a double-clamp:
v ← fminf(fmaxf(v, −8), 8). Particles that hit the cap turn orange; once
released they return to teal. Every 180 frames, particle 0 gets a quiet NaN written into
its x-velocity — the next step's fmaxf(NaN, −8) silences the NaN to −8.0
(IEEE 754-2008 minNum semantics), and the particle flashes red for 30 frames.
// Speed governor applied to each particle's velocity each frame.
float dx = (float)((int16_t)(CENTER_X - px[i])); // G_SITOFP → __floatsisf
float sdx = dx * K_ATTR; // G_FMUL → __mulsf3
float nvx = vx[i] + sdx; // G_FADD → __addsf3
// Speed governor — the codegen corners:
vx[i] = __builtin_fminf( // G_FMINNUM → fminf libcall
__builtin_fmaxf(nvx, -MAX_V), MAX_V); // G_FMAXNUM → fmaxf libcall
// NaN-quieting: fmaxf(NaN, -MAX_V) = -MAX_V
// (IEEE 754-2008 minNum/maxNum silences NaN to the number)
int16_t ivx = (int16_t)vx[i]; // G_FPTOSI → __fixsfsi
px[i] = (int16_t)(px[i] + ivx);
The simulation is entirely in float space; position is updated by truncating the clamped
float velocity to int16_t pixels per frame. The integer position fold is the
gate CRC witness — any fminf/fmaxf miscompile immediately shifts at least one particle's
trajectory and breaks the CRC.
Compiler stress-test
| Item | What it exercises |
|---|---|
| G_FMINNUM / G_FMAXNUM | The float minimum and maximum operations on 32-bit floats lower via .libcallFor S32 in the MOS legalizer — there is no inline SSE-style instruction on the 65816. The legalizer emits calls to fminf and fmaxf from the SDK's math.cc (lines 18–19), the only libm implementation for these functions. |
| NaN-quieting | IEEE 754-2008 minNum/maxNum semantics: if one input is NaN and the other is a number, the result is the number. A simple (a < b) ? a : b comparison instead propagates NaN. The ROM demos this every 180 frames by injecting a quiet NaN (0x7FC00000) into one particle's velocity; fmaxf(NaN, -MAX_V) = -MAX_V recovers it in the next step. The recovering particle flashes red for 30 frames. |
| distinct from #77 satcast | #77 uses fminf/fmaxf as a saturating-cast clamp immediately before G_FPTOSI (float-to-int), stressing the fminf→fptosi chain on a hex kaleidoscope. This demo uses fminf/fmaxf as the velocity speed governor on a physics simulation — the downstream use is int16 position update, not a saturating integer cast. |
| distinct from #26 boids | #26 is an integer fixed-point Reynolds flocking simulation built on a vec2 value type, testing aggregate-return ABI (struct by value). It has no float operations, no fminf/fmaxf, no libm calls. This demo uses float velocity and explicit NaN injection/recovery. |
| position fold CRC | The gate CRC runs 12 particles × 16 steps, folding all (px, py) pairs with prime multipliers (97, 13) to break XOR-cancel symmetry. The speed cap fires from step ~8 (attraction builds velocity past MAX_V in ~8 frames), so all 16 steps exercise the fmin/fmax path. GATE_N was reduced from 64 to 16 to keep computation within the 500-frame capture window (fminf/fmaxf libcalls cost ~3–8 k cycles each on the 65816). |
Written in C with the llvm-mos 65816 toolchain and verified against
bsnes-jg and MAME. Hit Verify fidelity to reproduce the build gate's WRAM assert
(speed-gov CRC 0x0116 — 12 particles × 16 steps position fold)
live in this tab. No far pointers —
host == default == +mos-a16 == +mos-xy16, -verify clean.