Starvoxel: Making a two-lane co-op tower defense work on mobile

Two players, two lanes, one shared health pool. How Starvoxel: Defender keeps mobile co-op in sync without trusting either phone.

By Michal KomárGuest|Last updated: August 31, 2026|6 minutes read
community spotlightindie
Starvoxel: Making a two-lane co-op tower defense work on mobile
Starvoxel: Defender started with a simple co-op idea: two players defend separate lanes, but both lanes feed into one shared health pool. Each player controls only their own board. You cannot fix your partner's layout for them, yet their mistakes still affect you. That rule creates the cooperation. It also creates most of the technical problems. The game runs on Android and iOS, with Android currently receiving the newest builds first.

Why the server has to be in charge

Online co-op uses a Socket.IO connection to an authoritative game server. The clients do not decide whether a tower was placed, merged, evolved, or upgraded successfully. They send an intent, such as "summon on this cell," and the server validates the action before changing the match state. We chose that model because a merge tower defense has too many connected outcomes to trust independently on two phones. Currency, board occupancy, tower levels, enemy health, and shared base health all need to agree. If each client simulated the final result and tried to reconcile afterward, small differences would compound quickly.

From full snapshots to events

The first version solved consistency by sending full match snapshots frequently. It worked, but it was wasteful and made the battle feel less responsive. Our current protocol sends one full state when a player joins, then uses discrete gameplay events with lightweight periodic reconciliation. The server remains authoritative, but the renderer can animate events immediately instead of repeatedly rebuilding the whole scene. That made the match feel better. It also made a different class of bug much more visible.

The difficult bug was initialization

The most difficult bug was not combat. It was initialization. A player could authenticate, join a match, and receive the one-time bootstrap state before the PixiJS battle runtime had finished creating its geometry and HUD. The socket layer had technically delivered the message, but the consumer was not ready. For the second player, that bootstrap also contained the seat information needed to place their own lane at the bottom of the screen. If the message was lost, the match could be valid on the server while the local view was wrong. We fixed this at two boundaries. The socket service keeps the latest bootstrap and replays it when the battle subscribes. The runtime also keeps a pending snapshot if its asynchronous startup has not finished, then applies it after the board and HUD exist. The duplication is intentional: one buffer covers a late subscriber, while the other covers a runtime that has subscribed but is not yet ready to render.

One board, two perspectives

Perspective created a separate problem. Both players should feel that their own lane is the primary lane at the bottom of a portrait screen, but there is only one authoritative board. We did not create a second mirrored simulation. Instead, the runtime converts board cells to screen positions through a shared coordinate boundary. For the opposite seat, that mapping flips the board visually while input is converted back to the original server coordinates. Sprites stay upright. Only the board perspective changes. This matters beyond presentation. A tap on the bottom-left build cell must become the correct authoritative cell, regardless of which seat the player occupies. Keeping that logic inside the coordinate conversion functions prevents rendering, input, and simulation from developing separate ideas of where a cell is.

Shared health without shared boards

The shared-health design also needed restraint. Letting both players edit both boards would make a stronger player take over the match. Keeping boards separate preserves ownership, while the shared base means both lanes still matter. A stable player may need to spend resources defensively because the team cannot afford another leak from either side, but they cannot simply play their partner's turn.

Solo mode uses the same two lanes

Solo mode keeps the same two-lane structure and supplies an AI ally. That was a useful design decision, not only a fallback for matchmaking. Players learn the same shared-health rhythm before going online, and the UI does not need a separate mental model for solo and co-op.

Protect every transition

The main lesson was that mobile co-op reliability is less about one clever networking technique and more about protecting every transition: connection to authentication, authentication to matchmaking, matchmaking to bootstrap, bootstrap to rendering, and input back to authoritative state. A packet can arrive successfully and still be functionally lost if the next layer is not ready for it. For a small team, the practical answer was to keep the simulation authoritative, keep the client messages narrow, buffer the one irreplaceable startup state, and make perspective a rendering concern rather than a gameplay concern.

Play Starvoxel: Defender

Starvoxel: Defender is available free on Google Play.

About the author

Michal Komár is the developer of Starvoxel: Defender, a mobile cooperative merge tower defense game.

Share this article

Frequently asked questions

Newsletter

Stay in the Loop.

Subscribe to our newsletter to receive the latest news, updates, and special offers directly in your inbox. Don't miss out!