Mapped asks how well you know the map. The world fills the screen, and you name every country, click the one it asks for, or name the one that lights up, across three topics: Countries, Flags and Capitals. Anyone can play without an account; signing in with Google saves your games and puts them on 63 public leaderboards. The interesting part is keeping those boards honest: the browser and the server run the same game code, and the server replays every finished game before it ranks it.

Forgiving, never wrong
Sporcle's Countries of the World, the quiz Mapped started from, matches names strictly, so a missing accent or "St." for "Saint" costs you a country. Mapped normalizes every guess first: accents, case, punctuation and the words "the", "and" and "of" are dropped, so "St. Kitts & Nevis" and "saint kitts and nevis" are the same key. Exact names, and 55 aliases from USA to Holland to DRC, count the moment they are typed.
Typos are only forgiven on Enter, and only when one name is that close. A name of 5 to 9 letters may be one edit away and a longer one two; anything shorter must be exact. The candidate has to be the only country or territory in the whole index within range, which is what keeps Iran from becoming Iraq, Austria from becoming Australia, and Niger from becoming Nigeria. "Niger" itself waits: while Nigeria is unfound, the game holds the match for 600 ms in case you are still typing.
Hints are there in every game, and every one counts against you. A board ranks by most found, then fewest hints, then fastest, so an unfinished run still ranks behind everyone who found more.
Nine games from one table
A topic changes what you are shown and how you answer; the answer is always a country, so the engine barely changes. One rules table gives each of the nine games three facts: whether targets come one at a time from a seeded queue or in any order, what the prompt is, and how you answer. The reducer, the input, the prompts and the hint ladders all read those facts instead of branching on names.
| Type | Locate | Identify | |
|---|---|---|---|
| Countries | Name them all, any order | Given a name, click the country | A country lights up; type its name |
| Flags | A flag; type its country | A flag; click its country | A country lights up; pick its flag from 4 |
| Capitals | A named country; type its capital | A capital; click its country | A named country; pick its capital from 4 |
The wrong flags are chosen to be hard. 37 hand-made groups hold the flags people confuse, from Chad, Romania, Andorra and Moldova to the Nordic crosses, and Flags · Identify takes decoys from the target's groups first, then its subregion, its continent, and anywhere. Capitals · Identify does the same with the target's neighbours, so Kampala's decoys are capitals of countries Uganda borders. Both are seeded by the game and the target, so a reload shows the same four.

Capitals needed a second try. It launched asking for every capital in a region from memory, and its first two games found about 1 per cent. Now Capitals · Type and Identify name the country and light it up, and only Locate, which gives a capital and asks you to click its country, stays hard. Runs under the old rules stay in their owners' history as "Unranked: older rules" and left the boards.
The server replays every game
A leaderboard where the browser reports its own score is a leaderboard of edited requests. So the game engine is one pure reducer with no DOM and an injected clock, imported by both the React app and the Worker, and every action the player takes is logged with its time.
Found, hints, time and how the game ended all come from the server's replay, never from the browser. The seeded order had one trap. The pool used to be sorted by name with localeCompare, whose order depends on the runtime's language, so a browser set to another language could shuffle a different order from the same seed than the Worker did. It is now sorted by country id with a plain code-unit comparison.
Pause was cut for the same reason. It let a player stop the clock and look answers up in another tab, so now the clock always runs: Esc asks "Give up?" and the clock keeps going while it asks. The reducer still understands a pause, so a log that contains one is marked unranked rather than trusted.
The known gap is written down: a bot that submits a made-up log at a believable human pace passes. There is no admin screen; removing a run is a D1 delete and a script that rebuilds the bests.

Play first, sign in later
An account is never needed to play. A game started signed out gets a claim token, which the browser keeps for 90 days, and the server keeps the finished game as long. Finishing offers "Sign in to save this run", with the rank the run would have had, and signing in on that browser claims every game it played since. Bests saved in the browser before accounts existed come along too, marked unranked because nothing verified them.
Sign-in is Google's OpenID Connect flow, done entirely on the server with PKCE, so there is no Google script on the page and the content security policy stays strict. Sessions, claim tokens and IP addresses are stored only as HMACs.
The whole thing runs on Cloudflare's free plan, and its limits shaped the code. The Worker runs only for /api/*; the app, the map and the flags are static assets, which cost no Worker requests. A whole-world replay stays under 5 ms in the tests, against a 10 ms CPU limit. D1 allows 50 queries per invocation, so claims go up eight at a time, and Your games, which shows a best and a rank for each of the 63 boards, computes every rank inside one query instead of one query per board, which used to blow through the limit.
Learn
Playing Flags well means knowing 197 flags, so Flags has an opt-in Learn switch on the setup card. It is a drill, not a game: no timer pressure, no hints, no board, and nothing sent to the server. A flag appears with four names below it, lookalikes again. A miss shows the right answer and that flag comes back three cards later; two right in a row and it is learned and leaves the round. Learned flags are kept in this browser, fill in on the map as they are learned, and the next round asks only the ones still left.

Learn is its own small reducer beside the game's, deterministic for a seed, and its four choices are reseeded for every card asked, so a returning flag's options move and you learn the flag rather than the button.
Status
Live at mapped.lukeghanna.com, built from 5 to 14 October 2026 in four specs: the map and the three modes, then accounts and leaderboards, then Flags and Capitals, then hints, easier Capitals and Learn. 197 countries drawn from Natural Earth 1:50m outlines at full detail in a 678 KB file, with 56 territories drawn but never guessable, and self-hosted flag SVGs so the page makes no third-party requests beyond Cloudflare's analytics. Light and dark themes, keyboard play throughout, and a phone layout where the cards become bottom sheets. 341 Vitest tests across 31 files, including Worker tests against a real local D1, and 40 Playwright tests (35 desktop, 5 on a Pixel 7 profile) against the real Worker with a fake sign-in that only works on localhost.