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

active-bezel

v0.10.1

Published

Active Bezels: executable companions to a specific ROM. A .ab package runs once per emulated frame, reads the emulator's live memory regions, and renders the complete final scene — maps, HUDs, reconstructed world graphics, artwork — around or over the ori

Readme

Active Bezels

npm tests license

An Active Bezel is an optional executable companion to a specific ROM.

It ships as a .ab file — an ordinary ZIP holding a manifest, a main.wasm guest, and optional assets. It starts with the ROM, runs once per emulated frame, reads the emulator's live memory regions, and renders the complete final scene.

Despite the name, it is not limited to decorating empty side panels. The package owns the whole output picture: it can centre the original game, push it to one side, draw a live map of the world around it, overlay a HUD, or replace the picture entirely.

A bezel putting the running game on a CRT in a 1983 basement

Every pixel of that 1920×1080 frame is drawn by the bezel: a photographed room, the live game mapped onto the curved glass with a multi-pass CRT shader, and the photo's own alpha trimming the picture to the tube. The game is Nova the Squirrel, an open-source NES homebrew.

poll input → run one core frame → run the bezel tick for that frame
                                       │
                    reads the state the core just produced,
                    may write memory back, emits its scene
                                       │
                              composite and present

The core tick and the bezel tick are ordered on the same host frame. Nothing is asynchronous, so the visible frame and the state used to enhance it are deterministic.

Two things a package can do that nothing downstream of the framebuffer can:

Take layers apart. The redraw emits the empty backdrop, the solid tiles and the sprites as separate batches, and a draw can route each to its own surface. The PPU draws the level geometry and the open sky as one layer, so a filter over the finished picture sees them as the same pixels and must treat them identically -- which is exactly why post-processing reads as a wash laid over a game rather than as the game changing.

Take one entity out. hide_cell / hide_sprite drop a background cell or an OAM slot from the render entirely, and isolate_sprite renders only the marked slots onto a surface of your choosing. Entities are identified by slot, which is live machine state: colour cannot do it, because a game is free to draw the player and several unrelated entities from the same sprite palette -- and often does.

What this repository is

The format specification, the reference runtime, and the packaging tool.

It deliberately does not belong to any one emulator. Two hosts consume it today:

  • retroemu renders Active Bezels to a window.
  • romdev composites them headlessly, so an agent can capture the composite, the raw core framebuffer, and the guest's own command stream for the same frame — which is what makes a package verifiable rather than merely plausible.

| | | |---|---| | docs/ACTIVE_BEZELS.md | the format and ABI | | docs/HD_TEXT.md | replacing a game's 8-bit font with real letterforms | | sdk/active_bezel.h | C header for guests | | sdk/abi.json | machine-readable ABI | | bin/abtool.js | scaffold / verify / inspect / pack | | runtimes/ | prebuilt Lua / Python / JS / Ruby guests |

Install

npm install active-bezel
import { ActiveBezelRuntime } from 'active-bezel';

const runtime = await ActiveBezelRuntime.create({
  packagePath: 'Game.ab',
  host,                 // your emulator host
  romBytes,             // for exact hash matching
  platform: 'nes',
});

// after each core frame:
const composite = runtime.processFrame(gameRgba, gameW, gameH, frameNumber);

The runtime accepts either embedder shape — a host exposing the Emscripten module as .core or as .mod — so you should not need an adapter. See src/Host.js if you are wiring a third.

Authoring a package

With no toolchain at all. The package ships four prebuilt runtimes; copy one next to a script and you have a bezel:

| Runtime | Language | Size | Script | Docs | |---|---|---|---|---| | runtimes/lua/ | Lua 5.4 | 418 KB | main.lua | Lua bezels | | runtimes/python/ | MicroPython | 306 KB | main.py | Python bezels | | runtimes/js/ | QuickJS (ES2023) | 598 KB | main.js | JavaScript bezels | | runtimes/ruby/ | mruby 3.4 | 640 KB | main.rb | Ruby bezels |

Each links to its own README: quick start, the full API by area, and the shapes that differ in that language. Overview of all four.

All four expose the identical API -- including offscreen surfaces, GLSL effects and multi-pass .glslp presets -- so a bezel ports between them by changing syntax, not capability.

All four runtimes also ship the platform redraw profiles (nes, gb, md, snes, msx, pce; Ruby spells them NES..PCE): renderers that rebuild the game picture from the core's live memory regions instead of sampling its framebuffer, so a bezel can substitute sprite art, restyle layers or extend the playfield while everything untouched stays pixel-identical to the emulator. The renderers are one shared C core linked into every runtime, so the same call produces the same pixels in every language. Their exactness is measured against real-cart corpora, and every rendering rule they encode has a must-fail control in the test suite. See Platform redraw profiles.

mkdir -p my-bezel/assets
cp node_modules/active-bezel/runtimes/python/main.wasm my-bezel/
cp node_modules/active-bezel/runtimes/python/main.py   my-bezel/   # the scaffold
$EDITOR my-bezel/main.py

Each runtime's scaffold is a working bezel with a commented example of every capability: 2D shapes, the live game, TrueType and bitmap text, live memory reads, transforms, a decoded PNG, a tilted perspective quad, a per-vertex mesh, and a GLSL effect.

def tick(frame):
    ab.clear(ab.rgb(14, 16, 26))
    ab.draw_game(0, 0, 1440, 1080, ab.SAMPLE['NEAREST'])
    ram = ab.region('system_ram')
    ab.draw_text(font, 'HP %d' % ab.read_u8(ram, 0x0E), 1500, 80, 40, ab.rgb(255, 255, 255))

Iteration is edit, reload, look -- romdev loads an unpacked directory directly, so nothing needs packing until you ship. Errors draw an on-screen panel with the failing line instead of killing the session.

Then package it:

npx abtool verify my-bezel
npx abtool pack my-bezel my-bezel.ab
npx abtool inspect my-bezel.ab

pack emits a deterministic stored ZIP. .ab is deliberately ordinary — rename it to .zip and look inside.

With a compiler: any language that emits WebAssembly.

A bezel is a wasm module. There is no privileged language and no runtime this package has to bless -- the contract is small enough to implement anywhere:

  • import from one module, ab_host
  • export five functions: ab_abi_version, ab_init, ab_tick, ab_event, ab_shutdown (three more are optional, for guests that render their own framebuffer rather than emitting draw commands; a further optional ab_pre_frame — ABI 2 — runs BEFORE each core frame and may write live regions or override the input the core is about to see)
  • no WASI, no filesystem, no clock beyond what ab_host hands you

Anything that can produce a freestanding wasm32 module qualifies:

| Language | Notes | |---|---| | C / C++ | sdk/active_bezel.h is the reference binding. npx abtool scaffold my-bezel c starts one. | | Rust | --target wasm32-unknown-unknown, #[no_mangle] exports, extern "C" imports | | Zig | -target wasm32-freestanding, export fn | | AssemblyScript | TypeScript-shaped, compiles straight to wasm | | Go (TinyGo) | tinygo build -target wasm | | Odin, Nim, D, ... | anything with a freestanding wasm backend |

A C guest gets the platform redraw profiles too, without an interpreter in the middle: compile runtimes/common/ab_profiles.c plus the renderers (ab_render.c, ab_nes.c, ab_gb.c, ab_md.c, ab_snes.c, ab_msx.c, ab_pce.c) and ab_wasi_stubs.c into your module and call the ab_prof_* API from runtimes/common/ab_profiles.h directly -- it is the exact core the four language runtimes link, so the pixels are identical by construction. The profile core uses libc, so build with emcc (-s STANDALONE_WASM=1 --no-entry) rather than the scaffold's -nostdlib line; a complete NES redraw bezel lands around 15 KB of wasm.

The full ABI is machine-readable in sdk/abi.json -- every host import and guest export with its signature -- so a binding for a new language is a mechanical translation. A guest imports only what it uses: the prebuilt runtimes take 55 of the 58 available.

The four prebuilt runtimes above are themselves just wasm guests built this way, from C. They exist because most bezels are not worth a compile step, not because scripting is the supported path and compiling is not.

When a bezel breaks

A script error never takes the session down, and it is never silent. It is reported three ways:

  • On screen -- a panel across the top half of the frame in an embedded TrueType face, on an opaque backing, with the game still running underneath so the failure stays in context. The font is compiled into the runtime: the panel exists for when the package is broken, so it cannot depend on the package shipping one.
  • On stderr, prefixed AB-ERROR:, with no debug flag required. Ordinary ab.log output stays behind RETROEMU_DEBUG / AB_LOG -- a crash is not ordinary logging. AB_SILENT=1 suppresses it if your host surfaces the error itself.
  • Programmatically, as runtime.status().scriptError.

That third channel is the one that matters for tooling, and it is worth understanding why it exists. A script error is caught by its own runtime, so the host's processFrame call returns normally -- the host-side error field stays null and every automated health check passes while the screen shows a stack trace. Anything that decides "is this bezel working?" programmatically has to read scriptError, not error. romdev surfaces it as BEZEL_SCRIPT_ERROR.

Reloading clears the latch, so a fixed script recovers without restarting the host.

Dependencies

native-gles is required — the hosts that consume this package need GL to run. It is still bound lazily, because a native addon can be installed but unloadable (a missing prebuild, a failed install script, a Node ABI mismatch), and that must not take down package loading, manifest validation or the CPU compositor, none of which need GL. When GL is unavailable for any reason the runtime falls back to the CPU compositor, which is fully featured; the GL path is a performance option, not a capability one.

wasmcart is genuinely optional and loads on demand: only a guest declaring the legacy runtime.language: "lua54-wasmcart" needs it. The four prebuilt runtimes are plain wasm guests and never touch it.

Shader presets

Bezels can run RetroArch .glslp shader presets -- the multi-pass CRT chains, not just single shaders -- into an offscreen surface, then map the result anywhere in the scene.

No shaders ship with this package. Point it at libretro/glsl-shaders, or at the copy an existing RetroArch install already has (~/.config/retroarch/shaders/shaders_glsl/).

491 of the 609 shipped presets run (81%), including 309 of the 377 multi-pass ones -- 150/150 handheld, 61/75 crt. The rest use desktop-OpenGL constructs that GLES 3 rejects; presets are used exactly as published and their source is never edited, so those stay unsupported. crt-royale is one of them.

Licensing is why nothing is bundled: the upstream repository has no repository-level licence and the per-file terms are a mix of GPL, MIT, public domain, and no grant at all. Loading from a copy the user already has keeps that between the user and the shader author.

Full detail, including the per-category table and what specifically fails, is in docs/ACTIVE_BEZELS.md.

A caution worth repeating

A .ab can load cleanly, tick without trapping, and emit perfectly valid draw commands while being completely wrong about the game.

That is not hypothetical. An early package for a maze game declared in its profile.json that it read the player's room, X and Y; its readable main.c contained room-aware logic; and the compiled main.wasm shipped alongside them ignored the room byte entirely and plotted raw coordinates across a fake map, with two diagnostic bars that looked convincingly like progress meters. Unit tests proved the package loaded and drew. They proved nothing about whether the picture meant anything.

So: validate that the guest reads the regions it claims to, correlate its output against live state over a real play trace, and treat profile.json labels as research leads until evidence says otherwise. romdev's command and region-access tracing exists specifically to make this class of error visible.

Licence

MIT — see LICENSE.

The bundled example packages (diagnostic, lua-starter) are generic: they contain no game content and target no specific ROM.

No third-party shaders are redistributed here. src/imgdec.wasm is built from the stb_image.h vendored in runtimes/common (public domain / MIT dual licence); see tools/imgdec/SOURCES.md.