35 Badges, Easter Eggs, and a 0.13% Drop Rate

May 26, 2026
By Made Me The Dev

The problem with typing tests is that they're the same every time. You type. You get a number. You type again. The number goes up, or it doesn't. There's nothing to collect, nothing to discover, no reason the 50th session feels different from the first.

I shipped 35 achievements to TypeVelocity. Visible ones with progress bars. Secret ones with no hints. And a hidden category nobody knows about yet — four easter eggs buried in the game, each one waiting to be stumbled on. Here's how it works and why I built it the way I did.

One file. No database.

The entire achievement catalog lives in a single file: js/14-badges.js. Every badge is an object in a plain array — id, name, description, icon, category, and a check function that takes context and returns a boolean. That's the whole contract.

{ id: 'velocity_god',
  name: 'Velocity God',
  desc: 'Hit 100 WPM on any run.',
  category: 'wpm',
  icon: 'diamond',
  check: function(c) { return c.bestWpm >= 100; } }

No server. No user accounts. Everything lives in localStorage as a JSON array of unlocked badge IDs. The check functions run against a buildBadgeContext() snapshot — lifetime stats, high scores, daily history — assembled fresh each time.

When a player finishes a run, the flow is: snapshot the pre-run unlocked set, trigger any event badges, call reconcileDerivedUnlocks() to evaluate all check functions against the updated state, diff against the snapshot, and surface only the new ones in the results modal. Anything that was already unlocked silently stays unlocked.

The retroactive part matters. If someone played 200 games before this shipped, they open the profile page and every badge they've already earned appears unlocked without them needing to do anything. reconcileDerivedUnlocks runs on both the results modal and the profile page for exactly that reason.

The accuracy problem

Early in testing I noticed a flaw. The "Sharp" badge requires 95%+ accuracy. Technically, typing a 3-letter word on Baby difficulty and getting 2 of 3 characters right is 66%. But if you type one word perfectly it's 100%. You could get a 99% accuracy badge from a run that lasted four seconds.

The fix was bestQualityAcc — a separate field in the badge context that only counts accuracy from mode/difficulty slots where your best WPM also reached 25 or higher. No padding the numbers with baby mode one-word runs.

if ((s.wpm || 0) >= 25 && (s.acc || 0) > bestQualityAcc) {
    bestQualityAcc = s.acc;
}

Sharp and Perfectionist (95%+ and 99%+ accuracy) both use this. Same logic applies to Flawless — zero mistakes, but the run has to be at least 50 characters at 20+ WPM. A 3-letter word with no errors is not a flawless run. It's just short.

This kind of thing matters more than it seems. Achievements that can be gamed cheaply stop feeling like achievements. People notice when a badge meant to represent skill can be earned by accident.

Two kinds of badges

Most badges are derived — they recompute every time from your stored stats. Hit 80 WPM somewhere and Velocity King unlocks. Pass 100 games and Centurion appears. Progress bars track where you are.

Event badges are different. They don't have check functions. They only fire once, when specific conditions are met mid-session, via triggerEventBadge(id). Flawless, Ghost Buster, and the secret ones all work this way. There's no polling. Either the moment happened or it didn't.

The Ghost Buster badge — awarded for out-typing your personal best ghost — had one subtle requirement: the snapshot of the ghost's final char count has to be taken before the current run updates it. Otherwise you'd always be comparing against yourself. The snapshot happens at the very start of finishTest(), before any stats are written.

The tooltip

Tapping any badge on the profile page slides up a bottom sheet. It shows the category, a status chip (Unlocked / Locked / Secret), the description, and a message that changes based on where you are.

Under 25% progress: "just getting started. every run moves the needle." Past 75%: "almost there. seriously. don't stop now." At 90%+, the message gets a gold highlight border and says "you are RIGHT there. one solid run and this is yours."

Unlocked badges get personalized copy instead of the proximity messages. Velocity God says "100 WPM. you are not normal. we mean that as a compliment." The marathon badge (5 hours typed) says "you ARE the marathon." Each one is written specifically for that badge, not generated from a template.

The tooltip also shows a version tag — v1 for launch badges, v2 for the new ones. Every badge added in the future gets a version. It's a small thing, but it means players can tell when something was added, and it gives me a clean way to track what shipped when.

Bugs in the Matrix

There's a new category on the profile page: Bugs in the Matrix. Four badges. None of them tell you how to unlock them.

Three are event badges with no hints beyond "keep playing" and "not everything on the page is decoration." One is a collection badge — Bug Hunter — that requires all three of the interactive easter eggs plus something that was already hidden in the game long before this update.

I'm not going to explain them here. That defeats the point.

What I will say: I already had several easter eggs built into the game before this shipped. The jumble effect that fires after enough truly terrible runs. The cheat code. Things I put in because they seemed funny and because I wanted the game to have layers that most players would never see. The achievement system gave me a reason to formalize that as a category rather than leaving it as isolated code no one knew to look for.

The Lucky Roll badge has a 0.13% chance of firing on any run — roughly one in 769. No skill required. No conditions. Just finish a run and occasionally the universe decides you get something. It'll probably take most players hundreds of sessions to see it, if they ever do. Some won't get it for months. I like that. Not every reward should be earnable on purpose.

If you want to find the Daily Challenge's PRNG or the ghost race logic, those are documented. The matrix bugs are not. Go find them.

What came with it

The profile page now shows all 35 badges grouped by category. Locked visible badges show a progress bar and a hint. Secret locked badges show ???. The matrix category shows you the collection badge with a live X / 4 count so you can track how many bugs you've found without knowing which ones remain.

The results modal shows newly unlocked badges after each run — staggered chip animations, a sound for each one, and a gold modal glow if three or more land at once. It's a bit much on purpose. Badge unlocks should feel like something happened.

35 is not the final number. I'll keep adding. The version stamps mean nothing gets lost, and the catalog design means a new badge is literally one object added to an array. The infrastructure is done. The rest is just ideas.

Go find the bugs. Some of them are obvious. Most aren't.

Play TypeVelocity