Portal
Sign In Console

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

v1v2Notes
response.strategyresponse.handsTop-level 169 hand matrix entries.
entry.combohand.handNameMatrix-cell label, for example AA, AKs, AKo.
entry.detailhand.combosConcrete two-card combo breakdown.
detailItem.combocombo.comboConcrete combo, for example AsKs.
detailItem.reachcombo.reachPer-concrete-combo reach.
detailItem.detailcombo.actionsPer-combo action frequencies and EVs.
entry.detail_agghand.actionsAggregated 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_timecomputeTimeMsv2 uses camel-case timing metadata.
statestateRenderv2 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

FieldTypeMeaning
handsarrayThe top-level 169 hand-matrix entries. This replaces v1 strategy.
overallStrategynumber[]One 11-slot strategy vector for the whole emitted range. By default this is a uniform average across top-level hand entries.
overallReachnumberAverage 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.
currentPlayernumberCurrent acting seat in client seat coordinates, or -1 when no player is acting.
numBoardCardsnumberCount of board cards currently present in the request/state: 0, 3, 4, or 5.
numCombosnumberNumber of concrete NLHE two-card combos considered by the grid builder, normally 1326 before board-card filtering.
pendingDealstring"" for a normal response, or "flop", "turn", or "river" when the state needs more board cards before strategy can be computed.
gameFinishedbooleanPresent on terminal v2 responses. true for fold-out/showdown-complete, false for all-in spots that still need board cards.
tableStateobjectStructured table snapshot for the UI: street, pot, board, players, acting player, and BB normalizer.
actionLabelsarrayLegal action metadata for the current decision, keyed by model slot.
actionHistoryarrayStructured action/deal history with positions and pot/spend context.
stateRenderstringDebug render of the universal-poker state. This replaces the old v1 state field name.
obsDebugobjectDebug-only observation metadata: action mask, action sizes, game tags, raw board cards, round, and observation dimension.
modelMd5stringMD5/hash metadata for the selected model, added by the serving wrapper.
inferenceTimeMsnumberTime spent in model inference, separate from total computeTimeMs.
supportScorenumberOptional model confidence score from newer model outputs. Present only when the model emits it.
strategyProfileConfigobjectv2 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:

FieldMeaning
street"preflop", "flop", "turn", or "river".
potCurrent pot in chips.
chipsPerBbBig-blind chip value used for BB conversion.
boardArray of dealt board-card strings.
actingPlayerActing client seat, or -1.
playersPer-seat state array.
actingContextBB-normalized pot/call/spend context for the current actor.
icmEquityBbMTT equity values when present. Empty for non-MTT cash spots.
squidEquityBbSquid equity values when present. Empty for non-squid spots.
bountyBbBounty 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:

FieldMeaning
indexDense UI index among legal actions.
slotOriginal 0-10 model action slot.
nameDisplay label, for example fold, call 2.0 BB, raise 7.5 BB.
rawActionCanonical action value used by the UI; -1 fold, 0 check/call, positive raise-to/all-in chips.
amountStrategy-act-style action amount in chips, or null for fold/check.
amountBbAction amount in BB.
raiseByBbRaise-by amount in BB.
strategyOptional 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:

FieldMeaning
isDealtrue for board-card deal entries, false for player actions.
playerSeatActing client seat, or -1 for deals.
positionDisplay position such as SB, BB, UTG, BTN; empty for deals.
labelHuman-readable action/deal label.
rawActionSame action value space as actionLabels[].rawAction.
amountBbAction/deal amount in BB.
raiseByBbRaise-by amount in BB.
potBeforeBbPot before the action/deal.
potAfterBbPot after the action/deal.
callFacingBbCall amount facing the actor before the action.
streetSpentBbActor's current-street commitment after the action.
totalSpentBbActor'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:

FieldMeaning
handNameMatrix-cell name such as AA, AKs, or AKo.
actionsAggregated structured actions for the hand. This replaces formatted detail_agg rows.
combosStructured 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:

FieldMeaning
ev_rawOriginal raw model EV before fold-normalization.
ev_rankOptional display rank derived from EV and probability when show_labels is enabled.
action_qualityOptional 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".