feat: auto cash-out targets, 1% house edge, terminal aesthetic

Auto cash-out closes a position at exactly the chosen target rather than
the next tick's multiplier, and fires whenever the target is at or below
the crash point. This is the feature that makes the game playable over a
network, where manual timing is at the mercy of latency.

House edge drops from 2% to 1% across crash and scratch. Scratch prize
tables retuned so the published 99% RTP is exact.

Adds docs/API.md: the client uses no private endpoints, so anyone can
write a bot against the same API.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
drjones
2026-08-05 16:24:31 +00:00
parent dee3becd47
commit 48a9120fe4
14 changed files with 997 additions and 266 deletions

View File

@@ -189,11 +189,11 @@ var Catalog = []Ticket{
Blurb: "Nine cells. Match three. Frequent small wins, modest top prize.",
Cells: 9,
// Weights are out of 1,000,000 and sum to it exactly. The weighted
// payout sums to 9.8e9, which is an RTP of exactly 98%.
// payout sums to 9.9e9, which is an RTP of exactly 99%.
Tiers: []Tier{
{Name: "No win", Weight: 463_400, PayoutBP: 0},
{Name: "No win", Weight: 458_400, PayoutBP: 0},
{Name: "Stake back", Weight: 350_000, PayoutBP: 10_000},
{Name: "Double", Weight: 150_000, PayoutBP: 20_000},
{Name: "Double", Weight: 155_000, PayoutBP: 20_000},
{Name: "Five times", Weight: 30_000, PayoutBP: 50_000},
{Name: "Twenty times", Weight: 6_000, PayoutBP: 200_000},
{Name: "Nebula jackpot", Weight: 600, PayoutBP: 1_000_000},
@@ -204,13 +204,13 @@ var Catalog = []Ticket{
Name: "Singularity",
Blurb: "Six cells. Rarely pays, but the top prize is five hundred times.",
Cells: 6,
// Same 98% RTP as Nebula Nine, but concentrated in the rare tiers:
// Same 99% RTP as Nebula Nine, but concentrated in the rare tiers:
// you lose far more often, and the top prize is 500x.
Tiers: []Tier{
{Name: "No win", Weight: 866_530, PayoutBP: 0},
{Name: "No win", Weight: 865_530, PayoutBP: 0},
{Name: "Stake back", Weight: 60_000, PayoutBP: 10_000},
{Name: "Triple", Weight: 45_000, PayoutBP: 30_000},
{Name: "Ten times", Weight: 25_000, PayoutBP: 100_000},
{Name: "Ten times", Weight: 26_000, PayoutBP: 100_000},
{Name: "Hundred times", Weight: 3_000, PayoutBP: 1_000_000},
{Name: "Singularity", Weight: 470, PayoutBP: 5_000_000},
},

View File

@@ -20,8 +20,8 @@ func TestCatalogIsCoherent(t *testing.T) {
func TestPublishedRTPIsHonest(t *testing.T) {
for _, ticket := range scratch.Catalog {
rtp := ticket.RTPBasisPoints()
if rtp != 9800 {
t.Errorf("%s: RTP = %d bp, want 9800", ticket.ID, rtp)
if rtp != 9900 {
t.Errorf("%s: RTP = %d bp, want 9900", ticket.ID, rtp)
}
}
}