v0.17.0 — browser play surface
Headline
The browser is now a first-class play surface. Boot Pokémon Emerald, scan a QR to your phone, and you're playing on the couch — no SDL window, no install on the phone.
gbax play emerald --no-sdl
# auto-opens http://127.0.0.1:8420/stream?mode=controller in your default browserWhat's new since v0.16.0
Browser stream (the big one)
GET /stream— self-contained HTML page with a stylish GBA-bezel viewer. Dark theme, status / fps / KB-per-frame HUD, 🔇/🔊 audio toggle.?mode=viewerjust watches;?mode=controlleradds the on-screen pad.WS /stream/ws— bidirectional WebSocket.- Server → client: framebuffer bytes. Default is raw RGBA8888 (240×160×4 = 153,600 B/frame, pixel-perfect, ~4.5 MB/s @ 30 fps).
?format=jpeg&quality=92switches to JPEG for constrained links. - Client → server:
{"type":"buttons","buttons":[...]}to drive input,{"type":"fast_forward","on":true}to TURBO. Sameruntime.set_buttonspath the SDL keyboard, USB gamepad, and couch already use — set-union all the way down.
- Server → client: framebuffer bytes. Default is raw RGBA8888 (240×160×4 = 153,600 B/frame, pixel-perfect, ~4.5 MB/s @ 30 fps).
WS /stream/audio/ws— live PCM s16le stereo @ 32 kHz via a newAudioBusfan-out. Browser decodes through an inlineAudioWorkletring buffer; click toggles, auto-resumes on first user gesture for iOS/Safari autoplay policy.- On-screen controller: D-pad, A/B (offset GBA style), L/R shoulders poking out the bezel, Select/Start pills, TURBO pill. Pointer Events handle multi-touch; keyboard shortcuts mirror the SDL play loop (arrows / X / Z / A / S / Enter / RShift). Dock layout flips between portrait (controls beneath the screen) and landscape (controls flanking the screen) — buttons never overlap the canvas.
gbax play --no-sdl
Skip the SDL window entirely. Runs a 60 Hz daemon thread, hosts the FastAPI server on the main thread, pops http://127.0.0.1:8420/stream?mode=controller in the user's default browser. Listen is implicit. The browser tab is the console.
Audio fixes
- Fast-forward dysync fixed. While
fast_forwardis set (local L-Shift, browser TURBO, or both),_on_audiomutes. Chipmunk audio gone. On the falling edge the SDL audio queue is cleared so the first normal samples don't sit behind a silence buffer. - New
gbax.api.audio_bus.AudioBus— many-producer many-consumer thread-safe PCM fan-out. Bounded per-subscriber queues; slow viewers drop chunks instead of blocking the play thread.
Gamepad support (USB / Bluetooth)
Plug in any pad SDL2's GameController DB recognizes (XInput, DualShock/DualSense, 8BitDo, Steam Controller, generic clones — most things with a USB connector). Multiple pads + the keyboard + the HTTP API + the browser combine via set-union. Hot-plug works. Default layout in docs/cli.md.
The couch — peer-to-peer plugin events
A new substrate for cross-instance plugin events. Two gbax instances on the same machine (or eventually across the internet) become "peers" in a "room" and trade typed events through a tiny broker.
gbax.couch.Broker+gbax.couch.Client— async Unix-socket fanout, capability-filtered receive.gbax.couch.handle.CouchHandle— sync façade the SDL play loop uses.gbax couch broker / send / listen / whoami / room-codeCLI.- Plugin API:
p.emit_couch("…"),@p.on_couch_event("…"),ctx.couch. Receive handlers fire on the SDL thread so they can safely touch the runtime. - Persistent identity at
~/.gbax/identity.json. Wireguard-style three-word room codes viagbax couch room-code(spunky-lilac-buck). - Demo plugin:
gbax.plugins.pokemon.emerald_couch— press G to gift a Master Ball to your first peer.
gbax browse — interactive ROM picker
gbax browse [<query>] — Textual TUI over the bundled 3,555-entry No-Intro index. Live filter as you type, ↑/↓ to navigate, Enter to download. Variants collapse into groups with (+N) badges; multi-variant groups open a modal picker. ROMs already in ~/.gbax/roms/ get a green ●. Empty search shows a curated list of ~100 famous GBA titles.
Landing page redesign
https://apiad.github.io/gbax/ — rotating 10-game screenshot showcase, three-audience cards, ascii art GBA bezel as the hero, and a 'Three commands' / 'Or, point-and-click' install path.
Try it
pip install --upgrade gbax
gbax download "pokemon emerald"
gbax play emerald --no-sdlMigrating from v0.16.0
No breaking changes. gbax play keeps the same CLI; --no-sdl is opt-in. All existing HTTP endpoints unchanged. Plugin API gained additions (emit_couch, @on_couch_event, ctx.couch); old plugins keep working.