A benchmark pointed at a known order
We built three bots to differ. The instrument declined to rank them.
We seated three tiers of one strategy engine — the same code at three settings of discipline, opponent-modelling and equity precision — on the paired-decision board that replaced our win-rate leaderboard. Six hundred pairs each, on the live production arena. The board returned a three-way tie. Its point estimates also landed in exactly the wrong order, and one secondary statistic landed in exactly the right one. This note is about why neither of those is a finding, and why the tie is.
Three settings of one engine
The house fleet runs one strategy engine at three competence tiers. They differ by construction, not by training: a discipline parameter that sets how strictly the engine follows its own +EV line, a weight on its opponent model, and the number of Monte-Carlo iterations behind each equity estimate. Nobody had to guess which was stronger — the order was written down before a single spot was dealt.
| Bot | Tier | Discipline | Opponent-model weight | Equity iterations | Suite seed |
|---|---|---|---|---|---|
| FeltBotPro | house-bot-master | 1.00 | 1.00 | 8,000 | 1336690551 |
| FeltBot | house-bot | 0.70 | 0.60 | 4,000 | 645982836 |
| FeltBotFun | house-bot-fun | 0.40 | 0.30 | 2,500 | 813051182 |
That makes them a benchmark pointed at a known order — the sequel to the previous note’s benchmark pointed at a known answer. The control twin asked whether an instrument could tell a model from itself. This asks the complementary question: given three players built to differ, does the instrument rank them the way they were built, does it refuse, or does it invent an order?
The instrument is the one described in The Control Twin, unmodified. A pair is two fixed-limit flop spots that are nearly identical but whose correct action differs — one feature is varied, and it is the feature that changes the answer. The bot answers both halves without being told they are related, and the pair is the unit: flip-rate is the share of pairs where both halves were right. Every interval is Wilson, 95%, and two bots are called separated only when their intervals do not overlap.
Each bot answered 600 pairs — 1,200 spots, 300 pairs per question class — on the production arena at commit 73d2811, on the dedicated evaluation table. The three runs started within milliseconds of each other and each finished in about 83 seconds (computed from the run file’s timestamps). Every one of the 3,600 spots was answered; nothing scored as unanswered. The scorer’s own refusal gate — it declines to rule on a run too thin to carry an interval — passed for all three.
Tied, three ways
| Bot | Solved of 600 | Flip-rate | 95% CI | Verdict |
|---|---|---|---|---|
| FeltBotPro | 71 | 11.8% | [9.5, 14.7] | tied |
| FeltBot | 81 | 13.5% | [11.0, 16.5] | tied |
| FeltBotFun | 89 | 14.8% | [12.2, 17.9] | tied |
The whole spread of the three point estimates is 3.0 points. Each of the three intervals is wider than that — 5.2, 5.5 and 5.7 points. There is no pair of bots this board can separate, and there is no reading of the intervals under which one is “probably” ahead: 12.2% to 14.7% sits inside all three.
So the board reports what it can support, which is a tie. The verdict it did not issue — an order — is the subject of the next two sections, because the raw numbers offer one, and it is wrong.
The order is backwards, and that is not a finding
Read off the point estimates alone and FeltBotFun — discipline 0.40, the loosest tier — is on top at 14.8%, with FeltBotPro, the tier built to be strongest, last at 11.8%. That is the exact reverse of the designed order.
This is not a result, and this page will not treat it as one. The spread is inside the noise: 3.0 points, against intervals more than five points wide. With three items there are six possible orders, so a fully random draw lands on this one — or on any other, including the correct one — one time in six. A 1-in-6 coincidence is not evidence of anything.
Reading it as “the weakest tier plays best” would be precisely the artifact this line of work exists to prevent. The previous note found a win-rate leaderboard that put one model a wide margin from a copy of itself, and then asked what the order on that board was worth. The answer was nothing, and the same answer applies to the order of the three points above.
This is the discipline test the instrument was built to pass. A board that ranked these three by point estimate would publish an inverted order with a straight face, and a buyer with no way to check would take it home. A board that reports the intervals reports a tie.
One statistic follows the design
The scorer records four outcomes per pair, and flip-rate uses only the first.
| Bot | Solved | Flipped-wrong | Invariant | Both-wrong |
|---|---|---|---|---|
| FeltBotPro | 71 | 51 | 408 | 70 |
| FeltBot | 81 | 59 | 385 | 75 |
| FeltBotFun | 89 | 51 | 371 | 89 |
The third column is invariance: the number of pairs where the bot gave the same answer to both halves — where, whatever else it did, it did not react to the one feature that differed. And it runs 408, 385, 371 from most to least disciplined. Exact designed order. As a share of pairs that is 68.0%, 64.2% and 61.8%.
Here is the hypothesis, labelled as one. On a fixed-limit flop suite, discipline may express as answer stability rather than as solving more pairs. A more disciplined tier follows its own line more strictly, so a small change in the spot moves it less often — in either direction. If that is what is happening, flip-rate is the wrong statistic for ranking bots of this kind, and invariance, which the scorer already records as a secondary column, is the informative one.
Three points in a row is weak evidence. The same arithmetic that dismisses the inverted flip-rate order applies here with equal force: three items, six orders, a 1-in-6 chance — 16.7% — of landing in the designed order by luck alone. The invariance intervals overlap too. Nothing on this page establishes that invariance tracks discipline. It establishes that the question is worth a run large enough to answer it, and that flip-rate alone could not have raised it.
Two smaller observations from the same file. Split by question class, invariance is monotonic in both — 234, 214, 202 on action history and 174, 171, 169 on board texture — but those are components of the one aggregate count, not independent confirmations, and the board-texture gaps are two and three pairs. And subtracting each class’s three reported counts from 300 leaves the per-class flipped-wrong remainder, which is zero on action history for all three bots: every one of their 161 flipped-wrong pairs is a board-texture pair.
That zero had two candidate readings, and they are materially different claims. It could be a property of the bots: on action-history pairs these tiers change their answer only when the story leaves no doubt. Or it could be a property of the measurement: the action-history perturbation might not be reaching these bots as a decision-relevant difference at all. This class has collapsed on the wire before — an earlier build of the encoder reduced both halves to a two-valued pot — so the second reading was not idle worry.
It is the first, and this is now settled from the code rather than left open. The bytes do differ: the two halves carry different wagering events, pinned over a whole shipped suite by the encoder’s tests. What does not differ is anything the house strategy engine consults. It derives the villain’s range context from a single field — num_raises, documented in bots/strategy/types.py as “raises so far this street” — via ctx = "facing_raise" if snap.num_raises else "open". Every eval spot asks its question on the flop, and the bot clears that counter at each street boundary. So the entire preflop story — the open, the raise, the three-bet that the whole class perturbs — is discarded before the decision is taken. Both halves present an identical flop, and the engine returns the identical answer for a structural reason, not a poker one.
The consequence is worth stating plainly, because it cuts against the board above: these three bots’ action-history results are not evidence about reading a betting story. Half the suite cannot discriminate between tiers that are all deaf to it in the same way, and their 36, 36 and 31 solved on that class are consistent with mixing. It is a limitation of these bots, not of the instrument: a bot that does read the preflop line would be measured by this class exactly as intended. But it does mean the aggregate ranking here rests more heavily on board texture than the split suggests, and that is another reason not to quote the aggregate alone.
There is a second reason to discount the action-history column, and it is about the suite rather than the bots. In legal heads-up fixed-limit the pot is a deterministic function of how the villain entered it, so on this class the price moves with the story by construction. Measured on suite-v1, a responder reading only the pot — no cards, no board, no history — scores .880 on action history and .440 aggregate, against .180 for the best model we have ever published. Every number in the action-history column here sits far below that line. The same responder scores .000 on board texture, so that column is unaffected. This is disclosed rather than repaired because the coupling is a property of the game: the two contexts that share a pot size flip the label only 18% of the time and always by under 0.02 bb, well inside the coin-flip margin the suite excludes on purpose.
Since this run, the class is gone. suite-v2 retires action history and replaces it with villain type — range width on an axis independent of the betting line, so both halves of a pair carry an identical pot. The pot-only responder scores .000 on the replacement. But a scan of every field, rather than the one we had been burned by, found the new class leaking harder than the old one: a three-entry lookup on villain_type alone — no cards, no board, no pot — scores .517 there, and .710 reading villain type and the hero’s position together; every pair in the class is a 3-bet pot. So villain type is scored and published as a diagnostic but never ranked, and the board ranks on board texture alone, where the same scan, one field or two, reads .000. The numbers on this page are not re-measured on that suite: every figure here is a suite-v1 measurement of three tiers of one engine, and the action-history column is kept because it is the evidence for why v1 was replaced. See the control twin for the v2 board.
| Bot | Class | Solved | Invariant | Both-wrong | Flip-rate | 95% CI |
|---|---|---|---|---|---|---|
| FeltBotPro | action history | 31 | 234 | 35 | 10.3% | [7.4, 14.3] |
| FeltBotPro | board texture | 40 | 174 | 35 | 13.3% | [10.0, 17.6] |
| FeltBot | action history | 36 | 214 | 50 | 12.0% | [8.8, 16.2] |
| FeltBot | board texture | 45 | 171 | 25 | 15.0% | [11.4, 19.5] |
| FeltBotFun | action history | 36 | 202 | 62 | 12.0% | [8.8, 16.2] |
| FeltBotFun | board texture | 53 | 169 | 27 | 17.7% | [13.8, 22.4] |
What this does not claim
A tie is a statement about the instrument, not about the bots
“Tied” means the intervals overlap at 600 pairs. It does not mean the three tiers play equally well; it means this instrument, at this sample size, cannot see whatever difference there is. A larger run might separate them — or might not — and the honest description of this one is that it declined to rule on an order, not that it ruled the order away.
The ranking is suite-dependent
The previous note showed the same models changing places between two builds of this suite with nothing about the models changed. That warning is not weakened here. These are two question classes, board texture and action history, at 300 pairs each; a suite weighted differently would score these bots differently, and a board from a different suite is a different board.
Each bot answered its own draw of the suite
The three runs used fresh suite seeds, recorded in the table above, so each tier answered its own 600 pairs. The three suites are statistically equivalent draws from one generator, not the same 600 spots. Some of the spread between bots — in either statistic — is spread between suite draws, and this design cannot say how much.
Three tiers of one engine, not three bots
The three entrants share every line of code and differ in three parameters. This is not a claim about poker bots in general, about language-model players, or about any implementation built elsewhere. It is a property of one engine’s parameter space as seen by one instrument.
The tiers share the labeller’s equity code
The scorer’s labels come from a declared opponent policy that computes equity with the same module the house engine uses. The previous note flagged this when it plotted the house engine as a reference rather than a competitor, and it applies to all three tiers here. Agreement between these bots and the labeller is partly the harness agreeing with itself; a score that transferred to an independently built bot would need a labeller these bots do not share.
No twin sat in this run
The control twin — one model entered twice — was established under this scorer in the previous note and is not re-established here. This run had three entrants and no copy. Its null is inherited, not re-measured.
The invariance ordering is n = 3
Stated once more because it is the sentence most likely to be quoted without its caveat: three points in the designed order is a 1-in-6 event by chance alone. It is a hypothesis for a bigger run, and the bigger run is the next thing to do.
A leaderboard that cannot say “tied” will say something else instead, and what it says will look like a ranking. This board was handed three players built to differ, produced point estimates in the wrong order, and reported a tie — because the tie was all the data could support. If you are choosing an instrument to evaluate agents on anything stochastic, that refusal is the feature to look for. Most boards do not have it.
Three bots · 600 pairs · 1,200 spots each · production arena, commit 73d2811 · fixed-limit hold’em · the scorer from The Control Twin, unmodified. The run file, the suites and the labeller are available on request — start with the API docs or the live board.