Player fingerprinting in StarCraft in the age of AI bots A new tool called scfingerprint maps StarCraft: Brood War accounts to their human players by analyzing how they play, addressing a scene where progamers like Flash earned a $250,000 salary and over $700,000 in prize money across 113 tournaments. The project ships a built-in identity map, such as one showing progamer Queen (ZerO on Liquipedia) using eight toons across five regions, and arrives as some ladder games are now played by AI bots rather than humans. The work targets a namespace where players buy accounts and create unlimited toons across Korea, U.S. East, U.S. West, Europe and Asia, with community tracking handled manually via Liquipedia's alternate ID lists and the CWAL.gg Player Tracker browser extension. Player fingerprinting in StarCraft in the age of AI bots TL;DR Not all games are child’s play. Competitive e-sport athletes are full-time professionals, and the ones at the top earn around what corporate executives make. When that much is on the line, working out who actually played a given game stops being a statistician’s obsession. And as of this month, some of those games are not being played by a human at all https://tl.net/forum/brood-war/646339-bot-on-ladder . Is it possible to fingerprint a human by the way they play? This article covers the problem space, the science in brief, and the results. Jaedong’s hands. This is not child’s play. Introduction StarCraft: Brood War has been a career in Korea for twenty-five years. At the height of the corporate-team era, Flash https://liquipedia.net/starcraft/Flash was on a $250,000 salary while still in high school, and his prize money alone comes to over $700,000 https://www.esportsearnings.com/players/1505-flash-lee-young-ho across 113 tournaments. The teams are gone, but the scene isn’t. Progamers now make their living two ways: - Livestreaming their games on SOOP https://liquipedia.net/starcraft/SOOP , the Korean Twitch https://www.twitch.tv formerly AfreecaTV , where income comes mostly from viewer “star balloon” donations. - Placing well in tournaments. ASL https://liquipedia.net/starcraft/ASL is the big one: run by SOOP, offline, 28 players, a prize pool of around $58,000 https://liquipedia.net/starcraft/ASL/22 a season. KCM Race Survival https://liquipedia.net/starcraft/KCM/Race Survival is the online circuit, on its 39th season at about $8,800 https://liquipedia.net/starcraft/KCM/Race Survival/2026/3 each. Daily Proleague https://liquipedia.net/starcraft/Daily Proleague/Major Proleague runs online showmatches all year. The infrastructure is unusually simple. - One game client. Everybody plays the same one. - One server. Korean competitive play happens on Blizzard’s Battle.net. The rules of the namespace - You buy an account that lets you play on Battle.net. - You may create as many “toons” as you want on it. A toon is an in-game name, and it namespaces your results. - Toons are unique per region, not universally. Two different accounts can own the same toon on two different regions. The regions are Korea, U.S. East, U.S. West, Europe and Asia. - You may buy as many accounts as you like. In principle there’s no reason to. In practice, there is. Here is what that looks like for one real progamer: The account of the progamer Queen ZerO https://liquipedia.net/starcraft/ZerO on Liquipedia , from scfingerprint’s built-in identity map https://github.com/marianogappa/scfingerprint/blob/main/internal/registry/registry.json . Eight toons, five regions, one human. 💎 The trap is in the bottom right. The toon people know and track is on Korea. The account he’s actually playing sits on U.S. East, under a barcode nobody wrote down. Everyone hides Every player creates aliases to keep some level of anonymity. Progamers do it so opponents can’t scout their builds, and so viewers can’t follow them around while they practise. Everyone else has their own reasons. And the cheapest anonymity in the game is a barcode: How the community keeps track By watching. Someone tunes into a stream, the ladder screen is on camera, and they read the toon off the overlay and write it down. The result is a mapping from toon to identity, maintained by hand. It has produced real infrastructure: - Liquipedia’s alternate ID lists https://liquipedia.net/starcraft/Ladder/Alternate IDs , split by era and by server. - The CWAL.gg Player Tracker https://chromewebstore.google.com/detail/cwalgg-player-tracker/gncpkdbhfgbgheppdpjodececlbbfobp , a browser extension that annotates cwal.gg https://cwal.gg with a shared alias list, introduced on TL.net https://tl.net/forum/brood-war/640634-a-cwalgg-extension-easily-keep-track-of-anyone and actively maintained. This mechanism is simple and it works in most cases. But it suffers from five problems: - It requires constant maintenance. The list is only as fresh as the last person who watched a stream. - It is possible to make mistakes. There’s no verification step and no confidence attached. - It does not survive the uniqueness rule. The same toon can be two different people on two different regions, and a flat string mapping cannot express that. - It rests on a single observation. It’s possible for people to play on each other’s accounts, and one sighting doesn’t establish who was at the keyboard for the next thousand games. - It does not scale past a hundred-odd people. Too much manual work. Liquipedia will track the ASL field. It will never track the four guys I play on Sundays. When money is on the line, this is not good enough. And this month the question got harder. A bot appeared on the ladder, good enough to beat progamers, and the community spent days trying to work out which accounts were it. A ladder bot taking a game off a pro. So the question is no longer only which human played a game. It is whether one did. So: can you fingerprint a human from their replays? Brood War writes a replay after every game: a .rep file that records not what happened, but the stream of commands each player issued, each one timestamped. I’ve written about how maddening that is to get meaning out of https://marianogappa.github.io/thoughts/building-an-advanced-starcraft-reporting-tool/ , and built an interactive explainer https://marianogappa.github.io/inside-brood-war/ of why. Here it is exactly the right data. We don’t need to know what happened in the game, only how the keys were pressed. And nobody chooses their keypress habits: they are muscle memory ground in over years, closer to handwriting than to a password. If that can be measured, every problem above goes away. But the fingerprint has to be: - Unique enough , so it doesn’t collide across the population of people playing. - Stable enough , so the same human always converges to the same fingerprint. The next section is the high-level science. Skip to the results results if you’d rather. The science If the premise is right, this is a behavioural biometric https://en.wikipedia.org/wiki/Biometrics , and specifically a close cousin of keystroke dynamics https://en.wikipedia.org/wiki/Keystroke dynamics : the idea that how you type identifies you regardless of what you type. Most of what follows is the speaker-recognition https://en.wikipedia.org/wiki/Speaker recognition pipeline with hands in place of a voice. What gets measured 360 numbers per player per game. All of them describe how , never what : no map, no position, no build order, no strategy. - Hotkey habits. Which control groups they bind and select, the ratio of selects to assigns, double-tap rate, and which groups they bind in their first five assignments of a game. - Command loops. A 10x10 matrix of “which kind of command follows which”. Some players cycle select → hotkey → right-click tightly; others interleave production checks. - Rhythm. The distribution of gaps between consecutive commands: median, percentiles, the modal gap, how long their bursts run. - Micro-timings. How long after binding a hotkey they first use it. The gap inside a double-tap. How big their selections are. - Tempo. APM, effective APM, redundancy, and APM by phase of the game. Two Zergs can open with the same build on the same map and be trivially separable here. One binds hatcheries on 4/5/6 and army on 1/2 with a 190ms double-tap. The other binds army on 1/2/3, checks production by clicking, and has a distinctive three-command burst. Nothing in their build orders tells them apart. Everything above does. How two players get compared 1. Standardise https://en.wikipedia.org/wiki/Standard score every feature against the training corpus, so that a millisecond and an APM count carry equal weight. 2. Keep the 150 most discriminative features , ranked by F-ratio https://en.wikipedia.org/wiki/F-test : between-player variance divided by within-player variance. Most of the 360 measurements distinguish games ; only some distinguish people . That ratio is the same quantity at the heart of linear discriminant analysis https://en.wikipedia.org/wiki/Linear discriminant analysis . 3. Whiten https://en.wikipedia.org/wiki/Whitening transformation using the within-player covariance, shrinkage-regularised https://en.wikipedia.org/wiki/Shrinkage %28statistics%29 because a 150x150 covariance estimated from limited games is otherwise unusable. Habits that move together stop being counted three times, and every remaining direction of variation carries equal information. 4. Compare with cosine similarity https://en.wikipedia.org/wiki/Cosine similarity . A player’s fingerprint is just the mean of their whitened per-game vectors, which is why it can be updated one game at a time and stored in a single database column. Here is Jaedong’s https://github.com/marianogappa/scfingerprint/blob/main/internal/dataset/players/jaedong.json , if you want to see what one actually looks like. 5. Grade that score against a crowd of strangers. A raw similarity of 0.8 means one thing for a player with common habits and something else entirely for a weirdo, so instead of using it directly we ask how far above a reference crowd it sits. That is z-norm and t-norm https://doi.org/10.1006/dspr.1999.0360 , lifted straight from speaker verification, and it is not optional garnish: raw cosine alone gives up roughly 40 points of true-positive rate at the strict threshold. 6. Calibrate https://en.wikipedia.org/wiki/Calibration %28statistics%29 on how much evidence there is. An average over eight games wobbles less than a single game does, by roughly the square root of the count https://en.wikipedia.org/wiki/Standard error , so a score of 5 means different things at one game and at fifty. Each evidence bucket gets its own calibration. Without it, one large aggregate out-ranks everything for entirely the wrong reason. It did, once. Is it unique enough? Score two piles of replays against each other, on cases where the answer is already known. Measured over the committed labelled corpus https://github.com/marianogappa/scfingerprint/tree/main/corpus . Stranger pairs land below 1.7. Same-person pairs land above 5.4. The 3.7 points in between stay empty. 💎 So: from a single game, one player is told apart from a stranger 99.8% of the time, and picked correctly out of 68 candidates 98.9% of the time. That is unique enough, and a case landing in the empty gap gets “I don’t know” instead of a guess. Those percentages are the usual biometric measures, read off a detection error tradeoff https://en.wikipedia.org/wiki/Detection error tradeoff curve. Is it stable enough? Split each player’s games in half by date, then score their earlier games against their own later ones. Every point traces back to a replay in the committed labelled corpus https://github.com/marianogappa/scfingerprint/tree/main/corpus . Same measurement, used two ways: - Above the bar: this is one human, and their habits held. - Below it: the label is describing more than one person. That is how shared accounts and curation mistakes get caught before they poison anything. Results I scored 474 ladder accounts https://github.com/marianogappa/scfingerprint/issues/66 against the catalogue, and checked every answer against the community list. It reaches parity, and dodges all five problems by construction: | Problem with the manual list | Why the fingerprint doesn’t have it | |---|---| | Needs constant maintenance | Re-derives itself from replays | | Can be wrong, silently | Carries a calibrated confidence | | Breaks on the per-region rule | Never looks at a name at all | | Rests on one sighting | Measures whoever is at the keyboard | | Caps out around 100 people | Capped by replays, not by eyeballs | What fingerprinting found ✨ A progamer filed under another progamer’s name. The community list maps three accounts to Blade . One of them carries no toon, only the battle tag llllllllllll , and it plays like https://github.com/marianogappa/scfingerprint/blob/main/corpus/pro exclusions.json Paralyze https://liquipedia.net/starcraft/Paralyze : a different player, same race. ✨ Accounts nobody had connected to anyone. Five barcodes fingerprint as a catalogued pro, and the community list attaches no name to any of them: - wiwiwiww92 → Jaedong https://liquipedia.net/starcraft/Jaedong - IlllIIlIIlIllII → Sharp https://liquipedia.net/starcraft/Sharp - lIIIlIlIIlIIIIl → Ample https://liquipedia.net/starcraft/Ample - lllilliliiilill → Alen https://liquipedia.net/starcraft/Alen - IIIlIlIIIIIIll → TimeIsGold Each matches its pro’s race and rating band, and has never played them on the ladder. Limitations - It cannot put a name on a face. It can tell you that an account filed under a pro isn’t that pro. It cannot tell you who the pro actually is, because the only thing that ever attaches a real name to a fingerprint is somebody watching a stream. - One game is a lead, not proof , and three or more is where it gets confident. Team games and custom maps are three to five times worse, because it was tuned on ladder 1v1. - The catalogue is the weakest link. Every wrong answer I’ve had came from a bad catalogue entry, not from the matcher: a player enrolled from contaminated games claims accounts that aren’t his. 💎 Which is why this is best viewed as a complement to the hand-maintained list, not a replacement for it, and not a better version of it. Try it scfingerprint https://github.com/marianogappa/scfingerprint is a Go library and CLI, MIT licensed, with the labelled corpus committed so every number above traces back to replays. scfingerprint match game.rep who is each player? scfingerprint same --a dirA/ --b dirB/ are these two the same human? scfingerprint whois Queennnnnn who owns this toon, and what else do they own? It’s wired into screpdb https://github.com/marianogappa/screpdb , my StarCraft reporting tool https://marianogappa.github.io/thoughts/building-an-advanced-starcraft-reporting-tool/ , so a barcode in your own replay folder resolves to the person behind it. The identity map and the fingerprint agreeing on the same account, plus every other toon it plays under. And the same across a whole replay folder. screpdb compiles to WASM, so you can ▶️ try a preview in your browser https://marianogappa.github.io/screpdb/ without installing it.