> Public demo deployment: play-money sessions only; the seamless-wallet contract below is not connected to a real operator here.

# Heatbreaker — Integration Guide (Operators & Aggregators)

Version 1.6 · Game ID `heatbreaker` · Demo: <https://heatbreaker.app>

Heatbreaker runs on a server-authoritative remote game server (RGS). Every outcome,
balance movement and responsible-gaming rule is decided on the server; the browser
client only displays results. This document covers everything an operator or
aggregator needs to integrate the game with real-money wallets.

> **Status.** The public demo uses an internal play-money wallet. The seamless-wallet
> contract below is implemented as a reference client (`wallet-seamless.js`) with a
> contract test against a reference operator (`node wallet-seamless.test.js`, 13 checks).
> Connecting a real operator is a configuration step; real-money operation additionally
> requires a licensed distribution partner and lab certification.

---

## 1. Architecture

```
Player browser (iframe)  ──HTTPS/WSS──►  Heatbreaker RGS (Cloudflare Workers + Durable Objects)
                                            │  one transactional object per player session
                                            │  one shared object per live table (market × RTP)
                                            ▼
                                   Operator / aggregator wallet  (seamless, HTTPS + HMAC)
```

* **Forge mode** — single-player: bet, strike with a chosen heat, quench (cash out) before the ingot breaks.
* **Live mode** — multiplayer crash format: all players of a table share one round.
* Hash-chained audit ledger per session; idempotent APIs; server-side loss limits,
  session time limits, self-exclusion and reality checks.

## 2. Launch

```
https://<game-host>/?lang=en&market=uk&rtp=96&currency=GBP&operator=<id>&player=<id>&parentOrigin=https://casino.example
```

| Parameter | Values | Meaning |
|---|---|---|
| `lang` | `en` (default), `de` | UI language |
| `market` | `default`, `uk`, `strict` (extendable) | Jurisdiction profile, see §6 |
| `rtp` | `94`, `96`, `97` (or `0.94` …) | RTP variant; must be allowed for the market |
| `currency` | ISO 4217 | Display currency (the demo always uses play money) |
| `operator`, `player` | strings | Operator and player identifiers (production: a one-time `launchToken`) |
| `parentOrigin` | origin | Only this origin receives `postMessage` events |

Invalid combinations are rejected when the session is created (`MARKET_UNKNOWN`,
`RTP_NOT_ALLOWED`). A session keeps its market and RTP for its whole lifetime.

### Operator handshake (live)

`GET /api/operator/handshake` returns the machine-readable contract: RTP variants,
market profiles, math parameters of both modes, launch parameters and event names.

### Events to the parent window

`window.parent.postMessage({ source: "heatbreaker", demo, type, … }, parentOrigin)`

| `type` | Payload |
|---|---|
| `ready` | `balance`, `version` |
| `roundEnd` | `mode` (`forge` / `live`), `result` (`win` / `break`), `payout`, `multiplier`, `balance` |

## 3. Mathematics (summary)

| | Forge | Live |
|---|---|---|
| RTP | 94 / 96 / 97 %, **identical for every strategy** | 94 / 96 / 97 %, identical for every cash-out target |
| Max multiplier | 250× (auto-collect) | 1,000× (auto cash-out) |
| Randomness | HMAC-SHA256(serverSeed, clientSeed│roundId│strike) | HMAC-SHA256(serverSeed, roundId), 52 bits |
| Fairness | commit–reveal: SHA-256(serverSeed) shown before play, seed revealed after | same |
| Proof | `SIMULATION_REPORT.md` (`node sim.js`) | `CRASH_MATH.md` (`node tools/crash-sim.js`) |

## 4. Seamless wallet contract

### 4.1 Transport and signing

* `POST` JSON over HTTPS to the operator's base URL, e.g. `https://wallet.operator.example`.
* Headers on every request: `X-Operator-Id`, `X-Timestamp` (Unix ms), `X-Request-Id`, `X-Signature`.
* `X-Signature = hex(HMAC-SHA256(sharedSecret, "POST|<path>|<timestamp>|<rawBody>"))`
* The operator signs its **response** the same way with method `RESPONSE`:
  `hex(HMAC-SHA256(sharedSecret, "RESPONSE|<path>|<timestamp>|<rawBody>"))` in `X-Signature`
  and `X-Timestamp` response headers. Unsigned responses are rejected (`SIGNATURE_INVALID`).
* Accepted clock skew: ±5 minutes. Timeout per call: 5 s (configurable).
* Amounts are decimal numbers with at most 2 fraction digits in the player's currency.

### 4.2 Endpoints

| Path | Purpose | Request body (additional to headers) | 200 response |
|---|---|---|---|
| `/wallet/v1/authenticate` | Exchange launch token | `launchToken`, `gameId` | `playerId`, `sessionId`, `currency`, `balance` |
| `/wallet/v1/balance` | Current balance | `playerId`, `sessionId` | `balance`, `currency` |
| `/wallet/v1/debit` | Stake (BET) | `playerId`, `sessionId`, `amount`, `currency`, `roundId`, `txId`, `gameId`, `gameMode`, `type:"BET"` | `balance`, `currency`, `operatorTxId` |
| `/wallet/v1/credit` | Win or round end | `playerId`, `sessionId`, `amount` (≥ 0), `currency`, `roundId`, `txId`, `refTxId`, `endRound`, `type:"WIN"│"END"` | `balance`, `currency`, `operatorTxId` |
| `/wallet/v1/rollback` | Cancel a stake | `playerId`, `sessionId`, `roundId`, `txId`, `refTxId`, `type:"ROLLBACK"` | `balance`, `currency`, `operatorTxId` |

### 4.3 Errors

Error responses use a non-2xx status and `{ "error": "<CODE>" }`.

| Code | HTTP | Game behaviour |
|---|---|---|
| `INSUFFICIENT_FUNDS` | 402 | bet refused, message to player |
| `PLAYER_NOT_FOUND`, `SESSION_EXPIRED` | 404 / 401 | session ends, player is asked to relaunch |
| `PLAYER_BLOCKED`, `LIMIT_REACHED` | 403 | bet refused (operator-side responsible gaming) |
| `CURRENCY_MISMATCH` | 400 | bet refused |
| `IDEMPOTENCY_CONFLICT` | 409 | same `txId` with a different payload — never booked |
| `TX_ROLLED_BACK` | 409 | debit arrived after its rollback — never booked |
| `SIGNATURE_INVALID` | 401 | request refused |
| `OPERATOR_ERROR` | 5xx | retried (credit / rollback) or bet refused (debit) |

### 4.4 Idempotency and retries (mandatory)

1. Every money call carries a unique `txId`. Keys used by the game:
   `bet:<roundId>`, `win:<roundId>`, `end:<roundId>`, `rb:<txId>`; live bets append the bet id.
2. Same `txId` + same payload → the operator returns the **original** response and books nothing new.
   Same `txId` + different payload → `409 IDEMPOTENCY_CONFLICT`.
3. **Debit** is never retried blindly. On timeout or network error the game sends a rollback
   for that `txId` and treats the bet as failed.
4. **Rollback before debit (tombstone rule):** if a rollback references an unknown `txId`, the
   operator stores it; a debit with that `txId` arriving later must be refused with
   `409 TX_ROLLED_BACK`. (Covered by the contract test.)
5. **Credit** and **rollback** are retried with exponential backoff and the same `txId` until
   acknowledged; unacknowledged calls stay in the game's persistent retry journal
   (live tables retry every 5 s).

### 4.5 Round lifecycle

| Mode | Event | Wallet call |
|---|---|---|
| Forge | round start (bet) | `debit` |
| Forge | quench / cap auto-collect / inactivity auto-collect (60 s) | `credit` (amount = payout, `endRound: true`) |
| Forge | ingot breaks | `credit` amount 0, `type:"END"` |
| Live | bet during betting phase | `debit` |
| Live | bet cancelled before take-off | `rollback` |
| Live | manual / auto cash-out, or cap at 1,000× | `credit` |
| Live | crash with open bet | `credit` amount 0, `type:"END"` |

## 5. Player API (game client ↔ RGS)

| Method | Path | Notes |
|---|---|---|
| POST | `/api/session` | body: `market`, `rtp`, `lang`, … → token + per-session config |
| GET | `/api/state` | balance, active round, session stats, limits, config |
| POST | `/api/round/start` · `/strike` · `/collect` | Forge; `X-Idempotency-Key` supported |
| GET | `/api/history` | finished rounds incl. revealed seeds (verification) |
| POST | `/api/limits`, `/api/self-exclude` | responsible gaming (increases take effect after 24 h) |
| WSS | `/api/crash/ws?table=<name>` | Live table; table name per market × RTP (from config) |
| GET | `/api/operator/handshake`, `/health` | contract / health |

## 6. Market profiles

Configured in `config.json → markets`; enforced server-side (limits, RTP, auto cash-out,
available modes) and in the client (autoplay, session clock, reality check).

| Market | RTP allowed | Bet limits | Autoplay | Auto cash-out | Reality check | Session clock |
|---|---|---|---|---|---|---|
| `default` | 94 / 96 / 97 % | 0.10 – 100 | yes (with mandatory loss limit) | yes | player setting | no |
| `uk` | 94 / 96 / 97 % | 0.10 – 100 | **no** | yes | at least every 60 min | **yes** (time + net result) |
| `strict` | 94 / 96 / 97 % | 0.10 – 10 | **no** | **no** | at least every 30 min | **yes** |

New jurisdictions are added as a new entry (label, RTP list, limits, switches, modes).
Each market × RTP combination gets its own live table automatically.

## 7. Responsible gaming (built in)

Loss limit, session time limit (hard cap 12 h), self-exclusion (24 h – 180 d), reality check
with session time and net result, cooling-off for limit increases (24 h), inactivity
auto-collect, visible break probabilities in Forge mode. Operator-side limits are respected
through `LIMIT_REACHED` / `PLAYER_BLOCKED`.

## 8. Verification for players and labs

* In-game fairness panel shows the server-seed hash before play and the seed afterwards.
* `verify.html` recomputes any Forge round (seed, client seed, round id, heats, RTP).
* Live history entries can be verified in-game (hash check + crash point recomputation).
* Simulations: `node sim.js 2000000` and `node tools/crash-sim.js 2000000 --write`.
