games: the buy-in and the rake each player sees are their own, not the table's
The two-browser pass found it: at a table two humans share, the felt quoted each of them the pair's total. "Bought in for 200" to a player who put in 100, and a session-rake line that climbed on a pot the other one won. Both were table totals the view read straight off the engine — correct while a table had one human, wrong the moment it had two. Fixed along the border it already draws: bought_in is border accounting, so it comes from the viewer's own game_seats.staked; session rake is a within-table event, so it rides a new per-seat Seat.Paid beside the audit's table-total s.Paid. And top-up never grew game_seats.staked, so the storage invariant drifted by every top-up and the felt under-reported the buy-in — it does now. Claude-Session: https://claude.ai/code/session_013M5nD7PgUboJXoDcYHzpuJ
This commit is contained in:
@@ -187,7 +187,12 @@ func (s *State) payPot(pot Pot, live []ranked, evs *[]Event) {
|
||||
// climbed while every human folded would be telling them it had.
|
||||
for _, w := range winners {
|
||||
if !s.Seats[w.seat].Bot {
|
||||
// The table total (for the audit's delta) and the winner's own
|
||||
// running tally (for the ledger line the felt shows them). At a
|
||||
// table with two humans these are different numbers: each player is
|
||||
// only ever quoted the rake that came out of a pot they won.
|
||||
s.Paid += rake / int64(len(winners))
|
||||
s.Seats[w.seat].Paid += rake / int64(len(winners))
|
||||
}
|
||||
}
|
||||
*evs = append(*evs, Event{Kind: "rake", Seat: -1, Amount: rake})
|
||||
|
||||
@@ -101,6 +101,7 @@ type Seat struct {
|
||||
Bet int64 `json:"bet"` // put in on this street
|
||||
Total int64 `json:"total"` // put in across this hand
|
||||
Won int64 `json:"won"` // taken out of the pot this hand
|
||||
Paid int64 `json:"paid"` // rake lifted from pots this seat has won, all session
|
||||
State SeatState `json:"state"`
|
||||
Acted bool `json:"acted"` // has chosen to do something this street
|
||||
}
|
||||
|
||||
@@ -458,6 +458,40 @@ func TestYouOnlyPayRakeOnPotsYouWin(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// At a table with two humans the session-rake line each is shown is their own,
|
||||
// not the table's: the house took it out of a pot one of them won, and quoting it
|
||||
// to the other says the house has taken money off them that it has not. So the
|
||||
// per-seat tally only ever moves on the seat that won the pot.
|
||||
func TestRakeIsChargedToTheSeatThatWonIt(t *testing.T) {
|
||||
s := State{Tier: Tiers[1], Flopped: true,
|
||||
Seats: []Seat{{Name: "Reala"}, {Name: "Bob"}}}
|
||||
var evs []Event
|
||||
|
||||
// Reala wins a 400 pot. The rake on it (5%, 20) is Reala's to have paid.
|
||||
s.payPot(Pot{Amount: 400, Eligible: []int{0}}, []ranked{{seat: 0}}, &evs)
|
||||
if s.Seats[0].Paid != 20 {
|
||||
t.Errorf("the seat that won the pot paid %d in rake, want 20", s.Seats[0].Paid)
|
||||
}
|
||||
if s.Seats[1].Paid != 0 {
|
||||
t.Errorf("the other player was charged %d in rake for a pot they were not in", s.Seats[1].Paid)
|
||||
}
|
||||
if s.Paid != 20 {
|
||||
t.Errorf("the table total is %d, want 20 — it is still the sum for the audit", s.Paid)
|
||||
}
|
||||
|
||||
// Bob wins the next one. His tally moves; Reala's stays where it was.
|
||||
s.payPot(Pot{Amount: 200, Eligible: []int{1}}, []ranked{{seat: 1}}, &evs)
|
||||
if s.Seats[0].Paid != 20 {
|
||||
t.Errorf("Reala's tally moved on a pot Bob won: %d, want 20", s.Seats[0].Paid)
|
||||
}
|
||||
if s.Seats[1].Paid != 10 {
|
||||
t.Errorf("Bob paid %d in rake on a 200 pot he won, want 10", s.Seats[1].Paid)
|
||||
}
|
||||
if s.Paid != 30 {
|
||||
t.Errorf("the table total is %d, want 30", s.Paid)
|
||||
}
|
||||
}
|
||||
|
||||
func TestASplitPotSplits(t *testing.T) {
|
||||
s := State{Tier: Tiers[1], Seats: []Seat{{Name: "You"}, {Name: "A", Bot: true}}}
|
||||
var evs []Event
|
||||
|
||||
Reference in New Issue
Block a user