From ec1457d1208b3228dcda5343eb063f4934367784 Mon Sep 17 00:00:00 2001 From: "pip-robot[bot]" Date: Wed, 26 Aug 2026 08:44:27 +0000 Subject: [PATCH 1/3] August: what shipped The monthly roundup, dated 1 September and covering 1-28 August. Six guides, the odds calculator, drills, the tournament card, the text size setting, the bots measurement and the corrections page. Every figure in it is imported rather than typed: the flop counts from config/flopSet.ts, the calculator's sample target from lib/poker/oddsQuote.ts, 990 computed as C(45,2) rather than written out, and the number of corrections read off the registry so the sentence cannot go stale the next time a row lands. A post about deriving figures rather than typing them does not get to type its own. Nothing in it describes the recap or the drills record as progress tracking, nothing implies a limit on drills, no /drills link, and "exact" is attached only to heads-up with the board complete. --- src/app/blog/august-what-shipped/page.tsx | 331 ++++++++++++++++++++++ src/config/blog.ts | 7 + 2 files changed, 338 insertions(+) create mode 100644 src/app/blog/august-what-shipped/page.tsx diff --git a/src/app/blog/august-what-shipped/page.tsx b/src/app/blog/august-what-shipped/page.tsx new file mode 100644 index 0000000..b3f55cf --- /dev/null +++ b/src/app/blog/august-what-shipped/page.tsx @@ -0,0 +1,331 @@ +import type { Metadata } from 'next' +import Link from 'next/link' +import { A, LegalPage, Section } from '@/components/marketing/LegalPage' +import { BLOG_POSTS, formatPostDate, postMetadata } from '@/config/blog' +import { CORRECTIONS } from '@/config/corrections' +import { + FLOPS, + FLOPS_BOARD_TRIPS, + FLOPS_SET_OR_BETTER, + FLOPS_WITH_YOUR_RANK, +} from '@/config/flopSet' +import { SAMPLE_TARGET, combinations } from '@/lib/poker/oddsQuote' + +const post = BLOG_POSTS.find((p) => p.slug === 'august-what-shipped')! + +export const metadata: Metadata = postMetadata(post) + +// The month's numbers are imported rather than typed, for the reason the whole +// post is about: a figure written into prose is a figure nothing re-reads. The +// flop counts come from the same registry the answer page renders, and the +// calculator's two constants from the module that computes the quote, so if +// either moves this page moves with it. + +const link = + 'font-medium text-foreground underline decoration-foreground/25 underline-offset-2 transition hover:decoration-foreground' + +/** + * Hands an opponent can hold heads-up with the board complete: your two and the + * five on the table are out, so it is every pair from the 45 left. Written as + * the sum rather than as 990 because 990 is the whole point of the paragraph it + * appears in, and a typed 990 is the kind of figure this post is about. + */ +const EXACT_HEADS_UP_HANDS = combinations(52 - 7, 2) + +export default function AugustRoundupPost() { + return ( + +
+

+ In August Pip stopped being only a poker table. There is somewhere to learn the game now, + six written guides and a free odds calculator, and there is a drill in the app that deals + you a spot and marks your answer. A tournament ends with a card that reads the run you + just played. +

+

+ Two of the things we did this month were about our own work rather than the product. We + measured our bots against how real people play and found them nothing like it, and we + published a page listing every factual claim we have got wrong. That second one was the + least comfortable thing here and is the one we would keep if we had to pick. +

+
+ +
+

+ + Learn + {' '} + opened at the start of the month with one guide and finished it with six: hand rankings, + how to play Texas Hold’em, which starting hands to play, pot odds, position, and how much + to bet. Each one has something on it you can tap and change rather than only read. +

+

+ Every percentage on those pages is computed from a count rather than typed in, and a test + deals the count out to check it. The starting-hands chart grades all 1,326 two-card hands. + The flop table on the newest page grades all {FLOPS.toLocaleString('en-GB')} flops a + pocket pair can meet. Written that way a wrong figure fails the build instead of sitting + on a page nobody re-reads, which is a boring decision that has already caught things. +

+
+ +
+

+ The last page of the month is a different shape from the others:{' '} + + how often do you flop a set + {' '} + is a question with a number for an answer rather than a guide to a subject. +

+

+ Writing it turned something up. The figure everybody quotes for flopping a set with a + pocket pair is 11.8%, and 11.8% is correct, but it is counting the wrong thing. It counts + the flops that contain one of the two remaining cards of your rank:{' '} + {FLOPS_WITH_YOUR_RANK.toLocaleString('en-GB')} of {FLOPS.toLocaleString('en-GB')}. Grade + the hands instead of counting the cards and you get{' '} + {FLOPS_SET_OR_BETTER.toLocaleString('en-GB')}, which is exactly 12%. +

+

+ The {FLOPS_BOARD_TRIPS} flops in the gap are the ones where the board comes three of a + kind on its own. Neither of your cards arrived, and you have a full house anyway. It is a + small difference and nobody is playing worse for the old number, but it is the sort of + thing you only find by dealing them all out. +

+
+ +
+

+ There is a free{' '} + + odds calculator + {' '} + on Pip now. Pick your two cards, add the board if there is one, say how many people you + are against, and it works out your equity. No account, no signup, nothing to install, and + it never leaves your browser. +

+

The reason it is worth a section is the second line of the answer.

+

+ The way you work out equity is to deal the rest of the hand out over and over and count + who wins. That makes the answer a sample rather than a fact. The same hand, under the same + seed, reads 67.8% at 1,500 deals and 65.2% at 100,000. A number printed to a decimal place + the run behind it cannot support is a made-up number, however precise it looks. +

+

+ So ours prints the margin next to it: 65% equity, ±0.7 points, from{' '} + {SAMPLE_TARGET.toLocaleString('en-GB')} hands dealt. The number of digits follows the + margin rather than the other way round, which is why you get a whole percent when a whole + percent is what the run supports. +

+

+ Where the spot is small enough to count outright, it counts it outright instead. Heads-up + with all five cards down, your opponent has {EXACT_HEADS_UP_HANDS} possible hands, all{' '} + {EXACT_HEADS_UP_HANDS} get dealt, and the answer reads “Exact. All {EXACT_HEADS_UP_HANDS}{' '} + possible hands your opponent could hold.” That is the only shape in the whole calculator + that gets the word exact. Everything else is a sample, and says so. +

+

+ The engine is the same one the game uses to work out the win percentage in the corner of + the table, in the same open repo, so none of this has to be taken on trust. +

+
+ +
+

+ There is a drill in Pip now. It is in the app, next to the tables, and it is called Which + hand wins? +

+

+ Two hands, a finished board, and one question. You pick who takes it, or that they split + it. The answer comes back with the five cards each hand actually plays and one sentence + saying what settled it, which is usually the part you wanted. Every spot is dealt fresh, + so it is a different nine cards every time you open it. +

+

+ It is settled by the same code that settles a showdown at the table, card by card, rather + than by a simulation. That matters more than it sounds. It means the drill cannot mark a + right answer wrong. +

+

+ There is a rating. It starts at 1000 and moves against what the spot was worth: two + different hands where the higher card wins is the easy end, the same hand twice settled by + a kicker is where people go wrong, and a board where neither hand is ahead is the one + nobody thinks to look for. Get an easy one right when you are already well above it and it + is worth almost nothing, which is the honest outcome rather than a stingy one. A number + that goes up every time you answer is a count of how much you played, not a reading of how + well. +

+

+ Being straight about those numbers: nobody had played this when we picked them, so they + are a judgement about the spots rather than a measurement of players. The order is the + part we would defend. The numbers themselves will move once there are real answers to + derive them from. +

+

+ What there is not, and will not be, is a streak. No daily goal, no counter that resets, + nothing you lose by not turning up for a week. Your rating is exactly where you left it + whenever you come back, because nothing in the drills layer is allowed to read the clock + at all. That is not a promise about our intentions, it is a test that fails the build. +

+

+ It is free and there is no limit on how many you play, and that is not a thing we can take + back later. The{' '} + roadmap says plainly + that nothing which ships free gets metered afterwards, and this is the first thing that + rule has had to hold. +

+
+ +
+

Tournaments used to just end. Now one card comes up first.

+

+ Three numbers across the top: where you finished, how many hands it took, and what it did + to your Roll. Then the one moment worth naming, which is the biggest pot you won, what you + showed for it and whose chips it took. Then a line on how you played this run against how + you usually play, something like “You played 70% of your hands this run, looser than your + usual 40%”. Then anything in your career that actually moved, a new best Roll or a better + finish at that venue than you had managed there before. +

+

+ Then it is gone. Nothing stores it, there is no recap to go back to, and nothing sends you + a copy. +

+

+ It reports one run, and that is the whole of it. It will not tell you whether you are + improving, because one tournament does not know, and it never asks you to come back + tomorrow. There is no streak counter anywhere in Pip and there was never going to be one. +

+

+ The other half of it is that the arithmetic has to be honest. The play-style read needs + twenty hands under it before it will say anything at all, and it only calls a difference + from your usual when the gap is bigger than ten points, because twenty hands carries about + eleven points of noise and anything under that is the sample talking rather than you. A + three-hand run gets no verdict on how you play. Three hands do not contain one. +

+

Pip would rather tell you nothing than tell you something it cannot support.

+
+ +
+

+ Pip has a text size setting now. It is in Settings: 100, 125, 150 or 200%, and it is kept + per device, so your phone can be set large and your laptop can stay where it is. +

+

+ It exists because pinch-zoom is switched off in Pip on purpose. That is most of what makes + the installed app feel like an app rather than a webpage, and a gesture that leaves you + panned off the side of a table mid-hand was never much of an accessibility feature. WCAG + asks that text reach 200%. So we built the other route to it. +

+

The reading surfaces go the whole way. The poker table stops at 150%.

+

+ That second part is deliberate. A poker table cannot reflow. The seats, the board, the pot + and the action row all have to be on screen at once and in the same places relative to + each other, so type that doubles has nowhere to go but over the edge. We played a hand at + 200% on a phone and it stopped being a poker table. 150% is the largest size where it + still is one, so that is where it stops, and if you pick 200% while sitting down the app + tells you rather than leaving you to wonder why nothing moved. +

+

One guideline met. Not a claim that Pip is accessible. It was the one in our way.

+
+ +
+

+ Somebody opened an issue saying the bots were unrealistically aggressive. We built a + harness to measure them, and the measurement said the opposite. +

+

+ Across the shipped ladder the bots were raising 1.9% to 7.8% of their hands before the + flop, where a real player opens somewhere between 12% and 25%. They came into pots by + calling, three to ten times more often than by raising, which is the signature of a + calling station rather than a player. At the tightest tables they were folding pocket aces + under the gun about half the time. +

+

+ The part worth admitting is that our tests were green throughout. Every one of them + compared our tables to each other: the loose field played wider than the tight one, the + ladder was ordered correctly, the personalities differed. Nothing in the suite knew what a + person does, so a whole game of calling stations passed every check we had. +

+

+ That is fixed, and the tests that hold it now assert a band rather than a comparison. + Every table in the game has to open more than 5% of hands and fewer than 40%, and cannot + call more than eight times as often as it raises. The numbers come from how people play, + not from how our other tables play. +

+

+ One more thing came out of it. The ladder does get harder as you climb, and we can now put + a number on it: measured by what a seat does when it enters a pot, the share of entries + that are raises runs from 32% at Friends’ Garage to 60% at The Main Event. It is not a + clean step up rung by rung. The casino and the riverboat come in as raisers slightly less + often than the pool hall and the card room do. +

+
+ +
+

+ On the 24th we published{' '} + + a page of our own errors + + . Every factual claim Pip has made in public and then got wrong: what it said, why it was + false, the day it started serving, the day the correction served, and how it was found. + There are {CORRECTIONS.length} on it. +

+

+ Two things about it are worth saying here. The dates are merge dates rather than the day + somebody noticed, because on this repository a merge is the deploy, and the gap between + writing a fix and shipping it is part of what the page is admitting to. And no test has + ever found one of these. Every single one was caught by a person reading, which is exactly + why the page needed writing. +

+

+ It is generated from a registry rather than maintained by hand, because a hand-maintained + list of your own mistakes is a list that quietly stops being updated, and one that stops + being updated implies you stopped making them. Each fixed row names a fragment of the + sentence that was wrong, and a test fails the build if that fragment ever appears on the + site again. +

+
+ +
+

+ The RSS feed on this blog was not built by us. Peter Z opened it as a pull request at the + start of the month, and it is the{' '} + + feed + {' '} + you can subscribe to. He came back on the 25th and added test coverage for the play-style + read, the thing in the tournament card that says whether you played looser than you + usually do. That is his fourth merged pull request since Pip launched. +

+

+ His name is on the{' '} + + credits page + + , which is the whole of the deal. The shelf of{' '} + + good first issues + {' '} + is kept stocked if you want yours there. +

+
+ +
+

+ Multiplayer is the big one and it is not close. Nearer than that: more guides, more + questions answered with a number, and more of the figures on this site derived from a + count rather than typed by a person. +

+

+ The rest of the direction is on the{' '} + roadmap, in the + open. The section worth reading is the one about how Pip pays for itself, which is the + question a free thing usually avoids until the day it stops being free. +

+
+
+ ) +} diff --git a/src/config/blog.ts b/src/config/blog.ts index f721c4d..6b28c3a 100644 --- a/src/config/blog.ts +++ b/src/config/blog.ts @@ -17,6 +17,13 @@ export interface BlogPost { /** Newest first — the index renders this order as-is. */ export const BLOG_POSTS: BlogPost[] = [ + { + slug: 'august-what-shipped', + title: 'August: what shipped', + description: + 'Six written guides, a free odds calculator, a drill that marks your answer, a card at the end of a tournament, and the month we measured our own bots and published our own errors.', + date: '2026-09-01', + }, { slug: 'what-we-got-wrong', title: 'Everything we have published that was wrong', From 75e8534da42a9e9de7b07b16ea22515f494aed56 Mon Sep 17 00:00:00 2001 From: "pip-robot[bot]" Date: Mon, 31 Aug 2026 15:42:49 +0000 Subject: [PATCH 2/3] post: top up the August roundup with the last three days of August The roundup was written on the 26th and merges on the 1st. Five PRs landed in between, two of them things the post is about. - The postflop multiway defect and its fix, as a section of its own. The preflop bands shipped earlier in the month measure how wide the bots play and nothing measured what they did after the flop, so the same failure repeated one street later and the tests written to catch it were green. - The pokersolver post, published on the 28th, which is the month's third piece about our own work rather than the product. No lead-rate percentages in it. The repo carries three different sets of figures for that measurement across two comments and none of them reproduce at 200 hands; the cells that carry the finding are 46 to 108 spots, which is a count, not a rate. The shape is defensible and the decimals are not. --- src/app/blog/august-what-shipped/page.tsx | 88 +++++++++++++++++++++-- 1 file changed, 84 insertions(+), 4 deletions(-) diff --git a/src/app/blog/august-what-shipped/page.tsx b/src/app/blog/august-what-shipped/page.tsx index b3f55cf..fbdf520 100644 --- a/src/app/blog/august-what-shipped/page.tsx +++ b/src/app/blog/august-what-shipped/page.tsx @@ -20,6 +20,16 @@ export const metadata: Metadata = postMetadata(post) // flop counts come from the same registry the answer page renders, and the // calculator's two constants from the module that computes the quote, so if // either moves this page moves with it. +// +// Two exceptions, both deliberate, because the rule is worth stating honestly +// rather than pretending to. Measurement outputs are typed: the preflop bands +// and the entries-raised ladder are the result of a run, not values a module +// holds, and there is nothing to import them from. And the two postflop gates +// (62% and 78%) are literals inside `src/lib/poker/ai/policy.ts` rather than +// exported constants, so the prose repeats them. `tests/ai.test.ts` asserts the +// arithmetic that ties them to the multiples the code actually uses, which +// means moving one fails the build even though it does not move this sentence. +// Asked of the tech side: export them. Until then this comment is the record. const link = 'font-medium text-foreground underline decoration-foreground/25 underline-offset-2 transition hover:decoration-foreground' @@ -47,10 +57,11 @@ export default function AugustRoundupPost() { just played.

- Two of the things we did this month were about our own work rather than the product. We - measured our bots against how real people play and found them nothing like it, and we - published a page listing every factual claim we have got wrong. That second one was the - least comfortable thing here and is the one we would keep if we had to pick. + Three of the things we did this month were about our own work rather than the product. We + measured our bots against how real people play and found them nothing like it, twice. We + wrote down what the library that settles every hand does when its README stops describing + it. And we published a page listing every factual claim we have got wrong. That last one + was the least comfortable thing here and is the one we would keep if we had to pick.

@@ -263,6 +274,46 @@ export default function AugustRoundupPost() {

+
+

+ Every band in that fix measures how wide the bots play before the flop. Nothing + measured what they did after it. On the 29th we wrote the first test that did, and it + found the bots betting a flop that had been checked to them far less often as the number + of opponents went up. Given company, they stopped betting. +

+

+ The cause was one assumption written down once and never revisited. Every postflop + decision was gated on an absolute number: lead out if you hold more than 62% of the + equity, raise for value above 78%. Those are heads-up numbers. Against three opponents 62% + is a much better hand than it is against one, so a gate that reads the same reads far + tighter, and the pot got checked down. +

+

+ The fix quotes those gates as a multiple of a fair share of the pot rather than as an + absolute, a fair share being one divided by the number of players still in. The reason + that could ship to all 29 tables without playing them is arithmetic: heads-up a fair share + is exactly a half, so every multiple works out to the number it replaced, to the decimal, + and not one heads-up hand plays differently. There is a test that does that multiplication + rather than a comment asserting it, which is a distinction this repository has now been + caught by twice. +

+

+ We are not putting the before-and-after rates on this page, and the reason is the same + kind of thing as the rest of the month. The spots that carry the finding are the ones with + three or four opponents left in, and those are the rarest spots in the sample: a run long + enough to be worth quoting heads-up gives you a few dozen of them. The collapse was large + enough to see through that. The exact percentages are not, and we would rather describe + the shape we can defend than print four decimals of noise. +

+

+ The lesson is the one from three weeks earlier and we had not learned it well enough. The + tests that stayed green this time were the ones we had just written to stop exactly this + from happening. They were green because they measured the half of the game we had thought + to look at, and a bot that comes into a reasonable range of pots and then never bets one + of them passes every check in that half. +

+
+

On the 24th we published{' '} @@ -289,6 +340,35 @@ export default function AugustRoundupPost() {

+
+

+ Pip does not have its own hand evaluator. Every showdown at the table, and every answer + the drill marks, is settled by an open-source library called{' '} + pokersolver, which we wrap in about + sixty lines. On the 28th we published{' '} + + five things it does that its README does not mention + + , each with the input that produces it. +

+

+ Ask it for the cards in a hand and a seven-card flush hands you seven. An ace playing low + in a five-high straight comes back labelled 1. A royal flush is called a straight flush. + Four of the five have been asked about on the library’s own issue tracker and left + unanswered for between three and six years, so as far as we can tell there was nowhere to + look them up. +

+

+ None of it is a complaint and none of it has ever reached a player, because the wrapper + already handles the ones that would. The reason it was worth a post is closer to home. Our + wrapper had been asserting three of those behaviours in a code comment, and the line that + keeps only the best five cards depends on one of them being true. A comment is not a test. + So the post ships with a test that re-runs every example on it against the installed + library, which means the page fails the build before it can start lying, and the wrapper + finally has the check it had gone a month without. +

+
+

The RSS feed on this blog was not built by us. Peter Z opened it as a pull request at the From e018438b69a6f3fc94f5bfbaad9c0ddf3333099a Mon Sep 17 00:00:00 2001 From: "pip-robot[bot]" Date: Wed, 2 Sep 2026 12:31:52 +0000 Subject: [PATCH 3/3] post: re-date the August roundup to 2 September --- src/config/blog.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/blog.ts b/src/config/blog.ts index cff874a..ce89043 100644 --- a/src/config/blog.ts +++ b/src/config/blog.ts @@ -22,7 +22,7 @@ export const BLOG_POSTS: BlogPost[] = [ title: 'August: what shipped', description: 'Six written guides, a free odds calculator, a drill that marks your answer, a card at the end of a tournament, and the month we measured our own bots and published our own errors.', - date: '2026-09-01', + date: '2026-09-02', }, { slug: 'pokersolver-undocumented',