Skip to content

Quantum API ​

Everything on this page comes from quantum-forge/quantum. It documents quantum-forge 3.0. Coming from 2.x? See Migrating to 3.0.

typescript
import {
  // Handles
  quantum,
  Quantum,
  isQuantum,
  QUANTUM_HANDLE,
  clearQuantumCache,
  observeQuantum,

  // Joint operations
  measure,
  forcedMeasure,
  probabilities,
  densityMatrix,
  measureWhen,
  forcedMeasureWhen,
  probabilityWhen,
  phaseRotate,

  // Recording
  QuantumRecorder,
  LegacyQuantumRecorder,

  // Loading and build info
  ensureLoaded,
  startBackgroundLoad,
  isReady,
  useQuantumForgeBuild,
  setWasmBasePath,
  getWasmBasePath,
  getVersion,
  getMaxDimension,
  getMaxQudits,
  getMaxStateSize,
  getWasmMemoryBytes,
  getAttribution,
  registerServiceWorker,

  // Raw WASM access
  getModule,
  getQuantumForge,
  OP,
} from "quantum-forge/quantum";

import type {
  QuantumValue,
  GateOptions,
  QuantumPredicate,
  SerializedQuantumPredicate,
  QuantumGateEvent,
  QuantumMeasureEvent,
  QuantumObserver,
  QuantumLog,
  QuantumLogEntry,
  QuantumProperty,
  Predicate,
  OpCode,
  BatchOp,
  BatchResult,
} from "quantum-forge/quantum";

Creating a property ​

quantum(values) ​

typescript
function quantum<V extends QuantumValue>(values: readonly V[]): Quantum<V>;
function quantum(dimension: number): Quantum<number>;

Creates a quantum property declared by the values it can take. The dimension is the number of values, and the property starts at the first one.

typescript
import { ensureLoaded, quantum } from "quantum-forge/quantum";

await ensureLoaded(); // once, before the first quantum() call

const color = quantum(["red", "green", "blue"]); // Quantum<"red" | "green" | "blue">, starts "red"
const alive = quantum([false, true]);            // Quantum<boolean>, starts false
const die = quantum(3);                          // Quantum<number>, values 0, 1, 2

Values are strings, numbers or booleans, each declared once. TypeScript infers the literal union from the array, so no as const is needed.

quantum() throws:

  • before ensureLoaded() has finished,
  • on fewer than two values, including quantum(1) and a one-element array (RangeError),
  • on a non-integer dimension or a non-finite number (RangeError),
  • on a value that is not a string, number or boolean (TypeError),
  • on a value declared twice,
  • past the loaded build's maximum dimension: 3 in the Qutrit Edition, 2 in the Qubit Edition.

quantum() is the only way to make a handle. The Quantum constructor is private and throws a TypeError if called.

QuantumValue ​

typescript
type QuantumValue = string | number | boolean;

The Quantum handle ​

typescript
class Quantum<V extends QuantumValue = QuantumValue>

One quantum property. A bare Quantum means Quantum<QuantumValue>, so a field typed Quantum holds any handle, named or numeric. Write Quantum<boolean> to narrow it.

Wherever a method takes a value, pass a declared value or its index in the declaration. color.is("green") and color.is(1) build the same predicate. When the declared values are themselves numbers, a number is read as a declared value first: on quantum([1, 0]), is(0) means the value 0 at index 1. A value that is neither throws a RangeError.

Properties ​

MemberTypeDescription
idnumberUnique per quantum() call, increasing, never reused
valuesreadonly V[]Declared values in order. Index 0 is the starting value
dimensionnumberNumber of declared values
disposedbooleantrue once dispose() has run
rawQuantumPropertyThe WASM property behind the handle, for the batch API. See Raw WASM access

Gates ​

Every gate returns the handle, so calls chain. A gate that reads only this property is evolution. A gate that touches a second property, or whose when predicate reads another property, is an interaction and leaves the two entangled.

MethodAliasDescription
hadamard(fraction?, opts?)superpose()Spread the property evenly across every value
inverseHadamard(opts?)Undo hadamard()
cycle(fraction?, opts?)next(), and flip() on two valuesMove to the next value, wrapping
shift(fraction?, opts?)previous()Move to the previous value, wrapping
clock(fraction?, opts?)phase()Rotate the phase of each value by its index
x(fraction?, opts?)Pauli X, the same gate as shift()
y(fraction?, opts?)Pauli Y, two values only
z(fraction?, opts?)Pauli Z, the same gate as clock()
swap(other, opts?)Exchange the states of two properties
iSwap(other, fraction, opts?)iSwap between two properties. The fraction is required

An alias is the same call under another name. It never adds behavior, and observers and the recorder report it under the physics name.

Signatures:

typescript
hadamard(fraction?: number | GateOptions, opts?: GateOptions): this;  // also cycle, shift, clock, x, y, z
superpose(fraction?: number | GateOptions, opts?: GateOptions): this; // also next, previous, phase, flip
inverseHadamard(opts?: GateOptions): this;
swap(other: Quantum<any>, opts?: GateOptions): this;
iSwap(other: Quantum<any>, fraction: number, opts?: GateOptions): this;

Fractions. Every gate except inverseHadamard, swap and iSwap takes an optional first fraction. Leave it out for the discrete gate. Any other number runs the continuous version, and 0.5 is the square root of the gate. Exactly 1 also runs the discrete gate, but write the call without a fraction when that is what you mean. A fraction that is NaN or infinite throws a RangeError.

Options. The options object can follow the fraction or stand in its place: b.flip({ when: [a.is(true)] }) and b.flip(0.5, { when: [a.is(true)] }) both work.

Throws.

  • flip() and y() on a property that does not have exactly two values. Use next() or cycle() on a qutrit.
  • swap() and iSwap() passed the handle they are called on, or a property with a different number of values.
  • Any gate whose when predicate reads the property the gate acts on (for swap and iSwap, either of the two).
  • Any gate on a disposed handle, or with a predicate on a disposed handle.
  • An interaction that would put more qudits in one entangled state than the build allows. The message names the method and the limit.

GateOptions ​

typescript
interface GateOptions {
  /** The gate acts only on the part of the state where every predicate holds. */
  when?: QuantumPredicate[];
}

Predicates ​

MethodReturnsDescription
is(value)QuantumPredicate<V>Holds where this property equals value
isNot(value)QuantumPredicate<V>Holds where this property does not equal value

Pass predicates to a gate in { when: [...] }, or to the *When free functions. Several predicates must all hold.

typescript
b.flip({ when: [a.is(true)] });             // CNOT
b.phase({ when: [a.is(true)] });            // CZ
c.next({ when: [a.is(true), b.isNot(0)] }); // two conditions
typescript
interface QuantumPredicate<V extends QuantumValue = QuantumValue> {
  readonly property: Quantum<V>; // the property this predicate tests
  readonly value: V;             // the declared value being tested
  readonly index: number;        // the index of that value
  readonly isEqual: boolean;     // true for is(), false for isNot()
}

Measurement and inspection ​

MethodReturnsDescription
measure()VMeasure, collapsing this property and every property entangled with it. Returns the declared value
forcedMeasure(value)VMeasure with the outcome forced to value. For replays and tests
probability(value)numberProbability that a measurement gives value. No collapse
probabilities()Array<{ value: V; probability: number }>Probability of every declared value, in declaration order. No collapse

The handle stays usable after measure(). It holds a definite value and can go back into superposition with another gate.

forcedMeasure() throws when value has zero probability in the current state, and leaves the state unchanged.

Lifecycle ​

MethodReturnsDescription
dispose()voidEnd the property's life
[Symbol.dispose]()voidSame as dispose(), so a using declaration disposes at the end of the scope

dispose() measures the property first, which collapses every property entangled with it. If the qudit is then alone in its state, it is reset and cached for the next quantum() of the same dimension. If it still shares a state with other properties, it is destroyed, which takes it out of that state. Calling dispose() a second time does nothing. Every other call on a disposed handle throws Quantum property #N was disposed and can no longer be used. dispose() also works on a frozen handle.

typescript
function rollDie(): number {
  using die = quantum(3).superpose();
  return die.measure();
} // die.dispose() runs here

using needs TypeScript 5.2 or newer and "ESNext.Disposable" in the lib of your tsconfig.json. Projects made by npx quantum-forge-engine init have both. Code that never writes using needs neither: the shipped types declare Symbol.dispose themselves.

See Lifecycle for patterns, and the Entities API for how EntityManager disposes handles on removal.

Diagnostics ​

MethodReturnsDescription
numActiveQudits()numberQudits in the shared state this property belongs to
stateVectorSize()numberBasis amplitudes in that shared state vector

Compare numActiveQudits() against getMaxQudits() before an interaction that could grow a shared state past the limit, rather than catching the throw.

Joint operations ​

Free functions for operations that span several properties, or that have no single target.

FunctionReturnsDescription
measure(...props)QuantumValue[]Measure several properties in one step. One declared value per property, in argument order
forcedMeasure(props, values)QuantumValue[]Measure with each outcome forced. For replays and tests
probabilities(...props)Array<{ values: QuantumValue[]; probability: number }>Joint probabilities. No collapse
densityMatrix(...props)Array<{ row: QuantumValue[]; col: QuantumValue[]; real: number; imag: number }>Reduced density matrix, rows and columns labelled with declared values. No collapse
measureWhen(preds)booleanMeasure whether every predicate holds, collapsing the state to agree
forcedMeasureWhen(preds, outcome)booleanThe same with the outcome forced. For replays and tests
probabilityWhen(preds)numberProbability that every predicate holds at once. No collapse
phaseRotate(angle, { when })voidRotate the phase of the part of the state where every predicate holds by angle radians
typescript
import { measure, densityMatrix, phaseRotate } from "quantum-forge/quantum";

const [va, vb] = measure(a, b);
phaseRotate(Math.PI, { when: [a.is(true), b.is(true)] });

const rho = densityMatrix(a, b);
const entry = rho.find(
  (e) => e.row[0] === true && e.row[1] === false && e.col[0] === false && e.col[1] === true,
);
const relativePhase = entry ? Math.atan2(entry.imag, entry.real) : 0;

measure, forcedMeasure, probabilities and densityMatrix throw when called with no properties. forcedMeasure() throws when values has a different length from props.

All three forced calls throw when the forced outcome has zero probability in the current state. forcedMeasure() and forcedMeasureWhen() check before they measure and leave the state unchanged when they throw.

phaseRotate() throws on an angle that is NaN or infinite.

Handle utilities ​

isQuantum(x): x is Quantum<any> ​

True when x is a handle made by quantum(). A spread copy of a handle is not a handle. The engine's EntityManager uses this to find handles on an entity.

QUANTUM_HANDLE ​

The registry symbol Symbol.for("quantum-forge.quantum-handle"). Every handle carries it as a non-enumerable property with the value true. isQuantum() reads it, and two copies of the core package recognize each other's handles through it.

clearQuantumCache(): void ​

Destroys every cached qudit. Live handles are not affected. Tests use it, and a game can call it between scenes to free memory.

observeQuantum(observer): () => void ​

Attaches an observer that sees every quantum operation after it succeeds, and returns a function that detaches it. QuantumRecorder is built on this. Most games never need it.

typescript
interface QuantumObserver {
  onCreate?(prop: Quantum<any>): void;
  onGate?(event: QuantumGateEvent): void;
  onMeasure?(event: QuantumMeasureEvent): void;
  onDispose?(prop: Quantum<any>, value: QuantumValue): void;
}

Events name handles by id and values by index, and use the WASM operation names: superpose() reports "hadamard", flip() and next() report "cycle". A gate's fraction is undefined when the discrete gate ran.

typescript
interface SerializedQuantumPredicate { id: number; index: number; isEqual: boolean }

type QuantumGateEvent =
  | { op: "hadamard" | "cycle" | "shift" | "clock" | "x" | "y" | "z";
      target: number; fraction: number | undefined; predicates: SerializedQuantumPredicate[] }
  | { op: "inverse_hadamard"; target: number; predicates: SerializedQuantumPredicate[] }
  | { op: "swap"; targets: [number, number]; predicates: SerializedQuantumPredicate[] }
  | { op: "i_swap"; targets: [number, number]; fraction: number; predicates: SerializedQuantumPredicate[] }
  | { op: "phase_rotate"; angle: number; predicates: SerializedQuantumPredicate[] };

type QuantumMeasureEvent =
  | { op: "measure"; targets: number[]; outcomes: number[] }
  | { op: "forced_measure"; targets: number[]; forced: number[]; outcomes: number[] }
  | { op: "measure_predicate"; predicates: SerializedQuantumPredicate[]; outcome: number }
  | { op: "forced_measure_predicate"; predicates: SerializedQuantumPredicate[]; forced: number; outcome: number };

Delivery rules:

  • An observer that throws does not stop the others from seeing the event, and the error never reaches the game call. It is reported through reportError(), or console.error where the runtime lacks it.
  • Events arrive in the order the operations ran. An operation run from inside an observer is delivered after the current event has reached every observer.
  • Gate and measurement events are frozen, and every observer gets the same object. onCreate and onDispose receive the live handle.
  • The measurement dispose() makes is not reported through onMeasure. onDispose carries its value instead.
  • Operations run through handle.raw are never reported.

QuantumRecorder ​

Records every operation on quantum() handles into a JSON log and replays it into fresh handles. See Recording & Replay for the guide.

typescript
import { QuantumRecorder, quantum, measure } from "quantum-forge/quantum";

const recorder = new QuantumRecorder();
recorder.startRecording();
const a = quantum([false, true]);
const b = quantum([false, true]);
a.superpose();
b.flip({ when: [a.is(true)] });
measure(a, b);
const saved = QuantumRecorder.serialize(recorder.stopRecording());

const handles = QuantumRecorder.replay(QuantumRecorder.deserialize(saved));
const replayedA = handles.get(a.id);

Constructor ​

typescript
new QuantumRecorder()

Takes no arguments. Passing one, as 2.x code did with a manager, throws a TypeError that points to LegacyQuantumRecorder.

Methods ​

MethodReturnsDescription
startRecording()voidBegin recording with an empty log. Calling it while recording restarts
stopRecording()QuantumLogStop and return the log
isRecording()booleanTrue between startRecording() and stopRecording()
getLog()QuantumLogA copy of the log so far. Works while recording
QuantumRecorder.replay(log)Map<number, Quantum<any>>Replay into fresh handles. Keys are the ids in the log
QuantumRecorder.serialize(log)stringJSON text
QuantumRecorder.deserialize(text)QuantumLogParse and validate JSON text

replay() makes a fresh handle for every create entry and replays every measurement as a forced measurement with the recorded outcome. The returned map holds the handles still live at the end of the log; handles the log disposes are not in it. A recorder that is running during replay() records what the replay did.

deserialize() throws on text that is not JSON, a missing or unsupported version, and any malformed entry. replay() validates the same way, and also throws on a log that lists untrackedIds or references an id with no earlier create entry. No handle survives a throw from replay().

Start recording before creating the handles you want to replay. An operation on a handle created earlier logs a warning once per handle and adds its id to untrackedIds, and replay() refuses that log.

QuantumLog ​

typescript
interface QuantumLog {
  version: 1;
  entries: QuantumLogEntry[];
  /** Present only when the log touched handles created before startRecording(). */
  untrackedIds?: number[];
}

type QuantumLogEntry =
  | { op: "create"; id: number; values: QuantumValue[] }
  | { op: "dispose"; id: number; outcome: number }
  | { op: "hadamard" | "cycle" | "shift" | "clock" | "x" | "y" | "z";
      target: number; fraction?: number; predicates: SerializedQuantumPredicate[] }
  | { op: "inverse_hadamard"; target: number; predicates: SerializedQuantumPredicate[] }
  | { op: "swap"; targets: [number, number]; predicates: SerializedQuantumPredicate[] }
  | { op: "i_swap"; targets: [number, number]; fraction: number; predicates: SerializedQuantumPredicate[] }
  | { op: "phase_rotate"; angle: number; predicates: SerializedQuantumPredicate[] }
  | { op: "measure"; targets: number[]; outcomes: number[] }
  | { op: "forced_measure"; targets: number[]; forced: number[]; outcomes: number[] }
  | { op: "measure_predicate"; predicates: SerializedQuantumPredicate[]; outcome: number }
  | { op: "forced_measure_predicate"; predicates: SerializedQuantumPredicate[]; forced: number; outcome: number };

Entries name handles by id, values by index, and gates by their physics name. A gate's fraction is left out when the discrete gate ran.

LegacyQuantumRecorder ​

The 2.x recorder under a new name. It records QuantumPropertyManager operations and replays logs made by the 2.x QuantumRecorder. It is deprecated along with the manager and goes in 4.0. See QuantumPropertyManager (deprecated).

Module loading ​

ensureLoaded(): Promise<void> ​

Loads the WASM module. Await it once before the first quantum() call. Later calls resolve at once.

startBackgroundLoad(logger?: LoggerInterface): void ​

Starts loading in the background via requestIdleCallback, or setTimeout where that is missing. Call it early for faster perceived startup, then await ensureLoaded() where you need the module.

isReady(): boolean ​

Non-blocking check. true once the module has loaded.

useQuantumForgeBuild(name: string): void ​

Selects a named WASM build, such as "qubit" for the Qubit Edition. Call it before ensureLoaded(). In a page it loads from /quantum-forge-<name>; in Node it loads from the variant's folder inside the installed package. A call after the module has loaded logs a warning and does nothing.

setWasmBasePath(path: string): void ​

Sets where the WASM files come from. In a page the default is "/quantum-forge", the path the Vite plugin serves. In Node the default is the WASM inside the installed package, so you only need this when the files live somewhere else; pass a file URL. Call it before ensureLoaded(). A later useQuantumForgeBuild() call replaces it.

getWasmBasePath(): string ​

The base path ensureLoaded() will load from, after the defaults above are applied. Useful when a load fails.

registerServiceWorker(swPath?: string): Promise<ServiceWorkerRegistration | null> ​

Registers the offline caching service worker. Default path: "/quantum-forge-sw.js". Resolves null where service workers are unavailable or registration fails.

getQuantumForge().initialize(options?: InitializeOptions): Promise<void> ​

Low-level WASM initialization. The QuantumForge class is not exported directly; reach it through getQuantumForge(). Most consumers use ensureLoaded() instead, but calling initialize() directly lets you pass InitializeOptions:

typescript
interface InitializeOptions {
  /** Override Emscripten's default stderr handler (console.warn). */
  printErr?: (text: string) => void;
  /** Override Emscripten's default stdout handler (console.log). */
  print?: (text: string) => void;
}

See Setup: WASM Output Redirection for usage examples.

INFO

The framework's startBackgroundLoad(logger) / ensureLoaded() already passes printErr to route WASM stderr through the provided logger at warn level.

Build info ​

getVersion(): string ​

The package version, as reported by the loaded WASM module.

getMaxDimension(): number ​

Maximum number of values per property in the loaded build: 3 for the Qutrit Edition, 2 for the Qubit Edition. Read it after ensureLoaded() to detect which edition is running.

getMaxQudits(): number ​

Maximum number of qudits one shared state can hold in the loaded build: 12 in the Qutrit Edition, 20 in the Qubit Edition.

getMaxStateSize(): number ​

Maximum number of basis amplitudes any single state vector can hold (compile-time limit, currently 100,000). See Lifecycle: State Budget.

getWasmMemoryBytes(): number | null ​

Current WASM heap usage in bytes, or null before the module has loaded.

getAttribution(): string ​

The attribution string to display in your application. Showing it is a license requirement, so put it somewhere a player can see: a credits screen, an about panel, or a splash screen.

Raw WASM access ​

Game code does not need this section. The handle API has no batch call of its own yet, so the batch APIs work on raw WASM properties, reached through handle.raw and getModule().

handle.raw ​

The WASM property behind a handle, typed QuantumProperty. Three rules:

  • Operations through .raw bypass the handle, so observers and QuantumRecorder never see them. A recorded session that mixes them in will not replay correctly.
  • Never call destroy() on it. The handle owns the property, and its dispose() fails after one.
  • It is valid only while the handle is live. Reading .raw on a disposed handle throws, and a reference kept from earlier may already back a different handle.

getModule() ​

Returns the loaded WASM module, and throws if ensureLoaded() has not finished. It carries executeBatch, executeBatchTape, and the QuantumSimulation class. WASM calls use snake_case names (i_swap, measure_properties) and basis indices, not declared values. Predicates for the batch API come from the raw property: a.raw.is(1), a.raw.is_not(0).

Batch execution ​

m.executeBatch(ops: BatchOp[]): BatchResult ​

Execute multiple gate operations in a single WASM call. See Gates: Batch Gate Execution for the full guide.

typescript
import { getModule, quantum, type BatchOp } from "quantum-forge/quantum";

const a = quantum([false, true]);
const b = quantum([false, true]);

const ops: BatchOp[] = [
  { op: "hadamard", target: a.raw },
  { op: "cycle", target: b.raw, predicates: [a.raw.is(1)] },
];
const result = getModule().executeBatch(ops);

BatchOp ​

typescript
type OpCode =
  | 'cycle' | 'shift' | 'clock'
  | 'x' | 'z' | 'y'
  | 'hadamard' | 'inverse_hadamard'
  | 'swap' | 'i_swap'
  | 'phase_rotate';

interface BatchOp {
  op: OpCode;
  target?: QuantumProperty;
  target2?: QuantumProperty;  // swap, i_swap
  fraction?: number;          // omit for the discrete gate
  angle?: number;             // phase_rotate only
  predicates?: Predicate[];
}

At the WASM level an omitted fraction selects the discrete gate, and fraction: 1.0 selects the fractional gate at 1.0, a slower path. Unlike the handle methods, the batch API does not turn 1 into the discrete gate.

BatchResult ​

typescript
interface BatchResult {
  opsExecuted: number;   // how many completed before stopping
  success: boolean;
  errorMessage: string;  // non-empty on failure
}

m.executeBatchTape(properties: QuantumProperty[], tape: Float64Array): BatchResult ​

High-throughput batch execution using a pre-encoded Float64Array. Bypasses per-op embind marshaling, so the tape crosses into WASM as one bulk memcpy instead of thousands of JS object conversions. Use for pre-built operation sequences with 50+ ops. Pass raw properties: [a.raw, b.raw].

Tape format per operation (variable length, minimum 6 doubles):

[opcode, target_idx, target2_idx, fraction, angle, pred_count,
 (pred_prop_idx, pred_value, pred_is_equal) × pred_count]
  • target_idx / target2_idx: index into properties array, or -1 for none
  • fraction: NaN for the discrete gate
  • pred_value: a basis index, not a declared value
  • pred_is_equal: 1.0 for is(), 0.0 for is_not()

OP constants ​

typescript
import { OP } from "quantum-forge/quantum";

OP.CYCLE              // 0
OP.SHIFT              // 1
OP.CLOCK              // 2
OP.X                  // 3
OP.Z                  // 4
OP.Y                  // 5
OP.HADAMARD           // 6
OP.INVERSE_HADAMARD   // 7
OP.SWAP               // 8
OP.I_SWAP             // 9
OP.PHASE_ROTATE       // 10
OP.ROTATE_BASIS_PAIR  // 11

ROTATE_BASIS_PAIR has a different tape layout: [11, nq, -1, NaN, angle, 0, a[0]..a[nq-1], b[0]..b[nq-1]]

See Gates: Batch Gate Execution for usage guide.

QuantumSimulation ​

Isolated simulation context for search and look-ahead, the way quantum chess uses it. Properties created within a simulation can entangle with each other but not with properties from other simulations or with quantum() handles. It works on raw WASM properties, not handles. See Lifecycle: QuantumSimulation for usage guide.

QuantumSimulation is not exported from quantum-forge/quantum. Reach it through the loaded module:

typescript
const sim = getQuantumForge().createSimulation();

// or, if you want the class itself
const { QuantumSimulation } = getModule();
const sim2 = new QuantumSimulation();
MethodReturnsDescription
createProperty(dimension)QuantumPropertyCreate a property bound to this simulation
destroyProperty(prop)voidFactorize one property out, auto-split separable qudits
factorizeAllSeparable()voidScan all shared states and split separable qudits
destroy()voidRelease all properties and state vectors at once
isDestroyed()booleanWhether destroy() has already run

Gates and queries on simulation properties come from getModule(). The simulation itself has no getModule().

Error types ​

Errors raised inside the WASM module are standard Error objects with a message prefix that names the error type. See Error Handling for the full guide.

PrefixMeaning
[QuantumForgeStateSizeError]State vector would exceed the maximum size (100,000 basis amplitudes)
[QuantumForgeOutOfMemoryError]WASM memory allocation failed
[QuantumForgeError]General error (invalid operation, destroyed property, cross-simulation entanglement)

The handle catches most mistakes before they reach the WASM and throws with a message in game terms instead: a disposed handle, an undeclared value, flip() on a qutrit, a predicate on the gate's own target, an impossible forced outcome, and an interaction past the qudit limit.

Deprecated: QuantumPropertyManager ​

QuantumPropertyManager, its PredicateSpec and QuantumRecorderHook types, and LegacyQuantumRecorder still ship in 3.x so a game can move over one system at a time. They go in 4.0. QuantumPropertyManager (deprecated) documents the old API, and Migrating to 3.0 maps each call to its handle form.

Powered by Quantum Forge