← All Posts

EchoQuest v0.19.8: Network Protocol Optimization

Phase 4 of the performance plan targets the network layer. Last time we optimized the server’s AI tick loop; now we’re optimizing what that loop sends to each player. The result: 60–70% less bandwidth per player on maps with spread-out players, with zero changes to gameplay.

The Problem

EchoQuest broadcasts game events — enemy positions, damage numbers, deaths, spawns, boss abilities — to every player on the map. On a large forest map with 30 enemies and 5 players spread across it, each player was receiving updates about all 30 enemies, even ones on the opposite side of the map they couldn’t see. Same for combat events: a fight in the northwest generated damage numbers that traveled to a player quietly mining in the southeast.

Viewport-Scoped Broadcasts

The fix is straightforward: only send events to players who are close enough to see them. We already had a spatial grid in place for player-to-player position filtering (AOI). Now we use the same grid for everything:

  • Enemy positions — each player receives only the enemies within 800px of their character
  • Combat events — damage, misses, deaths, and spawns are scoped to 800px around the enemy
  • Boss events — telegraphs, phase changes, armor breaks, ability executions all scoped to the boss position
  • Minion events — position updates, spawns, despawns, and state changes scoped to the minion
  • Harvest events — node hit and depletion animations scoped to the harvesting player’s position

The broadcast radius is 800px — intentionally wider than the player’s viewport (roughly 640px wide at 1280px resolution) so that enemies are already “known” to the client before they scroll into view. This avoids pop-in.

emitToNearby()

The core addition is a new emitToNearby(mapKey, x, y, radius, event, data) function on the map presence service. It queries the player spatial grid at the given position, finds all players within the radius, and emits directly to their sockets. The existing emitToMap() is kept for events that genuinely need map-wide delivery — dungeon floor clears, node respawns, and the full enemy list sent when a player first joins a map.

What Stays Map-Wide

Not everything should be proximity-filtered. These events still go to all players on the map:

  • Dungeon events — floor clears, completions, and enemy count updates (the whole party needs them)
  • Node respawns — players need to know when distant nodes come back for minimap/UI updates
  • Full enemy list on join — new players need the complete state to render the map
  • Player join/leave — presence events are already small and infrequent

Impact

On a map with 30 enemies and 5 spread-out players, each player now receives ~10 nearby enemy updates instead of all 30 — roughly a 66% reduction in enemy position bandwidth. Combat events see similar savings. On smaller maps or in dungeons where all players are clustered together, the savings are minimal (everyone is in range of everything), but there’s no overhead cost either — the spatial grid query is O(nearby), not O(all).

Combined with v0.19.6’s WebSocket compression and v0.19.7’s AI tick optimizations, the server now does dramatically less work and sends dramatically less data per player. The next bundle in the performance plan will target client-side rendering optimizations.

Try the EchoQuest Demo — free in your browser. Full game coming to Steam.