chuggie
GBA game engine — write Game Boy Advance games in tish
- BPM
- 155
- GRADE
- SSS
- LEVEL
- 8
- RELEASES
- 4
- YEARS
- 2026–2026
- chuggie.dev
- schlopai/chuggie
- schlopai/chuggie-toolchain
- schlopai/tish-gba
- npm @schlopai/chuggie
- cargo tish_gba_scenepack
- cargo tish_agb_sio
- cargo tish_agb
- cargo tish_gba_game_engine



Back On The Handheld
Tish has had a Game Boy Advance backend since the beginning, tishlang_runtime_gba. Chuggie is the first thing I built that uses it.
Chuggie lets you write GBA games in Tish, running on agb 0.25 underneath. It has two layers, and each one works on its own. The low level layer is sprites, backgrounds, input, audio, save, timers, rng and logging, enough to build a whole game with nothing on top. The game engine layer sits above it as an RPG Maker style framework: entities and components with Unity-style start and update behaviors, a fixed per-frame pipeline, dialogue, scenes, and genre modules for grid RPGs, platformers, isoboard battles and versus fighting. Framework games can always drop down to raw agb.
I wrote down the rules for how the two layers talk to each other so the top one doesn't reach around the bottom one. There's also a toolchain: one Docker image that builds every example and takes headless screenshots in CI, so I can check cartridge output without a cartridge.
Zepa Studios was twenty-two years earlier, on the same hardware family.
Four Crates, One Token
v0.2.1 was all about publishing: get tish_agb, tish_gba_game_engine, tish_gba_scenepack and tish_agb_sio onto crates.io together, and stop right away if the token is missing instead of failing halfway through with two of the four published.
The same day @schlopai/chuggie went to npm at 0.2.3, and tish_gba_scenepack showed up on crates.io for the first time. The next day v0.2.3 made tishlang_runtime_gba a registry dependency instead of a path dependency, since a path only builds on my machine.
Two days after that came v0.3.2: one toolchain image, build every example, publish the ROMs. So by then CI was compiling every sample game and attaching playable cartridges to the release, which is where I wanted to be before writing any actual game code.
One Image, Every Example
Three days in, CI was already the bottleneck. chuggie went up on August 25, was first published to npm and crates.io on the 26th with v0.2.1, and v0.2.3 came on the 27th to make tishlang_runtime_gba a registry dependency instead of a path one. Four crates, two layers: tish-agb for sprites, backgrounds, input, audio, save, timers, rng and log, and an optional engine on top with entities, components, dialogue, scenes and genre modules.
The build matrix was around ninety jobs, and every one spent its time on apt, building libmgba from source, a rustup toolchain and a cargo install of agb-gbafix before running a single test. So on August 28 I split out chuggie-toolchain: one Docker image with libmgba 0.10.5 built headless, the pinned Rust nightly with rust-src and thumbv4t-none-eabi, stable with rustfmt and clippy, and the rest. The tag is the first twelve characters of the Dockerfile hash, so a tagged image never changes. The verifiers check exact frames and exact audio, so the nightly has to stay pinned.
That's v0.3.2: one toolchain image, every example verified, the ROMs published. v0.3.4 came the next day and moved all releases through sem so none of it gets run by hand.
before this
- Impact Game Experiment2010HTML5 game engine and level editor
- PuerTS Three.js2025Unity-compatible API over three.js
- Fable & Fortune2025Unity-like GameObject system in the browser