Files
super-auto-pets-board-game/internal/ai/debug_report_test.go
T
Greyson Parrelli 8ccde03023 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.
2026-08-10 10:06:06 -04:00

60 lines
1.8 KiB
Go

package ai
import (
"encoding/json"
"testing"
"github.com/greyson/super-auto-pets-board-game/internal/game"
)
// A debug report is only worth taking if replaying it reproduces the battle it
// captured, and the situations worth reporting are the messy ones — six seats,
// three packs shuffled together, Mana and Trumpets banked, Komodo shuffling
// apples into a deck mid-fight. This test lives in the ai package because that
// is where full games get played: it hands the bots a table, then replays every
// battle of the last round out of the report and demands the identical result.
//
// If it ever fails, the engine has grown a source of randomness (or a piece of
// battle input) that the result doesn't record, and reports of that battle are
// no longer reproducible.
func TestDebugReportReplaysRealGames(t *testing.T) {
for _, tc := range []struct {
name string
packs []string
levels []float64
}{
{"two seats, one pack", []string{"turtle"}, []float64{0.9, 0.9}},
{"six seats, three packs", []string{"turtle", "golden", "unicorn"},
[]float64{0.9, 0.9, 0.9, 0.9, 0.9, 0.9}},
} {
t.Run(tc.name, func(t *testing.T) {
g := playBotTable(t, tc.packs, tc.levels...)
rep, err := g.DebugReport()
if err != nil {
t.Fatal(err)
}
if len(g.Battles) == 0 {
t.Fatal("a finished game should have battles to replay")
}
for i, recorded := range g.Battles {
replayed, err := rep.ReplayBattle(i)
if err != nil {
t.Fatalf("battle %d: %v", i, err)
}
if got, want := marshal(t, replayed), marshal(t, recorded); got != want {
t.Fatalf("battle %d replayed differently:\nrecorded %s\nreplayed %s", i, want, got)
}
}
})
}
}
func marshal(t *testing.T, res *game.BattleResult) string {
t.Helper()
b, err := json.Marshal(res)
if err != nil {
t.Fatal(err)
}
return string(b)
}