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:


At 00:23.000, the visible state has shifted to the green noise/subdivision
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:
- PKLITE supplies a 4075-byte runtime from a 3903-byte MZ file.
- The timer handler is only large enough to set a frame flag and preserve BIOS time through an accumulator.
- VGA setup begins with BIOS mode 13h but alters timing and palettes directly.
- Object data is generated into per-cell linked lists, then revealed by a threshold scanner.
- The noisy subdivision kernel expands four corner samples into a new 2x2 block with PRNG jitter in the inner loop.
- The later span writer uses self-patched fixed-point adds and five duplicated row writes per sample.
- Several regions intentionally overlap code, operands, table bytes, and action records. A linear disassembler makes parts of the file look nonsensical, but the hot loops are small and deliberate.
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.