projects

VeePeenini, Part 12: The Knockouts, and an Album You Can Hold

· Vitor Pontual · 7 min read

VeePeenini, Part 12: The Knockouts, and an Album You Can Hold

The group stage is 72 matches of the same shape repeating. Then the knockouts arrive, and the game changes character overnight. Back in Part 7 I flagged knockout bracket resolution as the thing I’d reasoned through but never watched run live, and I said the real bracket filling in with people’s predictions riding on it would be “its own kind of pressure.” Late June, it stopped being hypothetical. Here’s how that went, and a second thing I finally got to ship: an album you can actually hold in your hands.

The scary part was fine

The headline fear was bracket redraw. Group stage predictions are simple: you pick a scoreline and the win-draw-loss outcome falls out of it. Knockouts add progression — someone always advances, the bracket has to rewrite itself as results land, and a wrong result early cascades into every round after it. That’s the piece I’d only ever tested with hand-seeded data.

It mostly just held. I’d built a propagation job earlier that writes the real teams into the next round’s fixtures the moment a round’s last match grades, so the odds could be fetched before those matches lock. When the Round of 32 resolved, the bracket filled itself in, the next matches got their odds, and the machine kept turning. The thing I’d been nervous about for a month turned out to be the calm part.

The knockout bracket, mid-tournament The bracket, filling itself in as results land — the part I’d lost sleep over.

The quiet things were the work

This is a pattern I keep running into: the scary-sounding step goes fine, and the thing that gets you is some unglamorous detail nobody thought to worry about. The knockouts were a whole cluster of those.

Stadiums get renamed. Some venues carry a sponsor name that differs from the name in our fixture data, and the resolver that maps a result back to the right bracket slot was matching on venue. A renamed stadium meant a match couldn’t find its slot. So we added an alias resolver, a small table that says “this sponsor name is that stadium,” so a rebrand doesn’t strand a result. Deeply unglamorous, and it would have broken the bracket silently if we’d missed it.

Odds pointed the wrong way. A knockout fixture can arrive with its two teams in either home/away orientation relative to how the odds feed lists them. If you read the odds without checking which side is which, half the matches get priced backwards — an underdog quoted at favorite’s odds. That’s not a crash. It’s a silent mis-pricing, which is the worst kind, because everyone’s points ride on those numbers. We made the odds match in either orientation.

Penalty shootouts. This one took the most thought. In a knockout there are no draws — someone advances, even if it goes to a shootout, and a shootout is a coin flip that the 90-minute odds don’t really price. My call was to keep it honest and simple: price a penalty knockout at the raw 90-minute win odds, and — this is the important half — detect the penalty win off the actual final score, not off anyone’s prediction. If I’d keyed it to the prediction, a player who guessed the shootout right but the score wrong could score off a result that never happened. The truth has to come from what actually occurred on the pitch, never from the guess.

Only ask when it’s genuinely ambiguous. One small product call I’m happy with: the form only asks “who advances?” when you predict a draw. If your prediction has a winner — say 2-0 — that team is obviously who you’re sending through, so there’s no second tap. Only a level score is ambiguous, because that’s the one that goes to penalties. It’s a tiny thing, but it removes a pointless question from every non-draw pick, which is most of them.

And the live points race — the thing that makes the lounge feel alive during a match — now runs through the knockout stages too, so the standings still move in real time when it matters most.

Which finally paid off the thing I’d flagged as untested all the way back in Part 7: a dozen people reacting to a late 1-0 was the realtime layer’s nightmare I’d never actually watched happen. It happened, and it held — no lag. Most matches had quiet lounges, honestly, but the busiest single game pulled six of the nine players in at once, all reacting live as it swung, and the points race kept ticking without a stutter. The scary case turned out to be the fun case.

The lesson, again: I spent my worry budget on the bracket, and the bracket was fine. The renamed stadiums and the odds orientation and the penalty detection — the boring middle of the problem — were where the actual work was.

An album you can hold

The other thing that shipped is the one I’d wanted since the beginning. VeePeenini is a sticker album. And a sticker album, to me, is a thing you can hold, flip through, and eventually put on a shelf. Living entirely on a screen always felt like a half-promise. So we built the printable yearbook: a full PDF with a cover, a two-page spread for every country, the tournament record, the bracket, the leaderboard, and each player’s own points broken down. You export yours, and it’s a real book.

A country page from the printable album A country’s page in the exported PDF album — a thing you can actually print and hold.

Getting it to render live against real data was the fun half. The hard half was weight. A hundred-plus pages of player photos is a lot of image, and the first real export came out around 32MB — too heavy to casually share. Photos are the bulk of it, so we resize them on the fly while assembling the document, down to roughly what the page actually displays. That alone pulled it down to about 13.5MB, small enough to send to a friend without apologizing for it. The photo resizer is loaded defensively, too: if the image library isn’t available it falls back to the full-size photo rather than failing the whole export. A book that sometimes doesn’t build is worse than a slightly heavier one.

But the part I actually cared about was trust, not the file size. My north star for this whole project is that the standings are trustworthy — I’ve said it every time the subject of correctness comes up. The moment those numbers get printed on paper, they stop being something I can quietly re-grade later. They’re fixed. So before the yearbook could show anyone’s points, I needed to prove the printed numbers were exactly right.

So we built a deterministic points ledger. It splits every player’s score into its pieces — the outcome points, the exact-score bonus, the margin bonus, the album side — by re-running the real grader, not by asking an AI to summarize anything. Then a reconciliation audit runs the whole thing for everyone and asserts it matches: every match’s split has to equal the points we actually stored, and every player’s total has to equal what’s on their profile. When I ran it against production it reconciled across every player and hundreds of graded predictions, with nothing off by a point. That audit is the reason I can put a number on a printed page and stand behind it. The numbers on paper reconcile to the numbers in the database, exactly, or the export doesn’t get to claim them.

And a couple of small joys

Two lighter touches landed alongside all this. Pasting a full country into your album used to be a sticker-by-sticker chore, so there’s now a one-click “Paste All” — and when you finish a country, you get a “{Country} Completed!” celebration. It’s a small piece of confetti, and it’s exactly the kind of thing a physical album gives you for free when you press down that last sticker and the page is finally whole. Nice to hand a little of that back.

The bracket I lost sleep over ran itself, and the stadiums nobody thinks about are what tried to break it. And the album is, at last, a thing you can print and hold, with numbers on it I can actually promise are right. That last part is the one I’m proudest about.