npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

node-mgba

v0.2.6

Published

Scriptable, headless Game Boy and GBA emulator for Node.js powered by libmgba

Readme

node-mgba

CI npm version License: MIT Platform: Linux x64

Scriptable, headless mGBA emulator for Node.js — like PyBoy, but powered by libmgba for Game Boy, GBC, and GBA.

node-mgba provides native Node.js bindings to the C libmgba core. It is built for scripting, agent automation, and machine learning research, giving you direct programmatic control over the emulator:

  • Headless & fast: Runs without a GUI window at 1,000+ to 3,400+ FPS.
  • Controls & state: Step frames, inject button inputs, and save/load state in memory or to disk.
  • Direct memory access: Read and write directly to memory buses (WRAM, VRAM, HRAM) without socket overhead.
  • Screen & audio capture: Grab raw pixel buffers, encode to PNG/WebP, or stream audio/video frames.
  • Plugin decoders: Optional high-level memory decoders (includes Pokémon Red/Blue state parsing).

Originally built to power the 24/7 autonomous agent on Gemini Plays Pokémon (web viewer).

Installation

pnpm add node-mgba

Quickstart

import { Mgba } from 'node-mgba';

// Load ROM (supports .gb, .gbc, .gba)
const emu = await Mgba.load('./game.gb');

// Advance frames and send input
await emu.controls.tick(60);
await emu.controls.press('A');

// Read memory
const playerX = await emu.memory.read8(0xD362);

// Capture screenshot buffer
const pngBuffer = await emu.screen.toPng();

// Close when done
await emu.close();

Console Support & Limitations

| Platform / Model | Emulation & Controls | Direct Memory (read8, readBatch, slice) | Memory Snapshots (observe, GamePlugin.getState()) | |---|---|---|---| | Game Boy (DMG / SGB) | Supported (160×144) | Supported | Supported | | Game Boy Color (CGB) | Supported (160×144) | Supported | Supported | | Game Boy Advance (AGB) | Supported (240×160) | Supported (EWRAM, IWRAM, ROM) | Planned on Roadmap |

Note on GBA Memory Snapshots: GBA emulation, controls, audio/video streaming, savestates, and direct memory reads are supported. Multi-region snapshots (emu.observe({ memory: ... }) and GamePlugin.getState()) are currently limited to Game Boy (DMG/CGB/SGB) models and planned for GBA.


Common Tasks

Input & Frame Stepping

// Press returns a TurnResult with keyframes
const turnResult = await emu.controls.press('A', 8);
console.log(`Captured ${turnResult.keyframes.length} keyframes`);

// Hold a button across multiple ticks
await emu.controls.hold('B');
await emu.controls.tick(30);
await emu.controls.release('B');

// Run an input sequence
const sequenceResult = await emu.controls.sequence([
    { type: 'press', button: 'UP', holdFrames: 6, releaseFrames: 4 },
    { type: 'wait', frames: 10 },
    { type: 'press', button: 'A', holdFrames: 6, releaseFrames: 4 },
]);

Screen Capture & Cropping

// Capture full screen as PNG or WebP
const pngBuffer = await emu.screen.toPng();
const webpBuffer = await emu.screen.toWebp({ quality: 85 });

// Crop a sub-region with optional integer scaling (e.g. 2x, 4x)
const croppedPng = await emu.screen.crop({
    x: 16,
    y: 16,
    width: 32,
    height: 32,
    scale: 2, // 64x64 output
});

Reading & Writing Memory

// Read unsigned integers
const byte = await emu.memory.read8(0xC000);
const u16 = await emu.memory.read16LE(0xC001);
const u32 = await emu.memory.read32LE(0xC003);

// Read GBA regions directly
const ewram = await emu.memory.readRegion('EWRAM', 0, 64);
const iwram = await emu.memory.readRegion('IWRAM', 0, 64);

// Write to memory
await emu.memory.write8(0xC500, 0x42);

// Read multiple addresses in one call
const [x, y, mapId] = await emu.memory.readBatch([0xD362, 0xD361, 0xD35E]);

Savestates

// Save and load state files
await emu.states.saveToFile('./save.state');
await emu.states.loadFromFile('./save.state');

// In-memory state handles
const handle = await emu.states.save();
await emu.states.restore(handle);

Waiting for In-Game Conditions

// Wait until memory matches a condition (or timeout is reached)
await emu.waitFor({
    timeoutFrames: 300,
    condition: async (instance) => {
        const battleStatus = await instance.memory.read8(0xD057);
        return battleStatus !== 0;
    },
});

Video & Audio Streaming

import { Mgba, WebSocketMediaSink, FfmpegRecordingSink, maskToButtonNames } from 'node-mgba';

// Stream video and audio chunks over WebSocket clients
const wsSink = new WebSocketMediaSink({
    name: 'live-stream',
    clients: () => wss.clients,
});

// Or record gameplay directly to an MP4 file
const recorder = new FfmpegRecordingSink({
    outputPath: './gameplay.mp4',
    fps: 60,
});

// Custom MediaSink with per-frame button input tracking
const customSink = {
    name: 'input-tracking-sink',
    onVideoFrame: (packet) => {
        // packet.keys contains the 32-bit button bitmask active on this exact frame
        const buttons = maskToButtonNames(packet.keys);
        console.log(`Frame #${packet.frameIndex} rendered with active buttons:`, buttons);
    },
    onAudioChunk: (chunk) => {
        // stereo PCM audio
    },
};

// Pass sinks when loading the emulator
const emu = await Mgba.load('./game.gb', {
    mediaSinks: [wsSink, recorder, customSink],
});

Game Plugins & State Decoding

import { PokemonRedBluePlugin } from 'node-mgba/plugins';

// Attach game plugin
const pokemon = await emu.use(PokemonRedBluePlugin);

// Retrieve structured game state
const state = await pokemon.getState();
console.log(state.player.position, state.party);

Real-Time Emulation & Autonomous Agent Loops

import { EmulatorController, GB_FPS } from 'node-mgba';

// Launch autonomous 59.73 FPS real-time execution in a worker actor
const controller = new EmulatorController({
    romPath: './game.gb',
    realtime: true,
    fps: GB_FPS,
});

await controller.initialize();

// Send interactive key inputs or enqueue AI button presses
await controller.pressButtons(['START']);

// Stream 60 FPS video frames
controller.on('frame', (frame) => {
    // Handle VideoPacket (RGBA pixel buffer)
});

Performance

Measured on AMD Ryzen 9 5950X with Pokémon Blue (pnpm run bench):

| Metric | Throughput | Avg Latency | Notes | |---|---|---|---| | Headless Stepping | ~1,110 FPS | 0.90 ms / frame | Non-blocking worker_threads RPC | | Direct Core Stepping | ~3,420 FPS | 0.29 ms / frame | In-process native core | | Single Memory Read/Write | ~10,000 ops/s | ~100 µs / op | Direct WRAM bus access | | Batch Memory Read (50 addrs) | ~6,390 batches/s | 0.16 ms / batch | Single IPC round-trip | | Schema DSL Struct Decode | ~960,690 ops/s | 1.0 µs / struct | In-memory binary parser | | Full Game State Decode | ~22,270 decodes/s | 44.9 µs / decode | Populated party, bag, map, box | | Savestate Restore | ~2,280 restores/s | 0.44 ms / restore | Cycle-accurate state handle |

For full benchmarks and methodology, see the Performance & Benchmarks Guide.


Development & Testing

Running Tests

Tests run against bundled homebrew fixtures by default:

# Run test suite
pnpm test

# Run Pokémon Red/Blue plugin tests with a ROM dump
export POKEMON_ROM_PATH="/path/to/pokemon_blue.gb"
pnpm test

# Override test ROM or savestate path
export ROM_PATH="/path/to/game.gb"
export SAVESTATE_PATH="/path/to/game.ss0"
pnpm test

Environment Variables

| Variable | Default | Description | |---|---|---| | MGBA_LOG_LEVEL | warn | Native mGBA log level (silent, fatal, error, warn, info, debug). | | POKEMON_ROM_PATH | — | Path to Pokémon Red/Blue ROM for pokemon_red_blue.test.ts. | | ROM_PATH | Bundled homebrew fixtures | Test ROM path (.gb, .gbc, or .gba). | | SAVESTATE_PATH | Bundled homebrew fixture | Savestate fixture path (.ss0) for savestate tests. |

Interactive Web GUI Studio

node-mgba includes a built-in Vue 3 web interface for debugging emulation, testing input sequences, and inspecting real-time RAM decoding:

# Build the native shim, TypeScript, and GUI bundle
pnpm run build

# Launch the GUI server with a Game Boy ROM (opens at http://localhost:3456)
ROM_PATH="/path/to/game.gb" pnpm run gui

# Or run the GUI frontend in Vite development mode with hot-reloading
pnpm run gui:dev

Note: The GUI's state inspector and telemetry HUD panels are currently tailored specifically for Pokémon Red & Blue (displaying real-time party stats, inventory, badges, and map coordinates).


Subpath Exports

| Import | Description | |---|---| | node-mgba | Core emulator facade, controls, memory, and media sinks | | node-mgba/plugins | Plugin base class (GamePlugin) and built-in plugins (PokemonRedBluePlugin) | | node-mgba/schema | Declarative binary schema DSL for defining RAM decoders | | node-mgba/testing | In-memory MockMemoryReader for unit testing decoders | | node-mgba/browser | Browser media playback helpers (WebAudioPlayer, CanvasRenderer) |

Documentation