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

@8bitscript/random

v0.21.0

Published

Deterministic pseudo-random generators, explicitly seeded, the same code on every target: a 16-bit LCG at the bare import, a precomputed lookup table at ./table. Hardware entropy stays opt-in: ./entropy reads SID voice 3 on the C64 and POKEY on the Atari

Readme

@8bitscript/random

Deterministic pseudo-random generators, the same code on every target — VIC-20, C64, PET, C128, Atari 8-bit, NES, Commander X16, MEGA65, and web. Two, at two import paths, with the same three calls — and a third path, /entropy, that is one of the machine's own entropy sources where it has one:

pnpm add @8bitscript/random
import { random } from "@8bitscript/random"; // the default: a 16-bit LCG
// or
import { table } from "@8bitscript/random/table"; // a precomputed lookup table

random.seed(1234);
let roll: utinyint = random.range(6) + 1; // 1..6

| Call | What it does | | --- | --- | | seed(value) | Replaces the generator's whole state | | next() | One step of the generator; a byte, 0-255 | | range(bound) | next() % bound — a value from 0 up to (not including) bound |

table adds one more call, since it has no reason not to: at(index) reads the table directly with no state of its own, for a program that already keeps its own running counter (frames elapsed is the usual one).

Which one

| | @8bitscript/random | @8bitscript/random/table | | --- | --- | --- | | How next() works | state = state * 25173 + 13849, return the high byte | TABLE[index], then index++ | | Cost per call | A 16-bit multiply and add | One indexed load | | Period | 65536 | 256 | | Distribution | Good, not exact | Exact over every 256 consecutive calls — the table is a permutation of 0-255 |

Reach for the default generator first. Reach for /table where a 6502 multiply is specifically the thing a piece of code cannot afford — several picks in one frame, in a routine already fighting for cycles — and a 256-call period is not a problem for what it is picking. The two are interchangeable at the call site: switching is one import line.

Deterministic by default, explicitly seeded — never hardware entropy. The root AGENTS.md rule is that a program's ordinary random numbers must come from a small, seeded, fixed-state generator, and hardware entropy (a POKEY register, SID's oscillator 3, timing jitter) belongs behind its own separate, explicitly optional import. Both generators here are that default. Neither reads a register on any target — a program that wants a less predictable seed reaches for a machine's own entropy source (@8bitscript/atari8/random's POKEY counter, @8bitscript/c64/random's SID voice 3) and hands the byte it reads to either one's seed(). A program that has decided it wants the register on every draw, not just for a seed, says so with the import below.

/entropy: hardware where a machine has it

import { entropy } from "@8bitscript/random/entropy";

entropy.begin();                      // first statement of main() — see below
// every frame:
entropy.tick();
// when a number is wanted:
let cell: utinyint = entropy.range(16);

| Call | C64 | Atari 8-bit | The other seven | | --- | --- | --- | --- | | begin() | Claims SID voice 3 for noise, silenced (@8bitscript/c64/random's own begin()) | Nothing | Nothing | | tick() | Nothing — the oscillator free-runs | Nothing — POKEY's counter free-runs | One step of the default generator | | next() | A byte of the oscillator | A byte of POKEY's RANDOM | The default generator's next() | | range(bound) | that % bound | that % bound | that % bound |

One import; the compiler picks the file. src/entropy.8bs is the portable generator, and entropy.c64.8bs and entropy.atari8.8bs beside it are the machine twins the resolver takes for those two builds on its own — the same rule that makes screen.nes.8bs the NES's screen.8bs. A program never names them.

What it is for. A game that wants every spawn, every shuffle, to be unpredictable on the machines that can offer it, without carrying its own twin file per machine to say so. 2048 is where this was extracted from: its three rng.8bs files became this one import, and all nine of its builds are byte-identical to before — 2763 bytes on the 4K PET 2001, 3490 on the unexpanded VIC-20, 4599 on the C64, 3720 on the Atari 8-bit (0.11.0). The two hardware builds are smaller than the software generator would make them (40 and 70 bytes at 0.6.2, by 2048's own measurement), which is the whole case for reading hardware inside game logic rather than once at start-up.

What it costs. Replay. On the C64 and the Atari every range() reads a register, so a game cannot be reproduced from its start state or from a screenshot — a program that needs that keeps the bare import and seeds it once instead. And on the C64, begin() writes $D418 as a whole known byte (the SID's low registers are write-only), so it must run before anything else touches the chip; a program that also plays sound has to read @8bitscript/c64/random's header, not just add notes.

tick() once a frame. On the seven software targets a generator that only advances when asked plays the same game every run; stepping it every frame makes the sequence depend on the player's timing too, which is as much unpredictability as a machine with no entropy source can offer. The hardware twins make it empty, so the call inlines away to nothing there.

range() computes; it does not delegate. Each file writes range() as next-or-byte % bound rather than forwarding to random.range(): the forwarding form measured +8 bytes on every 6502 target (PET, VIC-20, C128, CX16, MEGA65 alike), because a call whose only job is to pass an argument on does not inline away. entropy.test.mjs checks the bodies stay that way.

Not yet on the C128 or MEGA65. Both have a SID and answer #fact(audio.entropy) true, but neither machine package has a ./random module yet, so /entropy is the software generator there until one exists — one more twin each, once it does.

Not called until you call it. A program that never calls seed() gets the same sequence every run. That is a feature for testing and for reproducing a bug from a screenshot, not an oversight — call seed() once, from whatever the program wants unpredictable (elapsed frames before the first key press is the usual shape on a machine with no hardware entropy at all), and every run after that differs.

Not cryptographic, on any target. Public, fixed steps over small state are exactly as guessable as they sound, table included. This is for game boards, shuffles, and enemy behavior — nothing a program needs to keep secret from the person playing it.

See src/index.8bs, src/table.8bs and src/entropy.8bs for each generator and the reasoning behind it.