Add replayable debug reports.

A report dumps everything needed to understand a game that went wrong:
the full state (decks card by card, shop row and tier deck order,
discards, pending choices, banked Mana/Trumpets/apples), the round's
battles, and the whole event log. Available as JSON, which replays, or
text, which reads — and the text ends with a paste-ready repro test.

Battles now record the randomness they consume alongside what they
started from, so a result can be replayed long after the round cleared
those banks: same lineups, same dice, same events, down to the log text.
Results predating the recording say so rather than quietly re-rolling.

Three ways in: the 🐛 panel's download/copy buttons, GET
/api/debug/report (DEBUG-only, since a report holds both players' hands
and the shop deck order), and `mise run report`, which reads the
database directly so a live game can be dumped without DEBUG.
This commit is contained in:
Greyson Parrelli
2026-08-10 10:06:06 -04:00
parent a4f5f6910d
commit 8ccde03023
16 changed files with 1480 additions and 19 deletions
+44 -5
View File
@@ -38,11 +38,49 @@ During development open the Vite URL (http://localhost:5173); it proxies
Set via environment or a `.env` file (see `.env.example`):
| Variable | Default | Purpose |
| ------------ | ---------- | ------------------------------------ |
| `DATA_DIR` | `data` | Directory holding the SQLite DB |
| `PORT` | `8080` | HTTP port |
| `STATIC_DIR` | `web/dist` | Built frontend to serve |
| Variable | Default | Purpose |
| ------------ | ---------- | ------------------------------------------- |
| `DATA_DIR` | `data` | Directory holding the SQLite DB |
| `PORT` | `8080` | HTTP port |
| `STATIC_DIR` | `web/dist` | Built frontend to serve |
| `DEBUG` | off | Unlocks the in-game debug panel (see below) |
## Debugging a game that went wrong
Any saved game can be dumped as a **debug report**: the full state (every deck
card by card, the shop row and the order of the remaining tier decks, discards,
pending choices, each player's banked Mana/Trumpets/apples), the round's battles
with their lineups and *the dice they rolled*, and the entire event log.
```sh
mise run report # list recent games
mise run report -- QWERT # the report, as text, for reading
mise run report -- -json -out internal/game/testdata/bug.json QWERT
```
With `DEBUG=1` the in-game 🐛 panel grows two buttons — **Download JSON** and
**Copy as text** — that pull the same report over `GET /api/debug/report`. A
report exposes both players' hands and the shop deck order, so that endpoint is
DEBUG-only; on a live server use `cmd/report`, which reads the database.
The JSON form is the useful one, because it replays. Drop it in
`internal/game/testdata/` and the battle re-runs exactly — same lineups, same
dice, same events, right down to the log text:
```go
rep, err := LoadDebugReportFile("testdata/bug.json")
res, err := rep.ReplayBattle(0) // 0 = the round's first pairing
if res.WinnerSeat != 1 { // assert what *should* have happened
t.Fatalf("expected seat 1 to win, got %d", res.WinnerSeat)
}
```
That works because every battle records the randomness it consumed
(`BattleResult.Draws`) alongside what it started from (`Lineups`, `Inputs`), so
a result is replayable long after the round cleared those banks. `rep.Game()`
hands back the whole game if the bug wasn't in the battle — carry on from the
shop, replay a different pairing, or read a deck card by card. The text form of
the report ends with a paste-ready version of the test above.
## Rules implemented
@@ -184,6 +222,7 @@ The Unicorn pack adds two mechanics the others don't have:
```
cmd/server/ entrypoint
cmd/report/ debug report dumper (reads the DB directly)
internal/game/ rules engine (pure, fully tested)
internal/ai/ computer opponent (decides from a player View only)
internal/server/ HTTP + WebSocket rooms; drives bot turns