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

@logigator/sim

v0.7.0

Published

Logigator simulation engine: native Node.js addon, browser WASM build, and the sim CLI.

Readme

logigator-sim

The Logigator simulation engine: a fast, tick-exact logic-circuit simulator written in Rust. One engine core ships as:

  • a Node.js native addon (@logigator/sim/node), which runs the simulation on its own thread,
  • a WebAssembly build for the browser (@logigator/sim/wasm),
  • a native CLI (sim), for running, tracing and benchmarking boards,
  • and the Rust crate itself (sim-core), for embedding.

All of them run the same engine and produce the same per-tick results. The work done each tick is proportional to what changed, not to the size of the board.

Install

npm install @logigator/sim

The package is ESM-only and needs Node.js 22 or newer. It bundles prebuilt addons for Linux (x64 glibc and musl, arm64), macOS (x64, arm64) and Windows (x64), the WASM build, and the CLI. Standalone CLI binaries are also attached to each GitHub release.

Boards

A board is a list of components that read and drive links (wires). Each component names its type by numeric id and lists the link ids of its input and output pins; a component with no inputs (or outputs) can leave that list out. The links are numbered by these ids, from 0 to the highest one used:

const board = {
  components: [
    { type: 200, outputs: [0] },            // UserInput, drives link 0
    { type: 1, inputs: [0], outputs: [1] }, // NOT, reads link 0 and drives link 1
  ],
};

A link driven by several outputs is high if any of them is (wired-OR). Every component takes one tick to propagate. Components are identified by their position in components.

A link is invalid while its drivers disagree (some drive it high, some low), or while an SR flip-flop that was clocked with S and R both high drives it. An invalid link keeps its wired-OR value, which is what the circuit reads; the flag is only reported to the host (in every snapshot), so an editor can show it or ignore it. It does not spread to the components reading the link.

Optional fields on a component:

  • The parameters of its type, listed under Parameters below, such as a clock's period or a ROM's data. A parameter on any other type is an error.
  • negInputs / negOutputs: indices of pins (into inputs / outputs) that read or drive the inverted value, at no extra delay. A NAND is an AND with negOutputs: [0], and a negated clock input turns a rising-edge component into a falling-edge one.

The board is validated when it is loaded, and an error names the offending component. Boards can also be stored in a compact binary format (.lgb), loaded with Simulation.fromBinary.

Component types

Multi-bit values (addresses, words, select bits) are read least-significant bit first. Clocked components act on the rising edge; negating the clock input makes them act on the falling edge.

Gates. One output each.

| Gate | id | Inputs | Behavior | |-------|----|--------|---------------------------------------------| | NOT | 1 | 1 | Inverts its input. | | AND | 2 | ≥ 2 | High when all inputs are high. | | OR | 3 | ≥ 2 | High when any input is high. | | XOR | 4 | ≥ 2 | High when an odd number of inputs are high. | | DELAY | 5 | 1 | Passes its input through, one tick later. |

Arithmetic and routing.

| Component | id | Inputs | Outputs | Behavior | |------------|----|------------------------------------|-------------------------------------------|------------------------------------------------------------------------------| | Half adder | 10 | a, b | sum, carry | Adds two bits. | | Full adder | 11 | a, b, carry-in | sum, carry | Adds three bits. | | Decoder | 18 | n bits (1–16) | 2ⁿ | Drives the output numbered by the input high, all others low. | | Encoder | 19 | 2ⁿ | n bits (1–16), then an optional valid pin | Outputs the index of the highest high input (0 if none). The valid pin, if present, is high when any input is. | | MUX | 20 | s select bits (1–16), then 2ˢ data | 1 | Outputs the data input chosen by the select bits. | | DEMUX | 21 | s select bits, then data | 2ˢ | Routes the data input to the output chosen by the select bits, all others low. |

Flip-flops. Outputs Q and Q̄; they act on a rising clock.

| Flip-flop | id | Inputs | On a rising clock | |-----------|----|-------------|---------------------------------------------------------------------------------------------------------| | D | 13 | D, clock | Stores D. | | JK | 14 | J, clock, K | J sets, K resets, both toggle. | | SR | 15 | S, clock, R | S sets, otherwise R resets. S and R together set Q and make both outputs invalid until a clock with only S or only R. |

Clock, memory and I/O.

| Component | id | Inputs | Outputs | Behavior | |----------------------|-----|--------------------------------------------|---------------------------------------|----------| | CLK | 6 | halt | 1 | Stays low for period ticks, then high for period ticks (square wave) or for one tick (pulse), and repeats. A high halt input stops it and holds the output low; it restarts at the beginning of a cycle. | | ROM | 12 | address (1–16 bits) | data word (1–64 bits) | Outputs the word at the current address. | | RNG | 16 | trigger | ≥ 1 | Outputs new random bits on each rising edge of the trigger. Reproducible for the same board. | | RAM | 17 | address (≤ 24 bits), data, write-enable, clock | data word (as wide as the data input) | Outputs the addressed word; on a rising clock with write-enable high, writes the data input there. In the legacy mode the outputs change only on a rising clock, which writes or else reads the addressed word. The host can read and write its memory. | | UserInput | 200 | none | ≥ 1 | Driven by the host; see Input. | | Recorder | 201 | 1–32 data bits, then a trigger | none | On a rising trigger, records the data bus as a number. The host reads the records. | | Addressable register | 204 | address, data (dataBus bits), clock | ≥ 1: the stored bits | Outputs its stored bits, in words of dataBus (the last may be partial). On a rising clock, writes the data bus into the addressed word. The address has ⌈log₂(⌈outputs / dataBus⌉)⌉ bits; a write past the last output is dropped. The editor shows it as an LED matrix. |

Parameters.

| Component | Parameter | Value | |----------------------|---------------|-------| | CLK | period | Required. ≥ 1. | | CLK | mode | "square" (default) or "pulse". | | ROM | data | The contents as bytes (a Uint8Array or an array of 0–255), words packed LSB-first (word a starts at bit a × outputs); missing bytes are 0, extra ones an error. | | RAM | read | "async" (default) or "clocked" for the legacy clocked read. | | Recorder | capacity | Required. Records kept (1–2²⁰). | | Recorder | recordTicks | Also store each record's tick. | | Addressable register | dataBus | Required. Data bus width, 1 to the output count. |

Usage

Node.js

import { Simulation } from "@logigator/sim/node";

const sim = new Simulation(board); // or Simulation.fromBinary(buffer)

sim.run({ ticks: 10 });  // blocking; needs a `ticks` or `ms` bound
sim.link(1);             // true: NOT of a low input

// Run in the background. getStatus() and snapshot() stay responsive while it runs.
const done = sim.runAsync();      // unbounded until stop()
const snap = await sim.snapshot(false, 0);
snap.links;                       // packed link bits: link l is bit l & 7 of byte l >> 3
sim.getStatus();                  // { running, tick, linkCount, componentCount }
sim.stop();
await done;

sim.destroy();

snapshot(delta, threshold) returns a consistent copy of all links taken between two ticks. With delta: true it returns only the links that changed since the previous snapshot, falling back to a full copy when more than threshold (0–1) of them changed. Either way, snap.invalid is a Uint32Array of every invalid link id, ascending.

Browser (WASM)

import init, { Simulation } from "@logigator/sim/wasm";

const { memory } = await init();
const sim = new Simulation(board);

await sim.runAsync({ ms: 1000 }); // runs in short slices, yielding to the event loop in between
sim.run({ ticks: 100 });          // or blocking, with a bound

const view = sim.snapshot(false, 0); // zero-copy view into WASM memory
const links = new Uint8Array(memory.buffer, view.ptr, view.len);
const invalid = new Uint32Array(memory.buffer, view.invalidPtr, view.invalidLen / 4);

The WASM build has the same API as the Node addon, except that everything runs on the calling thread: snapshot, readState and readRecords return their result directly instead of a promise. A snapshot view is valid until the next call into the simulation.

The full TypeScript contract shared by both builds is published as @logigator/sim/contract.

Input

UserInput components are driven by queuing timed frames on them:

sim.submitInput([
  { comp: 0, frames: [{ state: [true], hold: 5 }, { state: [false] }] }, // a 5-tick pulse
]);

Each frame sets the input's output pins (null leaves a pin unchanged) and stays at least hold ticks before the next frame is applied. Because frames are applied inside the tick loop, their timing is exact regardless of how the host batches its ticks. A frame with coalesce: true merges into a still-pending coalescing frame, so rapid changes such as a toggled switch reach the circuit once. replace: true discards the frames still queued. inputProgress(out) reports how many non-coalescing frames each input still has pending.

Component state

RAM contents and recorder captures can be read by the host, and RAM can be written to (for example to load a program):

sim.stateVersions(compIds, versions); // two Uint32Arrays; a version changes when the state does
await sim.readState(ram, 0, sim.stateLength(ram)); // RAM bytes (sync in WASM)
sim.writeState(ram, 0, bytes);
await sim.readRecords(recorder, 0);               // { count, first, values, ticks? }

Poll stateVersions and re-read only the components whose version moved.

CLI

sim run   <board> [--ticks N] [--ms N] [--dump state.json] [--dump-format json|bin]
sim trace <board> [--ticks N] [--out trace.json]   # the state after every tick
sim bench <board> [--ticks N] [--ms N] [--repeat N] # ticks per second

Installed through npm, the command is npx logigator-sim. A board file is a JSON board, a .lgb binary, or a JSON fixture as in corpus/boards/, which wraps a board with a default tick count and timed input:

sim run corpus/boards/gates.json --dump state.json
sim bench corpus/boards/clk.json --ms 1000 --repeat 3

Rust

sim-core is not on crates.io yet; depend on it from git:

sim-core = { git = "https://github.com/logigator/logigator-sim" }
use sim_core::{BoardBuilder, CompType, RunConfig, Simulation};

let mut b = BoardBuilder::new(2);
b.component(CompType::UserInput, &[], &[0], &[]);
b.component(CompType::Not, &[0], &[1], &[]);

let mut sim = Simulation::from_descriptor(&b.finish())?;
sim.run(RunConfig { ticks: 10, ..Default::default() })?;
assert!(sim.link(1));

Building from source

You need stable Rust (1.88+), Node.js 22+, wasm-pack and the wasm32-unknown-unknown target for the WASM build, and optionally just.

just build        # CLI, WASM package and Node addon
just build-cli    # → target/release/sim
just build-wasm   # → crates/sim-wasm/pkg
just setup-node   # once: install the addon's npm dependencies
just build-node   # → crates/sim-node
just test         # fmt, clippy and the Rust test suite
just test-node    # the same checks through the Node addon
just test-wasm    # the same checks through the WASM build

Every board in corpus/boards/ is checked tick by tick against its recorded trace in corpus/golden/, through each of the surfaces. Benchmarks and their results are in corpus/bench/RESULTS.md; just --list shows all recipes.

| Path | Contents | |-------------------|--------------------------------------------------------------| | crates/sim-core | the engine | | crates/sim-cli | the sim CLI | | crates/sim-node | the Node addon and the @logigator/sim package | | crates/sim-wasm | the WASM build | | corpus | test boards, golden traces, benchmark boards and their tools |

License

GNU AGPL-3.0