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", "pw", false) 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", "pw", false) 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) } }