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
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 superpositionKey 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:
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:
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:
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):
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:
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:
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 satisfiedUnlike 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:
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 1This 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:
const [value] = this.getModule().forced_measure_properties([prop], [1]); // forces outcome to 1forced_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:
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:
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:
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 valueOther 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).