aboutsummaryrefslogtreecommitdiff
path: root/internal/game/casino.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/game/casino.go')
-rw-r--r--internal/game/casino.go59
1 files changed, 54 insertions, 5 deletions
diff --git a/internal/game/casino.go b/internal/game/casino.go
index 79d324a..133eb34 100644
--- a/internal/game/casino.go
+++ b/internal/game/casino.go
@@ -70,21 +70,32 @@ func (w casinoWallet) Wager(amount int) (casino.Wager, error) {
// The record stays open as an outstanding stake until the round
// resolves; recovery refunds open stakes after a crash.
w.g.Economy.registerStake(rec)
- return casino.Wager{Total: amount, Chips: chips, Credits: credits}, nil
+ return casino.Wager{Total: amount, Chips: chips, Credits: credits, ID: rec.ID}, nil
}
// PayCredits applies a payout durably. The journal record is written first;
// if the save fails, the record stays open and startup recovery re-pays it
// (the player is owed the settlement).
func (w casinoWallet) PayCredits(amount int) error {
- return w.payCreditsFlagged(amount, false)
+ return w.payCreditsFlagged(amount, false, nil)
+}
+
+// PayStakeCover applies a settlement that INCLUDES the return of the listed
+// stake wager IDs (a table hand pays its stake back inside the payout). The
+// record is journaled with that linkage, so a shutdown refund never returns
+// those stakes a second time; recovery re-applies the owed payout instead.
+func (w casinoWallet) PayStakeCover(amount int, stakeIDs []string) error {
+ return w.payCreditsFlagged(amount, false, stakeIDs)
}
// payCreditsFlagged journals and applies a credit payout; a settlement
// payout (refund=false) proves the round resolved during recovery, while a
// refund payout (refund=true) marks a stake RETURNED at shutdown/eviction
// (its chip part returns in chips, not through this credit payout).
-func (w casinoWallet) payCreditsFlagged(amount int, refund bool) error {
+// stakeIDs, when non-empty, records WHICH open stakes this settlement
+// returns (table hands); empty means the payout resolves the round without
+// a linked stake return (slots winnings).
+func (w casinoWallet) payCreditsFlagged(amount int, refund bool, stakeIDs []string) error {
if amount <= 0 {
return nil
}
@@ -96,7 +107,7 @@ func (w casinoWallet) payCreditsFlagged(amount int, refund bool) error {
// With the cap blocking, the balance is unchanged and the open record
// keeps the owed amount durable; startup recovery re-applies it once
// the balance has room below the cap.
- rec, err := w.g.Economy.startKind("payout", refund, w.p, 0, amount,
+ rec, err := w.g.Economy.startKind("payout", refund, stakeIDs, w.p, 0, amount,
creditsBefore, creditsAfter, chipsBefore, chipsBefore)
if err != nil {
// The journal is unavailable (persistent append failure). Last
@@ -168,6 +179,15 @@ func (w casinoWallet) payCreditsFlagged(amount int, refund bool) error {
// honors the resolution instead of refunding an already-resolved round.
func (w casinoWallet) ResolveStakes() {
unresolved := w.g.Economy.ResolveStakes(economyKey(w.p))
+ w.recordResolutionEvidence(unresolved)
+}
+
+// recordResolutionEvidence persists file-side resolution evidence for stake
+// records whose settled markers could not be journaled. The character file
+// then proves the round resolved even though the journal could not record
+// its marker, so recovery still honors the resolution instead of refunding
+// an already-resolved round.
+func (w casinoWallet) recordResolutionEvidence(unresolved []*economyRecord) {
if len(unresolved) == 0 {
return
}
@@ -186,6 +206,18 @@ func (w casinoWallet) ResolveStakes() {
}
}
+// ResolveStake settles ONE outstanding stake by wallet handle: the hand it
+// belongs to was paid (its return is inside the settlement) or lost, so it
+// must never be refunded. Unresolvable markers fall back to the same file-
+// side evidence the bulk ResolveStakes uses.
+func (w casinoWallet) ResolveStake(stakeID string) {
+ if stakeID == "" {
+ return
+ }
+ unresolved := w.g.Economy.ResolveStakeByID(economyKey(w.p), stakeID)
+ w.recordResolutionEvidence(unresolved)
+}
+
// recordStakeEvidence adds every currently open stake of the player to the
// file-side resolution evidence list and returns how many IDs were newly
// recorded. Used when the economy journal is unavailable: the open wager
@@ -207,8 +239,25 @@ func (w casinoWallet) recordStakeEvidence() int {
// come back as chips, credits through the durable payout path. The stakes
// are resolved only once the return is durable; on failure the chip return
// is rolled back out of memory and the open records drive recovery instead.
+//
+// Stakes already accounted for by an open settlement payout are NOT
+// refunded: a journaled table settlement includes its hand's stake return
+// and recovery re-applies that owed payout, so refunding the stake here
+// would pay the round twice (the returned credits then arrive on top of
+// the stake). The stakes are resolved; their money arrives with the
+// re-applied settlement.
func (w casinoWallet) RefundOutstanding() (chips, credits int, err error) {
for _, rec := range w.g.Economy.openStakes(economyKey(w.p)) {
+ if resolvedStakeRecorded(w.p, rec.ID) {
+ // Already resolved through file-side evidence: recovery
+ // settles it, never refunds it.
+ continue
+ }
+ if w.g.Economy.stakeCovered(rec) {
+ // The covering settlement is journaled and owed: resolving
+ // the stake defers its return to the payout re-application.
+ continue
+ }
chips += rec.Chips
credits += rec.Credit
}
@@ -227,7 +276,7 @@ func (w casinoWallet) RefundOutstanding() (chips, credits int, err error) {
}
}
if credits > 0 {
- if err := w.payCreditsFlagged(credits, true); err != nil {
+ if err := w.payCreditsFlagged(credits, true, nil); err != nil {
if chips > 0 {
// Roll the chip return back out of memory: the durable
// refund will re-apply both amounts from the open records.