A whole boss fight in 256 bytes

Endbot is a 256-byte DOS intro that features a robot sprite with damage effects, explosions, and a MIDI soundtrack, showcasing extreme code optimization.
256-byte DOS intro · HellMood/Desire · Revision 2026
Endbot is a complete audio-visual demo that fits in exactly 256 bytes. It runs under DOS (via DosBox-X) and renders in real-time: a robot sprite with progressive bullet damage, a growing explosion, a scrolling checkerboard landscape, and a MIDI soundtrack - all from a single tiny .com file.
You need FASM (Flat Assembler). One command produces a raw binary with no linker step:
fasm endbot.asm endbot.com
The intro writes to MIDI port 0x330 for music. Use DosBox-X (not plain DOSBox) for its proper MPU-401 emulation.
[dosbox] machine=vgaonly memsize=4 [cpu] cputype=386 cycles=auto [midi] mpu401=uart mididevice=fluidsynth # use win32 on Windows, fluidsynth on Linux/macOS fluid.soundfont= # path to a GM .sf2, e.g. /usr/share/sounds/sf2/FluidR3_GM.sf2 [sblaster] sbtype=none [mixer] rate=44100
dosbox-x -conf dosbox-x.conf endbot.com
0xFF ) before exit so no notes hang open.; "Endbot" - 256b intro for DosBox-X ; presented at Revision 2026 ; by HellMood/Desire %define initbp 0x91C ; BP=Timer, use constant to ease calculations org 100h push 0xa000-20*4 ; VGA Memory Adress, slightly offset to beautify pop es ; into ES mov al,0x13 ; 13h : 320 * 200 Pixels in 256 Colors int 0x10 ; Set! mov cl,Nasenatmungsgeraeusch ; Dump the whole Code to MIDI until this point mov di,si ; Align Music Pointer with Pixel Pointer for 1st check
int 10h / AL=13h sets VGA mode 13h: 320×200, 256 colors, flat 64 000-byte framebuffer at A000:0000 . CL is loaded with the byte length of the combined music+sprite block - used by rep outsb on the first frame boundary to stream it all to MIDI port 0x330 in one shot.StartFrame: mov dx,0x330 ; Set MIDI Port sub di,si ; Advance Pixel by 4*n ( +1 by stosb is a nice dither) jnz StartPixel ; If not at the end of a frame, skip per-frame-stuff mov ax,bp ; sub al,initbp&0xFF ; hit four times, at time = 0, 256, 512, 768 jnz NoSound ; Output exactly four MIDI notes, except at start rep outsb ; CX is 4 except the first time! NoSound: mov al,1193182/256/30 ; PIT frequency / High Byte / FPS out 40h,al ; Set Timer to ~ 30 FPS hlt ; Sync against the timer inc bp ; Next time step xor byte [si-Nasenatmungsgeraeusch+Colors+1-8],cl ; angry flicker! in al,0x60 ; Key Code in AL sub al,2 ; Will leave 0xFF in AL on Exit! jc Quit ; ESC -> Stop Sounds -> END
40h then hlt pauses the CPU until the next hardware interrupt, locking to ~30 FPS. rep outsb streams CX bytes from [SI] to the MIDI port whenever BP mod 256 == 0 - four beat events over the demo's runtime; CX is 4 on beats 2–4 and the full block length on beat 1. The keyboard check reads scancode port 60h ; ESC has scancode 1, subtracting 2 leaves 0xFF with carry set → exit branch.StartPixel: mov ax,0xcccd ; The Rrrola trick to get X,Y in DL,DH mul di ; there we go mov ax,bp ; get timer in AX sub ax,(768+initbp) ; check if it's explosion time mov cx,dx ; "DrawSprite" gets x,y in DX as CX jc NoExplosion ; No explosion yet! (CF != ZF) jz Flash ; The very moment of impact, flash orange once
0xCCCD and reading the high-word gives approximate X (DL) and Y (DH) without a division instruction - 5 bytes of machine code vs 8+ for a proper divide+modulo. AX = BP − (768 + initbp) gives time-since-explosion: negative (CF set) = not yet; zero (ZF set) = exact impact frame; positive = expanding circle.sub ch,al ; After impact, move Bot down xchg bx,ax ; Save "time since impact" (TSI) in BL mov al,dh ; Y in AL sub al,77 ; center Y imul al ; YY sub bl,ah ; TSI - YY mov al,dl ; X in AL add al,128 ; center X imul al ; XX cmp bl,ah ; TSI - YY - XX = TSI - RR = Circle Flash: mov al,0x2a ; A beautiful orange value jz PlotPixel ; ON the circle -> plot orange jnl BackGround ; INSIDE -> dissolve into background js NoExplosion ; not yet exploded, and before 128 time steps after TSI Quit: ; (RET = Quit, top Stack = 0, [0] = int 20h = quit) out dx,al ; Either has 0xFF (silence) from above ... ret ; ... or outputs one trash byte after silence xD
X² + Y² = TSI defines the ring edge, growing as time increases. Pixels 0x2A ); pixels sub ch,al ). Exit uses ret : the stack top holds 0, and address 0 in a .com segment contains int 20h - the DOS "program terminate" call - saves 1 byte.NoExplosion: mov ax,bp ; get timer in AX cmp ax, 256+initbp ; first 256 time steps = no Sprite jl BackGround test al,32 ; mirror jnz NoAniFlip ; on not al ; x-axis NoAniFlip: and al,63 ; filter to last 6 bits add al,148 ; adjust X position of Sprite add cl,al ; timebased X zig zag movement of Sprite js BackGround ; inside signed byte range = Sprite sub ch,36+8 ; adjust Y position of Sprite js BackGround ; inside signed byte range = Sprite DrawSprite: ; Draw the 128x128 Pixel Sprite mov bx,Sprite ; Sprite data adress mov ax,bp ; Get timer in AX sub ax,(512+initbp) ; Time for damage already? jc SkipDamage ; AL = Time since shooting xor al,1010101b ; Pseudo Random Impact Location btc word [bx],ax ; Directly flip sprite bits SkipDamage: shrd ax,cx,18 ; SCALE and transfer of local X,Y test al,16 ; need to mirror? jz NoFlip ; mirror on y-axis not al ; if needed NoFlip: and ax,00011110b*256 + 00011110b ; filter : range&even xchg cx,ax ; just to get AX to CX add bl,ch ; offset Sprite to correct line mov ax,[bx] ; get the line data shr ax,cl ; shift down by (mirrored) column mov bl,Colors-1 ; CLUT -> 0 = transparent and al,3 ; set zero flag if "transparent" == 0 xlat ; looks up 3 Colors, discarded if 4th jnz PlotPixel ; if not transparent, directly PlotPixel
Colors . The zigzag horizontal movement comes from toggling bit 5 of the timer with test al,32 / not al . btc (bit-test-and-complement) flips one sprite bit per frame from frame 512 onward, accumulating visible bullet holes over time. xlat performs a 1-byte palette lookup: AL = [BX+AL] .BackGround: mov al,0x4e+72 ; a bluetiful color =) test dh,dh ; are we over the horizon? jns PlotPixel ; if yes, just plot the Blue sub dh,-(128+13) ; are we wrapped around to sky again!? js PlotPixel ; if yes, just plot the Blue mov ax,bp ; timer as depth, DIV DANGER div dh ; Constant / Y as distance in AL xchg dx,ax ; DL=distance AL=X add al,128 ; center X imul dl ; X' in AH add dx,bp ; distance += time (plane movement) xchg dx,ax ; DH= X' AL=distance' xor dh,al ; checker pattern imul dh ; distortion texture aam 9 ; irregular filter & range add al,212 ; offset to "landscape" colors PlotPixel: stosb ; FINALLY, plot the damn pixel! mov cx,4 ; for sound routines above and bot flicker jmp StartFrame ; Rinse and Repeat
div dh for a perspective depth value (BP/Y), perspective-corrects the X coordinate, then XORs for a scrolling checker pattern. The aam 9 (ASCII Adjust after Multiply) is a 2-byte modulo-9, providing irregular color banding across the landscape palette entries. stosb writes the final pixel to ES:[DI] and auto-increments DI; CX is restored to 4 for the music/flicker logic on the next frame boundary.Sprite: dw 0000111101110000b ; Pixels by Steffest / Desire dw 0111110111110100b ; he was so nice to anticipate dw 1111011111111100b ; i need reusable Sprite Data dw 0101111101111100b ; so he left me the following dw 0111111010110000b ; two bytes of actual MIDI data dw 1111111111110000b ; inside the middle of the Bot dw 1111111100000000b ; ************************************** dw 0111111111000000b ; 0xC07F, MIDI, set gunshot on channel 0 dw 1011011111110000b ; ************************************** dw 1011101111110000b ; this (too) is dumped to MIDI at start dw 1011101101111100b ; or is it a coincidence? dw 1011101110111100b ; a 1 in 65536 coincidence? dw 1011101110011111b ; Who knows ;) dw 0111011101111111b dw 1111111111111111b ; this gets modified by the code so ;dw 1111111111111011b ; this patch has to be applied 11 -> 10 dw 1111111111111100b MusicData: db 0xc9,56 ; set drum channel to SFX db 0x99 ; play on drum channel : db 70,0x58 ; helicopter, moderate db 81,0x7F ; wind
Source: Hacker News













