Systems and the Coordinator
A system is a named processing unit: a stored entity query plus scheduling metadata. The server never runs your game logic - systems are workers you write, and the per-index coordinator drives them: it ticks on a timer, hands each connected system the entities matching its query, and applies the mutations the system sends back.
Registering systems
snug ecs system register -x game1 --name MovementSystem \
--has-all Position,Velocity --priority 100 --timeout-ms 5000 \
--include-resources GameConfig
snug ecs system list -x game1
snug ecs system get -x game1 --name MovementSystem
snug ecs system update -x game1 --name MovementSystem --priority 10
snug ecs system delete -x game1 --name MovementSystem --forceRegistering a name that already exists fails with 409 system_exists.
--include-resources names resources whose current
values are bundled into every tick payload the system receives.
Execution order
Lower priority values run first (the CLI help says so, and it is verified: priorities 50 and 100 ordered the 50 first; updating the 100 to 10 moved it to the front):
snug --output json ecs system execution-order -x game1["MovementSystem", "RegenSystem"]The order is a topological sort over each system's runs_after /
runs_before dependencies, with ascending priority (then name) breaking
ties. The dependency fields exist only in the HTTP register request - the
CLI exposes priority alone. Disabled systems are excluded from the result.
Enabled, paused, and manual runs
Two independent off-switches, both verified to skip the system during coordinator ticks:
enable/disableflips theenabledflag stored on the system definition - the durable configuration switch.pause/resumeflips a runtime flag stored next to the system's stats - for temporarily suspending a live system.
snug ecs system disable -x game1 --name RegenSystem
snug ecs system enable -x game1 --name RegenSystem
snug ecs system pause -x game1 --name MovementSystem
snug ecs system resume -x game1 --name MovementSystemexecute triggers a single server-side run outside the tick loop - and it
runs even if the system is disabled. With --dry-run it resolves the
matching entities without recording the run:
snug --output json ecs system execute -x game1 --name MovementSystem --dry-run{
"name": "MovementSystem",
"tick": 0,
"entity_count": 1,
"execution_time_ms": 2,
"mutations_applied": 0,
"dry_run": true
}system stats accumulates per-run counters (execution_count,
avg_entity_count, avg_execution_time_ms, error_count); dry runs are
not counted.
The coordinator
One coordinator per index, running a tick loop:
snug ecs coordinator start -x game1 --tick-rate 100 # milliseconds
snug --output json ecs coordinator status -x game1
snug ecs coordinator stop -x game1{
"index": "game1",
"running": true,
"tick": 11,
"tick_rate_ms": 100,
"last_tick_at": "2026-08-28T04:36:05.495462+00:00",
"connected_systems": 0
}tick and last_tick_at advance while running (captured one second after
a 100 ms start - eleven ticks in). connected_systems counts workers
currently attached over WebSocket.
The worker loop
Workers attach to GET /api/v1/ecs/{index}/coordinator/ws, authenticating
with a Bearer header or ?token=. Verified handshake behavior: the upgrade
succeeds (101) only while the coordinator is running - a stopped
coordinator rejects with 400, a missing token with 401. Once attached, a
worker registers under its system name, receives a payload each tick with
the matching entities and any include_resources values, and replies with
mutations for that tick. The message schemas are in the
Coordinator API reference.
Submitting mutations over REST
Mutations can also be submitted outside the WebSocket loop - useful for
batch fix-ups and offline workers. Each mutation sets and/or removes
components on one entity, optionally guarded by expected_version:
snug --output json ecs coordinator mutations -x game1 \
--mutations '[{"entity_id":"player1","set_components":{"Health":{"current":50,"max":100}}}]'{
"results": [
{ "entity_id": "player1", "success": true, "error": null, "new_version": 5 }
],
"succeeded": 1,
"failed": 0
}Results are per entity: a stale expected_version fails only that entry
("error": "Version conflict: expected 1, found 7") while the rest apply.
The endpoint works whether or not the coordinator is running.
Caution: unlike entity create and component set, the mutation path
does not validate component data against schemas - a payload that violates
its schema is stored as-is (reproduced live). Validate in the worker.