Sobelscope

Algorithms & Numeric

A signed 3×3 Sobel edge detector over a procedural animated field. The Gx/Gy gradients are signed multiply-accumulates; the edge magnitude |Gx| +sat |Gy| uses saturating arithmetic (__builtin_elementwise_add_satG_SADDSAT) so bright edges clamp instead of wrapping. Compiler stress-test #87.

loading core…

Self-running — brighter cells = stronger detected edges in the field.

Click the screen to focus, then watch. Tab away and it pauses.

What it is

A procedural pattern of rings and diagonal steps scrolls across a 32×32 field. Each cell runs a 3×3 Sobel convolution against its neighbours to estimate the local gradient; the magnitude is quantized to four brightness levels, so the moving step-edges light up as the detector tracks them.

// Signed 3x3 Sobel: Gx/Gy are signed MACs; magnitude uses SATURATING add.
int16_t Gx = (s02-s00) + 2*(s12-s10) + (s22-s20);   // signed multiply-accumulate
int16_t Gy = (s20-s00) + 2*(s21-s01) + (s22-s02);
int16_t mag = __builtin_elementwise_add_sat(abs(Gx), abs(Gy)); // G_SADDSAT (clamps, no wrap)
uint8_t m8  = min(mag >> 1, 255);
uint8_t out = __builtin_elementwise_sub_sat(m8, 16);           // G_USUBSAT (floor, no underflow)

The magnitude combine uses a saturating add so a strong edge can't wrap around to look flat — the kind of clamp real image kernels rely on. On the 65816 that lowers to the backend's saturating opcodes; the host oracle checks the results are bit-identical.

Compiler stress-test

ItemWhat it exercises
saturating arithmeticThe edge magnitude adds |Gx| and |Gy| with __builtin_elementwise_add_sat, which clamps at the type maximum instead of wrapping — a bright edge stays bright rather than overflowing to a dark value. The floor subtract uses __builtin_elementwise_sub_sat (clamps at 0). These lower to the backend's G_SADDSAT / G_USUBSAT saturating opcodes.
signed MACGx and Gy are signed weighted sums (multiply-accumulate) over the 3×3 window with the classic Sobel kernels. On a CPU with no MAC instruction this is a sequence of signed adds and shifts; the accumulator stays 16-bit signed throughout.
host vs targetThe host oracle (gcc) has no elementwise saturating builtins, so it uses a bit-identical manual clamp; the 65816 target (clang) uses the real intrinsics. The differential gate confirms both produce the same CRC — i.e. G_SADDSAT/G_USUBSAT are correct.
distinct from #57 medfilt#57 is a median filter — a min/max compare-exchange sorting network, no multiply-accumulate and no saturation. This is the MAC + saturating-arithmetic member of the battery.

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 (edge CRC 0x2849 — a fold of every Sobel magnitude in the field) live in this tab. No far pointers — host == default == +mos-a16 == +mos-xy16, -verify clean.