Inspiration
The idea was one year of unemployment, told entirely through a phone screen.
The first honest problem showed up on day two: that's a narrative structure — prologue, fired, three career choices — not a repeatable action. The rules disqualify linked-screen click-throughs, and a one-time branch is exactly that. So the careers stopped being a branch and became parallel tracks you re-weight every turn.
Then twelve months became thirty days. The monthly version produced twelve form-filling interactions in a session; the daily version produces dozens of concrete taps, each with immediate feedback. A rent deadline measured in days also reads as urgency in a way "a year" never does.
Thirty later became twenty, and that cut came from a count rather than a feeling. Seven of the nine scheduled story beats landed in the first nine days, which left two-thirds of the calendar with nothing on it. Shortening the month kept every beat and deleted the empty stretch.
One rule was fixed before any of the writing: the joke is never on the player. A game about being out of work that laughs at the person out of work is cruel. The character who gets laughed at is PIP, the assistant, who is certain that saying less is helping more.
What it does
Twenty days. One phone. Rent's due on the last one.
There's no energy bar and no mood meter, because the phone's own status bar is the character sheet: the battery is your energy, the signal bars are your mood. Nothing explains that mapping. The battery just turns amber, then red, and starts breathing right after you overwork. Emphasis comes from motion, not from size.
Each day has a morning, an afternoon and an evening. You spend them across four jobs, each its own small system:
- DESK JOB — Somebody else's badge. Somebody else's desk. A 3x3 board. You're always on it, in a random cell; your deck fills the rest. Skill lives in building the deck, not in placing it — placement is random, so a deck with more agreeable pairs has a higher expected value.
- WAREHOUSE — Boxes, tape, a radio nobody controls.
- PICTURE DESK — Twelve pieces of somebody else's afternoon.
- WATER BOMB — Nights. Four players. One bomb.
Rent is $1,500 on day 20. There are four endings, and in the middle of the month only one thing can end a run early — running out of yourself, not out of money.
How we built it
A single index.html, fully offline, mobile portrait. Zero network requests of any kind — the
build lints for http(s) strings and for fetch() and goes red on either. It once caught an SVG
namespace string.
72 source files in four layers, and the build enforces the boundary:
- DATA — every number and every player-facing string.
validateData()runs at boot and throws loudly rather than letting a bad table reach the player. - CORE — the rules. Never touches
THREE,documentorwindow. - VIEW — reads state, pushes commands, never writes.
- PLATFORM — input, audio, storage, the loop.
One-way flow: touch to command to sim to event to view. The screen is a consequence of
world.phase, never of an event — one function maps phase to screen, and it's the only one
allowed to.
There is no Math.random() anywhere. The sim runs off a seeded RNG, so any run replays from its
seed. That isn't purity, it's leverage: it turns a balance question into a measurement. A headless
bot plays whole months in Node, a probe suite drives the real build in a browser, and the CSS and
asset linter alone carries 4,351 assertions.
Every session was built by prompting Claude, and the build log was written during each session rather than reconstructed at the end.
Challenges we ran into
The opening was unsurvivable. Days 1-2 offered only Work, which drained 26 energy from a starting 62. Two days of the only available action and the phone died — the player lost before the game had shown them anything. Three fixes, each a design decision rather than a patch: Rest is available from day one, because a game must never present a single action that destroys you; unspent parts of the day quietly restore energy, so a leftover afternoon is a small mercy instead of a dead end; and energy cost varies by shift, so the best-paying shift is no longer automatically the right one. Grinding still loses — it has to — but burnout moved from day 3 to day 7, which is long enough to see the inbox and the shop first. A loss that teaches something is worth keeping.
The shift we balanced was not the shift anyone played. Turns in the desk job come from the
battery: clamp(floor(energy / 10), 3, 6). We'd tuned the whole minigame around six turns. Then we
measured 150 real shifts out of bot runs: 3.23 turns on average, and 87% of shifts sitting at the
floor of three, because a real player lives near empty. The entire 3-6 range had collapsed to a
single point. The number to fix was never the turn cap — it was where energy comes from.
A rules screen that lied by five points. The desk minigame has exactly one piece of arithmetic: a pair scores the sum of the two numbers standing next to it, so any line can be checked at a glance. We added pairs for icons the player doesn't own yet, and a browser probe caught it on the first run — the panel printed 11 for a pair whose two badges on screen only added up to 6, because the badge row only lists icons that are in your deck. No unit test could have seen it; none of them build a DOM. The panel now lists only pairs you own both halves of, which has the happy side effect of making the rules sheet grow as you buy.
Two sound systems, and silent buttons. One had its own AudioContext wired straight to the speaker; the other had the real channel tree with an empty definition table — so the Music and Sound sliders in Settings were adjusting channels that no sound passed through. Separately, buttons had never made a sound at all, and that one was architecture rather than oversight: every sound in the game was played from the sim-to-view event drain, and a button only pushes a command. It doesn't emit an event. A clean rule had produced a clean hole.
Accomplishments that we're proud of
The hard constraint is enforced, not promised. "Zero network requests" isn't a line in a
readme — the build refuses to produce index.html if it finds an http(s) string or a fetch()
anywhere in the source. It has gone red on a string a human reviewer would have walked straight
past.
Every balance claim above is a measurement, not an opinion. One rule bought that: no
Math.random() anywhere, so any run replays from its seed. It's what let a bot play whole months
in Node and turn "does the opening feel unfair?" into "burnout lands on day 3 — and after the fix,
day 7."
The mapping needs no tutorial. Nobody is ever told that the battery is energy or that the signal bars are mood. The battery turns amber, then red, then breathes, right after you overwork. We removed the explanatory line and nothing broke.
We cut an entire engine. The firing that starts the story was going to be its own minigame. It got folded into the opening using the same mechanics as the core loop. The opening got shorter and the game got one system instead of two.
What we learned
When a mechanic needs a made-up noun, the theme usually already has the right one. "2 slots left" became "afternoon and evening left" and stopped needing an explanation. Abstract vocabulary is a sign that the mechanic has drifted away from the fiction.
A test that isn't red when you break the thing it protects doesn't exist. Every new assertion got deliberately broken to check that it complained. That habit found more than the assertions did.
Playing it finds a class of bug no test reaches. Every Node suite here loads CORE and DATA and
never touches the platform layer, and a try/catch in the frame loop swallows errors, so the
console stays quiet too. The rule became: open it, play a round, and ask the running game whether
it is still running.
Cutting is the cheapest way to score. Focus is a graded criterion and over-scope is penalised directly. Every feature we removed made the remaining loop easier to read.
What's next for Somebody Else's Badge
The next version is not a bigger simulation. It is a visual novel with this economy underneath it — a larger cast, more places, and days that branch on who you helped and what you spent, where the rent is what makes a kind choice cost something.
The groundwork is already the next task. The endings, and about half of the sixty-two letters, still run on hand-written code rather than through the event table that already carries a hundred and ten entries. Moving them across is what turns a fixed script into a branching one, and it is the same work either way.
Before that, one tuning pass on the back half of the month. Cutting thirty days to twenty kept every story beat, but the pacing after that cut has been measured once, not tuned.
Log in or sign up for Devpost to join the conversation.