quantum-forge 2.7.0 → 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 +213 -205
- package/README.md +74 -38
- package/dist/lib/quantum.d.ts +683 -64
- package/dist/lib/quantum.js +1073 -8
- package/dist/lib/quantum.js.map +1 -1
- package/dist/lib/vite-plugin.d.ts +1 -1
- package/dist/lib/vite-plugin.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 +6 -5
- package/quantum-forge-sw.js +3 -3
- package/scripts/prepare.mjs +16 -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-native/quantum-forge'] || 'not installed';
|
|
20
|
-
const engine = deps['quantum-forge-engine'] || deps['@quantum-native/quantum-forge-engine'] || 'not installed';
|
|
21
|
-
const corePkg = deps['quantum-forge'] ? 'quantum-forge' : '@quantum-native/quantum-forge';
|
|
22
|
-
const enginePkg = deps['quantum-forge-engine'] ? 'quantum-forge-engine' : '@quantum-native/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
|
|
|
@@ -42,6 +30,18 @@ useQuantumForgeBuild("qubit"); // before ensureLoaded()
|
|
|
42
30
|
await ensureLoaded();
|
|
43
31
|
```
|
|
44
32
|
|
|
33
|
+
### Node and headless
|
|
34
|
+
|
|
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.
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { useQuantumForgeBuild, ensureLoaded, getVersion, quantum } from "quantum-forge/quantum";
|
|
39
|
+
useQuantumForgeBuild("qubit");
|
|
40
|
+
await ensureLoaded();
|
|
41
|
+
console.log(getVersion()); // the package version
|
|
42
|
+
const coin = quantum([false, true]).superpose();
|
|
43
|
+
```
|
|
44
|
+
|
|
45
45
|
### Vite Plugin
|
|
46
46
|
|
|
47
47
|
```typescript
|
|
@@ -58,10 +58,11 @@ ESM note: if using `vite.config.js` (not `.ts`/`.mjs`), add `"type": "module"` t
|
|
|
58
58
|
|
|
59
59
|
Quantum Forge gives game objects real quantum state. The usage loop:
|
|
60
60
|
|
|
61
|
-
1. **
|
|
61
|
+
1. **Declare quantum properties** on things with `quantum([...values])`. Each property is a qudit (d-dimensional quantum digit)
|
|
62
62
|
2. **Apply gates** to transform state without collapsing it
|
|
63
|
-
3. **
|
|
63
|
+
3. **Let properties interact** (a two-property gate, or a gate predicated on another property). Entanglement is what an interaction leaves behind
|
|
64
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
|
|
65
66
|
|
|
66
67
|
### Programmer's Mental Model
|
|
67
68
|
|
|
@@ -69,301 +70,307 @@ Quantum Forge gives game objects real quantum state. The usage loop:
|
|
|
69
70
|
|
|
70
71
|
**Gates** are collection algorithms that transform every entry at once. `cycle` increments each value. `hadamard` creates equal superposition.
|
|
71
72
|
|
|
72
|
-
**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.
|
|
73
74
|
|
|
74
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.
|
|
75
76
|
|
|
76
|
-
##
|
|
77
|
+
## Quantum properties
|
|
77
78
|
|
|
78
|
-
|
|
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.
|
|
79
81
|
|
|
80
82
|
```typescript
|
|
81
|
-
import {
|
|
83
|
+
import { ensureLoaded, quantum } from "quantum-forge/quantum";
|
|
82
84
|
|
|
83
85
|
await ensureLoaded();
|
|
84
86
|
|
|
85
|
-
|
|
86
|
-
|
|
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
|
|
87
90
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
this.setProperty(id, prop);
|
|
94
|
-
}
|
|
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
|
+
```
|
|
95
96
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const p2 = this.getProperty(id2);
|
|
99
|
-
if (!p1 || !p2) return;
|
|
100
|
-
this.getModule().i_swap(p1, p2, 0.5);
|
|
101
|
-
}
|
|
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.
|
|
102
99
|
|
|
103
|
-
|
|
104
|
-
const prop = this.getProperty(id);
|
|
105
|
-
if (!prop) return 1.0;
|
|
106
|
-
const results = this.getModule().probabilities([prop]);
|
|
107
|
-
for (const r of results) {
|
|
108
|
-
if (r.qudit_values[0] === 1) return r.probability;
|
|
109
|
-
}
|
|
110
|
-
return 0;
|
|
111
|
-
}
|
|
100
|
+
### Dimension
|
|
112
101
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
this.deleteProperty(id);
|
|
118
|
-
this.releaseProperty(prop, value); // reset to |0⟩, return to pool
|
|
119
|
-
return value;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
```
|
|
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 |
|
|
123
106
|
|
|
124
|
-
|
|
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.
|
|
125
109
|
|
|
126
|
-
|
|
127
|
-
|-----------|--------|----------|
|
|
128
|
-
| 2 (qubit) | `\|0⟩`, `\|1⟩` | Binary: exists/doesn't, alive/dead |
|
|
129
|
-
| 3 (qutrit) | `\|0⟩` – `\|2⟩` | Three-way: rock/paper/scissors |
|
|
110
|
+
### Values and indices
|
|
130
111
|
|
|
131
|
-
|
|
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.
|
|
132
115
|
|
|
133
116
|
## Gates
|
|
134
117
|
|
|
135
|
-
|
|
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.
|
|
136
120
|
|
|
137
|
-
|
|
|
138
|
-
|
|
139
|
-
|
|
|
140
|
-
|
|
|
141
|
-
|
|
|
142
|
-
|
|
|
143
|
-
|
|
|
144
|
-
|
|
|
145
|
-
|
|
|
146
|
-
|
|
|
147
|
-
|
|
|
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 |
|
|
148
133
|
|
|
149
|
-
|
|
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.
|
|
150
137
|
|
|
151
|
-
|
|
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.
|
|
152
141
|
|
|
153
|
-
|
|
142
|
+
### Predicates (conditional gates)
|
|
154
143
|
|
|
155
|
-
|
|
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.
|
|
156
146
|
|
|
157
147
|
```typescript
|
|
158
|
-
// CNOT: flip target only when control is
|
|
159
|
-
|
|
160
|
-
// 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
|
|
161
151
|
|
|
162
|
-
//
|
|
163
|
-
|
|
152
|
+
// CZ
|
|
153
|
+
target.phase({ when: [control.is(true)] });
|
|
164
154
|
|
|
165
|
-
//
|
|
166
|
-
|
|
167
|
-
|
|
155
|
+
// Controlled Hadamard, and a fractional controlled gate
|
|
156
|
+
target.superpose({ when: [control.is(true)] });
|
|
157
|
+
target.flip(0.5, { when: [control.is(true)] });
|
|
158
|
+
|
|
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
|
|
168
162
|
|
|
169
163
|
// Multiple predicates are AND'd
|
|
170
|
-
|
|
164
|
+
target.flip({ when: [controlA.is(true), controlB.is(true)] });
|
|
171
165
|
```
|
|
172
166
|
|
|
173
|
-
|
|
167
|
+
## Entanglement patterns
|
|
174
168
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
{ property: controlProp, value: 1, isEqual: true },
|
|
179
|
-
];
|
|
180
|
-
const wasmPreds = specs.map(s =>
|
|
181
|
-
s.isEqual ? s.property.is(s.value) : s.property.is_not(s.value)
|
|
182
|
-
);
|
|
183
|
-
```
|
|
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.
|
|
184
172
|
|
|
185
|
-
|
|
173
|
+
### Entangle-split (iSwap)
|
|
186
174
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
Object splits into anti-correlated pair. Exactly one is real.
|
|
175
|
+
Object splits into an anti-correlated pair. Exactly one is real.
|
|
190
176
|
|
|
191
177
|
```typescript
|
|
192
|
-
entangleSplit(
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
m.cycle(prop1); // |1⟩ (exists)
|
|
197
|
-
m.i_swap(prop1, prop2, 0.5); // entangle
|
|
198
|
-
this.setProperty(originalId, prop1);
|
|
199
|
-
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);
|
|
200
182
|
}
|
|
201
183
|
```
|
|
202
184
|
|
|
203
|
-
### Correlated
|
|
185
|
+
### Correlated pair (CNOT)
|
|
204
186
|
|
|
205
|
-
Both match
|
|
187
|
+
Both match: both alive or both dead.
|
|
206
188
|
|
|
207
189
|
```typescript
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
m.cycle(prop1);
|
|
213
|
-
m.hadamard(prop1);
|
|
214
|
-
m.shift(prop2, 1, [prop1.is(1)]); // CNOT
|
|
215
|
-
this.setProperty(id1, prop1);
|
|
216
|
-
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)] });
|
|
217
194
|
}
|
|
218
195
|
```
|
|
219
196
|
|
|
220
|
-
### Quantum-
|
|
197
|
+
### Quantum-split
|
|
221
198
|
|
|
222
|
-
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.
|
|
223
202
|
|
|
224
203
|
```typescript
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
return false;
|
|
234
|
-
}
|
|
235
|
-
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;
|
|
236
212
|
return true;
|
|
237
213
|
}
|
|
238
214
|
```
|
|
239
215
|
|
|
240
|
-
### Overlap
|
|
216
|
+
### Overlap entanglement
|
|
241
217
|
|
|
242
|
-
|
|
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.
|
|
243
220
|
|
|
244
221
|
```typescript
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
### Conditional Superposition
|
|
249
|
-
|
|
250
|
-
A control property determines whether a target enters superposition:
|
|
251
|
-
|
|
252
|
-
```typescript
|
|
253
|
-
m.hadamard(target, 1, [control.is(1)]);
|
|
222
|
+
function overlap(a: Ball, b: Ball): void {
|
|
223
|
+
if (a.exists && b.exists) a.exists.iSwap(b.exists, 0.5);
|
|
224
|
+
}
|
|
254
225
|
```
|
|
255
226
|
|
|
256
|
-
### Choosing
|
|
227
|
+
### Choosing between mechanisms
|
|
257
228
|
|
|
258
229
|
| Mechanism | Correlation | Use |
|
|
259
230
|
|-----------|-------------|-----|
|
|
260
|
-
|
|
|
261
|
-
|
|
|
262
|
-
| `
|
|
263
|
-
| `
|
|
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 |
|
|
264
235
|
|
|
265
236
|
## Measurement
|
|
266
237
|
|
|
267
|
-
Measurement collapses superposition to a definite value. Entangled partners collapse
|
|
238
|
+
Measurement collapses superposition to a definite value. Entangled partners collapse too.
|
|
268
239
|
|
|
269
240
|
```typescript
|
|
270
|
-
const
|
|
271
|
-
// Always pool after:
|
|
272
|
-
this.deleteProperty(id);
|
|
273
|
-
this.releaseProperty(prop, value);
|
|
241
|
+
const value = prop.measure(); // declared value, e.g. true or false
|
|
274
242
|
```
|
|
275
243
|
|
|
276
|
-
|
|
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
|
|
277
248
|
|
|
278
249
|
```typescript
|
|
279
|
-
|
|
250
|
+
import { measure } from "quantum-forge/quantum";
|
|
251
|
+
const [va, vb, vc] = measure(propA, propB, propC);
|
|
280
252
|
```
|
|
281
253
|
|
|
282
|
-
### Reading
|
|
254
|
+
### Reading state (no collapse)
|
|
283
255
|
|
|
284
256
|
```typescript
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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 }, ...]
|
|
262
|
+
|
|
263
|
+
// Reduced density matrix: phase and correlation info, labelled with declared values
|
|
264
|
+
densityMatrix(a, b); // [{ row: [...], col: [...], real, imag }, ...]
|
|
288
265
|
|
|
289
|
-
|
|
290
|
-
const rdm = m.reduced_density_matrix([prop1, prop2]);
|
|
291
|
-
// Off-diagonal entries carry relative phase
|
|
266
|
+
probabilityWhen([a.is(true), b.is(false)]); // probability all predicates hold
|
|
292
267
|
|
|
293
|
-
//
|
|
294
|
-
|
|
295
|
-
// 1 = satisfied, 0 = not
|
|
268
|
+
// Predicate measurement: collapses to agree, returns a boolean
|
|
269
|
+
measureWhen([a.is(true), b.is(false)]);
|
|
296
270
|
|
|
297
|
-
// Forced measurement
|
|
298
|
-
|
|
271
|
+
// Forced measurement: replay and tests only. Throws if the value has zero probability.
|
|
272
|
+
prop.forcedMeasure(true);
|
|
273
|
+
forcedMeasure([a, b], [true, false]);
|
|
299
274
|
```
|
|
300
275
|
|
|
301
276
|
## Phase & Interference
|
|
302
277
|
|
|
303
|
-
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:
|
|
304
279
|
|
|
305
280
|
```typescript
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
//
|
|
310
|
-
|
|
311
|
-
//
|
|
312
|
-
// 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
|
|
313
287
|
```
|
|
314
288
|
|
|
289
|
+
A phase before a single split changes nothing. It needs a later superpose or interaction to turn into probability.
|
|
290
|
+
|
|
315
291
|
**Grover oracle** (walls/barriers): mark states with π phase, then diffuse with fractional Hadamard. Probability "bounces off" marked states.
|
|
316
292
|
|
|
317
293
|
```typescript
|
|
318
|
-
|
|
319
|
-
|
|
294
|
+
phaseRotate(Math.PI, { when: [hex.is(wallState)] }); // mark
|
|
295
|
+
hex.superpose(0.1); // diffuse
|
|
320
296
|
```
|
|
321
297
|
|
|
322
|
-
**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.
|
|
323
299
|
|
|
324
|
-
### Visualizing
|
|
300
|
+
### Visualizing phase
|
|
325
301
|
|
|
326
302
|
Extract from the reduced density matrix:
|
|
327
303
|
|
|
328
304
|
```typescript
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
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);
|
|
334
309
|
// Map to dial rotation, color hue, force magnitude, compass direction
|
|
335
310
|
}
|
|
336
311
|
}
|
|
337
312
|
```
|
|
338
313
|
|
|
339
|
-
##
|
|
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.
|
|
340
333
|
|
|
341
334
|
```typescript
|
|
342
|
-
import { QuantumRecorder } from "quantum-forge/quantum";
|
|
335
|
+
import { QuantumRecorder, quantum, measure } from "quantum-forge/quantum";
|
|
343
336
|
|
|
344
|
-
const recorder = new QuantumRecorder(
|
|
337
|
+
const recorder = new QuantumRecorder();
|
|
345
338
|
recorder.startRecording();
|
|
346
|
-
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
|
|
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);
|
|
350
348
|
```
|
|
351
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
|
+
|
|
352
360
|
## Performance
|
|
353
361
|
|
|
354
|
-
|
|
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.
|
|
355
365
|
|
|
356
366
|
Shipped limits: Qutrit Edition has dimension 3, 12 max qudits. Qubit Edition has dimension 2, 20 max qudits.
|
|
357
367
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
| 1–4 | No issues |
|
|
361
|
-
| 4–8 | Good with pooling |
|
|
362
|
-
| 8–12 | At the limit, aggressive pooling required |
|
|
363
|
-
|
|
364
|
-
**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.
|
|
365
370
|
|
|
366
|
-
|
|
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.
|
|
367
374
|
|
|
368
375
|
```typescript
|
|
369
376
|
// Monitor WASM memory
|
|
@@ -453,6 +460,7 @@ loop.start();
|
|
|
453
460
|
|
|
454
461
|
Map probability to visual properties (opacity, size, color):
|
|
455
462
|
|
|
463
|
+
<!-- sample:skip: method shown outside its class -->
|
|
456
464
|
```typescript
|
|
457
465
|
protected draw(state: GameState) {
|
|
458
466
|
for (const ball of state.balls) {
|
|
@@ -499,7 +507,7 @@ input.destroy();
|
|
|
499
507
|
|
|
500
508
|
| Export | Key APIs |
|
|
501
509
|
|--------|----------|
|
|
502
|
-
| `./quantum` | `
|
|
510
|
+
| `./quantum` | `quantum`, `Quantum`, `measure`, `probabilities`, `densityMatrix`, `measureWhen`, `phaseRotate`, `ensureLoaded`, `QuantumRecorder`, `useQuantumForgeBuild`, `setWasmBasePath` |
|
|
503
511
|
| `./logging` | `Logger` |
|
|
504
512
|
| `./vite-plugin` | `quantumForgeVitePlugin` |
|
|
505
513
|
|