How It Works
Terminal Version
The game uses a custom double-buffered screen renderer built on raw ANSI escape codes. Each frame, the screen buffer is populated with characters and colors, then flushed to stdout as a single optimized write. Input is handled via Node.js raw stdin mode. Terminals only provide key-press events, not key-release, so a key counts as held until its characters stop arriving, and the decay window is checked once per frame. Keys that act on the press rather than the hold get a longer window than the movement keys, so that the first character of an OS auto-repeat is not read as a second press. That single write is synchronous, so a terminal falling behind blocks it and the loop with it; the input layer tracks how long each frame took and discounts the time it was blocked, since a loop that was not reading is no evidence a key went quiet.
Browser Version
The browser version (index.html) is a self-contained HTML file that faithfully reproduces the terminal game as a canvas-based character grid. Each character cell is drawn to an HTML5 Canvas using a monospace font, matching the exact same rendering pipeline: screen buffer, perspective projection, tunnel drawing, entity rendering, HUD, and menus. The grid dimensions adapt dynamically to the browser window size: the font is drawn at its full size wherever the window has room for at least the 60x20 the game is laid out against, and shrinks toward that floor in a window that does not, so every cell the buffer holds has somewhere on the canvas to be drawn. Keyboard input maps directly to the same control scheme. All game logic — collision detection, entity spawning, difficulty scaling, scoring, and debug modes — is identical to the CLI version.
Six things differ, each because the medium allows or demands it. High score persistence uses localStorage, which the terminal has no equivalent for, so the CLI version keeps a best score for the session only. The damage screen shake offsets the canvas by a few pixels in the browser, while the terminal has no subpixel positioning and jolts the play area by a whole character column instead. Sound is synthesized with the Web Audio API: the engine names the events either way, queueing a cue for the frame it has just simulated, and only the browser turns those names into tones. A cue is played only when the audio context is actually running: a browser that blocks sound until the page has been touched hands back a suspended one, and the cues raised meanwhile are dropped rather than queued, so a run opened straight from a ?mode= link is silent until the first key and then plays from that moment on rather than releasing everything it missed. A browser that permits the sound outright — a returning player's high media engagement, or the site given a sound permission — hands back one already running, and the same link plays from its first frame. The CRT overlay is CSS laid over the canvas rather than anything drawn into the character grid, so C toggles it in the browser and it does not exist in the terminal. The colour scheme follows the system's own: a light desktop re-inks the palette the grid resolves through and repaints the page behind it, which a terminal has no say in, since there the colours are the terminal's. And a touchscreen gets a thumbstick and two buttons laid over the canvas, feeding the same two key maps the keyboard does — revealed on the first touch rather than on a capability check, because a laptop with a touchscreen is nearly always being driven by its keyboard.