Developer runs real DOOM inside Claude Code's statusline
Erkan DOGAN built a system that renders the 1993 DOOM engine live inside Claude Code's statusline, using four native extension points — statusline, `UserPromptSubmit` hook, MCP server, and slash command — with no patched binaries or VS Code extensions required.
Score breakdown
Understand the limits of Claude Code's Ink-based TUI renderer — especially its cell-width miscounting with 24-bit ANSI and Unicode 13 glyphs — before building any live-updating statusline widget or terminal UI extension.
- 01The 1993 DOOM engine renders live inside Claude Code's statusline using the `doomgeneric` library.
- 02Four Claude Code extension points are used: statusline, `UserPromptSubmit` hook, MCP server, and slash command — no patched binaries or VS Code extensions.
- 03Each DOOM frame is written atomically as 24-bit ANSI to `/tmp/doom-in-claude/frame.ansi` and read by the statusline on every refresh.
Erkan DOGAN built a working DOOM-in-Claude-Code integration using only four of Claude Code's existing extension surfaces: the statusline, a `UserPromptSubmit` hook, an MCP server, and a slash command. A background daemon runs the `doomgeneric` DOOM engine and atomically writes each rendered frame as 24-bit ANSI to `/tmp/doom-in-claude/frame.ansi`. The statusline reads that file on every refresh. Keystrokes typed into the chat prompt (`w/a/s/d/f`) are intercepted by the `UserPromptSubmit` hook and forwarded as DOOM inputs; anything unrecognized passes through to Claude normally. The MCP server exposes `doom_look`, `doom_move`, and `doom_state` tools, enabling Claude itself to play. The core gameplay pipeline required a 900-line C wrapper and a small statusline shell script.
Claude Code's TUI is built on Ink — a React-for-terminals framework — which maintains a virtual-DOM-like cell grid and emits only diffs rather than full redraws.
The real challenge was rendering stability. Claude Code's TUI is built on Ink — a React-for-terminals framework — which maintains a virtual-DOM-like cell grid and emits only diffs rather than full redraws. A single DOOM frame spans roughly 3,000 cells (120 × 28), generating approximately 6,000 SGR escape sequences and around 129 KB of data per refresh. Ink's diff engine occasionally miscounts cell widths, and those miscounts compound: after roughly 30 frames, cells Ink believes are at column 42 are physically at column 43, causing colors to paint on the wrong glyphs until the image becomes noise.
DOGAN's first fix was a Perl normalizer that padded every RGB triple to exactly 3 digits, replaced 1-byte ASCII spaces with 3-byte U+2800 Braille blanks for stable byte counts, stripped DEC private mode sequences, and collapsed duplicate reset codes — achieving 97% byte-identical successive frames. The drift persisted because the normalizer only ran in the `chafa` rendering path, while the user was actually on the sextant renderer (a native C fallback). Unicode 13 sextant glyphs (`U+1FB00`–`U+1FB3B`) are not recognized by most string-width libraries, so Ink cannot measure their width, causing every sextant-heavy cell to nudge the column tracker fractionally off with each frame.
Key facts
- 01The 1993 DOOM engine renders live inside Claude Code's statusline using the `doomgeneric` library.
- 02Four Claude Code extension points are used: statusline, `UserPromptSubmit` hook, MCP server, and slash command — no patched binaries or VS Code extensions.
- 03Each DOOM frame is written atomically as 24-bit ANSI to `/tmp/doom-in-claude/frame.ansi` and read by the statusline on every refresh.