PLO Tag Query & Browsing Contract
How to query the PLO range/tag browser and what comes back. Applies to
plo4, plo5, and plo6 hand requests (examples use plo4). The
controls described here are additive: they never mutate the underlying
canonical bucket enumeration, they only filter, sort, page, and annotate
it.
The request carries two optional objects:
| Object | Purpose |
|---|---|
ploTagOptions (alias plo_tag_options) | Response shaping: schema, per-combo tag/facet payloads, paging, sorting, zero-row skipping, profiling, strategy toggle. |
plo_tag_query | Filtering + query semantics: require / exclude tags, comboContains, rankPattern, groupBy, plus its own paging/sort/skip that can override ploTagOptions. |
Both are optional. Sending neither returns the default first page of the
canonical bucket map (with strategy — see the note below). Sending
plo_tag_query (even empty) switches the response into "query mode" and
adds a ploTagQueryResult block.
Every field accepts both camelCase and snake_case. Where a field is
read from both objects, the precedence rules in
Field precedence apply.
Strategy runs by default. The PLO grid pipeline runs model inference unless you turn it off. A bare browse request therefore returns
ploStrategyand needs a configured model (otherwise it errors with"V2 model not configured"). To browse tags/buckets without inference, setgetStrategy: false(orploGetStrategy: false) — see Strategy inference toggle.
Quick reference
plo_tag_query
| Field | Type | Default | Meaning |
|---|---|---|---|
require | string or string[] | [] | Tag names/aliases the hand must have (AND). |
exclude | string or string[] | [] | Tag names/aliases the hand must not have. |
comboContains (combo_contains) | string or string[] | [] | Suit-specific card filter (AND). See comboContains. |
rankPattern (rank_pattern) | string | unset | Contained rank-multiset filter (1–4 ranks, any suit/order). See rankPattern. |
groupBy (group_by) | string or string[] | [] | Grouping key(s); echoed back (grouping itself is reserved). |
normalize | string | "conditional" | Echoed normalization mode. |
pageOffset (page_offset) | int | 0 | Page start. |
pageSize (page_size) | int or "all" | 200 | Page size; integer capped at 5000; "all" returns the full matched set (no cap). |
sortField (sort_field) | string | "" | Sort key. See Sorting. |
sortActionSlot (sort_action_slot) | int | -1 | Action column for pct/ev (and the legacy skip criterion). |
sortDir (sort_dir) | string | "desc" | "desc" or "asc". |
skipZeroPages (skip_zero_pages) | bool | false | Drop zero/unavailable rows before paging. See Skipping zero rows. |
skipZeroSlot (skip_zero_slot) | int | -1 | Sort-independent skip criterion: drop rows with zero prob on this action slot. |
skipZeroCollapsedRaise (skip_zero_collapsed_raise) | bool | false | Sort-independent skip criterion: drop rows with zero collapsed raise mass. |
max_groups | int | 0 | Reserved (grouping cap). |
max_examples_per_group | int | 0 | Reserved (grouping cap). |
ploTagOptions
| Field | Type | Default | Meaning |
|---|---|---|---|
includeSchema (include_schema) | bool | false | Add ploTagSchema (the tag dictionary for this variant/street). |
includePerComboTags (include_per_combo_tags) | bool | false | Add ploTagMap (tag-id list per combo). |
includePerComboFacets (include_per_combo_facets) | bool | false | Add ploFacets (named facet values per combo). |
getStrategy / includeStrategy (get_strategy / include_strategy) | bool | true | Run model inference. Off ⇒ pure taxonomy/tag browse, no ploStrategy. |
profile | bool | false | Add ploProfile timing/count block. |
pageOffset (page_offset) | int | 0 | Page start. |
pageSize (page_size) | int or "all" | 200 | Page size; integer capped at 5000; "all" = full matched set (no cap). |
aggregateAll (aggregate_all) | bool | false | Compute exact aggregate over the full matched set, then strip per-combo payload. |
sortField (sort_field) | string | "" | Same values as plo_tag_query.sortField. |
sortActionSlot (sort_action_slot) | int | -1 | Action column for pct/ev. |
sortDir (sort_dir) | string | "desc" | "desc" or "asc". |
skipZeroPages (skip_zero_pages) | bool | false | Drop zero/unavailable rows before paging. See Skipping zero rows. |
skipZeroSlot (skip_zero_slot) | int | -1 | Sort-independent skip criterion (single action slot). |
skipZeroCollapsedRaise (skip_zero_collapsed_raise) | bool | false | Sort-independent skip criterion (collapsed raise mass). |
comboContains,rankPattern,require,exclude, andgroupBylive only inplo_tag_query.ploTagOptionsdoes not carry them. The paging, sort, and skip-zero fields live in both.
Strategy inference toggle
Tag/bucket browsing is logically separable from inference, but the server
runs strategy by default. Strategy outputs (ploStrategy, ploReach,
actionLabels, and the pct/ev/raise/raiseEv sorts and
skipZeroPages filtering) all depend on inference.
Turn inference off (pure browse — no ploStrategy, no model required)
with any one of:
- top level:
ploGetStrategy: false/plo_get_strategy/ploRunStrategy/plo_run_strategy - inside
ploTagOptions:getStrategy: false/get_strategy/includeStrategy/include_strategy
Leaving all of them unset ⇒ inference on. The top-level toggles win over
the ploTagOptions toggles.
comboContains
comboContains filters the range down to hands that physically contain
specific, suit-qualified cards. It is the right tool for "show me every
hand that has the Ace of hearts" or "show me hands with both the Ah and the
Kh". For a contained set of ranks in any suit (e.g. "any A-K-8-3 hand",
"any queen pair") use rankPattern; broad class searches
(e.g. "any pair", "double suited") belong in require / exclude.
Value format
Each entry is a string of one or more concatenated 2-character cards:
- Rank (first char):
2 3 4 5 6 7 8 9 T J Q K A, case-insensitive. - Suit (second char):
c d h s, case-insensitive. - Normalized internally to uppercase rank + lowercase suit (
ah->Ah).
"comboContains": ["Ah"] // single card
"comboContains": ["AhKh"] // compact multi-card string
"comboContains": ["Ah", "Kh"] // array form, same as "AhKh"
"comboContains": ["AhKsQdJc"] // a fully specified 4-card hand (plo4)A value may be a single string or an array of strings. Multiple cards (whether in one compact string or spread across the array) are combined and de-duplicated, then matched with AND semantics: a hand must contain every requested card.
What it returns
Unlike normal browsing (which returns canonical, suit-isomorphic
representatives with a multiplicity), comboContains returns the actual
physical combos that contain the requested cards. Every returned row
therefore has multiplicity = 1, and:
totalMatchingHandCount == totalMatchingComboCountEach combo is still mapped to its bucket in ploBucketMap, so bucketing,
tags, facets, sorting, and strategy all work on the physical rows.
Exact, deterministic match counts (plo4 preflop, empty board):
| Query | Matched hands | Why |
|---|---|---|
["Ah"] | 20825 | choose the other 3 cards from 51: C(51,3) |
["AhKh"] | 1225 | choose the other 2 from 50: C(50,2) |
["AhKsQdJc"] | 1 | the hand is fully specified |
Postflop, the board cards are removed from the deck first, so the counts shrink accordingly.
Validation (fails closed)
- A value must have even length and every 2-char chunk must be a valid
card. Anything else (e.g.
"AK83","ZZ","Ah5") marks that value invalid. - If any value is invalid, the whole query fails closed:
0matches, with:
"ploTagQueryResult": {
"status": "invalid_combo_contains",
"warning": "one_or_more_combo_contains_cards_are_invalid",
"comboContains": ["Ah", "ZZ"]
}- Requesting more distinct cards than the hand holds (e.g. 5 cards for
plo4) yields0matches withstatus: "ok". - Requesting a card that is on the board yields
0matches (you cannot hold a board card).
Composing with tags and sort
comboContains composes with require / exclude (the tag filter is
applied to each physical combo) and with any sortField:
"plo_tag_query": {
"comboContains": ["Ah"],
"require": ["double_suited"],
"exclude": ["pair"],
"sortField": "raise",
"sortDir": "desc",
"pageOffset": 0,
"pageSize": 50
}rankPattern
rankPattern filters the range down to hands whose ranks contain a
given rank multiset, ignoring suits and order. It is the rank-only
complement to comboContains: use comboContains when
you care about specific suited cards (Ah), and rankPattern when you care
only about ranks the hand must hold ("every hand with a queen pair", "every
A-K-8-3 hand").
This is a contained (subset) match, not exact equality, and the pattern may be shorter than the hand (1–4 ranks). This changed from the older "exactly
holeCardCountranks, exact multiset" contract. Forplo4a full 4-rank pattern still behaves like the old exact filter (a 4-rank subset of a 4-card hand is the whole hand), so existing plo4 callers using full patterns are unaffected.
Value format
A single string of 1 to min(holeCardCount, 4) rank characters
(so 1–4 ranks for plo4; still capped at 4 ranks for plo5/plo6), no
suits:
- Rank:
2 3 4 5 6 7 8 9 T J Q K A, case-insensitive. - Order does not matter; duplicate ranks are allowed (and meaningful).
- Echoed back uppercased, in the order sent (not reordered).
"rankPattern": "QQ" // any hand holding at least a pair of queens
"rankPattern": "AK" // any hand holding at least one ace and one king
"rankPattern": "AK83" // plo4: hands whose ranks are exactly {A,K,8,3}
"rankPattern": "3k8a" // same as "AK83" (case- and order-insensitive)
"rankPattern": "AAK3" // hands holding {A,A,K,3} — two aces, a K and a 3Unlike
comboContains,rankPatternis a single string (not an array) and carries no suits.comboContains: "AK83"is still invalid exact-card input and fails closed asinvalid_combo_contains— the two filters do not overlap.
Semantics
The hand's hole-card ranks are sorted and tested for multiset inclusion
of the sorted pattern: the hand matches iff its ranks contain every rank
in the pattern, counting duplicates. Because duplicate ranks are
significant, QQ matches only hands holding two or more queens, and
AAK3 matches only hands holding two or more aces plus a K and a 3.
For a plo4 hand (4 ranks):
- a 1–3-rank pattern is a genuine contains query (the remaining hole cards
are unconstrained), e.g.
QQ= "holds a queen pair, plus anything"; - a 4-rank pattern fills the whole hand, so it collapses to exact equality,
e.g.
AK83= "the four ranks are exactly A, K, 8, 3".
For plo5/plo6, even a 4-rank pattern leaves the extra hole card(s) free,
so it stays a partial (contains) match.
What it returns
On its own, rankPattern browses like the default: it returns canonical,
suit-isomorphic representatives with their multiplicity, so
totalMatchingComboCount is the raw-combo total and ploMultiplicity values
can be > 1. Combined with comboContains, it returns the physical combos
that contain the requested cards (multiplicity = 1), exactly as
comboContains does on its own.
Exact, deterministic raw-combo counts (plo4 preflop, empty board):
| Query | totalMatchingComboCount | Why |
|---|---|---|
rankPattern: "AK83" | 256 | 4 distinct ranks × 4 suits each: 4^4 |
rankPattern: "AAK3" | 96 | aces C(4,2)=6, K 4, 3 4: 6·4·4 |
rankPattern: "AA" | 6961 | hands with ≥2 aces: C(4,2)·C(48,2)+C(4,3)·48+1 |
rankPattern: "AK83" + comboContains: ["Ah"] | 64 | Ah fixed, other 3 ranks × 4 suits: 4^3 |
Postflop the board cards are removed from the deck first, so counts shrink
accordingly (a rank fully consumed by the board can drop the match to 0).
Validation (fails closed)
The pattern must be 1 to min(holeCardCount, 4) characters and every
character a valid rank. Anything else — empty string, too long (e.g.
"AK835" for plo4, or any 5+-rank pattern), or an invalid rank character
("AK8Z") — fails closed: 0 matches, with:
"ploTagQueryResult": {
"status": "invalid_rank_pattern",
"warning": "rank_pattern_is_invalid",
"rankPattern": "AK8Z"
}If both comboContains and rankPattern are invalid in the same request,
invalid_combo_contains takes precedence.
Composing with comboContains, tags, and sort
rankPattern ANDs with comboContains, require / exclude, and any
sortField:
"plo_tag_query": {
"rankPattern": "AK83",
"comboContains": ["Ah"],
"sortField": "raise",
"sortDir": "desc"
}This returns the A-K-8-3 hands that hold the Ah, ordered by collapsed
bet/raise probability.
Tag filtering: require / exclude
require and exclude take tag names or aliases and combine as
"has all of require AND none of exclude". The available vocabulary is
data-driven and depends on the variant and street, so treat the schema
as the source of truth rather than hard-coding names:
- Set
ploTagOptions.includeSchema: trueto getploTagSchema, an array of{ id, name, plane, domain, aliases }entries valid for the currentholeCardCountand board street. - Use a
nameor any of itsaliasesinrequire/exclude.
If a require tag cannot be resolved the query fails closed
(status: "unresolved_required_tag"); an unresolved exclude tag is
ignored with status: "ok_with_unresolved_exclude". Unresolved aliases are
listed under ploTagUnresolved (with ploTagWarning: "unresolved_tag_alias").
Sorting
Set sortField (and sortDir, default "desc"). Pages are slices of one
global order, so paging through a sort is stable.
sortField | Needs strategy? | sortActionSlot? | Ranks by |
|---|---|---|---|
"combos" | no | no | suit-isomorphic multiplicity |
"pct" | yes | required | probability of the chosen action slot |
"ev" | yes | required | EV of the chosen action slot |
"raise" / "betRaise" / "bet_raise" / "betraise" | yes | no | collapsed bet/raise = sum(strategy[2..10]) |
"raiseEv" / "raise_ev" | yes | no | sum(strategy[s] * evs[s]) / sum(strategy[s]), s=2..10 |
Action slots: 0 = fold, 1 = check/call, 2..10 = all-in + bet/raise sizes.
"combos"is resolved before inference from multiplicity, so it is applied directly (ploPaging.sort.applied: true)."pct"/"ev"/"raise"/"raiseEv"depend on model output, so the full matched set is ranked host-side by that output and sliced to the requested page. In the returned response the finalploPaging.sort.appliedistrueandploPageOrderreflects the order. Combos with no inference output always sort to the end regardless of direction.- For
"ev", if the model emits no usable or non-identical EVs the sort is not applied andsort.applied: falsewithsort.reason: "ev_unavailable"(the matched set is still paged, not returned whole). - An unsupported
sortFieldkeeps normal paging and setssort.applied: falsewithsort.warning: "unsupported_sort_field". - If a deployment disables host-side inference sorting, the requested page
is returned unsorted with
sort.applied: falseandsort.reason: "server_side_sort_disabled".
An inference sort (pct/ev/raise/raiseEv) with strategy turned off,
or a pct/ev sort without a valid sortActionSlot, is a hard error —
see Errors.
Skipping zero rows
skipZeroPages: true drops rows the hand never plays before paging, so a
page is not padded with zero-probability combos. It requires strategy
inference. The rows it drops are chosen by a criterion, resolved in this
order:
skipZeroCollapsedRaise: true— drop combos whose collapsed raise masssum(strategy[2..10])is zero. Sort-independent (works with any sort, including"combos", and needs nosortActionSlot). Wins if both explicit criteria are set.skipZeroSlot: <0..10>— drop combos with zero probability on that single action slot. Also sort-independent.- Legacy fallback (neither explicit criterion supplied):
raise/raiseEv/ empty sort fields fall back to collapsed raise mass; other fields fall back tosortActionSlot.
If none of these resolves to a criterion, skipZeroPages: true is a hard
error (see Errors).
Interaction with the sort field:
- For
pct/raise, it drops zero/unavailable rows before paging. - For
raiseEv, it drops rows with no aggregate raise weight but keeps finite aggregate EV values of0.0. - For
ev, the sort itself never treats0.0as unavailable (0 is a valid EV), but an explicitskipZeroSlot/skipZeroCollapsedRaisestill filters by action probability.
When skipZeroPages prunes rows, the response's totalMatchingHandCount /
totalMatchingComboCount (and the ploTagQueryResult matched* counts)
are recomputed over the surviving set.
Paging
pageOffset/pageSizepage the matched set. IntegerpageSizeis capped at5000(default200).pageSize: "all"returns the entire matched set with per-combo data (no cap). The full unfiltered PLO set is large, so this is an explicit opt-in.aggregateAll: trueenumerates the full matched set for an exact aggregate and then strips the per-combo payload, so only the rollup (ploOverallAggregate, bucket-tree aggregates,aggregateAll: true) is returned.- Response paging echoes
pageOffset,pageSize,pageHandCount,totalMatchingHandCount,totalMatchingComboCount,hasNextPage,nextOffset.
Field precedence
When both objects are present:
plo_tag_querysuppliesrequire,exclude,groupBy,comboContains,rankPattern, and (always) itspageOffset/pageSize.skipZeroPages,skipZeroSlot, andskipZeroCollapsedRaisefromplo_tag_queryeach win only if that field is present, otherwise theploTagOptionsvalue survives.- A sort from
plo_tag_querywins only ifsortFieldis non-empty, so aploTagOptions-level sort survives a filter-onlyplo_tag_query. (As a special case, aplo_tag_querythat carriesskipZeroPagesandsortActionSlotbut nosortFieldstill adopts thatsortActionSlot.)
Errors
Some misconfigured requests return a plain error object
({ "success": false, "error": "..." }) instead of a taxonomy/query
response:
| Condition | Error message (substring) |
|---|---|
Inference sort (pct/ev/raise/raiseEv) requested with strategy off | sortField "<f>" requires strategy inference; set ploTagOptions.getStrategy=true |
pct / ev sort without a valid sortActionSlot in [0,10] | sortField "<f>" requires sortActionSlot in [0, 10] |
skipZeroPages requested with strategy off | skipZeroPages requires strategy inference; set ploTagOptions.getStrategy=true |
skipZeroPages with no resolvable criterion | skipZeroPages requires skipZeroSlot in [0, 10], skipZeroCollapsedRaise, or sortActionSlot |
| Strategy on but no model configured for the spot | V2 model not configured (or the selector's reason) |
Filter-input problems (invalid_combo_contains, invalid_rank_pattern,
unresolved_required_tag) are not errors — they fail closed to 0
matches inside ploTagQueryResult.status (see the relevant sections).
Response
Always present (taxonomy)
| Field | Meaning |
|---|---|
taxonomyOnly | true. |
holeCardCount | 4, 5, or 6. |
numBoardCards | board size (0 preflop). |
sampledMode | false (results are exact). |
ploBucketTree | 3-level taxonomy tree (id, label, children). |
ploBucketMap | { comboString: bucketKey } for the page. Combo strings are concatenated cards, e.g. "AhKdQcJs". |
ploMultiplicity | { comboString: suitIsomorphicCount } (always 1 for comboContains). |
ploBucketCounts | { bucketKey: matchedCount } over the full matched set. |
ploPaging | paging block + sort echo (see below). |
ploPageOrder | array of combo strings in global sort order (present when a sort is applied). |
fullTotalCount, totalCanonicalHandCount, totalRawComboCount | enumeration totals. |
model, modelMd5 | resolved model + its md5 (present when strategy ran). |
computeTimeMs, stageTimingsMs | request timing / per-stage breakdown (see Timing). |
ploPaging.sort echoes the request and the resolution state. field and
dir are always present; applied says whether the page already reflects
the global order; actionSlot appears for pct/ev; skipZeroPages,
skipZeroSlot, skipZeroCollapsedRaise are echoed when honored;
warning/reason appear on the unsupported / ev-degenerate /
sort-disabled paths. While a sort is still deferred (applied: false) the
object also carries requestedPageOffset / requestedPageSize; those are
removed once the page has been sliced (applied: true).
"sort": {
"field": "raise",
"dir": "desc",
"applied": true
}When plo_tag_query is present: ploTagQueryResult
"ploTagQueryResult": {
"schemaVersion": 1,
"sampledMode": false,
"estimated": false,
"available": true,
"status": "ok",
"normalization": "conditional",
"require": ["double_suited"],
"exclude": ["pair"],
"groupBy": [],
"comboContains": ["Ah"],
"matchedHandCount": 8479,
"matchedComboCount": 8479,
"matchedWeight": 8479,
"paging": { "...": "mirror of ploPaging" },
"groups": []
}status values: ok, invalid_combo_contains, invalid_rank_pattern,
unresolved_required_tag, ok_with_unresolved_exclude,
tag_data_unavailable. rankPattern (uppercased) is echoed only when a rank
pattern was requested; skipZeroPages: true is echoed when honored.
(groups is reserved and returned empty by this path.)
When tag payloads are requested
includeSchema->ploTagSchema:[{ id, name, plane, domain, aliases }].includePerComboTags->ploTagMap:{ comboString: [tagId, ...] }(resolve ids viaploTagSchema).includePerComboFacets->ploFacets:{ comboString: { facetName: value } }.ploTagAvailable,ploTagSchemaVersion, and on alias problemsploTagWarning+ploTagUnresolved.
When strategy inference is on
| Field | Meaning |
|---|---|
ploStrategy | per combo: strategy[11], evs[11], ev, raise, betRaise, and reach (if a reach query ran). |
ploOverallAggregate | multiplicity-weighted fold/call/raise + EV rollup. |
ploBucketTree[].aggregate | the same rollup attached per tree node. |
ploReach | { comboString: reachProbability } (when reach is computed). |
actionLabels, actionHistory, tableState, supportScore | grid/UI metadata. |
A single ploStrategy entry:
"AhKhQdJd": {
"strategy": [0.12, 0.35, 0.03, 0.10, 0.18, 0.08, 0.14, 0.0, 0.0, 0.0, 0.0],
"raise": 0.53,
"betRaise": 0.53,
"ev": 1.24,
"evs": [0.0, 1.1, 1.2, 1.3, 1.4, 1.2, 1.1, 0.0, 0.0, 0.0, 0.0]
}If show_labels: true, each ploStrategy entry also gets ev_ranks[11]
and action_qualities[11] alongside strategy[11] / evs[11]. Setting
reduced_actions_tier adds a per-bucket collapsed-action view.
Reach query: reach_query → reachResult
Send a reach_query object to get a bucketed reach rollup (which hands
reach this node) in addition to per-combo reach:
reach_query field | Type | Default | Meaning |
|---|---|---|---|
target_seat_no | int | -1 | Seat whose range to compute (-1 = actor). |
normalize | string | "conditional" | "conditional" or "absolute". |
group_by | string | "bucket" | "bucket" or "combo". |
max_buckets | int | 0 | Cap on buckets returned (0 = all). |
max_examples_per_bucket | int | 0 | Cap on examples per bucket (0 = all). |
The response adds reachResult (bucketed/normalized/truncated reach). A
reach query computes reach over the full matched set.
Pending deal
If the request needs board cards that were not supplied, the pipeline
returns early with pendingDeal set to "flop" / "turn" / "river"
(plus tableState / actionHistory context) instead of a combo page, so
the frontend can prompt for the missing board.
Timing and profiling
computeTimeMs— total request time (ms).stageTimingsMs—{ state_enumerate_ms, obs_build_ms, inference_ms, reach_ms, reach_scope, emit_scope, aggregate_ms, num_combos }(cacheHitis added when the full-set result cache is engaged).reach_scopeis"full"/"page"/"sampled"/"skipped";emit_scopeis"full"/"page".profile: true(inploTagOptions) addsploProfilewithfilterPageMs,bucketCount,enumeratedCanonicalHands,matchedCanonicalHands,returnedPageHands.
Worked examples
1. Browse the default first page, no inference
{
"protocol_ver": "v2.1",
"hand": { "game_type": "plo4", "...": "..." },
"ploTagOptions": { "getStrategy": false, "pageOffset": 0, "pageSize": 50 }
}Returns the first 50 canonical buckets/combos with ploMultiplicity and no
ploStrategy. (Omitting getStrategy: false runs the model and adds
ploStrategy.)
2. comboContains: every hand with the Ah
{
"protocol_ver": "v2.1",
"hand": { "game_type": "plo4", "...": "..." },
"ploTagOptions": { "pageOffset": 0, "pageSize": 100 },
"plo_tag_query": { "comboContains": ["Ah"] }
}ploTagQueryResult.matchedHandCount == 20825; every key in ploBucketMap
contains Ah; all ploMultiplicity values are 1.
3. comboContains + tags + collapsed raise sort (with inference)
{
"protocol_ver": "v2.1",
"show_labels": true,
"hand": { "game_type": "plo4", "...": "..." },
"ploTagOptions": {
"getStrategy": true,
"pageOffset": 0,
"pageSize": 50,
"sortField": "raise",
"sortDir": "desc",
"includePerComboTags": true
},
"plo_tag_query": {
"require": ["double_suited"],
"exclude": ["pair"],
"comboContains": ["Ah"],
"sortField": "raise",
"sortDir": "desc"
}
}Returns double-suited, unpaired hands that contain Ah, ordered by total
bet/raise probability; ploPageOrder holds the order and each ploStrategy
row carries raise/betRaise.
4. pct sort on one size, skipping zero rows
{
"protocol_ver": "v2.1",
"hand": { "game_type": "plo4", "...": "..." },
"ploTagOptions": {
"getStrategy": true,
"pageOffset": 0,
"pageSize": 50,
"sortField": "pct",
"sortActionSlot": 4,
"sortDir": "desc",
"skipZeroPages": true
}
}Ranks combos by the probability of action slot 4 (a bet/raise size). Combos
with no probability there sort to the end, and skipZeroPages (legacy
fallback → sortActionSlot) removes those zero rows before paging.
5. Skip zero rows independent of the sort
{
"protocol_ver": "v2.1",
"hand": { "game_type": "plo4", "...": "..." },
"ploTagOptions": {
"getStrategy": true,
"sortField": "combos",
"skipZeroPages": true,
"skipZeroCollapsedRaise": true,
"pageSize": 100
}
}Sorts by suit-isomorphic multiplicity (sort.applied: true) but still
hides every combo whose collapsed raise mass sum(strategy[2..10]) is zero.
skipZeroCollapsedRaise (and skipZeroSlot) work with any sort and need no
sortActionSlot.
6. rankPattern: any queen pair, and every A-K-8-3 hand
{
"protocol_ver": "v2.1",
"hand": { "game_type": "plo4", "...": "..." },
"plo_tag_query": { "rankPattern": "QQ", "pageSize": "all" }
}ploTagQueryResult.status == "ok", rankPattern == "QQ", and every key in
ploBucketMap holds at least two queens. Using "AK83" instead returns the
exact-{A,K,8,3} hands (totalMatchingComboCount == 256); adding
"comboContains": ["Ah"] narrows it to the 64 physical combos that hold
the ace of hearts (each with multiplicity = 1).