RE:NODE

Guides12 min read

Arma 3 headless clients for AI offloading

How Arma 3 headless clients work: locality, server.cfg settings, the mission slot, moving AI with setGroupOwner or ACE, several HCs, and proving it helped.

0 readers

A headless client (HC) is a second copy of the Arma 3 server binary started with -client, which joins your server as an invisible player and runs the AI the mission gives it. It helps only when three things are true: AI is what is eating your server's frame time, the mission puts a headless client slot in the lobby, and something - mission code, ACE's headless component or another script - actually transfers AI groups to it. Get those right and a persistent mission with hundreds of AI holds a playable server FPS deep into a session; get any of them wrong and you have spent memory and a CPU share on a process that sits in a slot doing nothing.

The server-side switches (headlessClients[], localClient[]) are covered in the Arma 3 server.cfg guide. This post is about everything around them: why locality is the whole story, how to put the slot in a mission, the three ways AI gets moved, running more than one HC, and how to prove it worked.

Locality: why a headless client can help at all#

Every object in an Arma 3 multiplayer session is local to exactly one machine. That machine runs its simulation - for AI, that means pathfinding, target selection, knowledge updates, firing decisions - and broadcasts the results to everyone else. Every other machine holds a remote copy that it draws and interpolates but does not think for.

By default, AI placed in the editor or spawned by server scripts is local to the server. So the server's main thread does the thinking for every AI unit in the mission on top of everything else it does, and Arma's simulation is largely single-threaded. When the AI count rises, server FPS falls, and when server FPS falls everything gets worse at once: AI react late, vehicles stutter, players desync.

A headless client is just another machine for AI to be local to. It is a full Arma client with no rendering and no sound, so all of its frame time goes to simulating the units it owns. Move a hundred AI to it and those hundred units stop costing the server anything but network updates.

state updatesowns its groupsowns its groupsPlayersown their unitsHC1AI groups, eastHC2AI groups, westDedicated serverobjectives, scripts
Where AI is simulated with two headless clients

Three consequences fall out of this model and explain most HC problems:

  • AI does not move by itself. A unit is local where it was created until something calls setGroupOwner on it. Connecting an HC changes nothing about existing AI.
  • Locality is per group. You move whole groups, and the group's units, waypoints and vehicles they drive go with it.
  • Scripts run where they run. Code on the server that commands a unit which has moved to an HC may stop working, because many commands only take effect where the unit is local.

Is it worth it? Measure before you build#

A headless client is not free. It is a second Arma process with the same mod list, so it needs its own memory - typically 2 to 4 GB with a moderate mod set - and its own CPU time. If your server's problem is not AI, it gives you nothing.

Measure first. Log in as admin and run #monitor 5, which prints server FPS and related numbers into chat every five seconds. Then compare:

What you seeWhat it meansDoes an HC help?
Server FPS falls as AI count rises, recovers when AI diesAI is the costYes
Server FPS falls slowly over hours with stable AIScript leak or object build-upNo, fix the mission
Server FPS fine, players complain of lagNetwork or client performanceNo
Server FPS bad from the first minute with few AIMod or script cost at startNo, profile the mission
Memory near the limit, FPS fineMemory, not AINo, and an HC makes memory worse

The general version of this is in what tick rate actually means and CPU vs RAM for game servers. On a panel with a CPU graph, the tell-tale picture is the server process pinned at its CPU allowance while memory is flat and the mission is spawning AI.

The server side, briefly#

Two arrays in server.cfg make the server treat the HC properly:

server.cfg
headlessClients[] = {"127.0.0.1"};localClient[] = {"127.0.0.1"};

headlessClients[] lists the addresses allowed to take headless client slots. localClient[] marks addresses as local, which removes the bandwidth limits meant for players on home connections - without it the HC is throttled like a player and AI updates queue up. If the HC runs on another machine, both arrays need that machine's address instead of the loopback.

The HC is started with the same binary:

bash
$ ./arma3server_x64 -client -connect=127.0.0.1 -port=2302 \    -password="joinpassword" -profiles=hc1 -name=hc1 \    -mod="@cba_a3;@ace" -world=empty -nosound

What each part does:

  • -connect and -port point at the server's game port.
  • -password is the join password, if the server has one. Not the admin password.
  • -mod must match the server's client mod list exactly. -serverMod items are not needed.
  • -profiles and -name give the HC its own profile folder and log, so its .rpt file does not mix with the server's.

The HC connects, appears in the lobby, and takes a headless client slot if the mission has one. If it has none, it sits at the slot screen.

Putting the slot in the mission#

This is the step most "my HC does nothing" reports come down to. The mission must contain a headless client entity, and that entity must be playable.

In the Eden editor, the headless client is a logic entity named Headless Client. Place one per HC you intend to run, and for each:

  1. Give it a variable name - HC1, HC2, HC3. Scripts find the HC by this name.
  2. Tick Playable. A non-playable HC entity is never offered as a slot.
  3. Save and export the mission to multiplayer, so the .pbo in mpmissions contains it.

The HC takes the slot automatically when it connects; nobody has to assign it. In scripts, the variable name then refers to the HC's object, and owner HC1 (evaluated on the server) returns its client ID - the number setGroupOwner needs.

Large community missions such as Antistasi and Liberation ship with headless client slots already placed. A Workshop mission that does not mention headless clients in its description almost certainly does not.

Three ways to move AI onto a headless client#

Once the slot exists, something has to transfer AI. There are three approaches, and they can be mixed.

1. Spawn the AI on the HC in the first place

The cleanest approach for missions you write: run the spawning code on the HC itself, so the AI is local there from birth and never transfers.

init.sqf
// true only on a headless clientif (!hasInterface && !isServer) then {    [] execVM "scripts\spawnPatrols.sqf";};// fall back to the server if no HC is connectedif (isServer && {isNil "HC1" || {isNull HC1}}) then {    [] execVM "scripts\spawnPatrols.sqf";};

hasInterface is false on dedicated servers and headless clients; isServer is true only on the server. That combination identifies an HC. The fallback matters: a mission that only spawns AI on the HC has no enemies on a night the HC fails to connect.

2. Transfer groups from the server with setGroupOwner

For AI placed in the editor or spawned by existing server scripts, transfer it. setGroupOwner runs on the server only, takes a group and a client ID, and moves the group and its units.

initServer.sqf
[] spawn {    // wait until the HC has connected and has a client ID    waitUntil { sleep 5; !isNil "HC1" && {!isNull HC1} && {owner HC1 > 2} };    sleep 30; // let mission init finish first    private _hc = owner HC1;    {        private _grp = _x;        if (local _grp            && {({isPlayer _x} count units _grp) == 0}            && {!(_grp getVariable ["keepOnServer", false])}) then {            _grp setGroupOwner _hc;        };    } forEach allGroups;};

The checks in that loop are the important part. Only transfer groups local to the server, never a group containing a player, and give yourself a way to exclude groups that must stay put - scripted set pieces, units with server-side logic attached. The delay lets other initialisation finish first; transferring a group mid-setup is a classic source of half-configured AI.

3. Let a mod do it

ACE3 includes a headless component that transfers AI groups to connected headless clients automatically and rebalances when HCs join or leave. It is configured from the CBA addon settings: enable it, set a transfer delay, and decide what happens when an HC disconnects. It has a per-group and per-unit exclusion variable for things that must stay on the server; check the ACE documentation for its exact name in your version. For a modded group already running ACE, this is the least effort route and works with editor-placed and Zeus-spawned AI.

Older missions use Werthles' Headless Module, an editor module that does the same job. Pick one automatic mechanism per mission; two systems moving the same groups fight each other.

What breaks when AI changes owner#

Moving locality is not invisible to scripts. The common problems:

  • Local-effect commands. Some commands only work where the unit is local. A server loop that calls doMove, disableAI or setBehaviour on a group that has moved to the HC may silently do nothing. Run such code where the group lives, for example with remoteExec targeted at groupOwner, or run the whole behaviour script on the HC.
  • Event handlers. Handlers added with addEventHandler are local to the machine that added them, and several fire only where the unit is local. A server-side Killed or Hit handler on a transferred unit may stop firing. Use the multiplayer variants (addMPEventHandler with MPKilled, MPHit) or add handlers on the owning machine.
  • Settings applied before transfer. Some AI state set on the server before the move does not survive it. Apply skill, disableAI and behaviour after the transfer, on the new owner.
  • Dead bodies and cleanup. Garbage collection still runs on the server. That is fine, but a cleanup script that checks local before deleting will skip units owned by the HC.

When a mission misbehaves only with an HC connected, these four are the checklist.

Several headless clients, and dynamic simulation#

One HC is a second thread. Several HCs are several threads, and missions with very large AI counts distribute groups across HC1, HC2 and HC3 - by area, by side, or round-robin. Each needs its own playable slot, its own process with its own -profiles and -name, and its own share of memory and CPU.

More HCs only help if there are cores to run them on. On your own hardware that is a question of physical cores. On a hosted server, CPU is usually a hard allowance - on RE:NODE each server is one container throttled to the share you bought - so a headless client running inside the same allowance competes with the server for it. It still helps when the server is stuck on one thread with spare allowance unused, which is the common case, but it cannot create CPU that is not in the plan. A second HC in the same allowance rarely beats one.

The complement to headless clients is dynamic simulation, built into the game since 1.74. It freezes AI groups that are far from any player and wakes them when someone approaches:

initServer.sqf
enableDynamicSimulationSystem true;"Group" setDynamicSimulationDistance 1500;{ _x enableDynamicSimulation true } forEach allGroups;

A frozen group costs almost nothing wherever it is local. On a large terrain with AI garrisons everywhere, dynamic simulation often removes more load than a headless client does, and the two together are the standard setup for big persistent missions. Enable it per group after any transfer.

Proving it worked#

Do not trust that it works because the HC is in the lobby. Check three things.

  1. Groups actually moved. In the debug console with server execution, count groups per owner: {groupOwner _x == owner HC1} count allGroups. Zero means nothing transferred.
  2. The HC is coping. Have the HC log its own frame rate and group count into its .rpt:
init.sqf
if (!hasInterface && !isServer) then {    [] spawn {        while {true} do {            diag_log format ["HC fps %1, local groups %2",                diag_fps, {local _x} count allGroups];            sleep 60;        };    };};
  1. The server improved. Run #monitor 5 at the same point in the mission with and without the HC. If server FPS did not move, the bottleneck was not AI, and the HC can go.

An HC whose own FPS has collapsed is now the bottleneck instead of the server: the AI it owns reacts as badly as before. That is the point to split across two HCs, cut AI count, or lean harder on dynamic simulation. Keep the HC's .rpt logs alongside the server's; logs worth keeping covers how long and why.

Troubleshooting#

The HC never appears in the lobby. Its address is not in headlessClients[], the mod list does not match, or it is pointing at the wrong port. Read the HC's own .rpt - it states why the connection failed.

The HC sits at slot selection. The mission has no playable headless client entity, or all HC slots are already taken.

The HC connects but owns nothing. Nothing transfers AI. Add a setGroupOwner loop, enable ACE's headless component, or spawn on the HC.

AI on the HC stands still. Waypoints or behaviour scripts are running on the server and no longer apply. Move the script to the owner.

Server-side kill tracking broke. Event handlers are local. Use MPKilled or add the handler on the HC.

Everything got worse. The HC pushed the server towards its memory limit, or it is sharing a CPU allowance that was already full. Check both graphs and remove it if so.

The HC disconnects and the enemies vanish. When an HC leaves, its AI is handed back to the server. If the server then struggles, that is expected; if the AI is deleted, a script is cleaning up the HC's groups on disconnect.

FAQ#

Do I need a separate Arma 3 licence for a headless client?

No. The headless client is the free dedicated server binary started in client mode, downloaded the same way as the server. It needs the same mods as the server and nothing else.

How many headless clients should I run?

One, until you have measured that it is overloaded. Add a second only when the first HC's own FPS drops under load and there is CPU available to run another process. Most groups never need more than one.

Will a headless client fix desync?

Only if the desync is caused by the server running out of frame time because of AI. Network desync from a player's connection, or a server short of memory, is unaffected.

Does the mission have to be written for a headless client?

It needs a playable headless client slot. Moving the AI can be done by the mission, by ACE's headless component or by another script, so a mission without HC code can still benefit if you add a slot and an automatic balancer.

Can the headless client run on a different machine?

Yes. Put that machine's address in headlessClients[] and localClient[], open the path between them, and expect some extra latency on AI updates. On the same machine is simpler and usually faster.

What happens to AI when the headless client crashes?

Its groups transfer back to the server automatically. The mission continues, with the server carrying the load it was carrying before. A good mission or balancer re-transfers them when the HC reconnects.


Comments

Completely anonymous: no account, no email, no cookie. We store the name you type, the text and the time - nothing else. Links are limited and markup is not rendered.

0/2000