The Long Middle
In April this year I found the original Dark Pawns source on GitHub, read the C files, and figured porting it to Go would take a few weeks. Four months in, it is clear I vastly underestimated the work.
Translating syntax is the easy part. The real work is proving that runtime behavior matches the original down to the byte. It seems obvious now.
The estimate, and the slog
The first commit was almost 1,300 lines and landed on April 17. By June I had a server that booted, a character that could walk around, and a growing suspicion that "a few weeks" was completely off. The commit history shows the dip: June sagged as progress slowed and hidden architectural complexity surfaced. I was genuinely thinking about giving up.
On July 12 I landed the first differential test harness, a tool I called the oracle. A few days later, Anthropic published their process for AI code migrations, validating the exact same judge-first approach. Seeing a frontier lab articulate the same problem reinvigorated the project. Commit volume climbed and stayed high. Each subsystem I completed exposed interactions and edge cases I could never see from the outside.
Breadth is a trap
For the first stretch, progress meant coverage: getting every command to run without crashing, walking room initialization, and touching each system once. Dark Pawns has 508 registered commands, and checking them off gave a false sense of momentum.
The problem is that "it ran without crashing" and "it behaves correctly" are entirely different benchmarks. Of those 508 commands, 183 are socials (smile, cackle, grovel) that emit static strings and rarely fail. Including them pushed reported coverage to 58.7% without actually exercising gameplay mechanics.
I needed a deterministic way to verify correctness.
The oracle
The oracle boots the original C server and the Go port side by side on local ports, drives both with identical scripted inputs, and compares their output byte-for-byte. When they agree, the scenario passes. When they disagree, the harness shows the exact byte where the outputs split.
Applying that principle to a MUD port enforces one rule: the game is the game. A player in 2026 should receive the exact same bytes and combat rolls as a player in 1999.
The PRNG stream is a clear example. When the first character logged into a fresh world, it became the implementor (max level). The Go port mistakenly ran a level-up routine that the C server skips, pulling two extra random draws at character creation. Because all game events share a single PRNG stream, every subsequent roll (combat hits, saving throws, skill checks) was offset by two positions. The server did not crash, but the entire simulation ran out of sync.
Three combat skills (bash, trip, headbutt) failed immediately in the oracle. Two other skills drew wrong numbers but happened to trigger the same branching outcome, passing by coincidence. Outcome-based unit tests would have masked the desynchronization; byte-level tracing caught the root offset immediately. That is something not even player testing would have uncovered.
Depth
After building the oracle, the goal shifted from verifying that commands ran to proving every branch against the C implementation.
Each bar in the right panel represents one command and the number of distinct behavioral cases pinned against the original C implementation (missing targets, peaceful rooms, fighting states, inventory constraints). bash alone requires a dozen cases. Today the depth ledgers track 3,260 catalogued cases across 245 commands, with 2,849 proven against the C binary and the rest categorized into unit tests, out of scope, or blocked. Each case cites the exact file and line in the original source that it matches. This is where August’s 731 commits went: one command at a time across 153 PRs.
Depth has its own gradient. Not every case is equally hard to prove.
We track these cases across five depth tiers, labeled D1 through D5. The shallow tiers (D1 entry gates and D2 core outcomes) are straightforward: argument parsing and basic command dispatch. The middle tiers (D3 state mutations and D4 edge/timing boundaries) get harder: complex multi-entity interactions, container nesting, and tick-boundary events where world state has to mutate identically. The deepest cases (D5) are where two servers run through a long shared session and drift apart by a single byte hundreds of function calls later. Those take an afternoon of tracing to verify, but that is where the subtle regressions hide.
Current status
Basic command breadth is largely in place. The remaining work is depth, specifically the spec-procs: 528 catalogued cases covering mob AI, shopkeepers, guildmasters, breath weapons, and quest logic.
Building the judge first and proving behavior at the byte level turns a massive legacy port into a tractable verification problem. It catches regressions immediately and ensures that modern Go produces the exact behavior of the 1990s original. Porting a game isn't finished when the Go binary compiles or when a player can walk around the temple. The real work is in proving that when an assassin backstabs or a spell is cast, the world responds with the exact same numbers it did twenty years ago.
If you want to help close the gap on the remaining depth ledgers and mob AI, take a look at the Contributing Guide or grab an open issue on GitHub. You can also connect to the live port via telnet at darkpawns.labz0rz.com 7777 or test it in your browser at /play.