How to use a single process to implement multiple headless clients for stress-testing a DS server

Hey everyone,

If you have ever tried to load-test an Unreal dedicated server at realistic player counts, you probably hit the same wall: launching one real client process per simulated player quickly turns into a hardware provisioning problem. External protocol bots avoid that, but then you are no longer measuring your actual game’s handshake, replication, and RPC paths.

I built a plugin called RealNetRobot to take a different approach: it drives N simulated players from inside a single headless client process. Each robot connects to a dedicated server named on the command line, goes through the real login flow, and behaves like a real player connection.

The important part is that robots are not server-side AI bots. Every robot owns its own UWorld, UGameInstance, ULocalPlayer, and UNetDriver, and goes through the same handshake, replication, and RPC paths a real client does. That is exactly where the server cost you are trying to measure lives.

What this buys you:

  • N headless clients per process, each with a real connection to the DS
  • Per-robot world isolation — robots do not share state
  • Much lower memory cost — assets load once per process instead of once per client, so a fleet costs roughly one client + N × (world + actors) instead of N × one client
  • Staggered joins, so the fleet ramps instead of bursting
  • Command-line, console, Blueprint/C++ control, with the command path working in Shipping
  • Inert on install — nothing happens until you ask for robots
  • Ships with a demo map and a task-oriented user guide

A few honest limits, so nobody buys the wrong tool:

  1. You drive the robots yourself. There is no behaviour framework and no base class to inherit. The plugin exposes IsRobotWorld and GetRobotIndex, and you hang your own driver off them. An undriven fleet measures a floor, not a load.
  2. World Partition maps with runtime cells need the optional engine class installed and must run from a packaged build.
  3. Identity travels in the connect URL, not through UniqueNetId. Your login path has to read identity from the URL, or robots past the first will not log in distinctly.
  4. No metrics export — results come out as log lines plus a fleet summary call.

If you are testing DS performance and want to push player counts without turning the test into a machine-count problem, you can find the plugin here:

Happy to answer questions about the single-process approach or the trade-offs versus external protocol bots.