Ubunatic Coding

Stories · Terminal & TUI Stack · 👾 loom-games

Source (Codeberg) ↗ Copied from loom-games/docs/studies/2026-10-04-terminal-doom-story.md.

title: From a Snake prototype to Doom in the terminal

From a Snake prototype to Doom in the terminal

Loom Games started with a small ambition: make a Snake game that feels at home in a modern terminal. Its early work separated game rules from screen drawing, described the interface in ANSI mockups, and put tunable values in an embedded YAML specification. That foundation made a useful test bed for a larger question: how far could terminal rendering go?

The answer became loom-doom, a Go program that runs Chocolate Doom and renders its live frames as terminal characters. A C shim intercepts the SDL2 rendering path and shares frames through POSIX shared memory. Go maps pixels into ANSI colors and Unicode glyphs, with half-block, quad, or sextant modes and terminal or hybrid input. Capture mode uses an SDL surface to grab desktop input while the game remains visible in the terminal.

The codebase crosses Go game and terminal logic, C SDL helpers, shared-memory protocols, shell builds, YAML specs and JSON schemas. Documented desktop paths cover SDL2 and SDL3, GNOME Wayland, and X11. Doom's 320×200 source frame must fit terminal cells with different width and height while keeping game controls responsive.

A hard bug at the desktop boundary

The capture surface initially looked simple: make a window, grab the mouse and keys, then forward events. On GNOME Wayland, the SDL2 surface's alpha was treated as opaque. An SDL3 surface also became opaque in fullscreen. Early transparency attempts left a magenta glow because the compositor expected premultiplied color channels.

The fix came from testing the desktop mechanism directly. Canaries probed focus, event delivery, transparency, and fullscreen behavior. The design uses an SDL3 borderless, windowed-fullscreen transparent surface with premultiplied pixels, and keeps an SDL2 fallback. Ticket 036 records live GNOME Wayland verification; later docs explain that Wayland pointer lock requires the pointer to remain over the capture surface.

Unit tests can validate the shared-memory ring and control transitions, but only a real compositor exposed the opacity and pointer-lock behavior. Tickets 061 and 062 still track live checks for grab bindings and the Shift-F11 renderer cycle.

Work shaped as a trail of evidence

The repository links tickets, implementation commits, verification notes, and architecture docs. Issue 041 compared two ways to read Doom's hidden state: patch and rebuild the game to publish exact values, or infer values from its captured frame. The matcher first had 0 misses on 19 synthetic cases, then failed on real frames because the captured image was narrower than expected. After fixing geometry and accounting for changing HUD regions, a later run recognized the status bar in 119 of 120 samples and health and ammo in 114 of 115. The single miss was a screen transition; the ticket preserves that limit instead of presenting the synthetic result as proof.

The first benchmark was circular because test images used the same artwork the matcher knew. The workflow added real-frame fixtures and a ground-truth harness comparing images with values published by a game hook. The result makes uncertainty explicit and leaves a decision trail.

What agentic development contributed

Across the six weeks ending 4 October 2026, Git history contains 313 commits. The issue tracker has 63 numbered tickets, 41 closed at review time; 21 Go test files contain 170 test functions, and Go sources total about 13,000 lines. The project grew from Snake into a Doom rendering and capture system with performance probes, multiple render modes, specs, and tests.

The harness keeps agent work legible: tickets define tasks, embedded specs hold tunable intent, ANSI captures anchor visual design, and canaries probe uncertain behavior. Verification notes distinguish unit tests from live checks; sprint rules call for review and hygiene. The evidence for agentic development is the volume of traceable steps and preserved corrections. The breadth across terminal rendering, game integration, desktop input capture, and verification would be difficult for one developer to build and maintain at this pace without agent assistance.

Loom Games now shows a path from a compact terminal game to a demanding integration project. Its most reusable lesson is practical: keep the work small enough to inspect, test the boundary where the uncertainty lives, and write down what the measurement actually proved.

Facts

The project owner reports Doom tested and playable in Tilix, Foot, Kitty, Ptyxis, CRT, xfce4-terminal, and Alacritty. See ../TestedTerminals.md for the limits of the report. Git history also records performance contributions from Google Jules; see google-jules-performance-story.md.

Item Verified detail
Stack Go 1.26, C, Loom TUI, Cati, Cobra, SDL2/SDL3, POSIX shared memory, YAML specs, JSON Schema
Scope Snake and Chocolate Doom terminal rendering/input capture; Linux desktop paths include Wayland and X11
Size 313 commits in six weeks; 63 numbered issue files; 41 closed tickets; 21 Go test files, 170 test functions; about 13,000 Go lines
Dates Repository work began 2026-09-27; this review covers through 2026-10-04
References 7cdeb39, 092e2ff, dcb3f38, 74f93c3; issues 035-038, 041, 061-062