Initial commit Add the DOS fire demo source, screenshot, and project documentation as the initial repository state. Made-with: Cursor
@@ -0,0 +1,1 @@+DOSBOX/
@@ -0,0 +1,150 @@+# Fire ASM + + + +Repository: [runawaydevil/fire-asm](https://github.com/runawaydevil/fire-asm) + +This is a tiny 16-bit real mode DOS fire demo written in x86 assembly. + +No engine, no runtime, no framework. Just BIOS calls, VGA mode 13h, a hand-built +palette, a scratch buffer, and a loop that keeps pushing heat upward until you +press a key. + +## The Language + +The source is NASM-style x86 assembly. It is built as a `.COM` program, which is +about as bare-metal as DOS application code gets: + +- execution starts at offset `0x100` +- code and data live in the same segment +- there is no executable header +- DOS loads the program and jumps straight in +- BIOS and DOS interrupts are used directly + +The first line says it plainly: + +```asm +org 0x100 +``` + +That tells the assembler to lay out addresses for a DOS `.COM` file. There is +also a commented `org 0x7c00` path in the file, plus the commented boot +signature at the bottom, because the same kind of code can be pushed toward an +MBR-style boot sector with the right constraints. + +## Building It + +Use NASM to build a flat binary: + +```bat +nasm -f bin fire.asm -o FIRE.COM +``` + +Then run it inside DOSBox or DOSBox-X: + +```bat +FIRE.COM +``` + +Press any key to leave the demo. The program restores text mode before exiting. + +## What It Does + +The demo enters VGA mode 13h: + +```asm +mov ax, 0x13 +int 0x10 +``` + +Mode 13h gives you a 320x200 framebuffer with one byte per pixel. The video +memory starts at `A000:0000`, so drawing is just writing color indexes into a +64,000 byte linear buffer. + +After switching video modes, the program writes the text `pablomurad.com` using +BIOS interrupt `10h`, function `13h`. Then it builds the VGA palette by talking +directly to the DAC ports: + +- `0x3c8`: DAC address write register +- `0x3c9`: DAC data register + +The palette starts dark, ramps through red and yellow, then fills the rest with +white heat. Old school VGA palette tricks make the simulation cheap: the fire +buffer stores only byte-sized intensity values, and the hardware palette turns +those values into color. + +## The Fire Loop + +The bottom row is seeded with pseudo-random heat: + +```asm +imul ax, 181 +add ax, 359 +``` + +That is a small linear congruential generator. It is not cryptography, it is not +pretty, but it is fast and good enough to shake the flame. + +Each frame, the code walks backward through the buffer. For every pixel, it +looks at nearby pixels below it: + +- directly below +- below-left +- below-right +- two rows below + +It adds those values, scales the sum by `15 / 64`, and writes the cooled result +above: + +```asm +imul ax, 15 +shr ax, 6 +``` + +That tiny average-and-cool step is the whole fire effect. Heat is born at the +bottom, smeared upward by neighboring pixels, and slowly loses energy as it +rises. + +## Buffers And Video Memory + +The program does not render directly into the visible framebuffer all the time. +It copies the initial screen into a scratch buffer at a segment offset from the +program segment, then computes frames there. + +At the end of each loop it copies the scratch buffer back to VGA memory: + +```asm +push 0xa000 +pop es +rep movsw +``` + +The copy uses word moves for speed. This is the kind of code where `ds`, `es`, +`si`, `di`, and the direction flag matter. There is no abstraction layer between +the program and the machine. + +## Text Reflection + +The text is part of the trick. The program mirrors some lower fire rows onto the +area where the text lives, but only where destination pixels are already +non-zero. That keeps the logo shape while borrowing color and motion from the +flame below. + +In other words: the text is a mask, the fire is the texture, and the CPU is doing +the compositing one byte at a time. + +## Timing And Exit + +Frame pacing uses BIOS interrupt `1Ah`, waiting for the system timer tick. That +tick runs at about 18.2 Hz on a classic PC. + +Keyboard polling uses BIOS interrupt `16h`. When a key is waiting, the program +consumes it, restores text mode with `int 10h`, and exits through DOS interrupt +`21h`. + +## Files + +- `fire.asm`: the NASM source. +- `fire.png`: screenshot of the demo running in DOSBox-X. +- `FIRE.COM`: generated binary, if you build it locally. +
A
fire.asm
@@ -0,0 +1,191 @@+ org 0x100 ; For .com file. + ;org 0x7c00 ; For MBR. + +section .text +start: + ; Enter mode 13h: 320x200, 1 byte (256 colors) per pixel. + mov ax, 0x13 + int 0x10 + + ; Make sure es and ds point to our segment (cs). + push cs + push cs + pop ds + pop es + + ; Write string. + mov ax, 0x1300 ; ah=13h, al=write mode + mov bx, 0xf ; bh=page number (0), bl=attribute (white) + mov cx, (msg_end - msg) ; cx=length + mov dx, ((10 << 8) + (40 / 2 - (msg_end - msg) / 2)) ; dh=row, cl=column + mov bp, msg ; es:bp=string address + int 0x10 + + ; Set up the palette. + ; Jare's original FirePal: + cli ; No interrupts while we do this, please. + mov dx, 0x3c8 ; DAC Address Write Mode Register + xor al, al + out dx, al ; Start setting DAC register 0 + inc dx ; DAC Data Register + mov cx, (firepal_end - firepal) + mov si, firepal +setpal1: + lodsb + out dx, al ; Set DAC register (3 byte writes per register) + loop setpal1 + mov al, 63 + mov cx, (256 * 3 - (firepal_end - firepal)) +setpal2: + out dx, al ; Set remaining registers to "white heat". + loop setpal2 + sti ; Re-enable interrupts. + + ; A buffer at offset 0x1000 from our segment will be used for preparing + ; the frames. Copy the current framebuffer (the text) there. + push 0xa000 + pop ds + push cs + pop ax + add ax, 0x1000 + mov es, ax + xor si, si + xor di, di + mov cx, (320 * 200 / 2) + cld + rep movsw ; Copy two bytes at a time. + + push es + pop ds +mainloop: + ; On entry to the loop, es and ds should point to the scratch buffer. + + ; Since we'll be working "backwards" through the framebuffer, set the + ; direction flag, meaning stosb etc. will decrement the index registers. + std + + ; Let di point to the pixel to be written. + mov di, (320 * 200 - 1) + + ; Write random values to the bottom row. + ; For random numbers, use "x = 181 * x + 359" from + ; Tom Dickens "Random Number Generator for Microcontrollers" + ; http://home.earthlink.net/~tdickens/68hc11/random/68hc11random.html + mov cx, 320 + xchg bp, ax ; Fetch the seed from bp. +bottomrow: + imul ax, 181 + add ax, 359 + xchg al, ah ; It's the high 8 bits that are random. + stosb + xchg ah, al + loop bottomrow + xchg ax, bp ; Store the seed in bp for next time. + + ; For the next 50 rows, propagate the fire upwards. + mov cx, (320 * 50) + mov si, di + add si, 320 ; si points at the pixel below di. +propagate: + ; Add the pixel below, below-left, below-right and two steps below. + xor ax, ax + mov al, [si] + add al, [si - 1] + adc ah, 0 + add al, [si + 1] + adc ah, 0 + add al, [si + 320] + adc ah, 0 + imul ax, 15 + shr ax, 6 ; Compute floor(sum * 15 / 64), averaging and cooling. + stosb + dec si + loop propagate + + ; Mirror some of the fire onto the text. + mov dx, 15 ; Loop count, decrementing. + mov di, (90 * 320) ; Destination pixel. + mov si, (178 * 320) ; Source pixel. +mirrorouter: + mov cx, 320 ; Loop over each pixel in the row. +mirrorinner: + mov al, [di] ; Load destination pixel. + test al, al ; Check if its zero. + lodsb ; Load the source pixel into al. + jnz mirrorwrite ; For non-zero destination pixel, don't zero al. + xor al, al +mirrorwrite: + stosb ; Write al to the destination pixel. + loop mirrorinner + add si, 640 ; Bump si to the row below the one just processed. + dec dx + jnz mirrorouter + + ; Sleep for one system clock tick (about 1/18.2 s). + xor ax, ax + int 0x1a ; Returns nbr of clock ticks in cx:dx. + mov bx, dx +sleeploop: + xor ax, ax + int 0x1a + cmp dx, bx + je sleeploop + + ; Copy from the scratch buffer to the framebuffer. + cld + push 0xa000 + pop es + mov cx, (320 * (200 - 3) / 2) + xor si, si + mov di, (320 * 3) ; Scroll down three rows to avoid noisy pixels. + rep movsw + + ; Restore es to point to the scratch buffer. + push ds + pop es + + ; Check for key press. + mov ah, 1 + int 0x16 + jz mainloop + +done: + ; Fetch key from buffer. + xor ah, ah + int 0x16 + + ; Return to mode 3. + mov ax, 0x3 + int 0x10 + + ; Exit with code 0. + mov ax, 0x4c00 + int 0x21 + +; Data. +msg: db 'pablomurad.com' +msg_end: + +firepal: + db 0, 0, 0, 0, 1, 1, 0, 4, 5, 0, 7, 9 + db 0, 8, 11, 0, 9, 12, 15, 6, 8, 25, 4, 4 + db 33, 3, 3, 40, 2, 2, 48, 2, 2, 55, 1, 1 + db 63, 0, 0, 63, 0, 0, 63, 3, 0, 63, 7, 0 + db 63, 10, 0, 63, 13, 0, 63, 16, 0, 63, 20, 0 + db 63, 23, 0, 63, 26, 0, 63, 29, 0, 63, 33, 0 + db 63, 36, 0, 63, 39, 0, 63, 39, 0, 63, 40, 0 + db 63, 40, 0, 63, 41, 0, 63, 42, 0, 63, 42, 0 + db 63, 43, 0, 63, 44, 0, 63, 44, 0, 63, 45, 0 + db 63, 45, 0, 63, 46, 0, 63, 47, 0, 63, 47, 0 + db 63, 48, 0, 63, 49, 0, 63, 49, 0, 63, 50, 0 + db 63, 51, 0, 63, 51, 0, 63, 52, 0, 63, 53, 0 + db 63, 53, 0, 63, 54, 0, 63, 55, 0, 63, 55, 0 + db 63, 56, 0, 63, 57, 0, 63, 57, 0, 63, 58, 0 + db 63, 58, 0, 63, 59, 0, 63, 60, 0, 63, 60, 0 + db 63, 61, 0, 63, 62, 0, 63, 62, 0, 63, 63, 0 +firepal_end: + + ; For MBR: + ;times (510 - ($ - $$)) db 0 ; Pad to 510 bytes + ;db 0x55 ; MBR boot signature. + ;db 0xaa
A
fire.com
A
fire.png