aboutsummaryrefslogtreecommitdiff
path: root/internal/game/economy.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/game/economy.go')
-rw-r--r--internal/game/economy.go49
1 files changed, 34 insertions, 15 deletions
diff --git a/internal/game/economy.go b/internal/game/economy.go
index 04508be..afdcdd8 100644
--- a/internal/game/economy.go
+++ b/internal/game/economy.go
@@ -353,13 +353,21 @@ func (g *Game) recoverEconomy() {
players[name] = p
return p
}
- hasLaterPayout := func(name string, seq int) bool {
+ // laterPayoutEvidence reports whether any non-void payout record was
+ // journaled for the player after seq — evidence the round resolved and
+ // its settlement was recorded — together with the total credits those
+ // records promise. A payout covering the stake's full value returned
+ // the stake inside the payout (win, push, or refund); a smaller total
+ // (e.g. the credit side of a crashed mixed refund) leaves the chip
+ // side of the stake outstanding.
+ laterPayoutEvidence := func(name string, seq int) (resolved bool, credits int) {
for _, fr := range folded {
if fr.rec.Kind == "payout" && fr.rec.Player == name && fr.rec.Status != "void" && fr.seq > seq {
- return true
+ resolved = true
+ credits += fr.rec.Credit
}
}
- return false
+ return resolved, credits
}
compensated := 0
@@ -413,13 +421,23 @@ func (g *Game) recoverEconomy() {
paid++
compensated++
}
- for _, other := range group {
- if paid == 0 {
- break
+ for idx, other := range group {
+ if idx < paid {
+ g.Economy.settle(other)
+ settled[other.ID] = true
+ continue
}
- g.Economy.settle(other)
- settled[other.ID] = true
- paid--
+ // An unpaid sibling (the save failed, or the credit cap
+ // blocked re-application) stays owed. Re-anchor its
+ // checkpoints to the current durable balance so a later
+ // recovery re-applies it against a balance it can actually
+ // be paid from — without this, the balance a PAID sibling
+ // moved reads as this record's own durability and the
+ // liability is silently settled instead of paid.
+ other.CreditsBefore = p.Credits
+ other.CreditsAfter = p.Credits + other.Credit
+ settled[other.ID] = true // handled for the rest of this run
+ _ = g.Economy.append(*other)
}
case p.Credits == rec.CreditsAfter && len(group) == 1:
g.Economy.settle(rec)
@@ -439,7 +457,7 @@ func (g *Game) recoverEconomy() {
}
for k := len(openWagers) - 1; k >= 0; k-- {
rec := &folded[openWagers[k]].rec
- resolvedByPayout := hasLaterPayout(rec.Player, folded[openWagers[k]].seq)
+ resolvedByPayout, laterPayoutCredits := laterPayoutEvidence(rec.Player, folded[openWagers[k]].seq)
p := loadPlayer(rec.Player)
if p == nil {
continue
@@ -449,11 +467,12 @@ func (g *Game) recoverEconomy() {
creditsNow, chipsNow := p.Credits, countChips(p)
// Chips are recovered independently of the credit checkpoints. Any
// state where the chip debit is durable but the chips have not come
- // back (post-debit chip balance) still owes the chips, even when a
- // later payout record already resolves the credit side. A durable
- // chip refund always restores the balance to ChipsBefore, so this
- // cannot double-return.
- chipsOwed := rec.Chips > 0 && chipsNow == rec.ChipsAfter
+ // back (post-debit chip balance) still owes the chips — unless the
+ // resolving payout already returned the stake's full value in
+ // credits (a win, push, or refund includes the stake): returning
+ // the chips in kind on top would double-refund the round.
+ chipsOwed := rec.Chips > 0 && chipsNow == rec.ChipsAfter &&
+ (!resolvedByPayout || laterPayoutCredits < rec.Chips+rec.Credit)
if chipsOwed && !g.giveChips(p, rec.Chips) {
// No inventory room for the chip return: keep the record open
// for operator inspection rather than silently dropping the