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.

loading core…

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

ItemWhat it exercises
G_FMINNUM / G_FMAXNUMThe 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-quietingIEEE 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 CRCThe 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.