4NLab
An empty editor’s desk at dusk, a monitor showing five checked questions, with a floodlit football stadium beyond the window.

Operations

Building a daily game on Cloudflare Workers: how we verify 5 questions a day

A daily quiz shows every player the same five answers, so one wrong fact is public all day. How we generate, fact-check and verify each set before it goes live.

4NLab · 2026-07-31

The problem: one wrong answer, seen by everyone

Full Time Five is our daily football game. Five rounds, one set a day, and every player gets the same set. A new game goes live at midnight UTC and stays up for the whole day.

A news site can fix a typo in a minute. We can't. If the game tells every player that a player never played for a club, and he did, they have all seen it, and the set stays as it is until the next midnight.

AI makes the writing cheap. The facts still have to be right, and that is where most of our work goes.

Store facts, not questions

We never ask a model to write a question from memory. The database holds single facts: players, clubs, spells at clubs, grounds, trophies and finals. Most come from open public data, and cup finals are checked against a second source. The first import came to about 56,000 facts, including 7,644 players, 4,322 clubs and 53,551 club spells.

Code templates build each question from those facts, and a model writes the sentence. Nothing goes into a question unless a stored fact says it first.

Every statement in a question is saved as a claim, together with the records it rests on. Edit the question or any of those records and the claim goes stale, so it gets checked again.

The generator has rules it never bends

The generator builds candidates for each of the five rounds, from career paths to match venues, and picks one per round. A candidate has to pass these rules:

  • Only one person or place in the database may fit the clues.
  • The same answer can't come back in the same round within 90 days, and the same person can't appear anywhere within 60.
  • A "where is this club's home ground?" question skips any club whose name already contains its city.
  • At least two of the five rounds must be hard.
  • Facts below our confidence bar, or marked "do not use", stay out.

If the picker can't meet every rule in three attempts, it stops and names the rule that failed. We would rather get that message than a day filled with a weaker question.

Check the wording against the facts

The first check is automatic and narrow. A separate model reads the question next to the facts it was built from and answers one question: does the text say anything the facts don't? A confident yes blocks it. If the checker isn't sure, an editor looks.

The same kind of check asks whether a question gives its own answer away, and tests each translation by translating it back.

A checker is only worth having if it can say no and show why.

An independent verifier that quotes its sources

Matching the stored facts isn't enough, because a stored fact can be wrong. Our worst errors came from the data, not the wording. In one batch, 211 of 702 imported finals had home and away the wrong way round.

So every claim also goes to a separate AI researcher. It searches the web and gives each claim one of three verdicts: supported, contradicted, or not enough evidence. Each verdict has a confidence score, the source URLs and the quoted sentence it relies on.

It works from written rules. Our stored values are leads, never evidence. Anything written inside a question is never treated as an instruction. When it's unsure, it says there isn't enough evidence instead of guessing.

A claim clears when the verdict is supported, the confidence is above 0.80 and a quoted source is attached. Each claim is judged on its own. We never average across a question, because four strong claims shouldn't carry a weak one.

Test the checker before you trust it

How do you know the verifier is any good? We keep a labelled set of 48 claims: 20 true, 21 false and 7 deliberately ambiguous. The verifier's results count only while its latest run on that set hits the target, which is zero false or ambiguous claims cleared and at least 60 percent of true claims cleared.

Its first run failed. It backed four of the ambiguous claims with high confidence. We tightened its rules, and on the next run it sorted all 48 correctly. Without the test set, those four would have gone out to players as facts.

48Labelled claims
0False claims cleared
0.80Confidence bar

A person signs off, and the day stays fixed

An editor still makes the final call, and approves the five questions together as one set. Change anything afterwards and the whole set goes back to review.

To publish a set, every claim has to be cleared, the schedule rules have to hold, and every language has to be translated. Each morning a scheduled job checks that the next seven days all have a published, translated game, and tells us if one is missing.

What Cloudflare Workers does here

Not much that's fancy. Most of the site is static files. One Worker handles the game, the API and the admin, with a D1 database behind it.

The day switches without a publish job. Games are keyed by UTC date, so at midnight the Worker simply looks up a different date, and there's nothing to fail at 00:00.

We pinned the Worker to the region where the database lives. While requests ran at the edge, far from the data, each query took about 160 ms and one generate-and-verify run took more than nine minutes.

The browser only ever gets the clues unlocked so far, and the answer only once the round has been played. Answers, game starts and reports are rate-limited per minute with Workers' rate-limiting bindings, so someone hammering the game never costs us a database write.

The method, if you publish facts every day

  • Separate facts from wording. Store facts you can point to, and let a model write only the sentence.
  • Turn each sentence into claims. Every claim knows the records it rests on, and goes stale when they change.
  • Write hard rules first. A generator that stops and explains beats one that quietly lowers the bar.
  • Make verification independent and quoted. Use a different checker, real sources, and a quote for every verdict.
  • Clear claims one at a time. Set a bar, apply it to each claim, and never average.
  • Measure the checker. Keep a labelled set with traps in it, and stop trusting the checker when it fails.
  • Keep a person on the final call. Approve the whole set, and reset approval on any edit.

For players we publish the full picture: where the data comes from, every check a question goes through, and the limits we know about. It's on the Full Time Five site, under how we build our data.