Software archaeology · The DOOM study

DOOM's DNAHow code keeps its family resemblance

Forty-three programs are descended from the 1997 release of DOOM's source code. Measure only their shape and you can still tell who copied whom, and which translations kept the original blueprint.

The 43 descendants we scanned, around the original. Each name is sized by how many functions the program has, from the Claude-written COBOL raycaster's 45 to Doomsday's 25,482. Tap one to open it.

Strip a program of every name, every comment, every line of readable text. What's left is its shape: how long each function is, how often it branches, which parts of the program call which. That shape, it turns out, works like a fingerprint.

DOOM is the perfect place to test it. When id Software released the game's source code in 1997 (the version known as linuxdoom), programmers started copying it, porting it and rebuilding it, and they have not stopped. We gathered 43 of those descendants: faithful copies, souped-up source ports, and translations into about twenty languages, from Go and Scheme to Fortran, COBOL and even SQL. Then we fed them to a code-measuring engine that never reads the code, and asked it where each one came from.

87% / 35%Chocolate Doom keeps 87% of the original's function names, but only 35% of the code inside them is unchanged
0 to 0.6%of DOOM's shape shows up in Quake, Quake II and Lua. A faithful DOOM copy: 31%
60% vs 20%with every name hidden, a Go translation's functions still land in the right part of the game; guessing manages 20%

Names lie

First, the old-fashioned way. Before trusting a machine that never reads code, we read it: we compared every descendant's source text with the original's, function by function. That gives the ground truth for everything that follows. Then, in the next part, we throw the text away.

Start with the obvious way to trace a program's ancestry: look at what its functions are called. It does not work as well as you'd think. Chocolate Doom, whose whole mission is an accurate reproduction of the original "including bugs", keeps 87% of the original's function names, but only 35% of those functions still contain the same code. Doom Retro keeps 69% of the names and just 0.2% of the code. GZDoom, a heavily modernised engine, keeps 32% of the names and 0.2% of the code.

Copies keep the labels, rewrite the contents

Forks keep the original's function names long after the code inside has been rewritten

Share of the original's functions (at least 20 words of code) present in each C descendant, by name and by unchanged code. Measured by comparing the source text directly.
Show as a table

Chocolate Doom keeps 87% of DOOM's function names, and 35% of the code inside them.

Averages hide the fun part, which is following one function through the whole family. The original has 662 functions big enough to score. Pick one below and see what became of it in every descendant.

Play with it

Follow a function

Pick a famous one, or type any of the original's 662 functions. Each tile is one descendant; its colour is what became of that function there. Tap a tile to open that descendant at the bottom of the page.

identical same codenear-identical 90%+ the samederived 60%+name only name kept, code rewrittengone no trace

A fingerprint you can't read

So skip the names. The engine we used records, for every function, dozens of measurements: its length, its branches, its inputs, what it touches, and which other functions it calls. A companion tool then hunts for "look-alikes": functions in a descendant whose measurements match one of the original's too closely to be a coincidence. It never sees the code or the names.

Then we checked its work against the actual source text. 93% of the look-alikes it found really are copies, and it caught about two-thirds of all the copies the text comparison found.

The real test is what happens with programs that have nothing to do with DOOM. Quake and Quake II, id Software's own later games, and the Lua programming language are all written in C, like DOOM. They share between 0 and 0.6% of DOOM's shape. Chocolate Doom shares 31%. And the fingerprint is not tuned to DOOM: Quake and Quake II, which really are related, share 12.7% with each other.

Put those fingerprints together with each project's own account of where it came from and you can draw the whole family, the way biologists draw an evolutionary tree. One honest difference: a biologist infers who descends from whom. Here the family links are what each project says about itself; only the branch lengths are measured.

The family tree · who copied or translated whom, and how much code each one still shares with its parent
forktranslation into another languagerewrite- - - an ancestor the study did not scan (distance unknown)dot size: number of functions

Drawn like an evolutionary tree: short branch = close copy, long branch = little code in common. Branch length is one minus the code shape a descendant shares with its parent. Who descends from whom is as each project states it, not inferred; the lengths are measured. Translations rewrite every line, so they all reach the far edge, even the ones that kept DOOM's design; that is the next part of the story. Hover a name for its numbers; click to open it below.

Swipe sideways to see the whole tree.

The blind test

Translations are the hard case. Rewrite DOOM in Go and every line changes, every function gets a new name, and the look-alike hunt finds nothing. So what, if anything, survives?

We tried a blind test. DOOM's code is organised into 16 parts: the renderer, the monsters and physics, the sound, the status bar and so on. We hid every name and file path in a translation and asked the engine to sort each function into one of those parts using only its shape and who it calls. Then we checked where the names say it belongs.

Sorting functions with every name hidden

Some translations still sort themselves into DOOM's parts far better than guessing

Share of each descendant's functions the engine put in the same part of the game as their names do. The number on the right is how many points it beats guessing by. Chocolate Doom, a straight C copy, is the sanity check.
Show as a table

For the Go translation, the engine sorts 60% of the functions correctly; shuffled labels manage 20%. Among the quarter of functions it was most sure about, it was right 99% of the time. Scramble the Go program's wiring, keeping how many calls each function makes, and it drops to 39%: the wiring itself carries much of the signal.

Hide every name, and a Go port's functions still land in the right part of DOOM 60% of the time. Guessing manages 20%.

Translations that kept the blueprint

If the wiring carries the signal, look at the wiring directly. For every descendant we drew a map of which parts of the game call which, and compared it with the original's map. To know how much of the match is real, we also scored each program against itself with its functions shuffled at random between the parts. The gap between the two is the design that survived.

The wiring diagram, against chance

The Scheme and Go translations kept DOOM's wiring as well as a straight C copy; Java, Kotlin and C# kept none

Wiring match, from 0 (nothing in common) to 1 (identical). The number on the right is the gap from grey to blue: how much of the match comes from the program's design rather than chance.
Show as a table

The Scheme and Go translations keep the original's wiring as far above chance as Chocolate Doom does. The Dart translation keeps some. The Fortran, Java, Kotlin and C# translations keep none, and programs written from scratch, or not related to DOOM at all, sit at chance, as they should. Don't take our word for it: draw the maps yourself.

Play with it

Compare the wiring

Each circle is a part of the game, placed where the original's wiring puts it; each line is calls between two parts, thicker for more. A descendant wired like the original draws the same lines. Pick one.

Explore all 43

Every descendant has its own story: when it started, how big it grew, which famous functions it kept, how far it drifted year by year. Pick one from the list, or compare any two.

Loading...

Records · biggest, smallest, most faithful, most starred, oldest history, languages

Loading...

What it can't tell you

Written by Joe Esquibel, using the GitGalaxy code-scanning engine.

github.com/squid-protocol/gitgalaxy

Check my work

Every number on this page comes from public code and can be rebuilt from it. Here is everything you need to examine the data and the methods, or to run them yourself.

Found something that doesn't add up? Open an issue on the GitGalaxy repository.