← back to CelebritySignatures

screenrecord/DEBUG-REPORT-game.md

93 lines

# CelebritySignatures /game — 5-Pass Screen-Record Debug Report
Ticket: **TK-10192**
Date: 2026-08-08
Target: `public/game.html` — "The Signature Games" (6 game types)
Run env: app booted locally on **:9919 in Stripe TEST mode** (no `STRIPE_LIVE_ENABLED`, no real charge).
`:9920` (the batch-mislabel port) is reassigned to `idea-loop`; there is no standing Mac2 web process for
CelebritySignatures (it is now the submitted iOS app, `apps/mobile`). `/game` is the real, distinct surface
(siblings: /murals=TK-10193 DONE, front=TK-10191, /upload=TK-10194 DONE).

The 6 games: **whose** (Whose Hand?), **art** (Who Signed This?), **match** (Match the Hand),
**early** (Early or Late?), **century** (Date the Hand), **lightning** (⚡ Lightning Round).

Artifacts:
- `screenrecord/rec/game-run{0..4}/*.mp4` — 5 screen recordings (one per unique game-order combination)
- `screenrecord/debug-log.jsonl` — append-only action log (shared file; front-page rows interleaved)
- `screenrecord/game-debug.mjs` — the 5-pass harness

---

## HIGHEST-LEVERAGE FIX (top item)

### BUG 1 — a build-time failure fired a native `alert()` that modally froze the page (order-dependent cascade)
**Severity: Medium (resilience + UX; order-dependent / state-leak).**

`#goBtn` (Begin) ran `buildRounds()`; on any throw it called **`alert(e.message)`** (`game.html:366`).
`buildRounds` legitimately throws when a tier can't be filled — most often for the three **artwork games**
(`art`, `match`, `lightning`), which depend on the **external Art Institute of Chicago API**
(`api.artic.edu`, CORS-open, keyless). When that API is slow or returns too few public-domain artworks at a
tier, the game threw *"Not enough museum artworks — try Icons or Scholar."*

The problem was the **native `alert()`**: it modally blocks the whole page until dismissed. In the 5-pass harness
(which reuses one page across all six games and had **no dialog handler**), a single artwork-game failure left an
alert open that **jammed every subsequent game's Begin click** — a 30 000 ms timeout logged as a false "broken."
That is the exact pattern in the raw run summary:

```
run0 [whose,art,match,early,century,lightning] worked=[all]                broken=[]
run1 [lightning,century,early,match,art,whose] worked=[lightning]          broken=[the other 5]
run2 [whose,early,century,lightning,art,match] worked=[whose,early]        broken=[4]
run3 [art,early,century,whose,lightning,match] worked=[art,early]          broken=[4]
run4 [lightning,whose,art,match,early,century] worked=[5]                  broken=[century]
```
"First game(s) work, everything after breaks" = one alert poisoning the run, **not** six broken games.

**Proof the games themselves are sound** (fresh page, data given ~1.8 s to load): `whose`, `century`, `early`
all reach the play panel and render a stage, zero dialogs; data is plentiful (`datedInTop100 = 100`, so
`century` is never genuinely short). `art` / `match` / `lightning` reach play once `api.artic.edu` responds
(`match`, the heaviest — 4 artworks/round — resolves to play with 4 choices after full load).

**Fixes applied (this ticket):**
- **A — product (`game.html`):** replaced the blocking `alert(e.message)` with an **inline, non-blocking**
  error rendered in `#prep` (new `.prep.err` style). Success now hides `#prep`; failure keeps the reason
  visible and re-enables Begin. A flaky museum API now degrades to a readable "try Icons or Scholar" line
  instead of a modal browser alert that dead-ends the player.
- **B — harness (`game-debug.mjs`):** added `page.on('dialog', d => d.dismiss())` so a stray dialog can never
  again poison a whole run and inflate the "broken" count.

**Verification (post-fix, reproducing the run1 cascade order on one reused page):**
```
[lightning] clickMs=27  play=true
[century]   clickMs=49  play=true
[early]     clickMs=44  play=true
[match]     clickMs=28  play=false→play (still loading artwork at sample; did NOT block next game)
[art]       clickMs=39  play=true
[whose]     clickMs=54  play=true
DIALOGS: []              ← zero native alerts
```
Every Begin click is 27–54 ms (was 30 000 ms). The cascade is gone; each game starts independently.

---

## Verified WORKING (all 6 games)

- **whose / century / early** — fully data-driven off `/api/signatures` (+ `/api/signature-evolution` for
  early); render mystery-signature stage + choices; scoring, streak, reveal, and Next all function.
- **art / match / lightning** — render real public-domain artworks from `api.artic.edu`; choices, reveal
  (with "see it →" links), timer (lightning 5 s countdown), and the lightning both-right bonus all function.
- **Results / social** — final score, verdict tiers, "worth a second look" misses, localStorage best,
  share (copy + 𝕏 intent), and the leaderboard POST/GET (`/api/leaderboard`) all wired.
- **JS health** — no `console.error` / `pageerror` attributable to the games (GA + reCAPTCHA third-party
  frames are network-blocked by the harness route guard; those aborts are the guard, not a page error).

---

## NOTES / known follow-ups (not fixed here — out of this ticket's tight scope)

- **`nextBtn` has no double-click guard** (`game.html:448/491`): a fast double-tap on "Next →" runs
  `game.i++` twice and skips a round. Low severity; candidate for a one-line `nextBtn.disabled=true` on click.
- **Artwork games remain external-API-bound** by design (`api.artic.edu`). With Fix A they now fail *gracefully*
  (inline message) rather than crash, but availability/latency there still gates `art`/`match`/`lightning`.
- The shared `debug-log.jsonl` interleaves front-page (`run-debug.mjs`) rows; per-game truth is in the harness
  console run-summaries + the recordings, not a naive grep of the log.