aboutsummaryrefslogtreecommitdiff
path: root/internal/game/casino.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-09-16 19:57:45 -0400
committerhistoria <[not public]>2026-09-16 19:57:45 -0400
commit6ef40a5739f1723ec26cc32dfc48fe764f776dca (patch)
treee215a4dc96ea639bcea07bb589aae3e31c3609bb /internal/game/casino.go
parent1dad15698845d370e998f7cc5feb8aece3fab58f (diff)
downloadthehouseoficarus-6ef40a5739f1723ec26cc32dfc48fe764f776dca.tar.gz
fix: durable payouts survive journal failure, cap bank withdrawals, hash passwords off the owner loop.
Diffstat (limited to 'internal/game/casino.go')
-rw-r--r--internal/game/casino.go46
1 files changed, 43 insertions, 3 deletions
diff --git a/internal/game/casino.go b/internal/game/casino.go
index 9af9156..f9eb771 100644
--- a/internal/game/casino.go
+++ b/internal/game/casino.go
@@ -99,7 +99,28 @@ func (w casinoWallet) payCreditsFlagged(amount int, refund bool) error {
rec, err := w.g.Economy.startKind("payout", refund, w.p, 0, amount,
creditsBefore, creditsAfter, chipsBefore, chipsBefore)
if err != nil {
- return casino.ErrWalletPersistence
+ // The journal is unavailable (persistent append failure). Last
+ // resort: pay through the character file only. The save below is
+ // the commit point — the money is durable even without a journal
+ // record, so the settlement may be announced as paid. The stake
+ // resolution evidence (ResolveStakes) keeps recovery from treating
+ // the round as unresolved; without a payout record, an open wager
+ // can no longer be distinguished from a lost one, so the balance
+ // itself is the only record of what happened.
+ if capErr != nil {
+ return capErr
+ }
+ w.p.Credits = creditsAfter
+ if serr := w.g.AccountStore.SaveCharacter(w.p); serr != nil {
+ w.p.Credits = creditsBefore
+ return casino.ErrWalletPersistence
+ }
+ kind := "settlement"
+ if refund {
+ kind = "refund"
+ }
+ fmt.Fprintf(os.Stderr, "economy: %s payout of %d credits for %s could not be journaled; the character file is the only durable record\n", kind, amount, w.p.Name)
+ return nil
}
if capErr != nil {
return capErr
@@ -129,9 +150,28 @@ func (w casinoWallet) payCreditsFlagged(amount int, refund bool) error {
// ResolveStakes marks every outstanding stake for the player settled: the
// round resolved (won, lost, pushed, or forfeited), so startup recovery
-// must no longer refund it.
+// must no longer refund it. When the journal cannot persist the settled
+// markers, the record IDs are recorded in the character file as durable
+// resolution evidence (player.Player.ResolvedStakes) so recovery still
+// honors the resolution instead of refunding an already-resolved round.
func (w casinoWallet) ResolveStakes() {
- w.g.Economy.ResolveStakes(economyKey(w.p))
+ unresolved := w.g.Economy.ResolveStakes(economyKey(w.p))
+ if len(unresolved) == 0 {
+ return
+ }
+ changed := false
+ for _, rec := range unresolved {
+ if !resolvedStakeRecorded(w.p, rec.ID) {
+ w.p.ResolvedStakes = append(w.p.ResolvedStakes, rec.ID)
+ changed = true
+ }
+ }
+ if !changed {
+ return
+ }
+ if err := w.g.AccountStore.SaveCharacter(w.p); err != nil {
+ fmt.Fprintf(os.Stderr, "casino resolve stakes: evidence save for %s: %v\n", w.p.Name, err)
+ }
}
// RefundOutstanding returns every outstanding stake to the player: chips