OMEN SDK Guide

A complete tutorial for writing mission-app and vehicle-dynamics plugins for the OMEN tactical moving map (built on the Dead Eye engine).


Overview

OMEN plugins are shared libraries (.so on Linux/macOS, .dll on Windows) loaded at runtime via the --plugin flag. They communicate with the host through a stable C API defined in include/omen_sdk.h. No engine headers, no C++ standard library, no particular compiler required — a plugin can be written in plain C, Rust, Zig, or anything that can produce a shared library with C linkage.

What plugins can do

Capability API functions
Read own-ship position, heading, altitude, speed omen_get_ship_state
Read CoT-driven tracks (blue/red/neutral/unknown) omen_track_count, omen_get_track
Place labeled map markers omen_add_marker, omen_remove_marker, omen_clear_plugin_markers
Write to the HUD (8 plugin text slots) omen_set_hud_line
Log to the host console omen_log
Share data with other plugins omen_kv_set, omen_kv_get, omen_kv_delete
Persist settings across runs omen_config_set, omen_config_get, omen_config_save
Provide a flight dynamics model (replaces GTM/F-16 aero) OmenDynamicsModel function-pointer table
Provide an autopilot/controller (replaces cascade PID) OmenController function-pointer table

Build

Plugins link against nothing from the engine — only libc and the SDK header. The canonical build command is:

gcc -shared -fPIC -fvisibility=hidden -O2 -Wall \
    -I include/ \
    -o my_plugin.so my_plugin.c

On macOS, replace gcc with cc (Apple Clang). On Windows, use cl /LD or MinGW gcc -shared.

A minimal Makefile (adapted from the reference plugins):

CC      ?= gcc
CFLAGS  ?= -O2 -Wall -Wextra -fPIC -fvisibility=hidden
INC     := -I../../../include

OUT     := my_plugin.so
SRCS    := my_plugin.c
OBJS    := $(SRCS:.c=.o)

all: $(OUT)

$(OUT): $(OBJS)
    $(CC) -shared -o $@ $^

%.o: %.c
    $(CC) $(CFLAGS) $(INC) -c $< -o $@

clean:
    rm -f $(OUT) $(OBJS)

.PHONY: all clean

Running

./deadeye --omen --airframe gtm --manhattan \
    --plugin path/to/my_plugin.so

Multiple --plugin flags are supported. Plugins initialize in load order and shut down in reverse order.


Plugin Lifecycle

Every plugin shared library must export a single C function:

OMEN_PLUGIN_EXPORT const OmenPlugin* omen_plugin_entry(void);

This returns a pointer to a static OmenPlugin struct that describes the plugin and provides callback function pointers.

Lifecycle hooks

Host startup
  │
  ├─ dlopen("plugin.so")
  ├─ dlsym("omen_plugin_entry") → OmenPlugin*
  ├─ Check api_version == OMEN_SDK_API_VERSION
  │
  ├─ MovingMap::initialize() completes
  │
  ├─ on_init(ctx)          ← called once; return 0 for success, non-zero to abort
  │
  ├─ [render loop begins]
  │  ├─ on_frame(ctx, dt)  ← called every render frame with delta time in seconds
  │  └─ (repeat)
  │
  ├─ [CoT events arrive]
  │  └─ on_cot_rx(ctx, track)  ← called from CoT listener thread (if non-NULL)
  │
  └─ Host exit
     ├─ on_shutdown(ctx)   ← called once; ctx still valid
     ├─ auto-save dirty config
     └─ dlclose()

Any callback can be NULL to opt out. A v1 mission-app plugin typically provides on_init, on_frame, and on_shutdown. A v2 vehicle bundle may skip on_frame entirely and only provide dynamics and/or controller tables.


v1 vs v2: Mission Apps vs Vehicle Dynamics

The OMEN_SDK_API_VERSION is currently 2. The version distinction is about capability, not compatibility:

v1 — Mission app plugins

Classic lifecycle-driven plugins that read host state and draw overlays. They fill in the on_init / on_frame / on_shutdown callbacks and leave dynamics and controller as NULL. Examples: trail recorders, threat correlators, BDA engines, CoT triage bots.

v2 — Vehicle dynamics plugins

A single .so can provide a complete black-box vehicle: a flight dynamics model (replaces the built-in GTM/F-16 aero) and/or an autopilot (replaces the cascade PID). The plugin fills in the dynamics and/or controller fields of OmenPlugin with pointers to static function-pointer tables.

The host's dynamics and GNC factories pick these up when an airframe JSON specifies:

{
  "type": "gtm_t2",
  "plugin_path": "examples/sdk/simple_arrow/simple_arrow.so",
  ...
}

A plugin can be both v1 and v2 simultaneously — lifecycle hooks plus vehicle dynamics in one .so.


Data Types

All data crossing the plugin boundary is POD (Plain Old Data) with explicit field types. No std::string, no glm::vec, no virtual methods.

OmenContext

Opaque handle the host passes to every callback. Do not dereference — only pass it back to omen_* host functions.

OmenShipState

Own-ship state snapshot.

Field Type Units Description
lat_deg double degrees WGS84 latitude
lon_deg double degrees WGS84 longitude
alt_ft double feet MSL altitude
heading_deg double degrees True heading, 0-360
speed_kt double knots Ground speed
pitch_deg double degrees Pitch angle
roll_deg double degrees Roll angle
sim_time_sec double seconds Sim time since spawn

OmenTrack

CoT-driven track.

Field Type Description
uid[64] char[] Unique identifier
callsign[32] char[] Human-readable callsign
cot_type[24] char[] CoT type string, e.g. "a-f-A-M-F"
affiliation OmenAffiliation See enum below
lat_deg double WGS84 latitude (degrees)
lon_deg double WGS84 longitude (degrees)
alt_m double Altitude (meters)
last_update_sec double Sim time of last update

OmenAffiliation

OMEN_AFFIL_UNKNOWN        = 0
OMEN_AFFIL_FRIENDLY       = 1
OMEN_AFFIL_NEUTRAL        = 2
OMEN_AFFIL_HOSTILE         = 3
OMEN_AFFIL_ASSUMED_FRIEND = 4
OMEN_AFFIL_SUSPECT        = 5
OMEN_AFFIL_FAKER          = 6
OMEN_AFFIL_JOKER          = 7

OmenMarker

Map overlay marker.

Field Type Units Description
lat_deg double degrees WGS84 latitude
lon_deg double degrees WGS84 longitude
alt_m double meters Altitude
label[64] char[] — Text label
r, g, b, a float 0-1 RGBA color. Alpha 0 uses host default.

OmenRigidBodyState

Rigid body state for dynamics plugins. NED frame.

Field Type Units Description
pos_ned[3] double[3] feet North, East, Down (+down)
vel_body[3] double[3] fps u, v, w body-frame velocity
quat[4] double[4] — Attitude quaternion (w, x, y, z)
omega_body[3] double[3] rad/s p, q, r body rates

OmenAtmosphere

Atmospheric properties at the vehicle's altitude.

Field Type Units Description
rho_slug_ft3 double slug/ft^3 Air density
pressure_psf double lbf/ft^2 Pressure
temperature_R double Rankine Temperature
sound_speed_fps double fps Speed of sound
altitude_ft double feet MSL altitude
wind_ned_fps[3] double[3] fps Wind vector in NED

OmenControlInput

Actuator commands.

Field Type Units Description
elevator_rad double radians Elevator deflection
aileron_rad double radians Aileron deflection
rudder_rad double radians Rudder deflection
throttle double 0-1 Throttle position
flap double — Flap setting
gear double — Gear position

OmenWrench

Body-frame forces and moments returned by compute_wrench.

Field Type Units Description
force_body[3] double[3] lbf Fx, Fy, Fz
moment_body[3] double[3] lbf-ft L, M, N

OmenStateEstimate

Navigation estimate fed to controller plugins.

Field Type Units Description
pos_ned[3] double[3] feet Position in NED
vel_body[3] double[3] fps Body velocity
quat[4] double[4] — Attitude (w,x,y,z)
omega_body[3] double[3] rad/s Body rates
t_sec double seconds Current sim time

OmenGuidanceCommand

Guidance setpoint fed to controller plugins. Each channel has a value and an *_active flag (0 or 1) indicating whether the autopilot should track that channel.

Field Type Units Description
target_altitude_ft double feet Altitude setpoint
target_heading_deg double degrees Heading setpoint
target_airspeed_kt double knots Airspeed setpoint
target_climb_fpm double fpm Climb rate setpoint
target_bank_deg double degrees Bank angle setpoint
target_pitch_deg double degrees Pitch angle setpoint
alt_active int 0/1 Altitude hold active
hdg_active int 0/1 Heading hold active
spd_active int 0/1 Speed hold active
climb_active int 0/1 Climb rate hold active
bank_active int 0/1 Bank hold active
pitch_active int 0/1 Pitch hold active

OmenMassProperties

Mass and inertia advertised by dynamics plugins.

Field Type Units Description
mass_slug double slugs Vehicle mass
Ixx, Iyy, Izz double slug-ft^2 Principal moments of inertia
Ixz, Ixy, Iyz double slug-ft^2 Products of inertia
cg_body[3] double[3] feet CG in body frame
S_ref_ft2 double ft^2 Reference area
b_ref_ft double feet Reference span
cbar_ref_ft double feet Reference mean chord

OmenDynamicsModel

Function-pointer table for a flight dynamics plugin.

Field Type Description
mass_props OmenMassProperties Vehicle mass and geometry
compute_wrench function pointer Compute body-frame forces and moments
reset function pointer or NULL Called on sim rewind/respawn

The compute_wrench signature:

void (*compute_wrench)(const OmenRigidBodyState* state,
                       const OmenAtmosphere*     atm,
                       const OmenControlInput*   control,
                       OmenWrench*               out_wrench);

The host's rigid-body integrator handles gravity separately. The plugin returns total aero + propulsion + any other forces/moments.

OmenController

Function-pointer table for an autopilot plugin.

Field Type Description
control_rate_hz int The host gates compute_control to this rate
compute_control function pointer Compute actuator commands
reset function pointer or NULL Called on sim rewind/respawn

The compute_control signature:

void (*compute_control)(const OmenStateEstimate*   est,
                        const OmenGuidanceCommand* cmd,
                        const OmenAtmosphere*      atm,
                        double                     t_sec,
                        double                     dt_sec,
                        OmenControlInput*          out_control);

OmenPlugin

The top-level struct every plugin must provide.

Field Type Description
api_version int Must equal OMEN_SDK_API_VERSION (currently 2)
name const char* Short unique name (used in logs, config filenames)
description const char* Human-readable description
vendor const char* Organization name
version const char* Free-form version string, e.g. "1.0.0"
on_init function pointer Called once after host init; return 0 for success
on_frame function pointer or NULL Called every render frame with dt
on_cot_rx function pointer or NULL Called on CoT event (worker thread)
on_shutdown function pointer or NULL Called once on host exit
dynamics const OmenDynamicsModel* or NULL v2: flight dynamics model
controller const OmenController* or NULL v2: autopilot/controller

API Reference

Own-ship state

void omen_get_ship_state(OmenContext* ctx, OmenShipState* outState);

Fills outState with the most recent integrated own-ship values. Always succeeds. Safe to call from any thread.

Track list

int omen_track_count(OmenContext* ctx);

Returns the number of tracks currently in the host's track manager.

int omen_get_track(OmenContext* ctx, int index, OmenTrack* outTrack);

Copies track at the given index into outTrack. Returns 1 on success, 0 if out of range. Strings are copied into the fixed-size buffers.

Map markers

int omen_add_marker(OmenContext* ctx, const OmenMarker* marker);

Adds a labeled point marker. Returns a positive marker ID on success, 0 on failure (marker pool exhausted). Markers persist until explicitly removed.

void omen_remove_marker(OmenContext* ctx, int markerId);

Removes a single marker by ID. No-op if ID is 0 or unknown.

void omen_clear_plugin_markers(OmenContext* ctx);

Removes all markers added by the calling plugin.

HUD

void omen_set_hud_line(OmenContext* ctx, int line_index, const char* text);

Sets one of 8 plugin HUD text slots (indices 0-7). Call once per frame with the current text. The slot is recycled across frames.

Logging

void omen_log(OmenContext* ctx, const char* msg);

Prints to the host console with a [PLUGIN <name>] prefix. Safe from any thread.

Shared key-value store

A global (cross-plugin) string-to-string store for inter-plugin communication. Protected by a mutex — safe from any thread.

void omen_kv_set(OmenContext* ctx, const char* key, const char* value);

Sets a key-value pair. Overwrites any existing value for that key.

const char* omen_kv_get(OmenContext* ctx, const char* key);

Returns the value for a key, or "" if the key does not exist. The returned pointer is valid until the next call to omen_kv_get on the same thread.

void omen_kv_delete(OmenContext* ctx, const char* key);

Removes a key from the store. No-op if the key does not exist.

Example: sensor plugin publishes, weapons plugin reads:

// Sensor plugin (on_frame):
char buf[32];
snprintf(buf, sizeof(buf), "%.6f", target_lat);
omen_kv_set(ctx, "target_lat", buf);

// Weapons plugin (on_frame):
const char* lat_str = omen_kv_get(ctx, "target_lat");
double lat = atof(lat_str);  // "" → 0.0 if not set yet

Plugin config persistence

Each plugin gets its own config file at ~/.deadeye/plugins/<plugin_name>.json. The file is created automatically on first save. Values survive across host runs.

void omen_config_set(OmenContext* ctx, const char* key, const char* value);

Sets a config key-value pair in memory. Marks the config as dirty.

const char* omen_config_get(OmenContext* ctx, const char* key);

Returns the value for a config key, or "" if not set. On first access, lazily loads the config file from disk. The returned pointer is valid until the next call to omen_config_get on the same thread.

void omen_config_save(OmenContext* ctx);

Writes the in-memory config to ~/.deadeye/plugins/<plugin_name>.json. The host also auto-saves dirty configs when the plugin's on_shutdown returns, so explicit saves are only needed for crash resilience.

Example:

static int my_on_init(OmenContext* ctx) {
    // Read a setting from a previous run
    const char* interval = omen_config_get(ctx, "drop_interval");
    if (interval[0] != '\0') {
        g_interval = atoi(interval);
    }
    return 0;
}

static void my_on_shutdown(OmenContext* ctx) {
    // Save settings (also auto-saved by host, but explicit is fine)
    char buf[16];
    snprintf(buf, sizeof(buf), "%d", g_interval);
    omen_config_set(ctx, "drop_interval", buf);
    omen_config_save(ctx);
}

Examples

Minimal plugin skeleton

#include "omen_sdk.h"
#include <stdio.h>

static int my_init(OmenContext* ctx) {
    omen_log(ctx, "hello from my_plugin");
    return 0;
}

static void my_frame(OmenContext* ctx, double dt) {
    (void)dt;
    OmenShipState ship;
    omen_get_ship_state(ctx, &ship);

    char buf[128];
    snprintf(buf, sizeof(buf), "ALT: %.0f ft  HDG: %.0f",
             ship.alt_ft, ship.heading_deg);
    omen_set_hud_line(ctx, 0, buf);
}

static void my_shutdown(OmenContext* ctx) {
    omen_log(ctx, "goodbye");
}

static const OmenPlugin g_plugin = {
    OMEN_SDK_API_VERSION,
    "my_plugin",
    "Minimal example plugin",
    "My Org",
    "1.0.0",
    my_init,
    my_frame,
    NULL,           /* on_cot_rx */
    my_shutdown,
    NULL,           /* dynamics */
    NULL            /* controller */
};

OMEN_PLUGIN_EXPORT const OmenPlugin* omen_plugin_entry(void) {
    return &g_plugin;
}

Marker placement

static void my_frame(OmenContext* ctx, double dt) {
    (void)dt;
    OmenShipState ship;
    omen_get_ship_state(ctx, &ship);

    // Drop a red marker at the current position
    OmenMarker m = {0};
    m.lat_deg = ship.lat_deg;
    m.lon_deg = ship.lon_deg;
    m.alt_m   = ship.alt_ft * 0.3048;
    snprintf(m.label, sizeof(m.label), "WP-%d", g_count);
    m.r = 1.0f; m.g = 0.2f; m.b = 0.2f; m.a = 0.9f;

    int id = omen_add_marker(ctx, &m);
    if (id > 0) g_count++;
}

Reading tracks

static void my_frame(OmenContext* ctx, double dt) {
    (void)dt;
    int n = omen_track_count(ctx);
    for (int i = 0; i < n; i++) {
        OmenTrack t;
        if (omen_get_track(ctx, i, &t)) {
            if (t.affiliation == OMEN_AFFIL_HOSTILE) {
                char buf[128];
                snprintf(buf, sizeof(buf), "HOSTILE: %s at %.4f, %.4f",
                         t.callsign, t.lat_deg, t.lon_deg);
                omen_set_hud_line(ctx, 0, buf);
            }
        }
    }
}

HUD output

// 8 slots available (indices 0-7), positioned below the built-in HUD.
omen_set_hud_line(ctx, 0, "Line 1: status text");
omen_set_hud_line(ctx, 1, "Line 2: more info");
// Call every frame — slots are recycled, not accumulated.

Vehicle dynamics plugin

A dynamics plugin provides a compute_wrench function that the host's rigid-body integrator calls at every physics step. The plugin computes total body-frame forces and moments (aero + propulsion). Gravity is handled by the host.

See examples/sdk/simple_arrow/simple_arrow.c for a complete working example including both dynamics and controller in one .so.

Key points:


JSON Config: Airframe plugin_path

To use a plugin's dynamics model and/or controller, create an airframe JSON with a plugin_path field:

{
  "name": "my_vehicle",
  "type": "gtm_t2",
  "plugin_path": "path/to/my_vehicle.so",

  "visual": {
    "model_path": "models/my_model.obj",
    "heading_offset_deg": 0.0
  },
  "default_speed_kt": 200.0,

  "mass_slug": 10.0,
  "inertia_slugft2": {
    "Ixx":  1.0,
    "Iyy": 20.0,
    "Izz": 20.0,
    "Ixz":  0.0
  },
  "S_ft2":   0.5,
  "cbar_ft": 0.5,
  "b_ft":    0.5,

  "initial_state": {
    "u_fps": 337.56,
    "v_fps": 0.0,
    "w_fps": 0.0,
    "pos_ned_ft": [0.0, 0.0, 0.0],
    "phi_rad": 0.0,
    "theta_rad": 0.0,
    "psi_rad": 0.0,
    "omega_body_rad_s": [0.0, 0.0, 0.0]
  },

  "origin_alt_ft": 2000.0,
  "retrim": false
}

The type field is still required as a placeholder for the spawn code path. The host loads the plugin .so, calls omen_plugin_entry(), and swaps in the plugin's dynamics and/or controller tables after spawn.

Launch with:

./deadeye --omen --airframe path/to/my_vehicle.json

Debugging

omen_log

Use omen_log(ctx, msg) liberally during development. Output appears on the host's stdout with a [PLUGIN <name>] prefix:

[PLUGIN trail_recorder] trail_recorder loaded — drops a marker every sim second
[PLUGIN trail_recorder] trail_recorder shutting down after 42 breadcrumbs

Common pitfalls

  1. API version mismatch. If the plugin was compiled against a different OMEN_SDK_API_VERSION, the host refuses to load it. Error appears on stderr. Recompile the plugin against the current header.

  2. Missing omen_plugin_entry symbol. The function must be exported with OMEN_PLUGIN_EXPORT (which applies visibility("default") on POSIX). Without it, the symbol is hidden and dlsym fails.

  3. Returning non-zero from on_init. The host logs an error and skips all further callbacks for that plugin. The plugin will not receive on_frame ticks.

  4. Slow on_frame. The host renders at the display refresh rate. Anything over ~5 ms in on_frame will stutter the display. Offload heavy work to a background thread and use on_frame only to read results and update overlays.

  5. omen_kv_get / omen_config_get pointer lifetime. The returned const char* is valid only until the next call to the same function on the same thread. Copy the string if you need to keep it.

  6. Marker pool exhaustion. omen_add_marker returns 0 if the host's marker pool is full. Check the return value. Use omen_clear_plugin_markers to reclaim all of your plugin's markers.

  7. Config file location. Plugin configs live at ~/.deadeye/plugins/<name>.json. The directory is created automatically on first save. If HOME is not set, falls back to /tmp.


Thread Safety

Callback / function Thread Notes
on_init Render thread Called after MovingMap::initialize()
on_frame Render thread Called every frame from MovingMap::update()
on_shutdown Render thread Called on host exit, reverse load order
on_cot_rx CoT listener thread Fires on a worker thread — plugin must lock shared state
omen_get_ship_state Any thread Reads a snapshot, no locking needed by caller
omen_track_count Any thread Uses host mutex internally
omen_get_track Any thread Returns a copy of the snapshot
omen_add_marker Render thread Convention: call from on_init/on_frame
omen_remove_marker Render thread Convention: call from on_init/on_frame
omen_clear_plugin_markers Render thread Convention: call from on_init/on_frame
omen_set_hud_line Any thread Safe from any thread
omen_log Any thread Safe from any thread
omen_kv_set/get/delete Any thread Global mutex protects the store
omen_config_set/get/save Any thread Per-plugin mutex protects the config
compute_wrench Physics thread Called by the integrator at physics rate
compute_control Physics thread Called at control_rate_hz

Key rule: Marker calls assume the caller is on the render thread (the on_init / on_frame / on_shutdown convention). All other host functions are safe from any thread. If your on_cot_rx handler needs to update shared plugin state that on_frame reads, protect it with your own mutex.


Reference Plugins

Two complete reference plugins ship with the SDK:

examples/sdk/trail_recorder/

A v1 mission-app plugin (<100 LOC) that drops a labeled cyan breadcrumb marker at the own-ship position once per sim second. Demonstrates:

examples/sdk/simple_arrow/

A v2 vehicle-dynamics plugin that provides both a flight dynamics model and an autopilot in plain C. The "arrow" is a toy projectile with quadratic drag, constant thrust, and a proportional autopilot. Demonstrates: