Vi-Fighter

vi motions meet a roguelike terminal shooter

GoECSVimGAOnline LearningMPSCTerminalWASMTOMLReplay

About

Vi-Fighter is a real-time terminal game that combines Vim-style navigation and text operations with typing, shooting, procedural encounters, adaptive enemy movement, and generative audio.

It is written in Go around a custom Entity-Component-System, an event-settled fixed-step runtime, and TOML-authored hierarchical state machines. There is no application CGO requirement; terminal, TOML, color, and logging support come from separately maintained Go modules.

Movement, targeting, and editing use vi motions: hjkl, w/b/e, f/t, counts, operators, registers, searches, and macros across Normal, Insert, Visual, Search, Command, and Overlay modes. Play with the keyboard, mouse, or both.

The application supports interactive play, caller-driven headless simulation, and terminal playback of recorded runs. The shipped interactive client uses one selected local cursor; the underlying event-driven cursor roster also provides a common path for replay, bots, and future remote producers.

Some encounters learn during the run. Route selection shifts using observed enemy fitness, while a streaming genetic engine evolves eye parameters from lifetime outcomes.

Screenshots

Gameplay

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

Play in the Browser

The constrained Go build runs in WebAssembly inside xterm.js. It opens in a new tab because the terminal captures keyboard and mouse input while the game has focus.

Requires
A modern browser with JavaScript and WebAssembly support. WebGL2 improves xterm.js rendering but is optional; the terminal falls back to its canvas renderer when the WebGL addon is unavailable.
First load
The WebAssembly binary and browser runtime assets are fetched on startup. Built-in TOML configuration and fallback content are embedded in the binary, so the game does not fetch scenario data after it starts.
Audio
Silent. The WebAssembly target disables the native process and device audio backends.
Performance
Usually below the native build. The Go WebAssembly runtime, game loop, and xterm.js renderer all execute within the browser environment.
Rendering
The WebGL addon is used when available. If initialization fails or its context is lost, xterm.js continues with the canvas renderer.

Under the Hood

ECS Runtime

Entities are 64-bit IDs backed by typed sparse-set component stores. An event-settled fixed-step scheduler runs systems in stable priority order, while a fixed-capacity spatial grid handles cell, area, proximity, and line-of-sight queries. Composite actors split logic and pathing headers from their visible, collidable members. Player cursors are ordinary ECS entities managed through an FSM-owned, bounded roster.

Float64 Physics

Motion, velocity, acceleration, geometry, and physics use cell-centered float64 values; integer points and areas describe discrete grid cells. The physics library provides bounce integration, steering, collision impulses, orbital constraints, springs, and 3D helpers projected onto the terminal plane. Manual-clock runs are reproducible for one build, but floating-point simulation is not presented as a cross-platform lockstep contract.

Adaptive Enemies

Aspect-weighted Dijkstra flow fields move groups around dynamic obstacles. Footprint-aware route graphs give large actors several distinct corridors. An online multiplicative-weights selector learns which routes perform well while retaining uniform exploration, and a streaming genetic engine actively evolves eye parameters from lifetime fitness.

Terminal Compositor

A layered compositor supports TrueColor and xterm-256 output, independent foreground/background blending, semantic masks, camera transforms, and grayscale, dim, strobe, and occlusion effects. Background-only cleaner trails preserve existing text as they cross it, while precise simulation positions are converted explicitly onto the terminal-cell grid.

Procedural Audio

Pure-Go synthesis, sound-effect mixing, and a three-slot sequencer produce PCM without application CGO. Native builds can stream it to common host audio tools, FreeBSD OSS, a null sink, or WAV capture. A conductor tracks player APM and adjusts tempo and arrangement intensity during play.

Input and Replay

The parser turns counts, operators, motions, commands, and mouse input into semantic intents. A mode-aware router applies them through the selected cursor and a bounded MPSC event queue. Macros record and replay intents, while the journal and manual-clock runtime support reproducible headless runs and terminal playback.

Technical Details

Runtime

  • Platforms: Linux, FreeBSD, constrained WebAssembly, and experimental Windows
  • Toolchain: Go 1.26.5
  • Dependencies: separately maintained Go modules for terminal, TOML, color, and logging; no application CGO requirement
  • Data: TOML-authored game phases, species, visuals, audio, and keymaps, with built-in fallback scenarios and content
  • Modes: interactive play, manual-clock headless simulation, and journal replay
  • Player model: FSM-owned 16-slot roster; the shipped client selects one local cursor
  • Input: keyboard, mouse, or mixed
  • Audio: starts muted; press Ctrl-S to cycle music and effects
  • License: BSD-3-Clause

Build

Clone
git clone https://github.com/lixenwraith/vi-fighter --depth 1
Build
cd vi-fighter && make release
Run
./bin/vif