Doom · Volume 1

DOOM — What Doom Is, and Why It Travels

A first-person shooter released in December 1993 has since been made to run on a robot lawnmower, a tractor’s dashboard, a cash machine, a bluetooth dongle, a pair of wireless earbuds, a microcontroller cast into a transparent LEGO brick, the firmware layer that boots a personal computer, a satellite in a 515-kilometre polar orbit, and a 300-megabyte cascading style sheet. The question “but does it run Doom?” has been asked so often that it has its own encyclopaedia article and its own meme entry, and the phrase now functions as a general-purpose test of whether a piece of hardware is worth anything at all.

This dive is the sibling of one on “Bad Apple!!”, a monochrome animation subject to the same treatment, and it exists because of a difference the two phenomena are usually assumed not to have. That dive’s opening volume puts the matter precisely: playing “Bad Apple!!” “demands no logic at all — only that a device can be made to emit the right pattern at the right time.” A device that plays the animation has proved that its display can be driven. Nothing more follows.

Doom is the other case. A device that runs Doom has to hold a world in memory and mutate it, accept input that was not known in advance, traverse a data structure to decide what is visible, write a framebuffer, and do all of it again thirty-five times a second. There is no recorded pattern to emit, because nobody knows what the player will do. That is a genuinely different claim about a machine, and it is the reason the two tests are not interchangeable even though they are constantly treated as though they were.

It is also, as the later volumes show, a claim that a surprising number of celebrated “Doom ports” do not actually make.

1.1 The game

Doom was developed and self-published by id Software, and released for MS-DOS on 10 December 1993. The release itself has passed into folklore for a reason that turns out to matter: the team could not connect to the FTP server at the University of Wisconsin–Madison from which they intended to distribute it, because too many people were already connected waiting for it.

That distribution model is the first of the three structural facts that made everything afterwards possible. Doom shipped as shareware. The first episode was given away and was meant to be copied, passed around, and put on any disk that would hold it. A game distributed that way ends up on an enormous number of machines and in an enormous number of archives, and stays there.

Figure 1 — Registered Doom on four 3.5-inch floppies. The shareware first episode was free to copy and was meant to be copied, which is the first of three reasons the game is still being ported thirty years l…
Figure 1 — Registered Doom on four 3.5-inch floppies. The shareware first episode was free to copy and was meant to be copied, which is the first of three reasons the game is still being ported thirty years later. — File:Doom Install Disks (70145859).jpg by Matt Schilder. License: CC BY-SA 2.0. Via Wikimedia Commons.

The second structural fact is that the game is small. The third is that the source code is free. Both take some explaining, and the second one is the one people get wrong.

1.2 The engine, and the thing almost everyone says about it

Doom’s renderer is routinely described as a raycaster. It is not, and the distinction is not pedantry — it is the difference between a technique whose cost scales with the screen and one whose cost scales with the level’s structure, and it determines what a port has to do.

A raycaster, of the kind used in id’s own Wolfenstein 3D the year before, casts one ray per screen column into a uniform grid and marches it until it hits a wall. Doom does something else. Its levels are cut up, before the game ever runs, by a binary space partition: a recursive division of the floor plan by chosen lines, producing a tree whose leaves are convex regions. Those leaves are called subsectors, the wall fragments within them are called segs, and both ship inside the WAD data file. Nothing is partitioned at run time.

Figure 2 — How a Doom frame is actually drawn: a tree that was built when the level was built, walked near-side-first, with each screen column retired as soon as a solid wall closes it. The names are the real…
Figure 2 — How a Doom frame is actually drawn: a tree that was built when the level was built, walked near-side-first, with each screen column retired as soon as a solid wall closes it. The names are the real identifiers in the released source. — Vector diagram generated by make_bsp_diagram.py in this project, from the released source files named in the figure.

At run time R_RenderBSPNode descends that tree. At each node it works out which side the viewpoint is on, renders the near side first, and then tests whether the far side’s bounding box could be visible at all before bothering with it. Walls are drawn as vertical columns of texture. As solid walls accumulate they close off ranges of screen columns, recorded in a clip list, and once a column is closed nothing further will ever be drawn there. When every column is closed the traversal can stop.

Floors and ceilings are handled separately and lazily, as visplanes — deferred horizontal spans sharing a height, a light level and a texture — which are resolved after the walls are known. Sprites are gathered per sector, sorted far-to-near, and clipped against the walls already drawn. The engine’s description credits John Carmack with the design, with auxiliary work by Mike Abrash, John Romero, Dave Taylor and Paul Radek, in C and assembly.

Two consequences matter for porting. First, the expensive preprocessing has already happened and is shipped as data, so the run-time renderer is doing comparatively simple integer work — which is precisely why it can be made to fit on parts that could never have performed the partitioning themselves. Second, the engine’s cost depends on the level’s geometry rather than on the pixel count, so a port’s frame rate varies map to map in ways that a raycaster’s would not. Every microcontroller port discussed in volume two reports its worst level rather than its average, and that is why.

The renderer also carried hard limits, and they are visible in the source as plain constants. r_plane.c defines MAXVISPLANES as 128; exceed it and the original aborted with R_FindPlane: no more visplanes. This is worth holding on to, because it establishes something about the whole exercise: Doom was not a general, elastic piece of software that happens to be small. It was a tightly budgeted program with fixed ceilings, written for a specific machine, and the ceilings are part of what makes it portable.

1.3 The fixed points

Four numbers in the released source define what any port is signing up for, and they recur throughout this dive.

The screen is 320 by 200. doomdef.h sets SCREENWIDTH to 320 and SCREENHEIGHT to 200, one byte per pixel indexing a palette — a framebuffer of exactly 64,000 bytes.

The game logic runs at a fixed rate. TICRATE is 35. The simulation advances in thirty-fifths of a second regardless of how fast or slow the machine draws, which is what allows players on unequal hardware to share a game, and which makes timing a first-class porting problem rather than an afterthought.

Up to four players. MAXPLAYERS is 4.

And the memory. i_system.c sets mb_used = 6 and allocates the zone heap as mb_used*1024*1024 in a single call. Six megabytes, grabbed at startup, out of which the engine manages everything itself. That number is the wall almost every constrained port runs into first, and volume two is largely about what people do to it.

1.4 Porting Doom before there was a meme

The habit is older than the source release, which is easy to forget. The Super Nintendo port, as the encyclopaedia records, was developed by Sculptured Software on hardware that had no business running it: a 16-bit console, assisted by the Super FX coprocessor. Its lead programmer, Randy Linden, did not have a Super FX development kit, and worked instead from a modified Star Fox cartridge to get at the chip, writing his own engine — which he called the Reality Engine — rather than porting id’s.

That is worth stating early because it sets a bar. The SNES port was a reimplementation for a target that could not run the original: a serious engineering achievement, and also, strictly, not the same artefact. The taxonomy developed in volume three has to be able to say both of those things at once, and much of the difficulty in classifying modern cases is of exactly this kind.

1.5 The source release

Figure 3 — John Carmack, who designed the renderer and wrote the December 1997 release note. His judgement that the code was portable enough to be modified for "just about any" device is the closest thing the…
Figure 3 — John Carmack, who designed the renderer and wrote the December 1997 release note. His judgement that the code was portable enough to be modified for "just about any" device is the closest thing the phenomenon has to a founding statement. — File:John Carmack GDC 2010.jpg by Official GDC. License: CC BY 2.0. Via Wikimedia Commons.

On 23 December 1997, id Software released the Doom source code. Carmack’s release note — still at the head of the repository — opens “Here it is, at long last. The DOOM source code is released for your non-profit use.”

Three details of that release shaped everything after it.

It was not free software. The 1997 licence permitted non-profit use and no more. That was enough to start a modding and porting culture but not enough to make it durable, and the distinction is the reason the second date matters more than the first.

It was the Linux version. The tree that was released is linuxdoom-1.10, derived from the unofficial Linux port written by id programmer Dave Taylor. The DOS build was not included; Carmack’s note attributes that to licensing problems with the DOS version — a constraint arising from third-party code rather than from anything in the engine. The practical effect was benign and possibly decisive: what entered the world was not a pile of DOS-specific real-mode assembly but a version that had already been made to survive being moved once. Every port since has begun from code that had already proved it could be relocated.

And Carmack’s own assessment, recorded in the same encyclopaedia entry, was that although the code was written to run on Linux it was portable enough to be modified to run on “just about any” other device. That sentence is as close as this phenomenon has to a charter.

1.6 The relicensing, which is the actual cause

On 3 October 1999, the Doom engine source was relicensed under the GNU General Public License, version 2 or later.

This is the concrete reason the porting culture exists, and it deserves to be stated flatly rather than folded into a list. Under the 1997 terms, a port was a favour extended by a rights holder to hobbyists. Under the GPL, a port is a right. Anyone may take the engine, modify it for hardware id Software never imagined, publish the result, and — crucially — build on somebody else’s modified version rather than starting again. The community engines that every small port is actually derived from, chief among them Chocolate Doom and PrBoom, exist because that permission is transitive.

The comparison with the sibling dive is instructive here, because the mechanisms are not the same even though the outcomes rhyme. “Bad Apple!!” travels on a permissive derivative culture — a community norm of tolerating re-encoding and republication. Doom travels on a licence, which is enforceable, transitive, and did not depend on anyone’s goodwill after 1999. One is a social fact and one is a legal one, and the legal one is why Doom ports are routinely published as complete source repositories that the next porter can fork, while Bad Apple ports are routinely published as videos.

1.7 The half that is not free

The engine is GPL. The game is not.

Doom’s levels, textures, sprites, sounds and music live in a data file — a WAD — that remains commercial, copyrighted property. A port therefore consists of free code that will not do anything without non-free data, and every port has to obtain a WAD from somewhere.

This has two consequences that recur throughout the dive. The first is practical: the WAD is the bulk of the storage problem. The shareware DOOM1.WAD is about four megabytes, against, for instance, the two megabytes of flash on a Raspberry Pi Pico — so the data, not the code, is what has to be compressed, and volume two shows how far people go.

The second is that a free replacement exists. Freedoom is a project to produce a complete set of assets, compatible with the engine and its ports, under a free licence — the repository carries the terms. It matters more than it looks, because it is what allows a port to be distributed as a thing that actually runs rather than as a thing that runs if the user supplies the data. It is also why Freedoom screenshots, rather than Doom screenshots, illustrate a good deal of the freely licensed material on this subject, including in this dive.

Figure 4 — The engine rendering, in Freedoom — the free asset set built to run on the same code. The engine is GPL; the original game data never was, which is why a port is always two problems rather than one.
Figure 4 — The engine rendering, in Freedoom — the free asset set built to run on the same code. The engine is GPL; the original game data never was, which is why a port is always two problems rather than one. — File:Freedoom 2018.png by Freedoom developers. License: BSD. Via Wikimedia Commons.

1.8 What a porter actually starts from

Almost nobody ports linuxdoom-1.10 directly. The practical starting points are the community engines, and one shim in particular.

doomgeneric, itself forked from fbDOOM, exists to reduce a new machine to the smallest possible contract: implement five functions, and the engine is yours. Its header declares them plainly — DG_Init, DG_DrawFrame, DG_SleepMs, DG_GetTicksMs and DG_GetKey, plus an optional DG_SetWindowTitle — against a single exported framebuffer pointer, DG_ScreenBuffer.

Volume two takes that contract apart, because it is both the reason ports are now easy and the reason a great many of them are worse than they need to be. It is also where the budgets get worked as numbers: memory, storage, display bandwidth, input and timing, the way the sibling dive works its three.

1.9 Determinism, and a distinction that becomes important later

One further property of the engine has to be established here, because two later volumes depend on it and neither works without it.

Doom’s simulation is deterministic. It advances in fixed thirty-fifth-of-a-second steps, and it computes in fixed-point integer arithmetic rather than floating point, so the same starting state driven by the same inputs produces the same outcome on any machine that implements the arithmetic identically. That is what makes network play possible between computers of unequal speed: the machines exchange inputs, not positions, and each one arrives at the same world independently.

The same property produces Doom’s demo files. A recorded demo — a .lmp — does not contain any pictures. It contains the sequence of inputs a player made, and playing it back means running the entire engine again and re-deriving every frame from scratch. This has two consequences that recur in this dive.

The first is that demos are the field’s regression test. A port that plays the original demos to completion, in step, is almost certainly computing what the original computed. The author of the Raspberry Pi Pico port used exactly this as a correctness criterion, and reports what it cost: optimising the engine’s trigonometric tables introduced errors of at most one part in 65,536, and that was enough to break demo compatibility, because a divergence of one unit compounds until two runs are playing different games. Nothing else in the porting literature is so unforgiving, and nothing else is such good evidence that a port is real.

The second consequence is the one volume four turns on. Because a demo is an input recording rather than a video, a device replaying a demo is computing every frame. It is doing the whole job — the state, the traversal, the framebuffer — and merely not being played by anybody. That is a genuinely different situation from a device showing a recorded video, and the two are constantly conflated. A taxonomy that cannot separate them will misclassify at least one famous case.

1.10 What the word “port” has been made to carry

Three quite different activities travel under the same noun, and the confusion is load-bearing.

In the narrowest and commonest sense, a port is a recompilation. The engine’s C is built for a new processor and operating system, and the work consists of supplying whatever the platform does not already provide. Most of the cases in this dive are of this kind, and since the GPL relicensing they usually begin from a community engine rather than from id’s tree.

In a second sense, a port is a reimplementation. The Super Nintendo version is the archetype: the target could not execute the original engine, so a new engine producing a similar experience was written for it. Whether that counts depends on what is being asked. As a demonstration that the hardware can sustain the computation, it counts entirely. As a demonstration that this program runs there, it does not.

In the third and loosest sense — the sense the meme actually uses — a port is any circumstance in which Doom appears on a thing. This is the sense under which a display driven by a computer in another room, a recording played back on a panel, and a microcontroller executing the engine from its own flash all arrive at the same headline. Volume three exists because that sense is useless for answering the only interesting question, which is what the demonstration establishes about the device.

1.11 What this dive covers, and what it does not

Volume two establishes what a port has to satisfy, in figures taken from the source and from the ports’ own published write-ups. Volume three sets out a taxonomy of what “it runs Doom” can mean and classifies the well-known cases against it, with the evidence attached to each. Volume four examines the celebrated cases that turn out not to be what the headline says — including the most famous of them all. Volume five answers the question the sibling dive’s closing volume asks, in the form it takes here: what a Doom port proves, and what it does not.

A caveat on sourcing is owed at the start, and it is a sharper one than the sibling dive needed. The dominant source on this subject is the aggregated list — the listicle that names forty devices and cites none of them — and it is the least reliable material available. Lists of that kind copy from each other, retain claims after they have been corrected, and systematically omit the detail that decides whether a given case is a port at all. Wherever possible the account here rests on the primary artefact: the repository, the author’s own write-up, the source file. Where only secondary coverage could be found, that is said. Where a well-known claim could not be settled from any source located during this research, it is reported as unsettled rather than smoothed over, and volume four contains more of those than one might expect.

Comments (0)

  1. Loading…

Comments are held for moderation — nothing appears until approved.