This guide explains how HyperToken's components work together to power distributed game simulations.
HyperToken is built around CRDTs (Conflict-free Replicated Data Types) that enable automatic state synchronization across multiple peers without a central server.
| Component | Purpose | Think of it as... |
|---|---|---|
| Token | Game entity | A playing card, chess piece, or item |
| Stack | Ordered collection | A deck of cards, draw pile |
| Space | Zones with positions | A game board, tableau |
| Source | Combined decks | A Blackjack shoe (multiple decks) |
| Chronicle | Synchronized state | The "database" that syncs across players |
| Engine | Game coordinator | The game master |
┌─────────────────────────────────────────────────────────────────┐
│ Engine │
│ Coordinates game logic, dispatches actions, manages networking │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Stack │ │ Space │ │ Source │ │ Agents │ │
│ │ (cards) │ │ (zones) │ │ (decks) │ │(players)│ │
│ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │ │
│ └──────────────┴──────────────┴──────────────┘ │
│ │ │
│ ┌────────▼────────┐ │
│ │ Chronicle │ │
│ │ (CRDT State) │ │
│ └────────┬────────┘ │
│ │ │
└──────────────────────────────┼───────────────────────────────────┘
│
┌──────────▼──────────┐
│ Network Layer │
│ (P2P / WebSocket) │
└─────────────────────┘
What it is: An immutable data structure representing any game entity—cards, pieces, items, characters.
When to use: Whenever you need to represent a discrete game object.
import { Token } from './core/Token.js';
// Create a playing card
const aceOfSpades = new Token({
id: 'ace-spades',
label: 'Ace of Spades',
group: 'spades',
meta: { rank: 14, suit: 'spades' }
});
// Create a game piece
const knight = new Token({
id: 'white-knight-1',
label: 'Knight',
kind: 'piece',
meta: { color: 'white', movement: 'L-shape' }
});Key properties:
id- Unique identifierlabel- Human-readable namegroup- Category (suit, type, faction)meta- Arbitrary game data_tags- Runtime tags for filtering_attachments- Items attached to this token
What it is: The CRDT state container. Wraps Automerge to provide automatic conflict resolution when multiple peers modify state simultaneously.
When to use: You rarely interact with Chronicle directly—Stack, Space, and Source use it internally.
import { Chronicle } from './core/Chronicle.js';
// Create a session (shared state container)
const session = new Chronicle();
// All changes go through session.change()
session.change("draw a card", (doc) => {
doc.hand.push(card);
});
// State auto-syncs with other peers
session.on('state:changed', ({ doc, source }) => {
console.log('State updated from:', source); // 'local', 'merge', or 'load'
});Why CRDTs matter:
- No central server needed
- Automatic conflict resolution
- Offline-capable (changes merge when reconnected)
- Deterministic state across all peers
What it is: A CRDT-backed ordered collection of tokens. Think: deck of cards, draw pile, discard pile.
When to use: Card games, any ordered token collection.
import { Chronicle } from './core/Chronicle.js';
import { Stack } from './core/Stack.js';
import { Token } from './core/Token.js';
// Create tokens
const cards = [
new Token({ id: 'card-1', label: 'Ace', meta: { rank: 1 } }),
new Token({ id: 'card-2', label: 'King', meta: { rank: 13 } }),
// ...
];
// Create stack with Chronicle session
const session = new Chronicle();
const deck = new Stack(session, cards);
// Operations
deck.shuffle(); // Randomize order
const card = deck.draw(); // Draw one card
const hand = deck.draw(5); // Draw multiple cards
deck.burn(2); // Discard from top without drawing
deck.discard(card); // Add to discard pile
deck.reset(); // Restore to original stateState structure:
session.state.stack = {
stack: [...], // Cards remaining
drawn: [...], // Cards drawn
discards: [...] // Cards discarded
};Stack vs Array:
| Need | Use Stack | Use Array |
|---|---|---|
| Draw/discard semantics | ✓ | |
| Shuffle with seed | ✓ | |
| CRDT sync | ✓ | |
| Simple list | ✓ | |
| No sync needed | ✓ |
What it is: A CRDT-backed 2D zone manager. Think: game board, play areas, tableau.
When to use: Board games, spatial card layouts, any game with zones.
import { Chronicle } from './core/Chronicle.js';
import { Space } from './core/Space.js';
const session = new Chronicle();
const board = new Space(session, 'game-board');
// Create zones
board.createZone('hand');
board.createZone('battlefield');
board.createZone('graveyard');
// Place tokens in zones
const placement = board.place('hand', card, {
x: 0,
y: 0,
faceUp: true
});
// Move between zones
board.move('hand', 'battlefield', placement.id);
// Flip face up/down
board.flip('battlefield', placement.id);
// Zone operations
board.shuffleZone('hand');
board.clearZone('graveyard');
board.lockZone('deck'); // Prevent modificationsState structure:
session.state.zones = {
'hand': [{ id, tokenId, tokenSnapshot, x, y, faceUp, ... }],
'battlefield': [...],
// ...
};Stack vs Space:
| Need | Use Stack | Use Space |
|---|---|---|
| Cards have positions (x, y) | ✓ | |
| Cards belong to named zones | ✓ | |
| Cards can be face up/down | ✓ | |
| Simple draw pile | ✓ | |
| Order is all that matters | ✓ |
What it is: A CRDT-backed multi-stack manager with reshuffle policies. Think: shoe in Blackjack, combined decks.
When to use: When you need multiple decks combined, or automatic reshuffling.
import { Chronicle } from './core/Chronicle.js';
import { Stack } from './core/Stack.js';
import { Source } from './core/Source.js';
const session = new Chronicle();
const deck1 = new Stack(session, cards1);
const deck2 = new Stack(session, cards2);
// Combine decks into a source
const shoe = new Source(session, [deck1, deck2]);
// Configure auto-reshuffle when 10 cards remain
shoe.reshuffleWhen(10, { mode: 'auto' });
// Draw from combined source
const card = shoe.draw();
shoe.shuffle();
shoe.burn(2);Stack vs Source:
| Need | Use Stack | Use Source |
|---|---|---|
| Single deck | ✓ | |
| Multiple decks combined | ✓ | |
| Auto-reshuffle | ✓ | |
| Burn pile separate | ✓ | ✓ |
What it is: The game coordinator. Manages components, dispatches actions, handles networking.
When to use: Every game needs an Engine.
import { Engine } from './engine/Engine.js';
import { Stack } from './core/Stack.js';
import { Chronicle } from './core/Chronicle.js';
// Create components
const session = new Chronicle();
const deck = new Stack(session, cards);
// Create engine
const engine = new Engine({ stack: deck });
// Dispatch actions (async, supports WASM acceleration)
await engine.dispatch('stack:shuffle');
const card = await engine.dispatch('stack:draw');
const hand = await engine.dispatch('stack:draw', { count: 5 });
// Connect to multiplayer server
engine.connect('ws://localhost:8080');
// Listen for events
engine.on('state:updated', () => console.log('State changed'));
engine.on('net:ready', () => console.log('Connected to server'));Key features:
- Action dispatch: Unified interface for all game operations
- WASM acceleration: Optional Rust-powered performance
- Networking: Built-in P2P and WebSocket support
- Policies: Register game rules that evaluate after each action
- History: Undo/redo support
User Action → Engine.dispatch() → Action applied → State updated
│
▼
Chronicle.change()
│
▼
Event emitted
Local Action → Engine.dispatch() → Chronicle.change() → Network sync
│
▼
Other peers
│
▼
Chronicle.merge()
│
▼
State converges
Engine dispatches actions by type. Common patterns:
await engine.dispatch('stack:draw', { count: 1 });
await engine.dispatch('stack:shuffle', { seed: 12345 });
await engine.dispatch('stack:burn', { count: 2 });
await engine.dispatch('stack:reset');await engine.dispatch('space:place', { zone: 'hand', token });
await engine.dispatch('space:move', { fromZone: 'hand', toZone: 'play', placementId });
await engine.dispatch('space:flip', { zone: 'play', placementId });
await engine.dispatch('space:createZone', { name: 'discard' });await engine.dispatch('agent:create', { id: 'p1', name: 'Alice' });
await engine.dispatch('agent:giveResource', { name: 'Alice', resource: 'gold', amount: 100 });
await engine.dispatch('agent:drawCards', { name: 'Alice', count: 5 });await engine.dispatch('game:start');
await engine.dispatch('game:nextPhase', { phase: 'combat' });
await engine.dispatch('game:end', { winner: 'Alice', reason: 'victory' });See engine/ACTIONS.md for the complete action reference.
| Need | Use |
|---|---|
| Individual game objects | Token |
| Ordered draw/discard pile | Stack |
| Board with zones | Space |
| Multiple combined decks | Source |
| Game coordination | Engine |
Do you need to represent a game object?
└─ Yes → Token
Do you have an ordered collection (deck, pile)?
├─ Single deck → Stack
└─ Multiple decks combined → Source
Do you have zones or a board?
└─ Yes → Space
Do you need multiplayer sync?
└─ Chronicle (used internally by Stack/Space/Source)
Building a game?
└─ Engine (coordinates everything)
Card game (Blackjack, Poker):
const engine = new Engine({ stack: deck });Board game (Chess, Go):
const engine = new Engine({ space: board });Complex card game (Magic, Hearthstone):
const engine = new Engine({
stack: deck, // Draw pile
space: board // Battlefield, hand zones
});Casino game (Blackjack shoe):
const engine = new Engine({ source: shoe });HyperToken can be customized with custom actions, rules, policies, and agents. See the Extending Guide for details on:
- Registering custom actions
- Creating game rules with RuleEngine
- Adding policies that evaluate after each action
- Building AI agents
HyperToken includes optional Rust-compiled WASM for performance-critical operations:
const engine = new Engine({
stack: deck,
useWorker: true // Enable multi-threaded WASM
});- Large token collections (1000+ cards)
- High-frequency operations (AI training)
- Complex batch operations
For typical games, TypeScript is fast enough.
- Getting Started - Installation and quick start
- First Game Tutorial - Build your first game
- Actions Reference - All available actions
- Worker Mode - WASM performance optimization