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
Release notes

Making a teamfight readable: who killed whom, and why

Dota Captain
6 min read

On making the match log legible: attributing every kill, explaining cross-map rotations, flagging turning points, and adding post-game graphs so the sim can actually teach you something.

At first the match feed made me wince. It read like a spreadsheet that had lost half its columns. A line would say that so-and-so and four others killed somebody, and then gold and experience would appear out of nowhere, and the game would just tumble forward. If you were losing, the feed could not tell you why. If you were winning, it could not tell you how. I would open a finished match, scroll through it, and realize I had built a simulator that could not explain itself. And a sim that cannot explain itself cannot teach you anything, which was the entire reason I started this project.

The spreadsheet problem

The oldest version of the feed was a flat list. Names, a vague clump of participants, numbers that had no home. A few of you pointed this out politely, and a few less politely, and both were right. When someone died, you could not see who did it. When gold swung, you could not tie it to anything that happened on the map. It was technically a record of a match and practically a wall of noise.

What bothered me most was that this is exactly backwards from how you actually learn Dota. You do not improve by knowing that a fight happened. You improve by knowing who committed first, who got picked off, who cleaned up, and what that cost the losing side. The information that matters is the attribution, and the attribution was the part I had thrown away.

So the work I want to talk about here is not the caster voice or the story writing. Those are their own thing. This is the plumbing underneath all of it: making the log legible, so that every death, every coin of gold, and every rotation has a visible cause. Readability first, because everything else is built on top of it.

Who killed whom

The core change was attributing kills properly. The feed now names who killed whom, the gold and experience each killer earned, and the assists that went with it. When one of your own heroes goes down, it says so plainly, something like Crystal Maiden falls to Earthshaker, instead of hiding your loss inside a crowd of unnamed participants. Combat beats show the killer and victim portraits side by side, so at a glance you can see the trade.

This sounds obvious, and it is, which is why it stung that it took me until the 0.6 work to get it right. But the transferable idea underneath it is real. In Dota, a kill is never just a kill. It is a gold and experience transfer from one hero to another, and often split across several heroes through assists. The last hit gets the bounty, the assists get a share, and the death costs the victim time and often a buyback. If a feed hides that transfer, it hides the actual economy of the game.

Seeing it written out changes how you read a match. You start noticing that your support keeps getting the killing blow on farmed cores, or that a fight you thought you won actually fed two assists worth of gold into the enemy carry. That is the kind of thing you can only learn if the log is honest about where the numbers came from.

Why the hero was even there

The next gap was cross-map kills. The old feed would report a hero killing someone nowhere near their lane and offer no explanation, which reads as a bug even when the underlying simulation is fine. Now the feed explains it. When a hero kills away from their lane, it tells you, for example that Storm Spirit rotates from mid and kills Tinker. The movement has a reason attached to it.

Each combat beat also leads with why it happened before it tells you who died. Was it a smoke gank, a Roshan fight, a pickoff, a high-ground push. That framing matters because those are the situations a real player is trying to recognize on the fly. A pickoff and a high-ground push are both kills on the scoreboard, but they mean completely different things about the state of the game.

This is where I have to be honest about limits. The engine is an approximation. It reasons about lanes and rotations and pressure, but it does not see the map the way a human pro does, and sometimes the reason it gives for a rotation is thinner than I would like. I would rather show you the model's actual reasoning, imperfect as it is, than paper over it with a confident sentence that is secretly hollow.

Finding the fights that mattered

Not every fight is worth studying. Most of them barely move the needle. So the feed now flags turning points, the fights that swung the game the most, and shows the gold lead before and after each one. Instead of scrolling through forty combat beats hoping to spot the important one, you get the two or three moments where the game actually changed hands.

The concept here is one of the most useful in all of Dota, and it is easy to miss when you are in the game yourself. Games are usually decided by a small number of high-leverage fights, not by the steady drip of last hits. A single lost teamfight before a Roshan attempt, or one botched high ground, can undo twenty minutes of careful play. Learning to recognize those swing moments, and to play more carefully around them, is most of what separating a good player from an average one looks like.

Alongside that I added a Match Story that reads like an esports recap: who was ahead early and why, which fights turned it, how it ended, and each side's standout heroes. It leans on all the attribution work above, because a recap is only as trustworthy as the events it is summarizing.

Graphs, and the small stuff

Finally there are the post-match graphs, which are the thing I quietly wanted from the start. Team net worth and experience advantage over time, plus per-hero net worth and level curves, with hero portraits sitting beside the chart the way Dota's own post-game screen shows them. A net worth graph tells you a story a feed never can, the long flat stretch where nobody could break through, the sudden cliff where one fight cracked the game open.

I also went back and cleaned up the writing itself, which is less glamorous but mattered more than I expected. I dropped filler clauses and repeated phrases, and fixed hero and team names that were coming out mis-capitalised. When a name is wrong or a sentence repeats, you stop trusting the feed, and once trust is gone none of the good attribution work lands. Worth mentioning too that this all built on an earlier step, back in 0.3.1, when net worth first became visible during the sim and you could scroll the log while it was still running.

None of this makes the simulation correct. It makes it legible, which is a different and more modest claim. The engine still gets things wrong, and I still find matches where the story it tells does not quite hold together. But a readable feed is the thing that lets me, and you, catch those mistakes in the first place. You cannot fix what you cannot read, and for a long stretch of this project I genuinely could not read my own match logs. Now I mostly can, and that alone has taught me more than I expected.

Release notesMatch engine