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

@zakkster/lite-rollback-local

v1.0.0

Published

BroadcastChannel-backed transport for @zakkster/lite-rollback. Same-machine, cross-tab rollback netcode. Ideal for tests, demos, and single-machine two-player play.

Downloads

76

Readme

@zakkster/lite-rollback-local

BroadcastChannel-backed transport for @zakkster/lite-rollback. Same-machine, cross-tab rollback netcode with no server, no signalling. Open the same page in two browser windows and they're already connected.

npm version sponsor Zero-GC npm bundle size npm downloads npm total downloads TypeScript Dependencies License: MIT

npm install @zakkster/lite-rollback @zakkster/lite-rollback-local

Built for development, smoke tests, and "two friends at one laptop with one window each" multiplayer.

import { createSession } from '@zakkster/lite-rollback';
import { createLocalTransport, connectPeer } from '@zakkster/lite-rollback-local';

const session = createSession({
  capacity: 32,
  fields: { paddleY: { type: Float32Array, length: 2 } },
  numPlayers: 2,
  simulate: (f, i) => {
    f.paddleY[0] += (i[0] & 1 ? -2 : 0) + (i[0] & 2 ? 2 : 0);
    f.paddleY[1] += (i[1] & 1 ? -2 : 0) + (i[1] & 2 ? 2 : 0);
  },
});

const transport = createLocalTransport({ channelName: 'my-pong-game' });
const peer      = connectPeer({ session, transport, localPlayer: 0 });

// Render loop:
function frame() {
  peer.tickLocal(readMyKeyboard());
  peer.drain();
  session.step();
  render(session.fields);
  requestAnimationFrame(frame);
}

What it provides

| Export | Purpose | |---|---| | createLocalTransport({ channelName, peerId? }) | Returns a Transport (send, onMessage, close, peerId) backed by BroadcastChannel. The HTML spec guarantees no self-echo, so messages are sent as raw Uint8Array with zero JS-side wrapper allocation. | | connectPeer({ session, transport, localPlayer }) | Wires a Session and a Transport together. Returns { tickLocal, drain, close, peerId }. Zero JS-side allocation in steady state -- the incoming-message queue is a flat Uint32Array ring buffer, not an array of objects. | | packInput(out, frame, player, input, inputWords) | Encode an INPUT message into a pre-allocated Uint8Array. Direct byte writes, no DataView allocation. 10 bytes for inputWords=1. | | unpackMessageInto(buf, inputWords, scratch, scratchOff?) | Hot-path unpack. Writes decoded fields into a caller-owned Uint32Array. Returns the message type (1..3) or 0 on failure. Use this in render loops. | | unpackMessage(buf, inputWords) | Convenience wrapper around unpackMessageInto that returns a plain object. Allocates the result; use for tests / non-hot paths only. Non-reentrant. | | MSG | { INPUT: 1, CHECKSUM: 2, PING: 3 } | | assertTransport(t) | Re-exported from the core for symmetry. |

Wire protocol

All multi-byte values are little-endian.

INPUT     msgType=1 | frame:u32 | player:u8 | input:(u32 x inputWords)
CHECKSUM  msgType=2 | frame:u32 | hash:u32
PING      msgType=3 | timestamp:u32

A 1-inputWord INPUT message is 10 bytes. At 60 Hz x 2 players, that's 1.2 KB/s per peer -- negligible on any reasonable channel.


Allocation guarantees

After construction, the hot loop (tickLocal + drain + session.step) is zero-allocation:

  • tickLocal: setLocalInput writes into a pre-allocated input ring, packInput writes into a pre-allocated send buffer, transport.send calls BroadcastChannel.postMessage (the structured-clone copy lives in C++, not JS).
  • The incoming-message queue inside connectPeer is one Uint32Array(1024 * (3 + inputWords)) allocated at construction. Each received message parses directly into the next slot via unpackMessageInto.
  • drain reads from the queue and calls session.feedRemoteInput(player, frame, number) -- passing a numeric input for inputWords === 1, or a pre-allocated Uint32Array reused across calls for inputWords > 1.

The test suite pins this with npm run test:gc -- 10 000 iterations of (2x tickLocal + 2x drain + 2x step) grows the JS heap by less than 128 KB total.

Backpressure: the receive queue caps at 1024 messages. If it fills, the oldest message is dropped (LRU eviction). At 60 Hz that's ~17 seconds of unread inputs -- you have a different problem if you're hitting this.


Why BroadcastChannel?

  • No setup. No signalling, no STUN/TURN, no peer discovery. Just pick a channelName and you're connected.
  • No latency. Cross-tab messages are delivered on the same event loop in most browsers -- perfect for testing.
  • Same-origin only. Which makes it ideal for development and totally useless for production internet play. For production, use @zakkster/lite-rollback-webrtc.

Caveats

  • BroadcastChannel is browser-only and Node 22+ at the top level (or Node 21+ inside worker_threads). For Node-side tests, build an in-memory transport against the Transport contract -- there's a reference implementation in the core README.
  • The transport doesn't filter messages by game version. If you open two tabs with different builds of your game on the same channelName, you'll desync. Include a version byte in your channelName or in the wire format.
  • unpackMessage (the convenience form, not unpackMessageInto) is non-reentrant -- it uses a module-level scratch buffer. Don't call it from inside an unpackMessage callback. Use unpackMessageInto if you need reentrancy.

Compatibility

| Target | Supported | |---|---| | Chrome / Edge 54+ | yes | | Firefox 38+ | yes | | Safari 15.4+ (iOS 15.4+) | yes | | Node 22+ (top-level BroadcastChannel) | yes | | Node 21 (worker_threads only) | partial | | Node <= 20 | no | | Bun / Deno | recent versions, yes |


Testing

npm test            # node:test, alloc tests skip cleanly
npm run test:gc     # node:test with --expose-gc, alloc tests run

24 tests across wire-protocol round-trips, message-type decoding (INPUT/CHECKSUM/PING), construction validation, connectPeer rollback correctness, multi-word input round-trips, allocation budgets, and a smoke test against the real BroadcastChannel transport (runs in Node 22+ and any modern browser).


License

MIT (c) Zahary Shinikchiev