quantum-forge 2.7.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/QUANTUM_FORGE.md +203 -207
- package/README.md +54 -38
- package/dist/lib/quantum.d.ts +674 -61
- package/dist/lib/quantum.js +1054 -4
- package/dist/lib/quantum.js.map +1 -1
- package/dist/quantum-forge-qubit/quantum-forge-web-esm.wasm +0 -0
- package/dist/quantum-forge-web-esm.wasm +0 -0
- package/package.json +3 -2
- package/quantum-forge-sw.js +1 -1
- package/dist/quantum-forge-web-0.3.0.tgz +0 -0
package/QUANTUM_FORGE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: quantum-forge
|
|
3
|
-
description: Quantum Forge
|
|
3
|
+
description: Quantum Forge 3.0 reference for game code that imports quantum-forge or quantum-forge-engine. Covers quantum() handles, gates and their aliases, predicates, entanglement through interaction, measurement, dispose, QuantumRecorder, and the engine's rendering and input. Use when writing or debugging quantum game code or WASM loading.
|
|
4
4
|
allowed-tools: Read Glob Grep
|
|
5
5
|
user-invocable: true
|
|
6
6
|
argument-hint: [topic]
|
|
@@ -8,24 +8,12 @@ argument-hint: [topic]
|
|
|
8
8
|
|
|
9
9
|
# Quantum Forge Reference
|
|
10
10
|
|
|
11
|
-
##
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const p = JSON.parse(fs.readFileSync('package.json', 'utf-8'));
|
|
18
|
-
const deps = { ...p.dependencies, ...p.devDependencies };
|
|
19
|
-
const core = deps['quantum-forge'] || deps['quantum-forge'] || 'not installed';
|
|
20
|
-
const engine = deps['quantum-forge-engine'] || deps['quantum-forge-engine'] || 'not installed';
|
|
21
|
-
const corePkg = deps['quantum-forge'] ? 'quantum-forge' : 'quantum-forge';
|
|
22
|
-
const enginePkg = deps['quantum-forge-engine'] ? 'quantum-forge-engine' : 'quantum-forge-engine';
|
|
23
|
-
console.log('Core: ' + corePkg + '@' + core);
|
|
24
|
-
if (engine !== 'not installed') console.log('Engine: ' + enginePkg + '@' + engine);
|
|
25
|
-
console.log('Type: ' + (p.type || 'commonjs'));
|
|
26
|
-
} catch { console.log('No package.json found'); }
|
|
27
|
-
" 2>/dev/null
|
|
28
|
-
```
|
|
11
|
+
## Version
|
|
12
|
+
|
|
13
|
+
This reference covers core 3.0 and newer, where every quantum property is a `quantum()`
|
|
14
|
+
handle. Check the core version in the project's `package.json`. A 2.x install has no handles:
|
|
15
|
+
upgrade it rather than writing `QuantumPropertyManager` code, which is deprecated through 3.x
|
|
16
|
+
and removed in 4.0.
|
|
29
17
|
|
|
30
18
|
## Initialization
|
|
31
19
|
|
|
@@ -47,11 +35,11 @@ await ensureLoaded();
|
|
|
47
35
|
No Vite plugin is needed in Node (tests, servers, agents). The loader finds the WASM inside the installed package, and `useQuantumForgeBuild("qubit")` selects the variant the same way as in a page. Use `setWasmBasePath(fileUrl)` only for WASM files kept outside the package. Node 22 or newer.
|
|
48
36
|
|
|
49
37
|
```typescript
|
|
50
|
-
import { useQuantumForgeBuild, ensureLoaded,
|
|
38
|
+
import { useQuantumForgeBuild, ensureLoaded, getVersion, quantum } from "quantum-forge/quantum";
|
|
51
39
|
useQuantumForgeBuild("qubit");
|
|
52
40
|
await ensureLoaded();
|
|
53
41
|
console.log(getVersion()); // the package version
|
|
54
|
-
const
|
|
42
|
+
const coin = quantum([false, true]).superpose();
|
|
55
43
|
```
|
|
56
44
|
|
|
57
45
|
### Vite Plugin
|
|
@@ -70,10 +58,11 @@ ESM note: if using `vite.config.js` (not `.ts`/`.mjs`), add `"type": "module"` t
|
|
|
70
58
|
|
|
71
59
|
Quantum Forge gives game objects real quantum state. The usage loop:
|
|
72
60
|
|
|
73
|
-
1. **
|
|
61
|
+
1. **Declare quantum properties** on things with `quantum([...values])`. Each property is a qudit (d-dimensional quantum digit)
|
|
74
62
|
2. **Apply gates** to transform state without collapsing it
|
|
75
|
-
3. **
|
|
63
|
+
3. **Let properties interact** (a two-property gate, or a gate predicated on another property). Entanglement is what an interaction leaves behind
|
|
76
64
|
4. **Measure** to collapse quantum state to a classical value (the game moment)
|
|
65
|
+
5. **Dispose** the handle when the thing it describes is gone
|
|
77
66
|
|
|
78
67
|
### Programmer's Mental Model
|
|
79
68
|
|
|
@@ -81,301 +70,307 @@ Quantum Forge gives game objects real quantum state. The usage loop:
|
|
|
81
70
|
|
|
82
71
|
**Gates** are collection algorithms that transform every entry at once. `cycle` increments each value. `hadamard` creates equal superposition.
|
|
83
72
|
|
|
84
|
-
**Predicated gates** apply a filter: "if property A is
|
|
73
|
+
**Predicated gates** apply a filter: "if property A is true, flip property B." This creates entanglement, correlated entries in the collection.
|
|
85
74
|
|
|
86
75
|
The weights are **complex amplitudes**, not probabilities. When two entries with the same value meet, their amplitudes add. In phase = constructive interference (probability increases). Out of phase = destructive interference (probability cancels). Probability of a value = squared magnitude of its amplitude.
|
|
87
76
|
|
|
88
|
-
##
|
|
77
|
+
## Quantum properties
|
|
89
78
|
|
|
90
|
-
|
|
79
|
+
A property is declared by the values it can take. The dimension is the number of values, and
|
|
80
|
+
the property starts at the first one.
|
|
91
81
|
|
|
92
82
|
```typescript
|
|
93
|
-
import {
|
|
83
|
+
import { ensureLoaded, quantum } from "quantum-forge/quantum";
|
|
94
84
|
|
|
95
85
|
await ensureLoaded();
|
|
96
86
|
|
|
97
|
-
|
|
98
|
-
|
|
87
|
+
const color = quantum(["red", "green", "blue"]); // qutrit, starts "red"
|
|
88
|
+
const alive = quantum([false, true]); // qubit, starts false
|
|
89
|
+
const die = quantum(3); // numeric form: values 0, 1, 2
|
|
99
90
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
this.setProperty(id, prop);
|
|
106
|
-
}
|
|
91
|
+
color.superpose(); // alias for hadamard()
|
|
92
|
+
color.probability("green"); // 1/3, no collapse
|
|
93
|
+
const seen = color.measure(); // "red" | "green" | "blue"
|
|
94
|
+
color.dispose(); // end of life; the qudit is cached for reuse
|
|
95
|
+
```
|
|
107
96
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const p2 = this.getProperty(id2);
|
|
111
|
-
if (!p1 || !p2) return;
|
|
112
|
-
this.getModule().i_swap(p1, p2, 0.5);
|
|
113
|
-
}
|
|
97
|
+
Keep each handle on the entity it describes (`ball.exists = quantum([false, true])`). A ball
|
|
98
|
+
without a handle is classical. There is no id map and no manager.
|
|
114
99
|
|
|
115
|
-
|
|
116
|
-
const prop = this.getProperty(id);
|
|
117
|
-
if (!prop) return 1.0;
|
|
118
|
-
const results = this.getModule().probabilities([prop]);
|
|
119
|
-
for (const r of results) {
|
|
120
|
-
if (r.qudit_values[0] === 1) return r.probability;
|
|
121
|
-
}
|
|
122
|
-
return 0;
|
|
123
|
-
}
|
|
100
|
+
### Dimension
|
|
124
101
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
this.deleteProperty(id);
|
|
130
|
-
this.releaseProperty(prop, value); // reset to |0⟩, return to pool
|
|
131
|
-
return value;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
```
|
|
102
|
+
| Dimension | Declaration | Use Case |
|
|
103
|
+
|-----------|-------------|----------|
|
|
104
|
+
| 2 (qubit) | `quantum([false, true])` | Binary: exists/doesn't, alive/dead |
|
|
105
|
+
| 3 (qutrit) | `quantum(["rock", "paper", "scissors"])` | Three-way choices |
|
|
135
106
|
|
|
136
|
-
|
|
107
|
+
Start with two values unless your design needs more. The shipped Qutrit Edition allows up to
|
|
108
|
+
three; `quantum()` throws past the build's maximum.
|
|
137
109
|
|
|
138
|
-
|
|
139
|
-
|-----------|--------|----------|
|
|
140
|
-
| 2 (qubit) | `\|0⟩`, `\|1⟩` | Binary: exists/doesn't, alive/dead |
|
|
141
|
-
| 3 (qutrit) | `\|0⟩` – `\|2⟩` | Three-way: rock/paper/scissors |
|
|
110
|
+
### Values and indices
|
|
142
111
|
|
|
143
|
-
|
|
112
|
+
Every method that takes a value (`is`, `isNot`, `probability`, `forcedMeasure`) accepts the
|
|
113
|
+
declared value or its index. `alive.is(true)` and `alive.is(1)` are the same predicate. A value
|
|
114
|
+
that is not declared throws at the call. `measure()` returns the declared value.
|
|
144
115
|
|
|
145
116
|
## Gates
|
|
146
117
|
|
|
147
|
-
|
|
118
|
+
Gates are methods on the handle and return it, so they chain. Physics names are primary; each
|
|
119
|
+
alias runs exactly the same gate.
|
|
148
120
|
|
|
149
|
-
|
|
|
150
|
-
|
|
151
|
-
|
|
|
152
|
-
|
|
|
153
|
-
|
|
|
154
|
-
|
|
|
155
|
-
|
|
|
156
|
-
|
|
|
157
|
-
|
|
|
158
|
-
|
|
|
159
|
-
|
|
|
121
|
+
| Physics call | Alias | Dim | Description |
|
|
122
|
+
|--------------|-------|-----|-------------|
|
|
123
|
+
| `hadamard(f?, opts?)` | `superpose` | Any | Equal superposition. The main "make it quantum" gate |
|
|
124
|
+
| `inverseHadamard(opts?)` | | Any | Reverse of hadamard |
|
|
125
|
+
| `cycle(f?, opts?)` | `next`; `flip` on qubits | Any | Next value, wrapping. NOT at dimension 2 |
|
|
126
|
+
| `shift(f?, opts?)` | `previous` | Any | Previous value, wrapping. Same as cycle at dimension 2 |
|
|
127
|
+
| `clock(f?, opts?)` | `phase` | Any | Phase rotation. Shows on a superposition at the next gate that mixes values |
|
|
128
|
+
| `x(f?, opts?)` / `z(f?, opts?)` | | Any | Pauli X (= shift) and Pauli Z (= clock) |
|
|
129
|
+
| `y(f?, opts?)` | | **2 only** | Pauli Y. Throws if dimension != 2 |
|
|
130
|
+
| `a.iSwap(b, f, opts?)` | | Any | Anti-correlated entanglement. Fraction required |
|
|
131
|
+
| `a.swap(b, opts?)` | | Any | Direct state exchange |
|
|
132
|
+
| `phaseRotate(angle, { when })` | | Any | Free function. Phase on the states where the predicates hold |
|
|
160
133
|
|
|
161
|
-
|
|
134
|
+
`flip` throws on anything but two values; use `next` on a qutrit. `swap` and `iSwap` throw
|
|
135
|
+
when passed the handle they are called on. There is no `entangle()`. A property needs at least
|
|
136
|
+
two values, and `quantum()` is the only way to make one; the `Quantum` constructor is private.
|
|
162
137
|
|
|
163
|
-
|
|
138
|
+
Every gate except `inverseHadamard`, `swap` and `iSwap` takes an optional fraction first. An
|
|
139
|
+
omitted fraction, or exactly 1, is the discrete gate; any other number is the continuous version, and 0.5 is the square root of the gate. `0.1` barely moves the state.
|
|
140
|
+
Apply a small fraction every frame for a gradual effect.
|
|
164
141
|
|
|
165
|
-
|
|
142
|
+
### Predicates (conditional gates)
|
|
166
143
|
|
|
167
|
-
|
|
144
|
+
Pass predicates in `{ when: [...] }`. The gate acts only where every predicate holds. A
|
|
145
|
+
predicate on another property makes the gate an interaction, which entangles the two.
|
|
168
146
|
|
|
169
147
|
```typescript
|
|
170
|
-
// CNOT: flip target only when control is
|
|
171
|
-
|
|
172
|
-
// Result: (|00⟩ + |11⟩)/√2
|
|
148
|
+
// CNOT: flip target only when control is true
|
|
149
|
+
target.flip({ when: [control.is(true)] });
|
|
150
|
+
// Result: (|00⟩ + |11⟩)/√2, positively correlated
|
|
151
|
+
|
|
152
|
+
// CZ
|
|
153
|
+
target.phase({ when: [control.is(true)] });
|
|
173
154
|
|
|
174
|
-
// Controlled Hadamard
|
|
175
|
-
|
|
155
|
+
// Controlled Hadamard, and a fractional controlled gate
|
|
156
|
+
target.superpose({ when: [control.is(true)] });
|
|
157
|
+
target.flip(0.5, { when: [control.is(true)] });
|
|
176
158
|
|
|
177
|
-
// Predicate types
|
|
178
|
-
prop.is(value) //
|
|
179
|
-
prop.
|
|
159
|
+
// Predicate types
|
|
160
|
+
prop.is(value) // holds when the property equals value (declared value or index)
|
|
161
|
+
prop.isNot(value) // holds when it does not
|
|
180
162
|
|
|
181
163
|
// Multiple predicates are AND'd
|
|
182
|
-
|
|
164
|
+
target.flip({ when: [controlA.is(true), controlB.is(true)] });
|
|
183
165
|
```
|
|
184
166
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
```typescript
|
|
188
|
-
import type { PredicateSpec } from "quantum-forge/quantum";
|
|
189
|
-
const specs: PredicateSpec[] = [
|
|
190
|
-
{ property: controlProp, value: 1, isEqual: true },
|
|
191
|
-
];
|
|
192
|
-
const wasmPreds = specs.map(s =>
|
|
193
|
-
s.isEqual ? s.property.is(s.value) : s.property.is_not(s.value)
|
|
194
|
-
);
|
|
195
|
-
```
|
|
167
|
+
## Entanglement patterns
|
|
196
168
|
|
|
197
|
-
|
|
169
|
+
Entanglement is what an interaction leaves behind. A gate that touches two properties, or one
|
|
170
|
+
whose predicate reads another property, is an interaction. Measuring one entangled property
|
|
171
|
+
settles the other.
|
|
198
172
|
|
|
199
|
-
### Entangle-
|
|
173
|
+
### Entangle-split (iSwap)
|
|
200
174
|
|
|
201
|
-
Object splits into anti-correlated pair. Exactly one is real.
|
|
175
|
+
Object splits into an anti-correlated pair. Exactly one is real.
|
|
202
176
|
|
|
203
177
|
```typescript
|
|
204
|
-
entangleSplit(
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
m.cycle(prop1); // |1⟩ (exists)
|
|
209
|
-
m.i_swap(prop1, prop2, 0.5); // entangle
|
|
210
|
-
this.setProperty(originalId, prop1);
|
|
211
|
-
this.setProperty(newId, prop2);
|
|
178
|
+
function entangleSplit(original: Ball, clone: Ball): void {
|
|
179
|
+
original.exists = quantum([false, true]).flip(); // exists
|
|
180
|
+
clone.exists = quantum([false, true]); // does not
|
|
181
|
+
original.exists.iSwap(clone.exists, 0.5);
|
|
212
182
|
}
|
|
213
183
|
```
|
|
214
184
|
|
|
215
|
-
### Correlated
|
|
185
|
+
### Correlated pair (CNOT)
|
|
216
186
|
|
|
217
|
-
Both match
|
|
187
|
+
Both match: both alive or both dead.
|
|
218
188
|
|
|
219
189
|
```typescript
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
m.cycle(prop1);
|
|
225
|
-
m.hadamard(prop1);
|
|
226
|
-
m.shift(prop2, undefined, [prop1.is(1)]); // CNOT
|
|
227
|
-
this.setProperty(id1, prop1);
|
|
228
|
-
this.setProperty(id2, prop2);
|
|
190
|
+
function linkPair(a: Enemy, b: Enemy): void {
|
|
191
|
+
a.alive = quantum([false, true]).superpose();
|
|
192
|
+
b.alive = quantum([false, true]);
|
|
193
|
+
b.alive.flip({ when: [a.alive.is(true)] });
|
|
229
194
|
}
|
|
230
195
|
```
|
|
231
196
|
|
|
232
|
-
### Quantum-
|
|
197
|
+
### Quantum-split
|
|
233
198
|
|
|
234
|
-
Split an already-quantum object.
|
|
199
|
+
Split an already-quantum object. The split adds one qudit to the original's shared state, so
|
|
200
|
+
check that there is room first. Catching the over-limit throw instead leaks WASM memory on
|
|
201
|
+
every catch.
|
|
235
202
|
|
|
236
203
|
```typescript
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
return false;
|
|
246
|
-
}
|
|
247
|
-
this.setProperty(newId, prop2);
|
|
204
|
+
import { getMaxQudits, quantum } from "quantum-forge/quantum";
|
|
205
|
+
|
|
206
|
+
function quantumSplit(original: Ball, clone: Ball): boolean {
|
|
207
|
+
if (!original.exists) return false;
|
|
208
|
+
if (original.exists.numActiveQudits() >= getMaxQudits()) return false; // state is full
|
|
209
|
+
const split = quantum([false, true]);
|
|
210
|
+
original.exists.iSwap(split, 0.5);
|
|
211
|
+
clone.exists = split;
|
|
248
212
|
return true;
|
|
249
213
|
}
|
|
250
214
|
```
|
|
251
215
|
|
|
252
|
-
### Overlap
|
|
253
|
-
|
|
254
|
-
Objects that spatially overlap become entangled:
|
|
255
|
-
|
|
256
|
-
```typescript
|
|
257
|
-
this.getModule().i_swap(propA, propB, 0.5);
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
### Conditional Superposition
|
|
216
|
+
### Overlap entanglement
|
|
261
217
|
|
|
262
|
-
|
|
218
|
+
Quantum objects that spatially overlap become entangled. Both need a handle, and the pair
|
|
219
|
+
must differ somewhere: iSwap on two equal values does nothing.
|
|
263
220
|
|
|
264
221
|
```typescript
|
|
265
|
-
|
|
222
|
+
function overlap(a: Ball, b: Ball): void {
|
|
223
|
+
if (a.exists && b.exists) a.exists.iSwap(b.exists, 0.5);
|
|
224
|
+
}
|
|
266
225
|
```
|
|
267
226
|
|
|
268
|
-
### Choosing
|
|
227
|
+
### Choosing between mechanisms
|
|
269
228
|
|
|
270
229
|
| Mechanism | Correlation | Use |
|
|
271
230
|
|-----------|-------------|-----|
|
|
272
|
-
|
|
|
273
|
-
|
|
|
274
|
-
| `
|
|
275
|
-
| `
|
|
231
|
+
| `b.flip({ when: [a.is(true)] })` (CNOT) | Positive, both match | Linked states: both alive or both dead |
|
|
232
|
+
| `b.superpose({ when: [a.is(true)] })` | Conditional superposition | One object's quantumness depends on another |
|
|
233
|
+
| `a.iSwap(b, 0.5)` | Anti-correlated, exactly one | Object splits into two ghosts |
|
|
234
|
+
| `iSwap(0.5)`, `phase(f)`, `iSwap(0.5)` | Tunable bias | Player-influenced split probability |
|
|
276
235
|
|
|
277
236
|
## Measurement
|
|
278
237
|
|
|
279
|
-
Measurement collapses superposition to a definite value. Entangled partners collapse
|
|
238
|
+
Measurement collapses superposition to a definite value. Entangled partners collapse too.
|
|
280
239
|
|
|
281
240
|
```typescript
|
|
282
|
-
const
|
|
283
|
-
// Always pool after:
|
|
284
|
-
this.deleteProperty(id);
|
|
285
|
-
this.releaseProperty(prop, value);
|
|
241
|
+
const value = prop.measure(); // declared value, e.g. true or false
|
|
286
242
|
```
|
|
287
243
|
|
|
288
|
-
|
|
244
|
+
A measured handle is still usable. It holds a definite value and can go back into
|
|
245
|
+
superposition. When the object is gone, `dispose()` it.
|
|
246
|
+
|
|
247
|
+
### Joint measurement
|
|
289
248
|
|
|
290
249
|
```typescript
|
|
291
|
-
|
|
250
|
+
import { measure } from "quantum-forge/quantum";
|
|
251
|
+
const [va, vb, vc] = measure(propA, propB, propC);
|
|
292
252
|
```
|
|
293
253
|
|
|
294
|
-
### Reading
|
|
254
|
+
### Reading state (no collapse)
|
|
295
255
|
|
|
296
256
|
```typescript
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
257
|
+
import { probabilities, densityMatrix, probabilityWhen, measureWhen, forcedMeasure } from "quantum-forge/quantum";
|
|
258
|
+
|
|
259
|
+
prop.probability(true); // one value
|
|
260
|
+
prop.probabilities(); // [{ value: false, probability: 0.5 }, { value: true, probability: 0.5 }]
|
|
261
|
+
probabilities(a, b); // joint: [{ values: [false, true], probability }, ...]
|
|
300
262
|
|
|
301
|
-
// Reduced density matrix
|
|
302
|
-
|
|
303
|
-
// Off-diagonal entries carry relative phase
|
|
263
|
+
// Reduced density matrix: phase and correlation info, labelled with declared values
|
|
264
|
+
densityMatrix(a, b); // [{ row: [...], col: [...], real, imag }, ...]
|
|
304
265
|
|
|
305
|
-
//
|
|
306
|
-
const outcome = m.measure_predicate([prop1.is(1), prop2.is(0)]);
|
|
307
|
-
// 1 = satisfied, 0 = not
|
|
266
|
+
probabilityWhen([a.is(true), b.is(false)]); // probability all predicates hold
|
|
308
267
|
|
|
309
|
-
//
|
|
310
|
-
|
|
268
|
+
// Predicate measurement: collapses to agree, returns a boolean
|
|
269
|
+
measureWhen([a.is(true), b.is(false)]);
|
|
270
|
+
|
|
271
|
+
// Forced measurement: replay and tests only. Throws if the value has zero probability.
|
|
272
|
+
prop.forcedMeasure(true);
|
|
273
|
+
forcedMeasure([a, b], [true, false]);
|
|
311
274
|
```
|
|
312
275
|
|
|
313
276
|
## Phase & Interference
|
|
314
277
|
|
|
315
|
-
Phase is invisible to measurement
|
|
278
|
+
Phase is invisible to measurement on its own. A definite value ignores it. On a superposition it shows up at the next gate that mixes values, such as superpose or a partial iSwap, and steers that gate through interference. A phase between two half-splits decides where the object ends up:
|
|
316
279
|
|
|
317
280
|
```typescript
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
//
|
|
322
|
-
|
|
323
|
-
//
|
|
324
|
-
// Opposite phase = destructive interference (probability cancels)
|
|
281
|
+
const a = quantum([false, true]).flip(); // exists
|
|
282
|
+
const b = quantum([false, true]); // does not
|
|
283
|
+
a.iSwap(b, 0.5); // split
|
|
284
|
+
a.phase(bias); // bias from 0 to 1; 0.5 is a π/2 phase on true
|
|
285
|
+
a.iSwap(b, 0.5); // split again: the phase now moves probability
|
|
286
|
+
// a.probability(true) is 0 at bias 0, 0.5 at bias 0.5, 1 at bias 1
|
|
325
287
|
```
|
|
326
288
|
|
|
289
|
+
A phase before a single split changes nothing. It needs a later superpose or interaction to turn into probability.
|
|
290
|
+
|
|
327
291
|
**Grover oracle** (walls/barriers): mark states with π phase, then diffuse with fractional Hadamard. Probability "bounces off" marked states.
|
|
328
292
|
|
|
329
293
|
```typescript
|
|
330
|
-
|
|
331
|
-
|
|
294
|
+
phaseRotate(Math.PI, { when: [hex.is(wallState)] }); // mark
|
|
295
|
+
hex.superpose(0.1); // diffuse
|
|
332
296
|
```
|
|
333
297
|
|
|
334
|
-
**Key insight:** Phase is the control knob that lets players influence quantum outcomes without directly choosing them. The player can't pick which ball exists, but they can bias the odds by applying phase
|
|
298
|
+
**Key insight:** Phase is the control knob that lets players influence quantum outcomes without directly choosing them. The player can't pick which ball exists, but they can bias the odds by applying phase between two interactions.
|
|
335
299
|
|
|
336
|
-
### Visualizing
|
|
300
|
+
### Visualizing phase
|
|
337
301
|
|
|
338
302
|
Extract from the reduced density matrix:
|
|
339
303
|
|
|
340
304
|
```typescript
|
|
341
|
-
const
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
const phase = Math.atan2(entry.value.imag, entry.value.real);
|
|
305
|
+
for (const entry of densityMatrix(ref, target)) {
|
|
306
|
+
if (entry.row[0] === true && entry.row[1] === false &&
|
|
307
|
+
entry.col[0] === false && entry.col[1] === true) {
|
|
308
|
+
const phase = Math.atan2(entry.imag, entry.real);
|
|
346
309
|
// Map to dial rotation, color hue, force magnitude, compass direction
|
|
347
310
|
}
|
|
348
311
|
}
|
|
349
312
|
```
|
|
350
313
|
|
|
351
|
-
##
|
|
314
|
+
## Lifecycle
|
|
315
|
+
|
|
316
|
+
`dispose()` is the only lifecycle call. It measures the property, which collapses anything
|
|
317
|
+
entangled with it. A qudit that is then alone is reset and cached for the next `quantum()` of
|
|
318
|
+
the same dimension; one that still shares a state is destroyed, which splits it out of the
|
|
319
|
+
group. A reused handle never carries an old group.
|
|
320
|
+
|
|
321
|
+
A `using` declaration disposes at scope exit. It needs TypeScript 5.2+ with `"ESNext.Disposable"`
|
|
322
|
+
in the tsconfig `lib`; scaffolded projects have both.
|
|
323
|
+
|
|
324
|
+
The engine's `EntityManager.remove()` and `clear()` dispose handles stored directly on an entity
|
|
325
|
+
(string or symbol keys) or in an array field on it, and `add()` disposes the handles of an
|
|
326
|
+
entity it replaces that the new entity no longer carries. Removing an entity therefore
|
|
327
|
+
collapses whatever its handles were entangled with. A handle belongs to one entity. Pass
|
|
328
|
+
`{ disposeQuantum: false }` to keep the handles.
|
|
329
|
+
|
|
330
|
+
## Recording and replay
|
|
331
|
+
|
|
332
|
+
`QuantumRecorder` logs every operation on `quantum()` handles so you can save a session and rebuild its quantum state later, for replays, bug reports or deterministic tests. It listens through `observeQuantum()`, so the game makes no extra calls once recording has started.
|
|
352
333
|
|
|
353
334
|
```typescript
|
|
354
|
-
import { QuantumRecorder } from "quantum-forge/quantum";
|
|
335
|
+
import { QuantumRecorder, quantum, measure } from "quantum-forge/quantum";
|
|
355
336
|
|
|
356
|
-
const recorder = new QuantumRecorder(
|
|
337
|
+
const recorder = new QuantumRecorder();
|
|
357
338
|
recorder.startRecording();
|
|
358
|
-
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
339
|
+
const a = quantum([false, true]);
|
|
340
|
+
const b = quantum([false, true]);
|
|
341
|
+
a.superpose();
|
|
342
|
+
b.flip({ when: [a.is(true)] });
|
|
343
|
+
measure(a, b);
|
|
344
|
+
const saved = QuantumRecorder.serialize(recorder.stopRecording());
|
|
345
|
+
|
|
346
|
+
const handles = QuantumRecorder.replay(QuantumRecorder.deserialize(saved));
|
|
347
|
+
const replayedA = handles.get(a.id);
|
|
362
348
|
```
|
|
363
349
|
|
|
350
|
+
The log is plain JSON: `{ version: 1, entries: [...] }`. Entries name handles by their `id`, values by basis index, and operations by their physics name, so `superpose()` is logged as `hadamard` and `flip()` as `cycle`. `deserialize()` and `replay()` reject a log without that envelope or with any other version, and `deserialize()` checks every entry and throws on anything malformed.
|
|
351
|
+
|
|
352
|
+
Replay creates a fresh handle for every `create` entry and returns a map from the logged id to the new handle. Handles the log disposes are not in the map. Every measurement is replayed as a forced measurement with the recorded outcome, so the rebuilt state matches the original, including entangled pairs. A recorder that is running during `replay()` records what the replay did, so a game can load a save and keep recording into one log.
|
|
353
|
+
|
|
354
|
+
Start recording before creating the handles you want to replay. A handle created earlier has no `create` entry. The recorder warns once for each such handle and lists its id in the log's `untrackedIds`, and `replay()` refuses that log with an error naming the ids.
|
|
355
|
+
|
|
356
|
+
`new QuantumRecorder()` takes no arguments. Passing one, as 2.x code did with a manager, throws and points to `LegacyQuantumRecorder`.
|
|
357
|
+
|
|
358
|
+
Observers, the recorder included, cannot break the game. An error thrown inside an observer is caught and reported through `reportError()`, or `console.error` where the runtime lacks it, and the game call that triggered it goes on as normal. Observers receive operations in the order they ran.
|
|
359
|
+
|
|
364
360
|
## Performance
|
|
365
361
|
|
|
366
|
-
|
|
362
|
+
Dispose handles when their object is gone, or let `EntityManager` do it. A disposed property
|
|
363
|
+
stops counting against the qudit limit, so a game that keeps spawning and removing objects
|
|
364
|
+
stays within it.
|
|
367
365
|
|
|
368
366
|
Shipped limits: Qutrit Edition has dimension 3, 12 max qudits. Qubit Edition has dimension 2, 20 max qudits.
|
|
369
367
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
| 1–4 | No issues |
|
|
373
|
-
| 4–8 | Good with pooling |
|
|
374
|
-
| 8–12 | At the limit, aggressive pooling required |
|
|
375
|
-
|
|
376
|
-
**Most expensive operation**: tensor product (triggered by iSwap between properties in different shared states). Pooled properties avoid this — they're already in the shared state.
|
|
368
|
+
The most expensive operation is the tensor product, triggered the first time two properties in
|
|
369
|
+
different shared states interact.
|
|
377
370
|
|
|
378
|
-
|
|
371
|
+
To stay fast: dispose promptly, limit concurrent quantum objects, use two values unless you
|
|
372
|
+
need more, measure several properties in one `measure(a, b, c)` call, throttle probability
|
|
373
|
+
queries. `prop.numActiveQudits()` and `prop.stateVectorSize()` report how big the shared state is.
|
|
379
374
|
|
|
380
375
|
```typescript
|
|
381
376
|
// Monitor WASM memory
|
|
@@ -465,6 +460,7 @@ loop.start();
|
|
|
465
460
|
|
|
466
461
|
Map probability to visual properties (opacity, size, color):
|
|
467
462
|
|
|
463
|
+
<!-- sample:skip: method shown outside its class -->
|
|
468
464
|
```typescript
|
|
469
465
|
protected draw(state: GameState) {
|
|
470
466
|
for (const ball of state.balls) {
|
|
@@ -511,7 +507,7 @@ input.destroy();
|
|
|
511
507
|
|
|
512
508
|
| Export | Key APIs |
|
|
513
509
|
|--------|----------|
|
|
514
|
-
| `./quantum` | `
|
|
510
|
+
| `./quantum` | `quantum`, `Quantum`, `measure`, `probabilities`, `densityMatrix`, `measureWhen`, `phaseRotate`, `ensureLoaded`, `QuantumRecorder`, `useQuantumForgeBuild`, `setWasmBasePath` |
|
|
515
511
|
| `./logging` | `Logger` |
|
|
516
512
|
| `./vite-plugin` | `quantumForgeVitePlugin` |
|
|
517
513
|
|