You're reading the 2.x docs. quantum-forge 3.0 is out: this page in 3.x / migration guide

Skip to content

Measurement ​

Measurement is the moment quantum becomes classical. A property in superposition collapses to a single definite value, and that collapse is the most dramatic thing your game can do.

Basic Measurement ​

typescript
const prop = this.getProperty("ball-1");
const [value] = this.getModule().measure_properties([prop]);
// value is 0 or 1 (for dim=2)
// The property is now in state |value⟩. No more superposition

Key behaviors:

  • The outcome is probabilistic, sampled from the quantum state's probability distribution
  • The state collapses permanently to the measured value
  • Entangled partners collapse instantly too

Measure and Pool ​

Always release properties after measurement to stay within qudit limits:

typescript
measureEntity(id: string): number {
  const prop = this.getProperty(id);
  if (!prop) return 1; // classical fallback

  const [value] = this.getModule().measure_properties([prop]);
  this.deleteProperty(id);           // remove ID mapping
  this.releaseProperty(prop, value); // reset to |0⟩, return to pool
  return value;
}

This pattern appears in every production game. The specifics vary (what the value means, what to do after), but the sequence (measure, delete, release) is universal.

Batch Measurement ​

Measure multiple properties at once:

typescript
const props = [this.getProperty("a")!, this.getProperty("b")!, this.getProperty("c")!];
const [va, vb, vc] = this.getModule().measure_properties(props);

Batch measurement is slightly more efficient than measuring one at a time, and the outcomes are correlated correctly (entangled properties respect their correlations within the batch).

Reading Probabilities (No Collapse) ​

Query the probability distribution without disturbing the quantum state:

typescript
const prop = this.getProperty("ball-1");
const results = this.getModule().probabilities([prop]);
// results: [{ probability: 0.7, qudit_values: [1] }, { probability: 0.3, qudit_values: [0] }]

This is a read-only operation. It does not collapse the state. Use this for:

  • Rendering ghost objects at their existence probability
  • Showing probability meters in the UI
  • AI decision-making based on quantum state

TIP

probabilities() does not collapse anything and does not appear in recording logs.

Multi-property queries tensor first

Passing several properties at once is only free if they already share a state vector. probabilities() and reduced_density_matrix() both call tensor_state() on their arguments, so properties sitting in separate state vectors get merged into one before the query runs. Nothing about the measurement statistics changes, but the resulting vector is the product of the two sizes: bigger, slower for every later operation, and large enough in the worst case to throw [QuantumForgeStateSizeError] from a call you thought was read-only.

Query one property at a time when you only need per-property probabilities, and reserve multi-property queries for properties that are already entangled.

Reduced Density Matrix ​

For advanced analysis (phase, coherence, and correlations between properties):

typescript
const rdm = this.getModule().reduced_density_matrix([prop1, prop2]);
// rdm: array of { row_values: number[], col_values: number[], value: { real: number, imag: number } }

The off-diagonal entries carry relative phase information. From Quantum Pong:

typescript
getRelativePhase(refId: string, targetId: string): number {
  const propRef = this.getProperty(refId);
  const propTarget = this.getProperty(targetId);
  if (!propRef || !propTarget) return 0;

  const rdm = this.getModule().reduced_density_matrix([propRef, propTarget]);

  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 { real, imag } = entry.value;
      if (Math.abs(real) < 1e-10 && Math.abs(imag) < 1e-10) return 0;
      return Math.atan2(imag, real);
    }
  }
  return 0;
}

Game use cases:

  • Quantum Pong: Relative phase drives the phase dial display and biases entanglement interactions
  • Ponq: Off-diagonal magnitude (coherence) drives ball steering forces
  • Quantris: Coherence info drives piece glow/pulse visualization

Measure Predicate ​

Projective measurement that checks whether specific predicates are satisfied:

typescript
import type { PredicateSpec } from "quantum-forge/quantum";

const predicates: PredicateSpec[] = [
  { property: prop1, value: 1, isEqual: true },
  { property: prop2, value: 0, isEqual: true },
];

const wasmPreds = predicates.map(s =>
  s.isEqual ? s.property.is(s.value) : s.property.is_not(s.value)
);
const outcome = this.getModule().measure_predicate(wasmPreds);
// outcome: 0 or 1
// 1 = predicates were satisfied (state projected onto matching subspace)
// 0 = predicates were NOT satisfied

Unlike measure_properties, this doesn't fully collapse to a basis state. It performs a projective measurement onto the subspace defined by the predicates.

Predicate Probability (No Collapse) ​

predicate_probability() returns the probability that all the given predicates hold at once, without measuring anything:

typescript
const preds = [prop1.is(1), prop2.is(0)];
const p = this.getModule().predicate_probability(preds);
// p: 0.0 to 1.0, the chance measure_predicate(preds) would return 1

This is the read-only counterpart to measure_predicate(). Use it when you want to show the player how likely an outcome is, or let AI weigh a branch, without spending the collapse. The same tensor caveat applies: predicates spanning separate state vectors merge them.

Forced Measurement (Replay) ​

Force a measurement to produce a specific outcome. Used internally by QuantumRecorder.replayLog() to reproduce exact quantum states during save/load:

typescript
const [value] = this.getModule().forced_measure_properties([prop], [1]); // forces outcome to 1

forced_measure_properties requires forcedValues to be the same length as props, and throws if it isn't.

Forced predicate measurement ​

forced_measure_predicate() is the predicate equivalent, forcing the projective measurement to a chosen outcome:

typescript
const outcome = this.getModule().forced_measure_predicate(preds, 1); // force "satisfied"

It has one behavior worth knowing before you rely on it. If the outcome you asked for has zero probability, the call does not throw. It falls back to an ordinary stochastic measurement and returns whatever actually happened, so the return value can differ from the value you passed. Compare the return against your requested value if the difference matters to your replay:

typescript
const outcome = m.forced_measure_predicate(preds, 1);
if (outcome !== 1) {
  // The forced outcome was impossible. State diverged from the recorded run
}

WARNING

Forced measurement is for replay/testing only. Using it during normal gameplay breaks the quantum contract. Outcomes should be probabilistic.

Game Example: Quantum Pong Scoring ​

When a ball exits the play field, it's measured to determine if it scores:

typescript
const exists = registry.measureExistence(ballId);
if (exists === 1) {
  // Ball existed → scores a point
  state.score[scoringSide]++;
  showScoreBurst(ball.position);
} else {
  // Ball was a ghost → no score
  showGhostBurst(ball.position);
}
// Entangled partner (if any) collapses to the opposite value

Other measurement patterns include batch measurement for line-clearing (measure all pieces in a row), basis measurement (rotate before measuring for different observables), and battle resolution (measure overlapping hexes to determine ownership).

Powered by Quantum Forge