# Quantum Arcade — Design Spec

**Date:** 2026-08-05
**Status:** Approved for planning
**Repo:** https://gitea.thetempleofdoom.com/drjones/casino.git

## 1. Context and Scope

Quantum Arcade is a private, LAN-hosted physics gaming platform for the host and
their friends, including at parties of up to ~100 people on phones. It runs
entirely on a single Linux box owned by the host.

**In scope:** deterministic physics games (group and single-player), a
provably-fair RNG, a double-entry ledger, Lightning deposits/withdrawals and
peer-to-peer transfers, ephemeral chat, and a single-box Docker deployment.

**Out of scope for this phase:** public internet deployment, KYC/identity
verification, geo-gating, and licensed real-money operation. The architecture
keeps these as additive modules rather than rewrites, but they are not built
here and this system is not to be exposed publicly as-is.

**Success criteria:**
- A guest joins from a phone in under 5 seconds with no account creation.
- 100 concurrent phones sustain a group round with no visible desync.
- Any player can independently verify any round outcome on their own device.
- End-of-night balances reconcile exactly, with a per-transaction explanation.

## 2. Identity

Identity is a keypair, not an account.

On first visit the browser generates an ed25519 keypair stored in IndexedDB and
prompts for a nickname. The public key is the player ID: it is what the ledger
posts against and what signs bet intents. There is no email, password, reset
flow, or KYC.

Multi-device: a QR code exports the key from one device to another.
Key loss means balance loss. This is accepted and stated plainly in the UI.

Nicknames are display-only and non-unique; the pubkey prefix disambiguates.

## 3. Architecture

One Go binary with enforced internal module boundaries, plus PostgreSQL, Redis,
LND, and Caddy. Nine separate services on one machine would add latency and
operational burden with no benefit at this scale.

```
Caddy (TLS termination, static assets, WebSocket upgrade)
  └── quantum-arcade (single Go binary)
        ├── ledger/   double-entry postings, append-only
        ├── rng/      commit-reveal seed lifecycle
        ├── sim/      deterministic physics core
        ├── room/     round lifecycle, tick broadcast
        ├── wallet/   Lightning in/out, P2P transfers
        └── chat/     ephemeral, in-memory only
PostgreSQL  ledger, rounds, seeds, results
Redis       live room state, presence, rate limits
LND         Lightning node, isolated wallet
```

Modules communicate only through Go interfaces defined by the *consumer*. No
module imports another's internal types. Extracting a module into its own
process later is a transport change at the call site, not a rewrite.

**Scaling posture:** 100 concurrent WebSocket clients is a small fraction of a
single Go process's capacity. The genuine constraint is PostgreSQL write
throughput at settlement, addressed by settling each round in exactly one
transaction with batched postings.

## 4. Deterministic Simulation Core

The physics core is fixed-timestep and integer-seeded, using fixed-point
arithmetic rather than floating point to guarantee bit-identical results across
platforms. It is compiled twice from one source: natively for the authoritative
server, and to WASM for the browser.

Consequences:
- The server broadcasts a seed, not a position stream. Bandwidth per additional
  phone is negligible.
- Any client can replay any historical round locally.
- The public verifier is simply the sim run against published inputs.

Determinism is a hard invariant, enforced by test (§9), not a convention.

## 5. Provable Fairness

Per round:

1. **Commit.** Server generates `serverSeed`, publishes `SHA256(serverSeed)`
   before the betting window opens.
2. **Client seed.** Derived from the concatenated public keys of all
   participants in the round, in join order. The host cannot know the outcome in
   advance because the host does not control the inputs.
3. **Outcome.** `HMAC-SHA256(serverSeed, clientSeed || nonce)` seeds the sim.
   The crash point / result is a pure function of that seed.
4. **Reveal.** After settlement the server publishes `serverSeed`. Clients
   recompute the commitment hash, re-derive the seed, and re-run the WASM sim.

Verification runs on-device in roughly 400ms and requires trusting no server
response. Seeds, commitments, and participant sets are retained permanently.

## 6. Games

All games share the sim core and the fairness pipeline.

**Group rounds** — a fixed ~5-minute cadence; all players watch the same
simulation. Three crash variants rotate:
- *Rocket vs. gravity* — multiplier is altitude; the craft visibly strains as
  fuel depletes toward the seeded crash point.
- *Orbital decay* — multiplier is orbits completed, each pass dipping lower.
- *Stacking tower* — multiplier is height; wobble grows until collapse.

Round lifecycle: `betting_open` → `locked` → `running` → `settled`. Cash-out
intents are signed by the player key and accepted only while `running`.

**Single-player** — always available between group rounds, ~10 seconds per play,
one tap to start: gravity-slingshot targeting, physics drop, and a
momentum/timing challenge. Same fairness proof, per-play nonce.

## 7. Ledger and Money

Double-entry, append-only. No row is ever updated or deleted; corrections are
compensating entries.

Every posting records: transaction ID, timestamp, player pubkey, account,
amount, round ID, balance before, balance after. Each transaction's postings sum
to zero. Player balances may never go negative.

Accounts: per-player balances, house pot, and Lightning bridge accounts. The
house pot balance is displayed in the UI to all players — the pool is never
opaque.

**Lightning:** deposit and withdraw via invoice/QR against the on-box LND node.
The LND wallet is isolated from application state, with its own credentials and
backup path.

**Peer-to-peer transfers:** instant, purely internal ledger entries between two
player accounts, signed by the sender's key. This is the primary way value moves
between friends at a party.

## 8. Interface and Feel

Phone-first portrait layout; cash-out is thumb-reachable.

Deep obsidian and indigo ground, with dense ornate filigree etched into the
background. Vibrant cyan and magenta are reserved exclusively for the live
multiplier, the player's balance, and the cash-out control — everything that
matters is luminous, nothing else is.

Ambient music is synthesized in WebAudio: endless, non-looping, and shipping
with zero audio assets.

Chat is a compact sidebar, in-memory only, discarded when the round ends.
Nothing is written to disk.

## 9. Testing

- **Ledger:** property-based tests for the invariants in §7 — postings sum to
  zero, balances never negative, no value created or destroyed across arbitrary
  transaction sequences.
- **Sim determinism:** identical seed produces identical output across 10,000
  runs, and native output matches WASM output byte-for-byte.
- **Fairness:** commitment always matches revealed seed; independent
  reimplementation of the verifier agrees with the server on recorded rounds.
- **Load:** harness simulating 150 concurrent phone clients through a complete
  group round, asserting no desync and correct settlement.

## 10. Deployment

`docker compose up` on the single Linux box. Postgres and LND data on persistent
volumes with scheduled backups. Caddy terminates TLS on the LAN. Prometheus and
Grafana included for host and application metrics.

## 11. Explicit Non-Goals

- Not exposed to the public internet in this phase.
- No engagement mechanics designed to extend play beyond a player's intent: no
  loss-chasing prompts, no artificial near-miss tuning, no dark patterns on
  cash-out. Game feel comes from the physics being genuinely good.
- No mixing or obfuscation of fund provenance. The ledger is the product.
