Analysis model: gpt-5.5 xhigh

Spam4kb by Tobo - Technical Dissection

Scope

This is a static binary dissection of Spam4kb by Tobo, released in 1994 for the Assembly 1994 PC 4K intro competition. The Assembly 1994 result file lists it as second place in the PC 4KB intro competition, between Stoned by Dust and Optimize by Feenix/Epical.

Public references:

The included README says the intro was coded in July 1994 for Assembly 1994, requires a register-compatible VGA card and at least a 386, and that source code was available. This pass uses the public binary rather than source. The README also includes old personal contact details; those are intentionally omitted.

Spam4kb is a packed 3903-byte MZ executable. The file is small enough to be entered in a 4K compo, but it is not a raw COM image like many intros in the same class. It starts with a PKLITE MZ wrapper, expands a 4075-byte runtime image into memory, applies six relocation fixes, then enters a compact 16-bit runtime that sets a timer IRQ, installs a tweaked VGA mode, builds generated geometry, creates a noisy subdivision buffer, and finishes with palette and span effects.

Examined Files

Archive contents:

FILE_ID.DIZ    308 bytes
README        1155 bytes
SPAM.EXE      3903 bytes

Hashes:

177e9dc768c869fc115cfdc486ef3c80fb5da965692b1ce5d3177bbc637f70c2  spam4kb.zip
0c73fd9de81617d1411661f9735d8c71d3a2a04da9f2f145fa76f413fff6536a  spam4kb.diz
0c73fd9de81617d1411661f9735d8c71d3a2a04da9f2f145fa76f413fff6536a  FILE_ID.DIZ
0f05992ff8973751e0754980a67ecc0e8e6c4bd39ae506a6267bb9e4758d56a1  README
454a4635362b175bf97b12682ec1f989645c36e226833a7e66d98d7b7afd1224  SPAM.EXE

Runtime Captures

For visual reference frames, I ran the packed executable under DOSBox-X with a delayed Esc key as a safety stop:

autotype -w 90 -p 0.1 esc
dx-capture /v SPAM.EXE

The captured video ran for 31.832922 seconds. Two frames cover the useful visual range: the initial generated geometry and the later green noisy field.

At 00:05.000, the intro is in the bright checker/arc geometry part:

Spam4kb sparse runtime phase sequence

Spam4kb runtime frame at 00:05.000 showing the bright checker and arc geometry

At 00:23.000, the visible state has shifted to the green noise/subdivision field:

Spam4kb runtime frame at 00:23.000 showing the green noisy field

The GIF above is a sparse two-frame phase comparison assembled directly from the 720x400 DOSBox-X runtime frames. It shows the initial checker/arc geometry and the later green noise/subdivision field.

Runtime-To-Code Concordance

The two captured phases line up with two different halves of the unpacked intro: first the generated object/depth-list pass, then the noisy subdivision and span pipeline. The GIF is sparse, but it is enough to separate those code paths.

The 00:05.000 checker/arc frame is downstream of the runtime entry path:

00dc  call 005fh    ; setup: timer, VGA, palette, segment registers
00df  call 042bh    ; scripted object/depth passes and palette steps

005fh has already installed the local int-8 handler, set mode 13h, altered VGA timing through CRTC writes, uploaded the green/cyan/high-color DAC ramps, and kept GS = A000h for screen writes. Frame pacing is not a DOS delay loop: 0125h waits for the flag set by the 70 Hz-ish timer handler at 0145h, then increments the frame counter in DS:0000.

The visible arcs and checker-like projected geometry are generated by the scripted pass at 042bh. Each script clause writes constants such as 02ad, 02af, 02e7, and 02e9, then calls 0220h to build a 160 by 120 object field. The builder loops signed coordinates from roughly x=-80..79 and y=-60..59, computes an approximate radius with 010ch, projects the point, assigns a one-bit-ish color class, and writes an immediate coarse two-pixel mark to VGA:

036a  mov di,[02b5h]
036e  mov al,[02b2h]
0371  or  al,80h
0373  mov ah,al
0375  mov gs:[di],ax

At the same time, 0220h builds short sorted entries in the FS work segment. Those entries are consumed by the depth scanner at 0160h. The scanner waits for a timer frame, compares each cell head against the threshold at 02adh, draws a duplicated pixel word when the entry is ready, and pops list nodes until the 7fffh terminator:

0195  mov ax,[si]
0197  and al,0fh
0199  or  al,80h
019b  mov ah,al
01a4  mov gs:[bx],ax
01a8  mov ax,[si+2]
01ab  mov [si],ax          ; pop next list entry

That explains why the first capture looks like a constructed lattice rather than a bitmap. The object image is generated, sorted into compact per-cell lists, and revealed by a threshold scan.

The fade and strip behavior around that phase is handled by 01bbh, 01d5h, and 06b9h. 01bbh increments 27 high-palette DAC bytes at index 80h; 01d5h changes DAC entry 25h from the frame counter and clears mirrored columns; 06b9h fills most visible VGA memory with color 25h. These routines are the visual glue between the object pass and the later green field.

The 00:23.000 green noisy field belongs to the second half of the runtime:

00e2  call 06cbh    ; clear work segment 3023h
00e5  call 06e8h    ; noisy 128x128 subdivision / resample pass
00e8  call 09f8h    ; source-mask/table build for later spans
00eb  call 0be4h    ; final morph/span loop

06e8h/06f1h is a compact midpoint-subdivision kernel. For each 128 by 128 cell it reads four corner samples, stores the top-left value, and computes the top edge, left edge, and center by averaging neighbors and adding the pseudo- random term returned in BP by 00f1h:

071b  push bx
071c  add bx,ax
071e  sar bx,1
0720  call 00f1h
0723  add bx,bp
0725  mov fs:[di+1],bl

That is the source of the noisy surface in the later capture. It is not a stored texture; it is generated by repeated corner refinement, with the second seed at 0004h reduced near the low refinement levels so the roughness changes over time.

07bdh then performs a skewed resample using 027bh as a phase seed. It self-patches a word near 06c9h, advances SI by fractional carry, and copies from the refined buffer into another work area. The final visible enlargement comes from the span writer at 08f6h..09c2h. Its hot loop reads one source byte, duplicates it into a word, and writes the same sample to five rows:

0980  mov al,[si]
0982  inc si
0984  mov ah,al
0986  mov es:[bx],ax
0989  mov es:[bx+0100h],ax
098e  mov es:[bx+0200h],ax
0993  mov es:[bx+0300h],ax
0998  mov es:[bx+0400h],ax

The add cx,1234h and add dx,1234h operands in that loop are patched from outer fixed-point slopes, so the inner path avoids multiplies. The final 0be4h loop repeatedly calls the morph update at 083fh and this span setup, which is why the later green phase reads as a moving field rather than a single static subdivision result.

So the visible runtime organization is: PKLITE expands the real intro, 005fh sets a fast timer/VGA/DAC base, 042bh/0220h/0160h generate and reveal the bright object lattice, palette routines fade and clear between parts, and 06e8h/07bdh/08f6h/0be4h generate the noisy green span field. The compact 4K trick is not one magic renderer; it is several tiny kernels sharing buffers, timer pacing, self-patched operands, and direct VGA writes.

MZ Header

The executable header has the usual MZ signature plus a PKLITE marker string near the beginning of the file. Important header fields are:

e_cblp        013fh
e_cp          0008h
e_cparhdr     000ah
e_minalloc    2f1fh
e_maxalloc    ffffh
e_ss:e_sp     00f4h:0200h
e_cs:e_ip     fff0h:0100h
e_lfarlc      0052h
relocations   1
header bytes  160
file bytes    3903

The negative e_cs value is important. DOS loads the EXE image after the PSP, then adds fff0h paragraphs to the image segment for entry. That lands execution at PSP:0100h, which is the same physical address as image offset zero. In practice the packed MZ behaves like a COM-style loader while still getting EXE relocation support.

PKLITE Layer

The packed entry begins at runtime PSP:0100h. It performs a memory check, then copies a 0x188-byte depacker upward and far-returns into the copy:

0100  mov ax,3004h
0103  mov dx,00eah
0106  cmp ax,[0002h]       ; PSP top-of-memory segment
010a  jb  enough_memory
010c  mov ah,09h
010e  int 21h              ; small "not enough memory" path
0110  int 20h

0112  sub ax,0020h
0115  mov ss,ax
0117  sub ax,0019h
011a  mov es,ax
011c  push ax
011d  mov cx,00c4h
0120  xor di,di
0122  push di
0123  mov si,0144h
0126  rep movsw
0128  retf                 ; run copied depacker at highseg:0000

The copied depacker is a PKLITE LZ stream reader. The bitstream shape is very similar to the one in Stoned, but here it lives inside a normal PKLITE MZ shell:

bit 0       literal byte copied to output
bit 1       match copied from earlier output
length      decoded through a small table in the copied depacker
distance    decoded through a second table
end marker  special length code plus ffh escape byte

Translated execution of the depacker produced:

expanded runtime bytes       4075
literal commands             2084
match commands               550
total stream commands        2634
relocation fixes             6
final SS:SP                  image+2013h:0100h
final CS:IP                  image:00dch

The relocation tail reads fixup records from the stream after the LZ data. For each fixup it adds the image segment to a word inside the expanded payload. It then restores the application stack, pushes the final CS:IP, clears scratch registers, and uses retf to enter the real intro at image offset 00dch.

This wrapper matters for analysis because direct disassembly of SPAM.EXE is mostly the generic PKLITE loader and compressed bytes. The actual intro starts only after expansion.

Runtime Entry

The real entry is a short call chain:

00dc  call 005fh    ; setup: timer, VGA, palette, segment registers
00df  call 042bh    ; scripted object/depth passes and palette steps
00e2  call 06cbh    ; clear work segment 3023h
00e5  call 06e8h    ; noisy 128x128 subdivision / resample pass
00e8  call 09f8h    ; source-mask/table build for later spans
00eb  call 0be4h    ; final morph/span loop
00ee  call 0038h    ; restore timer and text mode, exit through DOS

The runtime uses several fixed segments:

CS          unpacked code/data image
DS, ES      10e0h for main variables and tables
FS          2023h / related work buffers
GS          a000h VGA memory
3023h       cleared scratch/image work area

Those segment constants look odd, but they are not random. The intro relies on paragraph arithmetic to make byte offsets wrap into useful screen and work buffers without spending bytes on full address calculations.

Setup at 005f

The setup code saves the original timer interrupt, installs its own IRQ0 handler, reprograms the PIT, switches to VGA mode 13h, writes a small CRTC timing table, builds palette ramps, and keeps GS = a000h for direct screen writes.

005f  push cs
0060  pop ds
0061  cld
0062  mov ax,3508h
0065  int 21h              ; get INT 8 vector
0067  mov [0001h],bx
006b  mov [0003h],es
006f  mov ah,25h
0071  mov dx,0145h
0074  int 21h              ; set INT 8 to local handler

      mov dx,0043h
0079  mov al,34h
007b  out dx,al            ; PIT channel 0, lobyte/hibyte, mode 2
007c  mov dl,40h
007e  mov al,95h
0080  out dx,al
0081  mov al,42h
0083  out dx,al            ; divisor 4295h

0084  mov ax,10e0h
0087  mov ds,ax
0089  mov ax,0013h
008c  int 10h              ; 320x200x8bpp base mode

The PIT divisor is 4295h (17045 decimal). A normal PC PIT clock divided by that value is about 70 Hz. The handler then chains to the old BIOS tick only when an accumulator overflows, so the intro gets a faster frame pulse without losing the normal 18.2 Hz DOS clock.

The VGA register section is dense:

008e  mov dx,03c4h
0091  mov dx,03c2h
0094  mov al,0e3h
0096  out dx,al
0097  mov dx,03d4h
009a  mov si,0006h
009d  mov cx,0009h
00a0  rep outsw            ; nine packed CRTC index/value words

The 03c4h load is immediately overwritten, so the live write is to VGA Misc Output at 03c2h. The following outsw loop writes words to 03d4h; on VGA that sends the low byte to the CRTC index port and the high byte to the data port. The table starts at data offset 0006h and contains nine index/value pairs. This is a byte-cheap way to alter timing after BIOS mode 13h.

Palette initialization follows immediately. First 64 DAC entries form a green ramp:

00a2  mov dx,03c8h
00a5  xor ax,ax
00a7  out dx,al            ; start at DAC index 0
00a8  inc dl               ; 03c9h
00aa  xor bx,bx
00ac  mov cx,0040h
00af  out dx,al            ; R = 0
00b0  mov al,bl
00b2  out dx,al            ; G = 0..63
00b3  mov al,bh
00b5  out dx,al            ; B = 0
00b6  inc bl
00b8  loop 00afh

The next 64 entries form a cyan/blue ramp with half green:

00ba  xor bl,bl
00bc  mov cx,0040h
00bf  out dx,al            ; R = 0
00c0  mov al,bl
00c2  shr al,1
00c4  out dx,al            ; G = n / 2
00c5  mov al,bl
00c7  out dx,al            ; B = n
00c8  mov al,bh
00ca  inc bl
00cc  loop 00bfh

Finally it sends 27 bytes from DS:01ac to the DAC and sets GS to VGA memory:

00ce  mov si,01ach
00d1  mov cx,001bh
00d4  rep outsb
00d6  mov ax,0a000h
00d9  mov gs,ax
00db  ret

Timer and Exit

The frame wait helper at 0125h polls a flag set by the IRQ handler. The flag is at CS:0000; the frame counter is at DS:0000.

0125  cmp byte [cs:0000h],0
012b  je  0125h
012d  mov byte [cs:0000h],0
0133  inc word [0000h]     ; DS frame counter
0137  mov ah,01h
0139  int 16h              ; key available?
013b  je  0144h
013d  mov ah,00h
013f  int 16h
0141  jmp 0038h            ; exit on key
0144  ret

The handler itself is only 23 bytes:

0145  mov byte [cs:0000h],1
014b  add word [cs:0005h],4295h
0152  jb  chain_old_timer
0154  push ax
0155  mov al,20h
0157  out 20h,al           ; EOI
0159  pop ax
015a  iret
015b  jmp far [cs:0001h]   ; original INT 8 vector

The add [cs:0005],4295h accumulator is the clock divider. Most fast timer ticks only set the intro frame flag and acknowledge the PIC. When the 16-bit accumulator wraps, execution jumps to the saved BIOS timer handler.

Exit restores the PIT to the BIOS divisor, restores the old INT 8 vector, switches to text mode, and exits through DOS:

0038  mov dx,0043h
003b  mov al,34h
003d  out dx,al
003e  mov dl,40h
0040  xor al,al
0042  out dx,al
0043  out dx,al            ; divisor 0000h means 65536
0044  mov ax,2508h
0047  mov dx,[cs:0003h]
004c  mov ds,dx
004e  mov dx,[cs:0001h]
0053  int 21h
0055  mov ax,0003h
0058  int 10h
005a  mov ax,4c00h
005d  int 21h

Small Math Helpers

The pseudo-random helper at 00f1h is tiny but important because it is called inside the subdivision loop. It keeps two 16-bit seeds in data offsets 0002h and 0004h:

00f1  push ax
00f2  push dx
00f3  mov ax,[0002h]
00f6  mov dx,4217h
00f9  imul dx
00fb  xor ax,1974h
00fe  mov [0002h],ax
0101  mov dx,[0004h]
0105  imul dx
0107  mov bp,dx
0109  pop dx
010a  pop ax
010b  ret

The first seed gets multiplied by 4217h and xored with 1974h; the second seed is multiplied into it, and the high word lands in BP. The caller adds BP to midpoint values, so this helper supplies the noise term for the fractal subdivision.

The helper at 010ch is an integer square-root approximation:

010c  mov bx,ax
010e  mov dl,40h
0110  xor cx,cx
0112  xor cl,dl
0114  mov al,cl
0116  mul al
0118  cmp ax,bx
011a  jle keep_bit
011c  xor cl,dl
011e  shr dl,1
0120  jne 0112h
0122  mov si,cx
0124  ret

It tries bits from 40h downward. At each step it squares the candidate in CL; if the square is too large, it removes that bit. The result in SI is a radius index. The object builder uses it to map an (x,y) pair to radial lookup tables without a divide-heavy true square root.

Scripted Object Pass at 042b

The 042b region is best read as a small inline script. It is not a clean subroutine sequence; it uses code bytes, timer counters, and action records in the same stream. A repeated pattern is visible:

042b  add word [di],ffffh
0430  jne next_action
0432  mov [02adh],c000h
0438  mov [02afh],01c7h
043e  mov [02e7h],0400h
0444  mov [02e9h],0000h
044a  call 0220h
044d  add word [di],7fffh
0451  ret

Later actions do the same with different constants:

object 1: 02ad=c000h, 02af=01c7h, 02e7=0400h, 02e9=0000h, call 0220h
scan 1:   02ad += 0084h, call 0160h
object 2: 02ad=be00h, 02af=0203h, 02e7=0200h, 02e9=0000h, call 0220h
scan 2:   02ad += 0090h, call 0160h
object 3: 02ad=d000h, 02af=023fh, 02e7=fc00h, 02e9=0300h, call 0220h
scan 3:   02ad += 0080h, call 0160h
palette:  call 01bbh, call 0125h
fade:     call 01d5h, call 06b9h / call 0125h

7fffh is the local "done" marker. A clause that has fired adds this value to the record word so the next pass skips it. That saves a separate state table.

Object Builder at 0220

0220h builds a 160 by 120 field. It uses signed coordinate loops:

0231  mov [02bfh],ffc4h    ; y starts at -60
0237  mov [02bdh],ffb0h    ; x starts at -80
...
037b  inc word [02bdh]
037f  cmp word [02bdh],0050h
0386  jmp 023dh            ; x until +79
0389  add di,00c0h
038d  inc word [02bfh]
0391  cmp word [02bfh],003ch
0398  jmp 0237h            ; y until +59

For each grid point it computes an approximate radius:

0242  mov ax,[02bfh]
0245  imul word [02bfh]    ; y*y
0249  mov cx,ax
024b  mov ax,[02bdh]
024e  imul word [02bdh]    ; x*x
0252  add ax,cx
0254  call 010ch           ; SI = approx sqrt(x*x+y*y)
0257  mov [02c1h],si

If the radius is zero, the point is skipped. Otherwise the radius selects table entries and the code projects the current (x,y) into two derived components:

0262  add si,00e2h
0266  mov bx,[si]
026a  mov dx,[si-00cah]
026e  mov [02bbh],dx

0272  imul word [02bdh]
0276  idiv word [02c1h]
027a  mov [02b7h],ax       ; projected x-ish term

027f  mov ax,bx
0281  imul word [02bfh]
0285  idiv word [02c1h]
0289  neg ax
028b  mov [02b9h],ax       ; projected y-ish term

Then it rejects points outside a small projected window:

0290  cmp bx,0010h
0293  jle skip
02ad  cmp dx,000dh
02b0  jge skip
02b2  cmp dx,fff3h
02b5  jl  skip
02b7  cmp ax,0016h
02ba  jge skip
02bc  cmp ax,fffeh
02bf  jl  skip

Accepted points get a small color class in 02b2h:

02c1  shr ax,1
02c3  shr dx,1
02c5  adc ax,dx
02c7  and al,1
02c9  inc al               ; color class 1 or 2
02cb  mov [02b2h],al

The next block builds short ordered edge lists. It uses three transform rows at 0299h, reads point records from the table selected by 02afh, calls the clip/intersection helper at 0507h, and writes a compact pair list at 02c7h and 02d7h. After sorting those small lists through the helper near 0007h, it copies the visible entries into FS and terminates each list with 7fffh:

0409  mov si,02c7h
040c  lodsw
040d  mov fs:[di],ax
0410  add di,2
0413  dec cx
0414  jne 040ch
0416  mov ax,[si+000eh]
0419  and al,0f0h
041b  or  al,[02b2h]
041f  mov fs:[di],ax
0422  add di,2
0425  mov word fs:[di],7fffh

The visible framebuffer also gets a direct two-pixel write for the current grid cell:

036a  mov di,[02b5h]
036e  mov al,[02b2h]
0371  or  al,80h
0373  mov ah,al
0375  mov gs:[di],ax
0378  add di,2

That gives the intro an immediate coarse image while the linked lists in FS are ready for the depth scanner.

Depth Scanner at 0160

The scanner at 0160h consumes the lists produced by 0220h. It waits for a timer frame, reads a threshold from 02adh, then scans a 160 by 120 field in segment 2023h.

0160  call 0125h
0163  mov dx,[02adh]       ; threshold
0167  push ds
0168  mov bx,2023h
016b  neg bx
016d  mov [cs:01a0h],bx    ; self-patched screen base delta
0172  neg bx
0174  xor si,si
017a  mov bp,0078h         ; 120 rows
017d  mov cx,00a0h         ; 160 columns
0180  mov ds,bx
0182  cmp [si],dx
0184  jle draw_one

If the head value for a cell is deeper than the threshold, it advances to the next cell. If it is in range, it draws a word to VGA and advances the linked list head:

0195  mov ax,[si]
0197  and al,0fh
0199  or  al,80h
019b  mov ah,al
019d  push bx
019e  add bx,1234h         ; operand patched at 01a0h
01a2  add bx,bx
01a4  mov gs:[bx],ax
01a7  pop bx

01a8  mov ax,[si+2]
01ab  mov [si],ax          ; pop next list entry
01ad  cmp ax,7fffh
01b0  je  list_done
01b2  add si,2
01b5  jmp 01a8h

This is a compact painter pass: object generation prepares depth-sorted cell lists, and the scanner reveals entries as the threshold increases. The inner loop cost is low: compare the current word, maybe draw one duplicated pixel word, pop list entries until 7fffh, then step to the next cell.

Palette Routines

01bbh animates 27 DAC bytes starting at palette index 80h:

01bb  mov si,01ach
01be  mov cx,001bh
01c1  mov dx,03c8h
01c4  mov al,80h
01c6  out dx,al
01c7  inc dx
01c8  inc byte [si]
01ca  lodsb
01cb  cmp al,3fh
01cd  jbe send
01cf  mov al,3fh
01d1  out dx,al
01d2  loop 01c8h

It increments each source byte before sending it, clamps at 3fh, and writes to the DAC data port. That is the fade-up path for the high palette entries.

01d5h writes one DAC color at index 25h using the frame counter, then clears symmetrical screen columns while the counter is in a small range:

01d5  mov dx,03c8h
01d8  mov al,25h
01da  out dx,al
01db  inc dx
01dc  mov ax,003fh
01df  sub ax,[0000h]
01e3  out dx,al
...
01fb  xor ah,ah
01fd  mov si,ax
01ff  mov di,009fh
0202  sub di,si
0204  add si,si
0206  add di,di
0208  mov cx,0078h
020b  mov word gs:[si],0
0210  mov word gs:[di],0
0215  add si,0200h
0219  add di,0200h
021d  loop 020bh

Because mode 13h rows are 320 bytes, adding 0200h jumps two scanlines at a time. The two mirrored word writes clear a pair of vertical columns.

Work Clears

There are two clears. 06b9h clears visible VGA memory through GS:

06b9  push es
06ba  push gs
06bc  pop es
06bd  mov ax,2525h
06c0  xor di,di
06c2  mov cx,7800h
06c5  rep stosw
06c7  pop es
06c8  ret

7800h words equals 61440 bytes. That is nearly the whole 64K VGA aperture, using color 25h in both bytes.

06cbh clears segment 3023h:

06cb  xor al,al
06cd  mov dx,3023h
06d0  mov es,dx
06d2  xor di,di
06d4  mov ax,2525h
06d7  mov cx,8000h
06da  rep stosw

That is a full 64K work clear. The following bytes are not a clean linear subroutine; the next public entry, 06e8h, lands in compact setup bytes that lead into the stable subdivision loop around 06f1h.

Noisy Subdivision at 06e8/06f1

The most interesting early visual kernel is the midpoint subdivision loop. The clean inner loop starts around 06f1h. It operates over a 128 by 128 region, reading four source corner samples from ES and writing an expanded two by two cell to FS.

At the start of each row:

06f1  mov cx,0080h
06f4  mov si,4040h
06f6  xor di,di
06f8  push cx             ; outer row count is also kept on stack
06f9  mov cx,0080h        ; 128 cells in this row
06fc  push cx

For each cell it fetches the four corners:

06fd  mov al,es:[si+0101h]
0702  cbw
0703  mov dx,ax           ; bottom-right
0705  mov al,es:[si+0100h]
070a  cbw
070b  mov cx,ax           ; bottom-left
070d  mov al,es:[si+0001h]
0711  cbw
0712  mov bx,ax           ; top-right
0714  mov al,es:[si]
0717  cbw                 ; top-left in AX

Then it writes the top-left sample unchanged:

0718  mov fs:[di],al

The top edge midpoint is average(top-left, top-right) plus a pseudo-random offset:

071b  push bx
071c  add bx,ax
071e  sar bx,1
0720  call 00f1h
0723  add bx,bp
0725  mov fs:[di+1],bl

The left edge midpoint is average(top-left, bottom-left) plus noise:

0729  add cx,ax
072b  push cx
072c  sar cx,1
072e  call 00f1h
0731  add cx,bp
0733  mov fs:[di+0100h],cl

The center point is average of all four corners plus noise:

0738  pop cx
0739  pop bx
073a  add dx,bx
073c  add dx,cx
073e  sar dx,2
0741  call 00f1h
0744  add dx,bp
0746  mov fs:[di+0101h],dl

The cell step is byte-small:

074b  inc si
074f  pop cx
0750  dec cx
0751  jne 06fch

That loop is doing diamond-square style refinement, but in a highly compact form. It never calls a general interpolation routine. Each midpoint is built in registers, jittered by the PRNG return in BP, and stored directly in the next buffer.

Every fourth row-ish pass it calls 07bdh, then scrambles a phase seed:

075d  test cx,0003h
      jne skip_resample
      push si
      push di
      call 07bdh
      mov ax,[027bh]
      mov dx,0fa88h
      mul dx
      mov [027bh],dx
      pop di
      pop si

Near the end of each subdivision level it calls the PRNG again and, for low level counts, decreases the second seed at 0004h by three:

077e  call 00f1h
0781  cmp word [027dh],0006h
0786  ja  keep_seed
078a  sub word [0004h],0003h
079e  dec word [027dh]
07a2  jne 06e8h

So the visual roughness changes as the refinement level drops. This is why the effect has a living, noisy surface instead of a static bilinear zoom.

Resample Pass at 07bd

07bdh is a skewed copy/resample helper. It uses the seed at 027bh to compute two fixed-point starting values, self-patches the word at 06c9h, and then walks 128 rows.

The setup:

07c0  mov ax,[027bh]
07c3  push ax
07c4  neg ax
07c6  push ax
07c7  mov dx,0080h
07ca  mul dx
07cc  add ax,8000h
07cf  adc dx,0
07d2  mov [cs:06c9h],ax
07d6  mov ax,0100h
07d9  mul dx
07db  mov si,ax

07dd  pop ax
07de  mov dx,0080h
07e1  mul dx
07e3  add ax,8000h
07e6  adc dx,0
07e9  mov bp,ax
07eb  add si,dx
07ed  pop dx
07ee  add dx,dx

The hot loop uses BX as the fractional accumulator and increments SI by the carry from that accumulator:

07f1  mov di,0f820h
07f4  mov cx,0080h
07f7  push es
07f8  pop ds
07f9  push cx
07fa  push si
07fb  mov bx,bp
0800  mov al,[si]
0802  add bx,dx
0804  adc si,1
...
0816  add bx,dx
0818  adc si,1

Some bytes after this point are operands and self-patched fragments that a linear disassembler reads badly. The reliable behavior is the address math: BP and DX form a fractional source step, SI advances by carry, and the destination starts near f820h. This is the cheap texture/height copy pass inside the noisy surface effect.

Span Preparation and 5-Row Writer

09f8h starts another mixed code/data area. The first stable intent is a copy from one buffer into two tables around 532fh and 932fh: one table holds source intensity bytes, and the second table is used as a per-column vertical offset or mask. Those tables feed the span writer at 08f6h..09c2h.

08f6h computes first and second differences from three point pairs:

08f6  mov ax,[0311h]
08f9  sub ax,[0315h]
08fd  mov [031fh],ax

0900  mov bx,[030dh]
0904  mov cx,[0309h]
0908  sub bx,cx
090a  sub cx,[0315h]
090e  sar cx,5
0911  mov [031dh],cx
0915  sub bx,ax
0917  sar bx,7
091a  mov [0321h],bx

The same shape repeats for the other coordinate using 0313h, 0317h, 030fh, and 030bh, producing 0329h, 0327h, and 032bh. This is a tiny second-order interpolator: each outer line step updates position and slope, and each inner pixel step uses self-patched increments.

The vertical span writer is the clearest hot loop:

095a  mov si,532fh
095d  mov di,932fh
0960  mov bp,0080h

0963  push ax
0964  push bx
0965  push cx
0966  push dx
0967  push bp
0968  sar ax,5
096b  mov [cs:099fh],ax    ; patch inner x step
096f  sar bx,5
0972  mov [cs:09a7h],bx    ; patch inner y step
0977  mov bp,0080h

Each of the 128 inner steps maps the current fixed-point position to BX, gets one source byte, duplicates it into a word, and writes it to five rows:

097a  mov bh,dh
097c  mov bl,ch
097e  sub bh,[di]
0980  mov al,[si]
0982  inc si
0983  inc di
0984  mov ah,al
0986  mov es:[bx],ax
0989  mov es:[bx+0100h],ax
098e  mov es:[bx+0200h],ax
0993  mov es:[bx+0300h],ax
0998  mov es:[bx+0400h],ax

The step instructions are self-patched:

099d  add cx,1234h         ; operand patched from AX >> 5
09a6  add dx,1234h         ; operand patched from BX >> 5
09a9  dec bp
09aa  jne 097ah

After one 128-pixel span, the saved outer values are restored and the second differences are applied:

09ac  pop bp
09ad  pop dx
09ae  pop cx
09af  pop bx
09b0  pop ax
09b1  add cx,[031dh]
09b5  add dx,[0327h]
09b9  add ax,[0321h]
09bd  add bx,[032bh]
09c1  dec bp
09c2  jne 0963h

This is a classic tiny intro span kernel: no multiply in the inner path, no address helper call, just table bytes, self-patched fixed-point adds, and five row writes per sample. The five-row duplication turns 128 samples into a much taller visible shape at very low byte cost.

Wave Table Builder at 0a80

The block around 0a80h uses 32-bit 386 instructions inside otherwise 16-bit code. It builds a symmetric table using a small polynomial-ish helper at 0c38h.

The helper:

0c38  mul esi              ; EDX:EAX = EAX * ESI
0c3b  shr eax,0fh
0c3f  mul esi
0c42  shr eax,0fh
0c46  idiv ebx
0c49  ret

The caller walks a word table, computes a base value from CX, then applies four weighted terms:

0a80  mov ebx,031dc910h
0a86  xor eax,eax
0a89  mov ax,cx
0a8b  mul ebx
0a8e  shr eax,0bh
0a92  mov esi,eax
0a95  mov ebp,eax
0a98  xor bx,bx

0a9a  mov bl,06h
0a9c  call 0c38h
0a9f  sub ebp,eax
0aa2  mov bl,14h
0aa4  call 0c38h
0aa7  add ebp,eax
0aaa  mov bl,2ah
0aac  call 0c38h
0aaf  sub ebp,eax
0ab2  mov bl,50h
0ab4  call 0c38h
0ab7  add ebp,eax

The result is written symmetrically:

0aba  mov [di+032dh],bp
0abe  mov [di+432dh],bp
0ac2  neg di
0ac4  mov [di+232dh],bp
0ac8  neg bp
0aca  mov [di+432dh],bp
0ace  neg di
0ad0  mov [di+232dh],bp

That table is later used by the morph/span code as a curved displacement or profile. The byte-level trick is using 386 EAX/EBX/ESI/EBP math to create a wide fixed-point function while keeping the surrounding intro in compact 16-bit addressing.

Buffer Add and Pair Scaling

The block at 0af1h walks bytes from 532fh, computes a signed difference around 20h, doubles it, and adds it into a buffer near 40fbh.

0aeb  mov si,532fh
0aee  mov di,40fbh
0af1  mov al,20h
0af3  sub al,[si]
0af5  add al,al
0af7  jc  negative_case
0afc  add [di],al
0b16  inc si
0b17  inc di
0b18  dec cx
0b19  jne 0af1h

0b09  neg al
0b0b  add fs:[di],al
0b16  inc si
0b17  inc di
0b18  dec cx
0b19  jne 0af1h

The nearby bytes include compact branch targets and data-like fragments, so the exact failure/saturation path is awkward in a linear disassembly. The main loop is clear: convert source brightness to a signed delta and accumulate it into one of two destinations.

The pair-scaling kernel at 0bbah is clearer. It scales coordinate pairs by a factor in CX, adds a center bias through DH += 20h, and writes the high word back:

0bb5  sar cx,1
0bb7  add ch,60h
0bba  mov ax,[bp+0]
0bbe  imul cx
0bc0  add dh,20h
0bc3  mov [bp+0],dx
0bc7  mov ax,[bp+2]
0bcb  imul cx
0bcd  add dh,20h
0bd0  mov [bp+2],dx
0bd4  add bx,4
0bd7  add bp,4
0bda  pop cx
0bdb  dec cx
0bdc  je  done

This is a compact 2D transform for a list of word pairs. The input words are treated as signed fixed-point values; after imul, DX contains the high part of the product. Adding to DH recenters the result into screen-ish byte space.

The final stage at 0be4h repeatedly calls the morph update and span setup:

0be4  call 083fh
0be7  call 08f6h
...
0bf3  jbe 0be4h
0bf5  ret

The bytes between 0bea and 0bf3 are another mixed code/data pocket; the two stable calls are the important part. 083fh adjusts a set of table words by 10h or 18h, calls the wave/buffer work, and then reaches the pair-scaling kernel. 08f6h turns the updated points into spans.

Why It Looks Larger Than 4K

Spam4kb gets a lot of visual variety from a few compact ideas:

The result is not one single effect. It is a sequence of tiny kernels sharing buffers: generated object lists, palette changes, fractal/noise refinement, skewed resampling, wave table math, and duplicated-row span drawing.