Perlin Noise
Rendering & Graphics Perlin noise is the smooth, organic
randomness behind decades of procedural clouds, terrain and textures. It places a random gradient
at each grid corner and blends between them with a carefully-shaped curve, so the field looks like
drifting smoke rather than static. Here it flows across a Super Nintendo — computed entirely in
fixed point, so it's bit-exact. Runs on its own; no joypad needed. Written in C and compiled with
the llvm-mos-based 65816 toolchain
(+mos-a16, 16-bit accumulator mode).
Self-running demo — the noise field drifts like smoke.
Click the screen to focus, then watch. Tab away and it pauses.
What it is
The trick is the blend. Each cell of the grid gets a random direction; a sample point in between is the fade-weighted mix of the four surrounding gradients:
fade(t) = 6t^5 - 15t^4 + 10t^3; // smooth S-curve blend
n = lerp( // blend the 4 corner gradients
lerp(dot(gAA), dot(gBA), fade(xf)),
lerp(dot(gAB), dot(gBB), fade(xf)),
fade(yf));
That fade curve, 6t⁵−15t⁴+10t³, is what removes the blocky grid and leaves a smooth,
flowing field. It's all multiply-adds in fixed point here, and the result is identical across every
codegen mode.
Compiler stress-test
| Item | What it exercises |
|---|---|
| gradient noise | Unlike random dots, Perlin noise is smooth: it places a random gradient at each grid corner and blends between them, so nearby points get similar values. The result looks like clouds, smoke or marble — coherent, not fuzzy. |
| the fade polynomial | The blend uses a specific quintic curve, 6t^5-15t^4+10t^3, which is flat at both ends (its first and second derivatives vanish) so the noise has no visible grid seams. Evaluating that polynomial per sample is the heart of it. |
| gradient dots + lerp | At each corner the gradient is dotted with the offset to the sample point, and the four results are interpolated with the fade curve. Lots of multiply-adds — done here entirely in fixed point. |
| no floats, bit-exact | It is computed in 8.8 fixed point with a seeded integer shuffle for the permutation, so there is no rounding ambiguity — the field is identical on the host and the 65816, a sharp differential. |
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 CRC 0xA72D — a fold of noise samples and fade-polynomial
values) live in this tab. No far pointers — the same source passes every way: host == default ==
+mos-a16 == +mos-xy16 == bsnes-jg, -verify clean.