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

Drafting against your friends, and the bugs I fought to get there

Dota Captain
6 min read

The honest story of shipping PvP drafting in Dota Captain, where two people draft against each other live, and the sync bugs I had to fight before it actually worked.

Early on Dota Captain could only ever be you against the machine. You drafted, it drafted back, and that was the whole world. But drafting is a social game. Half the fun of a captain's mind is reading a real person on the other side, watching them flinch when you first-pick the thing they wanted, baiting a ban. A few of you asked, in pretty much those words, whether you could just play against a friend. I wanted that too. What I did not fully understand when I started was how brutally unforgiving it is to get two people to draft against each other in real time, and how many small, fiddly gremlins stood between "it kind of works on my machine" and "it works for you and your friend two cities apart."

The gap: there was no room to meet in

Before any of the syncing headaches, there was a much dumber problem. There was simply no place for two people to find each other. You cannot draft against a friend if there is no lobby, no code to share, no list to browse. So the first real piece of PvP work was just making rooms exist. You can now create a Private room that is code-only, where nothing shows up anywhere and the only way in is the code you send your friend, or a Public room that gets listed for anyone to see.

Alongside that I built an open lobby browser on the Join side, so if you do not have a friend online right now you can browse the public waiting rooms and drop into one. That sounds simple written down. In practice it is the moment the app stops being a single-player toy and becomes something where two strangers' screens have to agree about the same room, at the same time, which is where all the trouble I am about to describe actually begins.

I will also admit to a genuinely silly bug in that first pass. On phones the waiting room did not scroll, so the room code, the captain names, and the matchup at the bottom just got clipped off the screen. People literally could not see the code they were supposed to share. It is fixed now, the room scrolls, but it is a good reminder that the unglamorous stuff breaks first.

Talking to the person across the table

A draft against a real opponent is a conversation even when nobody types a word. But it turns out people really do want to type words, so I added PvP match chat. You can message your opponent from the waiting room, all the way through the draft, and into the sim. Some of that is trash talk, some of it is 'gl hf', and honestly both are the point. A quiet lobby feels like playing against a wall.

While I was in that part of the code I also added hero search suggestions under the search bar, because watching people fail to find a hero by typing half a name and getting nothing back was painful. These are small quality touches, but multiplayer lives and dies on whether the room feels alive and responsive, so I did not want to skip them.

The timer that snapped to zero

Here is the first bug that really taught me humility. In a real draft the clock is sacred. You get your 20 or 30 seconds, and only when that runs out do you dip into reserve time. The whole tension of drafting under pressure comes from trusting that clock. Mine was lying. The PvP draft timer would snap straight to 0 before the reserve, so the pressure window you were supposed to have just evaporated. It made drafting feel broken and unfair, which for a drafting tool is about the worst thing it can feel.

I fixed it so the 20s and 30s timers now run fully first, the way they should, before any reserve time is touched. The transferable idea here, for anyone learning captain's mode, is that reserve time is not extra time you spend freely. It is an emergency buffer that only exists after your per-pick clock is gone. If you burn it early on easy picks you will be naked later when a hard decision actually needs the thought.

When your own clock betrays you

The timer had a second, sneakier problem, and this one is the kind of thing you never think about until it wrecks you. Not everyone's device clock is set correctly. Some run a little fast, some a little slow, and I had naively trusted each player's local time to decide when a turn expired. So if your device clock had drifted, your turn timer could expire instantly. You would get to your pick and it was already gone, through no fault of your own. That is a miserable way to lose a draft.

The fix was to stop trusting device time as the source of truth for expiry. Clock drift is one of those classic distributed-systems traps that feels obvious in hindsight and is completely invisible until a real person on a real machine hits it. I got it wrong first. That is the honest version. Multiplayer punishes every assumption you did not know you were making.

Sockets that quietly walked off

The last big one was the syncing itself. When your opponent picks or bans, that has to show up on your screen right away, or the whole illusion of drafting together falls apart. I lean on realtime updates to push those picks and bans across. The trouble is that sockets are not reliable in the way you want them to be. They drop, sometimes silently, and neither person gets told. One of you sees the pick, the other is staring at a frozen board wondering if the game is dead.

So I improved the PvP draft sync and, crucially, added a fallback for when the socket drops, so the draft can recover its state instead of just hanging. I want to be honest that 'improved' is the right word, not 'solved'. Realtime sync over the open internet is a moving target and I am sure there are edge cases I have not seen yet. If you hit one, telling me exactly what happened is genuinely the most useful thing you can do.

One small happy addition on top of all this: public PvP lobbies now announce on Discord with a join link, so an open room can actually find opponents instead of sitting empty. It is a modest feature, but it only makes sense once everything underneath it, the rooms, the chat, the timers, the sync, actually holds together.

What this cost, and what I learned

I am one person building this in spare time, and PvP is the part where that shows the most. Single-player bugs are between me and the code. Multiplayer bugs are between two people's clocks, two people's networks, and a server in the middle, and every one of those can betray you independently. I shipped several things that looked done and were not, and I only found out because they broke for real people. That is a little humbling to write, but it is the truth of how this got built.

If there is a takeaway beyond Dota, it is that syncing state between two humans in real time is deceptively hard, and 'it works on my machine' means almost nothing. And if there is a Dota takeaway, it is the one the timer bug forced me to respect: your clock is a resource, reserve time is a buffer and not a gift, and a draft is a conversation you are having with a real, unpredictable person on the other side. That is exactly why I wanted you to be able to play it against your friends. It is finally, mostly, working.

Release notesMultiplayer