Hacker-first, scriptable multi-console emulator (GBA + NES + SNES) with HTTP API
Project description
retrokix — Retro consoles you can drive.
Bring your keyboard, your gamepad, and your AI. They all work together.
retrokix is an emulator you can talk to. It plays Game Boy Advance, NES, and Super Nintendo games in a window with sound and a keyboard — and in the same session, it exposes the framebuffer, memory bus, and input as an HTTP API any coding agent can reach. Use it to speedrun Pokémon Emerald with Claude Code looking over your shoulder, or to test a neurosymbolic policy against 14,000+ hand-crafted GBA + NES + SNES environments where the level designers were genre masters. Same emulator, same session, same API. Whether you're the player or the algorithm, you're in the loop together.
Three commands
$ pip install retrokix
$ retrokix download "pokemon emerald"
$ retrokix play emerald
Pokémon Emerald, in a window, with sound. The wheel ships a prebuilt
mgba_libretro.so; no cmake, no apt-get, no $RETROKIX_CORE_PATH.
The game hub
$ retrokix serve
retrokix hub on http://127.0.0.1:8420
Open the URL: a fame-ranked grid of every owned ROM plus the top 24
unowned titles per console. Click an owned tile → it opens in a new
tab, playing. Click an unowned tile → it downloads from the bundled
archive.org mirror with a live progress bar, then opens in a new tab.
Type in the search box: every keystroke filters across all 14,000+
ROMs in the bundled No-Intro index. The hub spawns one
retrokix play --no-sdl subprocess per launched game on its own
port (libretro core crash kills one tab, not the hub) and reaps
children with no active viewers after 60 seconds idle.
The cooperative loop
Launch retrokix with both the keyboard surface and the HTTP API:
$ retrokix play emerald --listen --plugin retrokix.plugins.emerald_party
retrokix HTTP API listening on http://127.0.0.1:8420
plugin route: GET /plugins/emerald_party/party
plugin route: GET /plugins/emerald_party/slot/{idx}
Open another terminal — yours, or your coding agent's:
$ curl -s localhost:8420/plugins/emerald_party/party | jq '.slots[0]'
{ "species": 280, "level": 11, "hp": 33, "max_hp": 33,
"exp": 853, "friendship": 113 }
The plugin decoded Torchic's encrypted party slot for you. Want to know which menu the player is in right now? Take a screenshot in one atomic round trip:
$ curl -s -X POST localhost:8420/action \
-H 'content-type: application/json' \
-d '{"steps":[{"screenshot": true}]}' \
| jq -r '.screenshots[0]' | base64 -d > /tmp/now.png
Now you, or the agent, can look at /tmp/now.png and decide what to
do. The agent presses a button by sending the next action. The human
can keep playing — both inputs combine via set-union, neither blocks
the other.
That's the loop. The agent watches, thinks, sometimes nudges. You keep your hand on the keyboard. Together you write the next plugin, discover the next memory address, build the next algorithm.
What you get
- 14,000+ ROMs across GBA + NES + SNES in a fuzzy-searchable
bundled No-Intro index, ranked by Wikipedia 12-month pageviews with
a 0–5 star column in
retrokix browse.retrokix downloadpulls from the public archive.org mirror. - One HTTP API exposing the framebuffer, full memory bus, input,
cheat codes, save states, and an atomic
/actionfor multi-step agent plans. - Plugins that publish their own endpoints under
/plugins/<name>/.... The agent can write plugins for itself. - State tracker — supervised memory inference. Learn game memory by labeling, not by reading per-game wikis.
- Macros, save states, cheat codes for the player who just wants to play. ~6,700 cheat codes vendored from libretro-database; no network at runtime.
- GPU shaders (
crt-lottes, custom WGSL) when you want pretty. - Browser stream —
retrokix play --listenthen openhttp://localhost:8420/streamin any browser. Pristine raw RGBA frames over WebSocket plus a separate audio WebSocket for live PCM. Add?mode=controllerfor an on-screen GBA-bezel with D-pad- A/B + L/R + Select/Start + TURBO, dock-layout that flips between
portrait and landscape. Play from a phone, a laptop, or any window
in between. Pass
--no-sdlto skip the SDL window entirely — the browser tab becomes the console.
- A/B + L/R + Select/Start + TURBO, dock-layout that flips between
portrait and landscape. Play from a phone, a laptop, or any window
in between. Pass
- Game hub —
retrokix serveboots a small FastAPI app that serves a fame-ranked tile grid of your owned library plus the top 24 unowned per console, with full-library search across all 14,000+ bundled titles. Click to launch (new tab); click to download then launch. One subprocess per active game, idle reaper cleans up after itself. Same visual language as/stream. - One
pip install, one MPL-2.0 license, Linux x86_64 today.
Discovery toolkit
The AI-research / collaboration surface. Each entry links to its own docs/ page.
- HTTP API —
/frame,/buttons,/memory,/step,/action(atomic multi-step),/capture_state(record labeled snapshots),/stream+/stream/ws(live browser viewer with optional on-screen controller),/plugins/<name>/...(per-plugin namespaces),/plugins(active plugin listing). - Plugins — Python files that hook the play
loop AND publish HTTP routes. The bundled
retrokix.plugins.emerald_partyis the canonical example: a cookbook page walks through how it was built. - State tracker — capture / compile /
refine flow. Label what's true (
hp=22, scene=overworld); retrokix intersects labels across captures and infers where each value lives. - In-process Controller (automation.md) — the headless-script counterpart to plugins. Same scripting power without an HTTP round-trip.
The play surface
For the human-first reader. Each entry links to its details.
- Play window — SDL with sound, keyboard, save states. Hotkeys
are documented in docs/cli.md. Headlines:
Ctrl+1..9saves a slot,Shift+1..9loads,Left-Shiftis 8× fast-forward,F12screenshots. - Controllers — plug in any USB or Bluetooth pad (XInput, DualShock/DualSense, 8BitDo, Steam Controller, generic clones — SDL's GameController DB recognises them all). Multiple pads + the keyboard + the HTTP API combine via set-union. Hot-plug works. Layout in docs/cli.md#gamepad.
- Browser play —
retrokix play <rom> --no-sdlboots the runtime headless and pops a browser tab to a GBA-bezel viewer with on-screen controls. Touch / pointer drives the D-pad + A/B + L/R- Select/Start + TURBO; keyboard shortcuts mirror the SDL play loop's defaults (arrows + X / Z / A / S / Enter); audio streams live over WebSocket and plays through an AudioWorklet in the page.
- Cheats —
retrokix cheats <rom>lists;retrokix pin <rom> F1 max-moneybinds;F1-F9toggle in-game. Pins persist per ROM. - Macros — record a button sequence with
Ctrl+R, bind to any letter / digit / F-key, replay anywhere in-game. - Shaders —
retrokix play <rom> --renderer=wgpu --shader=crt-lottesvia the optional[gpu]extra. Full guide in docs/shaders.md. - Save state slots survive restarts. Per-ROM, in
~/.retrokix/saves/<rom-sha1>/.
Architecture
flowchart TB
subgraph clients[" "]
direction LR
kbd([Keyboard])
http([HTTP client<br/>script · LLM · shell])
browser([Browser tab])
end
subgraph cli["retrokix CLI (Typer)"]
play["retrokix play"]
serve["retrokix serve<br/>(hub)"]
other["search · download · state · macro · pin · …"]
end
sdl["SDL renderer<br/>window + audio + input"]
api["FastAPI server<br/>/frame /buttons /memory /step<br/>/action /capture_state /plugins/…<br/>/stream + /healthz"]
hub["Hub app<br/>/ landing · /api/search · /api/library<br/>/games/launch · /downloads/{id}/events<br/>+ idle reaper"]
children["one child play --no-sdl<br/>per active game"]
rt["EmulatorRuntime<br/>step · framebuffer · memory · save slots<br/>thread-safe via RLock"]
plugins["Plugins<br/>Python @on_* handlers + @p.route()"]
lr["LibretroCore<br/>~300 LOC cffi shim"]
so["mgba_libretro.so · fceumm_libretro.so · snes9x_libretro.so"]
kbd --> sdl
http --> api
browser --> hub
play --> sdl
play -.--> api
serve --> hub
hub --> children
children --> api
sdl --> rt
api --> rt
plugins --> rt
plugins --> api
rt --> lr
lr --> so
classDef ext fill:#eef,stroke:#33a,stroke-width:1px;
classDef core fill:#fef9c3,stroke:#a16207,stroke-width:1px;
class kbd,http,browser ext;
class so core;
EmulatorRuntimeis thread-safe via anRLock./actionand/capture_statehold the lock for their full duration; the SDL play loop blocks for the few ms each action takes, then resumes.- The SDL window, the FastAPI server, and plugin HTTP routes are independent clients of the runtime. They don't know about each other beyond the lock.
LibretroCoreis a ~300-line cffi wrapper around the libretro ABI. Swapping in another libretro core (vba-next, gpsp) is mostly a one-line config change.
Install
pip install retrokix # default install
pip install retrokix[gpu] # adds wgpu renderer + CRT-Lottes
One command on Linux x86_64. Other platforms fall back to the sdist
and need $RETROKIX_CORE_PATH set. Full coverage in
docs/installing.md.
Status
- Stable. v1.1.0. Works on Linux x86_64. macOS / Windows / ARM are PR-welcome.
- Multi-console. GBA via mGBA, NES via FCEUmm, SNES via snes9x —
all three shipped in the wheel. The runtime picks a core from the
ROM extension;
retrokix browseshows a per-row console badge and merges every library. - MPL-2.0. Same license as the underlying mGBA core. The bundled
FCEUmm core is GPLv2 (see
cores/LICENSE.FCEUmm) and the bundled snes9x core is under its own permissive non-commercial terms (seecores/LICENSE.snes9x). - No ROMs bundled.
retrokix downloadpulls from the public No-Intro mirror at archive.org. Use it for games you own; respect your local laws.
Roadmap
| Status | Slice |
|---|---|
| ⏳ | Predicate filters (@on_state_change("hp", below=20)) + ctx.wait sync API |
| ⏳ | HTTP /state — computed read of every tag in the compiled state map |
| ⏳ | GET/POST /savestate/<slot> over HTTP |
| ⏳ | xBRZ + multi-pass CRT shaders, shader hot-reload, parameter UI |
| ⏳ | macOS / Windows / aarch64 wheels |
| ⏳ | YAML user scripts — Ctrl+H runs a sequence |
Past releases: see GitHub Releases.
Credits
- mGBA by endrift — the GBA emulator core doing the actual heavy lifting. MPL-2.0.
- FCEUmm — the NES
core we ship for
.nesROMs. GPLv2. - No-Intro — the canonical ROM-naming and SHA-1 reference for both consoles.
- archive.org — hosts the No-Intro snapshot we point at by default.
- libretro-database — the cheat-code corpus we vendor.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file retrokix-1.1.0.tar.gz.
File metadata
- Download URL: retrokix-1.1.0.tar.gz
- Upload date:
- Size: 3.2 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
13905c4342c7e652fb9e4846eac382daab4f597dc4c58c132475d97d8f217eb6
|
|
| MD5 |
7006cc6927a251eb848ef33bd280c78e
|
|
| BLAKE2b-256 |
e1c9e056603879c994952bb276f173abc05ad5dfcfd8ac3f874d417541bc43c0
|
Provenance
The following attestation bundles were made for retrokix-1.1.0.tar.gz:
Publisher:
release.yml on apiad/retrokix
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
retrokix-1.1.0.tar.gz -
Subject digest:
13905c4342c7e652fb9e4846eac382daab4f597dc4c58c132475d97d8f217eb6 - Sigstore transparency entry: 1843189434
- Sigstore integration time:
-
Permalink:
apiad/retrokix@1416964d613e91d65c3487390b8c2573fafc60ae -
Branch / Tag:
refs/tags/v1.1.0 - Owner: https://github.com/apiad
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@1416964d613e91d65c3487390b8c2573fafc60ae -
Trigger Event:
release
-
Statement type:
File details
Details for the file retrokix-1.1.0-py3-none-manylinux_2_28_x86_64.whl.
File metadata
- Download URL: retrokix-1.1.0-py3-none-manylinux_2_28_x86_64.whl
- Upload date:
- Size: 4.3 MB
- Tags: Python 3, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3163ec37f72017746608fabf6b6702fb6aa89dd0bae019be7a9eae6f0d6c65fb
|
|
| MD5 |
93035a713bb3b80cc7e6e3647c44fb90
|
|
| BLAKE2b-256 |
fab242ee6c41cab0ddd95e002f840c6cbe2fb6b886f36195c34ad612fbe8c21c
|
Provenance
The following attestation bundles were made for retrokix-1.1.0-py3-none-manylinux_2_28_x86_64.whl:
Publisher:
release.yml on apiad/retrokix
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
retrokix-1.1.0-py3-none-manylinux_2_28_x86_64.whl -
Subject digest:
3163ec37f72017746608fabf6b6702fb6aa89dd0bae019be7a9eae6f0d6c65fb - Sigstore transparency entry: 1843189487
- Sigstore integration time:
-
Permalink:
apiad/retrokix@1416964d613e91d65c3487390b8c2573fafc60ae -
Branch / Tag:
refs/tags/v1.1.0 - Owner: https://github.com/apiad
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@1416964d613e91d65c3487390b8c2573fafc60ae -
Trigger Event:
release
-
Statement type: