poker-tree-env is a standalone Python + NumPy environment for one complete
Mac TexasSolver v0.2 postflop strategy tree. It reconstructs range-conditioned
states from a three-card flop root through the river; it does not run a solver,
play a match, evaluate showdowns, calculate payouts, or train a model.
The tree itself is the legal-action authority. The package validates the whole root before returning a usable session, including every strategy vector, action amount, child edge, chance branch, checksum, and range transition.
cd /Users/liumingshi/Documents/Environment
python3 -m venv .venv
.venv/bin/pip install -e .The import name is poker_tree_env; the command is poker-tree.
from poker_tree_env import ActionId, load
session = load(
"/absolute/path/root.json.gz",
"/absolute/path/root.manifest.json",
)
state = session.reset()
actions = session.legal_actions()
observation = session.observe("AcKd")
teacher = session.teacher_policy("AcKd")
# The exact acting combo is required for zero-reach detection.
next_state = session.step(ActionId.BET_33, "AcKd")
if next_state.status.value == "CHANCE":
outcomes = session.chance_outcomes()
card = session.sample_chance(mode="reach_weighted", seed=7)
next_state = session.step_chance(card)load() returns a mutable TreeSession. Its .environment is the immutable
PokerTreeEnvironment; cloned sessions share that core but have independent
cursors.
The public statuses are:
DECISION: teacher observations and action steps are available.CHANCE: enumerate or deal one physically valid board card.TERMINAL: fold, showdown, or completed all-in runout.ZERO_REACH: the selected exact combo assigned 0% to the chosen legal action. The edge is followed, but no later teacher observation is available.
| ID | Action | ID | Action |
|---|---|---|---|
| 0 | fold |
7 | bet_all_in |
| 1 | check |
8 | raise_33 |
| 2 | call |
9 | raise_66 |
| 3 | bet_33 |
10 | raise_100 |
| 4 | bet_66 |
11 | raise_150 |
| 5 | bet_100 |
12 | raise_all_in |
| 6 | bet_150 |
Each street/position manifest profile may enable any subset of the five bet sizes and five raise sizes. A tree may contain at most two non-all-in raises within one street.
Mac v0.2 numeric labels are incremental solver-chip contributions. With a
manifest chip_scale of 10, BET 99 contributes 9.9BB. Public raises use
the conventional final raise_to total. Public chip amounts are quantized to
0.1BB and JSON serialization always uses one decimal place. When multiple
solver labels quantize to the same physical wager, one action remains; a
stack-sized duplicate is represented only by the applicable all-in ID.
Every root requires a sidecar JSON object with these sections:
{
"schema_version": 1,
"root_id": "<64 hex characters>",
"root": {
"board": ["Ac", "Kd", "Qh"],
"pot": "5.0",
"stacks": {"oop": "97.5", "ip": "97.5"},
"ranges": {
"oop": {"AA": 1.0, "AKs": 0.0},
"ip": {"AA": 1.0, "AKs": 0.0}
}
},
"solver": {
"identity": "TexasSolver-v0.2.0-MacOS",
"sha256": "<solver binary SHA-256>",
"tree_sha256": "<exact tree-file SHA-256>",
"chip_scale": 10,
"dump_depth": "through_river",
"converged": true,
"exploitability": 0.25,
"iterations": 1000
},
"actions": {
"raise_limit": 2,
"streets": {
"flop": {
"oop": {
"bet": [33, 66, 100, 150, "all_in"],
"raise": [33, 66, 100, 150, "all_in"]
},
"ip": {
"bet": [33, 66, 100, 150, "all_in"],
"raise": [33, 66, 100, 150, "all_in"]
}
},
"turn": {"oop": {"bet": [], "raise": []}, "ip": {"bet": [], "raise": []}},
"river": {"oop": {"bet": [], "raise": []}, "ip": {"bet": [], "raise": []}}
}
}
}The abbreviated range in this illustration is not loadable: both oop and
ip must explicitly contain all 169 canonical classes, including zeroes.
Class mass is divided equally across 6 pair, 4 suited, or 12 offsuit physical
combos, then board blockers are removed and the 1,326-slot vector is
normalized.
An unconverged manifest is rejected. diagnostic=True (or CLI
--diagnostic) permits it only for inspection and marks every state
provisional=true. Diagnostic mode never bypasses checksum, structure,
strategy, amount, chance-completeness, or numeric validation.
for event in session.iter_events():
...
for decision in session.iter_decisions():
# One item for every positive-reach action node × acting combo.
...
record = session.sample_decision(mode="uniform_node", seed=123)Iteration is lazy. The environment never builds a list of every observation.
Random helpers require exactly one explicit integer seed or a
numpy.random.Generator, plus a mode:
reach_weighted: public branch reach × acting-combo reach.uniform_node: equal decision nodes, then range-weighted combo selection.uniform_decision: equal node/combo observations.
poker-tree validate TREE.json.gz ROOT.manifest.json
poker-tree summarize TREE.json.gz ROOT.manifest.json --json
poker-tree list-events TREE.json.gz ROOT.manifest.json --limit 100
poker-tree show-decision TREE.json.gz ROOT.manifest.json root/a0/a0/cAs AcKd
poker-tree sample-decision TREE.json.gz ROOT.manifest.json \
--mode reach_weighted --seed 7 --jsonJSON event listing is JSONL and remains streaming. --limit 0 traverses the
complete tree. The default uncompressed input guard is 512MiB; raise it with
--max-uncompressed-mib or explicitly disable it with --no-size-guard.
PYTHONPATH=src python -m pytest -qThe normal suite uses a generated, checked-in synthetic fixture definition
whose raw tree is complete through the river. A real-tree integration test
runs when both POKER_TREE_REAL_TREE and POKER_TREE_REAL_MANIFEST identify a
compatible tree and sidecar.