SwarmSim is a TypeScript-first multi-drone coordination simulator focused on independent drone processes, real message-bus communication, network constraints, and live visualization. Includes simulators written in Typescript and Rust.
packages/shared: shared domain types, Redis message contracts, and channel helpers.packages/kinematics: pure movement math shared by agents and future behaviors.packages/behaviors: pure deterministic swarm behavior helpers, starting with formation data types and ENU leader-relative formation math.packages/agent: drone process runtime, command handling, telemetry publishing, state broadcast publishing, inbox delivery handling, and inbox-derived neighbor caching.packages/bus: Redis connection helpers for telemetry, broadcast, command, inbox, and subscriber flows.packages/cli: CLI for operator commands.packages/netsim: network simulation service that subscribes to drone telemetry, maintains latest-position state, and forwards swarm broadcasts to tracked drone inbox channels.crates/netsim-rs: same network simulation as packages/netsim.packages/viz-server: WebSocket bridge from Redis telemetry to browser clients.packages/viz: React/Vite canvas visualization frontend.
pnpm build: build all packages.pnpm typecheck: typecheck all packages.pnpm dev:agent: run the agent placeholder.pnpm dev:cli: run the CLI placeholder.pnpm dev:netsim: run the network simulation service telemetry cache and broadcast forwarder.pnpm dev:viz-server: run the Redis-to-WebSocket visualization bridge.pnpm dev:viz: run the React visualization frontend.pnpm redis: start Redis with Docker Compose.
docker compose -f infra/docker-compose.yml up --buildThen open http://localhost:5173. Compose starts Redis, one drone-1 agent,
the Redis-to-WebSocket visualization server on port 8080, and the Vite
frontend on port 5173.
With the demo running, send a goto command from the host:
pnpm dev:cli -- send drone-1 goto 80 40Start the Rust netsim Docker stack with Redis and two TypeScript agents:
docker compose -f infra/docker-compose.rust-netsim.yml up --buildThis keeps the TypeScript netsim demos unchanged. The Rust service runs with
REDIS_URL=redis://redis:6379 inside Compose. Optional netsim settings can be
passed through from the host, for example:
NETSIM_LATENCY_MS=500 docker compose -f infra/docker-compose.rust-netsim.yml up --buildStart the formation demo with Redis, netsim, one leader, four followers, the visualization server, and the browser frontend:
pnpm demo:formationThen open http://localhost:5173. The formation demo starts all drones
stationary. Followers use FORMATION_SPEED_METERS_PER_SECOND=3 for faster
recording; set the leader command speed before sending the waypoint:
pnpm dev:cli -- send drone-1 setSpeed 3
pnpm dev:cli -- send drone-1 goto 80 50Start the range-limited formation variant to show that followers only react to messages netsim actually delivers:
pnpm demo:formation:rangeTo run the same range-limited formation demo with Rust netsim instead of the TypeScript netsim service:
docker compose -f infra/docker-compose.formation-range-rust-netsim.yml up --buildThis uses NETSIM_BROADCAST_RANGE_METERS=25. drone-5 starts far away at
(10, 90), outside leader broadcast range, so it should remain idle while the
closer followers react to the leader. The demo sends its own command sequence
after startup: setSpeed 3, goto 80 50, wait 3 seconds, then goto 0 50.
To replay it manually against an already-running stack:
pnpm dev:cli -- send drone-1 setSpeed 3
pnpm dev:cli -- send drone-1 goto 80 50
sleep 3
pnpm dev:cli -- send drone-1 goto 0 50pnpm dev:agent reads these optional environment variables:
REDIS_URL: Redis connection URL. Defaults toredis://localhost:6379.DRONE_ID: drone ID in thedrone-<number>format. Defaults todrone-1.TICK_DURATION_MS: runtime tick duration. Defaults to100.WORLD_WIDTH/WORLD_HEIGHT: world bounds in meters. Defaults to100by100.INITIAL_X/INITIAL_Y: initial position in meters. Defaults to10,20.INITIAL_VX/INITIAL_VY: initial velocity in meters per second. Defaults to1,0.INITIAL_HEADING_RADIANS: initial heading. Defaults to0.FORMATION_ROLE: optional formation mode. Set tofollowerto enable local formation steering,leaderto identify a command-driven leader, or leave unset/noneto disable formation runtime behavior.FORMATION_LEADER_ID: leader drone ID used by followers when deriving leader pose from received inbox broadcasts. Defaults todrone-1.FORMATION_SLOT_OFFSET_X/FORMATION_SLOT_OFFSET_Y: optional leader-relative slot offset for this agent, in meters. Used whenFORMATION_SLOT_MAP_JSONis unset.FORMATION_SLOT_MAP_JSON: optional JSON object mapping drone IDs to slot offsets, for example{"drone-2":{"x":-10,"y":-5}}. Defaults to a small V-shaped map fordrone-2throughdrone-5.FORMATION_SEPARATION_RADIUS_METERS: local crowding radius in meters. Active neighbors inside this radius push the follower away. Defaults to6, which is below the built-in 10 m first-row slot spacing but leaves room to avoid obvious overlap.FORMATION_SPEED_METERS_PER_SECOND: follower movement speed after the combined formation vector is normalized. Defaults to1, matching the single-drone demo speed and keeping convergence easy to observe.FORMATION_WEIGHT_SEPARATION: strength of local repulsion from nearby active neighbors. Defaults to1.2, high enough to resist crowding without overpowering a valid slot target.FORMATION_WEIGHT_ALIGNMENT: strength of steering toward the average active-neighbor heading. Defaults to0for the current leader-slot mode so followers settle at their assigned slots. Raise this only when intentionally experimenting with heading matching.FORMATION_WEIGHT_COHESION: strength of steering toward the active-neighbor centroid. Defaults to0for the current leader-slot mode because cohesion can keep pulling a correctly placed follower away from its slot.FORMATION_WEIGHT_LEADER_SLOT_ATTRACTION: strength of steering toward this follower's leader-relative slot. Defaults to2, making slot convergence the dominant formation-control term.
Example:
DRONE_ID=drone-2 INITIAL_X=30 INITIAL_Y=40 INITIAL_VX=0 INITIAL_VY=0 pnpm dev:agentThe current formation mode is opt-in. pnpm demo:formation starts the same
5-drone setup in Docker. To run the pieces manually instead, use these commands
in separate terminals from the repository root for a repeatable local 2-5 drone
V-shape demo.
Start Redis:
pnpm redisStart netsim so drone broadcasts are forwarded to inboxes:
pnpm dev:netsimStart the leader:
DRONE_ID=drone-1 INITIAL_X=50 INITIAL_Y=50 INITIAL_VX=0 INITIAL_VY=0 FORMATION_ROLE=leader pnpm dev:agentStart one or more followers. The default slot map supports drone-2 through
drone-5:
DRONE_ID=drone-2 INITIAL_X=35 INITIAL_Y=45 INITIAL_VX=0 INITIAL_VY=0 FORMATION_ROLE=follower pnpm dev:agent
DRONE_ID=drone-3 INITIAL_X=35 INITIAL_Y=55 INITIAL_VX=0 INITIAL_VY=0 FORMATION_ROLE=follower pnpm dev:agent
DRONE_ID=drone-4 INITIAL_X=25 INITIAL_Y=40 INITIAL_VX=0 INITIAL_VY=0 FORMATION_ROLE=follower pnpm dev:agent
DRONE_ID=drone-5 INITIAL_X=25 INITIAL_Y=60 INITIAL_VX=0 INITIAL_VY=0 FORMATION_ROLE=follower pnpm dev:agentSend a waypoint to the leader:
pnpm dev:cli -- send drone-1 goto 80 50Expected behavior: the leader moves under the normal goto command path while
followers use only forwarded broadcasts in their inbox caches to chase their
assigned leader-relative slots. With the default heading convention, the initial
east-facing V slots are behind the leader at (-10, -5), (-10, 5),
(-20, -10), and (-20, 10) meters. Followers stop if they have no active
leader broadcast or no configured slot.
Troubleshooting:
- Followers that do not move usually are not receiving forwarded broadcasts.
Confirm
pnpm dev:netsimis running and Redis is shared by all terminals. - In the range-limited Docker demo,
drone-5is expected not to move because it starts outside the configured 25 m broadcast range. - Duplicate agent IDs are rejected by the Redis identity lease. Stop the old
process or choose a different
DRONE_ID.
pnpm dev:netsim reads network simulation settings from built-in defaults, an
optional JSON config file, and explicit environment variables. Precedence is:
built-in defaults, then file values, then environment variables.
REDIS_URL: Redis connection URL. Defaults toredis://localhost:6379.NETSIM_CONFIG_PATH: optional path to a netsim JSON config file. When absent, netsim usesnetsim.config.jsonfrom the current working directory if that file exists; otherwise it runs from defaults and environment variables only. When set, the exact file must exist and contain valid JSON.NETSIM_BROADCAST_RANGE_METERS: maximum Euclidean ENU distance in meters between sender and recipient latest telemetry positions for broadcast delivery. Defaults to unbounded range.NETSIM_PACKET_LOSS_RATE: per-recipient probability from0through1that an otherwise eligible forwarded broadcast delivery is dropped. Defaults to0.NETSIM_LATENCY_MS: delay in milliseconds before publishing each eligible, non-dropped forwarded inbox delivery. Defaults to0.NETSIM_DEBUG_TELEMETRY: set to1to log a periodic count of tracked drones.NETSIM_DEBUG_TELEMETRY_INTERVAL_MS: telemetry summary interval. Defaults to5000when debug telemetry logging is enabled.
Optional netsim.config.json shape:
{
"broadcastRangeMeters": 50,
"packetLossRate": 0.1,
"latencyMs": 250
}All fields are optional. broadcastRangeMeters and latencyMs must be finite
numbers greater than or equal to 0; packetLossRate must be a finite number
from 0 through 1; unknown fields are rejected. The network config file is
watched while netsim is running. Valid changes affect future forwarded
broadcasts without restarting Redis subscriptions. Invalid changes or deletion
keep the last valid network config and log a warning. Already scheduled delayed
deliveries keep the latency they were scheduled with.
Scenario launch configuration is JSON and is validated by
@swarm-sim/shared. It describes launch-time inputs only:
{
"schemaVersion": 1,
"format": "json",
"world": { "width": 100, "height": 100 },
"drones": [
{
"id": "drone-1",
"initialPosition": { "x": 10, "y": 20 },
"initialVelocity": { "x": 0, "y": 0 },
"initialHeadingRadians": 0
}
]
}initialVelocity and initialHeadingRadians are optional. The
ENU coordinate convention is unchanged: x is east/right, y is north/up,
(0, 0) is bottom-left, heading 0 faces +x/east, and heading Math.PI / 2
faces +y/north.
List drones seen in recent telemetry:
pnpm dev:cli -- listSend a goto command to a running drone agent:
pnpm dev:cli -- send drone-1 goto 80 40Stop or kill a running drone through the existing command channel:
pnpm dev:cli -- send drone-1 stop
pnpm dev:cli -- send drone-1 killThe CLI publishes commands to drone.<id>.command using REDIS_URL,
defaulting to redis://localhost:6379.
Spawn one local agent process with optional initial state:
pnpm dev:cli -- spawn drone-2 --x 30 --y 40 --vx 0 --vy 0 --heading 0The spawn command preserves REDIS_URL and maps the options to the same agent
environment variables documented above. Omitted options use agent defaults.
Launch one local agent process per drone from a scenario file:
pnpm redis
pnpm dev:cli -- launch scenarios/two-drones.jsonThe launcher validates the scenario with @swarm-sim/shared, preserves
REDIS_URL, and maps each drone's initial state to the agent environment.
