Skip to content

Entanglement

Entanglement is the mechanic that makes quantum games fundamentally different from classical ones. When two properties are entangled, they share a quantum state. Measuring one instantly determines the other. No classical system can do this.

How Entanglement Happens

There are two ways to entangle properties in Quantum Forge:

  1. Predicated gates: any gate conditioned on another property's state entangles them
  2. i_swap: a two-property gate designed specifically for entanglement

Both work. Which you use depends on the correlations you want.

Predicated Gates (CNOT and Friends)

A predicated gate fires only when a control property satisfies a condition. Because the control is in superposition, the gate creates a correlation, and correlation between quantum properties is entanglement.

The simplest example: a predicated shift on a qubit is the CNOT gate.

typescript
const control = this.acquireProperty();
const target = this.acquireProperty();
const m = this.getModule();

m.cycle(control);     // |0⟩ → |1⟩
m.hadamard(control);  // → (|0⟩ + |1⟩)/√2

// CNOT: flip target when control is |1⟩
m.shift(target, 1, [control.is(1)]);

// Result: (|00⟩ + |11⟩)/√2
// Measuring control as 0 → target is 0
// Measuring control as 1 → target is 1
// Positively correlated

This generalizes to any gate. A predicated hadamard puts the target into superposition only when the control allows it. A predicated clock rotates phase conditionally. Each creates a different kind of entanglement with different correlations.

typescript
// Controlled-Hadamard: target enters superposition only if control is |1⟩
m.hadamard(target, 1, [control.is(1)]);

// Multi-control: gate fires only when BOTH controls are |1⟩
m.shift(target, 1, [controlA.is(1), controlB.is(1)]);

// Negative predicate: gate fires when control is NOT |0⟩
m.cycle(target, 1, [control.is_not(0)]);

iSwap

i_swap is a two-property entanglement gate. At fraction=0.5, starting from |10⟩:

|10⟩  →  (|10⟩ + i|01⟩) / √2

This means:

  • 50% chance prop1 is |1⟩ and prop2 is |0⟩
  • 50% chance prop1 is |0⟩ and prop2 is |1⟩
  • These outcomes are anti-correlated. Exactly one of the pair is |1⟩
  • Measuring either one instantly collapses the other
typescript
this.getModule().i_swap(prop1, prop2, fraction);

Where predicated gates create positive correlations (both same, or one depends on the other), i_swap naturally creates anti-correlations (exactly one of two). Use it for "object splits into two ghosts, only one is real."

i_swap also accepts predicates, enabling controlled entanglement:

typescript
// Only entangle if a gate property is |1⟩
m.i_swap(prop1, prop2, 0.5, [gateProp.is(1)]);

Choosing Between Them

MechanismCorrelationTypical Use
Predicated shift (CNOT)Positive, both matchLinked states: both alive or both dead
Predicated hadamardConditional superpositionOne object's "quantumness" depends on another
i_swap(0.5)Anti-correlated, exactly oneObject splits into two ghosts
i_swap + phaseTunable biasPlayer-influenced split probability

Entanglement Patterns

Pattern 1: Entangle-Split (iSwap)

A classical object enters a quantum zone and splits into an entangled pair. One property starts at |1⟩ (exists), the other at |0⟩ (doesn't exist), then i_swap(0.5) creates anti-correlated superposition.

typescript
entangleSplit(originalId: string, newId: string): void {
  const prop1 = this.acquireProperty();
  const prop2 = this.acquireProperty();

  const m = this.getModule();
  m.cycle(prop1);                    // |0⟩ → |1⟩ (exists)
  // prop2 stays at |0⟩              (doesn't exist)
  m.i_swap(prop1, prop2, 0.5);      // entangle

  this.setProperty(originalId, prop1);
  this.setProperty(newId, prop2);
}

After entangle-split:

  • Both objects exist with ~50% probability
  • Measuring one to "exists" collapses the other to "doesn't exist"
  • The game shows both objects as semi-transparent ghosts

Pattern 2: Correlated Pair (CNOT)

Two objects whose states always match, both alive or both dead. Uses predicated shift instead of iSwap.

typescript
createLinkedPair(id1: string, id2: string): void {
  const prop1 = this.acquireProperty();
  const prop2 = this.acquireProperty();

  const m = this.getModule();
  m.cycle(prop1);                              // |1⟩ (exists)
  m.hadamard(prop1);                           // superposition
  m.shift(prop2, 1, [prop1.is(1)]);           // CNOT: prop2 matches prop1

  this.setProperty(id1, prop1);
  this.setProperty(id2, prop2);
}

After CNOT pair:

  • State is (|00⟩ + |11⟩)/√2
  • Measuring one to "exists" means the other also exists
  • Measuring one to "gone" means the other is also gone

Pattern 3: Quantum-Split (iSwap)

Split an already quantum object. The new property starts at |0⟩ and entangles with the existing one. Pooled properties are preferred because they're already in the shared state (no tensor product growth).

typescript
quantumSplit(originalId: string, newId: string): boolean {
  const prop1 = this.getProperty(originalId);
  if (!prop1) return false;

  const prop2 = this.acquireProperty(); // prefers pooled
  this.getModule().i_swap(prop1, prop2, 0.5);

  this.setProperty(newId, prop2);
  return true;
}

Pattern 4: Overlap Entanglement

Objects that spatially overlap become entangled. Adjacent hexes at shared boundaries, overlapping pieces in a puzzle, etc.

typescript
entangleNeighbors(hexA: string, hexB: string): void {
  const propA = this.getProperty(hexA);
  const propB = this.getProperty(hexB);
  if (!propA || !propB) return;

  this.getModule().i_swap(propA, propB, 0.5);
}

Pattern 5: Conditional Superposition

A gate property controls whether another object enters superposition. The gate property's state determines the target's behavior:

typescript
conditionalQuantum(targetId: string, controlId: string): void {
  const target = this.getProperty(targetId);
  const control = this.getProperty(controlId);
  if (!target || !control) return;

  // Target enters superposition ONLY when control is |1⟩
  this.getModule().hadamard(target, 1, [control.is(1)]);
}

Tensor Product Growth

When an entangling operation connects properties from different shared states, the WASM module computes a tensor product to merge them into one combined state. This is the most expensive operation in quantum simulation.

What triggers it:

  • i_swap(propA, propB, ...) where propA and propB are in different shared states
  • Any predicated gate where the control and target are in different shared states
  • This typically happens when both properties were freshly acquired (not from pool)

What prevents it:

  • Pooled properties are already in the shared state, so operations between a pooled property and an existing one are cheap
  • Measuring and releasing properties via releaseProperty() keeps the pool full

What happens when you run out:

  • The shipped build supports up to 12 qudits in a single entangled system
  • Exceeding the limit throws an error
  • Your game should handle this gracefully (see the quantumSplit example above)
typescript
try {
  this.getModule().i_swap(prop1, prop2, 0.5);
} catch {
  this.logger?.warn?.("Qudit limit reached, cannot entangle");
  this.releaseProperty(prop2, 0); // return unused property
  return false;
}

Entanglement and Measurement

When you measure one property of an entangled pair:

  1. That property collapses to a definite value
  2. The entangled partner's probabilities update instantly
  3. For anti-correlated pairs (i_swap(0.5) from |10⟩), measuring one to |1⟩ forces the other to |0⟩
  4. For positively correlated pairs (CNOT from (|00⟩+|11⟩)/√2), measuring one to |1⟩ forces the other to |1⟩ too

This is what makes quantum scoring work in Quantum Pong: measuring one ball's existence immediately determines the other ball's existence.

Visualizing Entanglement

The common pattern: read probability via this.getModule().probabilities([prop]) and map it to visual opacity, size, color intensity, or glow. In Quantum Pong, entangled balls are rendered as semi-transparent ghosts whose opacity tracks existence probability.

Powered by Quantum Forge