In my first post about this project I described repeated-poker-analysis: a small, abstract toolkit for studying repeated poker spots as a commitment-analysis problem. It is a research and learning project, not a poker solver and not real-money advice.
That post ended on the analysis side, with a hand-built toy game where committing to a fixed strategy beats the one-shot baseline. I planned to spend the next stretch on the algorithm. In practice I spent most of it on input, validation, and saving — the layer the algorithm sits on. This post is about that part, and about the abstract model growing a bit at the same time.
This describes the project on the feat/single-hand-form-edit-cli branch at the time of writing, so details may move after it is merged.
What changed since the last post
Last time, scenarios were small fixed examples and helper functions inside the code. To analyze anything new, I edited Python.
Two things changed. First, a scenario is now a JSON file: the tool reads it, checks it, builds the abstract game, runs the existing analysis pipeline, and can export the result. Second, the abstract model itself widened — I added more scenario modes, so the JSON can describe slightly richer hand and action structure than the single toy spot from the first post.
So this wasn't a stretch with no model work. It was widening the abstract model and the input formats together, while keeping both small enough to check.
Why the input layer came first
Analysis output is only as trustworthy as its input. If a scenario is silently malformed — a probability that doesn't sum to one, a missing matrix cell — then any EV or deadline the tool reports is meaningless, and I might not notice.
So before making the analysis bigger, I wanted the input to fail loudly when it's wrong instead of quietly producing a plausible-looking number. That meant firming up the input and validation layer first, even though it isn't the part I was looking forward to.
What a JSON scenario is
A scenario is a JSON description of one small abstract river spot: the pot and bet sizes, the rake rule, how showdowns resolve, a baseline strategy, and the repeated-game parameters. JSON is the source of truth — every other representation in the tool is derived from it and checked back against it.
Reading one through the current loader looks like this:
JSON scenario
-> detect mode
-> parse + build the abstract game
-> (analysis pipeline, if requested)
The JSON is parsed and built into the actual game object before anything trusts it, so a broken scenario is rejected at the door.
The five scenario modes
I added the modes in order, each one allowing a little more abstract structure than the last:
- single-hand — one abstract hand bucket. Check/bet for one player, call/fold for the other. This is the mode the first post's toy game used.
- Hero-range-only — several Hero buckets with weights; the opponent still acts on a single information set.
- showdown-matrix — Hero and Villain buckets, with each matchup's result given directly as hero / villain / chop.
- equity-matrix — the same grid, but each cell is a Hero pot share between 0 and 1 instead of a discrete result. These numbers are supplied in the JSON; the tool does not evaluate real cards or compute equity.
- river betting-tree — one street with a few more actions: an IP stab after an OOP check, and a single IP raise line against an OOP bet. It's still a fixed one-street tree, with no re-raises, nested sizings, or street transitions.
The abstraction level is still high. There's no real card-range parsing, no real card evaluation, no external solver import, no solver-grade GTO calculation, no multiple streets, and no arbitrary nested betting trees.
I split this into five modes on purpose. The goal wasn't to make the model large quickly — it was to keep each step small enough that when something broke, I could tell which stage and which input shape caused it. So the model and input formats grew incrementally, but there's still groundwork to do before any of this becomes realistic analysis.
Why I built form models before the GUI
I want a GUI eventually, so editing a scenario doesn't mean hand-writing JSON. But before building any screens, I built a form model for each of the five modes.
A form model is a flat, edit-friendly representation of a scenario — the shape a GUI form would bind to. It doesn't replace the JSON; the JSON stays the source of truth. The form model just gives a future screen something convenient to read from and write to.
Two design choices mattered here:
- The validator returns field-level messages instead of throwing. While editing, a form is often temporarily broken — a probability that doesn't sum to one yet, a half-typed value — and a GUI needs to show that next to the field rather than crash.
- Invalid values are not quietly rounded to valid ones. If a field is wrong, it's surfaced as an error before saving, not silently "fixed." A scenario that passes validation is one that round-trips back through the parser and builder.
Building the form models for all five modes first let me check the data structures behind the screens before committing to any screen.
What the inspect / roundtrip / edit CLIs check
To confirm the form layer works without a GUI, there are three small developer-utility CLIs. They aren't user features; they're how I check the plumbing.
-
inspect_scenario_form.pyreads a scenario through its form model, detects the mode, runs validation, and confirms it round-trips back through the parser and builder. It's a health check for the input layer. -
roundtrip_scenario_form.pyreads a scenario, runs it through the form model, and writes it back out — only if validation and the round-trip succeed. This is close to what a GUI "save" will eventually do. -
edit_scenario_form.pyreads a scenario, edits a few fields, validates, and saves. It's the minimal version of "change a field and save it," and for now it's single-hand only.
Inspecting the first post's toy game looks like this:
$ python scripts/inspect_scenario_form.py examples/scenarios/nuts_chop_steal_bet98.json
Scenario form inspection
scenario_id: nuts_chop_steal_bet98
mode: single-hand
form: SingleHandScenarioForm
validation: ok
round_trip_parse: ok
round_trip_build: ok
It's a boring output, which is what I wanted: it confirms the read → validate → round-trip path holds before I build a screen on top of it.
Working with AI as the codebase grew
I'm building this with AI assistance, and how I work with it changed as the code grew.
I went into this stretch expecting to get a lot done over a weekend. To keep quality up, though, I started having the assistant explain code back to me, adding more tests, and running a review pass before moving on. That slowed feature work down, and the stretch ended at the form layer rather than further along.
I don't think that was bad stalling. It's the scaffold I'll need to trust the analysis and the GUI later. The cost is that explaining, testing, and reviewing as I go uses far more tokens and takes longer than just generating code. It's still clearly faster than doing all of this alone from scratch — but on a project this size, the trade-off is something I now weigh.
Tests and verification
There are now over 1,000 tests. I want to be careful about what that means: it does not mean I'm verifying a large poker solver. It means I've accumulated checks on a small abstract model, its input/output paths, and boundary conditions — for example, that all five modes parse, that a CLI writes JSON and nothing else, that it won't overwrite a file unless told to, and that it writes nothing when validation fails.
That kind of check is most of the test count. It buys confidence in the plumbing, not in any real-world poker claim.
The harder question is still open: as the project grows, the amount I'd need to verify grows faster than I can read. I haven't solved that. I expect I'll have to validate from the outside — design the checks to be as exhaustive as I can and run many configurations — rather than reading every line.
Current limitations
At this stage the tool is still a small abstract toolkit. In particular, it does not have:
- a real card-range parser or real hand evaluation,
- any external solver import or solver-grade GTO output,
- an arbitrary game-tree editor, multiple streets, or re-raise / multi-sizing / nested betting trees,
- a GUI yet, or edit CLIs for the range / matrix / betting-tree modes (edit is single-hand only),
- anything resembling real-money strategy advice, or any model of human learning speed or psychology.
The EV numbers it produces are outputs of a deliberately small model, not poker advice.
What I want to do next
The near-term goal is to make the tool easier to run and watch — to get from "abstract model that passes its checks" to "I can load a scenario, change it, and see the result" without editing Python.
Concretely, that means extending the edit flow beyond single-hand, and eventually a minimal GUI on top of the form models that are already in place. Alongside that, I still want to work out the verification approach for when the model grows, since that's the part I don't have a good answer for yet.
This stretch was mostly groundwork. It's not the part I was looking forward to, but the algorithm work I do want to do will be easier to trust because of it.
Links
- Repository: guriguri215-lang/repeated-poker-analysis
- Scenario format reference: docs/scenario_format_reference.md
- GUI / input design doc: docs/gui_input_design.md
- MVP walkthrough: docs/mvp_walkthrough.md
- Assumptions and limitations: docs/assumptions_and_limitations.md
Top comments (0)