Lifecycle & State Management
Quantum properties consume resources in the WASM simulator. By default, properties persist in their shared state vector until measured and pooled. This page covers tools for explicit lifecycle control: destroying properties you no longer need, querying state budget, and isolating independent simulations.
Destroying Properties
When a property is no longer needed, you can destroy it to immediately remove it from the shared state vector, shrinking the state and freeing resources.
destroy() is a method on the property handle itself, not on the module:
// Property is done, destroy it
prop.destroy();destroy() factorizes the qudit out of any shared state vector. This is different from the measure-and-pool pattern:
| Approach | What happens | When to use |
|---|---|---|
| Measure + release | Collapses to a value, resets to |0\u27E9, returns to pool | Normal gameplay — you need the measurement result |
destroy() | Factorizes the qudit out of the state vector entirely | Cleanup — you don't need the result, you want to shrink the state |
Why destroy instead of letting properties go out of scope?
When a property handle goes out of scope (or you lose the reference), the qudit remains in the shared state vector. It's a dead qudit — it still consumes state space and slows down every operation on the remaining properties. destroy() removes it cleanly.
// BAD: property leaked into the state vector
function doSomething(registry: QuantumPropertyManager) {
const temp = registry.acquireProperty();
const m = registry.getModule();
m.hadamard(temp);
m.i_swap(temp, otherProp, 0.5);
// temp goes out of scope, but its qudit is still in the shared state
}
// GOOD: explicit cleanup
function doSomething(registry: QuantumPropertyManager) {
const temp = registry.acquireProperty();
const m = registry.getModule();
m.hadamard(temp);
m.i_swap(temp, otherProp, 0.5);
// ... use the entanglement ...
temp.destroy(); // qudit factorized out, state vector shrinks
}Checking validity after destroy
After calling destroy(), the property handle is no longer backed by a quantum state. Ask the handle itself with is_valid():
prop.destroy();
prop.is_valid(); // falseWARNING
Using a destroyed property for gates, measurement, or queries is undefined behavior in current builds. The wrappers do not guard for validity, so the failure mode ranges from a thrown error to a WASM trap that takes the module down with it. Guard the call site with is_valid() rather than relying on a catch:
if (prop.is_valid()) {
m.hadamard(prop);
}Replay and adapters
destroy() is particularly useful in recording/replay scenarios. When replaying a quantum log, the adapter creates temporary properties to rebuild state. Once replay is complete, those temporary properties should be destroyed rather than left in the state vector:
// After replaying a log, clean up any properties that aren't needed
for (const tempProp of replayTemporaries) {
tempProp.destroy();
}State Budget Queries
You can query the size of the quantum state at runtime. This enables backpressure: your game can make decisions based on how much state is currently allocated.
Per-property queries
Both queries are methods on the property handle, not module functions:
// How many qudits share this property's state vector?
const numQudits = prop.num_active_qudits();
// How many basis amplitudes are in the sparse state vector?
const sparseSize = prop.state_vector_size();Both queries are read-only and do not modify state.
| Query | Returns | Use case |
|---|---|---|
prop.num_active_qudits() | Number of qudits in the property's shared quantum state | Check entanglement group size |
prop.state_vector_size() | Current number of basis amplitudes (sparse) | Monitor memory pressure |
Global limit
import { getMaxStateSize } from "quantum-forge/quantum";
const limit = getMaxStateSize(); // 100,000 basis amplitudesgetMaxStateSize() returns the compile-time maximum number of basis amplitudes any single state vector can hold. The WASM module enforces this limit — operations that would exceed it throw an error instead of crashing.
Implementing backpressure
Use state budget queries to implement graceful degradation:
QuantumPropertyManager has no public iteration API, so a subclass that wants to scan its own properties has to keep the ids it owns. Track them in a Set<string> maintained wherever you register and remove properties:
class QuantumRegistry extends QuantumPropertyManager {
private readonly STATE_BUDGET_WARN = 10_000;
private readonly STATE_BUDGET_HARD = 50_000;
/** Ids this registry owns. Kept in sync by makeQuantum / measure. */
private readonly liveIds = new Set<string>();
makeQuantum(id: string): void {
const prop = this.acquireProperty();
this.getModule().hadamard(prop);
this.setProperty(id, prop);
this.liveIds.add(id);
}
measure(id: string): number {
const prop = this.getProperty(id);
if (!prop) return 0;
const [value] = this.getModule().measure_properties([prop]);
this.deleteProperty(id);
this.releaseProperty(prop, value);
this.liveIds.delete(id);
return value;
}
/** Largest state vector across the properties this registry owns. */
private largestStateSize(): number {
let largest = 0;
for (const id of this.liveIds) {
const prop = this.getProperty(id);
if (!prop) continue;
largest = Math.max(largest, prop.state_vector_size());
}
return largest;
}
canCreateQuantumObject(): boolean {
return this.largestStateSize() <= this.STATE_BUDGET_HARD;
}
shouldPreferClassical(): boolean {
return this.largestStateSize() > this.STATE_BUDGET_WARN;
}
quantumSplit(originalId: string, newId: string): boolean {
if (!this.canCreateQuantumObject()) {
this.logger?.warn?.("State budget exceeded, falling back to classical");
return false;
}
const prop1 = this.getProperty(originalId);
if (!prop1) return false;
const prop2 = this.acquireProperty();
this.getModule().i_swap(prop1, prop2, 0.5);
this.setProperty(newId, prop2);
this.liveIds.add(newId);
return true;
}
}TIP
State budget queries are cheap read-only operations. You can call them every frame without performance concern. The Set scan above is over your own ids, so it costs whatever a few map lookups cost.
QuantumSimulation
QuantumSimulation creates an isolated simulation context. Properties created within a simulation are sandboxed: they can entangle with each other but not with properties from other simulations (or from the global context).
import { getQuantumForge, getModule, ensureLoaded } from "quantum-forge/quantum";
await ensureLoaded();
const sim = getQuantumForge().createSimulation();
const prop1 = sim.createProperty(2); // dimension 2
const prop2 = sim.createProperty(2);
// Gates come from the module, not from the simulation
const m = getModule();
m.hadamard(prop1);
m.i_swap(prop1, prop2, 0.5); // works, same simulation
// Clean up. Releases all properties and state vectors at once
sim.destroy();A simulation owns properties, not gates. There is no sim.getModule(). Its whole surface is createProperty, destroyProperty, factorizeAllSeparable, destroy, and isDestroyed. Gates and queries always come from getModule() (or manager.getModule()), and they work on properties from any simulation as long as a single call does not mix two of them.
Why isolated simulations?
The primary use case is search tree exploration in games like quantum chess. When exploring possible moves, each branch of the search tree needs its own quantum state. Without isolation, all branches share (and grow) the same state vector:
// WITHOUT QuantumSimulation — all replays share state, vector grows unbounded
for (const move of candidateMoves) {
const props = replayUpTo(move); // creates properties in global context
const score = evaluate(props); // state vector includes ALL replays
cleanupReplay(props); // even with cleanup, tensor products already happened
}
// WITH QuantumSimulation — each replay is isolated
for (const move of candidateMoves) {
const sim = getQuantumForge().createSimulation();
const props = replayUpTo(move, sim); // properties are sandboxed
const score = evaluate(props); // state vector is small (just this replay)
sim.destroy(); // everything released at once
}Cross-simulation entanglement is an error
Attempting to entangle properties from different simulations throws an error:
const simA = getQuantumForge().createSimulation();
const simB = getQuantumForge().createSimulation();
const propA = simA.createProperty(2);
const propB = simB.createProperty(2);
const m = getModule();
// This throws. propA and propB are in different simulations
m.i_swap(propA, propB, 0.5);
// Error: [QuantumForgeError] Cannot entangle properties from different QuantumSimulation contextsIf you match on the message, match on that exact text. It is the string the C++ layer throws.
This is a feature, not a limitation. It prevents accidental state vector growth from cross-contamination between independent quantum contexts.
Global context still works
QuantumSimulation is opt-in. The existing pattern of creating properties via QuantumPropertyManager.acquireProperty() or getQuantumForge().createQuantumProperty() continues to work. Properties in the global context can entangle freely with each other, as before.
// Both of these live in the global context
const prop = manager.acquireProperty();
const prop2 = getQuantumForge().createQuantumProperty(2);
// Global properties can still entangle with each other
getModule().i_swap(prop, prop2, 0.5); // works fineUse QuantumSimulation when you need isolation. Use the global context for simple cases.
Simulation lifecycle
| Method | Description |
|---|---|
getQuantumForge().createSimulation() | Create a new isolated simulation |
new (getModule().QuantumSimulation)() | Direct constructor, same result. The class is only reachable through the loaded module, not as a top-level import from quantum-forge/quantum |
sim.createProperty(dimension) | Create a property bound to this simulation |
sim.destroyProperty(prop) | Factorize one property out and free its resources |
sim.factorizeAllSeparable() | Split out any separable qudits across all shared states |
sim.destroy() | Release all properties and state vectors at once |
sim.isDestroyed() | Whether destroy() has already been called |
Gate and query calls are not on the simulation. Use getModule() for those.
Incremental property destruction
For search algorithms that create many ancilla properties, use sim.destroyProperty(prop) to free resources incrementally instead of waiting for sim.destroy():
const sim = getQuantumForge().createSimulation();
const board = sim.createProperty(2);
const ancilla = sim.createProperty(2);
const m = getModule();
m.i_swap(board, ancilla, 0.5);
// ... use the entanglement ...
m.i_swap(board, ancilla, -0.5); // undo
// Ancilla is now separable — destroy it to free resources
sim.destroyProperty(ancilla);
// ancilla.is_valid() === false
// board keeps its quantum state (superposition preserved)destroyProperty automatically detects separable qudits after removing the destroyed property and splits them into independent states. This prevents qudit accumulation across many do/undo search cycles.
Destroying entangled properties
If the property is entangled with others (not separable), destroying it measures the qudit, which collapses the entangled partners' state. A console warning is emitted in this case. To avoid unintended collapse, uncompute the entanglement before destroying.
Explicit separability scanning
After undo operations that may have restored separability (e.g. i_swap(a,b,f) followed by i_swap(a,b,-f)), call sim.factorizeAllSeparable() to split separable qudits into independent states without destroying any properties:
// After undoing a sequence of operations
sim.factorizeAllSeparable();
// Separable qudits now have independent state vectorsWARNING
After calling sim.destroy(), all properties created within the simulation become invalid. Check prop.is_valid() if you hold references to them.
Lifecycle Summary
| Tool | Purpose | When to use |
|---|---|---|
| Measure + pool | Collapse and recycle a property | Normal gameplay |
destroy() | Remove a qudit from the state vector | Cleanup without measurement |
| State budget queries | Monitor state vector size | Backpressure, debug overlays |
QuantumSimulation | Isolate independent quantum contexts | Search trees, replay branches, testing |