How Unpressed is built

Unpressed is one button for the whole world and one countdown of 120 seconds. If The Countdown ever reaches zero, the game is over for good, so the system behind it must never have a bad second: no lost press, no second opinion about the time, no server failure that ends the game.

One authoritative process

Everything that decides the game happens in one process, the game core, written in TypeScript on Node with uWebSockets.js. It owns the timer, the order of presses, their sequence numbers and the live feed to every viewer. There is exactly one of it writing at any moment.

It is deliberately not serverless. A countdown needs one clock and one order of events. Serverless functions run as many short-lived copies at once, none of which holds the timer, and they would have to agree through a database on every press. A single long-running process makes ties impossible by construction: every press passes through one queue, and its sequence number is its place in it.

Where a press goes

Your phoneCloudflareDNS · cache · flood protectionWebsiteNext.js on Vercel · pagesGame core · Fly.io, FrankfurtWriterHot standbyPostgresNeon, Frankfurt · the recordstandby reads thewriter's liveness here
A phone talks to Cloudflare. Pages come from the website on Vercel; presses and the live feed go to the game core at core.unpressed.app: a writer and a hot standby on Fly.io in Frankfurt. The writer records every press in Postgres on Neon before it answers; the standby watches the writer's liveness stamps there and takes over if they stop.

A press, from tap to log

  1. The tap sends one HTTPS request to the core with the player's signed token. Presses never go through the website's servers.
  2. The arrival is stamped first, to the millisecond, in server time, before the press waits for anything. That stamp decides whether it was in time: a press counts if it arrives before zero. One that arrives at zero exactly is too late. There is no grace period.
  3. One queue. Every press, and every check for zero, goes through the same queue in arrival order. A burst of presses is decided one by one as it arrived and written together.
  4. Once a day. The core checks that the account hasn't pressed this UTC day. A unique constraint in Postgres holds even if the fast check fails.
  5. Durable before acknowledged. The press is written to Postgres before anything else happens. Only then is the timer reset and the player told it counted. A press anyone was told about can't be lost in a crash.
  6. The broadcast. The press goes out over WebSockets to every viewer, with its sequence number and the server's time.

The Countdown is not a number ticking down anywhere. It is the server time of the last press: time left = 120 seconds − (now − last press). Each machine corrects its clock to the database's, so a takeover can't shift the deadline. Each browser measures its offset from the server's clock and its own delay, and shows the time a press sent now still has to arrive. Every message carries a number that only grows, across takeovers too, so a browser never applies an older update after a newer one.

Failover

The writer stamps a liveness record in Postgres five times a second, and the standby reads it as often. The standby follows the live feed and serves viewers too, so both machines take connections all the time.

If the stamps stop for a second, the standby takes over. It claims the writer's lease, a row in Postgres with a number that only grows. Every press write is valid only under the current number, so a cut-off writer that is still running can no longer write anything: there are never two writers. The new writer rebuilds the timer from the log, and browsers reconnect and receive the current state.

The target is a takeover in under 5 seconds, and then nothing happens to The Countdown: it simply continues. The failover test on the real machines runs before the opening.

If the game can't be served for more than 5 seconds, because both machines or the database are down, an outage is declared automatically. That is a published rule: The Countdown freezes from the last moment the game was served and resumes with the time it had then. A writer that can't reach the database for a second steps down rather than guess. Planned maintenance is declared by hand, at least 24 hours ahead. Every outage is in the public record, with when it started and ended and the time left at the freeze.

The public log and the API

Every press is public. The full log is exported every night at 00:10 UTC, at the end and on demand, as gzipped CSV with a column schema and a SHA-256 checksum for each file, linked under About · Status:

The core at core.unpressed.app also answers public read requests: the current state, the feed, presses and pressers per hour, every outage, the status and the newest log files. A log auditor checks the public record against the rules every minute, using only what the export carries: presses in arrival order, one per player per day, each before zero with the time left the log implies, none during an outage. Its verdict is on the status section. Anyone can run the same checks on the downloaded log.

The formal model

The protocol of the core is written as a formal model in TLA+: two machines, the writer's lease, presses, outages, the end and the takeover, with writes that succeed, fail, succeed but report failure, or hang and complete later. A model checker explores every reachable state and checks rules such as: outages never overlap; every press comes before zero, outside any outage, in arrival order; the end comes exactly at zero; every press a player was told about is in the record; and the time the writer showed always matches the record.

Checked exhaustively in a small scope, the model has 664,718,861 distinct states and no violation. Random runs over a larger scope reached 28 million states, also without a violation. Earlier bugs, put back into the model, are found within seconds.

A model only helps if the code does what it says. So a fault simulation runs the real core on two machines against real Postgres and Redis, with crashes, restarts, lost connections and stuck writes, and each run is replayed step by step in the model: every step must be one the model allows, and every database state must match. 130 runs follow the model, and the current code passes 3,000 seeds of the simulation.

The code has been through three review rounds and nineteen full internal code reviews; what touched the timer, the log or the winner was fixed, each with a test that reproduces it. An outside engineer reviews the press-resolution and game-end code before the opening.

The load test

The load test runs before the opening; its numbers will be published here.

What isn't public

How accounts are checked for automation stays private, because publishing it would only help automation. Every press passes a human check, and every press is public. Email addresses are never shown or exported.

Why the rules are what they are: Reddit's TheButton, penny auctions and Fomo3D are the same game.