Quantum Forge Setup
The WASM quantum simulator ships pre-built with the framework. Call ensureLoaded() before any quantum operations.
Quick Start
npx quantum-forge-engine init my-game # scaffolds project with setup included
cd my-game
npm run dev # WASM already configuredThe scaffolder prompts for template, platforms, and edition. To run it without prompts, in CI or from an agent, pass every answer up front:
npx -y quantum-forge-engine init my-game \
--template starter --platforms web --edition qutrit --no-claude-skillInitialization in Code
Always call ensureLoaded() before any quantum operation:
import { ensureLoaded, startBackgroundLoad } from "quantum-forge/quantum";
// Option 1: Background preload (recommended)
// Call after initial render for faster perceived startup
startBackgroundLoad(logger);
// Option 2: Blocking load before quantum operations
await ensureLoaded();TIP
startBackgroundLoad() uses requestIdleCallback to begin loading the WASM module without blocking the main thread. Call it early (e.g., after your first render), then await ensureLoaded() later when you actually need quantum operations.
Vite Plugin
The framework includes a Vite plugin that serves WASM artifacts during development:
// vite.config.ts
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 additional configuration needed.
Plugin options:
| Option | Default | Description |
|---|---|---|
wasmDir | "dist" | Directory containing WASM artifacts |
servePath | "/quantum-forge" | URL path prefix for serving |
Node and Headless Use
The same code runs in Node (tests, servers, agents) with no Vite plugin. On core releases after 2.7.0 the loader finds the WASM inside the installed package, and useQuantumForgeBuild("qubit") selects the variant the same way as in a page:
import { useQuantumForgeBuild, ensureLoaded, getModule } from "quantum-forge/quantum";
useQuantumForgeBuild("qubit"); // optional, before ensureLoaded()
await ensureLoaded();
const m = getModule();On core 2.7.0 the default base path is the URL path /quantum-forge, which Node cannot import. Point the loader at the package's dist/ with a file URL instead, and do not call useQuantumForgeBuild() afterwards, because it would replace the path with /quantum-forge-qubit:
import { setWasmBasePath, ensureLoaded } from "quantum-forge/quantum";
import { createRequire } from "node:module";
import { pathToFileURL } from "node:url";
import path from "node:path";
const require = createRequire(import.meta.url);
const dist = path.dirname(require.resolve("quantum-forge/package.json")) + "/dist";
setWasmBasePath(pathToFileURL(dist).href); // Qutrit Edition
// setWasmBasePath(pathToFileURL(dist + "/quantum-forge-qubit").href); // Qubit Edition
await ensureLoaded();Node 22 or newer.
Custom WASM Base Path
If you serve WASM files from a non-default location:
import { setWasmBasePath, ensureLoaded } from "quantum-forge/quantum";
setWasmBasePath("/assets/wasm"); // must be called BEFORE ensureLoaded()
await ensureLoaded();WASM Output Redirection
By default the WASM module prints diagnostic messages to console.log (stdout) and console.warn (stderr). You can redirect these by passing callbacks to QuantumForge.initialize():
import { getQuantumForge, ensureLoaded } from "quantum-forge/quantum";
// Low-level: call initialize() directly with options
const QF = getQuantumForge();
await QF.initialize({
printErr: (text) => myLogger.warn(text), // redirect stderr
print: (text) => myLogger.debug(text), // redirect stdout
});The framework's startBackgroundLoad() / ensureLoaded() already routes printErr through the logger you pass in, so framework consumers get this for free:
startBackgroundLoad(logger); // WASM stderr → logger.warn("...", "QuantumForge/WASM")TIP
This is useful for capturing WASM-level warnings in your game's logging system instead of losing them in the browser console.
Running headless (Node and Vitest)
ensureLoaded() resolves the WASM module against a URL path, defaulting to /quantum-forge, which is where the Vite plugin serves it during development. Outside a browser there is no dev server to answer that path, so the import fails. Point the loader at the files on disk first:
import { setWasmBasePath, ensureLoaded } from "quantum-forge/quantum";
import { join } from "node:path";
setWasmBasePath(join(process.cwd(), "node_modules/quantum-forge/dist"));
await ensureLoaded();Use an absolute path. A relative one resolves against the module doing the dynamic import, not your working directory.
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:
// check-circuit.mts
import {
setWasmBasePath,
ensureLoaded,
getModule,
QuantumPropertyManager,
} from "quantum-forge/quantum";
import { join } from "node:path";
setWasmBasePath(join(process.cwd(), "node_modules/quantum-forge/dist"));
await ensureLoaded();
const manager = new QuantumPropertyManager({ dimension: 2 });
const m = getModule();
const prop = manager.acquireProperty(); // |0⟩
m.cycle(prop); // |0⟩ → |1⟩
m.hadamard(prop); // → (|0⟩ − |1⟩)/√2
console.log(m.probabilities([prop]));
// [{ probability: 0.5, qudit_values: [0] }, { probability: 0.5, qudit_values: [1] }]
const [value] = m.measure_properties([prop]);
console.log("measured", value); // 0 or 1
manager.releaseProperty(prop, value);In Vitest, put the setWasmBasePath() and await ensureLoaded() pair in a beforeAll. Both are idempotent across a suite: ensureLoaded() caches its promise, and calling setWasmBasePath() after the module has loaded has no effect.
Offline Support
Recent versions of the core package ship the service worker file itself. 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:
cp node_modules/quantum-forge/quantum-forge-sw.js public/Then register it once, after page load:
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 uses a cache-first strategy for WASM artifacts. 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, and registerServiceWorker() on those versions only worked if you wrote the service worker yourself.
Editions
The published npm package ships two WASM build variants, called editions. You choose an edition when you run npx quantum-forge-engine init:
| Edition | Dimensions | Max Qudits | WASM Variant |
|---|---|---|---|
| Qutrit (default) | 2–3 | 12 | Default build — no extra config needed |
| Qubit | 2 only | 20 | Loads from /quantum-forge-qubit/ |
Qutrit Edition supports both qubits (dim=2) and qutrits (dim=3), giving you three-valued quantum states at the cost of a lower qudit ceiling. Qubit Edition locks dimension to 2 but raises the limit to 20 qubits, letting you run more quantum objects simultaneously.
Properties in separate (non-entangled) systems don't count against each other's limit. Proper pooling (measure → release → reuse) keeps the active count low. See Performance.
Switching Editions at Runtime
If you chose the Qubit edition during init, your generated main.ts already contains the required call. If you need to switch manually or add it to an existing project:
import { useQuantumForgeBuild, ensureLoaded } from "quantum-forge/quantum";
// Select the qubit variant — MUST be called before ensureLoaded()
useQuantumForgeBuild("qubit");
await ensureLoaded();The Qutrit edition uses the default build and does not need useQuantumForgeBuild().
WARNING
useQuantumForgeBuild() must be called before ensureLoaded(). If the WASM module is already loaded, the call is ignored with a warning.
Querying the Active Edition
After loading, you can verify which build is active:
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 arbitrary qudit dimensions and higher qudit counts. The current limits are chosen to balance capability with WASM binary size and memory usage.
Runtime Queries
After loading, you can query the WASM module's configuration:
import { getVersion, getMaxDimension, getMaxQudits, getMaxStateSize } from "quantum-forge/quantum";
console.log(getVersion()); // e.g., "2.6.5"
console.log(getMaxDimension()); // 3 (qutrit) or 2 (qubit)
console.log(getMaxQudits()); // 12 (qutrit) or 20 (qubit)
console.log(getMaxStateSize()); // max basis amplitudes in one state vectorgetWasmMemoryBytes() 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:
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
"Quantum Forge not loaded"
Call await ensureLoaded() before using any quantum operations.
WASM files not found in production
Ensure WASM artifacts are copied to your public/static directory and setWasmBasePath() points to the correct URL path.