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

Several levels, mouse control, god mode. Ubuntu 26.04, alacritty, tmux.

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