https://hellmood.111mb.de//A_whole_boss_fight_in_256_bytes.html Endbot Animated Intro Endbot 256-byte DOS intro * HellMood/Desire * Revision 2026 pouet.net thumbnail / pouet.net YouTube thumbnail > YouTube | endbot.zip sizecoding wiki What is this? 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. Build - ASM - COM You need FASM (Flat Assembler). One command produces a raw binary with no linker step: fasm endbot.asm endbot.com Run - DosBox-X Setup The intro writes to MIDI port 0x330 for music. Use DosBox-X (not plain DOSBox) for its proper MPU-401 emulation. dosbox-x.conf [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 Press ESC to quit. The code sends MIDI reset (0xFF) before exit so no notes hang open. Code Walkthrough 1 - File Header & Init ; "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 initbp is the time value already present when the program starts for size reasons. All timing is relative to it, so animation is consistent regardless of DosBox-X startup time. int 10h / AL=13h sets VGA mode 13h: 320x200, 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. 2 - Main Loop & Per-Frame Stuff 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 The outer loop runs once per pixel. DI is the pixel counter; when it wraps to zero (every 65 536 iterations = one full 320x200 frame) the per-frame block executes. Timer sync: writing to PIT port 40h then hlt pauses the CPU until the next hardware interrupt, locking to ~30 FPS. MIDI beats: 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. 3 - Per-Pixel: X/Y Decode & Explosion Check 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 The Rrrola trick: multiplying the linear pixel index DI by 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. 4 - Explosion Circle, Flash & Exit 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 ; Y*Y sub bl,ah ; TSI - Y*Y mov al,dl ; X in AL add al,128 ; center X imul al ; X*X cmp bl,ah ; TSI - Y*Y - X*X = TSI - R*R = 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 The explosion is a mathematically clean expanding circle: X2 + Y2 = TSI defines the ring edge, growing as time increases. Pixels on the edge are plotted orange (0x2A); pixels inside dissolve into the background. The bot drifts downward after the blast (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. 5 - Sprite: Entrance, Animation & Damage 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 The robot sprite (by Steffest/Desire) is 16 rows of 16-bit bitmasks. Each pixel uses 2 bits - 4 values: 0 = transparent, 1-3 = lookup into 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]. 6 - Background: Sky & Scrolling Landscape 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 Pixels with positive DH (below the horizon) are solid blue sky. The landscape uses div dh for a perspective depth value (BP/Y), perspective-corrects the X coordinate, then XORs for a scrolling checker pattern. The "*DIV DANGER*" note flags that DH < 13 would cause a divide fault - the horizon checks above ensure it never reaches zero here. 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. 7 - Data: Sprite, MIDI & Colors 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, maximum Colors: db 0x15,0x2C,0x13 ; bright gray, yellow, dark gray MusicData2: db 0xc1,91,0x91,37,127 ; Setup Bass, Play at Maximum Burst: db 0xb3,0,9,0xc3 ; Burst noise enable plus set (part1) Nasenatmungsgeraeusch: db 125,0x93,40,127 ; set (part2) and play the robo noise Gunsound: db 0x99, 70+3,90,0 ; machine gun + free byte (4 byte align) ExplosionAndTheEnd: db 0xFF,0x90,34,127 ; Kill all sounds, then explosion sound The sprite bitmap and MIDI music share one continuous memory block. At startup the entire block is streamed to the MIDI port via rep outsb. Row 8 of the sprite contain the bytes 0xC0 and 0x7F - a valid MIDI program-change command (channel 0, patch 127 = gunshot). The comment "a 1 in 65536 coincidence?" is of cause rhetorical: it was designed that way... or was it? ;) The commented-out sprite row shows the original value before the runtime code overwrites it. Size Tricks Trick Why it saves bytes push imm / pop es 4 bytes vs 5 for mov ax,... / mov es,ax BP as global clock Reuses existing register, no dedicated counter variable mul 0xCCCD for X/Y 5 bytes vs a full 16-bit divide+modulo sequence stosb for pixel write 1 byte; auto-increments DI for free hlt for frame sync 1 byte halt until next hardware IRQ xlat for color lookup 1 byte table lookup via BX+AL btc for sprite damage Bit flip + memory address in one instruction aam 9 for modulo 2 byte; AH = AL/9, AL = AL mod 9 Sprite doubles as MIDI Same bytes serve two purposes init data simultaneously ret to quit DOS 1 byte; stack top = 0 = int 20h = exit Endbot * HellMood/Desire * Revision 2026 * pouet.net * sizecoding.org/wiki/DOS