Skip to content

The equity panel is the AI -- and there is not a model in it

The headline teaching feature of the poker app is a little panel on your phone that tells you your pot odds and the chance your hand is still ahead. It is the thing the app sells -- the answer to "why use this instead of a deck of cards on the kitchen table." It is also, by every reasonable definition, the "AI" in the product: the smart thing the computer does that you cannot do in your head.

There is not a model anywhere near it.

That panel is a Monte Carlo enumeration over the cards still in the deck. Plain Python. No token budget, no API key, no latency tail, no provider to fall back from when the network hiccups. Meanwhile, three taps away, there is a language model -- an on-demand strategic advisor that talks you through a decision in sentences. Two features, both labeled "AI" in the marketing, and one of them is arithmetic and the other is a large language model. Keeping them apart was a design decision, and it is the most transferable decision in the whole codebase.

This post is about that split: when "let the AI handle it" should mean write the deterministic code, and when it should mean call the model. Poker happens to draw the line unusually sharply, but the line is not about poker.

Two things wearing the same word

Here is the boundary that developers new to AI-assisted work blur most often: not everything you call "AI" should be a model. The word has stretched to cover everything from a regex that classifies an email to a frontier LLM, and the marketing layer flattens all of it into "the AI does X." Under the hood, those are different machines with different failure modes, different costs, and different correctness guarantees -- and the design work is deciding which one a given feature is.

The poker app has exactly two intelligent surfaces, and they sit on opposite sides of the line.

The equity / pot-odds panel is computation. the game docs (Section 6.2) is blunt about it: "Pot odds are deterministic math -- no LLM, no delay, computable instantly," and "Equity is a Monte Carlo enumeration over remaining cards." Pot odds are a ratio. Equity is the share of possible runouts where your hand wins. Both are functions of the visible board, your two hole cards, and the cards left in the deck. There is a single correct answer and a finite procedure to get it.

The strategic advisor is generation. Section 7.3 of the same doc describes a separate surface: the player taps "Help me think through this," and a bring-your-own-key language model -- inheriting the advisor layer the catalog's Rail Routes game already built (Section 7) -- produces a short, reasoned suggestion. Not a number. A sentence or two of why, framed as teaching rather than a decree. There is no single correct output; there is a spectrum of more-and-less-helpful explanations.

Same product, same "AI" label on the box, two completely different machines. The rest of this post is the case for why that is correct and not an accident of how the code grew.

What the equity panel actually does

It helps to look at the real thing, because "deterministic Monte Carlo" sounds fancier than the code is. The compute core lives in the relevant server module, and its entire job is to answer one question -- given my two cards and the board so far, how often do I win? -- by trying the possibilities and counting.

The function signature is the whole story:

def compute_equity(hole_cards, board, opponent_count) -> EquityResult: # returns {"win": float, "tie": float, "lose": float}

Internally it does the obvious, honest thing. If the number of ways the hand can finish is small enough, it enumerates all of them: every combination of remaining community cards, against every combination of opponent hole cards, scoring each with the same hand evaluator the engine uses at showdown (evaluate_best_hand in the relevant engine module, which just checks all 21 five-card hands out of seven and keeps the best). It tallies win, tie, and lose, and divides. When the space is too large to walk exhaustively, it falls back to sampling a fixed number of random runouts instead -- the "Monte Carlo" part.

Two properties fall out of this that a model could never give you:

  • It is reproducible. Even the sampled path is seeded from a hash of the exact inputs, so the same hand and board always produce the same equity. There is no temperature, no "regenerate," no drift between two calls with identical inputs. You can write a test that asserts the number and it will be the number forever.
  • It is free and instant. The design doc budgets it at around 50 milliseconds server-side, cached per board state (Section 6.2), with zero LLM calls per session (Section 7.3 cost table). No rate limit is needed because there is nothing to rate-limit -- it is local math. Run it a thousand times a hand and the only cost is CPU you already paid for.

That is the shape of a feature that should be code. A bounded question, a single correct answer, a finite procedure, and a hard requirement that the answer be exact and the same every time.

What the advisor actually does

Now point the same lens at the strategic suggestion and watch every property invert.

The advisor's job is open-ended: explain, in language a newcomer understands, why a spot is close and what the options trade off. The design doc's own example output reads like "Pot odds 3:1 means you need 25% equity. KQs is around 35% against a typical raising range, so calling has positive expected value here. You could also raise..." (Section 7.3). Notice what that paragraph is doing -- it takes the deterministic numbers the equity panel already computed and wraps them in judgment, framing, and a teaching voice. There is no single right wording. Two good explanations of the same spot can read completely differently and both be excellent. That is the signature of a generation problem, and generation is what language models are for.

It is also the surface where the model's weaknesses do not matter. If the advisor phrases something slightly awkwardly, or offers a second option you would not have taken, nothing breaks -- you are getting coaching, not a verdict. Contrast that with asking the model to compute the equity: it would be slower (a network round trip instead of cached local math), pricier (tokens per call instead of free CPU), and -- the part that actually disqualifies it -- wrong some fraction of the time. LLMs approximate arithmetic; they do not execute it. A coaching tool that confidently tells a beginner their equity is 35% when it is 22% has taught them the wrong lesson with total conviction. The split protects the user from exactly that.

There is one more reason the advisor is architecturally interesting, and it is the same property that the four-role WebSocket protocol post is built around: the model is fed only that player's role-filtered view -- the public board plus their own two hole cards, never anyone else's. The server already computes that filtered view for every broadcast, so the advisor gets safe input for free. A physical table cannot offer private coaching mid-hand without someone leaning over your shoulder; the role filter is what makes "an AI that helps just you, without seeing your opponents' cards" structurally possible. The advisor template lives at the advisor framework, and it never receives a card it is not allowed to see.

The rule, stated generally

Strip away the poker and the split is a decision procedure you can carry to any feature:

  • Route exact, verifiable, bounded questions to deterministic code. If there is one correct answer and a finite procedure to compute it -- equity, pot odds, tax math, a schedule conflict, a unit conversion, a permissions check -- write the code. You get correctness, reproducibility, testability, zero marginal cost, and no latency tail. Reaching for a model here is choosing a slower, costlier, occasionally-wrong version of something a function does perfectly.
  • Route open-ended explanation, framing, and judgment to a model. If the value is in the wording and there is no single right output -- coaching, summaries, narrative recap, answering "wait, can I check-raise?" -- a language model is the right tool, and its fuzziness is a feature rather than a liability.

The trap for someone new to building with AI is that the model is so good at sounding right that it is tempting to put it everywhere, including in front of problems that have exact answers. The equity panel is the standing reminder: the most impressive, most load-bearing "AI" feature in the product is the one with no model in it at all. "Can a language model do this?" is the wrong question. "Does this have one correct answer?" is the right one, and it usually answers the first.

The opponents teach the same lesson from the other side

The split shows up again in how the app handles computer opponents, and it is worth a look because it is where the honesty of the project lives.

When you play a hand against the app, your opponents are driven by named personalities, all real classes in the relevant AI module: TAG (tight-aggressive), LAG (loose-aggressive), Nit (extremely tight, never bluffs), Calling Station (calls too much, bluffs too little), and Maniac (over-aggressive, plays trash). These are not arbitrary names -- they are the archetypes real poker players already use for each other (design.md Section 10), which means the label does teaching work before the hand even starts.

And each one teaches a concept by being playable against, which a lecture cannot. The design doc lays out the pedagogy (Section 10): play the Calling Station to learn to value-bet, because it calls you down with weak hands and punishes you for bluffing. Play the Maniac to learn to call down lighter, because its constant aggression makes your mid-strength hands worth more than they would be against a tight player. Play the Nit to learn to fold when a never-bluffing opponent finally bets. Losing a pot to the Maniac and feeling why you lost it lands the lesson in a way no tooltip does. This is the same teaching-by-experience idea that the AI personalities, not optimizers post makes for the family-game personalities -- here it is poker archetypes instead of household characters, but the move is identical: an opponent's personality is the teaching surface, not its raw strength.

What is under the hood matters for the honesty point. Open holdem_personalities.py and each personality is a small dictionary of hand-tuned knobs -- entry_threshold, call_threshold, raise_threshold, aggression, bluff_rate, size_pressure -- read by one shared heuristic that estimates hand strength and picks an action. The Maniac bluffs four times out of five because its bluff_rate is set high, not because a trained model decided to. These are deliberate, legible policies. They are not a solver.

The honest edge: the strong opponent is pencilled, not built

This is the part the post should not paper over. A genuinely strong poker AI -- one that approximates optimal play rather than acting out an archetype -- is a different machine entirely. The settled science for it is Counterfactual Regret Minimization (CFR), and the design doc says so plainly (Section 10): heads-up no-limit hold'em is essentially solved, 6-max is approachable with the same family of techniques, and "the science is settled; implementation effort is the gating cost."

What ships today is the heuristic-policy personalities above -- the "Easy" tier in the doc's own ladder. The CFR-based tiers that would beat a competent human are designed but not implemented. That gap is deliberate and stated, not a thing discovered in the diff: a friend-group teaching app needs characterful, beatable opponents far more than it needs a superhuman solver, so the heuristic personalities were the right first build and CFR is the pencilled next step. A forward-looking companion post, CFR for 6-max Hold'em, takes up what building that opponent actually involves; this post stops at the honest line between what is shipped and what is designed.

And note that even the strong opponent, when it arrives, lands on the computation side of the original split. CFR is a (large, expensive) deterministic procedure, not a language model. The advisor stays the only generative surface in the game. The line holds.

Why poker was the right place to make this argument

One last thing that makes poker a clean teaching vehicle rather than just a fun example: the rules are public domain. There is no licensing exposure in shipping Texas Hold'em, no IP-holder to negotiate with, no themed reskin standing between the code and the lesson. The catalog can use the most recognizable card game in the world as a neutral backdrop and spend all of its design budget on the thing that is actually novel -- the split between the math help and the coaching help.

That is the whole post in one sentence: the equity panel is the AI, the advisor is the AI, and the fact that those are two different machines is the most useful instinct a developer building with AI can develop. Exact questions go to code. Open questions go to the model. Label both "AI" on the box if you like -- just never let the box convince you they are the same thing inside.