mirror of
https://github.com/prosolis/gogobee.git
synced 2026-09-14 10:51:09 +00:00
adventure: stop the log claiming a win it didn't see
Four things the review found, all of them the code telling a player something that isn't true. A run that walks into a dead end filed its ending as "cleared" whatever the caller said, so both the liveblog and the summary reported a clear for a party that merely ran out of map — and the end beat is first-writer-wins, so nothing later could take it back. It says "cleared" only for a boss now. The re-offer branches of babysit and resume returned a zero cost, so a player who was in fact charged read "0 coins" in the verdict. Both re-quote the price they actually took. And the realm-firsts reseed retired its one-shot job even when the read under it had failed, which on a transient fault at Init would have left the ledger mis-dated permanently. The read now says whether it worked, and the job stays open when it didn't. Claude-Session: https://claude.ai/code/session_012bxpQQJDjC1mTtLN3VVtBQ
This commit is contained in:
@@ -76,7 +76,11 @@ type zoneFirstClear struct {
|
||||
// SQLite returns the user_id from the same row as MIN(completed_at) (bare-column
|
||||
// min/max rule), so the (zone, first clearer, time) triple is internally
|
||||
// consistent.
|
||||
func zoneFirstClears() []zoneFirstClear {
|
||||
// ok is false only when the read itself failed. A caller that is about to mark
|
||||
// a one-shot job complete has to be able to tell "no zone has ever been cleared"
|
||||
// from "the query fell over", or a transient DB fault at boot retires the repair
|
||||
// permanently.
|
||||
func zoneFirstClears() (firsts []zoneFirstClear, ok bool) {
|
||||
rows, err := db.Get().Query(
|
||||
`SELECT zone_id, user_id, MIN(completed_at)
|
||||
FROM dnd_zone_run
|
||||
@@ -84,11 +88,10 @@ func zoneFirstClears() []zoneFirstClear {
|
||||
GROUP BY zone_id`)
|
||||
if err != nil {
|
||||
slog.Error("backfill: zone-firsts query", "err", err)
|
||||
return nil
|
||||
return nil, false
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var firsts []zoneFirstClear
|
||||
for rows.Next() {
|
||||
var f zoneFirstClear
|
||||
if err := rows.Scan(&f.zoneID, &f.userID, &f.completedAt); err != nil {
|
||||
@@ -97,7 +100,11 @@ func zoneFirstClears() []zoneFirstClear {
|
||||
}
|
||||
firsts = append(firsts, f)
|
||||
}
|
||||
return firsts
|
||||
if err := rows.Err(); err != nil {
|
||||
slog.Error("backfill: zone-firsts rows", "err", err)
|
||||
return nil, false
|
||||
}
|
||||
return firsts, true
|
||||
}
|
||||
|
||||
// bootstrapRealmFirstsReseed repairs the zone half of news_realm_firsts.
|
||||
@@ -131,8 +138,16 @@ func bootstrapRealmFirstsReseed() {
|
||||
return
|
||||
}
|
||||
|
||||
clears, ok := zoneFirstClears()
|
||||
if !ok {
|
||||
// The read failed. Leave the job unmarked so the next boot tries again —
|
||||
// marking it here would retire the repair on the strength of a transient
|
||||
// DB fault and leave the ledger wrong forever.
|
||||
return
|
||||
}
|
||||
|
||||
seeded := 0
|
||||
for _, f := range zoneFirstClears() {
|
||||
for _, f := range clears {
|
||||
ts, ok := parseSQLiteTime(f.completedAt)
|
||||
if !ok {
|
||||
slog.Warn("reseed: unparseable clear time", "zone", f.zoneID, "at", f.completedAt)
|
||||
@@ -155,7 +170,7 @@ func bootstrapRealmFirstsReseed() {
|
||||
// dispatch per zone, attributed to its earliest boss-defeating clearer.
|
||||
// Returns the count emitted.
|
||||
func (p *AdventurePlugin) backfillZoneFirsts() int {
|
||||
firsts := zoneFirstClears()
|
||||
firsts, _ := zoneFirstClears()
|
||||
|
||||
n := 0
|
||||
for _, f := range firsts {
|
||||
|
||||
Reference in New Issue
Block a user