Skip to main content

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

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):
Start the v6 client:
Run the tests:
Reports are written to results/test-report.md and results/test-log.json.

Repository Structure


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.
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: 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 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:

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: 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

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.
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:

Global Setup Pre-flight Checks

Before any test runs, src/global-setup.ts verifies:
  1. Client reachableGET http://localhost:4000/ returns 2xx
  2. Server reachable and flag setPOST 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.