@quantumplayed/quantum-game-engine
v1.0.1
Published
A generalized, sparse-matrix quantum simulation library designed specifically for game developers.
Readme
Quantum Game Engine
A generalized, sparse-matrix quantum simulation library designed specifically for game developers.
Unlike traditional quantum SDKs (like Qiskit or Cirq) that focus on low-level circuit design and dense matrices, this engine abstracts Away the complex math. It uses "Second Quantization" and sparse amplitude dictionaries to allow you to easily attach quantum mechanics (superposition, entanglement, interference) directly to high-dimensional objects in your game (like an $(x,y)$ coordinate grid, a character's inventory, or a branching dialogue tree).
Core Concepts
QuantumEntity: Represents any quantum object in your game (a player, a coin, a chess piece). It manages its own state, but if it becomes entangled, it defers mathematically to a joint system behind the scenes.Operations: A suite of generalized quantum gates (Shift, Phase, Hadamard) that work on qudits of any dimension, not just qubits.EntanglementEngine: A powerful manager that tracks interactions between differentQuantumEntityobjects. When entities interact conditionally (e.g., "IF player is on square 5, open the door"), their states become mathematically linked.
1. Creating and Modifying Entities
A QuantumEntity requires a dimension (how many possible classical states it can have) and an initial state index.
import { QuantumEntity, Operations } from 'quantum-game-engine';
// A coin has dimension 2 (Heads=0, Tails=1)
const coin = new QuantumEntity({ dimension: 2, initialState: 0 });
// A character moving on a 10x10 grid has dimension 100
const player = new QuantumEntity({ dimension: 100, initialState: 45 });Applying Operations
You can mutate the state of an entity using apply().
1. Shift (Generalized Pauli-X / Clock) Shifts the classical state by a certain amount modulo the dimension.
player.apply(Operations.Shift(1)); // Moves player from 45 to 462. Phase (Generalized Pauli-Z) Applies a phase rotation to the state. Crucial for interference effects.
coin.apply(Operations.Phase(Math.PI));3. Hadamard (Quantum Fourier Transform) Puts the entity into a uniform superposition of all its possible states.
coin.apply(Operations.Hadamard());
// Coin is now 50% Heads, 50% Tails4. SparseTransition For complex game logic (like a "Quantum Split" move in Checkers), you can define a custom transition matrix. You only need to provide the non-zero mappings.
const splitMove = Operations.SparseTransition(dimension, {
45: [
{ state: 54, amplitude: { re: 0.707, im: 0 } }, // 50% chance to move left
{ state: 56, amplitude: { re: 0.707, im: 0 } } // 50% chance to move right
]
});
player.apply(splitMove);2. Entanglement and Interactions
The true power of the engine is the EntanglementEngine. In games, things interact. If a quantum player steps on a quantum button, they become entangled. Measuring one affects the other.
Use conditionalInteract to apply operations to a target only if the control entity is in a specific state.
import { EntanglementEngine } from 'quantum-game-engine';
const player = new QuantumEntity({ dimension: 10, initialState: 5 });
const door = new QuantumEntity({ dimension: 2, initialState: 0 }); // 0=Closed, 1=Open
// Player steps left and right at the same time
player.apply(Operations.Hadamard());
// If the player is on square 6, they open the door.
EntanglementEngine.conditionalInteract({
controlEntities: [{ entity: player, state: 6 }],
targets: [{ entity: door, operation: Operations.Shift(1) }]
});
// The Player and the Door are now ENTANGLED.
// - In a universe where player is at 6, the door is Open (1).
// - In a universe where player is at 5, the door is Closed (0).3. Observation and Rendering
Extracting Probabilities (For UI)
To render the game to the screen, you need to know the likelihood of an entity being in a certain state. Use getProbabilities() to get a classical probability distribution (0.0 to 1.0).
const probs = player.getProbabilities();
// Returns: { '5': 0.5, '6': 0.5 }
// If entangled, getProbabilities() automatically calculates the
// marginal probabilities of just this entity from the joint system!Advanced Physics: Reduced Density Matrices If your game logic requires knowing whether an entity has "decohered" due to entanglement (losing its quantum interference capability), you can calculate its One-Body Reduced Density Matrix:
const rho = player.getReducedDensityMatrix();
// Returns a 2D map of Complex numbers.
// rho[x][x] = Probability of state x
// rho[x][y] = Coherence between state x and yMeasuring (Collapsing the Wavefunction)
When gameplay requires an entity to commit to a single reality (e.g., an enemy attacks the player, forcing them to be in one specific location), use measure().
const finalPosition = player.measure();
// Returns exactly 5 or exactly 6.The Magic of Entanglement:
If player and door are entangled (as in the example above), measuring the player will instantly collapse the door as well.
If player.measure() returns 6, any subsequent call to door.measure() is mathematically guaranteed to return 1 (Open).
Forced Measurements (Deterministic Replay)
Pass an optional forcedOutcome to measure() to force a specific collapse state. This bypasses Math.random() entirely.
const outcome = player.measure(0); // Always collapses to state 0This works with entangled systems too — the forced outcome propagates through the joint system, collapsing entangled partners accordingly. Use this for:
- Game history replay — reproduce recorded measurement outcomes exactly
- Undo/redo — re-apply the same measurement results
- Testing — verify specific branches without randomness
4. Circuit Logging
The engine includes a toggleable CircuitLogger that records every state-mutating operation as an append-only log. Recording is off by default — zero overhead during normal gameplay.
import { CircuitLogger } from 'quantum-game-engine';
CircuitLogger.startRecording();
// ... game operations (apply, interactMany, conditionalInteract, measure) ...
const log = CircuitLogger.stopRecording(); // CircuitEntry[]Each entry is either a GateEntry (for apply, interactMany, conditionalInteract) or a MeasurementEntry (for measure), tagged with entity IDs, dimensions, and an auto-incrementing timestamp.
| Method | Description |
|---|---|
| startRecording() | Begin recording (resets log) |
| stopRecording() | Stop and return CircuitEntry[] |
| isRecording() | Fast guard for hot path |
| getLog() | Get log copy while recording |
| getLogForEntities(ids) | Filter by entity IDs |
| reset() | Clear log and counter |
Every QuantumEntity has a unique readonly id: number (auto-incrementing), used to identify entities in log entries.
Engine Boundaries and Limitations
- Joint Dimension Limit: When entities entangle, their state spaces multiply (a dimension-10 player interacting with a dimension-10 enemy creates a dimension-100 joint system). The
EntanglementEnginecurrently enforces a hard limit of4096total joint dimensions to prevent out-of-memory crashes on the browser's main thread. Design your game's entanglement mechanics to collapse frequently!
5. Full API Usage Examples
Example 1: A Quantum Platformer (Schrödinger's Jump)
The Scenario: You have a platformer game. The player (playerX) reaches a gap. They can press a "Quantum Jump" button to simultaneously jump and not jump, putting their position in a superposition. Later, they encounter a "Quantum Door" that only opens if the player is standing on a specific button.
import { QuantumEntity, Operations, EntanglementEngine } from 'quantum-game-engine';
// 1. Initialize the player's X position as a qudit (e.g., 100 possible tiles)
// We start purely classical: 100% chance of being at tile 5.
const playerX = new QuantumEntity({ dimension: 100, initialState: 5 });
// --- THE QUANTUM JUMP ---
// The player hits the "Quantum Jump" button!
// We want an equal 50/50 superposition of staying at 5 or jumping to 10.
// We apply a custom sparse unitary operation (a basic split/Hadamard variant).
const jumpOp = Operations.SparseTransition(100, {
5: [
{ state: 5, amplitude: { re: 0.707, im: 0 } }, // Branch A: didn't jump
{ state: 10, amplitude: { re: 0.707, im: 0 } } // Branch B: did jump
]
});
playerX.apply(jumpOp);
// playerX.amplitudes is now roughly:
// { 5: {re: 0.707, im: 0}, 10: {re: 0.707, im: 0} }
// --- ENTANGLEMENT ---
// A Quantum Door (qudit: 0=Closed, 1=Open)
const door = new QuantumEntity({ dimension: 2, initialState: 0 });
// If the player landed on the button at tile 10, the door opens.
EntanglementEngine.conditionalInteract({
controlEntities: [{ entity: playerX, state: 10 }],
targets: [{ entity: door, operation: Operations.Shift(1) }]
});
// Since playerX was in superposition, the Door is now entangled!
// The joint amplitudes (PlayerX ⊗ Door):
// {
// "5,0": {re: 0.707, im: 0}, // Player at 5, Door is Closed
// "10,1": {re: 0.707, im: 0} // Player at 10, Door is Open
// }
// --- MEASUREMENT / COLLAPSE ---
// The player walks into a "Camera / Observation Zone"
// We force a collapse of the player's position.
const result = playerX.measure();
// Let's say the engine rolled the 50% chance and returned '10'.
// Because of the Entanglement Engine, the Door's state automatically
// collapses to '1' (Open)! The universe branch where the player didn't
// jump is pruned (amplitudes deleted).Example 2: Quantum Checkers (Squares as Qubits)
The Scenario: A player wants to move a piece from square 12. Instead of a normal move, they do a SplitMove to squares 16 and 17. Later, an opponent tries to take the piece on square 16.
Note: In Cheqqers, instead of tracking a "Piece" as a 32-dimension entity (First Quantization), it is much more natural to track each "Square" as a 2-dimension Qubit (Second Quantization), where 0 = Empty and 1 = Occupied.
import { QuantumEntity, Operations, EntanglementEngine } from 'quantum-game-engine';
// We represent squares as individual Qubits.
// Initial state: Square 12 is Occupied (1), Squares 16 and 17 are Empty (0).
const sq12 = new QuantumEntity({ dimension: 2, initialState: 1 });
const sq16 = new QuantumEntity({ dimension: 2, initialState: 0 });
const sq17 = new QuantumEntity({ dimension: 2, initialState: 0 });
// --- THE SPLIT MOVE ---
// The "Quantum Chess Split" matrix is an 8x8 matrix that acts on 3 qubits
// (From, To1, To2). Our engine provides a helper to apply this sparse sub-matrix.
// Because it involves 3 entities, the EntanglementEngine automatically merges them
// into an 8-dimension joint Hilbert space (2x2x2).
EntanglementEngine.interactMany([sq12, sq16, sq17], Operations.SparseTransition(8, {
// Binary 100 (state 4) -> 010 (state 2) + 001 (state 1)
4: [
{ state: 2, amplitude: { re: 0.707, im: 0 } },
{ state: 1, amplitude: { re: 0.707, im: 0 } }
]
}));
// After the split:
// sq12 is now definitely Empty (|0>).
// The system of (sq16 ⊗ sq17) is in a superposition:
// 50% chance of |1, 0> (Piece is on 16)
// 50% chance of |0, 1> (Piece is on 17)
// --- THE PASSAGE OF TIME (PHASE) ---
// In Cheqqers, time/turns applies a phase shift to superpositions.
EntanglementEngine.conditionalInteract({
controlEntities: [{ entity: sq16, state: 1 }],
targets: [{ entity: sq16, operation: Operations.Phase(Math.PI / 4) }]
});
EntanglementEngine.conditionalInteract({
controlEntities: [{ entity: sq17, state: 1 }],
targets: [{ entity: sq17, operation: Operations.Phase(Math.PI / 4) }]
});
// --- CONDITIONAL CAPTURE (ENTANGLEMENT) ---
// Opponent moves a black piece from square 20 to square 12, capturing 16.
const sq20 = new QuantumEntity({ dimension: 2, initialState: 1 }); // Black piece
// The capture logic:
// IF Black is on 20 (it is) AND White is on 16 (it's in superposition):
// 1. Black moves to 12 (sq20 -> 0, sq12 -> 1)
// 2. White is captured (sq16 -> 0)
// This translates to a Toffoli (CCX) and CNOT (CX) sequence in quantum circuits.
EntanglementEngine.conditionalInteract({
controlEntities: [{ entity: sq20, state: 1 }, { entity: sq16, state: 1 }],
targets: [
{ entity: sq20, operation: Operations.Shift(1) }, // 1 -> 0 (wrapping)
{ entity: sq12, operation: Operations.Shift(1) }, // 0 -> 1
{ entity: sq16, operation: Operations.Shift(1) } // 1 -> 0 (captured)
]
});
// The Joint Hilbert space now tracks the entangled state of shapes.
// Branch A: (White was on 17).
// sq16 resolves to 0. The capture condition fails! Black stays on 20.
// Branch B: (White was on 16).
// sq16 resolves to 1. Capture fires! Black moves to 12, sq16 flips to 0.
// Because everything is sparse, branches where mathematically invalid things happen
// (like trying to capture an empty square) don't consume memory.