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

motely-wasm

v24.5.0

Published

Balatro seed search + per-seed analysis, powered by JAML (Jimbo's Ante Markup Language). Vectorized SIMD engine, compiled to WebAssembly.

Readme

motely-wasm

motely-wasm is the Bootsharp/WebAssembly package for MotelyJAML: the production Balatro seed-search engine, JAML loader, JAMLyzer analyzer, one-line JAML parser, and selected seed utilities exposed to JavaScript.

JAML is Jimbo's Ante Markup Language. YAML and JSON are the concrete syntaxes; both load to the same typed JamlConfig that the engine executes.

Install

npm install motely-wasm

Boot

Bootsharp exports a default runtime object plus named API namespaces. Subscribe to exported events and bind imports before boot().

This package ships embedded: the boot resources travel inside the module, so boot() takes no argument. getStatus() reports where the runtime is — boot when it returns BootStatus.Standby, and it's ready to use at BootStatus.Booted.

import bootsharp, {
  MotelyJaml,
  MotelyJamlyzer,
  MotelySearch,
} from "motely-wasm";

MotelySearch.onProgress.subscribe((p) => {
  console.log(`searched=${p.seedsSearched} matches=${p.matchingSeeds}`);
});

MotelySearch.onSeedMatch.subscribe((seed) => console.log(seed));
MotelySearch.onScoredResult.subscribe((result) => console.log(result.seed, result.score));

await bootsharp.boot();

Parse and validate JAML

Use fromYaml or fromJson once, then pass the returned JamlConfig to analyzer/search calls. Invalid filters fail loudly, and unknown keys raise errors too, so every mistake surfaces at parse time.

const jaml = MotelyJaml.fromYaml(`
name: example
deck: Red
stake: White
seeds: [AAAAAAAA, BBBBBBBB]
must:
  - voucher: Overstock
    antes: [1]
`);

const error = MotelyJaml.validate("must: [");
if (error) console.error(error);

Analyze seeds with JAMLyzer

const results = MotelyJamlyzer.analyzeSeeds(jaml);
for (const result of results) {
  console.log(result.seed, result.antes[0].voucher, result.antes[0].packs.length);
}

Scrollable analysis uses the streamStates object returned by a previous page. The state is seed-specific, so resume with a single-seed JamlConfig.

const first = MotelyJamlyzer.analyzeSeedsPaged(jaml, 10)[0];
const next = MotelyJamlyzer.resumeSeeds(jaml, first.streamStates, 10)[0];

Search

Call it, await it, use it — the promise resolves with the scored results:

const results = await MotelySearch.searchList(jaml);
for (const r of results) console.log(r.seed, r.score, r.tallies);

Each result is a MotelyScoredSeedResult: seed (string), score (number), and tallies — the raw per-clause hit counts, one per should-clause in JAML order. Tallies cross the boundary as an Int32Array; call Array.from(r.tallies) when a plain array matters.

Events stream alongside for live UIs: onProgress ticks while the search runs, onSeedMatch delivers each bare seed as it's found, onScoredResult delivers each typed result incrementally.

Available modes:

const fromList = await MotelySearch.searchList(jaml);
const fromRandom = await MotelySearch.searchRandom(jaml, 1000);
const fromWalk = await MotelySearch.searchSequential(jaml, 0n, 1n, 1);

searchSequential uses bigint batch indices because the C# parameters are long.

The fleet — one engine per web worker

A batch index is a seed prefix. batchChars names how many trailing characters a single batch enumerates, so one batch covers 35 ** batchChars seeds and the space holds 35 ** (8 - batchChars) batches. Batch 0 is the block of seeds beginning 1111…, batch 1 the next block, and so on up through GGGG…. Handing a worker a range of batch indices is the same as handing it a range of seed prefixes.

Give each worker its own contiguous block and the fleet needs no coordination at all:

const totalBatches = 35 ** (8 - batchChars);
const blockSize = Math.floor(totalBatches / fleetSize);

// worker w owns [w * blockSize, (w + 1) * blockSize) — the last worker takes the remainder
const startBatch = w * blockSize;
const endBatch = w === fleetSize - 1 ? totalBatches : (w + 1) * blockSize;
await MotelySearch.searchSequential(config, BigInt(startBatch), BigInt(endBatch), batchChars);

Blocks are disjoint by construction, so two workers never report the same seed and the fleet shares nothing: no queue, no locks, no mid-run coordination. A worker computes its own range from its own index, so it never needs to know the fleet size or agree with anyone. Losing a worker simply leaves its block unfinished, and every other block is untouched.

Search the seed space for however long the user cares to wait. The full 35⁸ ≈ 2.25 trillion seeds take days; real searches sample, then stop. Blocks make that sampling addressable — you can say which prefixes you covered, resume from a saved cursor, and hand block w to a second browser or a server just as easily as to a second worker.

Report progress per worker. Each worker owns its own counts, and summing them for a headline number is the consumer's job — a fleet has no single global tick to subscribe to.

Two environment rules keep a worker from hanging silently:

  • Use a module worker. new Worker(url, { type: "module" }). Chromium forbids dynamic import() inside a classic worker, and import maps don't reach workers, so pass motely-wasm's absolute URL and import() it inside.
  • Release self.onmessage before boot(). dotnet.js instantiates assets over the worker's global message channel and never resolves while an app handler holds it (dotnet/runtime#114918). Take the init message once, null the handler, and run all further traffic over a transferred MessagePort.

Stop a worker with worker.terminate() — it ends the batch mid-loop, immediately.

// worker.js
self.onmessage = (e) => {
  self.onmessage = null; // free the global channel for dotnet.js
  const { port, motelyUrl, jaml, startBatch, endBatch, batchChars } = e.data;
  (async () => {
    const engine = await import(motelyUrl);
    if (engine.default.getStatus() === engine.default.BootStatus.Standby)
      await engine.default.boot();

    const config = engine.MotelyJaml.fromYaml(jaml);
    const found = [];
    const onScored = (r) => found.push({ seed: r.seed, score: r.score });
    engine.MotelySearch.onScoredResult.subscribe(onScored);
    await engine.MotelySearch.searchSequential(config, BigInt(startBatch), BigInt(endBatch), batchChars);
    port.postMessage({ type: "finished", found });
  })();
};

Boot each worker's engine once and reuse it: booting is the expensive part, searching is not.

One-line JAML

A JAML clause can be written as one human line — the terse spelling of a clause, so it lives on MotelyJaml. Both calls delegate to the engine's JamlLine parser/formatter so packed item identity stays canonical.

MotelyJaml.validateLine("Eternal Blueprint in antes 1 or 2"); // null
MotelyJaml.canonicalizeLine("Showman in antes 1, 2");         // "Showman in antes 1 or 2"

Vocabulary

MotelyJaml.listItems(kind, query) serves the real engine vocabulary — jokers, vouchers, tags, bosses, and the rest — for autocomplete and agent grounding. Names come straight from the engine enums, so the vocabulary stays in lockstep with the engine by construction.

MotelyJaml.listItems("joker", "lucky"); // ["LuckyCat", ...] — case-insensitive substring match

Utilities

MotelyUtilities exposes seed math and keyword sequence helpers used by the CLI/provider modes.

MotelyUtilities.seedToTotalIndex("11111111");      // 66231629136n
MotelyUtilities.totalIndexToSeed(66231629136n);    // "11111111"
MotelyUtilities.searchIndexToSeed(0n, 8);          // "11111111"
MotelyUtilities.repeatCharKeywords(3)[0];          // "AAA"

Local development

From Motely.Wasm/:

npm test        # publishes the Release build, then runs the Node suite against dist/index.mjs
npm run test:ui # Playwright drives the real test UI in Chromium against the same artifact
npm run serve   # hand-drive the test UI at http://127.0.0.1:4173/
npm run pack:check

The test UI (testui/index.html) is a plain ES-module page with a CodeMirror 6 editor: live engine-driven lint and completion while you type, and a results table — feedback is continuous — live linting validates every keystroke, which is why the UI stays button-free. The Playwright specs in tests-ui/ prove the package where UX lives: a real browser.

Releasing, from Motely.Wasm/: sync "version" in package.json to <MotelyVersion> in the repo-root Directory.Packages.props, run npm test and npm run test:ui green, then npm publish.

Current coverage focus

The package test suite mirrors the C# behavior tests that are meaningful through the public WASM surface:

  • boot/runtime and version export
  • JAML YAML/JSON parse and validation strictness
  • JAMLyzer ante structure, event windows, score-by-analysis, and stream-state resume
  • real list/random/sequential searches
  • AND scoring, default source fallback, Hieroglyph pack-slot reachability, and luck-source regressions
  • One-line JAML canonicalization
  • seed math and keyword utility parity

Corpus-file loading and live ref-struct seed-router introspection remain native test concerns rather than JavaScript package behavior.