1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
|
package game
// Disconnect-forfeit regressions: a player who disconnects mid-round
// forfeits the committed stake, so startup recovery must NOT refund it; a
// stake left outstanding by a genuine crash still is.
import (
"testing"
"thehouseoficarus/internal/casino"
)
// TestDisconnectForfeitsMachineStake: disconnecting mid-spin forfeits the
// stake (the machine frees the seat immediately); recovery must not refund
// it on the next startup.
func TestDisconnectForfeitsMachineStake(t *testing.T) {
h := newHarness(t)
h.seedAccount("alice")
p := h.seedCharacter("alice", "alice")
h.g.RunOnOwner(func() {
p.Credits = 100
h.g.Casino.JoinMachine(1, casino.MachineConfig{ID: "slots", Game: casino.GameSlots, SpinTicks: 50}, "alice")
h.g.Casino.SetMachineBet(1, "slots", "alice", 10, casinoWallet{g: h.g, p: p})
// The disconnect path forfeits the in-flight spin.
h.g.Casino.MarkDisconnected("alice")
})
h.barrier()
restarted := harnessFromDir(t, h.dir)
loaded, err := restarted.g.AccountStore.LoadCharacter("alice")
if err != nil {
t.Fatal(err)
}
if loaded.Credits != 90 {
t.Fatalf("credits after forfeited spin = %d, want 90 (disconnect forfeits)", loaded.Credits)
}
}
// TestDisconnectForfeitsBlackjackStake: same policy at a table: the seat
// frees up and the stake is not refunded by recovery.
func TestDisconnectForfeitsBlackjackStake(t *testing.T) {
h := newHarness(t)
h.seedAccount("alice")
p := h.seedCharacter("alice", "alice")
// A natural opening hand (player or dealer blackjack) settles the round
// inside Bet() itself, before any disconnect can forfeit anything. Deal
// from deterministic seeds until the opening hand leaves the stake
// outstanding, so the regression exercises the mid-round forfeit path
// on every run instead of failing ~5% of the time on a lucky deal.
committed := false
for seed := 0; seed < 1000 && !committed; seed++ {
h.g.RunOnOwner(func() {
h.g.Casino = casino.NewManager(int64(seed))
p.Credits = 100
h.g.Casino.Join(1, casino.TableConfig{ID: "bj", Game: casino.GameBlackjack, MaxPlayers: 2}, "alice")
h.g.Casino.Bet(1, "bj", "alice", "", 10, casinoWallet{g: h.g, p: p})
committed = len(h.g.Economy.openStakes("alice")) == 1
})
h.barrier()
}
if !committed {
t.Fatal("no manager seed left the blackjack stake outstanding; deal or settle logic changed")
}
h.g.RunOnOwner(func() {
// The disconnect path forfeits the in-flight hand.
h.g.Casino.MarkDisconnected("alice")
})
h.barrier()
restarted := harnessFromDir(t, h.dir)
loaded, err := restarted.g.AccountStore.LoadCharacter("alice")
if err != nil {
t.Fatal(err)
}
if loaded.Credits != 90 {
t.Fatalf("credits after forfeited hand = %d, want 90 (disconnect forfeits)", loaded.Credits)
}
}
|