Running a simulation
Create a scenario, run it, act on it, read it back. Every step is one request.
Uses $DOME, $KEY and $WS from Authentication. Every field in a
scenario is on Scenario model.
1. Check what this deployment can run
# Each environment this deployment knows, whether it is set up, and whether it answers
curl -s $DOME/sim/simulators \
-H "X-Api-Key: $KEY" -H "X-Workspace-Id: $WS" \
| jq -c '.data[] | {kind, configured, reachable}'
# → {"kind":"lite","configured":true,"reachable":true} in the server, always there
# {"kind":"gazebo","configured":true,"reachable":true} a rendered world on another host
2. Create a scenario
SCENARIO=$(curl -s -X POST $DOME/scenarios \
-H "X-Api-Key: $KEY" -H "X-Workspace-Id: $WS" \
-H "Content-Type: application/json" \
-d '{
"name": "Harbour, two waves",
"spec": {
"seed": 20260918,
"region": {
"name": "Harbour",
"center": [1.3644, 103.9915],
"geofence_radius_m": 1500
},
"environment": { "kind": "lite" },
"forces": [
{
"id": "blue",
"side": "defender",
"controller": { "kind": "c2" },
"drones": { "kind": "specific", "drones": [
{ "label": "BLUE-01", "drone_type": "quadcopter", "approach_bearing": 0, "formation": "station" },
{ "label": "BLUE-02", "drone_type": "quadcopter", "approach_bearing": 0, "formation": "station" }
] }
},
{
"id": "red-recon",
"side": "adversary",
"controller": { "kind": "scenario" },
"objective": { "kind": "recon" },
"drones": { "kind": "swarm", "count": 1, "drone_type": "quadcopter",
"approach_bearing": 0, "formation": "single" }
},
{
"id": "red-strike",
"side": "adversary",
"controller": { "kind": "scenario" },
"objective": { "kind": "kamikaze" },
"drones": { "kind": "swarm", "count": 3, "drone_type": "fpv",
"approach_bearing": 90, "formation": "line" }
}
],
"time": { "scale": 1.0, "duration_s": 240 },
"video": { "enabled": false, "fps": 10 }
}
}' | jq -r '.data.id')
# seed same number, same raid, every run
# region the site: [latitude, longitude] and the geofence radius in metres
# environment lite runs in the server; gazebo and air_sim name a compiled level
# blue controller c2: ours to task, through the decision loop
# red-recon controller scenario: the simulator flies it. In from the north (0°), then orbits
# red-strike three FPV from the east (90°), flying into the site
# time stops itself after 240 simulated seconds; scale 2.0 would run twice as fast
# → SCENARIO=eecaa039-da47-4cc7-8b70-9c9ccbf0c18a
# Read back what was stored. The server keeps a canonical form, which can differ from what you sent
curl -s $DOME/scenarios/$SCENARIO \
-H "X-Api-Key: $KEY" -H "X-Workspace-Id: $WS" \
| jq -c '.data.spec.forces[] | {id, side, controller: .controller.kind}'
# → {"id":"blue","side":"defender","controller":"c2"}
# {"id":"red-recon","side":"adversary","controller":"scenario"}
# {"id":"red-strike","side":"adversary","controller":"scenario"}
A spec that does not parse is refused here with 400 and the field that broke it, not later
at start.
3. Attach the environment and start
# Put the workspace into simulation on Dome Lite.
# This resets the runtime: tracks from before are gone
curl -s -X PUT $DOME/sim/state \
-H "X-Api-Key: $KEY" -H "X-Workspace-Id: $WS" \
-H "Content-Type: application/json" \
-d '{ "mode": "sim", "simulator": "lite" }'
# → {"ok":true,"data":{"mode":"sim","simulator":"lite","run":"idle"}} attached, nothing running
# Start the scenario. The clock runs and both adversary waves fly from t=0
curl -s -X POST $DOME/sim/start \
-H "X-Api-Key: $KEY" -H "X-Workspace-Id: $WS" \
-H "Content-Type: application/json" \
-d "{ \"scenario_id\": \"$SCENARIO\" }" \
| jq -c '.data | {mode, run, loaded_scenario_id}'
# → {"mode":"sim","run":"running","loaded_scenario_id":"eecaa039-…"}
4. Watch the picture
# Count the tracks by affiliation, a few seconds in.
# friend: our drones, known by their own reports. suspect: the adversary, scored as threats
curl -s $DOME/tracks \
-H "X-Api-Key: $KEY" -H "X-Workspace-Id: $WS" \
| jq -c '[.data[] | .classification.affiliation] | group_by(.) | map({(.[0]): length}) | add'
# → {"friend":2,"suspect":4}
# One hostile, the fields that matter
curl -s $DOME/tracks \
-H "X-Api-Key: $KEY" -H "X-Workspace-Id: $WS" \
| jq '[.data[] | select(.classification.affiliation == "suspect")][0]
| {track_id, simulated, class: .classification.type, geo: .kinematics.geo}'
# → { "track_id": "T-00481", the id commands and plans name it by
# "simulated": true, made by a run; never mixed into the live picture
# "class": "uav_multirotor",
# "geo": { "lat": 1.3815, "lon": 103.9915, "alt_m": 99.9 } }
# Or hold the stream instead of polling: a ticket, then the stream (see Authentication)
TICKET=$(curl -s -X POST $DOME/auth/stream-ticket \
-H "X-Api-Key: $KEY" -H "X-Workspace-Id: $WS" | jq -r '.data.ticket')
curl -sN "$DOME/stream?ticket=$TICKET"
# → event: snapshot everything, once
# event: track_update then one event per track change
# event: engagement_slice threats, their stage, time to impact
Stream events: Picture & stream.
5. Act
# Candidate plans against the threats: 0 to 3, replaced whenever the planner replans.
# Each assignment pairs one of our drones with one track, and says whether it needs approval
curl -s $DOME/plans \
-H "X-Api-Key: $KEY" -H "X-Workspace-Id: $WS" \
| jq -c '.data[] | {id, assignments: [.assignments[] | {verb, track: .params.track_id, approval: .approval.reason}]}'
# → {"id":"plan-g1-max_coverage",
# "assignments":[{"verb":"follow","track":"T-00481","approval":"SHADOW is above the line"}]}
# follow sits on the shadow rung, above the default line (watch), so a person has to approve it
# Approve the first one. 404 means a replan replaced it: fetch again
PLAN=$(curl -s $DOME/plans -H "X-Api-Key: $KEY" -H "X-Workspace-Id: $WS" | jq -r '.data[0].id')
curl -s -X POST $DOME/plans/$PLAN/approve \
-H "X-Api-Key: $KEY" -H "X-Workspace-Id: $WS" \
| jq -c '.data | {id, status, authored_under}'
# → {"id":"plan-g1-max_coverage","status":"approved","authored_under":{"kind":"linear","line":"watch"}}
# The tasks the approval minted: one per assignment, origin plan
curl -s "$DOME/tasks?plan=$PLAN" \
-H "X-Api-Key: $KEY" -H "X-Workspace-Id: $WS" \
| jq -c '.data[] | {verb, level, status, origin: .origin.source}'
# → {"verb":"follow","level":"shadow","status":"issued","origin":"plan"}
# Or task a drone yourself. Find its asset id first.
# A scenario's drone can carry a run prefix (sim/<run>/BLUE-01), so match the start of the name,
# and URL-encode the id because it can contain slashes
BLUE=$(curl -s "$DOME/assets?kind=vehicle" \
-H "X-Api-Key: $KEY" -H "X-Workspace-Id: $WS" \
| jq -r '.data[] | select(.name | startswith("BLUE-01")) | .id | @uri')
# Send it to a point. The answer is the task that was written
curl -s -X POST $DOME/assets/$BLUE/command \
-H "X-Api-Key: $KEY" -H "X-Workspace-Id: $WS" \
-H "Content-Type: application/json" \
-d '{ "verb": "move_to",
"params": { "kind": "point", "lat": 1.3700, "lon": 103.9950, "alt_m": 100 } }' \
| jq -c '.data | {id, verb, level, gate, status}'
# → {"id":"tsk_46b8…","verb":"move_to","level":"manoeuvre",
# "gate":{"gate":"runs","stop_within":0},"status":"issued"}
# level the rung this verb sits on
# gate what the autonomy line answered: runs, asks, or not_permitted
Every verb, the task record and moving the line: Commanding assets.
6. Pause, stop, read it back
# Freeze the clock without ending the run. The picture holds
curl -s -X POST $DOME/sim/pause \
-H "X-Api-Key: $KEY" -H "X-Workspace-Id: $WS" \
-H "Content-Type: application/json" -d '{ "paused": true }'
# → {"ok":true,"data":{…,"run":"paused"}}
# Let it go again
curl -s -X POST $DOME/sim/pause \
-H "X-Api-Key: $KEY" -H "X-Workspace-Id: $WS" \
-H "Content-Type: application/json" -d '{ "paused": false }'
# → {"ok":true,"data":{…,"run":"running"}}
# End it. The run is recorded; the environment stays attached for the next one
curl -s -X POST $DOME/sim/stop \
-H "X-Api-Key: $KEY" -H "X-Workspace-Id: $WS"
# → {"ok":true,"data":{"mode":"sim","simulator":"lite","run":"idle"}}
# The run just recorded: scenario, seed, how long, how it ended
RUN=$(curl -s "$DOME/sim/runs?limit=1" \
-H "X-Api-Key: $KEY" -H "X-Workspace-Id: $WS" | jq -r '.data[0].id')
# Every track event it produced, in order. This is what replay reads
curl -s "$DOME/sim/runs/$RUN/events?kind=track&limit=1000" \
-H "X-Api-Key: $KEY" -H "X-Workspace-Id: $WS" | jq 'length'
# Back to live when you are done. This resets the runtime too
curl -s -X PUT $DOME/sim/state \
-H "X-Api-Key: $KEY" -H "X-Workspace-Id: $WS" \
-H "Content-Type: application/json" -d '{ "mode": "live" }'
Run it again and the seed replays the same raid. Routes: Simulation. Event kinds and filters: Observations & events.