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

Getting every hero into the right lane

Dota Captain
6 min read

For a while my simulation put heroes in lanes they would never actually play, and that quietly poisoned everything downstream. Here is how I fixed it with a curated position map, and why position is the foundation the whole draft reads from.

I kept staring at match reports where Io was grinding out the offlane and Crystal Maiden was flagged as a jungler, and every time I saw it I felt a little sick. Not because a solo project having a bug is surprising, but because this particular bug was lying to people. If the tool tells you a hero is playing a position they would never actually play, then nothing that follows can be trusted. A few of you reported it, politely and not so politely, and you were completely right to. This is the story of how heroes ended up in the wrong lanes, why that broke more than it looked like it should, and how I finally got them home.

The honest part

Here is the honest version. For a long stretch I trusted Dota's capability tags to tell me what a hero could do, and then I let the sim slot heroes into positions based on those tags. It felt reasonable. The tags describe whether a hero is durable, or an initiator, or capable of jungling, so surely they describe where a hero belongs. They do not. They describe what a hero is capable of in the abstract, not what that hero actually plays in a real game.

The result was heroes getting dumped into whatever slot the tags technically allowed. Io, one of the most iconic pos 5 supports in the game, has enough raw stat scaling that the capability data did not scream support, so it got shoved into offlane. Spectre, a hard carry, could land in support. Crystal Maiden carried a stale jungler flag. Every one of these is the kind of thing a Dota player spots in half a second, and it makes the whole simulation feel like it was built by someone who had never watched a game. That stung, because I have watched a lot of games.

Why one wrong lane breaks everything

The reason this mattered so much more than a cosmetic label is that position is not a decoration in Dota, it is the foundation. Almost everything else in a match reads off it. Farm priority is decided by role, because your pos 1 gets the lane creeps and the safe jungle while your pos 5 gets almost nothing and is fine with that. Lane matchups are role against role. Fight power depends on who is farmed and who is not, which depends on who was allowed to farm, which depends on position. Even the match story, the narration that tries to explain what happened, is built on top of who was doing what job.

So when the sim put Io in the offlane, it did not just print a wrong word. It handed Io a core's farm priority, matched it against the enemy offlaner, weighted its fight contribution like a real position 3, and then wrote a story around all of that. One wrong assignment quietly corrupted a whole chain of downstream calculations. This is the transferable Dota idea underneath the whole fix. Roles are the frame the entire draft is read through, and if the frame is wrong, every read inside it is wrong too, no matter how careful the rest of the math is.

Trading clever for correct

The fix was less clever than the thing it replaced, and that is exactly why it works. Instead of inferring position from generic capability tags, I now assign each hero their real position from a curated map, ordered most-played first. If a hero is overwhelmingly played as a support, that is what the sim treats them as, full stop, rather than trying to reason its way there from a list of abstract traits.

With that in place, Io and Spectre stopped getting dumped into offlane or support. Correct roles now feed fight power, farm priority, lane matchups, and the match story, so the simulation finally reads true from the position layer up. I also refreshed the underlying role data to match official Dota, which is how Crystal Maiden stopped being a jungler and Io dropped its stale Durable, Initiator, and Jungler tags. There was something satisfying and slightly humbling about deleting a heap of inference code and replacing it with a plain, honest map of what heroes actually do.

The enemy draft had the same disease

This was not only a display problem in your own lineup, it lived in how the AI drafted against you too. Earlier on, the enemy draft logic would fill roles greedily and then dump the last pick into whatever slot happened to be open, regardless of whether that hero suited it. Reserved draft tags could even block a genuinely better core from being picked. So you would face lineups that no real captain would ever assemble.

I reworked that so each role goes to the best-fitting hero instead of the leftover one, and the reserved tags stopped vetoing stronger picks. Later I went further and cleaned the pools themselves. The carry pool had offlaners and supports sitting in it, heroes like Earthshaker, Clockwerk, Oracle, and Phoenix, and the support pool was full of cores and initiators like Centaur, Magnus, Tusk, and Spirit Breaker. Pulling those out meant the AI captain finally picks carries from heroes actually played as carries and supports from actual supports.

Flex heroes, and letting the draft breathe

Real Dota is not tidy, though, and I did not want to pretend it was. Some heroes genuinely flex, and forcing them into a single box would be its own kind of lie. So I added flex support tags for Alchemist, Wraith King, Naga Siren, and Venomancer, so they can show up as support options where that is a real thing people do, without polluting the core pools they also belong to.

I also wanted enemy drafts to stop feeling robotic. The AI captain now picks carry and support from heroes tagged for those roles, and for each slot it rolls among the top five scorers rather than always grabbing the single highest number. That small bit of variety means you are not laning against the same optimal hero every single game, which is closer to how real matchmaking feels, where the theoretically best pick and the pick you actually see are often not the same hero.

Where this still falls short

I want to be clear that this fixed a foundation, not the whole building. Getting positions right removes a class of nonsense that was actively misleading people, but the match engine underneath is still an approximation, and it always will be. A curated position map captures how heroes are usually played, not the pocket picks, the role swaps, and the galaxy-brain drafts that make Dota Dota. There will be lineups where my map is too rigid to describe what a clever captain would really do.

So if you still catch the sim putting a hero somewhere strange, please keep telling me. Almost every improvement here started as someone pointing at a match and saying that is not right, and they were right. I am one person doing this in my spare time because I love the game, and honestly the fastest way I learn where the tool is wrong is when a player who knows their hero sees something I missed. Getting every hero into the right lane was the boring, necessary groundwork. Now the more interesting mistakes are the ones I get to find next.

Release notesMatch engine