Termdash
A Geometry Dash-style game rendered inside a terminal using Unicode Braille, with Bevy physics and its own graphical level editor.
Source codeThe terminal was the constraint
I had wanted to make a game for a while, and I wanted an excuse to properly learn Rust and Bevy. Terminals are also cool, so I started Termdash for Hack Club Horizons: a Geometry Dash-style game rendered inside one.
It began as a rendering experiment. Then I added physics, obstacles, triggers, portals, sprites, music, reusable prefabs, JSON levels, live testing and a separate graphical editor. At some point it became a fairly normal game engine wearing a terminal as a disguise.

Bevy gameplay rendered as coloured Unicode Braille.
Turning a game into text
A terminal cell does not give you much resolution. Unicode Braille helps because each character contains a 2 by 4 grid of dots, so one cell can represent eight image samples.
Termdash lets Bevy render the game normally, then converts the resulting image into Braille:
Bevy world
↓
rendered image
↓
2 by 4 pixel blocks
↓
Braille dots + colour
↓
terminal cells
Straight conversion worked, but thin spikes and lines disappeared when they fell between samples. The renderer slightly expands bright samples, ignores sufficiently dull pixels and boosts colour contrast before sending RGB values to the terminal. These small cheats make the result stable enough to play instead of flickering into Unicode soup.
The game itself does not know that it will become Braille. Bevy handles entities, transforms and rendering, while Avian2D handles physics. The terminal conversion sits at the end of the pipeline. That separation meant I could build normal game systems without writing an entire engine around terminal cells.
Building levels without editing JSON forever
Hand-writing level files got old quickly. Levels use reusable prefabs, so an object can inherit its visual, collider and behaviour while only overriding values such as position or scale.
That led to a separate egui editor. It can place and edit objects, save the level, launch the real game for testing, and undo or redo completed edits using bounded level snapshots.

The graphical editor and terminal game use the same level data.
Many level types needed the same Serde and Bevy reflection setup, so I wrote a small Rust attribute macro to add the repeated derives, handle optional fields and register each type with the app. It is more infrastructure than a square jumping over spikes should need, but learning those parts of Rust was part of the reason for making the project.
From rendering experiment to complete game
Termdash gave me one project where I had to connect Bevy’s ECS, physics, terminal rendering, proc macros, reflection, serialization, editor state and release builds.
It also contains an audio visualiser that looks like it reacts to the music but does not. The game needed bars moving in the background, so they move. That joke stayed because the README has always admitted it.
The unusual output was useful rather than decorative. It forced the renderer, gameplay and editor to stay separate, and turned a small terminal experiment into a complete playable system.