Vi-Fighter
vi motions meet a rogue-like terminal shooter
GoECSVIMRL/GAMPSCTerminalWASMTOML
About
A terminal action game on a custom Entity-Component-System engine. Go standard library only — no third-party dependencies, no CGO.
Movement, targeting and editing all run through vi motions: hjkl, w/b/e,
f/t, counts, operators, registers and macros, across normal, insert, visual,
search and command modes. Keyboard, mouse, or both.
Enemies adapt during the run in some map setups. Species parameters mutate against past-generation fitness, and spawn probability shifts toward routes that score.
Screenshots














Gameplay
Play in the Browser
The Go build compiled to WebAssembly, running in xterm.js. It opens in a new tab: the game claims the window and captures every keystroke and mouse event, so it cannot share a page with site navigation.
- Requires
- A modern browser with JavaScript and WebAssembly support, plus WebGL2 for the accelerated renderer. Without WebGL2, xterm.js falls back to its DOM renderer and the frame rate drops.
- First load
- The binary is several MB. TOML data is read from the embedded filesystem, so nothing further is fetched once it starts.
- Audio
- Silent. The synthesizer writes PCM to a system audio daemon; the browser build has no device to write to.
- Performance
- Below the native build. Go WASM is single-threaded, so the game loop and the renderer share one thread.
- Artifacts
- Long sessions can show rendering glitches from the WebGL addon. They clear within a few seconds.
Under the Hood
ECS Core
Entities are 64-bit IDs; components are contiguous sparse-set arrays; systems run in fixed priority order. A fixed-capacity spatial grid gives O(1) proximity, Bresenham line-of-sight and area queries without scanning the world. Large entities split header from members: an invisible phantom head carries logic and pathing, members sync to it.
Deterministic Physics
Q32.32 fixed point for every velocity, acceleration and angle, so mechanics match across hardware. 2D kinetics with boundary reflection for standard entities; 3D with Z-gravity, orbital mechanics and spring equilibrium for specific encounters, projected to screen space.
Adaptive Enemies
Dijkstra flow fields move swarms around dynamic obstacles without per-entity A*. An EXP3 bandit shifts spawn probability toward routes that score, and a streaming genetic engine mutates species parameters against past-generation fitness.
ANSI Renderer
Double buffered, TrueColor with a 256-color fallback that swaps glyph strategy. Masked layers (background, UI, transient, composite, field) allow grayout, dim and strobe post-processing without touching the UI. Sub-cell Unicode positioning carries the high-precision coordinates onto the terminal grid.
Procedural Audio
Pure Go PCM synthesis to PulseAudio, ALSA, PipeWire or OSS with zero CGO. Oscillators shaped by ADSR envelopes, mixed in float64 through a soft limiter. A conductor tracks the player’s APM and shifts BPM and arrangement density.
Input Routing
A parser accumulates counts, operators and motions into a semantic intent; input crosses into game logic over an MPSC queue, where a mode-aware router queries the world and emits concrete events. Macros record and replay intents, executed iteratively by the loop.
Technical Details
Runtime
- Platforms: Linux, FreeBSD, WASM
- Dependencies: Go standard library only, no CGO
- Data: TOML — game phases (hierarchical state machine), species, UI; embedded in the WASM build
- Input: keyboard, mouse, or mixed
- License: BSD-3-Clause
Build
- Clone
git clone https://github.com/lixenwraith/vi-fighter --depth 1- Build
cd vi-fighter && make release- Run
bin/vi-fighter