> ## Documentation Index
> Fetch the complete documentation index at: https://hedgeem-api.qeetoto.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Test Implementation

> How the HedgeEm v6 Playwright test harness is built and how to run it

## Overview

The HedgeEm v6 test harness validates the full game pipeline end-to-end — from the REST API's card evaluation logic through to what the Phaser 3 client renders on the canvas. It uses Playwright as the test runner and operates against two live local servers simultaneously.

The core challenge: Phaser renders everything to `<canvas>`. There is no DOM to inspect. Instead, the harness uses a `window.__hedgeem` hook injected into `GameScene.create()` that exposes the game engine's internal state as a plain JavaScript object. Tests call this hook to read the game state, drive the game forward, and inject forced deals.

***

## Architecture

```
┌─────────────────────────────────────────────────────────────┐
│  Test Harness (hedgeem_v6_testing/)                         │
│                                                             │
│  demo-records.ts  →  callForcedDeal()  →  /api/deal/forced │
│  (oracle data)       (server-client.ts)   (hedgeem-v6-server)│
│                              │                              │
│                              ▼                              │
│                       mapToGameRecord()                     │
│                              │                              │
│                              ▼                              │
│             window.__hedgeem.loadForcedRecord(record)       │
│                    (GameScene.js — Phaser client)            │
│                              │                              │
│                              ▼                              │
│             window.__hedgeem.getSnapshot()  ←── assertions  │
└─────────────────────────────────────────────────────────────┘
```

**Flow per demo record:**

1. `callForcedDeal()` — POST card data to `/api/deal/forced`; server evaluates odds fresh via Monte Carlo
2. `mapToGameRecord()` — map the BulkGameState response to the client's `GameRecord` shape
3. `loadForcedRecord(record)` — inject the record into the running Phaser game via `window.__hedgeem`
4. Wait for `dealStatus === 0` (HOLE stage rendered)
5. Assert HOLE-stage values via `getSnapshot()`
6. `advance()` → FLOP → assert
7. `advance()` → TURN → assert
8. `advance()` → RIVER → assert + `gameOver`

***

## Prerequisites

Both servers must be running before tests start. The global setup pre-flight check verifies both and fails with a clear error if either is missing.

**Start the API server (with forced-deal flag):**

```bash theme={null}
cd hedgeem-v6-server
ALLOW_FORCED_DEAL=true vercel dev --listen 3010
```

**Start the v6 client:**

```bash theme={null}
cd hedgeem-v5/hedgeem-v6-client
npm run preview
# Serves on http://localhost:4000
```

**Run the tests:**

```bash theme={null}
cd hedgeem_v6_testing
npx playwright test
```

Reports are written to `results/test-report.md` and `results/test-log.json`.

***

## Repository Structure

```
hedgeem_v6_testing/
├── package.json
├── tsconfig.json
├── playwright.config.ts
├── src/
│   ├── demo-records.ts          # 17 oracle records (card data + expected values)
│   ├── server-client.ts         # callForcedDeal() + mapToGameRecord()
│   ├── code-map.ts              # AssertionType → file:line registry
│   └── reporters/
│       ├── markdown-reporter.ts # → results/test-report.md
│       └── json-log-reporter.ts # → results/test-log.json
└── tests/
    └── game-validation.spec.ts  # Main spec: 17 demos × 4 stages
```

***

## Key Files

### `/api/deal/forced` — Server Endpoint

**File:** `hedgeem-v6-server/api/deal-forced.ts`

Accepts a POST body with known card data and returns a full `BulkGameState` — the same shape as `/api/games/bulk`. It reuses the existing `buildStageEntries` / `computeOddsChain` pipeline, substituting supplied cards for `dealGame()` output.

Gated by the `ALLOW_FORCED_DEAL=true` environment variable. Returns `403` if the flag is not set, so the endpoint can never accidentally reach production.

```json theme={null}
// POST /api/deal/forced
{
  "hands": ["KdKc", "7d3d", "5s5c", "JhQc"],
  "community": { "bc1": "5h", "bc2": "3h", "bc3": "4h", "bc4": "Qd", "bc5": "As" },
  "numberOfHands": 4
}
```

The response contains `handStageInfoList` — one entry per hand per stage, covering all four betting stages (HOLE, FLOP, TURN, RIVER).

### `window.__hedgeem` — Client Hook

**File:** `hedgeem-v5/hedgeem-v6-client/src/scenes/GameScene.js`

Injected during `GameScene.create()`. Exposes:

| Method                     | Description                                                              |
| -------------------------- | ------------------------------------------------------------------------ |
| `getSnapshot()`            | Full game state snapshot — `handStageInfo[]`, `gameOver`, odds, statuses |
| `getDealStatus()`          | Current stage: 0=HOLE, 1=FLOP, 2=TURN, 3=RIVER (−1 before deal)          |
| `loadForcedRecord(record)` | Inject a `GameRecord` → calls `_startDemoHand(record)`                   |
| `advance()`                | Advance one stage → calls `_onAdvance()`                                 |
| `getDebug()`               | Internal debug info (config, engine state)                               |

When `loadForcedRecord` is called with a different `numberOfHands` than the current scene, `_startDemoHand` triggers `scene.restart()`. The test harness polls `waitForFunction(() => window.__hedgeem?.getDealStatus?.() === 0)` to wait for the restarted scene to be ready.

### `src/demo-records.ts` — Oracle Data

Contains all 17 demo game records, each with:

* `hands[]` — hole card pairs (4 cards per hand as a 4-character string, e.g. `"KdKc"`)
* `community` — the five board cards
* `stages[0–3]` — per-hand oracle values for each betting stage

Oracle values are used differently depending on the field — see [Oracle Strategy](#oracle-strategy) below.

### `src/server-client.ts` — Type Bridge

`callForcedDeal(request, oracle, serverBaseUrl)` sends the POST and returns `BulkGameState`.

`mapToGameRecord(serverResp)` maps the server response to the `GameRecord` shape the client expects:

| Server                 | Client                                                            |
| ---------------------- | ----------------------------------------------------------------- |
| `percentWin` (0–100)   | `percentWin` (0–1)                                                |
| `bettingStage` (0–3)   | `gameState` (`'STATUS_HOLE'` etc.) + `enumGameState` (6/10/11/12) |
| `null` `handDescShort` | `''`                                                              |

### `src/code-map.ts` — Failure Hints

Maps each assertion type to the source file locations where bugs would most likely be introduced. The markdown reporter uses this to annotate failure output with direct file:line references, saving time when diagnosing failures.

***

## Oracle Strategy

The harness uses two oracle sources depending on the assertion:

| Field               | Oracle Source            | Rationale                                                                                               |
| ------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------- |
| `oddsRounded`       | Server response          | Monte Carlo varies between v4 C# and v6 Node.js; asserting client matches server validates the pipeline |
| `oddsActual`        | Server response          | Same reason                                                                                             |
| `handDescShort`     | Server response          | pokersolver naming differs from v4 C# hand names; server→client pipeline is what matters                |
| `statusCantLose`    | Server response          | Deterministic: `percentWinOrDraw >= 99.9%`                                                              |
| `statusIsFavourite` | `demo-records.ts` oracle | Deterministic given card data — highest `percentWinOrDraw` at each stage                                |
| `statusIsWinner`    | `demo-records.ts` oracle | Deterministic — asserted at RIVER only                                                                  |
| `gameOver`          | Client snapshot          | `snap.gameOver === true` after RIVER advance                                                            |

**Why not use coredata values for odds?** The coredata odds were computed by the original v4 C# Monte Carlo implementation. The v6 server uses a fresh Node.js Monte Carlo run with different random seeds and possibly different simulation counts. Results are close but not identical (±0.1–0.3 variance at early stages). Testing client vs server (not client vs coredata) validates the complete server→client pipeline without fighting implementation differences between v4 and v6.

***

## Assertion Types and Tolerances

| Assertion           | Stages     | Tolerance                         | Skip Condition                         |
| ------------------- | ---------- | --------------------------------- | -------------------------------------- |
| `oddsRounded`       | HOLE–TURN  | `toBeCloseTo(x, 1)` — within ±0.1 | `statusCantLose === true` or dead hand |
| `oddsActual`        | HOLE only  | `toBeCloseTo(x, 1)`               | `statusCantLose === true`              |
| `handDescShort`     | FLOP–RIVER | Exact string match                | —                                      |
| `statusIsFavourite` | HOLE–RIVER | Exact boolean                     | —                                      |
| `statusCantLose`    | FLOP–RIVER | Exact boolean                     | —                                      |
| `statusIsWinner`    | RIVER only | Exact boolean                     | —                                      |
| `gameOver`          | RIVER only | `toBe(true)`                      | —                                      |

**Dead hands** (percentWin=0 AND percentDraw=0) have their odds assertions skipped — the client renders them as eliminated, not with a meaningful odds figure.

**cantLose hands** (percentWinOrDraw >= 99.9%) skip odds assertions because the server returns `oddsActual=0` as a special sentinel value indicating certainty.

***

## Test Structure

Tests are serial with a single browser context — the Phaser game is stateful and cannot be parallelised.

```
playwright.config.ts
  workers: 1
  fullyParallel: false
  globalSetup: './src/global-setup.ts'
```

The outer `beforeAll` opens the page once, clicks the splash overlay, and waits for the `__hedgeem` hook to be registered. Each of the 17 demo `test.describe` blocks then injects its record and runs HOLE/FLOP/TURN/RIVER sub-describes in order.

Total: **856 tests** across 17 demos × 4 stages × 3–8 assertions per hand.

***

## Reporters

### Markdown Reporter

Writes `results/test-report.md` with:

* Summary line: total, passed, failed, skipped counts
* Full table: one row per test, pass/fail emoji
* Failures section: expected vs actual, code location hints from `CODE_MAP`

### JSON Log Reporter

Writes `results/test-log.json` with one structured entry per test:

```json theme={null}
{
  "test": "Demo 2: All hands have action — FLOP — hand 0 statusIsFavourite",
  "assertion": "statusIsFavourite",
  "stage": "FLOP",
  "handIndex": 0,
  "demoIndex": 1,
  "gameName": "All hands have action",
  "pass": false,
  "expected": true,
  "actual": false,
  "codeLocations": ["GameScene.ts:1136-1185", "games/bulk.ts:buildStageEntries"]
}
```

***

## Global Setup Pre-flight Checks

Before any test runs, `src/global-setup.ts` verifies:

1. **Client reachable** — `GET http://localhost:4000/` returns 2xx
2. **Server reachable and flag set** — `POST http://localhost:3010/api/deal/forced` returns 200 or 400 (not 403 or 404)

If either check fails, a clear human-readable error is thrown with the exact command to start the missing server.

***

## Known Limitations

**No visual assertions** — the harness reads game state via `getSnapshot()`, not by inspecting the canvas. Rendering bugs (wrong colour, wrong position) are not caught.

**Single browser** — tests run in Chromium only. Firefox/WebKit are not covered.

**No bet placement testing** — validating win labels (F049) requires placing bets, which needs an additional `window.__hedgeem.placeBet()` hook. This is planned as Phase 2.

**`handDescShort` naming differences** — the coredata oracle uses v4 C# hand names (e.g. "AceFlush", "QueenFlush") which differ from pokersolver names (e.g. "Flush"). The harness asserts client against server for this field, bypassing the naming difference entirely.
