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.

At the top of the center pane, two pairs of pill buttons control which section you're in:

  • Pre / Post — Switches between preflop and postflop modes
  • Study / Practice — Switches between study (browse strategies) and practice (drill with quizzes)

These combine into the four main sections:

StudyPractice
PrePreflop StudyPreflop Practice
PostPostflop StudyPostflop Practice

A Stats button sits to the right of the mode toggles, opening the Training Stats Dashboard.

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-CategoryExamples
MonstersStraight flush, quads, nut full house, full house
FlushesNut flush, 2nd nut flush, non-nut flush
StraightsAll straight + straight draw combos
Sets / TripsTop/mid/bottom set + draw combos, trips
Two PairAll two pair combinations including overpair + pair
OverpairOverpair + draw combos
Top PairTop pair + draw combos
Mid / Btm PairMiddle and bottom pair + draws
Pocket PairUnimproved pocket pairs / underpairs + draws
Flush DrawsNut flush draw, 2nd nut flush draw, flush draw (no pair)
Straight DrawsWraps, open-enders, gutshots
BackdoorsBackdoor nut flush draw, backdoor flush draw, flush blockers
AirNo 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:

  1. The tool deals a flop and navigates to a decision point. You see your four hole cards and answer the flop question.
  2. 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.
  3. If you answer correctly again, a river card is dealt and you face the river decision.
  4. The hand continues until you reach a terminal node (showdown or fold) or answer incorrectly.
  5. 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.

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

ShortcutAction
Shift+?Pin / unpin the cheatsheet
Shift+,Open Settings
Shift+`Focus the left pane (Players → Stacks → …) — see "Left-pane navigation" below
Shift+COpen the CLI notebook
Shift+FFocus the filter input in the current view
EscClose 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)

ShortcutAction
Shift+MToggle Study ↔ Practice
Shift+PToggle Preflop ↔ Postflop
Shift+NNew practice spot
Shift+EToggle EV display (Preflop Study)
Shift+DOpen Training Dashboard (Practice)

Card picker (browse the 4×13 grid)

ShortcutSlot
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 cyan 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 (Players → Stacks → Spot → 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.

KeyAction
1NPress 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 14 in practice.
Space or EnterContinue to next hand after the answer is revealed
MPress Mix in Preflop Practice
BackspaceUndo 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:

  1. Press Shift+rank — a small banner at the top arms the rank and waits for a suit.
  2. Press the suit letter (S ♠, H ♥, D ♦, C ♣) — with or without Shift held — and the card lands on the next empty slot.
ExampleResult
Shift+A then HAce of hearts → turn
Shift+3 then CThree of clubs → river
Shift+T then STen 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 29 are reserved exclusively for this chord — no other shortcut uses them.

CLI-specific shortcuts (Run, Save, Stop, cell ops) live in the CLI Notebook → Keyboard Shortcuts section below.

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:

  1. 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.
  2. Sidebar — the Variables panel. Every $name produced by a cell appears here with its kind and shape. Click the name to insert $name into the current cell; click ✕ to delete.
  3. 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).

InputEffect
solvesRun 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=$bSame with a named iteration variable — produces multiple bound variables $s_Kh7s2c, $s_AhKhQh, ...
# Just a commentCell 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:

ActionWhat it runsWhere
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 AllEvery cell in the notebook, top-to-bottom.Top bar button or ⌘⇧↵.
StopInterrupts 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

The CLI 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

ShortcutAction
Shift+RRun all cells
Shift+SSave notebook (creates slug if new, updates if yours)
Shift+.Stop a running range
Shift+\Toggle the variables sidebar
Shift+GJump to the cell that defined a variable
Run all (alias)
SSave (alias)
.Stop (alias)

Cell operations (target = focused cell, or last cell)

ShortcutAction
Shift+AAdd a new cell below the focused one (and focus it)
Shift+OAdd a new cell above the focused one
Shift+BackspaceDelete the focused cell
Shift+YDuplicate (yank) the focused cell
Shift+J / Shift+KMove the focused cell down / up (vim-style)
Shift+ / Shift+Focus previous / next cell
/ Focus previous / next cell (alias)

Inside the cell editor

ShortcutAction
Shift+Run the current cell
Run the current cell (alias)
TabAutocomplete command name / variable
/ Cycle suggestions, or command history (on single-line cells)
Accept the selected suggestion (when the suggestion panel is open)
EscDismiss the suggestion panel

Command Reference

Run help for the full list, or help <cmd> for detail on one command. Commands are grouped:

Session

CommandPurpose
help [cmd]List all commands, or show detail for one.
varsList active variables.
clear $var / clear --all / clear --cellsRemove a single variable, everything, or just cells.
export $var --format csv|jsonDownload 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

CommandPurpose
solvesEvery 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> AAKKdsOne canonical hand's full record.
hand-categoriesAll 5 categorization systems (preflop, suit, postflop, meta) in one call.
ppt "<expr>"Standalone PPT enumerator — every canonical hand matching an expression.

Preflop

CommandPurpose
preflop-lines <solve>All decision spots (RFI / SRP / 3BP / ...).
preflop-spot <solve> --line RFI --actor IPFull per-hand raise / call / fold / VPIP for a spot.
preflop-hand <solve> AAKKds --line RFI --actor IPAction mix for one hand (or all variants of a rank-name like AAKK).

Game Tree

CommandPurpose
tree <solve> --street flopDump 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 checkResolve a line + actors + board + postflop sequence to a concrete nodeId.

Ranges

CommandPurpose
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 the opponent's strategy matrix at the same node.
  • --by-combo — emit per-physical-combo rows (one row per specific 4-card combination) instead of per-canonical aggregation. Needed for blockers / 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:

CommandWhat it returns
preflop-categories31 preflop categories with meta grouping (AA+Pair, High Pairs, Rundowns, etc.).
suit-categories5 suit patterns (DS / SS / rainbow / mono / 3flush).
postflop-category-labels97 postflop categories (Top Set + NFD, Nut Flush, OESD, etc.).
meta-categories13 meta groups (Monsters, Flushes, Straights, Sets/Trips, ... Air).
categories flop --board Kh7s2cEvery hand on a board with its postflop category + meta.
category-of flop Kh7s2c AhKhQsJsOne hand's category on a board.
category-breakdown $s --by metaHistogram of a spot's hands by meta group or full category.
villain-hands $sOpponent hands at a spot, sorted by reach weight.

Blockers, Runouts, Combos

CommandPurpose
blockers $s AhKhHow much of the opponent's range is blocked by these cards. Needs --by-combo on the source spot.
blocker-map $s52-row heatmap: what % of opponent range each card blocks.
runouts <solve> --line SRP --actor IP --opponent OOP --board Kh7s2c --postflop checkFor every possible turn card, the action-mean of each action at the resulting node. Result is a runout grid that's also queryable as a table (one row per card, columns card / cardIdx / f_<action>).
combos <solve> AAKKdsEvery physical combo of a canonical hand.
combos-weighted $s AAKKdsSame list, with reach weight from a prior spot.

Equity / PQL

CommandPurpose
pql "<query>"Run a Poker Query Language query — equity, hand-type histograms, percentile selectors, scenario predicates. Supports all 9 PQL games (PLO4/5/6, Hold'em, Omaha-8, 5-card Omaha-8, Stud, Stud-8, Razz) plus Triton-rule shortdeck. $variables outside string literals expand to explicit combo lists, so GTO ranges from spot / preflop-spot pipe straight in. Flags: --trials, --seed, --timeout. See PQL inside the notebook.

Analyze (composed ops on $variables)

CommandPurpose
filter $s --ppt "AAKK" --action f_check --threshold 70Filter rows by PPT / category / action threshold.
diff $a $b --action f_bet100Per-hand delta between two spots on one action.
compare $a $b $c --action f_check --all-min 50Wide 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-metaAggregate per category — rows = categories, cols = each var's combo-/reach-weighted avg frequency for hands in that group.
join $a $b --on displayJoin two hand-row tables.
describe $sRow 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 categories
  • preflop-meta — 11 preflop meta groups

Grouped averages are reach-weighted by default (since spot now propagates reach unless you pass --no-reach). If a source variable was fetched with --no-reach, the aggregation falls back to canonical-combo weighting for that var.

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. 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.

User-defined functions you can call inside SQL:

FunctionReturnsUse
ppt(cards, "<expr>")1 / 0WHERE ppt(cards, 'AA:$ds') = 1 — filter by PPT
metaOf(postflopCatId)intpostflop meta group id (0-12)
metaName(postflopCatId)stringgroup label ("Sets / Trips", "Top Pair", …)
preflopCat(preflopCatId)stringpreflop category label
preflopMeta(preflopCatId)stringpreflop 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:

ColumnMeaning
handIndex0–16,431 canonical hand id. Stable identifier across solves of the same game.
displayHuman-readable canonical hand, e.g. AAKKds.
cardsCSV of 4 card values. Used by ppt(cards, ...) and by the pql command's $var substitution. Also present on preflop-spot rows so preflop ranges pipe into PQL the same way.
combosCanonical combo multiplicity (1 / 4 / 6 / 12 / 24).
reachWeightReach-weighted combo mass at this node. Present unless --no-reach was passed.
preflopCatId / preflopCatLabelPreflop category (0–30).
flopCatId / flopCatLabelFlop hand category (0–96). Always present when --categorize.
turnCatId / turnCatLabelTurn hand category (0–96). Present on turn/river spots.
postflopCatId / catLabelFinal-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-specific — f_bet100, f_bet50, f_bet25 in hu_100bb; f_bet66 on the flop and f_bet100 on the turn in 6max_100bb; plus f_check, f_call, f_fold, f_raise100, f_allin. Run describe $s after fetching a spot to see the exact action columns the resolved node exposes.

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"

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/Big-O), omaha85, 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

FlagDefaultEffect
--trials <n>100,000Monte-Carlo sample size. Higher = tighter CI, slower.
--seed <n>random per callRNG seed for reproducibility. Same query + same seed = bit-for-bit identical results.
--timeout <sec>60Hard wall-clock cap. The query stops cleanly with whatever it has so far if it hits the cap.

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 (e.g. straight-flush → quads → full house → flush → … → high card; for shortdeck, flush sits above full house).
  • Mixed scalars + histograms or multi-query batches → stats card with a queryable table projection underneath. Bind to $var and feed back into sql if you want to post-process.

Notes

  • Percentile syntax is '15%', not 'top 15%' — there's no top keyword. Bands like '15%-30%' work; the 6-handed variant is '15%6h'. PLO4/5/6 + Hold'em + Omaha-8 ship with prebuilt percentile tables; shortdeck does not (use specific hands instead).
  • $var expands 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 builds a canonical hand-rank map (~30–60 s on a fast laptop) and caches it for the rest of the session. A yellow hint appears after 5 s explaining the wait.
  • Stop button cancels long-running PQL queries — the worker is wired to PQL's cancelQuery hook and aborts within a couple of trials.
  • Reach weights are not used when piping a spot result into PQL — the variable substitutes as a uniform list of combos. To reach-weight an analysis, filter to the hands you care about (e.g. WHERE reachWeight > 0.1) before piping.

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).

ActionWhat 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 linkAppears for saved notebooks — copies the URL to clipboard.
File → Fork to my accountWhen viewing someone else's notebook, creates your own editable copy.
File → My notebooksLists your saved notebooks; click to open.
File → Import / Export JSONWorks 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 File → Examples menu ships five ready-to-run notebooks; clicking one loads a fresh sequence of cells you can step through (or edit freely). Each is designed to showcase a distinct slice of the CLI's capability surface.

NotebookWhat it covers
BB check-raise on Jh7d6d6max 3-bet pot, BB OOP vs BTN cbet on a wet flop. Pull the c/r portion via SQL filtering, equity it vs BTN's betting range with PQL, then break the c/r down into meta buckets (Sets / Two Pair / Top Pair / Draws / Air) and equity each one. Showcases the spotsqlpql pipe.
Turn equity shift (J87)6max BTN vs BB SRP on Js8d7c. Sweep all 47 turns with runouts, then deep-dive three contrasting turns (paired, straight-completing, brick) with PQL equity, hand-type histograms, and category breakdowns. Showcases runouts + per-turn PQL piping + multi-query batches.
Top X% vs GTO openPQL's percentile selector ranks hands by all-in equity. Compare '15%' against the actual GTO BTN open at 6max 100bb — equity, hand-type composition, multi-game (PLO4 / PLO5 / PLO6) — plus a quick shortdeck demo. Showcases percentile selectors, multi-game support, and pure-PQL workflows.
Getting StartedFirst commands: list solves, browse hand categories, check a preflop line, build a first spot. Good entry point.
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. Heaviest SQL workflow — UNION-ALL CTEs and 7-way slicing.

Notebook results are recomputed on load — only the cell inputs are stored. Use Run All after opening to regenerate everything, or step through cell-by-cell to follow the analysis.

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. 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

  • Variables clear on reload. Cell inputs survive a browser reload; cell results and $variables do not. Click ▶ Run All (or ⌘⇧↵) to regenerate after a reload, or open a saved notebook via its slug and Run All there.
  • Reach propagation is on by default. The spot command propagates combo weights through every hero action on the path, so reachWeight on each row is the true probability mass the hero's range puts into that hand at the final node. Pass --no-reach to 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, not FROM s1. The engine rewrites the $name into 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-specific. hu_100bb uses f_bet25 / f_bet50 / f_bet100 on the flop. 6max_100bb uses f_bet66 on the flop and f_bet100 on the turn. Facing-bet decisions add f_raise100 / f_call / f_fold. Run describe $s after 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 uses UTG / 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 powerful notation for describing sets of PLO hands.

Basic Patterns

PatternMeaning
AKHand contains an ace and a king
AAHand contains a pair of aces
AhHand contains the ace of hearts
As**KhHand contains ace of spades AND king of hearts

Operators

OperatorMeaning
,OR — matches either pattern
:AND — matches both patterns
!NOT — excludes the pattern
()Grouping

Suited Patterns

PatternMeaning
AxKxSuited ace-king (same suit)
AxKyOffsuit ace-king (different suits)
AxAyxyDouble-suited aces
AxAyxzSingle-suited aces
xxyyDouble-suited hand
xxyzSingle-suited hand
xxxxMonotone (all same suit)

Rank Patterns

PatternMeaning
RRAny pair
RROOTwo pair (two different pairs)
RRONExactly one pair
TT-77Pair from tens down to sevens
Q+Queen or higher
9876-Rundown starting from 9

Modifier Tags

PatternMeaning
$dsDouble-suited (2-2 in PLO4, 2-2-1 in PLO5, 2-2-1-1 in PLO6)
$ssSingle-suited (2-1-1 in PLO4, 2-1-1-1 in PLO5; n/a in PLO6)
$tsTriple-suited (2-2-2 — PLO6 only)
$npNo pair (all distinct ranks)
$ntNo trips (= $np for ≥5-card holes)
$opExactly one pair
$tpTwo pair (double paired)
$0g0-gap rundown (consecutive ranks; n-card per game)
$1g1-gap rundown (one larger step)
$2g2-gap rundown (one even-larger step)

Advanced Syntax

PatternMeaning
[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
>NHands stronger than rank N
<NHands 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 hand-vs-hand equity calculator for PLO4, PLO5, and PLO6. Set up 2 to 6 specific 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.app/odds. 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.

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 — 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.

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. 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 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. Touch devices skip the toast notifications since there's no keyboard to undo with.

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 (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: cyan 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:

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) — solveplo.app/odds?u=<8 chars>. The state is POSTed to /api/odds/share and stored in Cloudflare KV; the response is a short ID embedded in the URL. Tidy in chats, slacks, and tweets.
  • Hash fallback — If the network is unavailable or the API errors, the calculator falls back to solveplo.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. Title block reads SolvePLO in white plus a gold subtitle ("Hand Equity"); a variant chip (PLO4/5/6) sits top-right. Footer reads solveplo.app/odds. Dead cards are included if any are set. Lock buttons, remove buttons, and the dead-cards toggle are hidden in the snapshot for a clean export. Saved as odds.png.

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.

KeyAction
?Toggle the keyboard-shortcut overlay
EscRe-select the first empty slot (or close the help overlay / settings sheet)
Move the selected slot across hands / board / dead cards
Del / BackspaceClear the currently selected slot
rRandomize unlocked hands
cClear everything
[ / ]Cycle variant (PLO4 ↔ PLO5 ↔ PLO6)
4 / 5 / 6Jump directly to PLO4, PLO5, or PLO6
⌘ Z / Ctrl+ZUndo 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 r a 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.
  • 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. Every browser gets an answer.

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), 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 telemetry on the hands you enter, and no account needed. The page does record an anonymous "calculator was used" ping so we know the tool is alive, but the hands themselves never leave your device. Share links are opt-in: nothing is uploaded until you click Copy share link.

A free, browser-based range-vs-range equity tool for NLHE, PLO4, PLO5, and PLO6. 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 solveplo.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 page itself runs in your browser; ranges and the optional board are sent only when you click Copy share link (and even then only stored as a short ID in our KV).

Game Variants

The variant pills (top right) switch between four games:

VariantHole cardsEngine
NLHE2game='holdem'
PLO44game='omahahi'
PLO55game='omahahi5'
PLO66game='omahahi6'

Switching variants swaps the per-variant default ranges if you haven't edited them. The placeholder also updates: ** for NLHE, **** / ***** / ****** 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):

VariantPlayer 1 defaultPlayer 2 default
NLHEJJ+,AKs,AKoAA
PLO44x5x6y7y,6789,RR$B$BAA**
PLO54x5x6y7y8z,6789T,RR$B$B$BAA***
PLO64x5x6y7y8z9w,6789TJ,RR$B$B$B$BAA****

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.

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 (gold 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 query timing — Some PLO5/6 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 gold number (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=50,000". The first number is the 95% confidence half-width; the second is how many trials contributed.

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. The latter two are disabled until you've run a query.

Share & PNG Export

Copy share link — Two-tier, same as the other calculators:

  • Short URLsolveplo.app/range-calc?u=<8 chars>. POST to /api/range-calc/share, KV-backed.
  • Hash fallback?v=4&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=50,000)
Player 2 = 57.69%  (95% CI ±0.25%, n=50,000)

50,000 trials in 234ms

Save PNG — 1080-wide portrait PNG. Title block SolvePLO in white plus a gold 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. 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: solveplo.app/range-calc. Saved as range-calc.png.

Keyboard Shortcuts

KeyAction
⌘ Enter / Ctrl+EnterRun the query
EscClose 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 gold numbers tell you how the cold money splits.
  • Single-suited rundown vs aces on the flop — Player 1 4x5x6y7y, Player 2 AAxx, board 9s5s2c, run.
  • 3-handed bingo equity for a tight opener — UTG $B$B$B$B$ds, BTN 10%, 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'******' is too large to materialise; use a narrower shape like $B$B**** or concrete cards.
  • Determinism — Same ranges + board + trials + seed = identical numbers across machines. Useful for sharing exact results.

A free, browser-based hand-class histogram for ranges in NLHE, 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 so you can see whether your range really hits more than “normal.” Open it at solveplo.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.

Game Variants

Variant pills at the top: NLHE, PLO4, PLO5, PLO6. Switching variants swaps the per-variant default Player 1 / Player 2 ranges if you haven't edited them. Hand classes shown match the variant (quads/full-house/etc. are global; PLO surfaces them more often).

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). 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 player's hand class won the trial. Always evaluated on the river (other streets are disabled). Requires at least 2 ranges.

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. PLO5/PLO6 first-run percentile selectors trigger the same ~30–60 s lookup-table build as Range Equity (yellow hint after 5 s).

Reading the Chart

The chart is a horizontal bar histogram — one row per hand class, up to six colored series stacked within each row. Default order is best hand at top (straight flush → quads → full house → flush → straight → trips → two pair → pair → high card). The sort toggle above the chart flips between ↑ best top and ↓ worst top.

When no board is fixed, dashed vertical lines mark the random-hand baseline probability for each bucket. Hover (or focus) any row to pop a tooltip showing per-series percentages and trial counts; with exactly two series the tooltip adds the delta in percentage points. Click to pin the tooltip (gold border, "click to unpin · ESC" hint); 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 (solveplo.app/hand-dist?u=<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 PNGLandscape 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 with baked-in colors and 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 SolvePLO + gold "Hand Distribution"; footer solveplo.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 cyan 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 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.

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, 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 gold 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 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?". 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 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 inputswinrate, 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 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 (the card's title in the UI is just "Stake Progression"; older builds prefixed it with "Zoom out ·"). 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 (1 at NL100, 2 at 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 — Two-tier, like the rest of the calculators. Primary path is a KV-backed short URL, solveplo.app/variance?u=<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, EV/Best/Worst legend, and a solveplo.app/variance watermark. The PNG is self-contained — axes are baked into the image, not stripped HTML overlays. The chart subtitle summarises the run in plain text (e.g. "100k hands at +3 bb/100 with SD 140"); 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, end-state overlay, and time-per-stake bar.

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 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.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 9 games and all four of PPT's Classic range syntaxes alongside the modern Generic syntax.

Quickstart

  1. Open solveplo.app/pql.
  2. Pick an example from the ≡ Examples drawer (or start from the default AA-vs-random).
  3. Press ⌘ Enter / Ctrl+Enter to run.
  4. Tweak the trials input to sharpen the CI (higher = slower but tighter).
  5. 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. Default trials are per-game (~25k for the heaviest games like PLO6, ~50k for cheaper games); default max-seconds is 60.

Games Supported

Game keyMeaningHole cardsSplit
holdemTexas Hold’em2Hi
omahahiPot-Limit Omaha4Hi
omaha8Omaha Hi/Lo 8-or-better4Hi/Lo
omahahi55-card Omaha (Big-O)5Hi
omaha855-card Omaha Hi/Lo5Hi/Lo
omahahi66-card Omaha (PLO6)6Hi
shortdeckTriton-rule 6+ Hold’em (36-card deck, no 2–5). Aliases: 6plus, 6+, sd, short-deck. Routes through the Classic-Hold’em parser; flush ranks above full house in handType ordering.2Hi
studhi7-card Stud7 privateHi
stud87-card Stud Hi/Lo7 privateHi/Lo
razzRazz (A-5 lowball)7 privateLo 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

AggregatorInputOutput
avg(expr)numeric (equity, rating)arithmetic mean
count(expr)booleanfraction true + raw count
histogram(expr)enum, number, categoryfrequency table, sorted by poker rank when known
min(expr)numericsmallest value seen
max(expr)numericlargest 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

~80 functions are implemented (the playground'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 thirdseventh (stud games).

Equity / pot share:

FunctionDescription
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:

FunctionDescription
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:

FunctionDescription
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:

FunctionDescription
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 (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):

FunctionDescription
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:

FunctionDescription
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
boardHas{One,Two}DistinctLoCards()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:

FunctionDescription
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:

FunctionDescription
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: *.
  • 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: $s suited, $o offsuit, $ds double-suited, $ss single-suited, $ts triple-suited (PLO6).
  • Category macros: $B big (A-J), $M middle (T-7), $Z small (2-6), $L low (A-8), $N no-low (K-9), $F face, $R broadway, $W wheel.
  • Card-count top-off: hand patterns are padded to the game's hole count. AA in Omaha becomes AA**.

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-only AAKK, rank classes B 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'

Examples Drawer

Click ≡ Examples in the editor header to slide in the example library: an 8-query tour ordered simple → complex, each chosen to cover a distinct slice of what PQL can do. The current set:

  1. 3-way preflop comparison (Holdem) — AA vs KK vs AKs preflop.
  2. AKh on a two-tone, unpaired flopminFlopHandCategory + outsToHandType with a WHERE on board texture.
  3. Wheel draw vs random PLO8 — scoops, quartered lows, and fractional equity in a split-pot game.
  4. AAKK double-suited four-handed (PLO) — per-hand histogram + the handsHaving metafunction.
  5. Broadway PLO hands across opponent strengths — multi-query batch against random / top-15% / top-20%.
  6. Custom EV via CASE — a payoff table built with CASE WHEN … THEN … ELSE … END.
  7. PLO6 triple-suited broadway$ts macro and a 6-card range against villain's narrow opener.
  8. Stud-8 streaming sampler — ranges too wide to materialise; the engine streams per trial.

Click an example to load it into the editor (replacing whatever was there). The search box filters by title and description. Each example shows a one-line description before you commit.

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 URLsolveplo.app/pql?u=<8 chars>. POST to /api/pql/share with { query, trials, seed }; KV-backed.
  • Hash fallback?q=…&n=…&s=… when the API is unavailable. Works offline; capped around 1800 characters.

Loading either form re-runs the query and shows the original results. Tweak any input and the URL goes back to a clean /pql — the address bar doesn't drift while you edit.

PNG Export

Save PNG (in the gear sheet, disabled until you've run a query) generates a high-resolution image with two halves: an IDE-style syntax-highlighted query on top, and the formatted results (scalar values + CIs, histograms as multi-column tables) below. Dimensions are dynamic — tall queries or many-bucket histograms stretch the canvas accordingly. Title SolvePLO in white plus a gold subtitle ("PQL Engine"); footer solveplo.app/pql · seed={seed}. 2× device pixel ratio so it stays sharp on retina screens.

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. AKs in Generic means "A (any suit) + K of spades", not "AK suited". For "AK suited" in Generic, write AK:$s. For legacy AKs behaviour, add syntax='classic' to the FROM clause. Similarly, AKo in Generic parses as 3 slots (o is 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 for game='razz'. Other games default to generic.
  • Fractional equity is exact. fractionalRiverEquity(p) = 1/4 uses exact-rational compare (cross-multiply), not float coincidence. Integer-fraction literals like 1/4 or 13/914 are accepted directly.

Known Limits

  • PLO6 wildcard ranges. Full wildcards ('******') are too large to materialise in browser memory. Use a narrower shape like $B$B****, AAxxxx, or concrete cards.
  • 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 tables for shortdeck. Hold'em, PLO, PLO5/6, and Omaha-8 ship prebuilt percentile tables (with 3h and 6h variants where they make sense). Shortdeck does not — percent selectors like '15%' error in game='shortdeck' queries. Use specific hand patterns instead.
  • Multi-bracket cross-products. [A-Q][J-T] materialises each bracket independently, not as a Cartesian product. Single brackets work fine; nest deliberately.
  • No-pair brace nesting. Patterns like A{$W$W} (ace plus two distinct wheel cards) drop the constraint when nested. Use standalone braces.
  • nutHiOuts on 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.
  • 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. 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.

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. Share links upload only the query text and run parameters (only when you click Copy share link); we keep an anonymous "calculator was used" ping.

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.

SolvePlayersStackRakeBuckets (flop / turn / river)Turn textureRiver texturePostflop bet treeSpotsDecision nodesStrategy data
hu_100bb2 (HU)100bb0%200 / 100 / 100Large (8,942)Large (3,677)bet: 25% or pot · 2-bet: 50% or pot · 3-bet+: pot111,41424.0 GB
6max_12bb612bb5% (1bb cap)40 / 40 / 40Medium (958)Small (229)bet: 50% (also pot when BB plays) · raise: pot171,3300.27 GB
6max_20bb620bb5% (1bb cap)30 / 30 / 30Small (90)Small (229)flop bet 66% · turn/river bet pot · raise pot5730,5922.96 GB
6max_30bb630bb5% (1bb cap)30 / 30 / 30Small (90)Small (229)flop bet 66% · turn/river bet pot · raise pot5730,4342.93 GB
6max_40bb640bb5% (1bb cap)30 / 30 / 30Small (90)Small (229)flop bet 66% · turn/river bet pot · raise pot5745,4906.02 GB
6max_50bb650bb5% (1bb cap)30 / 30 / 30Small (90)Small (229)flop bet 66% · turn/river bet pot · raise pot5747,0544.55 GB
6max_100bb6100bb5% (1bb cap)30 / 30 / 30Small (90)Small (229)flop bet 66% · turn/river bet pot · raise pot57258,99922.6 GB
6max_150bb6150bb5% (1bb cap)30 / 30 / 30Small (90)Small (229)flop bet 66% · turn/river bet pot · raise pot57309,01726.8 GB
6max_200bb6200bb5% (1bb cap)30 / 30 / 30Small (90)Small (229)flop bet 66% · turn/river bet pot · raise pot57416,01333.5 GB

Card Colors

SolvePLO uses a 4-color deck throughout the tool:

SuitColor
Spades ♠White
Hearts ♥Red
Diamonds ♦Blue
Clubs ♣Green

Action Colors

Actions are color-coded consistently:

ActionColor
Check / CallGreen
FoldBlue
Bet / Raise (small)Light red
Bet / Raise (medium)Medium red
Bet / Raise (large)Dark red
All-InDeep red

When multiple bet/raise sizes are available, they are tiered from lightest (smallest sizing) to darkest (largest sizing).

Chip Denominations

ChipColor
$100Black
$25Green
$5Red
$1White
$0.25Pink

Chip stacks break down values using a largest-first greedy algorithm, so the visual representation always uses the fewest chips possible.