Sample size of many

October 27, 2026

studiobuild-in-publicgame-design

Sample size of many

Two months ago I wrote about a sample size of one: one person at a kitchen table, one look at a dark racing game, one verdict, and a whole register flip built on the back of it. I ended that post saying I'd do it again.

I would. But this morning Runoff went live on the App Store for iPhone and iPad, three weeks after the Mac version, and the arithmetic changed. The person at the kitchen table is no longer the only other human who has held this game. There are now people I will never meet, playing it on phones I don't own, forming opinions I can't hear. That's the point of shipping, and it's also faintly terrifying for someone whose entire product process has been me, at night, deciding.

My taste is not a roadmap

Here is the honest list of who has decided what Runoff becomes so far: me, and once, someone at a kitchen table on holiday. Every feature in it exists because at some point after the day job I thought it would be good, and then I spent my evenings proving myself right.

That works surprisingly well for the first version of a game, because the first version is a single opinion by definition. It works much less well for the second. My to-do list for Runoff has more items on it than I have evenings left this year, and I have been ordering it by a method best described as whatever annoyed me most recently. Which is a fine way to pick, if the only person playing is the one who gets annoyed.

So the question stopped being "what do I want to build next" and became "what do the people actually riding it want", and I had no way to ask.

So there's a board for that now

It lives at /runoff/roadmap, and it's a kanban board, because apparently I cannot build a thing for players without also building myself a project-management tool. Three columns: Backlog, In progress and Pending release. Cards move right as the work does, and the first one already has: by the evening, menus a screen reader can use had moved to In progress.

Four cards on it aren't up for a vote: a camera option that keeps the horizon level instead of rolling the world with the pipe; menus a screen reader can actually use; text that grows with your phone's text size; and honouring Reduce Motion when your phone has it switched on. They carry a badge that says committed, built first, and they're next, in that sense, whatever anyone picks.

Under them are the cards you do vote on. Today that's two: online races, so you can ride against a friend who isn't on your Wi-Fi, and an easier way to share a clip of your race. You pick up to three (I'm aware that two cards and a limit of three is generous of me), the most picked go into the updates after the accessibility work, and the totals show up once you've voted, so nobody just backs the leader.

The ballot is meant to grow, and that's what the second half of the page is for. If the thing you want isn't there, there's a small form to pitch it: a name for the feature and a line or two on why. No email, no account. Only I read the pitches, and the ones that fit the game become cards on the board, in my own words, marked as pitched by a player and put up for a vote with the rest. Nothing anyone types gets published as they typed it.

Why accessibility isn't on the ballot

When I first put this page up this morning, all four were on the ballot, and I wrote a paragraph here about how the people who need them should get a say in when they arrive.

Then I reread it, which is a step I recommend. Asking players to vote on whether someone who gets motion sick, or can't see the menus, gets to play the game is not giving them a say. It's making them win a popularity contest for something that shouldn't need one. A level horizon isn't a feature request; for some people it's the difference between playing and putting the phone down. So the four of them stopped being proposals and became the plan, and the vote is for everything after them. The fifth card from that first ballot, a readout of the gap to the next rider, is still on the board as planned work, just not one you vote on.

Before any of that, the page was the iPhone beta, which stopped making sense once the game was on the store: a beta that hands out the finished game free is a discount with extra steps. The old addresses all redirect to the board.

What a vote is and isn't

I want to be precise about what this promises, because "the community decides!" is a sentence that has been abused by a great many games with a great many servers.

It's a list, not a referendum. I still write the cards, which means I still decide what's possible, sensible and in keeping with the game. A pitch for a battle pass won't become a card, because I won't make it one. What the vote does is take the ordering, the part I was doing by mood, and hand it to people with better information than me: the ones playing.

It's also small. One person builds this in the evenings. "The most picked are built first" means first, not by Friday.

The one useful thing: a voter key that forgets itself

Runoff collects nothing, and I wasn't going to let a website feature quietly undo that. But a vote with no protection at all is a vote the most motivated person wins by refreshing. I wanted one ballot per connection per round without keeping anyone's address, and without keeping even the scrambled version a day longer than it was needed.

The trick has two parts. The key is a salted hash of the connecting address with the round baked into the salt, so one round's keys tell you nothing about the next round's:

// Per-round voter key: same address, different round → unrelated key.
const data = new TextEncoder().encode(`${secret}:runoff-beta:${round}:${ip}`)
const digest = await crypto.subtle.digest('SHA-256', data)
// The secret in the salt is what stops anyone hashing every address to reverse it.

And the table enforces "one per round" with a UNIQUE (round, voter) constraint, then forgets: once a round closes, the next request sets every old key to NULL. In SQLite, NULL never equals NULL, so a unique column happily holds any number of them. The constraint does its job while the round is open and quietly stops applying once the keys are gone. The picks stay, the address-shaped thing doesn't, and nobody has to remember to run a clean-up job (least of all me, on an evening).

What didn't change

The game still collects nothing, has no account and plays offline. Voting and pitching happen on this website, behind the same "are you a person" check as the other forms here, and the privacy policy says exactly what each keeps and for how long (pitches go after 180 days; the code that stops one connection sending more than three a day is gone by the next). A household on one Wi-Fi shares a ballot, which is a compromise I'm making knowingly rather than one I discovered later.

And I still think the kitchen table was right. A sample size of one got Runoff out of the dark. It just shouldn't be the thing that decides where it goes from here.

More soon: RSS if you'd like to watch me take orders for once, and occasionally correct my own.