Core Concepts
Quantum Forge gives game objects real quantum state. The core usage pattern is:
- Attach quantum properties to things, giving them quantum state
- Apply operations to change that state. Gates transform without collapsing
- Use predicated operations to create entanglement. Controlled gates link properties together
- Measure to collapse quantum state and make it classical (the dramatic game moment)
Everything else (pooling, registries, recording) is tooling around this loop.
Programmer's Mental Model
If you know how to work with collections, you already have the intuition for quantum state.
Superposition is a weighted collection of values. Each entry is a basis state (a value, or a combination of values when multiple properties are involved), paired with a weight. A qubit in equal superposition is just a collection with two entries: |0⟩ and |1⟩, each weighted equally.
Operations are collection algorithms. They can modify the values, the weights, or both, and they apply to every element in the collection. cycle is "increment each value mod d". shift is "decrement each value mod d" (the inverse). You're running a transform over the whole collection at once.
Predicated operations are the same thing with a filter condition on each entry's value. "If the mth bit is 0, flip the nth bit." The operation only fires for entries where the condition holds. This is how entanglement happens: a predicated operation correlates two properties because it changes some entries and not others based on value combinations.
The quantum part is what the weights actually are. They aren't probabilities. They're complex amplitudes. This matters when an operation produces two entries with the same value: their amplitudes add. If they're in phase, the weights reinforce (constructive interference). If they're out of phase, the weights cancel (destructive interference). This is where quantum behavior diverges from classical randomness.
The probability of getting a particular value on measurement is the squared magnitude of its amplitude. That's it. Build up amplitude on values you want, cancel amplitude on values you don't.
Quantum Properties
A quantum property is a handle to a single qudit: a d-dimensional quantum digit. For dimension 2 (the most common), a property can be in state |0⟩, state |1⟩, or any superposition of both.
You create properties through a QuantumPropertyManager:
import { QuantumPropertyManager, ensureLoaded } from "quantum-forge/quantum";
await ensureLoaded(); // load the WASM simulator
const manager = new QuantumPropertyManager({ dimension: 2 });
const prop = manager.acquireProperty(); // starts in |0⟩Every acquired property starts in the definite state |0⟩. You assign it meaning by mapping it to a game object:
manager.setProperty("ball-1", prop);
// Later, retrieve it:
const p = manager.getProperty("ball-1"); // QFProperty handleDimension
The dimension parameter controls how many basis states each property has:
| Dimension | States | Use Case |
|---|---|---|
| 2 (qubit) | |0⟩, |1⟩ | Binary: exists/doesn't, alive/dead, left/right |
| 3 (qutrit) | |0⟩ – |2⟩ | Three-way: rock/paper/scissors, left/center/right |
The package ships two editions: the Qutrit edition (default) supports dimensions 2–3 with up to 12 qudits, while the Qubit edition supports dimension 2 only but allows up to 20 qubits. You choose your edition during npx quantum-forge init. Start with dimension 2 unless your design specifically needs three-valued states. Higher dimensions grow the state space exponentially. See Quantum Setup: Editions.
Operations (Gates)
Gates transform quantum state without collapsing it. All gates are called via getModule():
const m = manager.getModule();
// Put a property into superposition
m.cycle(prop); // |0⟩ → |1⟩ (flip)
m.hadamard(prop); // |1⟩ → (|0⟩ − |1⟩)/√2 (equal superposition)
// Rotate phase (invisible to measurement, but affects interactions)
m.clock(prop, 0.5); // apply π/2 phase to |1⟩ componentKey gates:
| Gate | What it does | Common use |
|---|---|---|
cycle(prop) | Cyclic permutation (|0⟩→|1⟩→|0⟩ for dim=2). Alias: x for qubits | Initialize to |1⟩ |
hadamard(prop) | Create equal superposition | Put objects "into quantum" |
clock(prop, fraction) | Rotate phase. Alias: z | Bias future interactions |
i_swap(p1, p2, fraction) | Anti-correlated entanglement | Split one object into two |
shift(prop) | Inverse of cycle. Alias: x | Same as cycle for dim=2 |
y(prop) | Pauli Y (dim=2 only) | Bloch sphere rotation |
Gates accept an optional fraction parameter for partial application. hadamard(prop, 0.1) barely moves the state, while hadamard(prop, 1) is the full gate. See Gates for the complete reference.
Predicated Operations
This is the mechanism that makes quantum games work. Every gate accepts an optional predicates parameter that conditions the gate on the state of other properties:
const m = manager.getModule();
// Build predicates: "only fire if controlProp is in state |1⟩"
const preds = [controlProp.is(1)];
// This Hadamard only applies when the control is |1⟩
m.hadamard(targetProp, 1, preds);When a gate is predicated on another property, the two properties become entangled. Their quantum states are now correlated. This is how entanglement works in Quantum Forge: it's not a special operation, it's what happens when operations depend on other properties' states.
Predicated Gates Create Entanglement
Before a predicated gate, the control and target are independent. After, they share a quantum state. Measuring one instantly determines the other.
The simplest example: a predicated shift on a qubit is the CNOT gate, the most fundamental entangling operation in quantum computing.
const control = manager.acquireProperty();
const target = manager.acquireProperty();
const m = manager.getModule();
// Put control in superposition
m.cycle(control); // |0⟩ → |1⟩
m.hadamard(control); // → (|0⟩ + |1⟩)/√2
// CNOT: flip target ONLY when control is |1⟩
m.shift(target, 1, [control.is(1)]);
// Now they're entangled:
// State is (|00⟩ + |11⟩)/√2
// Measuring control as 0 → target must be 0
// Measuring control as 1 → target must be 1
// Positively correlatedAny predicated gate creates entanglement. A predicated hadamard puts the target into conditional superposition, a predicated clock rotates phase conditionally, each producing different correlations.
iSwap: Anti-Correlated Entanglement
i_swap is a two-property gate that creates anti-correlated entanglement: exactly one of the pair will be measured as |1⟩. Use it when an object splits into two ghosts.
m.cycle(prop1); // |0⟩ → |1⟩ (exists)
// prop2 stays at |0⟩ (doesn't exist)
m.i_swap(prop1, prop2, 0.5); // entangle
// Result: (|10⟩ + i|01⟩)/√2
// 50% chance prop1=1, prop2=0
// 50% chance prop1=0, prop2=1
// Anti-correlated: exactly one "exists"Where CNOT creates positive correlation (both match), i_swap creates anti-correlation (exactly one). Which you use depends on the game mechanic you want. See Entanglement for the full pattern catalog.
i_swap also accepts predicates, enabling controlled entanglement:
// Only entangle if a third property is in state |1⟩
m.i_swap(prop1, prop2, 0.5, [gateProp.is(1)]);Predicate Types
prop.is(value) // true when property is in state |value⟩
prop.is_not(value) // true when property is NOT in state |value⟩Multiple predicates are AND'd. All must be satisfied for the gate to fire. See Gates: Conditional Gates for the full PredicateSpec type.
Measurement
Measurement collapses quantum state to a definite classical value. This is the moment quantum becomes real.
const [value] = m.measure_properties([prop]);
// value is now 0 or 1 (for dim=2)
// The property has collapsed. No more superposition
// Entangled partners collapse instantly tooKey behaviors:
- The outcome is probabilistic, sampled from the quantum state's probability distribution
- The state collapses permanently to the measured value
- Entangled partners update instantly. Measuring one determines the other
After measurement, release the property back to the pool:
const [value] = m.measure_properties([prop]);
manager.deleteProperty("ball-1"); // remove ID mapping
manager.releaseProperty(prop, value); // reset to |0⟩, return to poolPooling is important for staying within the qudit limit (12 in the Qutrit edition, 20 in the Qubit edition). See Performance for details. But the core concept is simple: measure, then release.
Forced Measurement
For replay/save-load, you can force a measurement to produce a specific outcome:
const [value] = m.forced_measure_properties([prop], [1]); // forces outcome to 1This is used internally by QuantumRecorder. See Recording & Replay.
Reading Quantum State
You can inspect quantum state without collapsing it. These are read-only queries that don't change anything.
Probabilities
const results = m.probabilities([prop]);
// Returns: [{ probability: 0.5, qudit_values: [0] }, { probability: 0.5, qudit_values: [1] }]Use this to:
- Render ghost objects at their existence probability (opacity = probability)
- Show probability meters in the UI
- Make AI decisions based on quantum state
Reduced Density Matrix
For phase, coherence, and correlations between properties:
const rdm = m.reduced_density_matrix([prop1, prop2]);
// Returns: [{ row_values, col_values, value: { real, imag } }, ...]The off-diagonal entries carry relative phase, invisible to probabilities() but critical for how properties interact. Games extract phase to drive visual effects:
// Extract relative phase between two entangled properties
for (const entry of rdm) {
if (entry.row_values[0] === 1 && entry.row_values[1] === 0 &&
entry.col_values[0] === 0 && entry.col_values[1] === 1) {
const phase = Math.atan2(entry.value.imag, entry.value.real);
// Map to dial rotation, color hue, force magnitude, etc.
}
}Measure Predicate
Check whether specific predicates are satisfied without fully collapsing to a basis state:
const preds = [prop1.is(1), prop2.is(0)];
const outcome = m.measure_predicate(preds);
// 1 = predicates satisfied, 0 = not satisfiedPutting It Together
Here's the complete pattern: a quantum registry for a game where objects can enter superposition and entangle:
import { QuantumPropertyManager, ensureLoaded } from "quantum-forge/quantum";
await ensureLoaded();
class QuantumRegistry extends QuantumPropertyManager {
constructor(logger?: any) { super({ dimension: 2, logger }); }
/** Give an object quantum state (equal superposition of exists/doesn't) */
makeQuantum(id: string): void {
const prop = this.acquireProperty();
const m = this.getModule();
m.cycle(prop); // |0⟩ → |1⟩
m.hadamard(prop); // → 50/50 superposition
this.setProperty(id, prop);
}
/** Entangle two quantum objects (anti-correlated existence) */
entangle(id1: string, id2: string): void {
const p1 = this.getProperty(id1);
const p2 = this.getProperty(id2);
if (!p1 || !p2) return;
this.getModule().i_swap(p1, p2, 0.5);
}
/** Read existence probability without collapsing */
getProbability(id: string): number {
const prop = this.getProperty(id);
if (!prop) return 1.0;
const results = this.getModule().probabilities([prop]);
for (const r of results) {
if (r.qudit_values[0] === 1) return r.probability;
}
return 0;
}
/** Collapse. Returns 1 (exists) or 0 (gone) */
measure(id: string): number {
const prop = this.getProperty(id);
if (!prop) return 1;
const [value] = this.getModule().measure_properties([prop]);
this.deleteProperty(id);
this.releaseProperty(prop, value);
return value;
}
}This registry handles the full loop: create properties, apply gates, read state, measure and collapse. Every production game follows this pattern. The specifics of which gates to apply and what measurement means vary, but the structure is the same.
What's Next
- Gates: Full gate reference with predicates and fractional gates
- Measurement: Collapse mechanics, batch measurement, measure predicate
- Entanglement: Entanglement patterns (split, correlated pairs, overlap, predicate-triggered)
- Phase & Interference: Phase rotation, interference, Grover oracle
- Lifecycle & State Management: Destroying properties, state budget queries, isolated simulations
- Error Handling: Typed JS errors, OOM guard, graceful degradation
- Performance: Pooling, qudit limits, optimization strategies