feat(tournament): scheduled events with prize pools

Entry fees collect into a real ledger account rather than a number in a
row, so tournament money obeys the same double-entry invariants as
everything else and every movement is explained by a posting.

Settlement distributes the entire pool: dividing a pool across percentage
shares leaves a remainder, and dropping it would destroy money and break
conservation, so it goes to first place. Settlement claims the tournament
before paying, so two instances cannot both pay out. Cancellation refunds
every entrant and asserts the pool empties exactly.

18 tests including concurrent entry, concurrent settlement, unfunded
entry taking no seat, and books balancing after payout.

Removes an append-only trigger that had been over-applied to entry rows.
An entry is a seat reservation, not a financial record: a seat claimed
but unpaid must be releasable so the player can retry once funded. The
money side stays immutable because it is a ledger posting.

The journey test now derives the expected payout from the published fee
schedule instead of hardcoding it, so it keeps checking something real if
the rake changes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
drjones
2026-08-05 23:54:18 +00:00
parent ca39e8bad9
commit 8af6fd585e
8 changed files with 1155 additions and 18 deletions

View File

@@ -327,6 +327,10 @@ func TestCannotCashOutAfterTheCrash(t *testing.T) {
func TestCashedOutPlayerIsPaid(t *testing.T) {
f := newFixture(t)
// Isolate the payout arithmetic from the fee schedule, which is covered
// by its own tests. Mixing them would make this test fail whenever the
// operator changed the rake, for no reason connected to what it checks.
f.room.Fees = fees.NoFees()
id, pk := f.player("a", 100_000)
house, err := f.ledger.AccountByName(f.ctx, "house_pot")
if err != nil {
@@ -764,6 +768,9 @@ func TestAutoCashOutTargetMustExceedOne(t *testing.T) {
// An auto cash-out must pay the target exactly, not the tick's multiplier.
func TestAutoCashOutPaysTheTargetExactly(t *testing.T) {
f := newFixture(t)
// The claim under test is that the target pays exactly, not what the
// operator deducts afterwards.
f.room.Fees = fees.NoFees()
house, _ := f.ledger.AccountByName(f.ctx, "house_pot")
hp, _ := f.player("housefund", 5_000_000)
if _, err := f.ledger.Transfer(f.ctx, hp, house, 5_000_000); err != nil {