WOAH
A pixelated first-person corridor glowing on a tiny monochrome dot-matrix screen in the dark

Play

A PDF file can run Doom, and that is the boring version

By The host3 min#games#ports#classics

Doom runs inside a PDF file and glows through a gutted pregnancy test. Why the 1993 engine never stops finding stranger machines to draw it.

Somewhere on your machine right now sits a program that can run Doom, and it is not a game console. It is your PDF reader.

doompdf is the 1993 shooter running inside a single PDF file, playing in the same browser tab you open receipts in. You type into a text box to move, the corridors redraw as rows of ASCII characters in six shades of grey, and each frame takes about 80 milliseconds. It is slow, and it is unmistakably Doom.

How a document runs a video game

The PDF format has carried its own JavaScript engine for decades, a leftover from Adobe's dream of interactive paperwork, and browser PDF viewers implement a slice of that standard library to run form logic. That slice is the whole computer here.

The port, by a developer who goes by ading2210, starts from doomgeneric, a version of the engine maintained specifically to be carried to strange places, and compiles the original C with an old build of Emscripten that still emits asm.js, the highly optimizable JavaScript dialect from before WebAssembly existed, because a PDF cannot load anything newer. There is no canvas to draw on, so the port writes each screen row into a separate text field and swaps the characters every frame, one ASCII shade per brightness level. Those 80-millisecond frames pencil out to twelve and a half frames a second, in a file format designed for invoices.

The runtime really is that cramped. The PDF flavor of JavaScript gets no network access and nothing 3D, so the port ships its own scrolling text console for debugging, takes custom WAD files through a build page, and does every last bit of its rendering as strings. The whole thing is playable in your browser right now. The game loop was always there. Someone just found a stranger place to keep it.

Why it is always Doom

The reason is the code itself. id Software released the source in December 1997 and relicensed it under the GPL in 1999, and that engine was built to run with no graphics card and no floating-point unit, on a software renderer doing fixed-point math. Give it a few hundred kilobytes of memory and something shaped like a screen and it will draw a level. That is a very low bar, and thirty years of tinkerers have spent their weekends finding things that clear it.

The pregnancy test, with receipts

The pregnancy test is the honest edge of the joke, and the teardown is the receipt. When Foone Turing opened one up, the guts turned out to be an 8-bit Holtek microcontroller with 64 bytes of RAM, 1,024 words of ROM, and a screen that can show a few fixed glyphs, all of it there to read a paper strip that does the actual chemistry. Nothing in that list is reprogrammable. Nothing in that list will ever draw a corridor.

So the shell got a transplant: an Adafruit Trinket M0 and a 0.91 inch, 128 by 32 pixel monochrome OLED. Even then, Doom itself runs on a PC nearby; the game is dithered down to one bit per pixel and streamed to the tiny screen. Do the arithmetic and a full frame is 512 bytes, a trickle the little microcontroller sips without effort. The pregnancy test did not run Doom. The pregnancy test became a tiny second monitor for a Doom running somewhere saner, which is a different claim and a funnier one, and the people who build these are careful about the difference.

That care is the whole hobby. "Will it run Doom" is a real benchmark because the answer is almost always yes once you say what "it" means, and the good entries show their work: the transplant, the frame time, the compile target.

Port one yourself

The trailhead is doomgeneric, the same base doompdf forked. Implement five functions, init, draw frame, read a key, sleep, and read the clock, and the engine will paint its first frame on whatever you own that resembles a display. The example ports in the repo cover Windows, X11, SDL and the browser, and the pattern generalizes to sillier targets from there.

Browsers have quietly become the natural habitat for this kind of transplant; this tab has hosted heavier organs than a 1993 shooter, including a full Debian Linux booting with no server behind it.

Open the PDF one. Walk one corridor. Then remember it is a document.

Next up

Every one of these drawings is a single HTML div