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