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:
+28
-7
@@ -187,20 +187,41 @@ type Game struct {
|
||||
// RollDie overrides the rock die (faces 0,0,1,1,2,2) for tests. Nil
|
||||
// (including after loading from storage) means a fair random roll.
|
||||
RollDie func() int `json:"-"`
|
||||
|
||||
// drawTape records every battleDraw the battle currently resolving makes,
|
||||
// and drawReplay feeds a recording back in. Together they make a finished
|
||||
// battle exactly reproducible from a debug report (see debug.go). Both are
|
||||
// scratch state for one battle and are never serialized.
|
||||
drawTape []int
|
||||
drawReplay []int
|
||||
}
|
||||
|
||||
// battleDraw returns a random value in [0, n) for a battle's randomness — rock
|
||||
// dice and Komodo's apple shuffle alike. The RollDie test override applies to
|
||||
// rock dice (n == 3).
|
||||
// rock dice (n == 3). Every value handed out is appended to the running draw
|
||||
// tape, which runBattle files with the result so the battle can be replayed.
|
||||
func (g *Game) battleDraw(n int) int {
|
||||
switch {
|
||||
case n <= 0:
|
||||
if n <= 0 {
|
||||
return 0
|
||||
case n == 3 && g.RollDie != nil:
|
||||
return g.RollDie() // test override applies to rock dice
|
||||
default:
|
||||
return randInt(n)
|
||||
}
|
||||
var v int
|
||||
switch {
|
||||
case len(g.drawReplay) > 0:
|
||||
// Replaying a recording: take the next value off the tape. It's folded
|
||||
// back into range in case the replay diverged onto a differently-sized
|
||||
// draw, so a stale tape can never panic or roll an illegal face.
|
||||
v, g.drawReplay = g.drawReplay[0], g.drawReplay[1:]
|
||||
if v < 0 {
|
||||
v = -v
|
||||
}
|
||||
v %= n
|
||||
case n == 3 && g.RollDie != nil:
|
||||
v = g.RollDie() // test override applies to rock dice
|
||||
default:
|
||||
v = randInt(n)
|
||||
}
|
||||
g.drawTape = append(g.drawTape, v)
|
||||
return v
|
||||
}
|
||||
|
||||
// rollRockDie rolls one rock die: 0, 1, or 2 with equal probability.
|
||||
|
||||
Reference in New Issue
Block a user