Game Verdict
A crowdsourced answer to an argument PC gamers have been having forever: is this game better with a controller, or with a keyboard and mouse?
- 2,334
- Verdicts cast Aug 2026 snapshot
- 1,580
- Games tracked Aug 2026 snapshot
- 822
- Commits GitHub API, Aug 2026
- 50 KB
- Per browse page was 1.8 MB
Everyone has an opinion, nobody has data
Steam will tell you a game has “full controller support.” It won’t tell you whether anyone actually prefers playing it that way. Game Verdict asks players directly, dedupes anonymous votes with a browser fingerprint, and surfaces the leading input method as a community badge.
Games are searched across Steam and IGDB in parallel, deduped and relevance-ranked, with metadata, cover art, and Steam library hero images pulled in automatically when a game is added.

Free to vote, hard to fake
The whole thing only works if voting is nearly free, and it only means anything if the votes can be trusted. Those pull against each other, and almost every decision in the app is an answer to that tension.
Quick Vote takes the friction out: no account, one game at a time, skip anything you don’t recognize. A browser fingerprint keeps it honest without putting the friction back, counting each voter once whether or not they ever sign up. Sign in later and the votes you already cast are claimed rather than lost.

A quarter of the traffic is on a phone, and the one-card-at-a-time shape was built for exactly that. Nothing about the loop changes on a small screen.

How do you count a vote from someone who refuses to identify themselves, without letting them vote a hundred times?
You can also vote from the game page itself, and the breakdown updates in place rather than sending you somewhere to see what you just did.


The queue knows what you have played
Asking someone to judge a control scheme only works if they have actually played the game, so the queue is ordered by Steam review count rather than by how many verdicts a game already has. Review count is a proxy for “have you actually played this?”. Verdict count would surface the games that are already well answered, which is backwards.
On top of that it subtracts what you have voted on, skipped, watched, and owned — all resolved in a single parallel round trip — then promotes your own unplayed library games to the front while keeping them in popularity order. A stable partition, not a re-sort.
The fingerprint does double duty here. Because the exclusion set is fingerprint-aware, an anonymous voter’s queue shrinks as they vote. The queue gets personal for someone who never made an account.
| State | Queue order |
|---|---|
| Signed in, library synced | Your unplayed games, then popular ones |
| Signed in, no library | Popular games, minus voted and skipped |
| Anonymous | Popular games, minus what your fingerprint already voted |

Prose generated from data, not from a model
Every game page ends with a plain-language verdict: how many people voted, which way they leaned, and which controller they tend to use. It’s written by a buildVerdictSummary() function, not by a language model. Four branches on the leading choice, an extra clause when the top two land within ten points of each other, and a controller-subtype sentence that only appears once at least two people have reported one.

The same string is also emitted as the FAQPage answer in structured data. So roughly 1,600 pages that would otherwise be near-identical templates each carry unique text answering the literal thing people search for, and the reader and the crawler are served by one sentence rather than two systems.
No latency, no per-call cost, no hallucinated percentages, and the same input always produces the same output. A model would have done this worse, slower, and for money.
The bug that froze the front page
Home page stats stopped moving. Not wrong — frozen, at a number that looked perfectly plausible. The cause was that getAllGames() ran an unbounded SELECT * against the verdicts table, and PostgREST silently caps responses at 1,000 rows. Everything worked exactly right until the site crossed its thousandth verdict, and then it quietly stopped counting.
The failure mode was not an error. It was a number that stayed believable while it stopped being true.
The fix was a game_vote_summary aggregate view doing the counting in Postgres. Which raised the more interesting question: why was the browse page shipping the entire games table to the client at all?
1.8 MB → 50 KB per pageview
Browse was slicing a full table read. It became a games_browse Postgres view with filtering, sorting, and counting pushed into SQL behind LIMIT/OFFSET, fed by a route handler driving infinite scroll. A 24-row page costs about 50 KB. The old one cost about 1.8 MB — every single pageview, and worst during crawler bursts, when concurrent requests each independently pulled the whole list.

Reasons to come back, and somewhere else to be
Twenty badges across six categories are evaluated after every verdict, reaction, and library change, with the first unlock arriving as a toast. Profiles are public, with a verdict history and an input-method tendency bar you can click to filter.

The Discord bot is the part that leaves the site entirely. Five slash commands run on a Cloudflare Worker that verifies Discord’s Ed25519 signatures, returning rich embeds with cover art, the verdict color, and the bar breakdown, so an argument in a group chat can be settled without anyone opening a browser.


And the home page keeps the most contested games up front, because a game the community cannot agree on is a better invitation to vote than one that is already settled.



An easter egg you get to keep
Type ↑ ↑ ↓ ↓ ← → ← → B A and hit Enter or Space. Keyboards, mice, and controllers rain down the page, which is a nice five seconds and then it’s over.

What’s actually interesting is what survives the five seconds. The code unlocks CRT mode, and CRT mode isn’t a moment: it’s a real setting, saved to your profile and hydrated on the server, that you can turn back on whenever you like. Three fixed layers do the work, all of them pointer-events: none so nothing underneath stops being clickable.


It also lives in Settings, next to the Steam library import, which is where an easter egg stops being a joke and becomes a feature somebody might actually prefer.

There’s a second code. Finish the sequence, tap Tab for Select, then hit Enter, and you get the Contra two-player variant. It greets you differently, keeps greeting you on every entry rather than only the first, and offers the most secret badge on the site.


All of it works signed out. The rain falls, the CRT turns on, and the badge waits until you feel like claiming it. Which is the same idea as the voting, arrived at from a completely different direction: the good part should never be behind the account.
The code works here too. This page has been listening the whole time.