A game of Mafia usually starts by picking someone who doesn't get to play. They run the night, watch everyone point in the dark, and announce who didn't survive. At a table of five that's a fifth of the room, and it's usually whoever knew the rules well enough to explain them.
So I built a mode where the phone runs the night instead and everyone plays.
Then I solved a problem I was very pleased with, and spent rather less time noticing I'd given the answer away somewhere else entirely.
The problem
Night two. The obvious thing is to check who's still alive and skip the roles whose players are gone. Why wake a Detective who died last night?
Because skipping their turn tells the room the Detective is dead.
Everyone is sitting there with their eyes shut. They can't see anything, but they can hear the shape of the night. Last night there was a pause where the Detective did their thing. Tonight there isn't. That silence is free information, handed to every player — including the Mafia, who now know which power role is off the board.
Human moderators have long known this. You call the dead role anyway. You pause as though someone is deciding. Then you tell them to close their eyes.
So the machine does too:
const detective = name('detective', naming);
const alive = livingWith(game, 'detective').length > 0;
steps.push({
id: 'detective-pick',
line: `${detective}, tap the number of the person you are looking at tonight.`,
...(alive
? { pick: { of: 'check', from: seats }, hold: 0 } // stop, wait for a tap
: { hold: THINKING }), // 6.5s, then move on
});
Same words either way. With your eyes shut, a night where the Detective is dead sounds exactly like one where they aren't. I was pleased with this.
The part I missed
Then I opened my own tool.
"Ada did not survive the night. **They were a Doctor."
Reveal-on-death. It's the default I shipped, and it announces in broad daylight the exact fact the night code works so hard to hide. Every careful pause I'd built was guarding a secret the app had already read out to the room.
The precaution isn't wrong. It's conditional. It only does anything when you turn "say what they were" off — the harder game, and not the one the tool hands you by default.
What I actually learned
Not "silence is information", though it is.
The thing worth taking away is that an information leak is a property of the whole system, not of the function you happen to be looking at. My night code was careful. My morning code gave the same fact away for free — 115 lines further down the same file, in the same commit, written the same afternoon. Both were correct on their own, and I still didn't see it. You can't review a leak function by function. You have to ask what a player knows by the end of the round, and work backwards.
I've kept the precaution in, because it's never wrong. But I'd assumed it was nearly free, and when I actually added up the steps around it — wake the role, ask the question, hold the silence, send them back to sleep — a dead Detective costs 22 seconds a night, and a dead Doctor 18. With both gone that's 40 seconds of dead air, every single night, spent convincing a room that two people who aren't there are still thinking.
Which makes the default it's guarding look much worse.
So, a question
Should role reveal be on by default?
On, and the game is easy to follow — you know what's left, the day's argument has real information in it, and a first-timer can keep up. Off, and every timing precaution suddenly earns its keep, deduction gets genuinely hard, and the game is better for a table that's played before.
Right now it's on, because it's friendlier — which means anyone on the defaults is paying up to 40 seconds a night to hide something the app announced at breakfast. I'm not sure that's the correct call. If your table has a strong opinion, I'd like to hear it.
And a broader one: what else should be digitised? This started as a role dealer and became a narrator because it turned out the machine could do the boring half of the job. Other games have that shape — Coup, Avalon, Wink Murder, anything where one person ends up as the admin instead of a player. If that's a game your group plays, tell me and I'll have a go.
👉 99tools.dev/tools/role-dealer — set the narrator to Godless, deal, pass the phone round, and it runs the night. Free, no sign-up, entirely in your browser.
Top comments (0)