Engine AI Objects
This guide covers AI aircraft injection using the Engine client’s AICreate* methods — thin, typed wrappers over the raw SimConnect DLL. Every call maps directly to a single SimConnect API function with no extra abstraction.
Scope: This guide covers the raw Engine-layer
AICreate*/AIRemove*/AIRelease*API. Manager-layer wrappers (TrafficParked,TrafficEnroute,TrafficNonATC, fleet tracking) and thepkg/traffic.Fleetabstraction are out of scope — for the high-level Fleet see the Traffic Guide. For complete movement built on top of them, see Departure Taxi, Arrivals & Parking, Injected Ground Movement, Traffic Picture and Traffic Manager.
Aircraft Kinds
SimConnect exposes four creation APIs, each suited to a different role:
| Kind | Method | When to use |
|---|---|---|
| Parked ATC | AICreateParkedATCAircraft |
Static ground traffic at a known airport. SimConnect places the aircraft at a free parking spot. No position required. |
| Enroute ATC | AICreateEnrouteATCAircraft |
Aircraft following an MSFS .pln flight plan. dFlightPlanPosition is a waypoint index plus the fraction along the next leg; in MSFS 2024 it has no effect and the aircraft starts on the ground at the plan’s departure airport (see Enroute Aircraft). |
| Non-ATC | AICreateNonATCAircraft |
Aircraft at an explicit world position. Required when you need precise gate/apron placement or airborne injection outside the ATC system. |
| Simulated Object | AICreateSimulatedObject |
Non-aircraft objects: ground vehicles, boats, or any container title that is not an aircraft. |
All four return an object ID asynchronously via SIMCONNECT_RECV_ID_ASSIGNED_OBJECT_ID.
The Create-Acknowledge Pattern
Every AICreate* call is fire-and-forget from the caller’s perspective. You supply a requestID that you chose; SimConnect responds with SIMCONNECT_RECV_ID_ASSIGNED_OBJECT_ID carrying both your requestID and the live objectID. Store that objectID — it is what you need for every subsequent operation on the object.
//go:build windows
package main
import (
"fmt"
"github.com/mrlm-net/simconnect/pkg/engine"
"github.com/mrlm-net/simconnect/pkg/types"
)
const reqCreate uint32 = 1000
// Submit the create call — objectID is not yet known.
client.AICreateParkedATCAircraft("FSLTL A320 Air France SL", "N12345", "EGLL", reqCreate)
// In the message loop, wait for the acknowledgement.
for msg := range client.Stream() {
switch types.SIMCONNECT_RECV_ID(msg.DwID) {
case types.SIMCONNECT_RECV_ID_ASSIGNED_OBJECT_ID:
assigned := msg.AsAssignedObjectID()
if uint32(assigned.DwRequestID) == reqCreate {
objectID := uint32(assigned.DwObjectID)
fmt.Printf("Aircraft spawned: objectID=%d\n", objectID)
// objectID is now valid for AIRemoveObject, AIReleaseControl, etc.
}
}
}
AsAssignedObjectID() returns nil when the message is not of that type, so the switch-case guard is sufficient — no additional nil-check is needed inside the case block.
Parked ATC Aircraft
AICreateParkedATCAircraft places an aircraft at a free parking spot at the given airport. SimConnect selects the spot; you do not control the exact gate or ramp position.
Signature:
func (e *Engine) AICreateParkedATCAircraft(
szContainerTitle string, // Aircraft model container title (exact match required)
szTailNumber string, // Tail / registration number shown in cockpit and ATC
szAirportID string, // ICAO airport code
RequestID uint32, // Your request ID — returned in ASSIGNED_OBJECT_ID
) error
Example — spawn a parked aircraft at Prague:
//go:build windows
package main
import (
"context"
"fmt"
simconnect "github.com/mrlm-net/simconnect"
"github.com/mrlm-net/simconnect/pkg/engine"
"github.com/mrlm-net/simconnect/pkg/types"
)
const (
reqPark uint32 = 1000
)
func main() {
ctx := context.Background()
client := simconnect.NewClient("ParkedTraffic", engine.WithContext(ctx))
if err := client.Connect(); err != nil {
panic(err)
}
defer client.Disconnect()
// Queue the create call — SimConnect places the aircraft at a free spot.
client.AICreateParkedATCAircraft(
"FSLTL A320 Air France SL", // container title
"AFR123", // tail number
"LKPR", // ICAO
reqPark,
)
for msg := range client.Stream() {
switch types.SIMCONNECT_RECV_ID(msg.DwID) {
case types.SIMCONNECT_RECV_ID_ASSIGNED_OBJECT_ID:
a := msg.AsAssignedObjectID()
if uint32(a.DwRequestID) == reqPark {
fmt.Printf("Parked: objectID=%d\n", uint32(a.DwObjectID))
}
case types.SIMCONNECT_RECV_ID_EXCEPTION:
ex := msg.AsException()
fmt.Printf("SimConnect exception %d at index %d\n",
ex.DwException, ex.DwIndex)
}
}
}
Note: The container title must exactly match a model installed in the simulator. The
EnumerateSimObjectsAndLiveriesAPI, covered below, lets you discover valid titles at runtime.
Enroute ATC Aircraft
AICreateEnrouteATCAircraft injects an aircraft into the ATC system, following an MSFS .pln flight plan from the given position along the route (waypoint index plus the fraction along the next leg).
MSFS 2024: the position does not take effect. Tested live (#369), every enroute ATC aircraft appeared on the ground at its plan’s departure airport, and the simulator refused one (
CREATE_OBJECT_FAILED) whose departure airport it had not loaded. To have an aircraft appear airborne mid-route, create it withAICreateNonATCAircraftand give it the rest of the route as waypoints (traffic.EnrouteStart, see Traffic Manager).
Signature:
func (e *Engine) AICreateEnrouteATCAircraft(
szContainerTitle string, // Aircraft model container title
szTailNumber string, // Tail / registration number
iFlightNumber uint32, // ATC flight number (numeric part)
szFlightPlanPath string, // Absolute path to an MSFS .pln file
dFlightPlanPosition float64, // Waypoint index + fraction along the next leg (2.5 = halfway 2→3)
bTouchAndGo bool, // Whether the aircraft performs touch-and-go landings
RequestID uint32, // Your request ID
) error
Example — inject an aircraft at the beginning of a plan:
//go:build windows
package main
import (
"fmt"
"path/filepath"
simconnect "github.com/mrlm-net/simconnect"
"github.com/mrlm-net/simconnect/pkg/engine"
"github.com/mrlm-net/simconnect/pkg/types"
)
const reqEnroute uint32 = 1001
func spawnEnroute(client engine.Client, planDir string) {
client.AICreateEnrouteATCAircraft(
"FSLTL A320 Air France SL",
"AFR456",
456,
filepath.Join(planDir, "LKPREDDN_MFS_NoProc.pln"),
0.0, // inject at the start of the plan
false, // not touch-and-go
reqEnroute,
)
}
func handleMessages(client engine.Client) {
for msg := range client.Stream() {
switch types.SIMCONNECT_RECV_ID(msg.DwID) {
case types.SIMCONNECT_RECV_ID_ASSIGNED_OBJECT_ID:
a := msg.AsAssignedObjectID()
if uint32(a.DwRequestID) == reqEnroute {
fmt.Printf("Enroute aircraft: objectID=%d\n", uint32(a.DwObjectID))
}
}
}
}
func main() {
client := simconnect.NewClient("EnrouteTraffic")
if err := client.Connect(); err != nil {
panic(err)
}
defer client.Disconnect()
spawnEnroute(client, `C:\MSFS-TEST-PLANS`)
handleMessages(client)
}
Note: The flight plan path must be an absolute Windows path to an MSFS-compatible
.plnfile. Relative paths are not supported by the SimConnect API.
Non-ATC Aircraft at an Explicit Position
AICreateNonATCAircraft gives you full control over initial position, heading, altitude, and airspeed. Use this when you need to place an aircraft at a specific gate, apron stand, or airborne location that is not managed by ATC.
Signature:
func (e *Engine) AICreateNonATCAircraft(
szContainerTitle string,
szTailNumber string,
initPos types.SIMCONNECT_DATA_INITPOSITION,
RequestID uint32,
) error
SIMCONNECT_DATA_INITPOSITION fields:
| Field | Type | Description |
|---|---|---|
Latitude |
float64 |
Decimal degrees, positive = north |
Longitude |
float64 |
Decimal degrees, positive = east |
Altitude |
float64 |
Feet MSL (not meters) |
Pitch |
float64 |
Degrees, positive = nose up |
Bank |
float64 |
Degrees, positive = right wing down |
Heading |
float64 |
True degrees (0–360) |
OnGround |
DWORD |
1 = on ground, 0 = airborne |
Airspeed |
SIMCONNECT_DATA_INITPOSITION_AIRSPEED (DWORD) |
Knots. SIMCONNECT_DATA_INITPOSITION_AIRSPEED_CRUISE (0xFFFFFFFF, the SDK’s -1) selects the cruise speed and …_KEEP (0xFFFFFFFE, -2) keeps the current speed |
Example — spawn an aircraft at a specific gate:
//go:build windows
package main
import (
"fmt"
simconnect "github.com/mrlm-net/simconnect"
"github.com/mrlm-net/simconnect/pkg/engine"
"github.com/mrlm-net/simconnect/pkg/types"
)
const reqNonATC uint32 = 1002
func main() {
client := simconnect.NewClient("NonATCTraffic")
if err := client.Connect(); err != nil {
panic(err)
}
defer client.Disconnect()
// Precise gate coordinates obtained from facility data.
client.AICreateNonATCAircraft(
"FSLTL A320 Air France SL",
"N99001",
types.SIMCONNECT_DATA_INITPOSITION{
Latitude: 50.1006, // gate lat (decimal degrees)
Longitude: 14.2601, // gate lon
Altitude: 1247, // feet MSL (Prague elevation)
Heading: 245, // facing apron
OnGround: 1, // parked
Airspeed: 0,
},
reqNonATC,
)
for msg := range client.Stream() {
if types.SIMCONNECT_RECV_ID(msg.DwID) == types.SIMCONNECT_RECV_ID_ASSIGNED_OBJECT_ID {
a := msg.AsAssignedObjectID()
if uint32(a.DwRequestID) == reqNonATC {
fmt.Printf("Non-ATC aircraft: objectID=%d\n", uint32(a.DwObjectID))
}
}
}
}
EX1 Variants — Livery Selection
The EX1 variants of all three aircraft creation methods add a szLivery parameter that lets you specify an exact livery by name rather than accepting the model’s default.
func (e *Engine) AICreateParkedATCAircraftEX1(
szContainerTitle string,
szLivery string, // Exact livery name; empty string = default livery
szTailNumber string,
szAirportID string,
RequestID uint32,
) error
func (e *Engine) AICreateEnrouteATCAircraftEX1(
szContainerTitle string,
szLivery string,
szTailNumber string,
iFlightNumber uint32,
szFlightPlanPath string,
dFlightPlanPosition float64,
bTouchAndGo bool,
RequestID uint32,
) error
func (e *Engine) AICreateNonATCAircraftEX1(
szContainerTitle string,
szLivery string,
szTailNumber string,
initPos types.SIMCONNECT_DATA_INITPOSITION,
RequestID uint32,
) error
Example — parked aircraft with an explicit livery:
//go:build windows
client.AICreateParkedATCAircraftEX1(
"Boeing 787-10 Asobo",
"House", // livery name as returned by EnumerateSimObjectsAndLiveries
"BOE787",
"KSEA",
1003,
)
Pass an empty string ("") for szLivery to use the model default. The EX1 variants are MSFS 2024 additions; on MSFS 2020 they are available from SimConnect SDK version 11 onwards.
Object Management
Removing an Object
AIRemoveObject destroys an AI object and removes it from the simulation. The requestID is for your own correlation; no meaningful acknowledgement is returned for removal.
//go:build windows
if err := client.AIRemoveObject(objectID, reqRemove); err != nil {
// handle DLL call failure
}
Call this during shutdown to clean up all spawned objects before disconnecting.
Releasing Control
AIReleaseControl transfers ownership of an AI object from your add-on back to the simulator’s AI system. After release, the simulator drives the object autonomously. To give it a route, release it first, then send the waypoint chain with SetDataOnSimObject (pack it with engine.PackWaypoints(wps), unit size engine.WaypointWireSize). The simulator ignores waypoints for an aircraft it still holds.
//go:build windows
// Release AI control — simulator takes over.
client.AIReleaseControl(objectID, reqRelease)
Release, then waypoints, is the correct sequence when you want the aircraft to taxi and depart along your route. pkg/traffic.Fleet does this in ReleaseControl and SetWaypoints.
Assigning a Flight Plan
AISetAircraftFlightPlan assigns or replaces the flight plan on an existing AI aircraft. The object must already exist (use the objectID from ASSIGNED_OBJECT_ID).
//go:build windows
client.AISetAircraftFlightPlan(
objectID,
`C:\MSFS-TEST-PLANS\LKPREDDN_MFS_NoProc.pln`,
reqFlightPlan,
)
Note:
AISetAircraftFlightPlandoes not return anASSIGNED_OBJECT_ID. TherequestIDparameter is present for symmetry with the rest of the AI API but SimConnect does not dispatch a response message for this call.
Model Discovery
Before spawning aircraft you need valid container titles and livery names. EnumerateSimObjectsAndLiveries requests the full list of installed models for a given object type.
Requesting the enumeration:
//go:build windows
const reqEnum uint32 = 9000
client.EnumerateSimObjectsAndLiveries(reqEnum, types.SIMCONNECT_SIMOBJECT_TYPE_AIRCRAFT)
Receiving the results:
SimConnect returns one or more SIMCONNECT_RECV_ID_ENUMERATE_SIMOBJECT_AND_LIVERY_LIST messages. Each contains an array of SIMCONNECT_ENUMERATE_SIMOBJECT_LIVERY entries.
//go:build windows
case types.SIMCONNECT_RECV_ID_ENUMERATE_SIMOBJECT_AND_LIVERY_LIST:
list := msg.AsSimObjectAndLiveryEnumeration()
// list is *types.SIMCONNECT_RECV_ENUMERATE_SIMOBJECT_AND_LIVERY_LIST
for _, entry := range list.Entries() {
title := engine.BytesToString(entry.AircraftTitle[:])
livery := engine.BytesToString(entry.LiveryName[:])
fmt.Printf("Title: %q Livery: %q\n", title, livery)
}
AsSimObjectAndLiveryEnumeration() returns nil when the message is not of that type. RgData is a zero-length marker: the batch’s DwArraySize entries follow the 28-byte list header in the message buffer, and Entries() returns them as a slice (addModels in pkg/traffic/world/control.go reads them the same way by hand). SimConnect may send multiple messages for large model sets; collect them all before using the results.
Object type constants for EnumerateSimObjectsAndLiveries:
| Constant | Value | Description |
|---|---|---|
SIMCONNECT_SIMOBJECT_TYPE_ALL |
1 | All installed objects |
SIMCONNECT_SIMOBJECT_TYPE_AIRCRAFT |
2 | Fixed-wing aircraft only |
SIMCONNECT_SIMOBJECT_TYPE_HELICOPTER |
3 | Helicopters only |
SIMCONNECT_SIMOBJECT_TYPE_BOAT |
4 | Boats and watercraft |
SIMCONNECT_SIMOBJECT_TYPE_GROUND |
5 | Ground vehicles |
See Also
- Engine/Client Usage — Connection lifecycle, data definitions, message handling
- Client Configuration — Configuration options for the Engine client
- traffic-guide.md — Higher-level
pkg/traffic.Fleetabstraction - Departure Taxi and Arrivals & Parking — Controllers that drive one aircraft from stand to take-off, or from final to stand
- Injected Ground Movement — Moving aircraft by position injection
- Traffic Picture and Traffic Manager — All traffic around the user, and scheduled traffic spawned automatically
- examples/ai-traffic — Parked and enroute aircraft injection example
- examples/manage-traffic — Full departure sequence with facility data, waypoints, and cleanup