Mars.exe

Reverse engineering a 5,649-byte DOS voxel terrain renderer from 1993

DOS MZ 16-bit x86 Pixel-Perfect

Drag to move · Arrow keys / WASD

The Story

Mars was written by Tim Clarke in 1993 while still at school. The demo gained legendary status in the demoscene for generating and rendering fractal voxel terrain in real-time — all in under 6KB. Running at full speed on a 486, it was remarkable enough that Tim was headhunted to work for space agency Lunacorp in Washington for several summers while studying at Cambridge University.

Day 1

Down the Rabbit Hole March 9

Started with a 5,649-byte DOS executable from the Hornet demoscene archive. Parsed the MZ header, mapped segment layout, found entry point at file offset 0x200. The entire binary was annotated by hand in a hex dump format — every instruction, every data table, every constant. 800 lines that became the single source of truth.

Key algorithms fell into place: the PRNG (seed * 0xAB + 0x2BCD mod 0xCF85), diamond-square fractals with BX register walk, palette at 0x14CA. First web port worked but looked wrong — horizon at wrong height, perspective formula off, hex misreads (B9 40 00 is MOV CX,64 not 16384).

Day 2

Getting the Axes Right March 10

Terrain was rendering 90° rotated. A +0x4000 angle offset had leaked into the voxel pass. Fixed axis swap, added height & color interpolation, sorted out the horizon buffer — 0x7D00 was two packed 16-bit words in a STOSD.

Day 3

The One-Line Fix March 11

A single missing line caused dramatic vertical streak artifacts: the prevColor buffer wasn't being cleared between frames. The binary zeroes it at 0xC40 with REP STOSD. One line of code, hours of debugging.

Days 4–5

The Annotation Audit March 17–18

196 lines of the annotated hex dump had accumulated address drift (7–16 bytes off). Every address from 0x420 to 0xC20 re-verified with xxd. Two architectural fixes emerged: viewAngle was fiction (camera always faces +Y), and float math was wrong — replaced with integer DDA (ADD BX,AX; ADC SI,BP).

Key discovery: byte F5 in pattern A4 03 D8 13 F5 is a ModRM byte (ADC SI,BP), not a standalone CMC instruction.

Day 6

The Last Few Percent March 19

Height projection was truncating to 8 bits. Missing DEC BP compensation. Two voxel pass bugs: skip path must update prevColor, and dispatch table draw range is off-by-one due to DI wrap trick. Floor pass: pixel-perfect. Voxel pass: down to 0–81 pixel diffs.

Day 7

The Emulator Marathon March 22

Four sessions, 17 hours. Built a custom 16-bit x86 emulator (1,800 lines of JS) to run mars.exe instruction-by-instruction. First comparison: 39.5% pixel differences. Fixed PRNG seed isolation, DIV/IDIV edge cases, dynamic camera height, FS:[BX+1] carry semantics.

seed 42:    PIXEL PERFECT
seed 100:   PIXEL PERFECT
seed 500:   PIXEL PERFECT
seed 1000:  PIXEL PERFECT
seed 5000:  PIXEL PERFECT
seed 8888:  PIXEL PERFECT
seed 12345: PIXEL PERFECT
seed 32000: PIXEL PERFECT
5,649
bytes in original EXE
802
lines annotated by hand
1,800
lines in x86 emulator
131.6M
tokens with Claude Code
7
days to pixel-perfect
~20h
total session time

Architecture

Binary Memory Layout

CS Code + inline data (entry CS:0000 = file 0x200) 5,649 bytes DS Runtime vars, BSS (CS + 0x10C paragraphs = file 0x12C0) Jump table Step table Palette BSS vars Render buf 256x200 FS Heightmap buffer (64KB, fractal 0-255) GS Slopemap buffer (64KB, shading 0-63) Colormap Fractal → palette indices 64-95 ES = A000:0000 VGA framebuffer File offsets: 0x200 code 0x12C0 jump tbl 0x1450 step tbl 0x14CA palette

Rendering Pipeline

GENERATION (once) Diamond-Square PRNG + fractal subdivision Heightmap 256x256 wrapping Smooth + Slope h[i]-h[i+3]+32 Colormap byte>>3 + 0x40 PER FRAME Camera Height bilinear interp + 0x1900 Pass 1: Floor Plane 99 rows, DDA from colormap Pass 2: Voxel Cols ray march + Gouraud Sky Gradient 40 rows, heading/row KEY TECHNIQUES Fixed-Point DDA ADD BX,AX; ADC SI,BP Sub-pixel texture stepping Unrolled MOVSB Loop 256 iterations at 0x6DA-0xBD4 Jump table dispatch by scanline Overlapping Instructions 0x125C: dual-purpose code path JNZ opcode doubles as TEST+STC Horizon Buffer 256 words, init 0x7D00 (row 125) Back-to-front occlusion Gouraud Shading Slopemap color interpolation IDIV color step, top-down fill 16-bit Address Carry FS:[BX+1] wraps BL→BH Critical at x=255 boundaries

Binary ↔ Web Mapping

Binary FunctionFile OffsetWeb Equivalent
entry + init 0x200–0x2A5 genColormap(), genHeightmap()
gen_colormap 0x2F3 genColormap()
gen_heightmap 0x353 genHeightmap()
subdivide 0x419 subdivide()
handle_input 0x608 mouse/touch/keyboard handlers
render_columns 0x659 render() — Pass 1
fill_sky 0xBDC render() — sky gradient
ray_march 0xC1F render() — Pass 2
camera_height 0x125C cameraHeight()
Step table 0x1450 stepTable[]
Palette data 0x14CA PAL6[]

Annotated Source Code

Click any cross-reference link to jump between the original annotated hex dump and the JavaScript reimplementation. Highlighted regions show corresponding code.

💾 mars_annotated.txt 16-bit x86 hex
Loading annotated hex dump...
📄 mars.js JavaScript
Loading JavaScript source...