AI agents
Quantum Forge ships machine-readable docs and a headless path, so a coding agent can scaffold, run, and verify a quantum game without a browser. This page is for the agent and for whoever is configuring it.
Where the docs live
- /llms.txt on this site: the whole API in one flat file. Point an agent at
https://docs.quantum.dev/llms.txt. node_modules/quantum-forge/QUANTUM_FORGE.mdin any project that installed the core package: quantum properties, gates, entanglement patterns, measurement, lifecycle, recording, rendering, input, and performance, with examples throughout. It matches the installed version, so it is the safer reference when a project is pinned to an older release. Available offline oncenpm installhas run.
Claude Code skill
Pass --claude-skill at scaffold time and the CLI copies QUANTUM_FORGE.md into .claude/skills/quantum-forge/SKILL.md, giving you /quantum-forge in Claude Code.
There is no post-hoc install command yet. To add the skill to an existing project, copy the file yourself:
mkdir -p .claude/skills/quantum-forge
cp node_modules/quantum-forge/QUANTUM_FORGE.md .claude/skills/quantum-forge/SKILL.mdNon-interactive setup
The core needs no scaffold: npm install quantum-forge. To check the install, copy the example game, which never prompts:
npx -y quantum-forge example my-pong
cd my-pong && npm testnpm test plays Quantum Pong headless against the WASM. example needs core 3.1.0 or newer.
If the project uses the optional engine, pass every flag to its scaffolder and it never prompts:
npx -y quantum-forge-engine init my-game \
--template starter --platforms web --edition qutrit --no-claude-skillFrom quantum-forge-engine 1.3.0 the starter also writes CLAUDE.md and AGENTS.md into the project.
Verifying quantum code headlessly
The same code runs in Node with no Vite plugin and no browser. In Node the loader finds the WASM inside the installed quantum-forge package, so ensureLoaded() is all a script or a vitest file needs. useQuantumForgeBuild("qubit") picks the Qubit Edition the same way it does in a page. Node 22 or newer.
// check-quantum.ts, run with: npx tsx check-quantum.ts
import { ensureLoaded, quantum, measure } from "quantum-forge/quantum";
await ensureLoaded();
const a = quantum([false, true]).flip(); // a starts true
const b = quantum([false, true]); // b starts false
a.iSwap(b, 0.5); // (|10⟩ + i|01⟩)/√2, anti-correlated
const [va, vb] = measure(a, b);
console.log(va, vb); // always true false or false true
a.dispose();
b.dispose();Call setWasmBasePath() only when the WASM files live outside the package, for example a custom build. In Node it takes a file URL:
import { pathToFileURL } from "node:url";
import { setWasmBasePath, ensureLoaded } from "quantum-forge/quantum";
setWasmBasePath(pathToFileURL("/opt/wasm/quantum-forge-qubit").href);
await ensureLoaded();getWasmBasePath() reports where the loader will look, which helps when a load fails.
Gotchas
Things an agent gets wrong on the first attempt, in roughly the order it hits them.
Start from the core. quantum-forge is the SDK and needs nothing else. Add quantum-forge-engine only when the project wants its Engine, renderers, or input system. The core's quantum-forge command runs example itself and hands everything else (init, add-system, validate, doctor) to the engine CLI.
Imports split across two packages. quantum-forge owns /quantum, /logging, and /vite-plugin. Everything else (/engine, /rendering, /input, /events, /collision, /audio, /entities, and the rest) comes from quantum-forge-engine, and a core-only project has none of them.
Declare properties with quantum(). quantum([false, true]) or quantum(["red", "green", "blue"]), or quantum(3) for values 0, 1, 2. new Quantum() throws: the constructor is private. There is no manager to create first. Code that still uses QuantumPropertyManager, getModule() and snake_case calls like i_swap is 2.x code; see Migrating to 3.0.
Gates are camelCase methods on the handle. a.hadamard(), a.inverseHadamard(), a.iSwap(b, 0.5). The game-word aliases run the same gate: superpose is hadamard, next is cycle, previous is shift, phase is clock, and flip is cycle on a two-value property. flip() and y() throw on anything but two values.
iSwap requires a fraction. inverseHadamard and swap take none. The other gates take an optional first fraction; leave it out for the discrete gate.
Predicates go in { when: [...] }. b.flip({ when: [a.is(true)] }) is a CNOT. A predicate can name a declared value or its index, and it must read a different property than the one the gate acts on.
There is no entangle(). Entanglement is what an interaction leaves behind: a two-property gate like iSwap, or a gate whose predicate reads another property.
Measurements return declared values. quantum([false, true]).measure() gives false or true, not 0 or 1. measure(a, b) measures several at once and returns one value per property.
Dispose what you are done with. dispose() measures the property, which collapses anything entangled with it, and frees the qudit. Any later call on that handle throws. The engine's EntityManager disposes the handles on an entity it removes.
Dimension is capped at 3. Shipped builds are Qutrit (up to three values per property, 12 qudits) or Qubit (two values, 20 qubits). quantum() throws past the loaded build's maximum.
Engine subclasses must implement getHelpers() and reset(), and getState() returns Readonly<TState>. Never assign into state. Spread it into setState.
The recorder takes no arguments. new QuantumRecorder(), then startRecording() before creating the handles you want to replay. stopRecording() and getLog() return { version: 1, entries }. new QuantumRecorder(manager) is the 2.x form and throws; that recorder is now LegacyQuantumRecorder.