Start free

Building this · 5 min read

The night is an event log

Three of the features in this product were not built. They fell out of a single decision about how to store a night.

· Jay Walker

The decision

A night is not stored as a state — who has how many strikes, whose turn it is. It is stored as the list of things that happened, in order:

ADD_PLAYER  Ada
ADD_PLAYER  Marcus
SET_PAID    Ada, true
LOCK_IN     [Marcus, Ada, ...], seed 48211
SHOT        made
SHOT        missed
NEW_RACK
SHOT        missed
UNDO

The current state is a pure function of that list. Nothing stores "Marcus has two strikes"; it is computed, every render, by replaying the log from the start.

What falls out of it

Undo. Undo appends an UNDO event and replays. There is no inverse operation to write and therefore none to get wrong. Undoing a third strike correctly un-eliminates the player, restores their position in the rotation, and rewinds the rack — not because anybody wrote that code, but because the replay simply does not include the shot any more.

Offline. The log is written to the device, synchronously, before anything else happens. The screen redraws from it immediately. The network is not on the path of a shot at all, so losing signal mid-rotation changes nothing you can see.

Syncing. An append-only log with a sequence number syncs by sending everything after the last number the server confirmed. Retries are free, because re-sending an event that is already stored does nothing. There is no merge algorithm, because there is only ever one phone writing.

Support. When somebody says "it gave Marcus a strike he didn't earn", the answer is in the log, in order, with undos included. You can read what actually happened rather than asking them to reproduce it.

What it costs

Replaying from scratch on every render sounds wasteful, and would be if a night were large. A long night is a few thousand tiny objects and the reducer is a plain fold over an array — it is comfortably under a millisecond, and the alternative (incremental state plus a separate undo stack) is more code and more ways to be wrong.

The real cost is discipline. The reducer has to be pure: no clock, no randomness, no network. Every timestamp arrives inside an event. The shuffle at lock-in is seeded, and the seed is stored, so the same log always produces the same running order — which also means somebody can ask whether the draw was fair and get a real answer.

How it is tested

Because the engine is pure, it is testable without a browser, a database or a mock. There are just over three thousand assertions against it, and the most useful one is this: for a long, varied night, after appending each event, undoing it must produce a state deep-equal to the state before it was appended. Every step, every event type.

That single property is what makes it safe to put a one-tap undo in front of somebody who has had three drinks and is being shouted at by their friends.

Run your next night on it

Free, and it runs a whole night — the strike engine, the TV display, the player QR card, who has paid and what the pot pays out. No ads on any plan.

Start a night free