OMEN Scenario JSON Schema
Multi-vehicle engagement scripting for the --scenario
flag.
Usage
# By short name (resolves to assets/scenarios/<name>.json)
./deadeye --omen --scenario f16_snowflake_dogfight
# By full path
./deadeye --omen --scenario /path/to/my_scenario.jsonThe --scenario flag implies --omen --cot
automatically.
Top-Level Structure
{
"name": "string (required)",
"description": "string (optional)",
"origin": { ... },
"sim_duration_sec": 90,
"vehicles": [ ... ],
"events": [ ... ]
}| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Scenario identifier |
description |
string | no | Human-readable description |
origin |
object | yes | WGS84 origin for the NED frame |
sim_duration_sec |
number | no | Total simulation time (for UI scrub bar) |
vehicles |
array | yes | List of vehicle definitions |
events |
array | no | Time-triggered events (missile launches, etc.) |
Origin
"origin": {
"lat": 34.51,
"lon": -110.08,
"alt_ft": 15000
}| Field | Type | Required | Description |
|---|---|---|---|
lat |
number | yes | Latitude in decimal degrees |
lon |
number | yes | Longitude in decimal degrees |
alt_ft |
number | yes | Altitude MSL in feet (defines NED zero) |
All vehicle positions are relative to this origin in NED coordinates (feet).
Vehicles
{
"id": "blue-1",
"callsign": "VIPER01",
"affiliation": "friendly",
"ownship": true,
"airframe": "src/omen/dynamics/airframes/F16_SL.json",
"autopilot": "assets/autopilots/cascade_pid_F16_hdg_hold.json",
"initial": {
"north_ft": 0,
"east_ft": -30380,
"alt_ft": 15000,
"heading_deg": 90,
"speed_kt": 400
},
"evasion": {
"type": "hard_break_roll",
"break_turn_deg": 90,
"break_duration_sec": 3.0,
"barrel_roll_duration_sec": 2.0
}
}| Field | Type | Required | Description |
|---|---|---|---|
id |
string | yes | Unique identifier (referenced by events) |
callsign |
string | yes | CoT callsign shown on map |
affiliation |
string | yes | "friendly", "hostile",
"neutral", or "unknown" |
ownship |
boolean | yes | Exactly one vehicle must be true (drives HUD +
camera) |
airframe |
string | yes | Path to airframe JSON (relative to project root) |
autopilot |
string | yes | Path to autopilot JSON |
mission |
string | no | Path to mission JSON (waypoint route, etc.). If omitted, uses null mission (holds initial state) |
initial |
object | yes | Initial NED state |
evasion |
object | no | Defensive maneuver definition |
formation |
object | no | Formation station-keeping definition (follows a leader) |
Initial State
| Field | Type | Required | Description |
|---|---|---|---|
north_ft |
number | yes | North position relative to origin (ft) |
east_ft |
number | yes | East position relative to origin (ft) |
alt_ft |
number | yes | Altitude above origin (ft) |
heading_deg |
number | yes | Initial heading (degrees true) |
speed_kt |
number | yes | Initial airspeed (knots) |
Evasion
Triggered automatically when a missile is launched at this vehicle.
| Field | Type | Required | Description |
|---|---|---|---|
type |
string | yes | "hard_break_roll" (only supported type currently) |
break_turn_deg |
number | no | Break turn magnitude (default: 90°) |
break_duration_sec |
number | no | Duration of the break turn (default: 3.0s) |
barrel_roll_duration_sec |
number | no | Duration of the barrel roll (default: 2.0s) |
Evasion state machine: IDLE → BREAK (hard turn) → ROLL (barrel roll) → RECOVERED
Formation
Maintains a fixed offset relative to a leader vehicle. The offset is in the leader's body frame (rotated by the leader's heading each frame so the wingman turns with the leader).
"formation": {
"leader": "blue-1",
"offset_north_ft": 0,
"offset_east_ft": 500,
"offset_alt_ft": 0
}| Field | Type | Required | Description |
|---|---|---|---|
leader |
string | yes | Vehicle id of the formation leader |
offset_north_ft |
number | no | Body-frame offset: ahead of leader (ft, default 0) |
offset_east_ft |
number | no | Body-frame offset: right of leader (ft, default 0) |
offset_alt_ft |
number | no | Altitude above leader (ft, default 0) |
Common formations:
- Echelon right:
offset_east_ft: 500 - Echelon left:
offset_east_ft: -500 - Trail:
offset_north_ft: -500 - Finger-four right:
offset_north_ft: -200, offset_east_ft: 400
The wingman's guidance automatically tracks the leader's heading and airspeed. When the leader turns, the wingman follows. The leader can fly any mission type (waypoint, hold, scripted).
Events
{
"time_sec": 5.0,
"type": "launch_missile",
"launcher": "blue-1",
"target": "red-1",
"weapon": "src/omen/dynamics/airframes/AIM9X.json",
"autopilot": "assets/autopilots/pronav_AIM9X.json",
"callsign": "AIM9X-1"
}| Field | Type | Required | Description |
|---|---|---|---|
time_sec |
number | yes | Minimum simulation time before the event can fire |
type |
string | yes | Event type (currently only "launch_missile") |
launcher |
string | yes | Vehicle id that launches the weapon |
target |
string | yes | Vehicle id being targeted |
weapon |
string | yes | Airframe JSON for the weapon |
autopilot |
string | yes | Autopilot JSON for the weapon |
callsign |
string | yes | CoT callsign for the weapon track |
condition |
object | no | Conditional trigger (event fires when BOTH time_sec has elapsed AND condition is met) |
Conditional Events
Events can optionally include a condition block. When
present, the event fires only when both time_sec has
elapsed AND the condition is satisfied. Without a condition, the event
fires purely on time.
{
"time_sec": 0,
"type": "launch_missile",
"condition": {
"type": "range_lt",
"vehicle_a": "blue-1",
"vehicle_b": "red-1",
"value_ft": 30000
},
"launcher": "blue-1",
"target": "red-1",
"weapon": "src/omen/dynamics/airframes/AIM9X.json",
"autopilot": "assets/autopilots/pronav_AIM9X.json",
"callsign": "AIM9X-1"
}| Field | Type | Required | Description |
|---|---|---|---|
type |
string | yes | Condition type (see table below) |
vehicle_a |
string | yes* | First vehicle id (*required for all condition
types) |
vehicle_b |
string | yes* | Second vehicle id (*required for range conditions) |
value_ft |
number | yes | Threshold value (feet for altitude/range, seconds for time_after) |
Condition Types
| Type | Description |
|---|---|
range_lt |
Fires when 3D range between vehicle_a and
vehicle_b is less than value_ft |
range_gt |
Fires when 3D range between vehicle_a and
vehicle_b is greater than value_ft |
alt_below |
Fires when vehicle_a's altitude (above origin) drops
below value_ft |
alt_above |
Fires when vehicle_a's altitude (above origin) rises
above value_ft |
time_after |
Fires when simulation time exceeds value_ft seconds
(redundant with time_sec but explicit) |
Event Types
| Type | Description |
|---|---|
launch_missile |
Spawns a new 6DOF missile from the launcher's position, guided by ProNav toward the target. If the target has an evasion block, it triggers automatically. |
Available Airframes
| Name | Path | Type | Notes |
|---|---|---|---|
| F-16C | src/omen/dynamics/airframes/F16_SL.json |
stevens_lewis_f16 |
Supersonic fighter, relaxed static stability |
| GTM T2 | src/omen/dynamics/airframes/GTM_T2.json |
gtm_t2 |
Generic Transport Model (subsonic) |
| AIM-9X | src/omen/dynamics/airframes/AIM9X.json |
missile |
Short-range air-to-air missile |
| Simple Arrow | src/omen/dynamics/airframes/simple_arrow.json |
gtm_t2 + plugin |
SDK v2 reference vehicle |
Available Autopilots
| Name | Path | Best For |
|---|---|---|
| GTM Cascade PID | assets/autopilots/cascade_pid_GTM_T2.json |
GTM transport |
| F-16 Cascade PID | assets/autopilots/cascade_pid_F16.json |
F-16 (alt hold) |
| F-16 Hdg Hold | assets/autopilots/cascade_pid_F16_hdg_hold.json |
F-16 (heading + alt) |
| AIM-9X ProNav | assets/autopilots/pronav_AIM9X.json |
Missile intercept |
Example: Two F-16s Head-On Merge
{
"name": "f16_snowflake_dogfight",
"description": "Two F-16s head-on at 10 nm, Blue launches AIM-9X at t=5s",
"origin": { "lat": 34.51, "lon": -110.08, "alt_ft": 15000 },
"sim_duration_sec": 90,
"vehicles": [
{
"id": "blue-1", "callsign": "VIPER01", "affiliation": "friendly",
"ownship": true,
"airframe": "src/omen/dynamics/airframes/F16_SL.json",
"autopilot": "assets/autopilots/cascade_pid_F16_hdg_hold.json",
"initial": { "north_ft": 0, "east_ft": -30380, "alt_ft": 15000,
"heading_deg": 90, "speed_kt": 400 }
},
{
"id": "red-1", "callsign": "BANDIT01", "affiliation": "hostile",
"ownship": false,
"airframe": "src/omen/dynamics/airframes/F16_SL.json",
"autopilot": "assets/autopilots/cascade_pid_F16_hdg_hold.json",
"initial": { "north_ft": 0, "east_ft": 30380, "alt_ft": 15000,
"heading_deg": 270, "speed_kt": 400 },
"evasion": { "type": "hard_break_roll", "break_turn_deg": 90 }
}
],
"events": [
{
"time_sec": 5.0, "type": "launch_missile",
"launcher": "blue-1", "target": "red-1",
"weapon": "src/omen/dynamics/airframes/AIM9X.json",
"autopilot": "assets/autopilots/pronav_AIM9X.json",
"callsign": "AIM9X-1"
}
]
}Example: Formation Flight
{
"name": "f16_formation",
"description": "Two F-16s in echelon right formation on a waypoint route",
"origin": { "lat": 34.51, "lon": -110.08, "alt_ft": 15000 },
"sim_duration_sec": 300,
"vehicles": [
{
"id": "blue-1", "callsign": "VIPER01", "affiliation": "friendly",
"ownship": true,
"airframe": "src/omen/dynamics/airframes/F16_SL.json",
"autopilot": "assets/autopilots/cascade_pid_F16_hdg_hold.json",
"mission": "assets/missions/f16_formation_leader_route.json",
"initial": { "north_ft": 0, "east_ft": 0, "alt_ft": 15000,
"heading_deg": 0, "speed_kt": 400 }
},
{
"id": "blue-2", "callsign": "VIPER02", "affiliation": "friendly",
"ownship": false,
"airframe": "src/omen/dynamics/airframes/F16_SL.json",
"autopilot": "assets/autopilots/cascade_pid_F16_hdg_hold.json",
"initial": { "north_ft": 0, "east_ft": 500, "alt_ft": 15000,
"heading_deg": 0, "speed_kt": 400 },
"formation": { "leader": "blue-1",
"offset_north_ft": 0, "offset_east_ft": 500, "offset_alt_ft": 0 }
}
],
"events": []
}Example: Conditional Missile Launch (Range-Based)
{
"name": "range_triggered_launch",
"description": "AIM-9X launches when Blue and Red close to within 30,000 ft",
"origin": { "lat": 34.51, "lon": -110.08, "alt_ft": 15000 },
"sim_duration_sec": 90,
"vehicles": [
{
"id": "blue-1", "callsign": "VIPER01", "affiliation": "friendly",
"ownship": true,
"airframe": "src/omen/dynamics/airframes/F16_SL.json",
"autopilot": "assets/autopilots/cascade_pid_F16_hdg_hold.json",
"initial": { "north_ft": 0, "east_ft": -50000, "alt_ft": 15000,
"heading_deg": 90, "speed_kt": 400 }
},
{
"id": "red-1", "callsign": "BANDIT01", "affiliation": "hostile",
"ownship": false,
"airframe": "src/omen/dynamics/airframes/F16_SL.json",
"autopilot": "assets/autopilots/cascade_pid_F16_hdg_hold.json",
"initial": { "north_ft": 0, "east_ft": 50000, "alt_ft": 15000,
"heading_deg": 270, "speed_kt": 400 },
"evasion": { "type": "hard_break_roll" }
}
],
"events": [
{
"time_sec": 0,
"type": "launch_missile",
"condition": {
"type": "range_lt",
"vehicle_a": "blue-1",
"vehicle_b": "red-1",
"value_ft": 30000
},
"launcher": "blue-1",
"target": "red-1",
"weapon": "src/omen/dynamics/airframes/AIM9X.json",
"autopilot": "assets/autopilots/pronav_AIM9X.json",
"callsign": "AIM9X-1"
}
]
}Notes
- Distances: 1 nm = 6076.12 ft. So 5 nm = 30,380 ft.
- All vehicles use full 6DOF rigid-body dynamics — turns, climbs, and descents are physically realistic.
- Non-ownship vehicles appear as CoT tracks with MIL-STD-2525 symbology (affiliation colors + shapes).
- The ownship vehicle drives the HUD, camera, and own-ship CoT emission.
- Multiple launch events can be scheduled at different times for multi-wave engagements.
Wind
Each vehicle has a wind field for simple constant wind
(optional):
"wind": {
"speed_kt": 20,
"from_deg": 270
}This sets a steady-state wind using meteorological convention
(direction wind blows FROM). For high-fidelity turbulence, gusts, and
wind shear, use the full MIL-F-8785C Von Karman model in
src/world/std_wind.h (StdWind::WindModel_t)
which supports light/moderate/severe turbulence intensity levels,
altitude-dependent scale lengths, discrete wind shear, and the NRL HWM14
horizontal wind model.
Vignette event stream
A --vignette run publishes one record per thing that
happens and, when asked, appends each to a JSONL file: one complete JSON
object per line, no line breaks inside it. This is what the on-screen
log reads, what the sound bridge keys off, and what an external scorer
subscribes to; the field set below is fixed.
| Field | Type | Meaning |
|---|---|---|
kind |
string | spawn, lase, fire,
impact, hit, miss,
kill, call_for_fire, reload,
withdraw, run_end |
t_sec |
number | Simulation time of the event, seconds from the start of the run |
unit |
string | The unit the event belongs to (the shooter, the spotter, the unit placed) |
target |
string | The target, where there is one; otherwise "" |
round |
string | The round type, where there is one; otherwise "" |
ecef_m |
[x, y, z] | Where it happened, ECEF meters (the truth) |
lat_deg, lon_deg, alt_m |
numbers | The same place geodetic, so a reader need not convert |
range_m |
number | Shooter-to-target range at the time, where it applies |
tof_sec |
number | The round's time of flight, where it applies |
ph, pk |
numbers | The probabilities the shot was taken under (0 when the table is unset) |
Fields that do not apply to a kind are present with their zero values, so every line has every field. Example:
{"alt_m":712,"ecef_m":[-2400000,-4500000,3600000],"kind":"fire","lat_deg":35.33,"lon_deg":-116.47,"ph":0.88,"pk":0.8,"range_m":1850,"round":"m829","t_sec":12.25,"target":"R1","tof_sec":1.32,"unit":"A11"}