Engineering6 min read
How Tamagrow counts a kill
The overlay took about a week. Deciding what counts as a kill took the rest of the project.
Written by OZP Studios, Seattle, WA
In March a tester sent us a clip. His companion was doing its happy bounce while he sat in a death cam watching the guy who killed him reload. Nothing had crashed. The scoreboard was right. The pet was just pleased at the wrong moment, and that reads as broken in a way a wrong number never does.
Tamagrow reacts to live matches in Valorant and League of Legends. The overlay was about a week of work. The rest of the project was this: deciding what counts as a kill.
What the stream actually looks like
Overwolf hands you two different things. Discrete events (kill, death, match_start) arrive through onNewEvents. Separately, a state blob comes through onInfoUpdates2 carrying values like your current kill count. You subscribe with setRequiredFeatures, which fails if the game isn't ready yet, so in practice every Overwolf app has a retry loop in it somewhere. Ours asks again every two seconds until the game answers or the player quits.
Past that the two titles don't agree on much. League's data comes off Riot's live client API, where events carry an incrementing ID, so you can tell a duplicate from a second kill. Valorant doesn't give you an ID. You get a ping that says kill, and no way to know whether it's the one you already saw.
Counters, not events
So we mostly stopped believing events. An event is a hint that something happened. The counter in the state blob is the closest thing to a fact. If kills go 3 → 5 between two updates, that's two kills, even if only one event showed up, and even if we only play one animation for it. Growth, feed credit, and anything the player keeps comes off the counter. Reactions fire on whichever arrives first, which is sometimes the event and sometimes the diff.
That felt like a nitpick until we watched the alternative run for a few nights. Events alone drift. Three kills in a scrappy round land as two, a fourth shows up twice, and by the end the pet has firm opinions about a match nobody played.
The reconnect bug
The build our first testers got had a bug we still bring up. If the game closed and came back — a crash, a client restart — Overwolf replayed state, and we treated the replay as new play. The pet re-earned the entire match. One tester got a level and an egg out of a game he lost 13-2, and wrote in to say he'd like to keep both.
The fix is two fields: a match id, and the highest counter we've already paid out against it, persisted, with anything not strictly greater dropped on the floor. Twenty minutes to write. Eleven days to find, and we only found it because one of us had gotten into the habit of playing with the raw event log open on a second monitor. That's less a debugging strategy than a habit that happened to pay.
Where we just refuse
Some cases we don't try to be clever about at all:
- Spectating. No credit, no reactions.
- Custom games and anything we can't identify as a real queue.
- Anything that arrives after match_end, including the late duplicate that shows up while the player is already in the menu.
- Anything past a per-match cap, which exists because we don't fully trust the stream and would rather cap than audit.
We undercount on purpose. A pet that misses a kill is boring for a round. A pet that celebrates something that didn't happen gets turned off, and we have never once seen someone turn it back on.
This part doesn't end
Sitting on top of someone else's live game means your inputs can change on a Tuesday with no notice. We keep around thirty recorded matches as JSON, dumped straight off the wire, and replay them against the state machine before anything ships. It's not an impressive test suite. It catches shape changes, which is most of what breaks. Twice it didn't, and a user told us before our own logs did.
The one we haven't solved: a small number of Valorant matches never emit an end at all. The player is back in the menu and we're still holding an open match. For now we close it after ninety seconds of silence and don't award the tail, which is a guess wearing a specific number. If you've dealt with this and have something better than a timeout, tell us. We'd take it.