Every hand, deterministic.
Every chip, accounted for.
0
chip-conservation violations
every hand, every run — checked after every state transition
100,000
hands in the conservation soak
random rake, antes and micro-stacks, on every build, in about two seconds
2,598,960
five-card hands verified
every one, plus 12 million random hands against two independent oracles
43,000
bot hands per second
single-threaded, chips checked after every hand
The hand
One state machine.
Every game composed on it.
HandPhase
- NotStarted
- ForcedBets
- Dealing
- BringInstud
- Drawingdraw
- Betting
- RunOut
- Showdown
- Settled
- Voided
Dealing and betting repeat for every street; a hand that is voided at any point restores every stack and says so in its log. Rejections, phases and settlements are data — never exceptions.
Seven seams
- IDeckSpec
- 52 cards, or 36 for Short Deck
- IDealingSchedule
- streets, hole and community cards, burns, draw rounds
- IForcedBets
- blinds, antes, entry posts, straddles, bring-ins
- IBettingStructure
- no-limit, pot-limit, fixed-limit — raise ranges and caps
- IActionOrder
- who acts first, and next, on every street
- IShowdownRules
- high, low, Omaha's two-plus-three, show order, odd chip
- IDrawRules
- draw rounds, discards, stand pat, reshuffle
Games as compositions
Equity calculator
Side pots
Three all-ins.
Three pots, three winners.
- Side pots by contribution level, uncalled-bet return, configurable odd-chip rules and hi/lo split settlement.
- Each pot is contested only by the seats that paid into it, and settled on its own.
- Every seat's result is recorded at settlement: stack before and after, contributed, won, shown.
{
"event": "StreetCompleted",
"street": "Pre-flop",
"potTotal": 270,
"pots": [
{ "index": 0, "amount": 80,
"eligibleSeats": [0, 1, 2, 3], "level": 20 },
{ "index": 1, "amount": 90,
"eligibleSeats": [0, 2, 3], "level": 50 },
{ "index": 2, "amount": 100,
"eligibleSeats": [0, 3], "level": 100 }
],
"seq": 27,
"streetIndex": 0
}
What each seat put in
Board
Main pot
20 × 4 = 80
eligible: seats 0 · 1 · 2 · 3
Seat 1 wins with three twos
Side pot 1
30 × 3 = 90
eligible: seats 0 · 2 · 3
Seat 2 wins with a pair of aces
Side pot 2
50 × 2 = 100
eligible: seats 0 · 3
Seat 3 wins with a pair of kings
Seats 4 and 5 folded and paid nothing. The twos win only what they could cover, the aces take the middle pot, and the kings win back the top one — exactly what they put in.
Rake & house rules
Your rake.
Your rules.
- Basis points
- A percentage in hundredths — 500 is 5%.
- Cap
- A maximum per hand; zero leaves it uncapped.
- Cap by players dealt
- A different cap heads-up, short-handed and full.
- No flop, no drop
- No rake when the hand ends on the first street.
- Minimum pot
- Pots below it are not raked.
- Across side pots
- The rake split proportionally between the pots.
- A schedule
- Lines by game, structure, currency and stakes — the most specific line applies.
public long Compute(long potTotal, int playersDealt, bool reachedSecondStreet)
{
if (BasisPoints <= 0 || potTotal <= 0 || potTotal < MinimumPot)
{
return 0;
}
if (NoFlopNoDrop && !reachedSecondStreet)
{
return 0;
}
var rake = potTotal * BasisPoints / 10_000;
var cap = EffectiveCap(playersDealt);
if (cap > 0 && rake > cap)
{
rake = cap;
}
return Math.Max(0, Math.Min(rake, potTotal));
}
House rules, as settings
TDA and Robert's Rules defaults, big-blind and per-player antes, new-player posting, show and muck order, all-in run-out reveal and dead-button rules. Under-the-gun, button and Mississippi straddles, re-straddle chains and short-stack all-in straddles — or none, by house rule.
Straddle
Straddles
- None
- UnderTheGun
- Button
- Mississippi
MaxStraddles for re-straddle chains
AnteMode
Antes
- None
- PerPlayer
- BigBlindAnte
a short big blind posts the blind first
NewPlayerPosting
New players
- WaitForBigBlind
- PostBigBlind
- PostNothing
OddChip
Odd chips
- FirstLeftOfButton
- ToHigh
- ToBestSuitedHigh
RunItMultiple
Running it more than once
- Never
- ByUnanimousConsent
- Always
RunItTimes: 2 or 3
FixedLimitRaiseCap
Limit games
- 4 bets a street
a bet and three raises; HeadsUpUnlimitedRaises lifts the cap heads-up
Commit-reveal shuffle
Committed before the deal.
Verifiable after it.
- 1
Seed
32 bytes from a cryptographic random number generator, and a 16-byte nonce.
- 2
Commit
SHA-256 of seed and nonce is fixed before a single card is dealt, tagged with the algorithm, PEP-SHUFFLE-1.
- 3
Shuffle
Fisher–Yates, driven by an HMAC-SHA256 counter stream keyed by the seed; rejection sampling leaves no modulo bias.
- 4
Deal
The hand is dealt from that order. Changing a card would break the commitment.
- 5
Reveal
After settlement — never before — the seed and nonce are revealed.
- 6
Verify
Anyone recomputes the hash, compared in constant time, and the deck order, card by card.
public const string AlgorithmId = "PEP-SHUFFLE-1";
public static ShuffleCommitment Commit(ReadOnlySpan<byte> seed, ReadOnlySpan<byte> nonce)
{
Span<byte> material = stackalloc byte[SeedBytes + NonceBytes];
seed.CopyTo(material);
nonce.CopyTo(material[SeedBytes..]);
var hash = SHA256.HashData(material);
return new ShuffleCommitment(AlgorithmId, ImmutableArray.Create(hash));
}
internal static void FisherYates(List<Card> list, int from, HmacStream stream)
{
for (var i = list.Count - 1; i > from; i--)
{
var j = from + stream.NextInt(i - from + 1);
(list[i], list[j]) = (list[j], list[i]);
}
}
The committed shuffle at every table
Lab-certified RNG
Evaluator & bots
Proven on every hand.
Then on millions more.
12M
random hands against two oracles
10 million seven-card hands against the legacy evaluator, 2 million against the reference — identical keys
50,000
Omaha deals against the best five of nine
an Omaha hand is never better than the best five of its nine cards
The production evaluator counts ranks and suits instead of enumerating five-card subsets, and allocates nothing. It ranks by profile, so Short Deck's order — a flush over a full house — is a table, not a second evaluator; Omaha's two-from-the-hand, three-from-the-board rule is a constraint on the same code.
Bots for development and play-money tables
Rule-based bots that play every variant, a 43,000 hands/s runner for soak testing, and table fillers locked to play money.
- SmartBot
- RandomBot
- AlwaysCallBot
- AlwaysFoldBot
- AlwaysRaiseBot
- AlwaysAllInBot
43,000
bot hands per second
single-threaded, chips checked after every hand
$ dotnet run --project engine/Poker.Engine.Bots -c Release \
-- --hands 100000 --reset-stacks
Running 100,000 hands of No-Limit Hold'em with 6 bots (seed 1, stack 200)...
Hands: 100,000 Showdowns: 67,972 Actions: 1,519,237 Rake: 0
Elapsed: 2.28s (43,908 hands/s)
seat 0 Smart#0 net 845,359 rebuys 0 hands won 19,645
seat 1 Random#1 net -444,323 rebuys 0 hands won 15,788
seat 2 Random#2 net -366,518 rebuys 0 hands won 16,010
seat 3 Smart#3 net 764,085 rebuys 0 hands won 19,614
seat 4 Random#4 net -372,877 rebuys 0 hands won 16,037
seat 5 Random#5 net -425,726 rebuys 0 hands won 15,600
Chip conservation held for every hand.
Replay & hand histories
Replayed to the decision.
Exported, ready to read.
1,500
random hands replayed at every decision
the replay equals the live machine at each decision and at settlement
14
golden hands, byte for byte
side pots, straddles, odd chips, limit caps, Omaha and run it twice
// the full log
{
"event": "CardsDealt",
"seat": 3,
"cards": [{ "card": "Kc", "faceUp": false }],
"seq": 5,
"streetIndex": 0
}
// what a spectator receives
{
"event": "HiddenCardsDealt",
"seat": 3,
"count": 1,
"seq": 5,
"streetIndex": 0
}
PokerEnginePro Hand #1: Hold'em No Limit (1/2 PLAY) - 2026/09/23 12:00:00 UTC
Table 'Golden table' 6-max Seat #1 is the button
// … six seats, the blinds, seat 3's hole cards
Player3: raises to 100 and is all-in
Player4: folds
Player5: folds
Player0: calls 100 and is all-in
Player1: calls 20 and is all-in
Player2: calls 50 and is all-in
// … the hands, shown for the all-in run-out
*** FLOP *** [Qh Jd 5s]
*** TURN *** [Qh Jd 5s 2s]
*** RIVER *** [Qh Jd 5s 2s 6d]
*** SHOW DOWN ***
Player3: shows [Kc Kd] (Pair [King King Queen Jack Six])
Player0: shows [9s Ts] (HighCard [Queen Jack Ten Nine Six])
Player1: shows [2c 2d] (ThreeOfAKind [Two Two Two Queen Jack])
Player2: shows [Ac Ad] (Pair [Ace Ace Queen Jack Six])
Player1 collected 80 from pot
Player2 collected 90 from side pot-1
Player3 collected 100 from side pot-2
*** SUMMARY ***
Total pot 270 | Rake 0
Board [Qh Jd 5s 2s 6d]
Hand histories players already know
A replayer that cannot disagree
Read the log.
Then replay it yourself.
A walkthrough of the engine with its golden hands, its soak and its API reference — every number on this page, run in front of you.