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
import { ensureLoaded, quantum } from "quantum-forge/quantum";
await ensureLoaded();
const coin = quantum([false, true]).superpose();
const value = coin.measure(); // false or true, the declared value
// coin now holds that value. No more superpositionmeasure() returns the declared value, not an index: a property declared as ["red", "green", "blue"] measures to "red", "green" or "blue".
Key behaviors:
- The outcome is probabilistic, sampled from the quantum state's probability distribution
- The state collapses to the measured value
- Entangled partners collapse in the same step
The handle stays usable after a measurement. It holds a definite value and can go back into superposition with another gate. Measuring does not end its life; dispose() does.
Measure and dispose
When the thing a property describes is gone, dispose the handle so its qudit stops counting against the build's limit:
function measureExistence(ball: Ball): boolean {
if (!ball.exists) return true; // classical balls always exist
const value = ball.exists.measure();
ball.exists.dispose();
ball.exists = undefined;
return value;
}dispose() measures on its own, so if you don't need the value you can skip measure() and call dispose() directly. Either way, entangled partners collapse. If you keep handles on entities, the engine's EntityManager disposes them when it removes the entity. See Lifecycle.
Joint measurement
Measure several properties in one step with the free measure(). It returns one declared value per property, in argument order:
import { measure } from "quantum-forge/quantum";
const [va, vb, vc] = measure(a, b, c);One joint call is cheaper than three separate ones, and the outcomes respect every correlation between the properties.
Reading probabilities (no collapse)
Query the probability distribution without disturbing the quantum state:
coin.probability(true);
// 0.5 for a superposed coin
coin.probabilities();
// [{ value: false, probability: 0.5 }, { value: true, probability: 0.5 }]For several properties at once, the free probabilities() returns joint probabilities, one entry per combination of declared values:
import { probabilities } from "quantum-forge/quantum";
probabilities(a, b);
// [{ values: [true, false], probability: 0.5 }, { values: [false, true], probability: 0.5 }]
// for the iSwap split: exactly one of a and b is trueThese are read-only calls. They do not collapse the state. Use them for:
- Rendering ghost objects at their existence probability
- Showing probability meters in the UI
- AI decision-making based on quantum state
TIP
Reading probabilities does not collapse anything, and QuantumRecorder does not log reads.
Multi-property reads merge states first
Passing several properties at once is only free if they already share a state. The free probabilities(), densityMatrix() and probabilityWhen() merge properties that sit in separate states into one before they read. The statistics don't change, but the merged state is the product of the two sizes: bigger, and slower for every later gate on it.
Read one property at a time (prop.probability(), prop.probabilities()) when you only need per-property numbers, and keep multi-property reads for properties that are already entangled.
Density matrix
For advanced analysis (phase, coherence, and correlations between properties):
import { densityMatrix } from "quantum-forge/quantum";
const rho = densityMatrix(a, b);
// [{ row: [true, false], col: [false, true], real: number, imag: number }, ...]Rows and columns are labelled with one declared value per property. The off-diagonal entries carry relative phase. From Quantum Pong:
function relativePhase(ref: Quantum<boolean>, target: Quantum<boolean>): number {
for (const entry of densityMatrix(ref, target)) {
if (
entry.row[0] === true && entry.row[1] === false &&
entry.col[0] === false && entry.col[1] === true
) {
if (Math.abs(entry.real) < 1e-10 && Math.abs(entry.imag) < 1e-10) return 0;
return Math.atan2(entry.imag, entry.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 drives piece glow and pulse effects
Measuring a condition
measureWhen() measures whether every predicate holds, collapses the state to agree with the answer, and returns a boolean:
import { measureWhen } from "quantum-forge/quantum";
const matched = measureWhen([a.is(true), b.is(false)]);
// true: the state is now one where a is true and b is false
// false: that combination is ruled out, and the rest of the state remainsUnlike measure(), this doesn't have to collapse the properties to single values. It projects the state onto the part where the answer holds. With a and b each in an equal superposition, a false answer to measureWhen([a.is(true), b.is(true)]) leaves three combinations in play, and a.probability(true) becomes 1/3.
Condition probability (no collapse)
probabilityWhen() returns the probability that every predicate holds at once, without measuring anything:
import { probabilityWhen } from "quantum-forge/quantum";
const p = probabilityWhen([a.is(true), b.is(false)]);
// 0 to 1: the chance measureWhen() with the same predicates returns trueThis is the read-only counterpart to measureWhen(). 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 merge caveat above applies: predicates on properties in separate states merge those states.
Forced measurement (replay)
Force a measurement to a specific outcome. QuantumRecorder.replay() does this to rebuild exact quantum states from a saved log:
import { forcedMeasure, forcedMeasureWhen } from "quantum-forge/quantum";
coin.forcedMeasure(true); // collapses to true
forcedMeasure([a, b], [true, false]); // one value per property
forcedMeasureWhen([a.is(true), b.is(false)], true); // force the condition's answerThe method and forcedMeasure() accept declared values or indices. forcedMeasure(props, values) needs one value per property and throws if the lengths differ.
Forcing an outcome whose probability is zero throws, in all three calls. The check runs before the measurement, so nothing collapses. A replay that throws here has diverged from the recorded run.
WARNING
Forced measurement is for replays and tests only. Using it during normal gameplay breaks the quantum contract. Outcomes should be probabilistic.
Game example: Quantum Pong scoring
When a ball leaves the play field, the game measures it to decide whether it scores:
const exists = measureExistence(ball);
if (exists) {
// The ball existed: it scores a point
state.score[scoringSide]++;
showScoreBurst(ball.position);
} else {
// The ball was a ghost: no score
showGhostBurst(ball.position);
}
// Its split twin, if any, has collapsed to the opposite valueOther measurement patterns include joint measurement for line clears (measure every piece in a row with one measure(...)), basis measurement (rotate before measuring for a different observable), and battle resolution (measure overlapping hexes to decide ownership).