Protocol v2 strategy_grid Migration
This note describes the NLHE strategy_grid response shape differences between
the legacy v1 rangeviewer pipeline and the protocol v2 universal-poker pipeline.
It intentionally does not cover PLO.
Summary
The main response-shape change is that v1 exposes the 169 range-matrix entries
as top-level strategy[] items keyed by combo, while v2 exposes them as
top-level hands[] items keyed by handName.
Concrete two-card combos still exist in v2, but they are nested under each hand
entry in hand.combos[].
v1 Shape
In v1, each top-level strategy entry is a 169-matrix cell such as AA, AKs,
or AKo.
{
"strategy": [
{
"combo": "AKs",
"strategy": [0.1, 0.2],
"reach": 1.0,
"ev": 0.4,
"detail": [
{
"combo": "AsKs",
"reach": 1.0,
"detail": [["fold", "10.0%", "-1.00"]]
}
],
"detail_agg": [["fold", "10.0%", "-1.00"]]
}
]
}v2 Shape
In v2, each top-level hand entry is the same 169-matrix cell, but the field is
named handName. Concrete two-card combos are available under combos[].
{
"hands": [
{
"handName": "AKs",
"strategy": [0.1, 0.2],
"reach": 1.0,
"ev": 0.4,
"actions": [
{ "actionName": "fold", "probability": 0.1, "ev": -1.0 }
],
"combos": [
{
"combo": "AsKs",
"reach": 1.0,
"actions": [
{ "actionName": "fold", "probability": 0.1, "ev": -1.0 }
]
}
]
}
]
}Field Mapping
| v1 | v2 | Notes |
|---|---|---|
response.strategy | response.hands | Top-level 169 hand matrix entries. |
entry.combo | hand.handName | Matrix-cell label, for example AA, AKs, AKo. |
entry.detail | hand.combos | Concrete two-card combo breakdown. |
detailItem.combo | combo.combo | Concrete combo, for example AsKs. |
detailItem.reach | combo.reach | Per-concrete-combo reach. |
detailItem.detail | combo.actions | Per-combo action frequencies and EVs. |
entry.detail_agg | hand.actions | Aggregated actions for the matrix cell. |
["fold", "10.0%", "-1.00"] | { "actionName": "fold", "probability": 0.1, "ev": -1.0 } | v2 uses structured numeric fields. |
strategy_compute_time | computeTimeMs | v2 uses camel-case timing metadata. |
state | stateRender | v2 keeps rendered state as debug metadata. |
v2-Only Fields
These fields exist on the NLHE v2 strategy_grid response and were not part of
the legacy v1 strategy_grid contract.
Top-Level Fields
| Field | Type | Meaning |
|---|---|---|
hands | array | The top-level 169 hand-matrix entries. This replaces v1 strategy. |
overallStrategy | number[] | One 11-slot strategy vector for the whole emitted range. By default this is a uniform average across top-level hand entries. |
overallReach | number | Average reach across emitted concrete combos. |
gridWeightType | "uniform" or "reach" | Tells clients how aggregated hand actions and overall strategy were weighted. Defaults to uniform; can be requested as reach. |
currentPlayer | number | Current acting seat in client seat coordinates, or -1 when no player is acting. |
numBoardCards | number | Count of board cards currently present in the request/state: 0, 3, 4, or 5. |
numCombos | number | Number of concrete NLHE two-card combos considered by the grid builder, normally 1326 before board-card filtering. |
pendingDeal | string | "" for a normal response, or "flop", "turn", or "river" when the state needs more board cards before strategy can be computed. |
gameFinished | boolean | Present on terminal v2 responses. true for fold-out/showdown-complete, false for all-in spots that still need board cards. |
tableState | object | Structured table snapshot for the UI: street, pot, board, players, acting player, and BB normalizer. |
actionLabels | array | Legal action metadata for the current decision, keyed by model slot. |
actionHistory | array | Structured action/deal history with positions and pot/spend context. |
stateRender | string | Debug render of the universal-poker state. This replaces the old v1 state field name. |
obsDebug | object | Debug-only observation metadata: action mask, action sizes, game tags, raw board cards, round, and observation dimension. |
modelMd5 | string | MD5/hash metadata for the selected model, added by the serving wrapper. |
inferenceTimeMs | number | Time spent in model inference, separate from total computeTimeMs. |
supportScore | number | Optional model confidence score from newer model outputs. Present only when the model emits it. |
strategyProfileConfig | object | v2 camel-case strategy profile debug info. v1 used strategy_profile_config when that path populated profile metadata. |
tableState
tableState is new structured state for UI consumers. Important fields:
| Field | Meaning |
|---|---|
street | "preflop", "flop", "turn", or "river". |
pot | Current pot in chips. |
chipsPerBb | Big-blind chip value used for BB conversion. |
board | Array of dealt board-card strings. |
actingPlayer | Acting client seat, or -1. |
players | Per-seat state array. |
actingContext | BB-normalized pot/call/spend context for the current actor. |
icmEquityBb | MTT equity values when present. Empty for non-MTT cash spots. |
squidEquityBb | Squid equity values when present. Empty for non-squid spots. |
bountyBb | Bounty values when present. Empty for non-bounty spots. |
Each tableState.players[] entry includes:
{
"position": "BB",
"stack": 9900,
"startStack": 10000,
"committed": 100,
"folded": false,
"allIn": false
}tableState.actingContext includes:
{
"streetStartPotBb": 1.5,
"potBeforeBb": 2.5,
"callFacingBb": 1.0,
"streetSpentBb": 1.0
}actionLabels
actionLabels[] is new action metadata for matching model slots to UI actions. This is used by top-row frontend rangeviewer v2 to display aggregated actions for current game state.
Each entry can include:
| Field | Meaning |
|---|---|
index | Dense UI index among legal actions. |
slot | Original 0-10 model action slot. |
name | Display label, for example fold, call 2.0 BB, raise 7.5 BB. |
rawAction | Canonical action value used by the UI; -1 fold, 0 check/call, positive raise-to/all-in chips. |
amount | Strategy-act-style action amount in chips, or null for fold/check. |
amountBb | Action amount in BB. |
raiseByBb | Raise-by amount in BB. |
strategy | Optional overall strategy probability for this slot, attached from overallStrategy. |
actionHistory
actionHistory[] is new structured history for rendering and replay. Betting
entries include position and pot context; deal entries use isDeal: true.
Common fields:
| Field | Meaning |
|---|---|
isDeal | true for board-card deal entries, false for player actions. |
playerSeat | Acting client seat, or -1 for deals. |
position | Display position such as SB, BB, UTG, BTN; empty for deals. |
label | Human-readable action/deal label. |
rawAction | Same action value space as actionLabels[].rawAction. |
amountBb | Action/deal amount in BB. |
raiseByBb | Raise-by amount in BB. |
potBeforeBb | Pot before the action/deal. |
potAfterBb | Pot after the action/deal. |
callFacingBb | Call amount facing the actor before the action. |
streetSpentBb | Actor's current-street commitment after the action. |
totalSpentBb | Actor's total commitment after the action. Present for player actions. |
hands[] Additions
The hands[] entries are the replacement for v1 strategy[], but v2 also adds
fields that are easier to consume directly:
| Field | Meaning |
|---|---|
handName | Matrix-cell name such as AA, AKs, or AKo. |
actions | Aggregated structured actions for the hand. This replaces formatted detail_agg rows. |
combos | Structured concrete two-card combos nested under the hand. This replaces v1 detail. |
Each hand.actions[] and combo.actions[] entry uses:
{
"actionName": "raise7.5",
"probability": 0.42,
"ev": 1.25
}Compared with v1, actionName, probability, and ev are object fields rather
than positional array values. probability is a numeric fraction, not a
formatted string percentage.
When NLHE v2 request-profile post-processing is enabled, each combo.actions[]
entry may also include:
| Field | Meaning |
|---|---|
ev_raw | Original raw model EV before fold-normalization. |
ev_rank | Optional display rank derived from EV and probability when show_labels is enabled. |
action_quality | Optional action quality label when show_labels is enabled. |
PLO-only fields such as ploBucketTree, ploBucketMap, ploStrategy,
ploReach, taxonomyOnly, sampledMode, and holeCardCount are intentionally
excluded from this NLHE migration note and will be documented separately.
Migration Notes
Client code that iterates range-matrix entries should switch from:
for (const entry of response.strategy) {
const hand = entry.combo;
}to:
for (const hand of response.hands) {
const handName = hand.handName;
}Client code that opens a matrix cell and reads the concrete combo breakdown should switch from:
const rows = entry.detail.map((item) => ({
combo: item.combo,
reach: item.reach,
actions: item.detail,
}));to:
const rows = hand.combos.map((combo) => ({
combo: combo.combo,
reach: combo.reach,
actions: combo.actions,
}));v2 probabilities are numeric fractions, not formatted percentage strings. Convert only at display boundaries:
const pctText = `${(action.probability * 100).toFixed(1)}%`;
const evText = action.ev.toFixed(2);Request Migration
To use the v2 holdem strategy_grid path, send a strategy-act-style hand
request with:
{
"protocol_ver": "v2",
"hand": {
"game_type": "nlhe",
"players": [],
"actions": { "entries": [] }
}
}For rangeviewer requests, leave player hole_cards empty. The backend iterates
all NLHE two-card combos.
The /strategy_grid route selects the v2 holdem pipeline when
protocol_ver starts with v2.
Behavioral Notes
v2 may emit all 169 top-level hands cells, even when a cell has no live
concrete combos after board-card blocking. If old code expects only populated
cells, filter with:
const liveHands = response.hands.filter((hand) => hand.combos.length > 0);The default v2 overall strategy is available as overallStrategy. The response
also includes gridWeightType, which is uniform by default or reach when
requested through grid_weight_type: "reach" or gridWeightType: "reach".