You may cast as many votes as time allows. You may choose the force and direction of every throw. But you do not control the political wind.
A short, mobile-first political satire game. You stand in a polling station, crumple your ballot, and swipe it towards a container labelled BALLOT / WASTE. The room is full of wind — and the wind is made of politicians' speeches. Every statement reinforces, weakens or reverses it, and bends your ballot off course.
The voter casts the ballot, but political performance shapes the atmosphere through which it must travel.
Play the grey-box: https://playablestories.github.io/ballot-bin-game/
git clone https://github.com/PlayableStories/ballot-bin-game.git
cd ballot-bin-game
nvm use # Node 24 (see .nvmrc)
npm install
npm run dev # localhost:5173
npm run build # production buildA 60-second session. A crumpled ballot rests in your hand; swipe up to throw it at the bin six metres away. Swipe length and speed set the power; the angle sets your aim.
The catch: the room has a wind — a single sideways force on the ballot in flight — and the wind is political. Two rival speakers stand at podiums flanking the lane. Every few seconds one of them speaks, and the speech changes the wind four ways:
- PUSH — shove it left or right,
- AMPLIFY — make the current wind stronger,
- DAMPEN — settle it down,
- REVERSE — flip it.
The wind eases to each new value (it never snaps) and decays slowly back toward calm, so it accumulates across throws. You can only get the ballot in by aiming at where the wind will carry it, not where you meant it to go. Ballots that make it are in the bin; ballots swept aside land on the floor and stay there — by the end, the floor is a physical record of the session. Once per session, a single telegraphed gust arrives: brief, warned in advance, and deliberately unbeatable.
Status: playable grey-box — no art yet, and none needed until Stage 4. Stage 2 (wind + speech) and Stage 3 (in-hand tuning) are done. Next is Stage 4 — real rendering.
Two rules hold the design together:
- The catch window is asymmetric — forgiving in depth, tight laterally. Sloppy power survives; sloppy wind-reading does not. That is what makes reading the wind the skill, not throw strength.
- The wind cap is derived, not guessed — the strongest possible wind is just beatable by the strongest possible counter-swipe. If wind ever exceeded counter-steer, the game would quietly start arguing that voting is futile, which is a lazier claim than the one it is making. A test fails if that invariant breaks.
And one architectural rule makes the game forkable (see Fork it): the engine holds no player-facing string and no colour literal. Everything the player reads or sees the colour of lives in a two-file content pack — src/content.ts (the words, the two factions, the speeches) and src/theme.ts (the colours and fonts). The mechanics stay frozen in src/config.ts.
The trigger was the media framing around the 2026 Clacton by-election, but the game is built to outlive it. The two speakers are fictional archetypes — The Strong Leader and The Outsider — with no real likenesses, names, or invented quotations; the satire is of political behaviour, not of individuals.
They are not a left/right seesaw. Both push the wind both ways across their speeches — the point is that rhetoric accumulates from whoever is loudest, not that "both sides are the same". Colour identifies a campaign, never a side: amber and teal, not red and blue. There is no win/lose, no grade, no stars — the count screen reads the result and pushes turnout, in the wind metaphor.
The question the prototype exists to answer:
Does hearing a political statement, seeing it alter the wind, and then compensating through a ballot throw communicate the idea that political speech shapes the environment of voting?
Everything not serving that question is out of scope.
Ballot / Waste isn't just a game — it's a base game for the Playable Stories series, built to be re-skinned. The mechanic (throw a ballot through an accumulating wind two voices keep blowing) is fixed; who the voices are and every word and colour are yours. Two paths:
Keep the mechanic, swap the election. A different pair of candidates, a different country, a different argument entirely. Edit two files — src/content.ts for all text and the speeches, src/theme.ts for all colours and fonts — then run npm run validate. No engine code, no TypeScript beyond filling in the pack.
→ See FORKING.md for the full re-skin guide.
Recreate the game on a different stack — or bend the mechanic itself into a new story — using an AI code builder (Replit, Bolt, v0, Lovable, Cursor, Claude Code). The reference prompt is self-contained, preserves the required mechanics and the two-file editable architecture, and asks the AI to confirm with you before writing code.
→ See REFERENCE_PROMPT.md for the prompt and platform notes.
Phaser 4.2 · TypeScript · Vite · Vitest. No backend; a static build deployed to GitHub Pages on every merge to main. Node 24 (pinned via .nvmrc).
The defining choice is the two-file editable architecture: all player text in src/content.ts, all visuals in src/theme.ts, the frozen mechanics in src/config.ts, and a renderer seam so the art path (grey-box → polygons → sprites) stays reversible without touching game logic.
npm run validate # checks the content pack (content.ts + theme.ts)
npm run check # typecheck + validate + tests + build — the full gatenpm run validate fails with a plain-language message if a re-skin breaks a design line: not exactly two factions, a faction with no colour (or an orphan colour), a speech by an unknown faction, a faction missing an effect or a REVERSE, a PUSH that isn't aimed both ways (the no-seesaw rule), or a count line that dropped its {n} placeholder.
ballot-bin-game/
├── README.md # you are here
├── FORKING.md # Level 1 (re-skin) guide
├── REFERENCE_PROMPT.md # Level 2 (AI rebuild) prompt
├── CONCEPT.md · GDD-PROTOTYPE.md · IMPLEMENTATION-PLAN.md · DEVLOG.md · PROCESS-LOG.md
├── index.html
├── package.json
├── src/
│ ├── content.ts # 📝 all player text + the two factions + speeches
│ ├── theme.ts # 🎨 all colours + fonts
│ ├── content-rules.ts # the validator's rules
│ ├── config.ts # ❄️ frozen mechanics — do not re-skin
│ ├── main.ts
│ ├── scenes/ # Play — the scene
│ ├── systems/ # Wind, Ballistics, Gesture, Projection, Speeches, Narration — pure, testable
│ └── render/ # Renderer seam + GreyboxRenderer
├── scripts/
│ └── validate-content.ts # npm run validate
├── docs/progress/ # a screenshot per stage
└── concept-art/ # visual direction boards
Playable Stories repos are teaching material as well as version control, so the record of how the game was built is part of the product.
| Document | Contents |
|---|---|
FORKING.md |
Level 1 re-skin guide — the two editable files, what's free to change, what's frozen. |
REFERENCE_PROMPT.md |
Level 2 rebuild prompt — recreate the game on any stack with an AI builder. |
CONCEPT.md |
The why — theme, metaphor, political framing, visual direction, decision history. |
GDD-PROTOTYPE.md |
The how — world model, gesture maths, wind parameters, build order. |
IMPLEMENTATION-PLAN.md |
Stages 0–6, the two go/no-go gates, and where code stops and a human starts. |
DEVLOG.md |
What happened, per stage. What broke, and the numbers. |
PROCESS-LOG.md |
How this was built with an AI partner — including where the AI was wrong. |
docs/progress/ |
A screenshot per stage. |
concept-art/ |
Visual direction boards, and the three defects that survived regeneration. |
PROCESS-LOG.md records what the AI caught, what it argued for that would have damaged the game, and where the designer overruled it — including the session where it wrote 72 passing tests over a game that was unplayable, and only found out by running it. A log that only records good decisions teaches nobody anything.
The two-file architecture means most contributions land in src/content.ts (new speeches, new factions, new copy) or src/theme.ts (visual variants) — no game-engine knowledge required. Run npm run check, then branch and open a PR.
Inspired by the framing around the 2026 Clacton by-election, but built to outlive it — the candidates, speeches, and every word are a content pack, not a rewrite. See FORKING.md.