The tool opens to Postflop Study mode by default, remembers your last mode between visits, and honors ?view= and ?game= deep links from the site. One app serves both games: a Game row (PLO / NLHE pills) sits at the top of the left pane, and switching is instant and in place, with practice accuracy tracked separately per game. This guide describes the PLO side; The NLHE Library covers what differs for NLHE. Below the Game row, set the table format with the controls described next.
Players
Two game formats are available, toggled with HU and 6max pill buttons:
- HU - Heads-up (2 players). Positions are OOP (out of position) and IP (in position).
- 6max - 6-max (up to 6 players). Positions are UTG, HJ, CO, BTN, SB, and BB.
Stack Sizes
Below the player toggle, a row of pill buttons lets you select the effective stack depth in big blinds. Available sizes depend on the format:
- HU: 100bb
- 6max: 12bb, 20bb, 30bb, 40bb, 50bb, 100bb, 150bb, 200bb
Each stack size is a separate solver solution. Switching stacks preserves your current board and navigation path when possible - if certain actions don't exist at the new stack depth, the tool navigates as far as it can and notifies you.
Those are the PLO ladders. NLHE is 6-max only, at eight depths of its own (20bb through 200bb, with 70bb in place of 12bb) - see The NLHE Library.
Seats (6max)
When 6max is selected in Postflop Study, a Seats row of six pills (UTG, HJ, CO, BTN, SB, BB) appears below the stack selector. Pick exactly two; clicking a third swaps out the seat you picked first. The pair resolves to one of the 15 two-seat postflop matchups (BBvsBTN, COvsBTN, and so on) and determines which preflop lines are available. Postflop Practice uses its own Hero and Opponent seat rows instead, and the preflop modes pick position in their own left panes.
Browse GTO preflop ranges for every position, line, and stack depth. See exactly how often the solver opens, 3-bets, calls, or folds with every one of the 16,432 canonical PLO hands.
Left Pane: Configuration
Preflop Action - A row of toggle buttons selects which preflop decision you're studying:
- RFI - Raise first in (open-raising). The first aggressive action preflop.
- vs RFI - Facing an open raise (the 3-bet decision).
- vs 3-Bet - The original raiser facing a 3-bet (the 4-bet decision).
- vs 4-Bet - Facing a 4-bet (the 5-bet decision).
- vs 5-Bet and vs 6-Bet - Deeper re-raise levels (when available).
- vs Limp, vs Limp-Raise, etc. - Limp-related lines (when available for the solve).
Opener / Opponent - When studying lines beyond RFI, a second row of position buttons appears:
- For vs RFI, this is labeled Opener and lets you pick which position opened (e.g., UTG, HJ, CO, BTN).
- For deeper lines (vs 3-Bet, vs 4-Bet, etc.), this is labeled Opponent and selects which position you're facing.
Together, Preflop Action + Opener/Opponent uniquely identifies the decision point. The center pane then shows each position's strategy for that exact scenario.
Center Pane: Strategy Table
The center pane shows a poker table visualization and a strategy table for the selected preflop line.
Poker Table - A compact top-down view with a seat box per position. Seats sit in fixed places (no hero rotation); the position you're studying is highlighted, and stacks show for the players in the scenario. Click any position that has a decision in this line to switch to its strategy.
Raise buttons navigate. An action in the line's action row that leads to a published spot renders as a button with a › mark: click it and the tool walks to the decision that action creates (BTN's open, for example, becomes the BB's vs-RFI spot), with a tooltip naming the destination. Fold and call never navigate, and actions with no published target stay inert.
Strategy Table - The main data display below the table. Hands are organized into collapsible meta-groups (Aces, High Pairs, Double Paired, Medium Pairs, Low Pairs, Rundowns, Gapped, Broadway, Ace-High, Disconnected, Trash). Each meta-group expands to show its sub-categories.
For each category row, you see:
- Category name - e.g., "AA + Broadway", "High Rundown"
- Hands - The category's reach percentage: its combo-weighted share of all 270,725 dealt combos that arrive at this decision point
- Action Mix bar - Colored segments showing raise, call, and fold frequencies combined. The raise label (e.g., "Open", "3-Bet", "4-Bet") changes based on the line.
Table Controls:
- Sortable columns - Click the column headers (Starting Hand Categories, Hands, Action Mix) to sort ascending or descending
- Weighted toggle - On by default: bar widths reflect range-weighted frequencies. Turn it off to see each category's preferred-action mix at full width.
- Click a category to filter the hand catalog in the right pane to only hands in that category
- Click an action segment within a category row to further filter by that specific action
Under the category table, DS / SS / R / M / 3F chips (hover for the full suit-pattern names) show the suitedness composition of whatever is currently filtered.
Right Pane: Hand Catalog
The right pane shows the Hand Catalog - a browsable list of all 16,432 canonical PLO hands with their preflop frequencies.
Tag Filters - A row of clickable tag pills at the top filters hands by properties:
- Hand type tags: broadway, connected, rundown, paired, etc.
- Suitedness tags: double-suited (DS), single-suited (SS), rainbow, monotone, 3-flush
Tags are toggleable - click to activate, click again to deactivate. Multiple tags combine as AND filters.
Search / PPT Filter - A text input for filtering hands using PPT syntax or plain text search (e.g., typing "AA" shows all hands containing pocket aces).
Hand List - Each row shows:
- Hand - The four cards with suit symbols
- Combos - A pill with the suit-isomorphic multiplicity (1, 4, 6, 12, or 24)
- Freq - The frequency of the displayed action, color-stepped from green (90%+) through yellow and orange down to red
- Action - The hand's highest-frequency action (or the action you filtered by), plus its Category
The list is flat, sorted by frequency, caps at 500 rows, and updates in real time as you change position, line, tags, or filters.
Test your preflop knowledge with randomized quizzes. The tool deals you a hand, shows you the situation, and you pick the correct action.
Left Pane: Filters
Preflop Action - Checkboxes to select which lines to practice: RFI (open-raising), vs 3-Bet (the 4-bet decision), vs 4-Bet, and deeper re-raise levels where the solve has them (vs 5-Bet, vs 6-Bet). Facing-an-open and limp lines are browsable in Preflop Study but aren't drilled here.
Check multiple lines to practice a mix of scenarios. At least one must be selected.
Hero Position - Checkboxes for which positions you want to be dealt as. Options depend on the selected lines (e.g., UTG, HJ, CO, BTN, SB, BB). Check all for maximum variety, or focus on specific positions.
Timer - A slider from 5 to 60 seconds. Sets the time limit for each question. Changing the slider only affects the next dealt hand. If time runs out, the hand is marked incorrect and the answer is revealed.
New Spot - Deals a fresh random hand. The button reads "Dealing..." while the next spot is being prepared.
Session - Running totals for your current session:
- Correct - Number of correct answers
- Total - Total hands answered
- Accuracy - Percentage correct
- Streak - Current consecutive correct answers (resets to 0 on any wrong answer or timeout)
- Reset Stats - Clears the session counters (appears after at least one answer)
Center Pane: Quiz
The center pane shows the Quiz Panel - a poker table with your hand and action buttons.
Poker Table - Same top-down table visualization as in study mode, showing all seat positions with stack sizes, blind and raise chip stacks, the pot on the felt, and your seat highlighted at the bottom.
Hole Cards - Your four PLO cards displayed prominently below the table. Cards use the 4-color deck.
Timer Bar - A horizontal bar below the hole cards that shrinks as time passes. Color changes from green to yellow to red as time runs low.
Action Path - A text line above the action buttons showing the current line and any actions that have already occurred.
Action Buttons - The available actions, ordered from passive to aggressive:
- Fold - Always available when facing a raise
- Call - Call the current bet (shows the call amount)
- Raise / 3-Bet / 4-Bet - One or more raise sizes may be available
- All-In - When available
- Mix - Indicates the hand is mixed (see below)
Number keys 1-9 correspond to the action buttons from left to right.
After Answering - A result modal appears with "Correct!" (green), "Wrong" (red), or "Time's Up!" on a timeout, plus frequency bars showing every action's solver frequency, a line showing what the hand's whole class does at this node, and a Continue button (also triggered by Space or Enter). Dismiss the modal to study the table behind it; a floating Next Hand button keeps the session moving.
The Mix Button
PLO hands are often played with mixed strategies - the solver doesn't always take a single pure action. The Mix button is your answer when you believe no single action dominates.
A hand counts as "mixed" when no single action clears 75% frequency. Press Mix on a truly mixed hand and you're correct; press it when one action does clear 75% and you're wrong.
Mixed hands are forgiving the other way too: any specific action the solver takes at least 20% of the time is graded correct, and every qualifying button lights green on the reveal. The rule rewards recognizing close spots without punishing you for picking one of the hand's real actions.
Preflop Practice always offers the Mix button, because preflop strategies mix at every depth in both games. Postflop the button appears only on solves whose postflop strategy genuinely mixes - see Postflop Practice for which those are.
Right Pane: Session Info
The right pane shows session information:
- Section title - "Preflop Practice" with the current line and position
- Score - Correct/Total with accuracy percentage
- Streak - Current consecutive correct answers
- Session summary - Detailed stats matching the left pane
The deepest section of the tool. Browse complete solver strategies for any flop, navigate through the game tree street by street, see how hands are categorized, explore the opponent's range, and preview runout cards.
Left Pane: Board & Tree Navigation
Board - Three card slots for the flop, plus optional turn and river slots. Click any empty slot to open the Card Picker - a 13×4 grid of all 52 cards (cards already on the board are grayed out). For the flop, you pick all three cards in sequence.
- Click a filled card slot to replace that card (and remove all cards after it)
- Click the Reset button below the board to clear all cards
Today's Free Boards - Preset buttons below the board, each showing a flop in colored card notation. Free accounts and signed-out visitors get 3 boards that rotate daily and work in full; picking any other flop shows the Core upgrade gate (Core unlocks all 1,755 flops). Paid users see a short "Sample Flops" strip instead.
Browse all flops - Opens the flops explorer in the center pane: all 1,755 canonical flops for the spot you are studying, in one sorted, filterable table. Each row carries that board's aggregate frequencies - how often the out-of-position player bets, how often the in-position player bets after a check, and how the first player answers each bet size - so you can find the textures where a line is extreme instead of clicking through boards one at a time. Filter by suit, pairing, high card and connectivity; click any value column to sort; click a row's flop to load it. The table renders the first 300 matches and says how many more the filter holds. The numbers are precomputed and served free, so browsing costs nothing against your data allowance. Coverage is every seat matchup and every preflop line at every PLO stack depth, heads-up included; no NLHE aggregates are published yet. Free accounts see the table for today's three free boards; Core unlocks the other 1,752.
Copy link - Copies a link that reopens exactly what you are looking at: game, solve, seats, preflop line, board, the full action path, your hand filter and the active category lens. Paste it to a student, a study group or a forum and they land on the same node, not just the same board. Open in Notebook beside it turns the same spot into a Solver Notebook query.
Preflop Line - A row of buttons for the available preflop lines (e.g., SRP, 3BP). Click a line to navigate to its entry point in the game tree.
Tree Navigator - Once a board is set, the tree navigator shows your current position in the game tree, organized by street:
- Flop header - Shows the three flop cards. Click to rewind to the start of flop action. Below it, each action taken on the flop appears as a button showing the player name (colored by position) and the action (colored by action type). Click any past action to rewind to just before that decision. At the bottom, the current actor is shown with "to act".
- Turn header - Appears after a turn card is dealt. Same action button layout as flop.
- River header - Same pattern for river.
Navigate forward by clicking actions in the center pane, and backward by clicking past actions in the tree navigator.
Pot & Eff - Below the tree navigator:
- Pot - The total pot size (including current-street bets)
- Eff - The effective stack (the smallest stack among active players)
Display - The same BB / $ toggle as Postflop Practice sits under Pot & Eff, so study amounts can read in real money at a chosen stake.
Center Pane: Strategy Analysis
When no board is set - A placeholder prompting you to select a flop.
Spot context header - Above the action bar, a header states the spot in one line: who is to act, the preflop line, the matchup, street, board and pot, with a back arrow that undoes one action and a clickable breadcrumb of the whole line grouped by street.
When a turn or river card is needed - The Runout Grid appears - a 13×4 matrix of all remaining cards. Each card cell shows a miniature action frequency bar: colored segments representing how the aggregate strategy shifts if that card is dealt. Click any card to deal it.
Action Bar - Action buttons with names and percentages sit above a horizontal frequency bar whose colored segments are proportional to the solver's aggregate strategy. The two layers do different jobs:
- Click an action button to take that action and navigate deeper into the tree
- Click a bar segment to filter the hand list and category breakdown to hands that take that action (hovering highlights it)
Category Breakdown - A table grouping all hands by postflop hand category. There are 97 granular categories grouped into 14 meta-categories:
| Meta-Category | Examples |
|---|---|
| Monsters | Straight flush, quads, nut full house, full house |
| Flushes | Nut flush, 2nd nut flush, non-nut flush |
| Straights | All straight + straight draw combos |
| Sets / Trips | Top/mid/bottom set + draw combos, trips |
| Two Pair | All two pair combinations including overpair + pair |
| Overpair | Overpair + draw combos |
| Top Pair | Top pair + draw combos |
| Mid / Btm Pair | Middle and bottom pair + draws |
| Pocket Pair | Unimproved pocket pairs / underpairs + draws |
| Flush Draws | Nut flush draw, 2nd nut flush draw, flush draw (no pair) |
| Straight Draws | Wraps, open-enders, gutshots |
| Backdoors | Backdoor nut flush draw combos (flop only - the group disappears on turn and river) |
| Blockers | Nut flush blocker, nut flush draw blocker, flush blocker |
| Air | No pair, no draw |
Each row shows the category name, combo count, percentage, and action frequency bar. One segmented control above the table chooses how the rows are grouped - a lens, or Classic for the flat list of all 97 categories.
Lenses - The same hands, regrouped along a different axis:
- Strength - Made-hand strength, strongest first. Hands whose only feature is a draw have no made hand and collect at the bottom under No Made Hand - use the Draw lens for those.
- Draw - The draw a hand holds, independent of how strong it already is, so a set with the nut flush draw appears under its draw. Only the best draw per hand is recorded in the data. The lens needs a category vocabulary that actually splits on draws, so it's available on PLO solves and absent on NLHE.
- Value - How much of the opponent's current range each hand beats at showdown, split into Value (ahead of 65%+), Marginal (35-65%) and Weak (under 35%). This is computed for this board and this node rather than assumed from the hand class, which is why the same set can be Value on one flop and Weak on a river where the opponent's betting range has passed it. Draws count as their made part, since draw equity is not showdown strength. It needs the opponent's range, so it appears once that has loaded.
Table controls:
- Click column headers (Category, Hands, Action Mix) to sort
- Weighted toggle (on by default) - range-weighted vs preferred-action display
- Click a category row to filter the right-pane hand list
- Click an action segment within a row to filter by action
Opponent Range Breakdown - Below the category breakdown (in heads-up spots), shows the opponent's range composition at this node, always grouped by made-hand strength - it does not follow the lens you picked above, so you can read your draws against their made hands. When you hover over a hand in the right-pane hand list, this section shows blocker information: how many of the opponent's combos your hand blocks, the blocked percentage, and weighted blocker counts.
Right Pane: Hand List
The right pane displays the Hand List - every possible 4-card PLO hand for the current board, with its solver strategy.
Header - Shows the total count, e.g., "1,247 combos (892 weighted)". The weighted count reflects range tracking.
PPT Filter - A text input at the top for filtering hands using PPT syntax. A ? help icon shows a quick-reference tooltip, and a Strength… dropdown beside it writes made-hand comparators (>=set, <twopair) straight into the filter box.
Column Headers (when range weights are available):
- Hand - The four cards
- Wt - Reaching weight. Click to sort. Color-coded: white=high, amber=medium, orange=low.
- Action - The solver's preferred action
- Freq - Action frequency bar
Filter Frequency Bar - When any filter is active, an aggregate bar shows the combined strategy for all filtered hands.
Hand Rows - Each row shows four cards in the 4-color deck, weight percentage, best action (colored), and a frequency bar. Hover to see blocker info in the center pane. The list caps at 500 visible hands.
Drill your postflop decision-making with randomized scenarios. The tool picks a random board, navigates to a decision point in the tree, deals you a hand weighted by its probability of reaching that spot, and quizzes you on the correct action.
On a free account, practice grades 50 hands a day across postflop and the preflop trainer combined (15 signed out). The counter in the session stats row shows what's left; it resets at midnight Chicago time. Study never counts against it. Core has no meter.
Left Pane: Filters & Options
Lines - Checkboxes for which preflop lines to include (e.g., SRP, 3BP).
Streets - Checkboxes: Flop, Turn, River. Check multiple for variety. Disabled when "Play Flop to River" is active.
Spot - Any / First-in / Facing bet / Facing raise, so you can drill only the spots where money is already in. Pinned to Any while "Start at first flop action" is on.
Turn card - Texture checkboxes for which turn classes to deal (pairing card, flush card, broadway, middle, low). Leave them all off for any turn.
Position - In HU: OOP and IP checkboxes. In 6max: two seat-pill rows, Hero and Opponent, each defaulting to all six seats; every valid hero-vs-opponent pairing you leave selected gets drilled.
Timer - Slider from 5 to 60 seconds (in 5-second increments). Changes apply to the next hand only.
Display - Toggle between two value display modes:
- BB - Show all values in big blinds
- $ - Show values in real money. A dropdown appears to choose the cash game stake ($0.25/$0.50 through $50/$100).
Options - Three checkboxes:
- Accept any bet size (on by default) - If the solver's answer is "Bet 33" but you click "Bet 75", you're still marked correct as long as you got the action type right (bet vs. check vs. fold).
- Play Flop to River (on by default) - Each spot starts on the flop and continues through turn and river with the same hole cards. See Play Flop to River.
- Start at first flop action (shown with Play Flop to River, on by default) - Start each hand at your first flop decision instead of a random mid-flop node.
New Spot - Generates a new random scenario.
Changing any of these filters resets the session score - a running total would otherwise measure a different mix of spots.
Center Pane: Poker Table Quiz
The center pane shows the Quiz Panel - a realistic poker table with your hand and action buttons.
Poker Table - A top-down table visualization with:
- Seat boxes - Each active player showing position name and remaining stack. Hero is always at the bottom. Inactive seats appear dimmed.
- Dealer chip - A "D" marker on the button position.
- Pot chips - Realistic chip stacks on the felt. Chip denominations: black ($100), green ($25), red ($5), white ($1), pink ($0.25).
- Bet chips - Player bet stacks between seats and the center. Single stacks for bets, double for raises.
- Board cards - The community cards in the 4-color deck.
Hole Cards - Your four PLO cards shown below the table.
Timer Bar - Shrinks as time passes. Green >50%, yellow 20-50%, red <20%. If time expires, the answer is auto-revealed and marked incorrect.
Action Path - The preflop line name and sequence of actions leading to this decision. Street names in bold, actions color-coded.
Action Buttons - Ordered passive to aggressive: Fold, Check/Call, Bet/Raise sizes, All-In, colored by your action palette (see Action Colors). Number keys 1-9 map to buttons left to right. A Mix button appears only on solves whose postflop strategy actually mixes - today the NLHE re-solves at 20/30/50/100bb. PLO and the remaining NLHE depths were solved with only the preflop strategy averaged, so postflop each hand plays one action outright and there is no mix to call.
Result Modal - After answering: "Correct!", "Wrong", or "Time's Up!" header, frequency bars for every action, a line showing what the hand's whole class does at this node, and a Continue button (Space or Enter). Dismissible when you want to study the table first.
Session Stats - Below the buttons: the flame streak badge with your current XP combo multiplier, score (Correct/Total), accuracy percentage.
Right Pane: Results & History
Title - "Practice Mode" ("Spot Rush" during a rush run).
Spot Rush - A button here starts a 60-second rush run; see Spot Rush. Your current and best answer streaks also live in this pane.
Score - Large Correct/Total with accuracy percentage.
All-Combo Display (after reveal) - Shows every suit combination of the hand rank you were dealt, with the solver's best action for each combo. Your exact combo is highlighted.
Action History - A condensed tree navigator showing the current line, action path by street, and current decision point.
Session Stats - Correct / Total / Accuracy % / Reset Stats button.
A 60-second survival run inside postflop practice, started from the Spot Rush button in the right pane. Correct answers score by street - 1 point for a flop decision, 2 for a turn, 3 for a river - and the next spot deals instantly, with no feedback pause. The first wrong answer ends the run; so does the clock. A run costs 5 from the free daily meter.
- Per-spot timers are suspended during a run; only the 60-second clock matters
- Play Flop to River switches off for the run and restores when it ends
- The HUD shows seconds left and score, and turns urgent under 10 seconds
- Your high score saves to your account; the end-of-run summary offers an instant Retry
Spot Rush replaced the old Heater mode. Streaks didn't go anywhere: your current and best answer streaks show in the right pane during normal practice.
On by default in postflop practice, Play Flop to River turns each spot into a multi-street hand:
- You get a flop decision with four hole cards - your first flop action, unless you turn "Start at first flop action" off.
- Answer correctly and your chosen action is played into the tree. With "Accept any bet size" on, the size you clicked is what happens, even when the solver prefers another. A correct Mix plays an action sampled from the solver's own frequencies.
- The opponent's response is sampled from the solver's strategy - they never just fold you the pot, and a river check-back that would end the hand without a decision is skipped. When the street closes, the next card deals and you face the next decision with the same hole cards.
- The hand runs until you fold, answer wrong, or reach showdown - where the villain's hand, sampled from the range they'd actually hold on this line, is revealed face-up at their seat.
This drills full hand lines rather than isolated street decisions. When active, the Streets filter checkboxes are disabled and the timer applies to each street decision separately.
Open the Training Stats Dashboard from the Stats button in the top mode bar (desktop), or on mobile from the streak widget's dashboard icon or the Settings row. It shows your historical training data:
- Training Days - Current and best day streaks
- Total Spots, overall accuracy, and your best heater (longest-ever answer streak)
- Study Activity - How many flops, turns, rivers, and preflop spots you've studied
- Accuracy by mode, street, position, and spot - With a worst-first sort for finding weaknesses
- Suggested Study - Your lowest-accuracy spots with 10+ hands played
- Today's Quests and your badges - See Streaks, XP & Quests
You don't need an account to build history: signed-out training is tracked anonymously in your browser and merges into your account when you sign up (guests get 15 graded hands a day).
Training feeds a progress layer, summarized in the streak widget at the top of the left pane (top of the setup screen on mobile): your day streak, a level gauge, today's spot count, and on free accounts the runout-depth chips (TURN n/12, RIVER n/12).
- XP and levels - Answers earn XP, boosted by a combo multiplier that climbs with consecutive correct answers. The flame badge shows the current multiplier. XP earned on the site's calculators and study pages counts toward the same level.
- Perfect Day - 10 practice spots in a day completes the daily goal.
- Daily quests - Three rotating goals a day in the Training Dashboard; finish all three and claim the set for bonus XP.
- Badges - Milestone awards, granted the moment you earn them - trophies from the site's calculators and study pages show in the same case. Browse them in the dashboard; any badge can be shared as a card.
- Handle - Click the pencil in the streak widget to set your display name.
Free Runout Depth
Free accounts study a daily set of 5 turn cards and 5 river cards per flop; the rest of the runout grid shows locked. From your second consecutive study day, each day unlocks one more card (turns first), reaching 12 turns and 12 rivers on day 15. Miss a day and the earned depth survives one extra calendar day before dropping back to 5+5. Signed-out visitors stay at 5+5. Core removes the ladder entirely. The ladder applies in practice too: free deals draw turns and rivers from your unlocked set.
XP also unlocks three card styles - Aurora at level 2, Royal Felt at level 5, Midnight at level 10 (see Settings).
Click the gear icon in the top-left corner (desktop) or top-right corner (mobile) to open Settings.
Card Style - A carousel of 12 deck styles, browsed with the arrows or dots, each previewing all four suits: Classic, Classic Big, Vivid, Tinted, Neon, Bold, Bold Dark, Standard, Icon, plus three that unlock with XP - Aurora (level 2), Royal Felt (level 5), and Midnight (level 10). Locked styles preview dimmed with the level required. Each style changes how card faces look throughout the tool.
Data - A Metered connection checkbox stops all background loading: no preset-board or next-spot prefetching, no background street trees, and runout grids wait behind a "Load runout grid" button until you ask. A line underneath shows the session's transfer ("This session: 4.2 MB · 31 requests"). If your browser reports Data Saver the tool suggests the toggle once; it never switches itself on.
Appearance - Themes are not in this modal: pick them on your Account page under Appearance. 21 themes (15 dark, 6 light) plus a separate action-colours choice with a colorblind-safe palette. The choice covers the site and the solver together - an already-open solver tab re-themes instantly - and saves on the device, not your account. See Action Colors for what the palettes change.
Take the tour - Replays the guided tour that runs automatically on a first visit. A Training Dashboard row opens your stats - the mobile path to the dashboard.
Desktop only. Almost every app action has a Shift+ shortcut - and you don't have to memorize them. Hold Shift for about a quarter-second and a translucent cheatsheet fades in showing every shortcut available right now (filtered to the current view). Release Shift to dismiss.
If you want the cheatsheet to stay open while you experiment, press Shift+? to pin it; Esc closes it. New visitors also see a small "Hold ⇧ for shortcuts" hint in the corner - it auto-dismisses the first time you open the overlay.
Global
| Shortcut | Action |
|---|---|
| Shift+? | Pin / unpin the cheatsheet |
| Shift+, | Open Settings |
| Shift+` | Focus the left pane (Game → Players → Stacks → …) - see "Left-pane navigation" below |
| Shift+C | Open the Solver Notebook |
| Shift+F | Focus the filter input in the current view |
| Esc | Close the topmost modal or cheatsheet - or blur the focused text input (so you're not trapped inside the filter) |
Restart the tour from Settings → "Take the tour" - the global tour shortcut was retired so that T stays available for the rank Ten in the card chord (below).
Trainer (Study, Practice, Preflop)
| Shortcut | Action |
|---|---|
| Shift+M | Toggle Study ↔ Practice |
| Shift+P | Toggle Preflop ↔ Postflop |
| Shift+N | New practice spot |
| Shift+D | Open Training Dashboard (Postflop Practice) |
Card picker (browse the 4×13 grid)
| Shortcut | Slot |
|---|---|
| Shift+[ | Flop card 1 |
| Shift+] | Flop card 2 |
| Shift+\ | Flop card 3 |
| Shift+; | Turn |
| Shift+' | River |
Punctuation cluster on the right side of the keyboard - five adjacent keys, zero conflicts with the card chord (Shift+rank) or any letter shortcut. Postflop Study tab only - Practice spots are randomly generated, so manually editing the board isn't useful there. Inside the picker, arrow keys navigate the 4×13 grid, Enter picks, and type-to-pick works (e.g. A then S for A♠).
Left-pane navigation
Press Shift+` to enter a Tab-scoped navigation mode for the left config pane. A coloured stripe on the right edge of the pane confirms you're in nav mode. From there:
- Arrow keys move within a group (HU ↔ 6max, 100bb ↔ other stacks, SRP ↔ 3BP ↔ 4BP, …).
- Tab moves to the next group (Game → Players → Stacks → Seats → Sample Flops → Preflop Line) - scoped to the left pane only; Tab won't escape into the strategy panel or the hands list.
- Shift+Tab goes back.
- Enter or Space activates the focused option.
- Esc exits nav mode and blurs focus back to the page.
- Clicking anywhere outside the left pane also exits nav mode.
Each group is one Tab stop and Tab lands on the currently-selected option, so you start where you are. Same in Practice mode for the Lines / Streets / Position filter checkboxes.
Flow keys (no modifier needed)
These are tagged flow in the cheatsheet - fast keys for actually answering questions, not navigating the UI.
| Key | Action |
|---|---|
| 1 … N | Press the action button at that position, left to right. Practice spots and Study nodes never have more than ~4 actions, so you'll only ever use 1-4 in practice. |
| Space or Enter | Continue to next hand after the answer is revealed |
| M | Press Mix in Preflop Practice - and in Postflop Practice on solves that show the button |
| Backspace | Undo the last Study step |
Action buttons are always laid out in a consistent order: Fold → Call → Raise (smallest → largest) when facing a bet, or Check → Bet (smallest → largest) otherwise. So 1 is always Fold (or Check), 2 is always Call (or the smallest Bet), and 3… are the raise/bet sizes in increasing order - same mapping at every node, whether you're in Study, Practice, or Preflop Practice.
Direct card entry (turn & river)
When the board is at 3 cards (turn waiting) or 4 cards (river waiting), you can add the next card in two keystrokes - no picker UI:
- Press Shift+rank - a small banner at the top arms the rank and waits for a suit.
- Press the suit letter (S ♠, H ♥, D ♦, C ♣) - with or without Shift held - and the card lands on the next empty slot.
| Example | Result |
|---|---|
| Shift+A then H | Ace of hearts → turn |
| Shift+3 then C | Three of clubs → river |
| Shift+T then S | Ten of spades |
Postflop Study tab only. No time limit: the armed rank waits until you press a suit (or Esc to cancel). You can keep Shift held the entire time - both Shift+H and bare H complete the chord. If you wander off and press any other "real" key, the banner clears itself so it doesn't go stale. Duplicate cards (already on the board) are ignored. Shift+A, K, Q, J, T, and 2-9 are reserved exclusively for this chord - no other shortcut uses them.
Notebook-specific shortcuts (Run, Save, Stop, cell ops) live in the Solver Notebook → Keyboard Shortcuts section below.
What it is
A notebook-style data explorer for every data point the trainer uses. Available at solvepoker.app/solver/notebook (the old /solver/cli URL redirects). Each line of a cell is one command; results can be bound to $variables and composed into ad-hoc analyses - "for this spot on these 10 flops, which hands cbet vs check, and which category does each go into?"
Commands execute in your browser against the same solve data and login session as the main tool (each cell run logs an anonymous ping carrying the command name and solve id, never the argument text). Results are auto-rendered as tables, bar charts, diffs, or prose. Cell inputs are saved in your browser between visits; saved notebooks live at /solver/notebook/:slug for sharing. A fresh session opens with a short welcome notebook - an intro note plus help and solves, run automatically, local and unmetered.
Both games (PLO + NLHE)
The notebook serves both ladders from the same session, and the solve id picks the game - anything starting nlhe_ loads the NLHE library, everything else PLO. There is no mode to switch, and a single notebook can hold variables from both.
Everything that differs between the games follows the solve automatically:
| PLO | NLHE | |
|---|---|---|
| Hand classes | 16,432 | 169 |
| Combos | 270,725 | 1,326 |
| Preflop categories | 31 | 13 |
| Postflop categories | 97 | 35 (+ draw flags) |
| Range syntax | PPT generic Omaha | Hold'em range notation |
| PQL game | game='omahahi' | game='holdem' |
An NLHE spot fits on one screen. 169 rows instead of 16,432 means --by-combo is usable by default, and categories are computed in your browser rather than fetched - so --categorize on NLHE costs no data budget at all and throws in draw flags (NFD, OESD, Gutshot, BDFD…) alongside the made-hand category.
spot --equity is NLHE-only and gives hero equity against the opponent's actual arriving range - exact on the turn and river, sampled on the flop (the summary says which). NLHE solves carry no postflop EV at all, so this is the honest value number.
Comparisons never cross games. compare, join, diff and sql all refuse a mix: a hand index means one of 169 things in NLHE and one of 16,432 in PLO, so a join across them would return rows that mean nothing. Put them side by side with two separate breakdowns instead.
Solves are not uniform within the NLHE ladder either - rake, postflop purity and the bet menu all differ by depth. Run solve-info before reading anything into a cross-depth difference:
solves --game nlhe
solve-info nlhe_6max_100bb # mixed postflop, 42 flop buckets, rake 5% cap 2bb
solve-info nlhe_6max_200bb # pure postflop, 30 buckets, rake 5% cap 1.5bb
$bb = spot nlhe_6max_100bb --line SRP --actor BB --opponent BTN --board Kh7s2c --postflop check --categorize The UI
Three regions:
- Top bar - breadcrumb, editable notebook title, File menu (New, Examples, My notebooks, Import/Export, Clear, Save, Save as new, Copy share link, Delete notebook - the Save entry reads "Fork + save a copy" on someone else's notebook), Help menu, progress indicator, and the primary ▶ Run All / ■ Stop / + Cell buttons.
- Sidebar - the Variables panel. Every
$nameproduced by a cell appears here with its kind and shape. Click the name to insert$nameinto the focused cell at the caret (the last cell if none is focused); click ✕ to delete. - Notebook main area - the ordered list of cells. Each cell is numbered
#1, #2, #3, …and has a header with the varname chip (if any), timing, csv/json export buttons, copy-the-command and copy-result-as-Markdown buttons, ▲ run (above) / ▼ run (from here) scope buttons, ↑/↓ reorder, ⎘ duplicate, + insert below, ✕ delete, plus collapse, re-run, and clear-output buttons (folded into a ⋯ menu on mobile).
Cells & Variables
Each line of a cell is one command, optionally bound to a variable (Enter inserts a newline; Shift+Enter runs). Multi-statement cells run top to bottom: variables bound by earlier lines are visible to later ones, the last statement's result displays, and an error names its [line N]. Quoted strings can span lines, so a multi-line sql "…" stays one statement.
| Input | Effect |
|---|---|
solves | Run a command, display its result inline (table, stats card, bar chart, etc. depending on what it returns). |
$x = spot hu_100bb --line SRP ... | Bind the result to $x. Appears in the sidebar; referenced in later cells as $x. |
$flops = [Kh7s2c, AhKhQh, 9h8h7s] | Make a list variable. |
each $flops | spot ... --board=$_ | Run a template once per list item; $_ substitutes the current value. |
$x = each $flops as b | spot ... --board=$b | Same with a named iteration variable; the iterations merge into one stacked table, bound by the outer $x =. |
# Just a comment | Cell with only # lines renders as a markdown note - not executed. |
Autocomplete: Tab completes command names in command position (the start of a statement, or right after =), plus --flags from the command's own schema and known values (solve ids, enum choices); $variable completion works anywhere. Suggestions appear inline; ↑/↓ navigate them, Enter selects, Esc dismisses.
History: with the cursor on a single-line cell, ↑/↓ cycle through the 100 most recent commands you've run (saved in your browser between visits).
Markdown Cells
A cell is automatically rendered as prose (not executed) when every non-blank line starts with #. This lets you write narrative notes between query cells without typing anything special - just # at the start of every line, the way you'd comment out code.
Supported markdown subset:
- Headings: the first
#on a line is the comment marker itself, so## Titlerenders as the big heading,### Subheadthe next size down, and deeper levels render small. - Bullet lists: lines starting with
-or*. - Numbered lists: lines starting with
1.,2., … - Inline:
**bold**,*italic*,`code`,[link text](url). - Horizontal rule: a line of 3+ dashes, em-dashes, or underscores.
Markdown cells auto-run on notebook load (including when you open a shared notebook via slug), so prose appears immediately - you don't have to click Run on every comment.
Running Cells
Every cell can be run individually or as part of a range:
| Action | What it runs | Where |
|---|---|---|
| Run (per-cell) | Just this cell. | Cell's Run button, or Shift+Enter in the input. |
| ▲ run (per-cell) | All cells above this one, top-to-bottom. | Cell header, left of move buttons. |
| ▼ run (per-cell) | This cell and all cells below, top-to-bottom. | Cell header. |
| Run All | Every cell in the notebook, top-to-bottom. | Top bar button or ⌘⇧↵. |
| Stop | Halts a range run and aborts the cell currently executing - a lone hung cell stops too. | Top bar (replaces Run All during a run) or ⌘. |
During a range run, the top bar shows "Running cell N of M…". Runs halt immediately on the first cell that errors - you'll see a red-bordered cell pointing to what failed.
Keyboard Shortcuts
The notebook uses the same Shift+ system as the rest of the app - hold Shift for a quarter-second to see the full cheatsheet, filtered to the notebook. Most cell operations are Shift+letter so a single keyboard hand can run, save, add, move, or delete cells. Cmd / Ctrl variants are kept as aliases for muscle memory (⌘ on Mac, Ctrl elsewhere).
Notebook
| Shortcut | Action |
|---|---|
| Shift+R | Run all cells |
| Shift+S | Save notebook (creates slug if new, updates if yours) |
| Shift+. | Stop a running range |
| Shift+\ | Toggle the variables sidebar |
| Shift+G | Jump to the cell that defined the first variable |
⌘⇧↵ | Run all (alias) |
⌘S | Save (alias) |
⌘. | Stop (alias) |
Cell operations (target = focused cell, or last cell)
| Shortcut | Action |
|---|---|
| Shift+A | Add a new cell below the focused one (and focus it) |
| Shift+O | Add a new cell above the focused one |
| Shift+Backspace | Delete the focused cell |
| Shift+Y | Duplicate (yank) the focused cell |
| Shift+J / Shift+K | Move the focused cell down / up (vim-style) |
| Shift+↑ / Shift+↓ | Focus previous / next cell |
⌘↑ / ⌘↓ | Focus previous / next cell (alias) |
Inside the cell editor
| Shortcut | Action |
|---|---|
| Shift+↵ | Run the current cell |
⌘↵ | Run the current cell (alias) |
| Tab | Autocomplete command name / variable |
| ↑ / ↓ | Cycle suggestions, or command history (on single-line cells) |
| ↵ | Accept the selected suggestion (when the suggestion panel is open) |
| Esc | Dismiss the suggestion panel |
Command Reference
Run help for the full list, or help <cmd> for detail on one command. Commands are grouped:
Session
| Command | Purpose |
|---|---|
help [cmd] | List all commands, or show detail for one. |
vars | List active variables. |
clear $var / clear --all / clear --cells | Remove a single variable, everything, or just cells. |
export $var --format csv|json | Download a variable's result as CSV or JSON. |
No default solve. Every command that needs solve data takes --solve <id> or accepts it as a positional argument. This lets one notebook mix hu_100bb, 6max_50bb, and 6max_200bb freely.
Solves & Catalogs
| Command | Purpose |
|---|---|
solves | Every available solve - 9 PLO (hu_100bb, 6max_12-200bb) and 8 NLHE (nlhe_6max_20-200bb, with a 70bb). --game plo|nlhe filters the list. |
solve-info <solve> | Solve metadata: players, positions, spots, tree size, decision counts, rake, and postflop purity. |
flops [--suit --pair --high --conn] | All 1,755 canonical flops with the same texture vocabulary agg uses, plus each flop's multiplicity and weight share. Zero data budget; joins agg results on board. |
hands <solve> --ppt "AA:$ds" | Canonical hand catalog (16,432 classes), filterable by PPT / cat / suit pattern. |
hand <solve> AAKKds | One canonical hand's full record. |
hand-categories | All 5 categorization systems (preflop, preflop-meta, suit, postflop, postflop-meta) in one call. |
ppt "<expr>" --solve <solve> | Standalone PPT enumerator - every canonical hand matching an expression (--solve is required). |
Preflop
| Command | Purpose |
|---|---|
preflop-lines <solve> | All decision spots (RFI / SRP / 3BP / ...). |
preflop-spot <solve> --line RFI --actor IP | Full per-hand raise / call / fold / VPIP for a spot. --ev adds per-action EV in bb - preflop is the only street with EV in the served data, and EVs are absolute from the start of the hand, so folding is worth minus the blind you posted, not 0. |
preflop-hand <solve> AAKKds --line RFI --actor IP | Action mix for one hand (or all variants of a rank-name like AAKK); takes --ev too. |
Game Tree
| Command | Purpose |
|---|---|
tree <solve> --street flop | Dump nodes for one street. |
node <solve> <nodeId> | One node's actions, children, parent chain. |
walk <solve> --path <action-codes> | Follow a sequence of action codes from the root (advanced - find-spot is the friendlier interface). |
find-spot hu_100bb --line SRP --actor IP --opponent OOP --postflop check | Resolve a line + actors + postflop sequence to a concrete nodeId. Board-independent - the trees are abstract. |
Ranges
| Command | Purpose |
|---|---|
range-index <solve> | Dump of the per-node range index for a solve. |
range <solve> <nodeId> | Raw range mass for a player at a flop-entry node. |
range-summary <solve> <nodeId> | Range aggregated per canonical hand, sorted by mass. |
Spot Command (the composite)
The spot command is the workhorse - given a line + board + postflop sequence, it returns per-hand action frequencies at the resulting node. Reach propagation is always on (opt out with --no-reach if you don't want it). One command is one line; a full call looks like:
$s = spot hu_100bb --line SRP --actor IP --opponent OOP --board Kh9s5s --turn 2d --river Qh --postflop check,bet100,call,check,bet100,call,check --categorize
# Narrow and shape the result:
$aa = spot hu_100bb --line SRP --actor IP --opponent OOP --board Kh7s2c --postflop check --ppt "AA" --action bet100 --threshold 50 --sort f_bet100 --limit 500 Optional flags:
--turn <card>/--river <card>- required when the postflop walk crosses into a later street. The strategy is runout-specific.--boardalso accepts the 4- or 5-card board inline.--by-combo- emit per-physical-combo rows (one row per specific combination) instead of per-canonical aggregation. Needed forblockers/blocker-map.--range "<expr>"/--ppt/--pql-range- hand filters; see Range filters in the notebook.--cat <id>- keep only hands in one preflop category.--equity(NLHE only) - hero equity vs the opponent's arriving range;--vs <seat>picks which opponent in a multiway line. See Both games.--no-reach- skip reach propagation. Faster but every combo has weight 1, which overweights trash hands in downstream aggregations. Rarely useful; only for speed when you don't care about mass.--categorize- annotate each hand with its hand category at every street (see below).--node-id <id>- skip line resolution if you already know the nodeId (--boardis still required).
Reach propagation (default)
Every spot call propagates combo weights through the hero's path - reachWeight on each row is the true mass the hero's range puts into that specific hand at the final node. Each combo's weight at the final node is its starting preflop weight multiplied by the hero's own frequency of taking each action along the path, so a hand the hero almost always folds will have near-zero reach weight at any node deeper in the line. The same propagation drives the main study page, so numbers match. The actionMix line in the result summary is this reach-weighted node mix - the same number as the Solver UI's action bar and the published aggregate pages. If the reach fetch fails (rate limit, network), the command errors instead of quietly returning an unweighted table; pass --no-reach when you explicitly want the unweighted view. Canonical classes that never arrive at the node (reach exactly 0) are dropped from the table and counted in the summary - --no-reach or --by-combo shows the whole strategy surface.
Hand categories (--categorize)
You get one category column per street the board reaches:
flop cat(flopCatId/flopCatLabel) - always present. What the hand IS on the flop. Drives most line-assignment decisions.turn cat(turnCatId/turnCatLabel) - present when the board reaches turn or river. What the hand IS on the turn.river cat(postflopCatId/catLabel) - present on river spots. What the hand HAS MADE by showdown.
All three share the same category id space (0-96) so metaOf() / metaName() work on any of them. postflopCatId is always the final-board cat (= flop cat on flop spots, turn cat on turn spots, river cat on river spots) - kept as back-compat with older queries.
This lets you trace how a hand's category evolves - e.g. a flop flush draw (flop cat = "Flush Draws") that turns into top pair (turn cat = "Top Pair") and eventually rivers a set (river cat = "Sets / Trips"). All three columns visible side-by-side.
Categorizations
Six independent systems are exposed (the five below take --game plo|nlhe to read the other game's taxonomy; without it they follow the ambient solve):
| Command | What it returns |
|---|---|
preflop-categories | 31 preflop categories with meta grouping (AA+Pair, High Pairs, Rundowns, etc.). |
suit-categories | 5 suit patterns (DS / SS / rainbow / mono / 3flush). |
postflop-category-labels | 97 postflop categories (Top Set + NFD, Nut Flush, OESD, etc.). |
meta-categories | 14 meta groups (Monsters, Flushes, Straights, Sets/Trips, ... Blockers, Air). |
categories flop --solve hu_100bb --board Kh7s2c | Every hand on a board with its postflop category + meta. |
category-of flop Kh7s2c AhKhQsJs | One hand's category on a board. |
category-breakdown $s --by meta | Histogram of a spot's hands by meta group or full category. --mass <action> weights each hand by reach times that action's frequency - the composition of the hands that actually take it. |
draw-flags | The NLHE draw-flag reference (NFD / FD / OESD / Gutshot / BDFD / Overcards) - the sixth system, NLHE only. |
Blockers, Runouts, Combos
| Command | Purpose |
|---|---|
blockers $s AhKh | How much of the opponent's range is blocked by these cards. Needs --by-combo on the source spot. |
blocker-map $s | 52-row heatmap: what % of opponent range each card blocks. |
runouts <solve> --line SRP --actor BTN --opponent BB --board Kh7s2c --postflop check,bet33,call | For every possible next card (turns by default; --target river from a 4-card base board), the mean frequency of each action at the decision node. The walk must END on the target street - after a flop bet-call, the next decision IS the turn node - and the command errors with guidance if it doesn't. The stat is an unweighted mean over all remaining combos (same number as the Solver UI's runout grid), not weighted by the arriving range; on NLHE, --weighted switches to the arriving-range mix, and the summary names which ran. --group-by class rolls cards into texture classes. Result is a runout grid that's also queryable as a table (one row per card, columns card / cardIdx / f_<action>). |
combos <solve> AAKKds | Every physical combo of a canonical hand. |
combos-weighted $s AAKKds | Same list, with reach weight from a prior spot. |
Equity / PQL
| Command | Purpose |
|---|---|
pql "<query>" | Run a Poker Query Language query - equity, hand-type histograms, percentile selectors, scenario predicates. Supports all 10 PQL games: PLO4/5/6, Hold'em, Omaha-8, 5-card Omaha-8, Stud, Stud-8, Razz, and Triton-rule shortdeck. $variables outside string literals expand to explicit combo lists - reach-weighted (combo@weight) whenever the rows carry reachWeight - so GTO ranges from spot / preflop-spot pipe straight in. Flags: --trials, --seed, --timeout, --unweighted. See PQL inside the notebook. |
matchup $a $b --board Kh7s2c | Range-vs-range equity with a 5%-band distribution, reach-weighted. Cross-game pairs are rejected. |
Analyze (composed ops on $variables)
| Command | Purpose |
|---|---|
filter $s --ppt "AAKK" --action f_check --threshold 70 | Filter rows by PPT / category (--cat, --meta) / action threshold (--max for an upper bound), with --sort / --asc / --limit. Takes --range/--ppt but not --pql-range. |
diff $a $b --action f_bet100 | Per-hand delta between two spots on one action. |
compare $a $b $c --action f_check --all-min 50 | Wide table: rows = hands, cols = each var's action frequency. --all-min keeps hands where every var clears the bar; --any-min where any does. |
compare $a $b $c --action f_bet100 --ppt "AA:$ds" | Filter each var's hands via PPT before the join. |
compare $a $b $c --action f_bet100 --group-by postflop-meta | Aggregate per category - rows = categories, cols = each var's combo-/reach-weighted avg frequency for hands in that group. |
compare --solves 6max_20bb,6max_100bb,6max_200bb --line SRP --actor BTN --opponent BB --board Kh7s2c --action check | Cross-solve sugar: runs the same spot in each listed solve and compares - one cell instead of N spot cells plus a compare. Rejects --node-id (node ids are per-solve) and fails loud if a solve's tree lacks the action (different depths use different bet sizes). |
join $a $b --on display | Join two hand-row tables (--how inner|left; the right table's clashing columns get a b_ prefix). |
line-freq $s | Reach-weighted share of a player's flop range that takes the line a spot describes. Refuses --no-reach data. |
range-compare $a $b --by meta | Composition shift between two ranges, grouped by category or meta group. |
describe $s | Row count, column names, numeric column stats (mean/min/max). |
agg / $a = agg srp_btn_bb / agg srp_btn_bb --by suit | Precomputed aggregate references: per-flop tables across all 1,755 canonical flops of a line (c-bet, donk, and check-raise-response frequencies, reach-weighted), computed offline by the same certified pipeline behind the c-bet reference page. Zero data budget. Published for every seat matchup and preflop line at every PLO stack depth, so the same id exists at several depths - a bare agg srp_btn_bb resolves to your session's solve, and --solve picks another. Ids read <line>_<seats in preflop order>, so srp_btn_bb is the BB-vs-BTN spot; which player is out of position is in the result, not the id. Bet sizes differ by depth and pot type (33%/pot at 100bb single-raised, a lone 66% at many shorter stacks), so read the column names rather than assuming. PLO only for now - no NLHE artifacts are published yet. Free accounts get the rows for today's three free boards, with the withheld count in the summary; Core opens all 1,755. The texture-atlas example notebook ships its saved results, so you can read a full agg analysis without running it. --by suit|pair|high|conn rolls up by texture; --sort/--asc/--limit shape the table; bind to a $var and it's SQL-queryable. |
--group-by values:
postflop-cat- fine-grained (97 postflop cats)postflop-meta- 14 meta groups (Monsters, Flushes, Straights, Sets/Trips, ... Blockers, Air)preflop-cat- 31 preflop categoriespreflop-meta- 11 preflop meta groups
Grouped averages are reach-weighted by default (since spot now propagates reach unless you pass --no-reach). compare and diff refuse a mix of reach-weighted and --no-reach variables outright - the percentages would be computed over different populations - and the error says which variable is which; re-run them the same way.
compare drops per-flop columns (river cat, reach weight, cards, and every f_/v_ column) from the joined identity - those vary across flops and showing just $s1's value would be misleading. With --categorize, the flop and turn cat labels survive. Hand identity is handIndex / display / combos / preflop cat.
SQL over $variables
The sql command runs any SQL query over your session's $variables. Reference any variable that holds a table inside the SQL text by its full $name - the engine rewrites each $varname into a positional placeholder and binds the row data behind the scenes. So FROM $s1, JOIN $other ON …, and sub-selects all work and the $ prefix is required. CTEs (WITH), inner/left JOIN, GROUP BY, ORDER BY, HAVING, UNION, and sub-selects all work. One SQL statement per cell (a single trailing semicolon is fine); since a quoted string can span lines, a long query stays one statement. Top-level UNION chains run branch by branch and re-merge, so column names follow the first SELECT and a leading WITH applies to every branch; an empty branch errors loudly. Mixing PLO and NLHE variables in one query is rejected by name. --limit caps the rendered rows.
User-defined functions you can call inside SQL:
| Function | Returns | Use |
|---|---|---|
range(cards, "<expr>") | 1 / 0 | Game-dispatched hand filter - PPT dialect on PLO rows, Hold'em notation on NLHE rows. Percentile tokens aren't available inside SQL; filter with them at the spot/filter level instead. |
ppt(cards, "<expr>") | 1 / 0 | WHERE ppt(cards, 'AA:$ds') = 1 - PLO rows only (errors on NLHE; use range()) |
drawFlags(bits) | string | Decode an NLHE row's draw-flag bitmask (NFD, OESD, …) |
metaOf(postflopCatId) | int | postflop meta group id (0-13) |
metaName(postflopCatId) | string | group label ("Sets / Trips", "Top Pair", …) |
preflopCat(preflopCatId) | string | preflop category label |
preflopMeta(preflopCatId) | string | preflop meta group label |
Examples:
# Top cbet hands that match a PPT expression
sql "SELECT display, f_bet100 FROM $s1
WHERE ppt(cards, 'AA') = 1
ORDER BY f_bet100 DESC LIMIT 20"
# Three-way JOIN across flops, sort by biggest cbet delta
sql "SELECT k.display,
k.f_bet100 AS k72,
m.f_bet100 AS mono,
(k.f_bet100 - m.f_bet100) AS delta
FROM $spot_k72 k
JOIN $spot_mono m ON k.handIndex = m.handIndex
ORDER BY delta DESC LIMIT 30"
# CTE + HAVING - preflop classes where hands cbet >=50% on avg, n>=10
sql "WITH by_class AS (
SELECT preflopCatLabel, AVG(f_bet100) AS avg_cbet, COUNT(*) AS n
FROM $s1 GROUP BY preflopCatLabel
)
SELECT * FROM by_class
WHERE avg_cbet >= 50 AND n >= 10
ORDER BY avg_cbet DESC" Gotchas: total, count, and a few other words are reserved by the SQL engine - use different aliases (e.g. total_combos). If you hit one, the error message names the conflicting keyword and suggests an alternative.
Window functions have limited support: ROW_NUMBER() over simple partitions works, but RANK() and more advanced analytical functions don't. For 16,432-row tables everything runs in under a second.
Column cheat-sheet for spot results:
| Column | Meaning |
|---|---|
handIndex | Canonical hand id: 0-16,431 in PLO, 0-168 in NLHE. Stable identifier across solves of the same game. |
display | Human-readable canonical hand, e.g. AAKKds. |
cards | CSV of the hand's card values (4 in PLO, 2 in NLHE). Used by range(cards, ...) and by the pql command's $var substitution. Also present on preflop-spot rows so preflop ranges pipe into PQL the same way. |
combos | Canonical combo multiplicity (1 / 4 / 6 / 12 / 24). |
reachWeight | Reach-weighted combo mass at this node. Present unless --no-reach was passed. |
preflopCatId / preflopCatLabel | Preflop category (0-30). |
flopCatId / flopCatLabel | Flop hand category (0-96). Always present when --categorize. |
turnCatId / turnCatLabel | Turn hand category (0-96). Present on turn/river spots. |
postflopCatId / catLabel | Final-board hand category (0-96) - = flop cat on flop spots, turn cat on turn spots, river cat on river spots. |
f_<action> | Frequency % of that action. Token names are tree- and node-specific - hu_100bb offers f_bet25 / f_bet100 at first-in nodes and f_raise50 / f_raise100 facing a bet; 6max_50bb uses f_bet66 on the flop and f_bet100 on the turn; plus f_check, f_call, f_fold, f_allin. Run describe $s after fetching a spot to see the exact action columns the resolved node exposes. |
drawFlags / strength / pctile / equity | NLHE extras: draw-flag bitmask (with --categorize), hand rank 1 (nuts) to 7,462 and its percentile (per-combo tables), and hero equity (with --equity). Per-combo tables key the hand as combo rather than combos. |
Range filters in the notebook
Every command that filters hands accepts --range "<expr>" (and --ppt, its long-standing alias - both behave identically). Inside SQL, use the range(cards, '<expr>') UDF in any WHERE / SELECT context.
The syntax follows the solve's game, and the two dialects are not interchangeable. This matters more than it sounds: PPT's generic Omaha grammar reads a trailing s as the spade suit, so on a PLO solve AKs means "an ace and the king of spades". On an NLHE solve the same flag speaks standard Hold'em notation, where AKs means suited ace-king. Each is correct for its game; neither is correct for the other, which is why the notebook picks for you rather than guessing.
| On a PLO solve | On an NLHE solve |
|---|---|
AA, AAKK, AA:$ds | AA, AKs, AKo |
AA-JJ, AxKx, [A-J] | 99+, TT-77, ATs+, A5s-A2s |
$ds $ss $0g $np | Axs, AsKh, !TT |
15% / 10%-25% - percentile, both games | |
Percentile tokens rank hands by all-in equity using the PQL engine's own tables, so --range "15%" and pql "hero='15%'" describe exactly the same hands. See PPT Filter Syntax for the full PLO reference.
--pql-range is the escape hatch: it takes the raw PQL grammar on either game, which is where you reach for AK:$s-style shape constraints, weighted atoms, or solver-range references. It's accepted by hands, categories, preflop-spot, range-summary, spot, and compare - not by filter, which takes --range/--ppt only (unknown flags are hard errors, never silently ignored).
# Standalone enumerator - every canonical hand matching the expression:
ppt "AAKK:$ds" --solve hu_100bb
ppt "22+, ATs+, KQs" --solve nlhe_6max_100bb
# Filter a spot (PLO dialect):
$aces = spot hu_100bb --line SRP --actor IP --opponent OOP --board Kh7s2c --postflop check --range "AA"
# Filter a spot (NLHE dialect):
$broadway = spot nlhe_6max_100bb --line SRP --actor BB --opponent BTN --board Kh7s2c --postflop check --range "22+, ATs+, KQs"
# Filter a prior variable - uses that table's own game:
filter $s --range "AA-JJ"
# Filter inside SQL:
sql "SELECT display, f_bet100 FROM $s WHERE range(cards, 'As:$np')
ORDER BY f_bet100 DESC LIMIT 20" PQL inside the notebook
The pql command runs Poker Query Language queries - the same engine that powers /pql, embedded in the notebook so equity / percentile / scenario questions can pipe directly off solver ranges. Full PQL syntax, supported games, every aggregator and function, range DSL, and known limits are documented in the PQL Engine section. This subsection covers the notebook-specific parts.
Basic shape:
# Equity of AAKK double-suited vs random on a dry flop:
pql "select avg(equity(hero)) from game='omahahi', hero='AAKK:$ds', villain='**', board='Kh7s2c'"
# Hand-type histogram on the river:
pql "select histogram(handType(hero, river)) from game='omahahi', hero='AAKK:$ds', villain='****', board='Kh7s2c'"
# Percentile selector - top 15% of PLO hands by all-in equity vs random:
pql "select avg(equity(hero)) from game='omahahi', hero='15%', villain='**'"
# Different game - Triton-rule shortdeck (6+ Hold'em):
pql "select avg(equity(hero)) from game='shortdeck', hero='AKs', villain='QQ'" Supported games: holdem, omahahi (PLO), omaha8, omahahi5 (PLO5), omaha85 (Big O), omahahi6 (PLO6), studhi, stud8, razz, and shortdeck.
Piping GTO ranges into PQL
Any $varname reference outside a string literal is replaced at run time with the variable's row data as an explicit comma-separated combo list (e.g. 'AsAhKsKh,AsAhKhKc,…'). Both spot and preflop-spot rows carry a cards column the adapter uses to emit unambiguous 4-card combos:
# 1. Get the IP range at a postflop decision node.
$ip = spot hu_100bb --line SRP --actor IP --opponent OOP --board Kh7s2c --postflop check
# 2. Equity of that range vs random:
pql "select avg(equity(hero)) from game='omahahi', hero=$ip, villain='**', board='Kh7s2c'"
# 3. Filter the GTO range first via SQL, then equity it - only hands betting >= 50%:
$ip_bets = sql "SELECT * FROM $ip WHERE f_bet100 >= 50"
pql "select avg(equity(hero)) from game='omahahi', hero=$ip_bets, villain='**', board='Kh7s2c'"
# 4. Both players' GTO ranges at the same node - the actual range vs range matchup:
$oop = spot hu_100bb --line SRP --actor OOP --opponent IP --board Kh7s2c --postflop check
pql "select avg(equity(hero)) from game='omahahi', hero=$ip, villain=$oop, board='Kh7s2c'"
# 5. Pipe a preflop range too - preflop-spot rows now carry cards as well:
$btn_open = preflop-spot 6max_100bb --line RFI --actor BTN --sort raise_pct --limit 16432
$btn_core = sql "SELECT * FROM $btn_open WHERE raise_pct >= 50"
pql "select avg(equity(hero)) from game='omahahi', hero=$btn_core, villain='**'" Substitution happens outside string literals only, so '$ds' inside a quoted PQL range stays literal - exactly what the PPT shape macros need.
Multi-query batches
Separate queries with ; to run several in one cell. Multi-query results merge into a stats card with one labeled row per query, plus a queryable table projection underneath.
pql "select avg(equity(hero)) from game='omahahi', hero='15%', villain='**';
select avg(equity(hero)) from game='omahahi5', hero='15%', villain='**';
select avg(equity(hero)) from game='omahahi6', hero='15%', villain='**'" Flags
| Flag | Default | Effect |
|---|---|---|
--trials <n> | 100,000 | Monte-Carlo sample size. Higher = tighter CI, slower. |
--seed <n> | random per call | RNG seed for reproducibility. Same query + same seed = bit-for-bit identical results. |
--timeout <sec> | 60 | Hard wall-clock cap. The query stops cleanly with whatever it has so far if it hits the cap. |
--unweighted | off | Sample a piped $var uniformly, ignoring its reach weights. |
Result rendering & downstream use
- Scalar aggregators (
avg,count,min,max) → stats card with the value and standard error. - A single
histogram→ bar chart sorted by poker rank (straight-flush → quads → full house → flush → … → high card). - Mixed scalars + histograms or multi-query batches → stats card with a queryable table projection underneath. Bind to
$varand feed back intosqlif you want to post-process.
Notes
- Percentile syntax is
'15%', not'top 15%'- there's notopkeyword. Bands like'15%-30%'work; the 6-handed variant is'15%6h'(Hold'em, PLO4, and Omaha-8 also ship a 3-handed variant; PLO5/6 are 6-handed only). PLO4/5/6, Hold'em, Omaha-8, and 5-card Omaha-8 ship with prebuilt percentile tables; shortdeck does not (a percent range falls back to the full universe with a warning). $varexpands to a literal combo list, so PQL set-algebra operators (:,!,,) work inside a single quoted range string but not between two substituted variables. Use SQL pre-filtering (WHERE) on the variable, then pipe the filtered result into PQL.- First percentile query on PLO5/6 or Big O builds a canonical hand-rank map (~30-60 s on a fast laptop) and caches it for the rest of the session - the cell just shows running while it builds.
- Stop button cancels long-running PQL queries - the worker is wired to PQL's
cancelQueryhook and aborts within a couple of trials. - Reach weights ARE used when piping a spot result into PQL: rows with a positive
reachWeightsubstitute as weighted combos ([email protected]), so equity sampling follows the solver's arrival distribution instead of treating every hand class as equally likely. Tables that carry areachWeightcolumn with no positive weights (a--no-reachrun, or a filter that removed every reached hand) now error instead of silently sampling the whole table uniformly - pass--unweightedwhen uniform sampling is what you want. - Postflop EV is not in the served data - the solve files carry action frequencies only, and no command invents an EV column. Reach-weighted PQL equity over piped ranges is the sanctioned way to ask value questions.
Save & Share
Notebooks can be saved to your account and shared by URL. Sign in at solvepoker.app and then File → Save creates a permanent URL like solvepoker.app/solver/notebook/AB23C9Y7. Anyone with the link can open and read it - saved results render immediately from a snapshot taken at save time (Run All recomputes live). Only you can overwrite it; others get a "Fork to my account" option. A saved notebook is world-readable by anyone holding (or guessing) its slug, so don't put anything sensitive in one.
Limits: 100 saved notebooks per account, 500 KB of content per notebook (result snapshots are the usual reason to hit it), and 500 cells. Saving is manual - there is no autosave.
| Action | What it does |
|---|---|
File → Save (⌘+S) | Creates a new slug if unsaved; overwrites if you own it. Copies the share link to your clipboard. |
| File → Copy share link | Appears for saved notebooks - copies the URL to clipboard. |
| File → Fork to my account | When viewing someone else's notebook, creates your own editable copy. |
| File → My notebooks | Lists your saved notebooks; click to open. |
| File → Import / Export JSON | Works regardless of auth - a local-only transport if you want to back up or sideload notebooks. Export carries cell inputs only, no results. |
The notebook title shown in the top bar is editable inline (click it). The title is saved with the notebook content, along with sanitized result snapshots (a ~380 KB budget allotted in cell order; big tables snapshot their first 200 rows) so shared links render data immediately; Run All recomputes everything live. Your local scratch session goes further: full results and variables persist in the browser's IndexedDB, so reloading the page restores your work product for any cell whose input hasn't changed (edited cells show the re-run hint instead).
Example Notebooks
The File → Examples menu ships three ready-to-run notebooks (a fresh, untouched session also shows them as a card gallery above the first cell); clicking one loads a sequence of cells you can step through or edit freely. Each showcases a distinct slice of the notebook's capability surface.
| Notebook | What it covers |
|---|---|
| The texture atlas: 1,755 flops at once | The whole single-raised flop table at 6max 100bb through agg, sliced with sql, spot-checked with spot, and equity-checked with pql. Ships its saved results, so the full analysis reads without running a cell (17 cells). |
| Anatomy of a river bluff | hu_100bb: find-spot → spot → sql → blockers → blocker-map, ending on which bluffs the blockers actually pick (19 cells). |
| Why this turn has two bet sizes (NLHE) | nlhe_6max_100bb: spot, sql and describe across a turn node where the re-solve genuinely splits its sizing (14 cells). |
Example notebooks load with their cell inputs; the texture atlas also carries saved results that render straight away (marked as a snapshot) so you can read the whole study without running anything - handy when a notebook analyses data your plan doesn't open. Run All recomputes everything live against your own account.
End-to-end Workflow
The canonical "research" pattern:
# 1. Resolve the spot you want to study.
find-spot hu_100bb --line SRP --actor IP --opponent OOP --postflop check
# 2. Fetch per-hand strategy, categorized.
$s1 = spot hu_100bb --line SRP --actor IP --opponent OOP --board Kh7s2c --postflop check --categorize
# 3. Look at what hands CBET >= 70% of the time.
filter $s1 --action f_bet100 --threshold 70 --limit 50
# 4. Compare the same spot across multiple flops.
$s2 = spot hu_100bb --line SRP --actor IP --opponent OOP --board AhKhQh --postflop check --categorize
$s3 = spot hu_100bb --line SRP --actor IP --opponent OOP --board 9h8h7s --postflop check --categorize
compare $s1 $s2 $s3 --action f_bet100 --limit 30
# 5. Isolate interesting subsets.
compare $s1 $s2 $s3 --action f_bet100 --all-min 80 --limit 25
# 6. Pipe the GTO range into PQL - equity vs random on this flop.
pql "select avg(equity(hero)) from game='omahahi', hero=$s1, villain='**', board='Kh7s2c'"
# 7. Same range, hand-type composition by the river.
pql "select histogram(handType(hero, river)) from game='omahahi', hero=$s1, villain='**', board='Kh7s2c'"
# 8. Export for offline analysis.
export $s1 --format csv --filename k72_cbet.csv Notes & Caveats
- Results and variables survive a reload. The local scratch session keeps full results and
$variablesin the browser's IndexedDB for 7 days, restored for any cell whose input hasn't changed (a solve-data re-pack invalidates them, and single results over 24 MB aren't kept). Edited cells show the re-run hint; ▶ Run All recomputes everything live. - Shift shortcuts pause while you type in a cell - Shift+S has to mean a capital S there. The ⌘/Ctrl aliases (⌘S, ⌘↵, ⌘.) still work mid-edit.
- Reach propagation is on by default. The
spotcommand propagates combo weights through every hero action on the path, soreachWeighton each row is the true probability mass the hero's range puts into that hand at the final node. Pass--no-reachto skip propagation (faster, but downstream aggregations overweight trash hands). - SQL
$prefix is required. Reference variables in SQL queries by their full$name- e.g.FROM $s1, notFROM s1. The engine rewrites the$nameinto a positional placeholder and binds the row data; without the prefix alasql looks for a literal table by that name and errors. - Bet column keys are tree- and node-specific.
hu_100bbusesf_bet25 / f_bet100at first-in nodes; its 50% size is a two-bet size, so it appears asf_raise50when facing a bet.6max_50bb(and the other 20-200bb solves, except 100bb) usesf_bet66on the flop andf_bet100on the turn; the re-run6max_100bbhas its own richer set. Facing-bet decisions addf_raise*/f_call/f_fold. Rundescribe $safter fetching a spot to see what the resolved node actually exposes. - Cells are numbered
#1, #2, #3, …in order of appearance. Reordering via the↑/↓buttons renumbers - cells have no stable identity from the user's perspective. The slug in the URL is the notebook-level identifier. - Comment cells render as markdown. If every non-blank line of a cell starts with
#, the cell is prose, not code - and it auto-runs on notebook load. See Markdown Cells. - Position names are solve-specific: HU uses
IP/OOP; 6max usesUTG/HJ/CO/BTN/SB/BB. - Rate limits apply for non-paid accounts - same as the main tool.
The hand filter supports ProPokerTools (PPT) syntax - a compact notation for describing sets of PLO hands. The same syntax drives the tool's hand filters and the calculators' range inputs (via the PQL engine); the few places the two implementations differ are flagged below.
Basic Patterns
| Pattern | Meaning |
|---|---|
AK | Hand contains an ace and a king |
AA | Hand contains a pair of aces |
Ah | Hand contains the ace of hearts |
As**Kh | Hand contains ace of spades AND king of hearts |
Operators
| Operator | Meaning |
|---|---|
, | OR - matches either pattern |
: | AND - matches both patterns |
! | NOT - excludes the pattern |
() | Grouping |
Suited Patterns
| Pattern | Meaning |
|---|---|
AxKx | Suited ace-king (same suit) |
AxKy | Offsuit ace-king (different suits) |
AxAyxy | Double-suited aces |
AxAyxz | Single-suited aces |
xxyy | Double-suited hand |
xxyz | Single-suited hand |
xxxx | Monotone (all same suit) |
Rank Patterns
| Pattern | Meaning |
|---|---|
RR | Any pair |
RROO | Two pair (two different pairs) |
RRON | Exactly one pair |
TT-77 | Pair from tens down to sevens |
Q+ | Queen or higher |
9876- | Rundown starting from 9 |
Modifier Tags
| Pattern | Meaning |
|---|---|
$ds | Double-suited (2-2 in PLO4, 2-2-1 in PLO5, 2-2-1-1 in PLO6). How often each pattern is dealt: see the 5-card Omaha suit-pattern table. |
$ss | Single-suited (2-1-1 in PLO4, 2-1-1-1 in PLO5; n/a in PLO6) |
$ts | Triple-suited (2-2-2 - PLO6 only) |
$np | No pair (all distinct ranks) |
$nt | No trips or quads in the tool's filter (pairs allowed); in the PQL engine it's identical to $np |
$op | Exactly one pair |
$tp | Two pair (double paired) |
$0g | 0-gap rundown (consecutive ranks; n-card per game) |
$1g | 1-gap rundown - one skipped rank. The tool's filter treats the number as a total gap budget, so tighter rundowns match too. |
$2g | 2-gap rundown - two skipped ranks at one step (same budget caveat in the tool) |
Advanced Syntax
| Pattern | Meaning |
|---|---|
[A-J] | Rank range: ace through jack |
[T+] | Ten or higher (tool hand filter only) |
[2s,Jc,T] | Specific card set |
15% | Top 15% of hands by strength (PQL engine and calculators; the tool's hand filter ignores percent terms) |
30%-50% | Percentile band (same caveat) |
Examples: (AA,KK):$ds - Aces or kings, double-suited. AA:$tp - Aces plus a second pair. KQ!Ks - King-queen without king of spades. RR:$ds - Any pair, double-suited.
Solver Ranges (#…)
Drop a precomputed GTO preflop range straight into any range input. Shape: #<format><stack>/<line>/<hero>[/<opponent>][/<action>]. PLO4 (Omaha-hi) only.
| Field | Values |
|---|---|
| format + stack | hu (100bb only); 6m = 100bb, or pick a stack: 6m12 6m20 6m30 6m40 6m50 6m100 6m150 6m200 |
| line | rfi, limp, 3b, 4b, 5b, 6b (also vslimp, vslimp3b, …; long forms like 3bet work too) |
| hero / opponent | HU: ip, oop. 6-max: utg hj co btn sb bb. The opponent (opener) is required for non-RFI lines. |
| action (optional) | raise, call, or vpip (raise+call). Default = the line's headline action (open / 3-bet / …). |
Solver ranges are frequency-weighted - a hand that 3-bets 40% of the time enters at weight 0.40, so equities reflect the real mixed strategy. Examples: #6m/rfi/btn - BTN's 6-max open. #6m50/3b/bb/sb - BB's 3-bet vs an SB open, 50bb. #hu/4b/ip/oop - HU 4-bet. #6m/rfi/co/vpip - CO's full open-or-limp range. Compose like any range: #6m/rfi/btn:$ds (double-suited opens).
Note: combining a solver range with : (AND) or ! (NOT) treats it as a set - the frequency weights are dropped - while , (OR) keeps and sums them. An @N suffix flattens the solver's per-hand frequencies to one constant weight, which defeats the point of a solver ref; filter with : instead.
A "Monker sim" is the output of running MonkerSolver on a specific PLO scenario long enough that its strategy converges close to GTO. Each sim is fully defined by a handful of settings: the stack depth (how many big blinds each player starts with), the player count and seating (HU vs 6-max), the bet-sizing tree (which raise sizes the solver is allowed to consider at each decision), and the abstraction parameters that decide how finely the solver groups strategically similar situations together. The two big abstraction knobs are hand buckets per street (fewer buckets = faster but coarser grouping of similar PLO hands into shared strategies) and board-texture buckets for turn and river (None / Small / Medium / Large / Perfect - at "Large" each of 8,942 turn textures gets its own strategy, at "Small" they collapse to 90). Other settings like the iteration count, convergence threshold, iso level, rake structure, and game type round out the recipe.
We run nine sims in production, all 4-card PLO. One is heads-up at 100bb (hu_100bb) with no rake; the other eight cover 6-handed play across common stack depths (12bb, 20bb, 30bb, 40bb, 50bb, 100bb, 150bb, 200bb), all with 5% rake capped at 1bb (the re-solved 100bb uses a 2bb cap). Preflop, every position can fold or pot-raise (at 20bb the open is a min-raise instead), with one limp exception per format: in HU the SB/BTN can also limp, and in 6max the SB can also limp. 3-bets and beyond are always pot-sized. Postflop bet trees differ by format. HU 100bb allows two initial-bet sizes (25% or pot), two two-bet sizes (50% or pot), and pot only for any subsequent re-raise. 6max 20bb-200bb (except 100bb) use a single initial-bet size per street - 66% pot on the flop, pot on the turn and river - with pot-only raises. 6max 100bb was re-solved in June 2026 on a richer tree: initial bets of 33% or pot and raises of 40% or pot, on every street. 6max 12bb is a small special case: most spots use just 50% pot, but spots that include the BB also offer pot as a second initial-bet size. The HU sim runs on a rich abstraction - 200 flop buckets, 100 turn/river buckets, and "Large" turn/river texture grouping - and the re-solved 6max 100bb comes close, with 100/75/75 buckets and the same fine texture grouping. The other 6-max sims share 30 buckets per street and "Small" turn and river texture grouping. A 6-max sim solves up to 57 positional matchups simultaneously - 15 heads-up spots (e.g. BBvsBTN), 20 three-way spots, 15 four-way, 6 five-way, and 1 six-way - across all 6 seats UTG / HJ / CO / BTN / SB / BB; the 100bb re-solve covers the 50 matchups through four-way. The tool's Seats picker surfaces the 15 two-seat matchups; multiway spots live in the data and the Solver Notebook. The 6max 12bb sim is the outlier here too. At such a short stack most preflop lines just go all-in, so only 17 spots are reachable, but it gets a slightly richer abstraction (40 buckets, Medium turn texture) because the smaller tree affords it.
| Solve | Players | Stack | Rake | Buckets (flop / turn / river) | Turn texture | River texture | Postflop bet tree | Spots | Decision nodes | Strategy data |
|---|---|---|---|---|---|---|---|---|---|---|
hu_100bb | 2 (HU) | 100bb | 0% | 200 / 100 / 100 | Large (8,942) | Large (3,677) | bet: 25% or pot · 2-bet: 50% or pot · 3-bet+: pot | 1 | 11,414 | 24.0 GB |
6max_12bb | 6 | 12bb | 5% (1bb cap) | 40 / 40 / 40 | Medium (958) | Small (229) | bet: 50% (also pot when BB plays) · raise: pot | 17 | 1,330 | 0.27 GB |
6max_20bb | 6 | 20bb | 5% (1bb cap) | 30 / 30 / 30 | Small (90) | Small (229) | flop bet 66% · turn/river bet pot · raise pot | 57 | 30,592 | 2.96 GB |
6max_30bb | 6 | 30bb | 5% (1bb cap) | 30 / 30 / 30 | Small (90) | Small (229) | flop bet 66% · turn/river bet pot · raise pot | 57 | 30,434 | 2.93 GB |
6max_40bb | 6 | 40bb | 5% (1bb cap) | 30 / 30 / 30 | Small (90) | Small (229) | flop bet 66% · turn/river bet pot · raise pot | 57 | 45,490 | 6.02 GB |
6max_50bb | 6 | 50bb | 5% (1bb cap) | 30 / 30 / 30 | Small (90) | Small (229) | flop bet 66% · turn/river bet pot · raise pot | 57 | 47,054 | 4.55 GB |
6max_100bb | 6 | 100bb | 5% (2bb cap) | 100 / 75 / 75 | Large (8,942) | Large (3,677) | bet: 33% or pot · raise: 40% or pot (all streets) | 50 | 50,333 | 98.9 GB |
6max_150bb | 6 | 150bb | 5% (1bb cap) | 30 / 30 / 30 | Small (90) | Small (229) | flop bet 66% · turn/river bet pot · raise pot | 57 | 309,017 | 26.8 GB |
6max_200bb | 6 | 200bb | 5% (1bb cap) | 30 / 30 / 30 | Small (90) | Small (229) | flop bet 66% · turn/river bet pot · raise pot | 57 | 416,013 | 33.5 GB |
The NLHE half of the library lives in the same solver app as PLO: a Game toggle sits above the Players and Stacks pickers, and /solver/?game=nlhe deep-links straight to it. Same account, same Core plan, nothing separate to buy. The NLHE overview page covers the pitch, this section covers the facts.
The solves. Eight 6-max NLHE cash solves from MonkerSolver, one per stack depth: 20, 30, 40, 50, 70, 100, 150, and 200 big blinds, all with 5% rake capped at 1.5bb (the depths we re-solved - 20/30/50/100bb - use a 2bb cap). The tool prints the rake for the selected depth next to the stack selector. Four of the eight are MonkerSolver 2.3.1 output; the other four (20/30/50/100bb) were re-run by us in 2026 on MonkerSolver 2.3.10 with a richer bet tree (flop 25% / 75%, turn 33% / pot, river 33% / pot / 175%) and 42 flop buckets instead of 30. The ladder totals 6.7 trillion iterations (the 150bb solve alone ran 1.9 trillion). All 1,755 strategically distinct flops are covered at every depth, with every turn and river card navigable. Strategies render on a 13x13 matrix with per-combo drilldown, a Matrix/List toggle, and mode pills for Strategy, Range and EQ - EQ computes hero's equity against the opponent's arriving range on demand, and the hand list shows each combo's full action mix rather than a single action. The data carries per-action preflop EV for every hand (the Solver Notebook reads it with preflop-spot --ev); the study tables show frequencies, and postflop there is no EV overlay, because the data doesn't carry one and the tool doesn't invent numbers.
Pure vs mixed postflop. The depths we re-solved (20/30/50/100bb) store averaged postflop strategies, so a single combo can genuinely mix - split its action across check, bet, and raise. The other four depths store a pure strategy per combo: a hand bets or it doesn't, and any mixing visible in a 13x13 cell is the blend across the combos aggregated into that cell, which the per-combo drilldown breaks out. Preflop is mixed at every depth. This is a property of the solve data, not of the decoder - the tool reports whatever the file stores.
Free tier, exactly. Every preflop range at every depth is free with no account, plus three free postflop boards a day that rotate daily (same three for everyone, shared with PLO). A strategy unit is one underlying strategy fetch - a batched request charges one per range - and tree structure and manifests don't count. Anonymous browsing gets 5,000 units per 3-hour window, a free account gets 20,000, Core gets 200,000. Each tier also has a data allowance for the same window (1 GB, 4 GB, and 32 GB) set high enough that units, not bytes, are the limit you'd meet - a heavy study session measures around 8 MB. Both budgets are shared with the PLO solver, one window per account. Practice grades 50 hands a day on a free account, 15 anonymous. A practice streak on a free account unlocks extra turn and river runouts on the daily boards, one per streak day. Core opens board entry, all 1,755 flops, and every runout.
Practice and progress. Postflop practice deals spots from solved boards and grades your action against the solver's strategy; the preflop trainer drills ranges by position and depth. Practice accuracy and answer streaks are tracked per game, so your NLHE numbers never blend into your PLO numbers. XP, training days, daily quests, and badges are account-wide across both games.
What it deliberately does not do. No heads-up NLHE solves, no multiway postflop, no ICM or tournament configurations, no nodelocking, no HUD, and no custom sims inside the plan. The flops explorer and the notebook's agg tables have no NLHE aggregates published yet (the notebook's other commands run NLHE fine). It is a fixed library of standard 6-max cash spots, done thoroughly, and that trade is the product.
Free references built from these solves: the NLHE stack-depth reference works out opening, 3-betting, and defending frequencies at all eight depths with a CSV download, the NLHE preflop charts render any position, facing line, and depth on an interactive 13x13 grid, the starting-hand rankings put all 169 hands' equity next to what every seat actually opens, and NLHE vs PLO variance measures the swing gap between the two games. Hand Tracker grades your NLHE preflop decisions against these exact solves.
A free, browser-based hand-vs-hand equity calculator for eight games: NLHE, PLO4, PLO5, PLO6, Short Deck, and three hi-lo split-pot games - O8, Big O (5-card), and Big O6 (6-card). Set up 2 to 6 specific hands (up to 8 in bomb-pot double-board mode), add a board and dead cards, and get live win/tie percentages with no server round-trips and no account required. Open it at solvepoker.app/odds; the same calculator also answers at preset landing URLs like /plo5-equity-calculator, /omaha-hi-lo-calculator (and /plo8-calculator), /big-o-calculator, /bomb-pot-calculator, and /short-deck-equity-calculator. For range-vs-range equity see Range Equity.
Overview
The calculator picks its math automatically based on how many runouts are left:
- Flop, turn, or river: enumerates every remaining runout exactly. The result is labelled ✓ exact with no margin of error.
- Many-way preflop: when the hands between them consume enough of the deck that only a handful of boards remain (for example, 6 hands of PLO6 leave just 4,368 possible boards), the calculator still enumerates exactly. You get ✓ exact preflop too.
- Heads-up / light preflop: falls back to a Monte Carlo simulation - tens of thousands of random runouts per second - and converges on the equity with a confidence interval that shrinks as more runouts accumulate. Stops early and shows a converged badge once every hand is within ±0.3 pp.
Two variant quirks: standard-deck NLHE preflop always runs Monte Carlo (even heads-up it has 1.7 million possible boards), while Short Deck's 36-card deck makes exact preflop more common. The equity math itself always runs locally in your browser.
Setting Up Hands
Game pills - Pick NLHE, PLO4, PLO5, PLO6, Short Deck, O8, Big O, or Big O6 in the bar above the calculator. A second pill group, Single board / Double board, appears for the games that support bomb pots (high-only games on the standard deck - see Bomb Pots). Switching between a high game and its hi-lo twin (PLO4 ↔ O8, PLO5 ↔ Big O, PLO6 ↔ Big O6) keeps every card in place, since only the scoring changes; any switch that changes the hole-card count or the deck clears the board and hands. In Short Deck the deck drops to 36 cards - the bank renders a 9-column grid with no 2-5 columns at all - and flushes beat full houses.
Card bank - The 52-card grid in the middle. Click a card to place it into the currently selected slot. Cards that are already in play (in any hand, on the board, or marked dead) are grayed out and unclickable.
Always-selected invariant - One slot is always highlighted as the "target." After you place a card, selection jumps to the next empty slot automatically, so you can click through the bank without touching slots in between. Press Esc to re-target the first empty slot anywhere on the page.
Clearing a slot - Click a filled slot to clear it. Right-click and Del / Backspace do the same. The freshly-empty trailing slot becomes the new selection target so your next bank pick lands in an obviously empty position.
Replacing a card - Click the filled slot to clear it, then pick the new card from the bank.
Arrow-key navigation - Use ← → to move the selected slot left / right within a row, and ↑ ↓ to jump between hands, the board, and the dead-cards tray.
Adding / removing hands - Use the dashed "+ Add hand" row below the last hand to add another (up to 6 on a single board, 8 in double-board mode). Once three or more hands are set up, every hand shows a × button to remove it; the count can't drop below two.
Board & Dead Cards
Board - Five slots in the right column: flop (grouped in a dashed box), turn, and river. Click a slot to select it, then pick a card from the bank. Partial flops (1 or 2 cards) are invalid - an amber validation banner ("Flop must have exactly 3 cards.") blocks the compute until you finish the flop. Similar banners appear if dead cards exhaust the deck.
Dead cards - Below the board, click Add dead cards to reveal the dead-cards tray. Any card placed here is removed from the deck, which matters when you're simulating a spot where opponents have folded known cards. Dead cards have no direct equity effect but they do change the probability distribution of runouts. The tray grows on demand (one trailing empty slot is always shown) and a count badge sits next to the toggle. Expand/collapse the tray at any time - the open/closed state is preserved in share links.
Hi-Lo Games (O8, Big O, Big O6)
The three hi-lo pills score 8-or-better split pots: O8 is 4-card Omaha hi-lo, Big O the 5-card game, Big O6 the 6-card one. Each hand's headline number stays its total pot share, with a scoops X% - quartered Y% sub-line so the two ways a hi-lo hand wins or bleeds are visible at a glance.
A Split breakdown accordion under the results opens the full accounting per hand: Scoop, Wins hi side, Wins lo side, Both sides, Quartered, and Total pot share. Under the equity bar, a low-possibility line reports how often a qualifying low is possible on the remaining runouts and how often someone actually makes one; on a complete board with fewer than three distinct ranks eight-or-lower, a banner says "No low possible - the whole pot plays for high."
Hi-lo scoring needs WebAssembly (see Under the Hood), and hi-lo games have no double-board mode and no strength percentile chip.
Bomb Pots (Double Board)
Switch the board-mode pills to Double board and the right column shows two labelled boards, Top board and Bottom board, each with its own flop, turn, and river slots, plus a Deal both flops button (shortcut F) that deals both from one shuffle. Up to 8 hands can sit in a double-board pot.
Each hand's headline is its total pot share across both boards, with a top X% - bottom Y% sub-line. A Per-board breakdown accordion adds Total share / Top board / Bottom board / Both boards / Scoops both columns, and a correlation line reports how often the hand that wins the top board also wins the bottom.
Double board is available for the high-only games on the standard deck (NLHE, PLO4, PLO5, PLO6) - the pill group is hidden for hi-lo and Short Deck. It needs WebAssembly, and double-board share links use the self-contained hash form rather than a short URL.
Randomize & Locking
Randomize (a button in the controls column; shortcut r) - Replaces every unlocked hand with a fresh set of random cards drawn from the remaining deck. Useful for running a specific hero hand against N random opponents, or for exploring equity intuition quickly. After randomizing, the selection jumps to the first empty board slot so your next click goes to the flop.
Locking a hand - A padlock icon appears in the top-right corner of any hand card once all of its slots are filled. Click it to lock - the icon lights up, and Randomize will skip that hand (and exclude its cards from the draw pool). Click again to unlock.
Auto-unlock - Removing any card from a locked hand makes it incomplete, which automatically unlocks it (the padlock icon disappears). Re-fill the hand and you can lock it again.
Clear all (the Clear button next to Randomize; shortcut c) - Wipes all hands, the board, and dead cards, and unlocks everything. No confirmation prompt, but the change is undoable - press ⌘ Z / Ctrl+Z to restore the previous state.
Undo - Every destructive action (Clear, Randomize, variant change) snapshots the previous state. ⌘ Z / Ctrl+Z restores it once; there's no redo and no deeper history. Touch devices skip the toast notifications since there's no keyboard to undo with.
Reading Results
Per-hand readout - Each hand card shows:
- Equity % - The headline number: this hand's total share of the pot (outright wins plus its share of chops).
- Win / Tie split - Shown when chops are possible. Win is the probability of winning outright; Tie is the expected share of pot from chops (fractional wins when multiple hands tie).
- ±margin (Monte Carlo only) - The 95% confidence-interval half-width in percentage points, shown on hover over the equity number. Shrinks as more runouts accumulate.
- ✓ exact (flop/turn/river) - Replaces the margin once the result is from exact enumeration.
- P chip - Each complete hand shows its starting-hand strength percentile (lower is stronger), from the same tables as Starting Hands. NLHE and the PLO high games only - Short Deck and the hi-lo games have no percentile tables.
Equity bar - The wide horizontal bar below the three columns. Each segment is one hand's outright-win share, colored to match that hand's accent. Tie groups appear between hands as diagonal stripes that interleave the colors of every hand tied in that group, so you can see at a glance which hands chop and how often. The bar fades to empty while a new compute is in flight so you're never reading stale numbers. Focus or hover the bar (it's keyboard-focusable) to reveal a full numeric breakdown - one row per segment with swatches and percentages, handy for keyboard users and screen readers.
Status strip - Below the bar. Idle: "Pick cards to see live equity.". Exact: "✓ Computed exactly from N runouts · X.XXs". Monte Carlo running: a progress bar plus iteration counts plus current margin. Monte Carlo done: green check, total iterations, optional converged badge if it stopped early (all hands inside ±0.3 pp), and the elapsed time. Errors render in red.
Settings (gear)
The ⚙ gear button in the right-hand controls column opens a slide-in settings sheet (the gear icon replaced the older ··· button). It contains two actions:
- Copy share link - see Share & PNG Export below.
- Save PNG - see Share & PNG Export below. Disabled until at least two hands have computed equity.
Press Esc or click the backdrop to close the sheet. Mobile slides the sheet up from the bottom; desktop slides in from the right.
Share & PNG Export
Copy share link (in the gear sheet) - Copies a URL that encodes your current variant, hands, board, dead cards, any hand locks, and the dead-cards tray state. Anyone who opens the link sees the exact same setup. The button is two-tier:
- Short URL (preferred) -
solvepoker.app/odds/<8 chars>. The state is POSTed to/api/odds/shareand stored in Cloudflare KV; the response is a short ID that becomes the last path segment. Every mode gets one, double board included, and every share resolves at/oddswhichever landing route you copied it from - the stored state carries the game and the board mode. Links minted before 2026-08-23 used/odds?u=<8 chars>and still work. - Hash fallback - If the network is unavailable or the API errors, the calculator falls back to
solvepoker.app/odds#<encoded-state>. Self-contained, no server needed.
The calculator doesn't write to the URL as you click around - the address bar stays clean during normal use. If you land on the calculator via a share link (either form), it hydrates once and then strips the URL back to a clean /odds.
Save PNG - Renders an offscreen 720×variable portrait PNG using the actual hand columns, equity bar, and board you see on screen, with a slight font and card-size bump for legibility, in your active theme's colors. Title block reads SolvePoker plus a subtitle that follows the mode ("Hand Equity", "Hi-Lo Equity", or "Bomb Pot Equity"); a variant chip sits top-right. The footer carries the page you exported from (solvepoker.app/odds, or the landing route like /big-o-calculator), and the file is named to match (odds.png, omaha-hi-lo.png, bomb-pot.png). Dead cards are included if any are set, captioned Dead so they don't read as part of the board. Lock buttons, remove buttons, empty dead-card slots, and the dead-cards toggle are hidden in the snapshot for a clean export.
The per-hand readout in the PNG is the same one the page draws, in every mode: hi-lo exports carry the scoops X% - quartered Y% line, bomb pots carry top X% - bottom Y% with both boards labelled Top and Bottom, and high-only exports carry the Win/Tie split. Under the boards, a status line records how the number was produced - Exact · 1,560 runouts, or Monte Carlo · 150,000 simulations · ±0.26% · converged. A sampled equity without its sample size and margin isn't worth quoting, so the export carries both.
Keyboard Shortcuts
Press ? at any time inside the calculator to pop up the full cheat sheet. The floating ? button in the bottom-right does the same thing for mouse users.
| Key | Action |
|---|---|
? | Toggle the keyboard-shortcut overlay |
Esc | Re-select the first empty slot (or close the help overlay / settings sheet) |
← → ↑ ↓ | Move the selected slot across hands / board / dead cards |
Del / Backspace | Clear the currently selected slot |
r | Randomize unlocked hands |
c | Clear everything |
[ / ] | Cycle games within the active scoring family (high-only or hi-lo) |
2 / 4 / 5 / 6 / s | Jump to the game with that hole-card count in the active family; s = Short Deck (high-only single board only) |
L | Toggle high-only ↔ hi-lo scoring |
B | Toggle single ↔ double board (bomb pot) |
F | Deal both flops from one shuffle (double board only) |
⌘ Z / Ctrl+Z | Undo the last destructive action (one-deep) |
Inside the card bank, Tab moves focus into and out of the grid (exactly one card is in the tab order at a time - the "roving tabindex" pattern). Once you're inside, ← → ↑ ↓ move across the 4×13 grid, and Home / End jump to the ends of a suit row.
Use Cases
- Spot study - Lock hero's exact PLO5 hand, randomize 3 villain seats, hit
ra few times to feel the equity range across plausible villain holdings. - Sweat a runout - Punch in two hands and a flop, then click turn and river slots to walk through specific runouts. Each card you place collapses the runout space; the engine instantly re-enumerates.
- "What if villain folded a king?" - Open dead cards, drop the king(s) into the tray, see how your equity shifts. Useful for analyzing folds in multi-way pots.
- Many-way preflop coolers - PLO6, all six hands set, fully exact in milliseconds because the deck has only ~5k boards left. Faster than any sim.
- "How often does A2 get quartered?" - O8, your A2xx against two calling hands, read the Quartered column in the split breakdown.
- Bomb-pot table stakes - Double board, deal both flops, eight hands in. The correlation line tells you how often one hand scoops both.
- Coaching exports - Set up the spot, hit Save PNG, drop the image into Discord/Slack/Notion. The image is self-contained and matches what's on screen.
Under the Hood
The compute kernel is compiled to WebAssembly. On browsers that support cross-origin isolation it runs multi-threaded across every CPU core; otherwise it falls back to a single-threaded WebAssembly worker, and on browsers that don't support WebAssembly at all to a pure-JavaScript evaluator for high-only single boards. Hi-lo and double-board modes are WASM-only - without it their pills disable with a note.
Cactus-Kev evaluator - Hand strength is evaluated with the Cactus-Kev 7-card lookup, which gives roughly a 5× Monte Carlo speedup over the previous evaluator on Omaha hands.
Auto exact vs Monte Carlo - Before computing, the orchestrator counts how many runouts are possible. If that number is small enough for the current backend (about 200k on multi-threaded, 30k on single-threaded; hi-lo and double board use 120k/20k on their heavier evaluations), the calculator enumerates every board exactly. Otherwise it runs Monte Carlo. This is why many-way preflop spots (like a 6-hand PLO6 all-in simulation) resolve to ✓ exact in milliseconds, while heads-up preflop uses Monte Carlo with a small confidence interval.
Adaptive termination - Rather than always running a fixed iteration count, the Monte Carlo loop stops early once every hand's 95% confidence interval (Wilson score) is within ±0.3 percentage points. Most preflop spots converge in well under a second on desktop; when that happens the status strip adds a converged badge so you know the engine stopped on purpose.
Privacy - No account needed, and the equity math itself runs entirely in your browser. Each compute logs an anonymous usage ping that includes the cards you entered, so we can see how the tool gets used. Share links are separate: state is only stored when you click Copy share link.
A free, browser-based range-vs-range equity tool for seven variants: NLHE, PLO4, PLO5, PLO6, the hi-lo games O8 and Big O, and Short Deck. Type a range per player using PPT range syntax, optionally fix a board, and run the simulation. Up to six players, full PPT macros and percentile selectors, no account required. Open it at solvepoker.app/range-calc. For specific hands (not ranges), see Hand Equity.
Overview
Range Equity is a thin UI on top of the PQL Engine. Each row is a player slot; the text you
type is parsed as a PPT range. Press Run (or ⌘ Enter) and the engine returns each player's
all-in equity through the river. Results show a single percentage per
player plus a 95% confidence half-width and trial count below.
The simulation runs in your browser. Each completed run logs an anonymous usage ping that includes the generated query (your ranges and board), so we can see how the tool gets used; Copy share link separately stores the full state behind a short ID in our KV.
Game Variants
The variant pills (top right) switch between seven games:
| Variant | Hole cards | Split | Engine |
|---|---|---|---|
| NLHE | 2 | Hi | game='holdem' |
| PLO4 | 4 | Hi | game='omahahi' |
| PLO5 | 5 | Hi | game='omahahi5' |
| PLO6 | 6 | Hi | game='omahahi6' |
| O8 | 4 | Hi/Lo | game='omaha8' |
| Big O | 5 | Hi/Lo | game='omaha85' |
| Short Deck | 2 | Hi | game='shortdeck' |
Switching variants swaps the per-variant default ranges if you haven't
edited them. The placeholder also updates: ** for NLHE and
6+, **** / ***** / ****** for
PLO4/5/6. The board input format doesn't change with variant.
Players
Minimum 2, maximum 6. + Add player appears below the
last row when fewer than six are filled; click to add one with a
variant-appropriate default range. Each row past the first has a × remove button on the right.
Per-variant default ranges (used as the starting point on first load):
| Variant | Player 1 default | Player 2 default |
|---|---|---|
| NLHE | JJ+,AKs,AKo | AA |
| 6+ | AKs,KQs,QJs | QQ+ |
| PLO4 | 4x5x6y7y,6789,RR$B$B | AA** |
| PLO5 | 4x5x6y7y8z,6789T,RR$B$B$B | AA*** |
| PLO6 | 4x5x6y7y8z9w,6789TJ,RR$B$B$B$B | AA**** |
| O8 | A2$B$B | 25% |
| Big O | A23$B$B | AA*** |
Range Input
Each player input accepts the full PPT Generic range syntax -
pairs (AA), suited shapes (AKs in classic, or AK:$s in generic), rank/suit variables
(RROO, AxKx), spans (TT-77),
percentile selectors (15%, 15%-30%),
modifier macros ($ds, $ss, $ts, $np, $op, $tp, $0g, $1g, $2g, $B, $M, $Z, $L, $N, $F, $R, $W), bracket sets
([A-J], [2s,Jc]), and the operators , (or), : (and), ! (not), (...) grouping. See the PPT Filter Syntax section for the full
reference.
Validation runs at Run time, not as you type - if a
range fails to parse, the error banner above the run button shows the
message and (when known) the column position. Empty ranges are
invalid: every player slot needs at least something, even if it's just ****.
Optional Board
The collapsible Add board (optional) details element
takes a board as concatenated card codes - e.g. 9s5s2c (flop), AsKhQh2c (turn), or AsKhQh2c5d (river). 3, 4, or 5 cards. Whitespace is
stripped; duplicates are rejected; invalid tokens are flagged.
Comma-separate several boards (9s5s2c,Ks7d2h) and the run
averages over the set - each segment must be a valid 3-5 card board on
its own. Handy for "this range across three flop textures" questions.
When the board input has a parse error the box turns red and the hint text below it explains what went wrong. Leave the field empty to compute preflop equity.
No dead-card support here. If you need to remove specific known-folded cards from the deck, use Hand Equity instead.
Running a Query
Run (the primary button, lower right) starts the
simulation. Keyboard shortcut: ⌘ Enter / Ctrl+Enter. While running, the button switches to Cancel (red) and the status badge in the players
header shows "Running… Nms" - click to stop the
in-flight run.
When done, the badge turns green and shows total elapsed time. Trial count and filter count appear below the run row: "50,000 trials in 234ms".
PLO5/6 and Big O query timing - Some 5- and 6-card
queries can take ~5-60 seconds to calculate. The slowest are typically
the first percentile selector (15%, 5%-25%)
used on a given game - the engine builds a hand-strength
canonical map, which is then cached. After 5 s of running with no
progress a yellow hint appears.
Reading Results
Each player card shows two pieces of information:
- Equity - The big number, in that player's own series color (e.g. 42.31%). For Monte Carlo runs this is the trial average; for tractable enumerations it can be the exact value.
- Confidence & sample - The muted line below: "±0.25% · n=50000". The first number is the 95% confidence half-width; the second is how many trials contributed.
On the hi-lo variants (O8, Big O) each card adds three chips - hi X% · lo Y% · scoops Z% - with an explainer under the results: hi and lo include ties, so the columns overlap and one player's hi plus lo can pass 100%.
Once two or more equities are in, a stacked equity bar renders under the player cards with each player's share in their color, and a parsed board preview shows the entered board as real card tiles inside the board box.
While running, the equity cell shows an animated …;
idle slots show -.
Settings (gear)
The ⚙ gear button opens a slide-in sheet with three Run params and three Actions:
- Trials - 1,000 to 5,000,000. Default 50,000. More trials = tighter CI at the cost of compute time.
- Max seconds - 1 to 600. Default 60. The engine stops at this wall-clock budget if it hasn't already converged.
- Seed - Default 42. Same query + trials + seed = identical results.
Actions: Copy share link, Copy results, Save PNG. Copy results needs a run in progress or finished; Save PNG waits for the run to finish, since a half-computed card would carry equities that are about to move.
Share & PNG Export
Copy share link - Two-tier, same as the other calculators:
- Short URL -
solvepoker.app/range-calc/<8 chars>. POST to/api/range-calc/share, KV-backed. The older?u=<8 chars>form still resolves. - Hash fallback -
#v=<game>&p1=...&p2=...&b=...if the API is unavailable.
Copy results - Plain-text dump suitable for pasting into chat:
Player 1 = 42.31% (95% CI ±0.25%, n=50000)
Player 2 = 57.69% (95% CI ±0.25%, n=50000)
50000 trials in 234ms On the hi-lo variants the dump adds the hi / lo / scoop lines per player.
Save PNG - 1080-wide portrait PNG in your active
theme's colors. Title block SolvePoker plus an accent
subtitle ("Range Equity") and a variant chip top-right. Each player
gets a colored card with their range on the left and the equity / CI
on the right, above the same stacked equity bar as on screen. The
optional board renders as real CardSlot tiles for a
single board (matching what you see on the Hand Equity calculator) or a comma-separated text
row for multiple boards. Footer: solvepoker.app/range-calc. Saved as range-calc.png.
Keyboard Shortcuts
| Key | Action |
|---|---|
⌘ Enter / Ctrl+Enter | Run the query |
Esc | Close the settings sheet |
Use Cases
- Preflop SB vs BB equity in PLO5 - Set the variant to PLO5, leave the SB on the default 3-bet range, paste the BB defending range, run. The two big accent numbers tell you how the cold money splits.
- Single-suited rundown vs aces on the flop - Player 1
4x5x6y7y, Player 2AAxx, board9s5s2c, run. - 3-handed bingo equity for a tight opener - UTG
$B$B$B$B$ds, BTN10%, BB****, run. Compare with a wider UTG range. - Compare two opening ranges - Run with range A, copy results, swap range A for range B, run again, paste both into a doc/Slack.
Limits & Tips
- Max 6 players - Hard cap (more than that exhausts the deck on PLO6 anyway).
- No dead cards - Use Hand Equity if you need to remove known-folded cards.
- No range locking - Each run uses whatever's currently in every box. There's no "freeze player N's range".
- PLO6 wildcard ranges run fine - plain hand patterns like
'******'stream instead of materialising. Only compositions that can't stream fail, with a clear "too large to materialize" error. - Determinism - Same ranges + board + trials + seed = identical numbers across machines. Useful for sharing exact results.
A free, browser-based hand-category histogram for ranges in NLHE, Short Deck, PLO4, PLO5, and PLO6. Pick one to six ranges and a street; the chart shows how often each range makes a pair, two pair, trips, straight, flush, full house, quads, or straight flush - with a random-hand baseline overlay (precomputed at 2 million trials per variant) so you can see whether your range really hits more than "normal." Open it at solvepoker.app/hand-dist.
Overview
Two questions this answers in seconds:
- "What does my range actually make on the river?" - Pick Hand Category, set the street, see the distribution per range.
- "At showdown, which range's hand class wins?" - Pick Winning Hand, run multi-way, see the breakdown of
winningHandType().
Behind the scenes the page is a thin UI on top of the PQL Engine's histogram(handType(...)) and histogram(winningHandType()) aggregators. The
histogram computes in your browser; like the other calculators, each
completed run logs an anonymous usage ping that includes the generated
query (your ranges and board).
Game Variants
Variant pills at the top: NLHE, 6+ (Short Deck), PLO4, PLO5, PLO6. Switching variants swaps the per-variant default Player 1 / Player 2 ranges if you haven't edited them. Hand categories shown match the variant (quads/full-house/etc. are global; PLO surfaces them more often). One 6+ quirk: the chart sorts flush above full house (Triton ranking, matching the evaluator).
Player Ranges
Up to six rows, minimum one. Player 1 starts with a
variant-appropriate default; Player 2 is optional - type a range
or click + Add Player 2 to fill it with a comparison
preset (a varied opener). Player 3+ default to wildcards
(**** in PLO4, etc.) so they don't accidentally exhaust
the deck.
Each row has a colored label, a monospace text input that accepts PPT range syntax, and a × remove button. Up to six ranges total; rows are colored to match the
chart series colors.
Optional Board
The Add board (optional - hides random-hand baseline) collapsible accepts the same concatenated card-code format as Range
Equity (9s5s2c, AsKhQh2c, AsKhQh2c5d), including comma-separated multi-boards
(9s5s2c,Ks7d2h) to average over several textures. When a
board is set, the dashed random-hand baseline lines disappear from the
chart - the runout is no longer random, so the baseline doesn't
apply.
Street & Hand Type
Two pill rows under the ranges:
- Street: Flop / Turn / River. The
handType(player, street)function is evaluated at the chosen street. - Hand: Hand Category or Winning Hand.
- Hand Category - Per-player histogram of
handType()at the chosen street. The chart shows one series per range. - Winning Hand - Single histogram of
winningHandType(): which hand category won the trial at showdown. Always evaluated on the river (other streets are disabled). Requires at least 2 ranges.
- Hand Category - Per-player histogram of
Running a Query
Run button on the lower right of the inputs pane;
keyboard shortcut ⌘ Enter / Ctrl+Enter. Cancel while running stops the in-flight worker.
Status badge top-right of the results pane: Idle, Running… Nms, Done · Nms, or Error. If a PLO5/PLO6 run stalls past 5 s a yellow hint
appears; first runs can be slower while the engine warms up.
Reading the Chart
The chart is a horizontal bar histogram - one row per hand category, up to six colored series stacked within each row. A legend above the chart maps each color to its player and labels the dashed baseline tick. Default order is best hand at top (straight flush → quads → full house → flush → straight → trips → two pair → pair → high card; in 6+ mode flush sorts above full house). The sort toggle above the chart flips between ↑ best hand top and ↓ worst hand top.
When no board is fixed, dashed vertical lines mark the random-hand baseline probability for each bucket
(in Winning Hand mode the baseline assumes a heads-up pot, so it
hides for 3+ players). Hover or tap any row for a tooltip showing
per-series percentages with their ± Monte Carlo sampling
error and a cumulative that hand or better column; with
exactly two series the tooltip adds the delta in
percentage points. Click or tap to pin the tooltip
(accent border); click again or press Esc to unpin.
Mobile screens (≤768 px) shorten labels for legibility - "straightflush" → "st flush", "twopair" → "2pair", etc.
Trial count and elapsed time appear below the chart: "50,000 trials in 234ms". A note also appears when the baseline is hidden (because a board is set).
Settings (gear)
Same as Range Equity: Trials (1k-5M, default 50k), Max seconds (1-600, default 60), Seed (default 42). Actions: Copy share link, Copy results, Save PNG (the latter two disabled until you have results).
Share & PNG Export
Copy share link - Same two-tier short URL
(solvepoker.app/hand-dist/<8 chars>) backed by /api/hand-dist/share, with hash fallback that encodes
variant, ranges, board, hand-type mode, street, and sort.
Copy results - Plain-text dump:
# handType / river
Player 1:
straightflush 0.10% (50)
quads 0.05% (25)
fullhouse 2.10% (1050)
...
Player 2:
... Save PNG - Landscape 1920×variable. Inputs panel on the left (variant pills, range
rows, optional board, street/hand pills); chart on the right (rendered
from a self-contained SVG whose colors are read from your active theme
at export time, with high-DPI fonts).
When the optional board is set, it renders as real CardSlot tiles for a single board (matching what you see
on the Hand Equity calculator) or a
comma-separated text row for multiple boards, placed in the inputs
panel directly under the range rows. Title SolvePoker + accent "Hand Distribution"; footer solvepoker.app/hand-dist · N trials. Saved as hand-dist.png.
Mobile Layout
Below 768 px the page collapses to a single column with an Inputs / Results tab strip at the top. A pulsing dot on the Results tab tells you a run is in progress while you're editing inputs. The settings sheet slides up from the bottom on mobile (side drawer on desktop). Bucket labels and tooltip text shrink to fit narrow screens.
Use Cases
- "Does my preflop range hit more flushes than my opponent's?" - PLO6, Player 1 your range, Player 2 a wider opener, street = River, mode = Hand Category. Compare the flush bucket.
- "Random hand baseline on the turn" - Single range =
****, no board, street = Turn. The dashed lines and the colored bar should match (the bar is the random baseline). - "AAKK$ds vs broadway double-suiteds on AsKhQh" - Two ranges, board
AsKhQh, street = Flop. See how often each range makes top set / two-pair / trips. - "At showdown which range wins more flushes vs trips" - Two ranges, no board, mode = Winning Hand, street = River. The chart shows the winning class distribution.
Limits & Tips
- Max 6 ranges; min 1 (single-range mode just plots the one distribution).
- Winning Hand mode needs at least 2 ranges and is river-only (other streets are disabled).
- No dead cards.
- First PLO5/PLO6 percentile query takes ~30-60 s to build the canonical map; subsequent queries on the same game are fast.
- Determinism - Same ranges + board + trials + seed = identical histogram across machines.
A free, full-featured Monte Carlo variance simulator for cash games and tournaments. Covers every standard analysis a serious grinder needs - confidence bands, downswing probabilities, risk of ruin, Bayesian "am I a winner?" credibility, backing deals, a monthly year-breakdown, and a multi-year stake-progression career sim. No account required, everything runs locally in your browser. Open it at solvepoker.app/variance.
Overview
The page opens with a big chart showing thousands of possible futures from your exact inputs. The EV line sits in the middle; confidence bands fan out around it (95% faint, 70% bolder); twenty thin sample-run lines trace actual simulated paths; and the single best and single worst of 1,000 trials highlight the extremes. Below the chart, the three headline numbers tell you the expected profit, the probability you finish profitable, and the 95% range around your EV.
Below that, collapsible analysis cards drill into specific angles of the same underlying simulation. Stake Progression sits directly under the headline chart in cash mode; a "More analysis" stack follows in this order: Outcome distribution, Bankroll & risk of ruin, Downswings, Am I a winner?, Your year month by month, and Backing deal. All cards are expanded by default; click the chevron in the top-right of a card to collapse it.
When To Use It
Useful any time variance is in the picture, but most users come for one of:
- Pre-coaching reality check - Before you commit to a stake or a study budget, see what a normal year actually looks like at your assumed winrate.
- Bankroll planning - Plug your roll into the Bankroll card and see your live risk of ruin; the required-bankroll table tells you what to roll for at each tolerance.
- Stake-move decisions - Use the Stake Progression card to model years of climbing the ladder under realistic decay assumptions.
- Evaluating a backing offer - The Backing card gives both player EV and backer EV plus end-in-makeup and worst-makeup stats so you can sanity-check a deal.
- "Am I actually a winner?" - Bayesian credibility on your true winrate / ROI given your observed sample.
Cash vs Tournament
The toggle at the top of the page switches between two distinct simulation models:
- Cash - A random walk over 100-hand blocks. Each block's profit is drawn from a Student-t distribution scaled to your winrate and SD in bb/100. The df (degrees of freedom) controls how heavy the tails are - lower for games like PLO where coolers dominate. Inputs: winrate, standard deviation, number of hands, and (via Advanced) the tail shape.
- Tournament - Each tourney is a discrete outcome drawn from a finish distribution derived from buy-in, rake, field size, payout structure, and your target ROI. Inputs: number of tourneys + the tourney preset (or custom).
Assumptions. The sim treats your winrate and SD as constant over the horizon - it doesn't model tilt, game selection, or schedule drift, so treat its numbers as a lower bound on variance. The Bayesian "Am I a winner?" card uses a weakly informative Gaussian prior - cash mode assumes most players are within ±20 bb/100 of breakeven (2σ), tournament mode assumes most regs are within ±60% ROI of breakeven. At 100k+ hands (or 1k+ tourneys) the data dominates; at smaller samples the prior keeps the posterior realistic. Both modes return 0.5 for the edge case of zero observations.
The layout stays the same in both modes - the same chart, headline stats, and analysis stack - with mode-specific controls and cards where the math differs.
Inputs & Presets
Inputs read like a sentence: "I win +3 bb/100 over 100k hands of PLO 6-max." (PLO 6-max with SD 140 is the default; older builds defaulted to NLH 6-max.) Click any underlined number to edit it; click the game-type picker to swap the preset (NLH 6-max, NLH Full Ring, NLH Heads-Up, PLO 6-max, PLO Full Ring, PLO Heads-Up, Short Deck 6-max, Mixed games, or Custom).
The preset carries the standard deviation (SD) automatically - NLH 6-max is 100 bb/100, PLO 6-max is 140 bb/100, etc. The current SD is shown as a chip next to the sentence and can be overridden in Advanced.
Hands input - Accepts 100k, 2M, 1,000,000, and 1000000 interchangeably. When you click to edit,
the value shows comma-grouped (100,000) so you can actually count zeros.
Capped at 10 million hands per sim.
Re-roll - The 🎲 button next to the inputs generates a new random seed and re-runs the simulation. Same inputs, different cards - a quick way to feel the variance. The dice icon is large and pulses for a beat after each click so you register the seed change.
Tournament sentence reads similarly: "I play 10,000 tourneys of Reg MTT ($109) at +10% ROI." Presets include Reg MTT, Turbo MTT, Sunday Major, Satellite, and Custom. Deeper overrides (payout structure, field size, rake, realistic vs. uniform finish distribution) live in Advanced.
Reading the Chart
The main chart layers several pieces of information:
- Solid EV line - Mathematically exact expected value over time.
- 70% confidence band (bolder) - The range where 70% of outcomes fall. Monte-Carlo-derived from trial quantiles at ~51 checkpoints along the x-axis, linearly interpolated.
- 95% confidence band (faint) - The range where 95% of outcomes fall. Same MC-derived method as the 70% band.
- 99.7% envelope (dashed outline) - The rare-tail outline. Practically all trials stay inside it; a few pathological ones may peek past.
- Twenty thin sample runs - Actual simulated paths picked at evenly-spaced trial indices, drawn red when a run ends below breakeven so losing runs read as losing at a glance. These are honest examples of what your session could look like.
- Solid "best" line - The single luckiest trial (the legend names the trial count, "Luckiest of 1,000" at the default).
- Solid red "worst" line - The single unluckiest trial.
- Dashed zero line - So you can see at a glance where breakeven lies.
The y-axis is titled Net won, and an on-chart legend maps the lines: EV, Luckiest of N, Unluckiest of N, and the 70% / 95% bands.
The Y-axis auto-switches between bb (big blinds) and BI (buy-ins, 1 BI = 100 bb) depending on magnitude - small sims read in bb, larger sims in BI. Tap or hover anywhere on the chart to pin a vertical guide with the exact EV, 95% CI, best, and worst values at that point.
A small spinner appears centered on the chart while a new sim is running (most visible on re-roll). The chart stays mounted during recompute so the axes don't jump.
Headline Stats
Three numbers sit directly under the chart:
- Probability of profit - Share of trials that finished above zero.
- Expected profit - The mean of the simulated endpoints (1,000 trials by default).
- 95% of runs land between - The 2.5% and 97.5% quantiles of the endpoint distribution. An outcome band, not a confidence interval on your winrate.
Downswings
Answers "how bad does it typically get, and for how long?". The card's intro line shows live parameter chips (winrate, hands, SD) drawn from the actual sim above, so it's always clear which inputs the numbers below are conditional on. Three pieces:
- Depth table - Probability that your worst peak-to-trough drawdown (at any point in the sample) exceeds each threshold: 500, 1,000, 2,500, 5,000, and 10,000 bb (larger values display in BI).
- Duration table - Probability that at some point you're stuck below a prior peak for at least N hands. Thresholds scale to your sample: 2%, 5%, 10%, 25%, and 50% of it, so 2k through 50k at the default 100k hands.
- Peak-to-trough inside a window - Your worst drawdown observed within any sliding window of size W. Three windows displayed simultaneously: W = hands/12 ("any month-equivalent stretch"), W = hands/4 (quarterly), and W = full sample. Threshold pills at the top let you pick any of 5 / 10 / 15 / 20 / 30 / 50 / 75 / 100 BI; the three probabilities update instantly.
Probabilities are capped at the resolution of the simulation - with 1,000 trials a
zero-count event renders as <0.1% (not 0.0%, which
would overclaim certainty) and a unanimous event as >99.9%. If you
bump the trial count up or down in Advanced the cap moves with it, so a 500-trial sim
floors at <0.2% honestly.
Bankroll & Risk of Ruin
"I have [X] BI" - A first-class input at the top of the card. Edit it and the risk-of-ruin number to the right updates live, without re-running the simulation - bankroll is a post-hoc parameter derived from the simulation's per-trial minimum cumulative. Dragging the slider is near-instant; the trajectories themselves are unchanged.
The displayed RoR is the Monte Carlo value (from the simulation's actual trajectory minima - the fraction of trials that ever dipped to or below −bankroll) whenever one is available, falling back to the Malmuth closed-form RoR = exp(−2μB/σ²) before the sim returns.
Required bankroll by risk tolerance - Table of the bankroll needed to hold RoR at or below 1%, 2%, 5%, and 10%, computed from the simulation's own per-trial minima once a result is in (closed-form only before the first run), so it always agrees with the headline RoR. Click any row to re-compute the sensitivity table below at that target - useful if your personal tolerance isn't the default 5%.
"If you're wrong about your winrate" - Sensitivity table showing how the bankroll requirement balloons under the assumption that your true winrate is lower than you think. Scenarios: your assumption, slightly worse (½×), much worse (¼×), and breakeven (→ ∞). Target RoR defaults to 5%; click a row in the table above to change it.
Outcome Distribution
Seven anchor points of the endpoint distribution, ordered top to bottom:
- Top 1% - the luckbox run (99th percentile of the 1,000 trials).
- Top 5% - the 95th percentile.
- Top 25% - the 75th percentile.
- Median - the typical run. This is what "a normal session" actually looks like - not the EV.
- Bottom 25% - 25th percentile.
- Bottom 5% - 5th percentile.
- Bottom 1% - the nightmare (1st percentile).
Am I a Winner?
Bayesian credibility on the hypothesis that your true winrate is positive. Uses your exact top-level inputs - winrate, SD, and hands - as the observed data.
- Probability you are actually a winning player - The posterior probability your true rate is positive, under the Gaussian prior described in Cash vs Tournament. Big number, tinted green for ≥85%, amber for 60-85%, red below.
- Credible intervals on your true winrate at 60%, 75%, 90%, and 95% confidence, from the same posterior. At small samples the prior visibly pulls them toward breakeven; past ~100k hands your data dominates.
- Hands needed until you know your winrate within [X] bb/100 at [Y]% confidence - Solves for the N at which the measurement alone (no prior) reaches your target precision. "How many hands until I'm sure I'm beating the game at 1 bb/100?"
Backing Deal
Simulates a staking arrangement where a backer absorbs losses, takes a percentage cut of cleared profits, and chops at a configurable cadence. Uses the exact same Student-t sampling as the main cash simulation, so the backing sim inherits the same tail heaviness the headline chart shows.
- Backer takes [X]% of cleared profit.
- Session length in hands.
- Chop every [K] sessions - Every K sessions, if cumulative profit since the last chop is positive, split it (backer gets cut, player gets rest, makeup resets to zero). If cumulative is negative, the backer keeps carrying the makeup.
- Backer bankroll in BI - Used to compute the backer's risk of ruin.
Outputs (left to right):
- Your EV / Backer's EV - the two sides of the deal, averaged across trials.
- % of sims ending in makeup - fraction of trials where the final-chop attempt left cum < 0 (backer carried unrecovered losses all the way to the end).
- Avg makeup owed at end - average magnitude of that unrecovered balance, conditional on ending in makeup.
- Avg worst makeup (any point) - the deepest makeup each trial ever hit during the engagement, averaged across all trials. This is the lived experience of the staker, not just the final accounting - a trial can dip −40 BI mid-engagement, grind back to positive, and end with the player in profit; the end-only stat misses that entirely.
- Backer risk of ruin - fraction of trials where the backer's running net position ever touched −backerBankroll.
Your Year, Month by Month
Rolls a single random year - 12 months of hands/12 hands each - and shows the outcome as a colored heatmap. Each month's profit is drawn by summing that month's 100-hand blocks from the same Student-t(df) source the main cash simulation uses, so the tails match the headline chart and disaster-month frequencies aren't understated by a silent Normal approximation.
Green months are above expectation, red below; z-scores (±σ units) annotate how lucky/unlucky each one was. Tiles get emoji badges at ideal thresholds: 🍀 for +1σ lucky, 🔥 for +2σ blessed, ❄️ for −1σ unlucky, 💀 for −2σ disaster.
Click Re-roll (the dice icon next to the heatmap) to draw a fresh year with the same parameters. Useful for feeling what "a typical year" actually looks like month-to-month - most years have at least one month-long downswing, and most have at least one heater.
Period picker - A small toggle at the top of the card lets you interpret the top-level hands input as either monthly volume (per-month sample, year is 12× that) or yearly volume (year as a whole, monthly volume is 1/12). Card durations and labels update accordingly - "best month", "worst month", and the heatmap's per-tile hand counts all reflect the chosen interpretation.
Stake Progression
A multi-year career simulation, sitting directly under the headline chart in cash mode. Models moving up through the stake ladder as bankroll allows, and back down on drawdowns. Every assumption is editable:
- Start with [X] BI at [stake] for [Y]M hands - initial bankroll in buyins at a chosen starting stake, over a total career length in millions of hands (0.5M step, up to 10M).
- Winrate at start - your bb/100 at the starting stake. Drops as you climb.
- Drops [X] [bb/100 | %] per step up - the decay model. Absolute mode subtracts X bb/100 per step; percent mode multiplies by (1−X%). Moving down reverses the decay, so easier games give a higher winrate (anchored at your start stake).
- Move up at [X] BI, down at [Y] BI - bankroll thresholds that trigger stake changes.
- Don't move up past [stake] - a ceiling on how high you'll climb. Defaults to NL2K, a realistic ceiling; raise it to the top of the ladder if your ambitions differ.
The ladder spans eleven tiers - NL10, NL20, NL25, NL50, NL100, NL200, NL500, NL1K, NL2K, NL5K, NL10K - so you can simulate a career that actually starts at
micros. Selecting any PLO preset flips the prefix to PLO.
A ladder preview strip above the chart shows the derived winrate at every stake as you tweak params - so you can see the effect of "drops 33% per step" vs "drops 1 bb/100 per step" before running. Stakes above the cap are dimmed.
The chart draws your bankroll trajectory as line segments colored by whichever stake you were at during that stretch - green (lowest) through magenta (highest). An overlay at the top of the chart reads "END $X at [stake]." Below, a time-per-stake bar chart shows what fraction of the career was spent at each level.
Roll another career re-seeds the simulation, which is a good way to feel how wildly the path varies.
Advanced Options
The ⚙ button next to the inputs (its tooltip reads "Settings") opens the Advanced options drawer with the rest of the knobs, plus the Copy share link action at the top. The cash drawer shows SD, tail shape, trials, display unit, multi-tabling penalty, hands-per-hour, and seed. The tournament drawer shows rake, field size, payout structure, realistic-finish toggle, starting bankroll, trials, and seed.
- Standard deviation (cash) - Overrides the preset's SD. Typical values: NLH 100, PLO 140, Heads-Up 130-160.
- Tail shape / df (cash) - Student-t degrees of freedom. Lower = heavier tails. PLO 6-max ≈ 5, NLH 6-max ≈ 10. Minimum 3 (below that the distribution's variance is undefined).
- Trials - 500 / 1000 (default) / 2500 / 5000. More trials = more precise percentiles and downswing stats, at the cost of compute time. 1000 is the sweet spot.
- Display unit (cash) - Switch the chart and stats between big blinds (with auto-BI scaling) and dollars. Dollars mode asks for $ per big blind (
1at NL100,2at NL200, etc.). - Multi-tabling penalty (cash) - Subtracts N bb/100 from your effective winrate to model attention dilution.
- Hands per hour (cash) - Only affects the $/hr readout under Expected Profit when Display unit is Dollars. ~500 for 2-table online, ~25 for live.
- Rake ($) (tournament) - Fixed fee in dollars added to the buy-in. A $109 entry is a $100 buy-in + $9 rake. Not a percentage.
- Realistic finish distribution (tournament) - Toggle that shifts paid-rank probability from uniform (all paid finishes equally likely, unrealistic for strong players) to payout-weighted (winning players finish deeper more often, fit so total ROI matches your target).
- Starting bankroll (tournament) - In dollars. Used by the post-hoc risk-of-ruin calculation. Changing it never re-runs the simulation.
- Random seed - Every identical (inputs + seed) combination produces exactly the same simulation. Shareable via URL. The Randomize button pulses the seed number so you register the change.
Share & Save
Copy share link (at the top of the Advanced drawer) -
Two-tier, like the rest of the
calculators. Primary path is a KV-backed short URL, solvepoker.app/variance/<8 chars> via /api/variance/share; the request body carries the full state
(mode, all inputs, seed, which cards are expanded). If the API is unavailable,
the page falls back to a hash-encoded URL that's longer but self-contained.
Either form, anyone opening the link gets the exact same chart and results.
Save image - Generates a PNG of the main chart with title,
plot frame, y-axis labels and gridlines, x-axis labels, the full legend (EV,
Luckiest of N, Unluckiest of N, and the band swatch), and
a solvepoker.app/variance watermark. The PNG is self-contained -
axes are baked into the image, not stripped HTML overlays. The chart subtitle
summarizes the run in plain text (e.g. "100k hands at +3 bb/100 with SD 140 bb/100");
no em-dashes, so it copies cleanly into chat clients that munge them.
Stake Progression PNG - The Stake Progression card has its own Save image button that exports the multi-year career chart at the same fidelity: colored bankroll trajectory, ladder preview, and end-state overlay (the time-per-stake bars stay on the page).
Units: bb, BI, $
The calculator uses big-blind units throughout internally - never big bets (fixed-limit convention). In display:
- bb = big blinds (always lowercase).
- BI = buy-ins, where 1 BI = 100 bb.
- Small values (under 100 bb) render in bb, larger values in BI - and a related set of numbers (the outcome-distribution ladder, a collapsed card's teaser) pins to BI as a group once any member crosses the line, so no ladder mixes units. This keeps headline numbers readable (pros think "30 BI downswing" rather than "3000 bb downswing").
- Switching display unit to $ in Advanced converts via the $ per big blind factor.
Under the Hood
Engine - Monte Carlo with seeded reproducibility. Normal draws use the Ziggurat method; Student-t draws are Normal-scale-mixtures through a Marsaglia-Tsang Gamma sampler, rescaled to unit variance so the SD input stays interpretable.
Same engine everywhere - Cash, backing, year-by-month, and stake ladder all draw from the same unit-variance Student-t source, so the tails in the secondary cards match the headline chart.
Threshold pills update instantly because all drawdown thresholds are evaluated together inside one pass over each trial.
Instant first paint - The page renders immediately on load from a precomputed default simulation. Edits dispatch a fresh background simulation; switching modes or typing a new input cancels the in-flight sim immediately so you never see stale numbers.
Bankroll is computed from the simulation's results, not as an input. Dragging the bankroll slider updates the risk-of-ruin number instantly without re-running the Monte Carlo.
Privacy - No server calls for the simulation itself: the Monte Carlo runs on your device. Each run logs an anonymous usage ping with the headline inputs (mode, win rate, SD, volume, trials) so we can see how the tool gets used. Your bankroll number stays on the page.
A free, browser-based PQL (Poker Query Language) interpreter. PQL is the SQL-like DSL pioneered by ProPokerTools for asking probabilistic questions about poker spots - "how often does AA hold up vs a random hand?", "what fraction of turns give hero a flush draw?", "given villain's river bet, how often is hero quartered?". Open it at solvepoker.app/pql.
What is PQL?
PQL is a SQL-like query language for poker. You describe a spot (game, players, ranges, optional board and dead cards), then select any combination of aggregators over trial-level expressions. The engine either enumerates every possible runout exactly (when the state space is small) or runs a seeded Monte Carlo simulation, reporting the answer with a confidence interval.
Example: "how often does AA win against a random hand?" becomes select avg(riverEquity(hero)) from game='holdem', hero='AA', villain='**' which returns ~85.2%. The same shape works for 10 games and
all four of PPT's Classic range syntaxes alongside the modern
Generic syntax.
Quickstart
- Open solvepoker.app/pql.
- Pick an example from the ≡ Examples drawer (the editor opens on the 3-way AA vs KK vs AKs comparison).
- Press
⌘ Enter/Ctrl+Enterto run. - Open the ⚙ gear sheet to tweak trials (higher = slower but tighter CI), seed (a different-but-reproducible MC draw) and max seconds.
Every query uses a seeded PRNG - same query + same trials + same seed gives reproducible results, so share links reproduce. You rarely need to touch trials: the examples pick a sensible per-game default (~25k for the heaviest games like PLO6, ~50k for cheaper ones), and you can override it in the gear sheet; default max-seconds is 60.
Games Supported
| Game key | Meaning | Hole cards | Split |
|---|---|---|---|
holdem | Texas Hold'em | 2 | Hi |
omahahi | Pot-Limit Omaha | 4 | Hi |
omaha8 | Omaha Hi/Lo 8-or-better | 4 | Hi/Lo |
omahahi5 | 5-card Omaha (PLO5) | 5 | Hi |
omaha85 | 5-card Omaha Hi/Lo (Big O) | 5 | Hi/Lo |
omahahi6 | 6-card Omaha (PLO6) | 6 | Hi |
shortdeck | Triton-rule 6+ Hold'em (36-card deck, no 2-5). Aliases: 6plus, 6+, sd, short-deck. Generic parser by default (syntax='classic' routes through the Hold'em dialect); flushes beat full houses in the evaluator. | 2 | Hi |
studhi | 7-card Stud | 7 private | Hi |
stud8 | 7-card Stud Hi/Lo | 7 private | Hi/Lo |
razz | Razz (A-5 lowball) | 7 private | Lo only |
Query Structure
Every PQL query is a SQL-like statement with three clauses:
select <aggregator>(<expr>) [as <alias>], ...
from game='<game>', <player1>='<range>', <player2>='<range>', ...
[where <boolean_expr>] Keywords are case-insensitive. String literals use single quotes;
doubled '' is an escape. Multiple queries in one input
are separated by ;. Line comments start with --; block comments /* … */.
The FROM clause takes game= (required), board= and dead= (optional, both accept
range-DSL strings), syntax= ('generic' default
or 'classic' for legacy dialects), plus any number of
player-name assignments. Player names are free identifiers
(hero, villain, p1, v2, …)
- they become the first-argument value for per-player
functions like riverEquity(hero).
Aggregators
| Aggregator | Input | Output |
|---|---|---|
avg(expr) | numeric (equity, rating) | arithmetic mean |
count(expr) | boolean | fraction true + raw count |
histogram(expr) | enum, number, category | frequency table, sorted by poker rank when known |
min(expr) | numeric | smallest value seen |
max(expr) | numeric | largest value seen |
A query can have any number of aggregators, comma-separated, each
with an optional AS alias. Results appear as separate
cards. Same-shape histograms (e.g. histogram(handType(p1, river)) and histogram(handType(p2, river))) are merged into a single
multi-column table sorted by poker strength so you can compare ranges
side by side.
Built-in Functions
88 callable functions (77 distinct implementations plus 11 aliases;
the PQL engine's Function reference drawer is auto-generated
from the live registry and always up to date). Arguments like player are bare identifiers (not quoted); street is
one of preflop, flop, turn, river (flop games) or third…seventh (stud games).
Equity / pot share:
| Function | Description |
|---|---|
riverEquity(p) | All-in equity after full runout (0-1) |
equity(p) | Alias for riverEquity |
HvHequity(p, street) | Hand-vs-hand equity on the given street |
HvRequity(p, street) | Hand-vs-range equity on the given street |
minEquity(p, street) | Minimum equity over all remaining-card completions |
minHvREquity(p, street, threshold) | True if HvR equity exceeds threshold |
fractionalRiverEquity(p) | Equity as an exact rational fraction |
bestHiRating(p?, street?) | Best achievable hi rating; 0/1-arg = max across players, 2-arg = best for player |
bestLoRating(p?, street?) | Best achievable lo rating (split-pot games) |
Wins / ties / scoops:
| Function | Description |
|---|---|
wins(p) | True if p wins the whole pot outright |
winsHi(p) | True if p wins/shares the hi pot |
winsLo(p) | True if p wins/shares the lo pot |
tiesHi(p) | True if p chops the hi pot (>= 2 ties) |
tiesLo(p) | True if p chops the lo pot |
scoops(p) | True if p wins BOTH halves outright |
Hand types and categories:
| Function | Description |
|---|---|
handType(p, street) | Coarse category (pair, twopair, flush, …) |
minHandType(p, street, t) | True if p has at least hand type t |
exactHandType(p, street, t) | True if p has exactly hand type t |
winningHandType() | Hand type that won the pot |
flopHandCategory(p) / turnHandCategory(p) / riverHandCategory(p) | PPT category (toppair, overpair, set, …) at street |
min{Flop,Turn,River}HandCategory(p, c) | At-least check on category |
exact{Flop,Turn,River}HandCategory(p, c) | Exact check on category |
hiRating(p) / loRating(p) | Opaque hi/lo strength (higher hi / lower lo = better) |
minHiRating(p, street, threshold) | True if hi rating exceeds threshold |
Hand value predicates and draws:
| Function | Description |
|---|---|
nutHi(p) | True if p has the nuts on the river |
nutHiForHandType(p, t) | True if p has the nuts of a specific hand type |
nutHiOuts(p, street) | Outs to nut hi on the next street |
outsToHandType(p, street, t) | Cards that upgrade p's hand to type t or better (per-trial cached) |
fourFlush(p, street) / threeFlush(p, street) | True if p has a 4-flush / 3-flush at street |
hasFlushDraw(p, street) / hasStraightDraw(p, street) | True if a flush/straight draw exists |
hasGutshot(p, street) / hasOpenEnder(p, street) | True if a gutshot or OESD exists |
pocketPair(p) / overpair(p) | True if p has a pocket pair / overpair to the board |
hasTopBoardRank(p) / hasSecondBoardRank(p) | True if p's hole shares the highest / second-highest board rank |
Lo predicates (Omaha-8, Stud-8, Razz):
| Function | Description |
|---|---|
madeLo(p) | True if p has a qualifying low |
nutLo(p, street) | True if p has the nut low |
hasLo(p) | True if a qualifying low exists in p's holding (split-pot games) |
nutLoOuts(p, street) | Outs to the nut lo on the next street |
Board texture:
| Function | Description |
|---|---|
pairedBoard(street) | True if any rank appears on the board |
rainbowBoard(street) / twotoneBoard(street) / monotoneBoard(street) | 4-suit / 2-suit / 1-suit board predicates |
flushingBoard(street) | True if any suit has 3+ on the board |
straightBoard(street) | True if a 5-card straight exists on the board |
boardsuitcount(street) | Distinct suits on the board at street |
boardLoCardCount() | Count of low cards (A-8) on the river board |
boardHasOneDistinctLoCard() / boardHasTwoDistinctLoCards() | True if board has exactly 1 / 2 distinct low ranks |
boardAllowsMadeLo() | True if the board allows a made low |
handBoardIntersections(p, street) | Count of shared ranks between hand and board |
Range / pattern matching:
| Function | Description |
|---|---|
inRange(p, 'range-string') | True if p's actual hand is inside the given range |
boardInRange('range-string') | True if the current board matches the pattern (constant-time matcher, not enumeration) |
handsHaving(fn, args…) | Inner-function enumeration; counts hands that satisfy the inner predicate |
Rank introspection:
| Function | Description |
|---|---|
maxRank(p) / minRank(p) | Highest / lowest rank in p's hole |
nthRank(p, n) | n-th highest rank |
rankCount(p, rank) | Count of a specific rank in p's hole |
handRanks(p) / boardRanks() | Number of distinct ranks in hole / board |
intersectingHandRanks(p) / nonintersectingHandRanks(p) | Hole ranks shared with / disjoint from the board |
duplicatedBoardRanks() / duplicatedHandRanks(p) | Count of paired ranks on board / in hole |
turnCard() / riverCard() | Specific street card (flop games) |
upCard(p, n) | n-th up-card for player p (stud games, 1-indexed) |
Plus the SQL building blocks: AND, OR, NOT, comparison = <> != < <= > >=,
arithmetic + - * /, IN (…), CASE WHEN … THEN … ELSE … END (both
searched and simple forms).
Range Syntax (Generic)
Every quoted range string uses the Generic DSL by default. This is the same syntax as the PPT Filter Syntax used elsewhere in the tool - see that section for a detailed reference. Key points:
- Ranks:
A K Q J T 9 8 7 6 5 4 3 2. - Literal suits:
s h d c. - Suit variables:
w x y z(same var → same suit; different var → different suit). - Rank variables: any other letter (conventionally
R,O,N). - Wildcard rank:
*; the whole wordrandomis an alias for the full universe. - Operators:
,(OR),:(AND),!(NOT),()grouping. Precedence:!>:>,. - Spans:
AA-TT,KK+,AA-,[A-Q]. Ascending spans normalize (22-AA≡AA-22). - Percent:
15%(top 15%),15%-30%,15%6h(6-handed ordering). - Shape macros:
$ssuited,$ooffsuit,$dsdouble-suited,$sssingle-suited,$tstriple-suited (PLO6),$npno pair,$opexactly one pair,$tptwo pairs (4+ hole cards only),$ntno trips (the complement of trips - paired hands still match, unlike$np). - Rundown macros:
$0gconnected runs,$1g/$2gruns with exactly 1 / 2 missing internal ranks, distributed any way -$2gin PLO matchesAKQ9,KQT8andQJ97alike. - Category macros:
$Bbig (A-J),$Mmiddle (T-7),$Zsmall (2-6),$Llow (A-8),$Nno-low (K-9),$Fface,$Rbroadway,$Wwheel. - Solver ranges:
#6m/rfi/btndrops a frequency-weighted GTO preflop range in (PLO4 only) - see Solver Ranges. - Card-count top-off: hand patterns are padded to the game's hole count.
AAin Omaha becomesAA**.
Weighted ranges. @N after an atom sets
its weight as a percentage of full weight: unweighted atoms are
implicitly @100, AA@50 is half-frequency
AA, values above 100 oversample relative to the rest
(AA@10000,KK@100 samples AA 100× as often as KK),
and @0 excludes the atom outright. Negative or
non-numeric weights are rejected at parse time. Weights survive
composition: union sums weights when the same hand appears twice,
intersection and difference carry them through (so #6m/rfi/btn:$ds keeps the solver frequencies, which
live on the same internal 0-1 scale), and exact enumeration weighs
each configuration instead of ignoring weights.
Classic Syntaxes (syntax='classic')
Pass syntax='classic' in the FROM clause to switch the
range parser into a legacy dialect. The parser is selected
automatically by game:
- holdem → Classic Holdem:
AKs= suited AK (all 4 combos),AKo= offsuit AK,AA-TT,AK+,*h*h(both hearts). - omahahi / omaha8 / omahahi5 / omaha85 / omahahi6 → Classic Omaha: literal hands
AsKsTdTh, rank-onlyAAKK, rank classesB M Z L N W, operators&(intersect),!(difference),,(union). PLO6 (omahahi6) also supports the new$ts(triple-suited,xxyyzz) macro. - studhi / stud8 → Classic Stud:
|-delimited per-street segments. - razz → Classic Razz:
A35,JJ4,9-(low-open),3+(high-open),(T- 7- 4)(all-different with parens).
Note that Generic is the default and is what most online PQL examples use. The Classic parsers exist for parity with legacy PPT scripts.
Example Queries
All of these run in the PQL engine; pick one from the Examples drawer or paste them in:
Preflop all-in equity (Holdem):
select avg(riverEquity(hero))
from game='holdem', hero='AA', villain='**' Flop-pair conditional win rate:
select count(winsHi(hero))
from game='holdem', hero='AK:$s', villain='**'
where handType(hero, flop) = pair Omaha-8 nut low frequency:
select count(nutLo(hero, river)) as nutLoRate
from game='omaha8', hero='A2**', villain='****' Quartered with nut low:
select count(tiesLo(hero)) as quartered
from game='omaha8', hero='A2**', villain1='15%', villain2='15%'
where nutLo(hero, river) and not (winsHi(hero) or tiesHi(hero)) River hand-type distribution:
select histogram(handType(hero, river))
from game='holdem', hero='AA', villain='**' Multi-query batch:
select count(winsHi(hero)) as AK_wins
from game='holdem', villain='10%', hero='AK';
select count(winsHi(hero)) as J2_wins
from game='holdem', villain='10%', hero='J2' Examples Drawer
Click ≡ Examples in the editor header to slide in the example library: 8 queries ordered simple → complex, with All / Basics / Omaha / Split pot category filters and a search box. Each one teaches something the others don't — questions with a single obvious answer live in the recipe pages instead. The current set:
- Three hands, three columns (Holdem) - AA vs KK vs AKs preflop, one SELECT column per player.
- Filtering trials with WHERE -
minFlopHandCategory+outsToHandTyperestricted to two-tone, unpaired flops. - A custom EV payoff with CASE - a nut flush draw on a paired turn scored at 3:1 pot odds with
CASE WHEN … THEN … ELSE … END; small enough to enumerate exactly. - Weighted ranges: what @ actually means - the same 22 combos run weighted and unweighted, so the
@Npercentage scale shows up as a ~12-point equity gap. - Shape macros, histograms and handsHaving (PLO) -
AAKK$dsagainst three real solver opening ranges (#…), with a per-hand histogram andhandsHaving. - A multi-query batch across opponent strengths -
$B$B$B$Bagainst random, the top 15%, and a real BTN solver open: three statements separated by semicolons. - Split-pot accounting (Omaha Hi-Lo) - scoops, quartered lows and an exact-fraction test (
= 1/4) in one query. - Stud-8: nut low locked on seventh - split-pot stud counting scoops and quarterings only in the trials where hero ends with the nut low.
Click an example to load it into the editor (replacing whatever was there). Each example shows a one-line description before you commit.
Beyond the drawer, solvepoker.app/pql/recipes collects ready-to-run queries as web pages. Three worked guides lead: PQL by example (a dozen one-line recipes spanning equity, draws, histograms, exact mode, split pots, weights and solver refs) plus two deep studies, Draw density decides the call and Nuttiness beats raw equity. Below them sit fifteen single-question recipes grouped by topic - preflop equity, hand vs hand, flop draws, made hands, board textures, PLO, 6-card PLO and Omaha Hi-Lo. Every query has a link that loads it straight into the PQL engine.
Function Reference
Open the Function reference from the gear sheet
or with ⌘ ? - a searchable overlay listing
every PQL function. Each entry shows the name, arity (which arguments
it takes), the games it's available in, a one-line description, and
(for many functions) a runnable code snippet with a Load into editor button. The reference is generated
from the live function registry, so it never drifts from what the
engine actually supports.
Complexity Hint
Below the editor, a small status line shows the current query's complexity - game, player count, trial count, and an estimated wall-clock runtime. The estimate uses per-game base throughput numbers and scales with the player count. Queries that the estimator thinks will run for more than 30 s get a slow query badge so you can dial trials down or narrow your ranges before pressing Run.
Share Links & Seeds
Every PQL run uses a seeded PRNG. The same query with the same trials + seed gives bit-for-bit identical results on every device and every run, so share links reproduce.
Copy share link (in the gear sheet) is two-tier:
- Short URL -
solvepoker.app/pql/<8 chars>. POST to/api/pql/sharewith{ query, trials, seed }; KV-backed. The older?u=<8 chars>form still resolves. - Hash fallback -
#q=…&n=…&s=…when the API is unavailable. Works offline; capped around 1800 characters.
Opening either form loads the query, trials and seed into the
editor - press Run to execute. The URL is cleaned back to /pql on load, so the address bar doesn't drift while
you edit.
PNG Export
Save PNG (in the gear sheet next to Copy share link,
Copy results, and the Function reference; disabled until a run has
finished) generates a high-resolution image with two halves: an
IDE-style syntax-highlighted query on top, and the formatted results
below - scalar values with their 95% CIs (both count and avg carry a real CI; exact-enumeration results show
none because they have no sampling error), histograms as
multi-column tables.
Dimensions are dynamic - tall queries or many-bucket histograms
stretch the canvas accordingly. Panels, syntax highlighting and
text all come from your active theme, so the image matches the
editor you ran the query in. Title SolvePoker plus
an accent subtitle ("PQL Engine"); footer solvepoker.app/pql · seed={seed}. 2× device
pixel ratio so it stays sharp on retina screens.
Differences from PPT
Our implementation aims for full behavioral parity with PPT's PQL. A few areas of PPT's spec are genuinely ambiguous; we've picked sensible rules. Things most likely to surprise you:
- Generic vs Classic holdem shorthand. In the Omaha games,
AKsin Generic means "A (any suit) + K of spades", not "AK suited" - for "AK suited" writeAK:$s, and the runtime flags the slot-count mismatch with a clear error pointing here. Hold'em and Short Deck are friendlier: range strings that look like classic Hold'em notation (AKs,AQo,TT+) auto-route to the Classic parser, nosyntax='classic'needed. - Bracket card-lists.
[2c-6c]and[2c,3c,4c,5c,6c]both parse as the same explicit 5-card set. (PPT is inconsistent here; we pick the simpler rule.) - Classic Omaha
&and Generic:are exactly the same operator in different glyphs. Despite PPT's published precedence tables appearing to differ, both parsers produce identical results. - Razz auto-routes to classic syntax. Razz queries
use the classic-stud-style hole+up format
(
'A 3 5 | * | * | * | *') and all-different parens ('(T- 7- 4)'); we route to the classic parser by default forgame='razz', and stud games try the classic parser first before falling back to generic. Flop games default to generic. - Fractional equity is exact.
fractionalRiverEquity(p) = 1/4uses exact-rational compare (cross-multiply), not float coincidence. Integer-fraction literals like1/4or13/914are accepted directly.
Known Limits
- Ranges too large to materialise either stream
(plain hand patterns and shape macros - full PLO6 wildcards like
'******'run fine this way) or fail with a clear "too large to materialize" error for compositions that can't stream. Nothing is silently truncated. - First percentile query on PLO5/6. The first time
a query in PLO5 or PLO6 uses a
%selector, the engine builds a canonical hand-rank map (~30-60 s on a fast laptop). A yellow hint appears after 5 s explaining what's happening. Subsequent queries on the same game reuse the cached map. - No percentile table for shortdeck. Hold'em, PLO,
PLO5/6, Omaha-8, and 5-card Omaha-8 ship prebuilt percentile tables
(with 3h and 6h variants where they make sense). Shortdeck does not
- a percent selector like
'15%'ingame='shortdeck'falls back to the full universe with a warning. Use specific hand patterns instead. nutHiOutson Omaha is per-trial expensive (~50 ms/trial on holdem, ~200 ms/trial on omaha) because it enumerates every opponent hole-card combo against every candidate next-street card. For high-trial queries, consider sampling fewer trials or restricting villain to a narrow range.
Under the Hood
Evaluators - Hold'em and Stud evaluate hands directly on 5- or 7-card sets. Omaha enforces the "exactly 2 from hole + 3 from board" rule via a 60-subhand enumeration (100 for 5-card Omaha). The A-5 low evaluator supports both 8-or-better qualifier (Omaha-8, Stud-8) and unqualified Razz. Hold'em and Omaha hi run a Cactus-Kev-style WASM evaluator with batched lookups for significant speedups over the previous pure-JS implementation.
Worker pool - Trials are dispatched across a pool of Web Workers (typically 2-4 depending on your CPU). The main thread streams progress messages and stops as soon as the trial budget or wall-clock budget is hit.
Adaptive early-stop - When every count aggregator's 95% confidence interval has
narrowed below 0.5 percentage points and at least 20,000 trials
have run, the pool cancels the remaining budget and returns what
it has - the results header announces it ("stopped early at N trials
(converged)"). Queries with no count aggregator always
run their full budget.
Plan caching - The worker keeps a small LRU cache of recently-compiled query plans, so repeated runs of the same query (different seed / trials) skip the parse + materialise step.
Mobile - The page collapses to a single column with Query / Results tabs at the top. The settings sheet slides up from the bottom. The complexity hint stays visible underneath the editor.
Determinism - A seeded PRNG drives every run. With a fixed seed, the same query produces bit-for-bit identical results across devices.
Privacy - The simulation itself never leaves your browser. Each run logs an anonymous usage ping that includes the query text; Copy share link separately stores the query and run parameters behind a short ID.
A free study dashboard and quiz for PLO starting-hand strength at solvepoker.app/starting-hands. PLO4 has 16,432 strategically distinct hand classes, PLO5 has 134,459 and PLO6 has 962,988. We ranked all of them by Monte Carlo equity and grouped them into the categories players actually think in. Variant pills switch between PLO4, PLO5, and PLO6 (PLO4 is the default); three tabs - Categories, Rankings, Quiz - cover the rest. On the Quiz tab, a settings gear holds Copy share link, which encodes your current tab, variant, and quiz settings.
Categories
The default tab. For PLO4 it draws the 31-category preflop taxonomy, rolled into 11 meta-groups, as span bars on a shared 0-100% strength axis. It's the same hand vocabulary the solver's Preflop Study and Hand Tracker's Leaks tab use, so what you learn here transfers. Each bar covers the middle 80% of the category's combos, the bright segment the middle 50%, the tick the median. Expand a category for best/typical/worst example hands, per-suit medians (double-suited down to monotone), and - PLO4 only - the solver's open frequency per seat at 100bb 6-max. A toggle re-sorts the axis for heads-up, 3-max, or 6-max play (PLO5/6 have no 3-max table, so they toggle between heads-up and 6-max). A PPT filter box with tool tag pills sits above the bars: filter, and every span recomputes over just the matching hands.
PLO5 and PLO6 use the same category treatment - the PLO4 taxonomy plus Triple Paired folded in - so the vocabulary carries across games.
At the bottom of the tab, a Hand types chart plots one line per hand type (Double pair, Pair + connectors, Single pair, Rundown, Rundown + dangler; PLO6 swaps in Three pair) against the key rank, so you can read what each type is worth as its main card climbs from 5 to A. Two toggles: Suits (double-suited / single-suited / rainbow, plus triple-suited in PLO6) and Strength vs (heads-up or 6-max). The chart follows the page's variant pill.
Rankings
The full ladder, in three screens:
- Browse - Every hand class from strongest to weakest, with a draggable percentile rail (tick marks at 1 / 10 / 25 / 50 / 100%), top and bottom jump arrows, a heads-up / 6-max sort toggle (plus 3-max in PLO4), and a PPT filter box with about 32 tool tag pills across five groups - suit, structure, connectivity, rank, and composites - each with a live match count. Select a hand and the detail pane shows its cards, suit-pattern title, PLO4 category chip, a rank bar per field size (top X%, equity, rank), and solver EV by seat. An image button in the pane's header saves the selected hand as a branded PNG - header, variant chip, the full detail body, and a solvepoker.app/starting-hands footer, rendered in your active theme.
- Movers - One list over every class, ordered by the percentile shift from heads-up to 6-max: biggest climbers first, biggest fallers last. Click a hand to open it in Browse.
- Opens (PLO4 only) - Every class ranked by total preflop EV for a chosen seat at 100bb 6-max, UTG through SB: opening, facing raises, and blind defense, weighted by how often each spot comes up, with the first-in open / mix / fold tag alongside. Deterministic solver output, no sampling noise.
A "Compare variants" button overlays the equity-vs-percentile curves on one chart. NLHE spreads from about 85% (AA) down to 32%; PLO compresses hard toward the middle - PLO4's best hands sit near 71%, and the 5- and 6-card curves top out near 65%. Paste a PPT range to plot where it sits on the curve, and read the combo-volume histogram under the main chart.
Quiz
Three drills: "Higher or lower" (pick the stronger of two hands), "Order five" (drag five hands into strength order), and "Place the band" (drag a band onto the hand's percentile). Three difficulties tighten the gaps - on Hard, higher-or-lower pairs sit 1-5 percentile points apart. Best streaks persist in your browser.
Where the Numbers Come From
Rankings are seeded Monte Carlo equity vs random opponents, run per canonical hand class: 400k trials per PLO4 class, 200k per PLO5, and 50k per PLO6 class heads-up (PLO5 publishes as 959 percentile bins, PLO6 as 974). Sixteen marquee AA hands were re-run at 100 million trials each (95% CI ±0.009pp) to pin the close orderings - AATT double-suited really does edge AAKK, 71.53% to 70.67% vs a random hand. The PLO4 solver overlays (the Opens screen and per-seat open frequencies) come from the same 6-max 100bb MonkerSolver solve as the main tool. The page's methodology accordion carries the rest of the detail.
A static probability reference at solvepoker.app/poker-probabilities, in two panels: how often hands hold specific cards, and how often board textures come and run out. There is no board-entry input - every number is precomputed and exact.
Hands Panel
Nine holdings (straight, set, trips, pocket pair, two pair, pair, flush draw, flush, wrap) across seven game columns: NLHE, PLO4 through PLO8, and Short Deck. All closed-form combinatorics. A toggle switches between exact values and round-half figures for memorizing. Sample: a random PLO4 hand holds a flush draw on a two-tone flop 21.4% of the time.
Board Panel
A drill-down tree over three texture dimensions: pairs (unpaired/paired/trips), suits (rainbow/two-tone/monotone), and straight coordination. Rotate which dimension drills first, or hide levels to aggregate over them. Frequency rows are texture shares - rainbow flops are 39.8% of all flops, monotone 5.2%. The rows in the second colour are runout events conditional on the row above: how often the board pairs by the river, a flush draw completes, or a bigger straight becomes possible. Leaf textures also show what a random PLO4 hand holds there.
Everything is exact enumeration of all 22,100 flops and every turn and river behind them - about 52 million states, nothing simulated. The one approximation: the "random hand here holds" rows use a single representative flop per texture.
A reference at solvepoker.app/range-frequencies showing how real preflop ranges bend dealt odds, with a game switch for NLHE and PLO. One table, three columns per holding: the frequency in a random dealt hand, in the solver's BTN opening range, and in the BB's 3-bet range against that open, with a multiplier on every non-dealt cell. In PLO the button opens 48.6% of all deals and the big blind 3-bets 9.4% of them; pocket aces go from 2.9% of dealt hands to 27.2% of that 3-bet range, ace-king straight cards more than triple, and 2-4 wheel cards collapse from 7.2% to 0.6%. NLHE runs narrower on every row: a 45% button open and a 12.5% 3-bet, where aces, kings and queens are all pure raises and land on the same 3.9%.
The centerpiece contrasts two boards with identical dealt flush-draw odds - one with high flush cards on board (A-Q of the suit), one with low ones (7-5). In PLO's 3-bet range the low-flush-cards board's flush-draw frequency is 31.7% against the other's 21.5%, because the ace and queen of the suit on board block the big suited combos the range is built around.
The numbers are exact weighted counts over every starting-hand combo of the game - 1,326 in NLHE, 270,725 in PLO - against that game's own 6-max 100bb solve and its actual preflop frequencies, so a hand the solver opens 40% of the time counts at weight 0.4. Nothing is simulated.
Pure pot geometry at solvepoker.app/plo-bet-sizing-reference: no data files, no inputs, just the arithmetic every bet reduces to, precomputed for the sizes you actually see. Two tables:
- What every bet size requires - Seven sizes from 1/6 pot to pot, three columns each: the equity a caller needs (
b/(1+2b)), the bluff share of a balanced polar range (same formula), and the MDF (1/(1+b)). Pot-size bet: caller needs 33.3%, bluffs are a third of the betting range, defend half. - The call-down ladder - MDF, MDF², and MDF³ for five sizes: how much of a flop range is still in after one, two, or three streets of pressure at minimum defense. Three pot-size barrels leave 12.5% of the starting range at showdown.
Range-vs-range equity and flopped-nuts share for real solver ranges at solvepoker.app/plo-range-advantage. Pick a pot type (RFI, 3-bet, or 4-bet) and a seat pairing - 33 valid matchups; 4-bet pots only reach a flop when the BB defends against a CO, BTN, or SB open. Twelve curated flop textures, from ace-high dry rainbow to monotone king-high to a low rundown, each show two tug-of-war bars: the equity gap and the nuts gap between aggressor and caller. Expanding a flop breaks both ranges into 13 hand classes (full house+, nut flush, wraps, flush draws, and so on). "Flopped nuts" here means the one best hand the texture allows: the nut flush on a monotone board (straight flushes count in the same bucket where the ranks allow one), a full house or quads on a paired board, the nut straight when the flop's ranks make a straight possible, and top set otherwise.
Ranges are reach-weighted from the 6-max 100bb solve. The 3-bet-pot caller's range is the opener's RFI frequency times its call-vs-3-bet frequency, so a hand the opener never opens can't appear in it. Equity runs 50,000 frequency-weighted combo pairs per flop through the same Omaha hand rankings as the equity calculator (the printed ± is the Monte Carlo error); class shares are exact. An "Add your own flop(s)" input runs any comma-separated flops through the same code in a Web Worker, so custom numbers are directly comparable to the curated ones.
Card-removal ladders at solvepoker.app/plo-blocker-effects: how often a random opponent hand makes a category as copies of the key cards die on the board or in your hand. A game toggle covers PLO4 through PLO8. The left ladder tracks one rank or one suit - how often they hold at least one King or two of them (what that makes depends on the row's board: top pair on K-9-6, trips on K-K-9, quads on K-K-K), or a flush on a monotone board as your held cards in that suit go from 0 to 4. The right ladder tracks two-card combos: the nut straight on 9-8-7 (needs a Jack and a Ten) and top two pair, across six blocker configurations from holding none of the key cards to holding four.
All exact closed-form combinatorics over the remaining deck, zero simulation. The rank ladder varies how many copies the board buries; the flush and two-card ladders remove cards you hold.
Beyond the six study tools above, a shelf of free reference pages reads straight from the same solves and simulations. They explain themselves, so this guide just points at them:
- Preflop charts - PLO preflop charts (open, 3-bet, and calling ranges for every 6-max seat at 100bb) and the interactive NLHE preflop charts (any position, facing line, and depth on a 13x13 grid).
- Stack depth - how preflop play changes across the ladder, for PLO and NLHE (the NLHE one has a CSV download).
- Starting-hand rankings - full ranked lists for PLO4, PLO5/PLO6, Big O and O8, and NLHE.
- C-bet frequencies - every one of the 1,755 flops' c-bet, donk, and check-raise numbers from the 6-max 100bb solve; the same certified pipeline feeds the notebook's
aggcommand. - Variance - PLO downswing and bankroll numbers and the measured swing gap between NLHE and PLO.
- Glossaries - PLO terms and the tracker stat definitions, each with its formula.
- Hand history files - where each of the nine supported sites writes them, for feeding the Hand Tracker.
A free hand history tracker at solvepoker.app/hand-history. Import the files your poker site already writes and you get win-rate stats with confidence intervals, opponent profiles, a replayer with a live equity overlay, a whole-library spot browser, and a leak report that only makes claims it can prove. NLHE and the full PLO family, cash and tournaments, nine site formats. Everything is in the free tier; only the caps differ (see Free Tier & Limits). There is no HUD, on purpose - it's a post-session tool.
Six tabs: Stats, Players, Replayer, Spots, Leaks, Upload. Signed-in members land on Stats. Without an account you can replay 9 sample hands and run the Stats, Leaks, and Spots demos on them. Paste-a-hand gives anyone - signed in or not - an instant replay of any single hand. Importing your own hands needs a free account, because the library has to be stored somewhere. The whole tracker is keyboard-operable: library rows, stat drill-downs, and player profiles all open with Enter or Space.
Importing Hands
Drag files - or whole folders - onto the Upload tab, or use the file and
folder pickers. Accepted: .txt, .xml, .log, .hh, and .zip archives
(GG's native export ships zipped; archives unpack in your browser, up
to 50 MB per zip entry and 300 MB inflated across one drop).
Files upload with per-file progress and parse server-side; the imports
list shows each file's site, hand count, duplicates, skipped hands, and
the error reason when something fails. One bad file never aborts the
batch. A drop bigger than the 500-file cap keeps the first 500 - split
huge archives across imports.
Sites - Nine formats parse: PokerStars (including Zoom and Home Games), GGPoker, Full Tilt, Bovada/Ignition/Bodog, Winamax, iPoker, 888poker, partypoker, and WPN (old and new formats). Straddled pots parse correctly, and GG's bumped straddles replay as a single post at the final total. Run It Once hands parse through the PokerStars grammar. OnGame, Microgaming, and Merge files are recognized and skipped cleanly rather than erroring the import. If your site is missing, send us a sample file.
Games - NLHE, Limit Hold'em, Short Deck, PLO4, PLO5, PLO6, O8, PLO5/PLO6 Hi/Lo, and non-PL Omaha Hi, cash and tournaments. Stud and draw hands are skipped per hand with a clear reason.
Dedupe - Every hand is keyed by site plus hand id, so re-importing the same file (or overlapping archives) reports duplicates instead of double-counting, and duplicates never consume your hand cap. Timestamps are normalized to true UTC wherever the file makes that possible (PokerStars, Full Tilt and Bovada write Eastern time with DST, 888 writes GMT, partypoker stamps an explicit zone per hand, and so on), so cross-site sessions and the day-by-hour heatmap line up. Two exceptions, because the files don't carry the information: iPoker skins write their server's wall-clock with no zone token, and GGPoker switched from UTC to the player's local time around December 2022. Those are stored unshifted and the heatmap renders them unrotated, which is right for the common case of playing in your own timezone.
Stats
The dashboard reads a precomputed stat cube (59 stats per hand), so every filter and drill-down is instant. It always scopes to one game and one of cash or tournaments at a time - your PLO winrate is never blended with your NLHE hands. Filter by position, stake, table size, and month range. Three panels deliberately ignore that scope and read the whole library: Sessions, Tournaments, and the day-by-hour heatmap - each says so on the panel.
Headline cards: win rate in bb/100 with a 95% confidence interval and per-100 standard deviation, net won, all-in EV (all-in pots with every hand known settled at their equity, with the luck delta shown), showdown vs non-showdown winnings, WTSD / W$SD / WWSF, VPIP / PFR and the gap, AF / AFq, and a Money & rake card (net at face value in real currency, with rake paid in bb and bb/100 underneath; cash scope only).
Below those: a bankroll curve with legend toggles for the all-in-EV line, the showdown/non-showdown split, and a ±95% variance envelope, an x-axis switch (active days or cumulative hands), a recent-form readout, and its own PNG export; variance in context of your own rate; monthly trends; win rate by position with CIs; a starting-hand breakdown for hold'em (the 13x13 matrix) and PLO4 (the same 31-category taxonomy the solver uses elsewhere - PLO5/6 classes aren't reported yet); 28 preflop and postflop grid stats, positional and stake and table-size breakdowns, sessions (a 45-minute gap starts a new one), tournament ROI and ITM read from your files (never estimated), and a day-by-hour "when you play (and win)" heatmap in your local time. Click almost any number and it opens the hands behind it, filtered - drill-ins from the heatmap, a session, or an exact stake show up as removable filter chips over the library.
Stat definitions deviate from PokerTracker and Hold'em Manager in five documented places (3-bet excludes limp-reraises, walks are excluded from VPIP/PFR, and three more). The Poker Stat Glossary states every definition so your numbers reconcile.
Players
A sortable, searchable table of your opponents (the 20,000 you've played most, when a library gets that big) with the full stat set, a site filter, and four call-out cards: tracked players, most played, costs you most, and pays you most - the money call-outs need 30 hands on an opponent before they'll name anyone. Set the minimum hands per opponent to cut the long tail. Every row expands into a profile: open their hands or showdowns, the full stat grid, a VPIP/PFR-by-position split, and the notes box with one of five color tags (autosaved). Anonymous-pool sites (GGPoker, Bovada/Ignition, Run It Once) can't be tracked by name; the tab explains that instead of showing junk rows. CSV export included.
Exploits - Once an opponent has 30 hands, the tab reads them an archetype and lists up to five stat-backed adjustments, each naming the stat and sample it came from. Confidence is stated, not implied: tentative under 75 hands, likely from 75, strong from 150. There is also a river read - whether their bets showed up honest - for that player and for your whole pool.
Replayer
Frame-by-frame replay on the same evenly-seated table view the solver's practice mode uses - equal spacing for any player count from 2 to 9, hero pinned bottom-center, chip stacks, fold and all-in states, and a showdown chip slide. Winner pills carry the site's own made-hand text, and side pots itemize. Controls: play/pause (space bar) at 1x, 2x or 4x, first/prev/next/last, jump straight to a street, a scrub slider, and a clickable action log. Keyboard: ←/→ step frames, Home/End jump to the ends, Space plays, and ↑/↓ step to the previous or next hand in the whole filtered library without leaving the replayer. Two persistent toggles: show every amount in big blinds, and reveal known villain cards from the first frame. Hands that ran it twice or three times show every runout as a card strip under the table; the replay and the equity race follow run #1.
Equity overlay - When two or more hands are known, a per-street equity race renders next to the action. Flop, turn, and river are exact enumeration; preflop is Monte Carlo and labelled ≈. When only your own cards are known, a vs-random estimate takes its place - your equity at the current street against the rest of the field, labelled ≈ too. O8 hi/lo splits and Short Deck rankings are handled.
Decision math - For the bet faced at the current frame: the equity you need to call, the MDF against that size, and the balanced bluff frequency.
Below the replayer sits the library: 25 hands per page, sortable by pot, result, or date, with quick filters (site, game, cash/MTT) and advanced ones (position, farthest street, pot size, table size, VPIP, showdown, starred, dates, stakes). Star hands for review, attach notes, and step prev/next through the whole filtered set without leaving the replayer.
Spots
The whole-library browser. Slice every hand you've played by position, pot type (single-raised, 3-bet, 4-bet+, limped), betting line (c-bet, double barrel, check-raise, probe, donk, and more), street, result, showdown, and what you held - pick a street first, then the made hand and/or draw as two independent axes, so "top pair plus nut flush draw" matches both at once (the draw axis retires on the river). There are also cash/MTT (defaulting to cash), stake range, starred, all-ins-only, date-range and variant filters, and sorts for biggest win and biggest loss. All-ins-only means all-ins with a computable equity - uncalled jams and unknown-opponent all-ins aren't included. The filtered set reports its aggregate net bb, bb/100 with a confidence interval, EV bb/100 when all-in luck moved the money, win %, and WTSD, which makes slicing itself the analysis - and a situation-only slice warns that it counts hands you folded. Rows open in the replayer; CSV export covers the rows loaded so far (the button says how many).
Saved views - Name any filter combination and it's kept on your account, up to 50, so a slice you check every session is one click instead of nine dropdowns.
Leaks
The leak report uses two sources of truth, and says so on the tab: solver differences against our own PLO and NLHE solves, and exact pot math. Anything softer appears as an observation, never a verdict.
Solver tier - Preflop decisions are graded per actual holding against the same solves the study tools run on, matched to each hand's table size and effective stack. PLO uses 9 solves (6-max at 12/20/30/40/50/100/150/200bb plus heads-up 100bb), with a deliberate ungraded gap at 64-80bb where neither the 50bb nor the 100bb solve is a fair yardstick. NLHE uses 8 6-max solves (20/30/40/50/70/100/150/200bb), whose depth windows are contiguous from 16bb up, so there is no gap. Rules cover RFI width per seat (±6pp tolerance), wrong opens, missed opens, 3-bet width, fold-vs-3-bet, defense vs opens, open-limping, and how often you flat a 3-bet of your own open - the last two vs-3-bet checks are PLO only, since the NLHE decode doesn't carry the opener-vs-3-bet response node. Scope is cash only: PLO4 at 6-max or heads-up, and NLHE at 6-max. Tournaments, PLO5, PLO6, heads-up NLHE and other table sizes get the math tier instead.
Math tier - River bluff share vs the indifference ratio for genuinely polarized sizings. Below-MDF river defense is surfaced as an observation, never a verdict - folding more than MDF is only a leak when your continues would actually beat the bluffs, which the files can't prove.
Games with no leak report at all - Hi/lo (O8, PLO5/PLO6 Hi/Lo), Short Deck and fixed-limit hands are excluded from both tiers, not just the solver one. Split pots and inverted rankings change the pot math, and forced sizings break the polarized-sizing model, so there is nothing honest to say. Those hands still get full stats, Players, Spots and the replayer.
Every verdict must clear a one-sided 95% Wilson bound on top of its tolerance, so a small sample can't flag you. Findings roll up into 13 leak families across 6 categories, ranked by impact (proven gap times sample, per 100 hands), with a "Fix these first" card naming the top three. Each category expands into full detail tables - your opens vs the solver, facing opens and 3-bets, money by starting-hand category, river bluffing mix, bet-size value shares, defense vs sizes, all-in equity by street, deep stack-offs. "Review these hands" jumps any family back into the filtered library, and a "Your progress" panel tracks each flagged leak on the hands played since it was first seen - Improving, Slipping, No clear change yet, or Resolved (plus Just flagged, Watching, and Multiple spots), with sparklines. When nothing is provable at your sample size, the report says exactly that.
Sharing & Export
Under every replayed hand: Copy for forum (canonical
PokerStars-format text that round-trips through our own parser - the
format 2+2 and Reddit hand threads expect; the rare hand that can't be
serialized cleanly falls back to the markdown summary), Copy
markdown (a compact summary), and Create share
link. Share links are immutable anonymized snapshots at solvepoker.app/h/<slug>: they keep working if you
delete the original hand, never expose your library, expire after a
year, and can be listed and revoked. Name anonymization defaults to on,
and each link gets its own preview card so it unfurls properly in chats
and forums.
CSV: export the current library filter (up to 10,000 rows, in your current sort order), the Spots rows loaded so far, or the Players table.
Free Tier & Limits
No feature is gated. Free and Core differ only in caps:
| Free | Core | |
|---|---|---|
| Hands processed (lifetime) | 100,000 | 5,000,000 |
| Storage | 2 GB | 50 GB |
Per-file limit is 50 MB, up to 500 files per import, 30 imports an hour, and 50 imports open at once. Duplicates never count against the meter. Deleting an import frees its storage but doesn't refund processed hands, and cancelling Core keeps your library readable.
Card Colors
SolvePoker uses a 4-color deck throughout the tool:
| Suit | Color |
|---|---|
| Spades ♠ | White |
| Hearts ♥ | Red |
| Diamonds ♦ | Blue |
| Clubs ♣ | Green |
Those are the values on the dark themes. The six light themes keep the same 4-color mapping but darken it for a light ground: spades go near-black, and hearts, diamonds, and clubs deepen. The calculators' PNG exports render in whatever theme you're on, so a light-theme export carries the darkened deck; the Hand Tracker's share-card image is the one export that stays frozen on the dark-ground colors. How the card faces look is separate: the solver's 12 deck styles (see Settings) restyle the faces while keeping this same suit mapping.
Action Colors
Actions are color-coded consistently. Two action palettes exist; pick between them on your Account page under Appearance:
| Action | Classic (default) | Colorblind-safe |
|---|---|---|
| Check / Call | Green | Sky blue |
| Fold | Blue | Gray |
| Bet / Raise (small → large) | Light red → dark red | Light orange → dark orange |
| All-In | Its own fixed shade (salmon) | Its own fixed shade (pink) |
When multiple bet/raise sizes are available, they are tiered from lightest (smallest sizing) to darkest (largest sizing) in both palettes. Action colors encode meaning, so they are deliberately identical in every theme, light or dark - which color means which action never changes. The colorblind-safe palette sits on a blue/orange axis chosen to survive red-green color blindness, and it also switches the Hand Tracker's profit/loss coloring from green/red to blue/orange.
Chip Denominations
| Chip | Color |
|---|---|
| $100 | Black |
| $25 | Green |
| $5 | Red |
| $1 | White |
| $0.25 | Pink |
Chip stacks break down values using a largest-first greedy algorithm, so the visual representation always uses the fewest chips possible.