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

@demystify/play-runtime

v0.2.0

Published

The shared Demystify Play engine — fixed-timestep loop, input intents, DOM/canvas surfaces, packaged audio, the zero-network trap and the shared game mini-kit.

Readme

@demystify/play-runtime

The shared engine every Demystify Play game runs on: fixed-timestep loop, device-independent input, DOM and canvas surfaces with theme awareness, packaged audio, a zero-network trap, and the mini-kit primitives games reuse.

npm install @demystify/play-runtime

Budget: ≤35 KB gz, because it loads on every game page.

The loop

import { createLoop } from "@demystify/play-runtime";

const loop = createLoop({ tick: (i) => step(i), draw: (alpha) => render(alpha) });
loop.start();

Fixed timestep, always. If update took a variable delta, the same input on a 60 Hz phone and a 120 Hz laptop would produce different states and replays would not reproduce. Two failure modes are handled explicitly: catch-up is capped so a long stall drops time instead of spiralling, and the loop stops on visibilitychange/blur — a rAF loop left running in a hidden tab is the single biggest battery complaint driver for web games.

Note: TICK_MS is 1000/60, which is not representable in binary floating point. Repeated subtraction leaves a residue, so an exactly-N-ticks frame silently produced N−1 ticks until an epsilon was added to the accumulator comparison. The bug made games run slow at a rate that varied by machine.

Input — intents, not devices

import { createInput, bindInput } from "@demystify/play-runtime";

const input = createInput();       // "left" | "action" | "undo" | …
const unbind = bindInput(el, input, cellFromEvent);

A game never sees a KeyboardEvent. That buys four things at once: keyboard completeness becomes structural (there is no mouse in the vocabulary), gamepad and touch come free, replays are device-independent, and the automated keyboard-only test drives the same path a player does. Pointer position is reported in grid cells, never pixels — a pixel would make a replay depend on the viewport.

Canvas that follows the theme

import { createCanvasSurface, readTheme, watchTheme } from "@demystify/play-runtime";

The DOM follows a theme for free; a canvas does not, so a canvas game that hardcodes its palette is permanently one theme. watchTheme watches both the data-theme attribute and the prefers-color-scheme query — watching only one leaves a canvas stuck in the wrong palette when the OS appearance changes.

The zero-network trap

import { withNetTrap } from "@demystify/play-runtime";

const { violations } = await withNetTrap(() => playASession());

Replaces fetch, XMLHttpRequest, WebSocket, EventSource, navigator.sendBeacon and new Image().src with recording throwers — including the two people forget: sendBeacon fires during unload and is invisible to most capture tooling, and an image src is a request that never looks like one. Packaged data: URIs are allowed. Intended for tests and development; production relies on CSP plus an end-to-end capture.

The mini-kit

makeGrid / flood / neighbours (immutable, edge-clipped, reading-order deterministic) · createUndoStack · createCountdown / createCooldown (ticks, never wall-clock) · createHintBudget / createStuckDetector · STATE_MARKS / CATEGORY_MARKS.

State marks bind colour and glyph as one indivisible value, so "never colour alone" cannot drift. categoryMark refuses a ninth category rather than reusing a colour — a board needing more than eight simultaneous categories has a working-memory problem no palette fixes.

Licence

Apache-2.0 · Demystify Systems