aboutsummaryrefslogtreecommitdiff
path: root/internal/casino/money_regression_test.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-09-16 14:09:22 -0400
committerhistoria <[not public]>2026-09-16 14:09:22 -0400
commit2f34e75f27e5f99cfc1c5f8eaf6ab4ff2fddb1d9 (patch)
treec50388fe639286bda1b1999ca6c2c972d03eb3a5 /internal/casino/money_regression_test.go
parentc054b27f628f23fecc505b9e09e78721738f3c4e (diff)
downloadthehouseoficarus-2f34e75f27e5f99cfc1c5f8eaf6ab4ff2fddb1d9.tar.gz
fix: reserved-loot pickup, credit pickup, payout recovery, and slot stake/bonus shutdown handling
Diffstat (limited to 'internal/casino/money_regression_test.go')
-rw-r--r--internal/casino/money_regression_test.go72
1 files changed, 72 insertions, 0 deletions
diff --git a/internal/casino/money_regression_test.go b/internal/casino/money_regression_test.go
index 43bf505..e6b3ef8 100644
--- a/internal/casino/money_regression_test.go
+++ b/internal/casino/money_regression_test.go
@@ -175,3 +175,75 @@ func TestShoeReshufflesOnlyAtRoundBoundaries(t *testing.T) {
t.Fatalf("shuffleIfDue did not reshuffle at the boundary (%d cards)", got)
}
}
+
+// TestSlotsLeaveAfterFailedPayoutKeepsStakeOpen: a spin whose payout could
+// not be persisted leaves its stake record OPEN as recovery evidence for
+// the owed settlement. Standing up afterwards must not resolve (forfeit)
+// that stake — doing so would erase the refund evidence and silently drop
+// the owed win.
+func TestSlotsLeaveAfterFailedPayoutKeepsStakeOpen(t *testing.T) {
+ machine := newMachine(1, MachineConfig{ID: "slots", Game: GameSlots, SpinTicks: 1}, newTestRand())
+ wallet := &failingPayoutWallet{testWallet{credits: 100}}
+ machine.join("alice")
+ machine.setBet("alice", 10, wallet)
+ machine.spin = &SlotSpin{Payout: 20}
+ for i := 0; i < 20 && machine.phase != machineReady; i++ {
+ machine.tick()
+ }
+ if len(wallet.open) != 1 {
+ t.Fatal("setup: a failed payout must leave the stake open")
+ }
+ machine.leave("alice")
+ if len(wallet.open) != 1 {
+ t.Errorf("leaving after a failed payout erased the outstanding stake; want it preserved for recovery/refund")
+ }
+}
+
+// TestSlotsShutdownDuringBonusRefundsStake: free spins keep the triggering
+// stake OUTSTANDING until the bonus completes. A graceful shutdown mid-feature
+// therefore refunds the stake; the remaining feature EV is forfeit, but the
+// player never loses the wager they paid for the round.
+func TestSlotsShutdownDuringBonusRefundsStake(t *testing.T) {
+ machine := newMachine(1, MachineConfig{ID: "slots", Game: GameSlots, SpinTicks: 1}, newTestRand())
+ wallet := &testWallet{credits: 100}
+ machine.join("alice")
+ machine.setBet("alice", 10, wallet)
+ machine.spin = &SlotSpin{ScatterCount: 3} // deterministic bonus trigger
+ for i := 0; i < 20 && !machine.inFreeSpins; i++ {
+ machine.tick()
+ }
+ if !machine.inFreeSpins || machine.freeSpins != 7 {
+ t.Fatalf("setup: expected the first of eight free spins (inFreeSpins=%v freeSpins=%d)", machine.inFreeSpins, machine.freeSpins)
+ }
+ machine.refundAll()
+ if got := wallet.credits + wallet.paid; got != 100 {
+ t.Errorf("shutdown during the bonus left %d of the 100-credit balance; want the stake refunded", got)
+ }
+}
+
+// TestSlotsStakeResolvesOnlyAfterFullRound: without any failure and without
+// a feature, a completed spin resolves its stake (no shutdown refund), and a
+// bonus that RUNS TO COMPLETION also resolves the stake at the end.
+func TestSlotsStakeResolvesOnlyAfterFullRound(t *testing.T) {
+ machine := newMachine(1, MachineConfig{ID: "slots", Game: GameSlots, SpinTicks: 1}, newTestRand())
+ wallet := &testWallet{credits: 100}
+ machine.join("alice")
+ machine.setBet("alice", 10, wallet)
+ machine.spin = &SlotSpin{ScatterCount: 3} // trigger, then run all free spins
+ for i := 0; i < 200 && (machine.phase != machineReady || machine.inFreeSpins); i++ {
+ machine.tick()
+ }
+ if machine.inFreeSpins || machine.freeSpins != 0 {
+ t.Fatalf("setup: bonus must have completed (inFreeSpins=%v freeSpins=%d)", machine.inFreeSpins, machine.freeSpins)
+ }
+ if len(wallet.open) != 0 {
+ t.Fatalf("stake still outstanding after the feature completed: %+v", wallet.open)
+ }
+ // A completed round has nothing outstanding: the shutdown refund must
+ // not pay anything extra.
+ paidBefore := wallet.paid
+ machine.refundAll()
+ if wallet.paid != paidBefore {
+ t.Errorf("refundAll double-paid a completed round: paid %d -> %d", paidBefore, wallet.paid)
+ }
+}