Signed-Bitfield Terrain
Algorithms
An animated eroding terrain on a Super Nintendo. Each cell is a 16-bit struct packing
int16_t height:5, slope:4, flow:4 and uint16_t mat:3. Reading the
signed fields fires G_SEXT_INREG — the sign-extend-in-register legalizer at
llvm-mos line 130 — expanding each field
read to a shift-pair. Ridges erode, valleys fill. The indigo valleys (signed negative heights)
prove sign-extension fires correctly. Compiler stress-test #78, the first signed-bitfield demo.
Self-running — ridges erode and valleys fill each frame.
Click the screen to focus, then watch. Tab away and it pauses.
What it is
A 16×16 grid of SBCell structs, each packing three signed bitfields into one
16-bit word. The height field (5 bits, range −16..15) drives the colour: indigo for deep
negative valleys, teal for shallow valleys, orange for low ridges, gold for high ridges.
Each frame, one erosion step reads all three signed fields per cell — triggering
G_SEXT_INREG three times each — and updates height and flow based on the signed slope.
// SBCell: 5+4+4+3 = 16 bits, one storage word.
typedef struct {
int16_t height : 5; // [-16..15] — G_SEXT_INREG on read
int16_t slope : 4; // [-8..7] — G_SEXT_INREG on read
int16_t flow : 4; // [-8..7] — G_SEXT_INREG on read
uint16_t mat : 3; // [0..7] — zero-extend (no sext)
} SBCell;
// One erosion step — three G_SEXT_INREG reads per cell:
int16_t h = cell->height; // sext_inreg(word, 5): shl(11) >> 11
int16_t s = cell->slope; // sext_inreg(word, 4): shl(12) >> 12
int16_t f = cell->flow; // sext_inreg(word, 4): shl(12) >> 12
if (s > 0) { h--; if (h < -16) h = -16; } // ridge erodes
else if (s < 0) { h++; if (h > 15) h = 15; } // valley fills
f = (f + s) >> 1; // signed running average The erosion is a classic cellular-automaton process: positive-slope cells wear down, negative-slope cells fill. The wavefront is visible as a moving boundary between gold ridges and indigo valleys.
Compiler stress-test
| Item | What it exercises |
|---|---|
| G_SEXT_INREG | Reading a signed bitfield back from a struct fires G_SEXT_INREG in GlobalISel. The MOS legalizer at line 130 lowers it with .lower(): sext_inreg(x, N) → (x << (16−N)) >> (16−N), using an arithmetic shift pair (ASL to position the sign bit, then arithmetic shift right to fill). For a 5-bit field: shl(11) + ashr(11). For a 4-bit field: shl(12) + ashr(12). |
| distinct from #29b and #52 | #29b truchet used uint16_t bitfields (orient:1/style:1/hue:3/phase:2/mark:1/energy:4) which zero-extend — no G_SEXT_INREG. #52 disbits used uint32_t bitfields — also zero-extend. Neither had an int16_t container, confirmed by grep. This demo is the first signed-bitfield path in the battery. |
| signed valleys as proof | A height value of −8 maps to colour 0 (indigo), while +0 maps to colour 2 (orange). If the compiler generated zero-extension instead of sign-extension, −8 stored as 5-bit 0b11000 would read back as +24 and map to colour 3 (gold), not colour 0. The indigo valleys visible on screen prove sign-extension is firing correctly. |
| erosion wavefront | Cells with positive slope erode (height decrements) and cells with negative slope fill (height increments). This creates a self-organising wavefront that sweeps through the grid: ridges wear down toward the floor, valleys fill toward the mean. After many frames the terrain converges toward a flat plain, making the motion a signature of the signed arithmetic working correctly. |
| integer-exact gate | 16 erosion steps × 16×16 cells × 3 signed field reads per cell = 12,288 G_SEXT_INREG operations. Any zero-extension (wrong read) shifts negative values positive, changing the erosion trajectory and diverging the CRC immediately. All-integer, no float. Disasm shows asl=41 (ASL for the shift-left half of each sign-extend pair). |
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 (gate CRC 0x40C5 —
16 steps × 16×16 cells, fold of signed height/slope/flow × mat) live in this tab.
No far pointers — host == default == +mos-a16 == +mos-xy16, -verify clean.