We're seeing some capacity limits, so parts of the site may be slow or briefly unavailable while we work on an upgrade. Please try again later, or join our Discord for updates.
Discord
Dev blog
Match engine

Kills should equal deaths (and why that was hard)

Dota Captain
5 min read

In any match, the kills one team scores equal the deaths the other team takes. Making a simulation guarantee that, while still handing out assists fairly, was harder than it sounds.

Here is a truth so obvious it is easy to forget: the number of kills your team scores is exactly the number of deaths the enemy team suffers. A scoreboard that violates that is broken on its face. When you build fights out of several independent systems, keeping that identity true, while still crediting the right heroes, turned out to be a real piece of engineering.

Why a sim drifts out of balance

In my engine, kills, assists, and deaths are produced by systems that each have their own view of a fight. One decides that a team got a pickoff, another decides who was nearby to help, another tracks who is alive. Left unchecked, those views disagree: a kill gets credited with no matching death, or an assist lands on a hero who was farming a different part of the map and never touched the fight.

None of those slips is dramatic on its own, but together they make a scoreboard that does not add up, and an attentive player notices immediately.

The fix: fights are located events with a cast list

I made a fight a real, located event with a set of actual attendees. Assists now go only to heroes who were coherently present, with the unclaimed credit scaled sensibly rather than sprayed across the team. Kills are clamped to heroes who are alive and actually on the map, and a team that has been wiped cannot keep scoring. On top of all of that sits a battery of tests that enforces the identity directly: total kills must equal total deaths, every match, or the build fails.

That last part matters. It is one thing to fix the balance by hand and another to make it impossible to break again. Encoding the invariant as a test means every future change to the fight systems has to keep the scoreboard honest.

What it means for reading a fight

The player-facing payoff is that a teamfight in the sandbox now has a real place and a real cast. When you see a pickoff, it happened somewhere, to specific heroes, with specific helpers. That is exactly how you should read fights in your own games: not as a blur of numbers, but as located events where being present, or being absent, is the whole story.

A carry split-pushing across the map should not be getting assists for a fight at the other team's fountain. Making the simulation respect that is the same discipline that makes you a better player: know who was actually there.

Match engineDesign notes