Smite Lab: Training the Most Stressful Button in League
A browser trainer for the jungler's one-press minigame, and what it took to make it fair once other people could play it.
Baron is at 2,000 HP and dropping fast. Their jungler is standing in the pit. You have Smite up, one press, and somewhere around a quarter of a second where that press means anything.
A quick bit of context
If you've never played League of Legends: it's a five-versus-five game where two teams fight over a map for half an hour or so until one of them destroys the other's base. Every team has one jungler, the player who roams between the lanes instead of holding one, and only the jungler carries Smite. It's a spell that does a fixed chunk of damage to monsters, and it exists for one job.
Scattered around the map are big neutral monsters: Dragons, Rift Herald, Voidgrubs, Atakhan, and Baron Nashor. Killing one gives your whole team a reward, and the late ones can win the game outright. The catch is that a monster belongs to whoever lands the killing blow. So as it gets low, both junglers are standing there with Smite, watching its health bar, waiting for it to drop into range.
Press too early and Smite doesn't kill it, and now it's on cooldown. Press too late and the other jungler takes it, or one of the eight champions brawling around you lands the last hit instead. There's no number on the screen telling you when you're in range. You're reading a health bar against a number in your head, in the middle of a teamfight, with four teammates who will absolutely let you know how it went.
It's a minigame inside the game. One person per team gets to play it, and everyone is watching.
If you've played jungle, you know the feeling. Objectives come up all game long and every one of them ends in a Smite, but a real game is the only place to practice it, and a real game doesn't tell you anything. You got it or you didn't, and there's no way to know whether you were 30 ms late or 300.
So I built Smite Lab. It's live at smite.dop-amine.com.

What it is
You stand at one of those monsters while allied and enemy champions take its health down in realistic chunks, at their real in-game damage. Press Smite the instant it drops into range. The game tells you exactly how many milliseconds early or late you were, whether the enemy jungler beat you to it, and keeps your stats over time.

The modes grew as the thing grew:
- Practice: endless attempts on whatever fight you set up, with your jungler's abilities on auto or on your own keys
- Daily challenge: today's scenario for everyone, five fights, first run counts
- Solo: ten standard fights against a Diamond jungler bot, rated, with a leaderboard
- Duels: send a friend a link, race each other to the same Smite
- Ranked: matchmaking against other players, with an LP ladder
It started as a static page on a Monday. Ten days and 55 pull requests later it has accounts, live multiplayer, an anti-cheat model I'm actually happy with, and a kill switch on its own bill.
The first problem was the clock
A trainer whose whole job is telling you your error can't have any of its own. The first version read your press on the next animation frame, which at 60 Hz quietly adds up to 16 ms to every result. That's half the difference between a good Smite and a lucky one.
Browsers already know the exact moment a key went down. It's on the event:
window.addEventListener('keydown', (e) => {
if (e.repeat) return
onPress(e.timeStamp) // the moment it happened
}, { capture: true })Damage is a list of hits, so health drops in steps, and the moment your Smite would kill is an exact timestamp. Your press is compared against that, to the tenth of a millisecond.
Here's a Baron fight where I didn't press at all. The enemy jungler took it 338.6 ms after its window opened:

Same fight, same seed, next attempt. I had 338.6 ms to work with and used 11.6 of them:

The close-up at the bottom is my favorite part. Health only moves when a hit lands, so the shaded band is the exact window you had, and the line is where your Smite landed in it.
There's also a calibration test, and it's a rhythm game on purpose. A ring closes onto a target every 700 ms and you tap along with it. Tapping in time with something predictable cancels out your reaction time, so what's left is your screen and input latency.

Then other people wanted to play
Duels were the first multiplayer mode: make a room, send a friend the link, and you both race for the same Smite.

The first online version sent the fight's seed to the browser and trusted it to report when you pressed. Which means anyone who opened the dev tools could compute the whole fight from the seed and press perfectly every time. That was fine while it was a trainer. It stopped being fine the day there was a leaderboard.
So the server runs the fight now, and your browser only finds out what each hit does when it lands. Your press is judged against what was on your screen, with an allowance for your ping that the server measures itself. The part I like most is how a duel stops you reacting to your opponent's Smite: the moment theirs lands, you keep seeing the fight as it would have gone without it, right up until it's too late for a reaction to count. Then you get the real one. The details are on the project page.

The bots found the good bugs
Most of the online work was proven with scriptable bots: duels, Ranked, laggy connections, load, abuse, reconnects, and one that checks the "nobody sees a Smite early" rule on every single message. They found things I would never have found by clicking around.
My favorite: a message whose entire text was null took down the whole server.
msg = JSON.parse(String(raw)) // the text "null" is valid JSON
if (msg.t !== 'ping') { // TypeError: Cannot read properties of null (reading 't')One weird message from anyone, and every live fight on the server died with it. Now every message goes through a parser that hands back either a valid message or nothing, and that case has a test.
The bots also found an account that could end up in two Ranked matches at once, and that exact ties in a duel always went to whoever made the room. Two bots with identical reactions went 8-0 for the host. Ties are drawn from the fight's seed now.
Then there's iOS, which kills a backgrounded home-screen app whenever it feels like it. The first time I tried that mid-duel, my opponent got "opponent left" instantly and the app came back to a blank page. Now a page that disappears holds its seat for 15 seconds. I killed the installed app mid-duel in the iOS simulator, reopened it four seconds later, and it landed right back in the round, with the bot on the other side none the wiser.

Whether a real iPhone closes the connection the same way the simulator does, the production logs will tell me.
The inbox
The scary part of putting anything public on a cloud bill is that nothing stops the bill. So there are two budgets: a small one that just reports, and a hard cap. If a month's spend ever reaches it, a function scales the game to zero and the site goes dark. For a hobby project, going offline beats an open-ended bill.
That kill switch had a bug I only found because it was annoying. This week my inbox filled up with alert emails. Most of them were the kill switch itself running out of memory, on every budget update, about twice an hour. It was checking the spend and then dying, which is fine as long as the answer is "you're fine". The one time it mattered, actually turning the site off, it would have died first. It has twice the memory now.
None of that emails me anymore. Every alert goes to J.A.R.V.I.S., which pulls them in over an outbound connection, so nothing on the internet can reach it. A critical alert is a push to my phone. Everything else waits until I look.
What's next
It's live, it's fair, and right now it has exactly one player, so Ranked is a ladder of me. Next is its own domain, a guide for people who find it from search, and putting it in front of junglers who'll tell me everything that's wrong with it.
If you play jungle, go get some reps. Baron's waiting.
Smite Lab was created under Riot Games' "Legal Jibber Jabber" policy using assets owned by Riot Games. Riot Games does not endorse or sponsor this project. League of Legends, Riot Games and all associated properties are trademarks or registered trademarks of Riot Games, Inc.