Perlin Noise
Rendering & GraphicsPerlin 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, then play. (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.