The tool opens to Postflop Study mode by default. Before diving into any section, you'll configure the game format using the controls at the top of the left pane.
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.
Spots (6max)
When 6max is selected, a Spot dropdown appears below the stack selector. This chooses which positional matchup to study. Spots are grouped into three sections:
- Heads-Up — Two-player spots like BBvsBTN, BBvsSB, COvsBTN, etc.
- 3-Way — Three-player pots like BBvsCOvsBTN
- 4+ Way — Four or more players to the flop
The spot determines which positions are involved and which preflop lines are available.
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 top-down view of the table with:
- Seat boxes for each position showing the position name (UTG, HJ, CO, BTN, SB, BB) and stack size
- A dealer chip ("D") on the button
- Chip stacks showing blind postings and any raises already made in the preflop action
- The pot total displayed on the felt
- The currently selected position is highlighted as the "hero" seat at the bottom
Position Selector — Below or around the table, you can click different positions to see their strategy for this line. Each position that has a decision in this line is clickable.
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 — How many canonical hands fall into this category, with the reach percentage showing how often they 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 (Category, Count, Action) to sort ascending or descending
- Frequency / Weighted toggle — Switch between showing the solver's preferred action frequency and the range-weighted frequency
- 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
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 hand entry shows:
- Hand display — The four cards in rank notation with suitedness pattern (e.g., "AKQ9ds")
- Raise % — The raise/open frequency as a number, color-coded on a gradient from green (high frequency) to red (low frequency)
- Call % — The call frequency (when available)
Hands are grouped by category when a category filter is active. The list 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 decisions
- vs RFI — 3-bet decisions
- vs 3-Bet — 4-bet decisions
- Additional lines as available (vs 4-Bet, vs Limp, etc.)
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) or "Wrong" (red), frequency bars showing every action's solver frequency, and a Continue button (also triggered by Space or Enter).
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 is considered "mixed" when no single action has greater than 75% frequency. If you press Mix and the hand truly is mixed, you're marked correct. If the hand has a clear best action (>75%), pressing Mix is wrong.
This rewards recognizing when hands are close and should be randomized rather than always played one way.
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
Sample Boards / Today's Free Boards — Quick-access preset buttons below the board. Each button shows a flop in colored card notation. Free users see 3 boards that rotate daily. Paid users see additional sample boards. Click any preset to instantly load that flop.
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)
Center Pane: Strategy Analysis
When no board is set — A placeholder prompting you to select a flop.
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 — A horizontal frequency bar divided into colored segments, one per available action. Each segment's width is proportional to the solver's aggregate frequency for that action. Below the bar, each action is labeled with its name and percentage.
- Hover over a segment to highlight it
- Click a segment to filter the hand list and category breakdown to only show hands that take that action
Category Breakdown — A table grouping all hands by postflop hand category. There are 97 granular categories grouped into 13 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, backdoor flush draw, flush blockers |
| Air | No pair, no draw |
Two view modes: Grouped (default, expandable meta-categories) and Classic (flat list of all 97 categories). Each row shows the category name, combo count, percentage, and action frequency bar.
Table controls:
- Click column headers (Category, Count, Action) to sort
- Toggle between Freq and Weighted 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. 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.
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.
Left Pane: Filters & Options
Practice / Heater — Two sub-mode buttons at the top:
- Practice — Standard practice with configurable filters
- Heater — Competitive streak mode (see Heater Mode)
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.
Position — Checkboxes for which positions to play as. In HU: OOP and IP. In 6max: the positions involved in the selected spot.
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 — Two checkboxes:
- Accept any bet size — 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 — Each spot starts on the flop and continues through turn and river with the same hole cards. See Play Flop to River.
New Spot — Generates a new random scenario.
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 (gray), Check/Call (green), Bet/Raise sizes (red/orange), All-In, Mix. Number keys 1-9 map to buttons left to right.
Result Modal — After answering: "Correct!" or "Wrong" header, frequency bars for every action, and a Continue button (Space or Enter).
Session Stats — Below the buttons: streak badge (fire icon), score (Correct/Total), accuracy percentage.
Right Pane: Results & History
Title — "Practice Mode" or "Heater Mode".
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.
Heater mode is a competitive variant of postflop practice designed to test consistency under pressure.
How it works:
- All filters are locked to maximum variety — every line, every street, every position
- "Accept any bet size" is forced on for fairness
- "Play Flop to River" is disabled
- Each correct answer increments your heater count (displayed as a large number in the right pane)
- Any wrong answer or timeout resets your heater to zero and submits your streak to the leaderboard
Leaderboard — The right pane shows a leaderboard of the longest heater streaks for the current solve (e.g., "HU 100bb"). Your entry is highlighted in gold. The leaderboard updates after each streak ends.
Right Pane in Heater Mode:
- Current Heater — Large streak counter
- Score — Correct/Total for the current session
- Best — Your longest heater this session
- Leaderboard — Top streaks with rank, name, and streak count
To switch back to standard practice, click the Practice button in the sub-mode toggle.
When the Play Flop to River checkbox is enabled in postflop practice, each spot becomes a multi-street drill:
- The tool deals a flop and navigates to a decision point. You see your four hole cards and answer the flop question.
- If you answer correctly, the solver's action determines the next move in the tree. A turn card is dealt, and you face the next decision on the turn with the same hole cards.
- If you answer correctly again, a river card is dealt and you face the river decision.
- The hand continues until you reach a terminal node (showdown or fold) or answer incorrectly.
- If you answer incorrectly at any point, the hand ends and the result is revealed.
This mode is excellent for practicing full hand lines rather than isolated street decisions. When active, the Streets filter checkboxes are disabled. The timer applies to each individual street decision.
Click the Stats button in the top mode bar (desktop only) to open the Training Stats Dashboard. This modal shows your historical training data:
- Total Spots — Lifetime count across all sessions
- Training days and streak records — How consistently you've been practicing
- Accuracy by spot, mode, position, and street — Breakdowns showing where you're strong and weak
- Study event counts — How many flops, turns, and rivers you've studied
- Suggested weak spots — Areas where your accuracy is lowest
Stats are tracked across sessions when you're logged in.
Click the gear icon in the top-left corner (desktop) or top-right corner (mobile) to open Settings.
Card Style — A carousel of 9 deck visual styles. Use the left/right arrows or dot indicators to browse: Classic, Classic Big, Vivid, Tinted, Neon, Bold, Bold-Dark, Standard, Icon.
Each style changes how the card faces look throughout the tool. A preview of all four suits is shown for each style.
| Key | Action |
|---|---|
| 1-9 | Select the corresponding action button (left to right) |
| Space or Enter | Continue to next hand (after answer is revealed) |
| Backspace or Z | Undo last action in study mode (rewind one step) |
| / | Focus the hand filter input in study mode |
| Escape | Close the card picker |
| M | Press Mix in preflop practice |
What it is
A notebook-style data explorer for every data point the trainer uses. Available at tool.solveplo.app/cli. Every cell runs 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?"
The notebook runs entirely in your browser against the same solve data and login session as the main tool. Commands are 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 /cli/notebook/:slug for sharing.
The UI
Three regions:
- Top bar — breadcrumb, editable notebook title, File menu (New, Examples, My notebooks, Import/Export, Clear, Save/Share), 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 current cell; 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, ▲ run (above) / ▼ run (from here) scope buttons, ↑/↓ reorder, ⎘ duplicate, + insert below, and ✕ delete.
Cells & Variables
Each cell is one command, optionally bound to a variable. A cell can be a single line or multi-line (Enter inserts a newline; Shift+Enter runs).
| 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. |
each $flops as b | $s_$b = spot ... --board=$b | Same with a named iteration variable — produces multiple bound variables $s_Kh7s2c, $s_AhKhQh, ... |
# Just a comment | Cell with only # lines renders as a markdown note — not executed. |
Autocomplete: Tab completes command names from any position at the start of a word. 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 in this session.
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:
# Title(h1 equivalent),## Subhead,### Smaller. - 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 | Interrupts a Run All / Above / Below between cells. | 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
Modifier: ⌘ on Mac, Ctrl elsewhere.
| Shortcut | Action |
|---|---|
Shift+↵ | Run current cell |
⌘⇧↵ | Run all cells |
⌘. | Stop a running range |
⌘S | Save notebook (creates slug if new, updates if yours) |
⌘↑ / ⌘↓ | Move focus to previous / next cell's input |
↑ / ↓ | Command history (on single-line cells) |
Tab | Autocomplete command name |
Esc | Dismiss autocomplete |
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 (hu_100bb, 6max_12/20/30/40/50/100/150/200bb). |
solve-info <solve> | Solve metadata: players, positions, spots, tree size, decision counts. |
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, suit, postflop, meta) in one call. |
ppt "<expr>" | Standalone PPT enumerator — every canonical hand matching an expression. |
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. |
preflop-hand <solve> AAKKds --line RFI --actor IP | Action mix for one hand (or all variants of a rank-name like AAKK). |
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 --line SRP --actor IP --opponent OOP --board Kh7s2c --postflop check | Resolve a line + actors + board + postflop sequence to a concrete nodeId. |
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).
$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 # flop cat + turn cat + river cat columns
--ppt "AA" # filter by PPT expression
--action bet100 # keep hands whose bet-pot freq >= --threshold
--threshold 50
--sort f_bet100 # sort column (default: primary action)
--limit 500 Optional flags:
--turn <card>/--river <card>— required when the postflop walk crosses into a later street. The strategy is runout-specific.--include-opponent— also return OOP's strategy matrix at the same position.--by-combo— emit per-physical-combo rows (one row per specific 4-card combination) instead of per-canonical aggregation. Needed forblockers/blocker-map.--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.
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.
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
Five independent systems are exposed:
| 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 | 13 meta groups (Monsters, Flushes, Straights, Sets/Trips, ... Air). |
categories flop --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. |
villain-hands $s | Opponent hands at a spot, sorted by reach weight. |
Blockers, Runouts, Combos
| Command | Purpose |
|---|---|
blockers $s AhKh | How much of the opponent's range is blocked by these cards. Needs --by-combo --reach on the source spot. |
blocker-map $s | 52-row heatmap: what % of opponent range each card blocks. |
runouts <solve> --line SRP --actor IP --opponent OOP --board Kh7s2c --postflop check | For every possible turn card, the action-mean of each action at the resulting node. |
combos <solve> AAKKds | Every physical combo of a canonical hand. |
combos-weighted $s AAKKds | Same list, with reach weight from a prior spot. |
Analyze (composed ops on $variables)
| Command | Purpose |
|---|---|
filter $s --ppt "AAKK" --action f_check --threshold 70 | Filter rows by PPT / category / action threshold. |
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. |
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. |
join $a $b --on display | Join two hand-row tables. |
describe $s | Row count, column names, numeric column stats (mean/min/max). |
--group-by values:
postflop-cat— fine-grained (97 postflop cats)postflop-meta— 13 meta groups (Monsters, Flushes, Straights, Sets/Trips, ... Air)preflop-cat— 31 preflop categoriespreflop-meta— 11 preflop meta groups
When any source variable was fetched with --reach, grouped averages are reach-weighted. Otherwise they fall back to canonical-combo weighting.
compare drops per-flop columns (postflop cat, reach weight) from the joined identity — those vary across flops and showing just $s1's value would be misleading. Hand identity is handIndex / display / combos / preflop cat, all of which are constant across flops.
SQL over $variables
The sql command runs any SQL query over your session's $variables. Each variable that holds a table becomes a queryable relation using the name without the $ — so $s1 is queried as FROM $s1 in the SQL text. CTEs (WITH), inner/left JOIN, GROUP BY, ORDER BY, HAVING, UNION, and sub-selects all work.
User-defined functions you can call inside SQL:
| Function | Returns | Use |
|---|---|---|
ppt(cards, "<expr>") | 1 / 0 | WHERE ppt(cards, 'AA:$ds') = 1 — filter by PPT |
metaOf(postflopCatId) | int | postflop meta group id (0-12) |
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, rank, 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 will tell you which keyword conflicts and suggest an alternative.
Window functions have limited support (ROW_NUMBER(), RANK() over simple partitions work; more advanced analytical functions may not). For 16,432-row tables everything runs in under a second.
Column cheat-sheet for spot results:
| Column | Meaning |
|---|---|
handIndex | 0–16,431 canonical hand id. Stable identifier across solves of the same game. |
display | Human-readable canonical hand, e.g. AAKKds. |
cards | CSV of 4 card values. Used by ppt(cards, ...). |
combos | Canonical combo multiplicity (1 / 4 / 6 / 12 / 24). |
reachWeight | Reach-weighted combo mass at this node (only when --reach was used). |
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. Keys: f_bet100, f_bet25, f_check, f_call, f_fold, etc. |
PPT in the CLI
Every command that filters hands accepts --ppt "<expr>". Inside SQL, use the ppt(cards, '<expr>') UDF in any WHERE / SELECT context. The parser is the exact same one the main tool uses — see PPT Filter Syntax for the full reference. Practical CLI examples:
# Standalone enumerator — every canonical hand matching the expression:
ppt "AAKK:$ds" --solve hu_100bb
# Filter a spot:
$aces = spot hu_100bb --line SRP --actor IP --opponent OOP \
--board Kh7s2c --postflop check --ppt "AA"
# Filter a prior variable:
filter $s --ppt "[AA-JJ]"
# Filter inside SQL (hands with Ace of spades AND no pair in hand):
sql "SELECT display, f_bet100 FROM $s WHERE ppt(cards, 'As:$np')
ORDER BY f_bet100 DESC LIMIT 20" Save & Share
Notebooks can be saved to your account and shared by URL. Sign in at solveplo.app and then File → Save creates a permanent URL like tool.solveplo.app/cli/notebook/AB23C9Y7. Anyone with the link can open and read it; only you can overwrite it (others get a "Fork to my account" option).
| 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. |
The notebook title shown in the top bar is editable inline (click it). The title is saved with the notebook content. Cell outputs are NOT stored server-side — opening a shared notebook re-runs the cells on demand (or use Run All to recompute everything).
Example Notebooks
The sidebar ships five ready-to-run notebooks; clicking one loads a fresh sequence of cells you can step through (or edit freely).
| Notebook | What it covers |
|---|---|
| Getting Started | First commands: list solves, browse hand categories, check a preflop line, build a first spot. |
| C-bet by Flop | BTN vs BB SRP — fetch the same spot on 9 structurally different flops, compare cbet frequencies across all flops side-by-side, and drill into category-grouped summaries. |
| PPT Hand Queries | Tour of the hand-query syntax: exact cards, rank multisets, suit patterns, ranges, rundowns, logical ops, macros. |
| RFI by Position | Compare UTG / HJ / CO / BTN opening ranges in 6max 100bb to see which hands widen with position. |
| Category Analysis | Deep-dive on the 5 categorization layers and how to break a spot's hand distribution down by meta group. |
| SQL Analysis | Full SQL workflow — CTEs, three-way JOINs across flops, GROUP BY by meta, and the ppt() UDF. |
| Line Distribution (Kh9s5s) | BTN's 7 postflop lines on Kh9s5s (turn 2d, river Qh): which flop hand types fill each line, how reach-weighted mass flows through the tree, and where flush-draw composition ends up at showdown. |
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 \
--board Kh7s2c --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. Export for offline analysis.
export $s1 --format csv --filename k72_cbet.csv Notes & Caveats
- Variables clear on reload. Cell inputs survive a browser reload; cell results and
$variablesdo not. Click ▶ Run All (or⌘⇧↵) to regenerate after a reload, or open a saved notebook via its slug and Run All there. - Strategy-only by default. The
spotcommand returns the tree's strategy at each hand; use--reachto propagate combo weights through the hero's actions and get the true probability mass at the final node. - 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. - Bet column keys are pot-fraction-encoded:
f_bet100= pot,f_bet50= half-pot,f_bet33= third-pot, etc. Thespottable also labels them human-readably ("Bet Pot", "Check", etc.). - Rate limits apply for non-paid accounts — same as the main tool.
The hand filter supports ProPokerTools (PPT) syntax — a powerful notation for describing sets of PLO hands.
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) |
$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 (= $np for ≥5-card holes) |
$op | Exactly one pair |
$tp | Two pair (double paired) |
$0g | 0-gap rundown (consecutive ranks; n-card per game) |
$1g | 1-gap rundown (one larger step) |
$2g | 2-gap rundown (one even-larger step) |
Advanced Syntax
| Pattern | Meaning |
|---|---|
[A-J] | Rank range: ace through jack |
[T+] | Ten or higher |
[2s,Jc,T] | Specific card set |
15% | Top 15% of hands by strength |
30%-50% | Hands ranked between 30th and 50th percentile |
>N | Hands stronger than rank N |
<N | Hands weaker than rank N |
Examples: (AA,KK):$ds — Aces or kings, double-suited. AA$nt — Two aces, no trips. KQ!Ks — King-queen without king of spades. RR:$ds — Any pair, double-suited.
A free, browser-based equity calculator for PLO4, PLO5, and PLO6. Set up 2 to 6 hands, add a board and dead cards, and get live win/tie percentages with no server round-trips and no account required. Open it at solveplo.com/odds.
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.
Everything runs locally in your browser — no hand data leaves your device.
Setting Up Hands
Variant toggle — Pick PLO4, PLO5, or PLO6 in the top-right of the controls column. Switching variants clears the board and all hands since hole-card counts differ.
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 greyed 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). Each hand past the first two has a × button in its header to remove it.
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 — the calculator will prompt you to finish the flop.
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. Expand/collapse the tray at any time.
Randomize & Locking
Randomize (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.
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 turns gold, 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 (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.
Reading Results
Per-hand readout — Each hand card shows:
- Win % — The probability this hand wins outright at showdown.
- Tie % — 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. Shrinks as more runouts accumulate.
- ✓ exact (flop/turn/river) — Replaces the margin once the result is from exact enumeration.
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 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. For Monte Carlo: shows the running simulation count and the current convergence margin. For exact: shows total enumerated runouts and the “exact” label. If the simulation converges before reaching its iteration target (all hands within ±0.3 pp), it stops early and surfaces a converged badge.
Share Links
Share link — Copies a URL that encodes your current variant, hands, board, dead cards, any hand locks, and the dead-cards tray state into the hash. Anyone who opens the link sees the exact same setup, including which hands are locked.
The calculator doesn't write to the URL as you click around — the address bar stays clean during normal use. The Share link button writes the hash briefly (into the clipboard, plus the current address bar for a moment so you can verify), then strips it again. If you land on the calculator via a share link, it hydrates once from the hash and then strips it from the URL.
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) |
← → ↑ ↓ | Move the selected slot across hands / board / dead cards |
Del / Backspace | Clear the currently selected slot |
r | Randomize unlocked hands |
c | Clear everything |
[ / ] | Cycle variant (PLO4 ↔ PLO5 ↔ PLO6) |
4 / 5 / 6 | Jump directly to PLO4, PLO5, or PLO6 |
⌘ 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.
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. Every browser gets an answer.
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), 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.2 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 server calls, no telemetry on the hands you enter, no account needed. You can run it offline after the first load.
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 solveplo.com/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 8 games and
all four of PPT's Classic range syntaxes alongside the modern
Generic syntax.
Quickstart
- Open solveplo.com/pql.
- Pick an example from the Examples drawer (or start from the default AA-vs-random).
- Press
⌘ Enter/Ctrl+Enterto run. - Tweak the trials input to sharpen the CI (higher = slower but tighter).
- Tweak the seed to get a different-but-reproducible MC draw.
Every query uses a seeded PRNG — same query + same trials + same seed gives identical results every time, so share links reproduce bit-for-bit.
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 (Big-O) | 5 | Hi |
omaha85 | 5-card Omaha Hi/Lo | 5 | Hi/Lo |
omahahi6 | 6-card Omaha (PLO6) | 6 | 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 |
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 in the playground.
Built-in Functions
~30 functions are implemented in v1. 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) | Exact hand-vs-hand equity on the given street |
HvRequity(p, street) | Hand-vs-range equity on the given street |
fractionalRiverEquity(p) | Equity as an exact fraction (v1: numeric) |
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:
| Function | Description |
|---|---|
handType(p, street) | Coarse category (pair, twopair, flush, …) |
minHandType(p, street, t) | True if p has at least hand type t |
winningHandType() | Hand type that won the pot |
flopHandCategory(p) | PPT flop category (toppair, overpair, set, …) |
minFlopHandCategory(p, c) | At-least check on flop category |
exactFlopHandCategory(p, c) | Exact check on flop category |
hiRating(p) | Opaque hi strength (higher = better) |
loRating(p) | Opaque lo strength (lower = better) |
Lo predicates (Omaha-8, Stud-8, Razz):
| Function | Description |
|---|---|
madeLo(p, street) | True if p has a qualifying low |
nutLo(p, street) | True if p has the nut low |
Board texture:
| Function | Description |
|---|---|
boardsuitcount(street) | Distinct suits on the board at street |
boardLoCardCount() | Count of low cards (A-8) on the river board |
boardHasOneDistinctLoCard() | True if board has exactly 1 distinct low rank |
boardHasTwoDistinctLoCards() | True if board has exactly 2 distinct low ranks |
boardAllowsMadeLo() | True if the board allows a made low |
fourFlush(p, street) | True if p has a 4-flush |
threeFlush(p, street) | True if p has a 3-flush |
handBoardIntersections(p, street) | Count of shared ranks between hand and board |
Range predicates:
| Function | Description |
|---|---|
inRange(p, 'range-string') | True if p's actual hand is inside the given range |
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:
*. - Operators:
,(OR),:(AND),!(NOT),()grouping. Precedence:!>:>,. - Spans:
AA-TT,KK+,AA-,[A-Q]. - Percent:
15%(top 15%),15%-30%,15%6h(6-handed ordering). - Shape macros:
$ssuited,$ooffsuit,$dsdouble-suited,$sssingle-suited. - Category macros:
$Bbig (A-J),$Mmiddle (T-7),$Zsmall (2-6),$Llow (A-8),$Nno-low (K-9),$Fface,$Rbroadway,$Wwheel. - Card-count top-off: hand patterns are padded to the game's hole count.
AAin Omaha becomesAA**.
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 playground; 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' 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.
The playground doesn't mirror state to the URL as you type — the address bar stays clean. A future Share link button will encode the query, trial count, and seed as a hash-based URL that anyone can open to reproduce your result. In the meantime, just copy the query text.
Differences from PPT
Our implementation aims for full behavioural 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.
AKsin Generic means "A (any suit) + K of spades", not "AK suited". For "AK suited" in Generic, writeAK:$s. For legacyAKsbehaviour, addsyntax='classic'to the FROM clause. Similarly,AKoin Generic parses as 3 slots (ois a rank variable). The runtime flags this with a clear error and points to this doc. - 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'. Other 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
nutHiOutson omaha is per-trial expensive (~50ms/trial on holdem, ~200ms/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.bestHiRating(p, street)2-arg form enumerates over the unseen deck, not the player's range. The 0-arg / 1-arg (street-only) form is correct (max across all players).
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.
Determinism — A seeded PRNG drives every run. With a fixed seed, the same query produces bit-for-bit identical results across devices.
Privacy — No server calls, no telemetry on queries. Everything runs in your browser.
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 solveplo.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% in faint gold, 70% in bolder gold); 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 — downswings, bankroll requirements, percentile breakdown, Bayesian credibility, backing, year-by-year, and stake progression. All cards are expanded by default; click the chevron in the top-right of a card to collapse it.
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 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, 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.
Tournament sentence reads similarly: “I play 200 tourneys of Reg MTT ($109) at +20% 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 gold EV line — Mathematically exact expected value over time.
- 70% confidence band (bolder gold) — 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 gold) — The range where 95% of outcomes fall. Same MC-derived method as the 70% band.
- 99.7% envelope (dashed gold outline) — The rare-tail outline. Practically all trials stay inside it; a few pathological ones may peek past.
- Twenty thin cyan sample runs — Actual simulated paths picked at evenly-spaced trial indices. These are honest examples of what your session could look like.
- Solid cyan "best" line — The single luckiest trial of 1,000.
- Solid red "worst" line — The single unluckiest trial of 1,000.
- Dashed zero line — So you can see at a glance where breakeven lies.
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 gold 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:
- Expected profit — The mean of 1,000 simulated endpoints.
- Probability of profit — Share of trials that finished above zero.
- 95% confidence range — The 2.5% and 97.5% quantiles of the endpoint distribution.
Downswings
Answers "how bad does it typically get, and for how long?". Three pieces:
- Depth table — Probability that your worst peak-to-trough drawdown (at any point in the sample) exceeds each threshold (500 bb, 1,000 bb, 2,000 bb, 5 BI, 10 BI, 20 BI …).
- Duration table — Probability that at some point you're stuck below a prior peak for at least N hands (10k, 25k, 50k, 100k, 250k).
- 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
"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%. Closed-form. 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.
Percentile Breakdown
Seven anchor points of the endpoint distribution, ordered top to bottom:
- Best 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.
- Worst 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 — Φ(r / (SD/√(N/100))). Big gold number, tinted green for ≥85%, amber for 60–85%, red below.
- Credible intervals on your true winrate at 60%, 75%, 90%, and 95% confidence. Under flat-prior Normal updating, these are r ± z · SD/√(N/100).
- Hands needed until you know your winrate within [X] bb/100 at [Y]% confidence — Inverts the above: solves for the N at which the credible-interval half-width 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 Roll year 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.
The monthly volume is derived from the top hands input — adjust hands above to change year length. (e.g. 1M hands ⇒ ~83k per month.)
Zoom Out — Career Stake Progression
A multi-year career simulation. 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 the top of the ladder.
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 ⚙ Advanced button next to the inputs opens a slide-in drawer with the rest of the knobs. 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 in gold so you register the change.
Share & Save
Copy share link — Encodes the full current state (mode, all inputs, seed, which cards are expanded) into the URL hash. Anyone opening the link gets the exact same chart and results.
Save image — Generates a 1600×900 PNG of the main chart
with title, plot frame, y-axis labels and gridlines, x-axis labels, EV/Best/Worst
legend, and a solveplo.app/variance watermark. The PNG
is self-contained — the axes are baked into the image, not stripped HTML overlays.
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. 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. Nothing you type about your win rate or bankroll leaves your device.
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. Preflop, every position can fold or pot-raise, with one 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 all use a single initial-bet size per street — 66% pot on the flop, pot on the turn and river — with pot-only raises. 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 the richest abstraction — 200 flop buckets, 100 turn/river buckets, and "Large" turn/river texture grouping. The 6-max sims share 30 buckets per street and "Small" turn and river texture grouping. Each one solves 57 distinct 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 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% (1bb cap) | 30 / 30 / 30 | Small (90) | Small (229) | flop bet 66% · turn/river bet pot · raise pot | 57 | 258,999 | 22.6 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 |
Card Colors
SolvePLO uses a 4-color deck throughout the tool:
| Suit | Color |
|---|---|
| Spades ♠ | White |
| Hearts ♥ | Red |
| Diamonds ♦ | Blue |
| Clubs ♣ | Green |
Action Colors
Actions are color-coded consistently:
| Action | Color |
|---|---|
| Check / Call | Green |
| Fold | Blue |
| Bet / Raise (small) | Light red |
| Bet / Raise (medium) | Medium red |
| Bet / Raise (large) | Dark red |
| All-In | Deep red |
When multiple bet/raise sizes are available, they are tiered from lightest (smallest sizing) to darkest (largest sizing).
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.