# An AI ported WinQuake to C#, and per-frame hashes prove it bit-exact

> Source: <https://dev.to/andre_ad2623514c/an-ai-ported-winquake-to-c-and-per-frame-hashes-prove-it-bit-exact-15f1>
> Published: 2026-10-24 13:30:48+00:00

How can a developer prove code converted completely by an AI from one language to another?

Surely not by reading it: nobody reviews 32,000 lines of C# line by line against 90,000 lines of 1996 C and catches a float that should have been a double.

Edict is an experiment: how to make the translation prove itself. It is a C# port of WinQuake's engine, written mostly by an AI coding agent, that computes exactly what the C program computes - every server frame, every client frame, every rendered pixel, every mixed sound sample - and shows it with state hashes against a C reference build, frame for frame.

**Repository:** [github.com/AndreKfm/Edict](https://github.com/AndreKfm/Edict) · **Windows and Linux packages:** [github.com/AndreKfm/Edict/releases](https://github.com/AndreKfm/Edict/releases)

AI coding agents can now produce a port of a real engine in days.

But what are the results, how to prove them?

Ported games are usually tested by playing them: it feels like the original, so it must be right?

But it can still differ in the last bit of a velocity, the colour of a particle or the timing of a sound.

So the question for Edict was not *"can an AI port Quake?"* but *"can a port be made to prove that it is exact, so that it does not matter who wrote it?"*

With two side conditions that make it harder: no allocation in the game loop, and no slowdown against the C original, which is a tough task for C#.

There is, more than one, and they are good projects:

SharpQuake reimplements GLQuake in C# on OpenTK, Quake2Sharp ports Quake II, and Managed Doom ports Linux Doom and plays all of the original v1.9 demos in sync.

They show that a port *plays* like the original; Managed Doom's demo compatibility is a strong check of that. Edict asks a stricter question - does it *compute the same state*, bit for bit, after every frame, renderer and mixer included - and the method to answer it is the point of the project.

The obvious ground truth, `quake.exe` from 1996, cannot be used for that.

It does not expose its state, and it is not reproducible: `sin`, `rand`, `sprintf` and `atof` come from whatever C library it was built against, so the same source computes slightly different bits on Windows and Linux, or with another compiler.

So the first thing built is a reference build of id's GPL source release:

`-O2 -ffp-contract=off` (no fused multiply-adds), SSE scalar math, `-fno-strict-float-cast-overflow`.` sin`, `cos`, `tan`, `atan`, `atan2`, `rand`, `strcmp`, `sprintf` and `atof` to implementations of the harness's own (the trigonometry after fdlibm); the C# side ports exactly those. Consequence, stated openly: the reference is "WinQuake with a pinned libm", not the 1996 binary.
Input comes from recorded tapes instead of a keyboard and the frame clock is fixed, so a run is a pure function of its input and can be repeated exactly. That makes it a measuring instrument.

It is checked like one, too: `-O0` against `-O2`, AddressSanitizer and UBSan builds, Windows against Linux all write the same records.

The C# port runs the same tapes and demos and compares its hashes with the reference's, record by record. The first differing bit fails, and the tooling narrows it down to the entity and the field where the two part ways.

The corpus covers the server, the client, over 150,000 presents of the software renderer, and 172,765 frames of mixed sound.

For AI-written code the next question is obvious:

**are the gates themselves good enough?**

That is answered by **mutation testing**. 451 mutants - an off-by-one in a trace, a `<` that should be `<=`, a sum in float that C does in double, a forgotten client drop - are applied one at a time, and the gates must fail. All are detected except 34 known blind spots, each written down with the reason the corpus cannot see it (usually an exact equality the frame clock never hits). Where a mutant survived, the corpus or the gate was extended until it was caught, or the reason it cannot be was written down.

Most of a port is careful translation. What makes it hard is the long tail of places where C and C# quietly differ - exactly the places a fluent translator, human or not, gets wrong without noticing:

Each of these showed up as a failing frame, not as a vague feeling that something plays differently.

Gating against a 64-bit, sanitized reference turned up two genuine bugs in the original source: the sound loader reads past the end of a file, and the IP ban check bans every client on 64-bit Linux. Both are fixed by small patches in the reference (twelve in total: 64-bit fixes, hook points for the measurements, these two bug fixes).

A Roslyn analyzer enforces the allocation rule statically: methods carry a `[NoAlloc]` attribute, and every callee must use the same atteibute or a reviewed allowlist entry, so the guarantee composes across the call graph. The allocation gates also measure it: 0 bytes in every frame that does not load or run a console command.

It runs at 0.72 to 1.08 of the C reference's time per frame - on par with C.

Claude Opus 5.5 (Anthropic), working as a coding agent in Claude Code, wrote most of it: the C# code, the harness, the tools, the documentation.

More interesting is how AI and human interacted:

The plan, each step with what it found, and the decisions are in the repository ([`docs/PLAN.md`](https://github.com/AndreKfm/Edict/blob/main/docs/PLAN.md), [`docs/adr/`](https://github.com/AndreKfm/Edict/tree/main/docs/adr)), and every commit is marked as co-authored. What transfers beyond Quake is the recipe: pin a reference, hash its state, gate the new code against it record by record, and test the gates by mutation. It applies to any migration of old code where the behaviour has to stay exactly the same, whoever does the translating.

Edict is playable: a window on Windows and Linux, keyboard and mouse, Quake's sound mixer, the exact software renderer, and by default an OpenGL 3.3 renderer with GLQuake's look that only reads the exact state, so both renderers run the same game. Multiplayer works over UDP, with a dedicated server, and interoperates with the reference's own network build. It runs on LibreQuake's free data; no id data is involved.

Download the package for your system from the [releases](https://github.com/AndreKfm/Edict/releases), unpack it, and start `play.cmd` (Windows) or `./play.sh` (Linux); the script fetches LibreQuake's free data on the first start. To check the proof yourself, [`docs/TESTING.md`](https://github.com/AndreKfm/Edict/blob/main/docs/TESTING.md) explains how to build the reference, generate the corpora and run the gates; the CI does the fast part on every pull request.

*Quake is a registered trademark of id Software LLC. Edict is not affiliated with or endorsed by id Software, ZeniMax Media or Microsoft.
WinQuake's source is licensed under the GPL, and so is Edict.
This article was drafted with Claude and edited by the author.*
