Skip to content

Quantum Forge setup ​

The WASM quantum simulator ships pre-built in the quantum-forge package. Load it once with ensureLoaded(), then declare quantum properties with quantum().

Quick start ​

bash
npm install quantum-forge

Then add the Vite plugin and call ensureLoaded(). To see a working setup first, npx quantum-forge example my-pong copies Quantum Pong, which ships inside the package, into a new project with the plugin already configured. See the Quick start.

The optional quantum-forge-engine package also scaffolds projects with this setup included: npx quantum-forge-engine init my-game. See the CLI.

Loading the module ​

The WASM module loads asynchronously. Await ensureLoaded() once at startup, before the first quantum() call:

typescript
import { ensureLoaded, startBackgroundLoad, quantum } from "quantum-forge/quantum";

// Option 1: start loading in the background (recommended)
// Call after your first render for faster perceived startup
startBackgroundLoad(logger);

// Option 2, or later on: wait until the module is ready
await ensureLoaded();

const coin = quantum([false, true]);

Calling quantum() before the module has loaded throws.

TIP

startBackgroundLoad() uses requestIdleCallback to begin loading without blocking the main thread. Call it early, then await ensureLoaded() where you first need a quantum property. ensureLoaded() hands back the same promise on every call, so calling it more than once is safe.

TypeScript and using ​

A using declaration disposes a handle at the end of its scope:

typescript
function rollDie(): number {
  using die = quantum(3).superpose();
  return die.measure();
} // die.dispose() runs here

using needs TypeScript 5.2 or newer and "ESNext.Disposable" in the lib of your tsconfig.json:

json
{
  "compilerOptions": {
    "lib": ["ES2022", "DOM", "ESNext.Disposable"]
  }
}

Projects made by npx quantum-forge-engine init have both. Code that never writes using needs neither: the package's types carry their own Symbol.dispose declaration, so a project on a default lib still type-checks.

Vite plugin ​

The framework includes a Vite plugin that serves WASM artifacts during development:

typescript
// vite.config.ts
import { defineConfig } from "vite";
import { quantumForgeVitePlugin } from "quantum-forge/vite-plugin";

export default defineConfig({
  plugins: [quantumForgeVitePlugin()],
  build: {
    rollupOptions: {
      external: [/quantum-forge-web-api/],
    },
  },
});

The plugin routes /quantum-forge/* requests to the built WASM files in dist/. No other configuration is needed.

Plugin options:

OptionDefaultDescription
wasmDir"dist"Directory containing WASM artifacts
servePath"/quantum-forge"URL path prefix for serving

If you use vite.config.js (not .ts or .mjs), add "type": "module" to your package.json.

Node and headless use ​

The same code runs in Node (tests, servers, agents) with no Vite plugin. In Node the loader finds the WASM inside the installed package: dist/ for the default build, or dist/quantum-forge-qubit/ after useQuantumForgeBuild("qubit"). You call the same functions in the same order as in a page:

typescript
// check-circuit.mts
import { ensureLoaded, quantum, measure } from "quantum-forge/quantum";

await ensureLoaded();

const a = quantum([false, true]).superpose();
const b = quantum([false, true]);
b.flip({ when: [a.is(true)] });  // CNOT: a and b now always measure equal

console.log(a.probabilities());
// [{ value: false, probability: 0.5 }, { value: true, probability: 0.5 }]

console.log(measure(a, b));      // [false, false] or [true, true]
a.dispose();
b.dispose();

This is how CI, unit tests, and AI coding agents should exercise quantum code. Nothing about the simulator needs a browser, so a plain Node script is enough to check that a circuit does what you think it does.

In Vitest, put await ensureLoaded() in a beforeAll. Every test file in a suite can call it, because it caches its promise.

getWasmBasePath() returns the path the loader imports from. Log it when a load fails to see where the loader looked.

Node 22 or newer.

Core 2.7.0 and earlier

In those releases the Node loader defaulted to the URL path /quantum-forge, which Node cannot import, so headless code had to call setWasmBasePath() with a file URL to the package's dist/ first. From 3.0 that call is only needed when the WASM files live outside the package.

Custom WASM base path ​

If you serve the WASM files from somewhere other than the default location, set the base path before loading:

typescript
import { setWasmBasePath, ensureLoaded } from "quantum-forge/quantum";

setWasmBasePath("/assets/wasm"); // call BEFORE ensureLoaded()
await ensureLoaded();

In Node, pass a file URL:

typescript
import { setWasmBasePath, ensureLoaded } from "quantum-forge/quantum";
import { pathToFileURL } from "node:url";

setWasmBasePath(pathToFileURL("/opt/wasm/quantum-forge-qubit").href);
await ensureLoaded();

setWasmBasePath() wins over the default whenever you call it. useQuantumForgeBuild() replaces any earlier setWasmBasePath(), so if you use both, call setWasmBasePath() second.

WASM output redirection ​

The loader routes the WASM module's stderr through the logger you pass to startBackgroundLoad(), as logger.warn(text, "QuantumForge/WASM"):

typescript
startBackgroundLoad(logger);

To take full control, initialize the module yourself with print and printErr callbacks:

typescript
import { getQuantumForge } from "quantum-forge/quantum";

const QF = getQuantumForge();
await QF.initialize({
  printErr: (text) => myLogger.warn(text),  // stderr
  print: (text) => myLogger.debug(text),    // stdout
});

TIP

This keeps WASM-level warnings in your game's logging system instead of losing them in the browser console.

Offline support ​

The core package ships a service worker file. Projects created with npx quantum-forge-engine init get public/quantum-forge-sw.js written for them, so there is nothing to copy.

For a manual install, or a project scaffolded before the file existed, copy it out of the package into your public directory:

bash
cp node_modules/quantum-forge/quantum-forge-sw.js public/

Then register it once, after page load:

typescript
import { registerServiceWorker } from "quantum-forge/quantum";

await registerServiceWorker(); // defaults to "/quantum-forge-sw.js"

registerServiceWorker() returns null instead of throwing when the browser has no service worker support or registration fails, so it is safe to call unconditionally. Pass a different path as the only argument if you serve the file from somewhere other than the root.

The service worker caches WASM artifacts cache-first. After the first load, the module works offline.

WARNING

The shipped quantum-forge-sw.js requires quantum-forge 2.6.5 or newer. Older packages did not include the file.

Editions ​

The published npm package ships two WASM builds, called editions. You choose one when you run npx quantum-forge-engine init:

EditionValues per propertyMax quditsWASM variant
Qutrit (default)2 or 312Default build, no extra config
Qubit220Loads from /quantum-forge-qubit/

The Qutrit Edition allows properties with two or three values, such as quantum([false, true]) or quantum(["rock", "paper", "scissors"]), at the cost of a lower qudit ceiling. The Qubit Edition allows only two values per property but raises the limit to 20 qubits, so more quantum objects can share one entangled state. quantum() throws when you declare more values than the loaded edition allows.

The qudit limit applies to one entangled state. Properties that never interacted don't count against each other, and a disposed property stops counting. See Performance.

Switching editions at runtime ​

If you chose the Qubit Edition during init, your generated main.ts already contains the call. To add it by hand:

typescript
import { useQuantumForgeBuild, ensureLoaded } from "quantum-forge/quantum";

useQuantumForgeBuild("qubit"); // call BEFORE ensureLoaded()
await ensureLoaded();

The Qutrit Edition is the default build and does not need useQuantumForgeBuild(). The call works the same way in Node, where it selects the variant folder inside the package.

WARNING

Call useQuantumForgeBuild() before ensureLoaded(). Once the module has loaded, the call is ignored with a warning.

Querying the active edition ​

After loading, check which build is active:

typescript
import { getMaxDimension, getMaxQudits } from "quantum-forge/quantum";

console.log(getMaxDimension()); // 3 (Qutrit) or 2 (Qubit)
console.log(getMaxQudits());    // 12 (Qutrit) or 20 (Qubit)

Future versions

Future versions will support more values per property and higher qudit counts. The current limits balance capability against WASM binary size and memory use.

Runtime queries ​

After loading, you can query the module's configuration:

typescript
import { getVersion, getMaxDimension, getMaxQudits, getMaxStateSize } from "quantum-forge/quantum";

console.log(getVersion());        // the package version, e.g. "3.0.0"
console.log(getMaxDimension());   // 3 (Qutrit) or 2 (Qubit)
console.log(getMaxQudits());      // 12 (Qutrit) or 20 (Qubit)
console.log(getMaxStateSize());   // 100,000: max basis amplitudes in one state vector

Older releases returned ".." from getVersion(). From 3.0 it reports the package version.

getWasmMemoryBytes() is also exported, but in current builds it always returns null. See Performance: WASM heap usage.

Attribution ​

The license requires visible attribution. getAttribution() returns the string to display, so you don't have to keep it in sync by hand:

typescript
import { getAttribution } from "quantum-forge/quantum";

console.log(getAttribution());
// "Powered by Quantum Forge — © Quantum Native — quantumnative.io"

Put it somewhere a player can find it: a credits screen, an about panel, a corner of the title screen. The loader also prints a badge to the browser console on startup, but that is a developer nicety, not the attribution.

Troubleshooting ​

"QuantumForge not loaded. Call ensureLoaded() first and await it." ​

Something called quantum() or another quantum function before the module finished loading. Await ensureLoaded() first.

WASM files not found in production ​

Make sure the WASM artifacts are copied to your public or static directory and that setWasmBasePath() points at the URL path they are served from.

Headless load fails ​

Log getWasmBasePath() to see where the loader looked. If the WASM files are not inside the installed quantum-forge package, point the loader at them with setWasmBasePath() and a file URL.

Powered by Quantum Forge