Show HN:DOOM 的 DNA——代码结构如何在 44 个移植版本中存续
Show HN: The DNA of DOOM – How code structure survives across 44 ports

原始链接: https://squid-protocol.github.io/gitgalaxy/museum-of-code/doom-dna/

DOOM 1997 源代码的四十三个后代通过代码形态而非文本进行分析。去除名称和注释后,比较了函数长度、分支、输入和调用关系。这种形态如同指纹:C 语言的复制和移植揭示了血统,而无关的 C 程序(Quake、Quake II、Lua)最多共享 DOOM 形态的 0.6%。Chocolate Doom 保留了 87% 的函数名,但只有 35% 的代码未更改。仅基于形态的相似查找器识别了 93% 的真实副本,以及约三分之二由文本找到的副本。在盲测中,来自 Go 翻译的函数以 60% 的准确率被分入 DOOM 的 16 个子系统,而猜测为 20%。连接图显示 Scheme 和 Go 像 C 副本一样在偶然之上保留了 DOOM 的设计,而 Fortran、Java、Kotlin 和 C# 没有。微小函数和大量重组解释了部分表面损失。结果通过源文本、打乱控制、无关程序和独立工具验证。该研究追踪血统和存续设计,而非运行时行为。

joe-esquibel 的 Show HN 帖子描述使用自定义结构提取引擎分析 44 个 DOOM 移植版,研究存活函数、关键词结构和调用网络;方法与数据在 gitgalaxy 仓库中。评论褒贬不一:一名用户称文章为“slop”,作者表示自己很努力并欢迎建设性批评,另一名用户询问是否主要由 LLM 撰写,还有一名用户觉得有趣。作者回应说感谢关注,并将写作描述为对数据的直接陈述。
相关文章

原文

Software archaeology · The DOOM study

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

same function namesame code, unchanged

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

sorted correctly, names hiddenguessing (labels shuffled)

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

match with the original's wiringsame program, functions shuffled

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

What it can't tell you

  • Family across languages from look-alikes. Look-alike functions only work within one language. Across languages only the wiring carries a signal, and only for the closer translations.
  • Why Java, Kotlin and C# show nothing. Those translations reorganised the most and chopped the code into tiny pieces: 34% of the Java port's functions and 43% of the Kotlin port's are three lines or shorter, against under 2% in the C copies. A lost design and a test that can't see it look the same here.
  • Whether two programs play the same. This measures the shape of code, not what it does.

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.

  • The full study Every table behind this article, the checks, and the commands that regenerate them.
  • The 44 programs (the original and its 43 descendants) Each descendant's source repository, the exact commit measured, its language, licence and stated lineage.
  • The engine, as measured GitGalaxy at commit 6c7c6c1bc, the one version every number here comes from.
  • The fingerprint scanner The tool that compares programs by shape alone: look-alike functions, wiring maps, the blind test and the drift timelines.
  • The DOOM runs The scanner's DOOM inputs and results, including the subsystem map, the declared lineage and the blind-test output.
  • Method: reading a score against chance Why every similarity here sits next to a shuffled version of the same program.
  • Method: the blind test How functions were sorted with every name hidden, and the checks against chance and a scrambled call graph.
  • The data on this page One JSON file with every measurement the charts and tools above draw from (no source code). Download the data (doom-dna-data.json)

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

Measurements only: no program's source code is reproduced, and only programs with a clear licence are included. Function names and file paths appear where they help read a result. "DOOM" is used descriptively; this project is not affiliated with or endorsed by id Software, ZeniMax Media or Microsoft. Quake and Quake II (GPL-2.0) and Lua (MIT) were scanned as unrelated controls.
联系我们 contact @ memedata.com