Performance
Quantum simulation is computationally expensive — state space grows exponentially with the number of qudits. This page covers how to keep your game running at 60 FPS.
The One Rule: Pool Your Properties
Property pooling is the single most important optimization. Without pooling, every new property grows the tensor product. With pooling, measured properties are reset and reused within the existing shared state.
// After measurement, ALWAYS release to pool
const [value] = this.measureProperties([prop]);
this.deleteProperty(id);
this.releaseProperty(prop, value); // reset to |0⟩, return to poolQuantumPropertyManager handles this automatically when you use removeProperty(id), which measures, deletes, and releases in one call.
Why Pooling Works
When you acquireProperty():
- Pool is not empty → reuses a property already in the shared state. No tensor product growth. Fast.
- Pool is empty → creates a fresh property. If this property later
iSwaps with an existing one, the WASM module computes a tensor product to merge the state spaces. Expensive.
Pooled properties are already in the shared state, so entangling them (via iSwap) is cheap.
Performance Budget
60 FPS target guidelines for dimension 2:
| Active Properties | Performance | Notes |
|---|---|---|
| 1–8 | No issues | Comfortable headroom |
| 8–14 | Good | Monitor frame times |
| 14–20 | Watch carefully | Only with good pooling |
| 20+ | Risk of slowdown | Need aggressive pooling or higher qudit limit |
These numbers assume the default WASM build. Higher dimensions or more qudits shift the thresholds.
Expensive Operations
Ranked by cost:
Tensor product — triggered by
iSwapbetween properties in different shared states. Cost grows exponentially with the number of qudits in each state. This is the operation to minimize.Fresh property creation — when the pool is empty. The property itself is cheap, but the first
iSwapwith an existing property triggers a tensor product.Measurement — proportional to state vector size. Fast for small systems, noticeable for 15+ entangled qudits.
Gate application — fast. Scales linearly with state vector size.
Probability query — similar cost to measurement but no state change.
Optimization Strategies
1. Aggressive Pooling
Measure and release as soon as a property is no longer needed:
// Ball exits field → measure immediately, don't wait
const exists = registry.measureExistence(ballId);
// Property is now back in the pool2. Limit Concurrent Quantum Objects
Design your game so that only a few objects are quantum at any time:
- Quantum Pong — max 4-6 balls quantum at once (2-3 pairs)
- Quantris — current piece + at most 2-3 recently placed pieces
- Bloch Invaders — only the targeted invader is actively quantum
3. Choose the Right Dimension
Higher dimensions = exponentially larger state space:
| Dimension | States per property | Memory per entangled pair |
|---|---|---|
| 2 | 2 | 64 bytes |
| 4 | 4 | 256 bytes |
| 7 | 7 | 784 bytes |
| 8 | 8 | 1 KB |
Use the smallest dimension your game design allows. Don't use dim=8 "just in case."
4. Separate Registries for Independent Systems
If your game has quantum objects that never interact, use separate registries:
const playerRegistry = new PlayerQuantumRegistry(logger); // player abilities
const enemyRegistry = new EnemyQuantumRegistry(logger); // enemy states
// These systems never entangle with each other,
// so they maintain independent (small) state spaces5. Batch Measurements
Measure multiple properties at once instead of one at a time:
// Slightly more efficient than three separate calls
const values = this.measureProperties([prop1, prop2, prop3]);6. Throttle Probability Queries
Reading probabilities for visualization doesn't need to happen every frame:
private probCache = new Map<string, number>();
private lastProbUpdate = 0;
getExistenceProbability(id: string): number {
const now = performance.now();
if (now - this.lastProbUpdate < 100) { // update at most 10x/sec
return this.probCache.get(id) ?? 1.0;
}
this.lastProbUpdate = now;
const prop = this.getProperty(id);
if (!prop) return 1.0;
const prob = /* query probabilities */;
this.probCache.set(id, prob);
return prob;
}Build Variant Selection
Choose your WASM build based on your game's needs:
| Game Type | Recommended Build | Reason |
|---|---|---|
| Binary states (exists/doesn't) | Small (d2, n5) | Fast build, plenty of room |
| Standard quantum game | Medium (d2, n8) | Good balance |
| High-dimension puzzles | Custom (d7, n10) | Hex geometry needs dim=7 |
| Research/experimental | Custom (d3, n32) | Maximum flexibility |
Monitoring
Use getWasmMemoryBytes() to monitor WASM heap usage:
import { getWasmMemoryBytes } from "quantum-forge-framework/quantum";
// In your debug overlay
const memBytes = getWasmMemoryBytes();
if (memBytes !== null) {
debugText(`WASM: ${(memBytes / 1024).toFixed(0)} KB`);
}Error Handling
When the qudit limit is reached, WASM operations throw. Handle gracefully:
quantumSplit(originalId: string, newId: string): boolean {
const prop1 = this.getProperty(originalId);
if (!prop1) return false;
const prop2 = this.acquireProperty();
try {
this.iSwap(prop1, prop2, 0.5);
} catch {
this.logger?.warn?.("Qudit limit reached — falling back to classical");
this.releaseProperty(prop2, 0);
return false;
}
this.setProperty(newId, prop2);
return true;
}Design your game so that hitting the qudit limit is graceful, not fatal. Quantum Pong falls back to classical behavior when no qudits are available.