Skip to content

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.

typescript
// After measurement, ALWAYS release to pool
const [value] = this.measureProperties([prop]);
this.deleteProperty(id);
this.releaseProperty(prop, value); // reset to |0⟩, return to pool

QuantumPropertyManager handles this automatically when you use removeProperty(id), which measures, deletes, and releases in one call.

Why Pooling Works

When you acquireProperty():

  1. Pool is not empty → reuses a property already in the shared state. No tensor product growth. Fast.
  2. 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 PropertiesPerformanceNotes
1–8No issuesComfortable headroom
8–14GoodMonitor frame times
14–20Watch carefullyOnly with good pooling
20+Risk of slowdownNeed 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:

  1. Tensor product — triggered by iSwap between properties in different shared states. Cost grows exponentially with the number of qudits in each state. This is the operation to minimize.

  2. Fresh property creation — when the pool is empty. The property itself is cheap, but the first iSwap with an existing property triggers a tensor product.

  3. Measurement — proportional to state vector size. Fast for small systems, noticeable for 15+ entangled qudits.

  4. Gate application — fast. Scales linearly with state vector size.

  5. 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:

typescript
// Ball exits field → measure immediately, don't wait
const exists = registry.measureExistence(ballId);
// Property is now back in the pool

2. 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:

DimensionStates per propertyMemory per entangled pair
2264 bytes
44256 bytes
77784 bytes
881 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:

typescript
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 spaces

5. Batch Measurements

Measure multiple properties at once instead of one at a time:

typescript
// 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:

typescript
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 TypeRecommended BuildReason
Binary states (exists/doesn't)Small (d2, n5)Fast build, plenty of room
Standard quantum gameMedium (d2, n8)Good balance
High-dimension puzzlesCustom (d7, n10)Hex geometry needs dim=7
Research/experimentalCustom (d3, n32)Maximum flexibility

Monitoring

Use getWasmMemoryBytes() to monitor WASM heap usage:

typescript
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:

typescript
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.

Powered by Quantum Forge