Scripted AI Objects

From FlightGear wiki
Jump to navigation Jump to search

Note: This project is currently under active development. And we're looking for volunteers interested in contributing.
So if you'd like to help in one way or another, please get in touch or just help improve the article in the meantime!
Useful Skills:
PropertyList XML File, Property Tree, Nasal scripting


Contributors: Red Leader and 5H1N0B1

Mentors: Xiii, Hooray and Philosopher (get in touch to learn more)
It's possible that this article hasn't been updated in a while, so to catch up with the latest developments, you are advised not to start working on anything directly related to this without first coordinating your ideas with fellow FlightGear contributors using the FlightGear developers mailing list or the FlightGear forums. See also the talk page.

Scripted AI objects
Screenshot of several independent AI objects.
Screenshot of several independent AI objects.
Started in 11/2014
Description Script-able AI objects
Contributor(s) Red Leader and 5H1N0B1 (since 10/2014)
Status Prototyping
Topic branches:
fgdata Topic branch


Objective

To come up with generic Nasal modules and helper classes for creating script-able AI guided munitions/objects in an aircraft-agnostic fashion.

Ideally, multiple instances per aircraft should be supported, including AI aircraft (e.g., for Bombable), while factoring out the code specific to controlling AI objects into a reusable module, so that existing use-cases can be updated accordingly (tanker.nas, fox2.nas, missile.nas and maybe eventually bombable.nas), but also so that new efforts can be easily supported (e.g. UAVs/drones or a Cruise missile This is a link to a Wikipedia article).

Internally, the main separation into familiar building blocks using FlightGear concepts will be maintained, to allow certain components to be easily replaced using existing components, namely:

The main motivation here being to reuse existing C++ code by exposing this to scripting space, instead of seeing FDMs, autopilot and/or route manager logic being re-invented and re-implemented in scripting space, at the cost of performance.

The upcoming framework will hopefully also be flexible enough to recursively support sub-payloads which may have their own FDM/guidance logic. Equally, this would involve AI-guided aircraft with AI-guided munitions. In future, we could have F-14Bs firing missiles at each other and AI aircraft being refueled by an AI tanker.

Another mid-term goal is supporting multi-instance setups, including those commonly used at FSWeekend/LinuxTag, but also multiplayer in general (shared stated being replicated across multiple instances).

Status (02/2015)

After playing around with the existing AI system (scenarios and tanker.nas) ThomasS built a Nasal based prototype for GroundServices that is based on a graph system, that lets vehicles move along the edges of a graph like trains on rails. It currently only moves ground vehicles along an airports taxiways without providing any services. Thomas hopes he can use this as a basis and extend it for getting real ground services in the future.[1]


Motivation

We are seeing an increasing number of scripted AI objects in FlightGear, most implementations being incompatible with each other and not reusing/sharing any existing code unfortunately. We would like to come up with a single module that satisfies most/all use-cases, while addressing all requirements:

Gallery

Roadmap

AI Objects

  • Code a dedicated ai.nas module. 70}% completed Ongoing Ongoing
  • Document the API (both in the code and on the wiki). Pending Pending
  • Centralize update loop. Done Done
  • Create a modified version of tanker.nas using ai.nas. Done Done
  • Generalize infrastructure modules (ai.nas and missile.nas). Done Done
    • Review tanker.nas to ensure that ai.nas is sufficiently generic. Done Done
    • Review fox2.nas and missile.nas to generalize the new unified modules accordingly. Done Done
  • Consider adding a dedicated ObjectManager for handling updates in a smarter way, i.e. based on viewer distance, to prioritize updates according to range/visibility. Not done Not done
  • Review possible ground traffic applications (such as vehicles, vessels, trains). [1] This is a link to the FlightGear forum. Not done Not done

Missiles

  • Prototype a missile.nas module using ai.nas. 20}% completed Ongoing Ongoing
  • Add basic architecture from fox2/missile.nas to the framework. Done Done
  • Examine the FDM/guidance logic in fox2.nas and missile.nas to split up and generalize things by moving existing code to appropriate base classes (which may involve coming up with different base classes for sensory purposes, e.g. altitude, radar, heat, FLIR etc). 10}% completed
  • Come up with a simple XML-configurable mechanism for storing missile specifications and objects for each state. (not strictly necessary, a Hash-based declaration would work just as well, but may be less accessible to non-coders) Done Done
  • Code system for transferring missile information over MP. Currently warns target aircraft of missile attack. Done Done
  • Add a Target class for accessing target position/information.
    • Basic storage of position. Can either store a static value (e.g. 12.3456) or a path string (e.g. "/ai/models/aircraft[0]/position/xx-xx"). Done Done
    • Store information such as aspect ratio, heat, IR, etc. Not done Not done
  • Get several aircraft developers involved to update their aircraft accordingly, and ask for feedback. Not done Not done

Multiplayer

Cquote1.png In future, we should support MP properties using OOP, so that we can define an arbitrary number of properties to be transferred over MP
— Red Leader (Sun Jan 18). Re: Trucks models in and out.
(powered by Instant-Cquotes)
Cquote2.png
Cquote1.png That is technically possible already by using AndersG's mp_broadcast.nas module (see wildfire.nas for examples) - it basically overloads generic properties and encodes custom data into it.

The same mechanism could be used to make the AI class agnostic to the way it is controlled, i.e. it could be just as well updated locally or remotely using MP messages.
"mp_broadcast" is the search term you'll want to use if you're looking for additional info on doing that.

But basically any update logic could be remotely managed and handled by local script instances just as well.


— Hooray (Sun Jan 18). Re: Trucks models in and out.
(powered by Instant-Cquotes)
Cquote2.png

Other

  • Refactor and generalize the "missile view" code from missile.nas and move it to FGCamera, so that we can better reuse the code. Not done Not done
  • Extend test.nas to add more test cases, for different types of AI guided objects (missiles, bombs, aircraft, drones, etc.). Not done Not done


Upcoming API

This article or section contains out-of-date information

Please help improve this article by updating it. There may be additional information on the talk page.

Using an existing missile

Currently, there are the following types of missiles in the process of being unified and generalized:

Description Filename Status/Progress Comments
Aim9 $FG_ROOT/Nasal/ai/aim9/aim9.missile 10}% completed Used for prototyping
Fox2 $FG_ROOT/Nasal/ai/fox2/fox2.missile Pending Pending None

The idea is to use the Aim9 and Fox2 in order to come up with a sufficiently generic framework, which is why those two should probably be developed concurrently. Common/shared functionality should usually live at the base class level, while missile-specific features should only be part of the actual missile class. If features are likely to be found on other types of missiles (e.g. guidance system components), it would make sense to introduce additional base classes, for better code reuse.

To create a new missile object, add the following snippet to your code.

io.include("Nasal/ai/aim9/aim9.missile");
 
Aim9.staticInit(); # static initialization (e.g., read missile XML specs only once)
 
# Use the current position as source
var source = geo.aircraft_position().apply_course_distance(0, 100);
# come up with a target position using an offset
var target = geo.aircraft_position().apply_course_distance(0, 20000);
 
var aim9 = Aim9.new(tgt: target, src: source);
var pos = geo.aircraft_position().apply_course_distance(getprop("orientation/heading-deg"), 650);
 
aim9.setState(Aim9State.LAUNCH);
aim9.aiObj.set_pos(pos.lat(), pos.lon(), pos.alt());

For testing purposes, you can also directly invoke the following snippet using the Nasal Console:

ai.selftest();

This will instantiate a missile for testing/development purposes, which can be tracked via the Property Browser.

Implementing a new Missile

Conceptually, each missile is split into 3 distinct components:

  • the FDM (controlled by the autopilot)
  • the autopilot (controlled by the guidance system)
  • the guidance system (controlling the AP, often in combination with tracking/sensory input)

Each of these components must implement a corresponding base class by sub-classing a corresponding interface class:

  • new FDM classes must inherit from ai.FDM
  • new Autopilot classes must inherit from ai.Autopilot
  • new Guidance classes must inherit from ai.Guidance

The constructor of the guidance system will typically require a reference to the autopilot object, while the autopilot object requires a reference to the FDM object.

However, these classes can just be empty stubs inheriting from the proper base class. Control is passed down to each component, so you don't necessarily need to implement an autopilot or FDM if you don't want to.

This would typically be the case for unguided/unpropelled payloads, i.e. those lacking dedicated propulsion, guidance/control and autopilot systems. Thus, only the FDM class would ultimately be running to deliver the payload, which would make it possible to re-implement submodel-based payloads using Nasal.

Equally, this separation of concerns also makes it possible to have payloads that may deliver their own payloads - possibly having their own FDMs.

In the mid-term, our hope is to grow a library of generic building blocks that can be easily reused by people without necessarily having to be experienced programmers. Long-term, the idea is to establish the property tree as the main/sole interfacing mechanism for each component, analogous to the corresponding C++ subsystems in FlightGear (FDM, AP, RM).

While those are currently not yet exposed to scripting space (i.e. inaccessible), this is one of those cases where it would simply make sense to reuse existing C++ code, especially exposing the FDM and autopilot subsystems to Nasal would mean that existing FDMs and autopilot components could be reused for modeling missiles/payloads, i.e. better code-reuse, while also reducing the amount of specialized Nasal code that would normally be overlapping with functionality already existing in C++ space. Which also means there will be less Nasal overhead, as well as better performance due to using C++.

As long as the property-level interface between the AP and the FDM is closely modeled after the underlying conventions in the C++ code, we could easily "plug in" existing FDMs and Autopilots.

By convention, the three file types mentioned above are using a corresponding file extension to make their purpose more self-explanatory. While these names are arbitrary, they are intended to be descriptive. For instance, consider the aim9 directory, which contains:

  • aim9.fdm - the actual FDM
  • aim9.autopilot - the autopilot implementation
  • aim9.guidance - the guidance system

This division also makes it possible for several people to easily collaborate without introducing unnecessary merge conflicts, i.e. by having one team porting/developing the flight dynamics, and another one handling the guidance/autopilot system.

Finally, there's a main file called aim9.missile, which merely includes these three files using the io.include(); directive to set up a corresponding missile type:

io.include("aim9.fdm");
io.include("aim9.autopilot");
io.include("aim9.guidance");
 
var Aim9 = {
    parents: [ai.Missile],
 
    new: func(tgt, src){
 
         # set up the specs for this missile type
         var specs = { parents: [ai.ObjType],
              type: "missile",
              model_path: "Aircraft/Generic/Stores/AIM-9/AIM-9.xml",
              FDM: Aim9FDM,
              AP: Aim9Autopilot,
              GUIDANCE: Aim9Guidance,
         };
 
         return ai.Missile.new(tgt:tgt, src:src, type:specs);
    },
 
};

Internally, the system is set up such that the guidance system will ensure that the autopilot object is updated, which in turn updates the FDM object (a dedicated model manager may follow sooner or later). Equally, we may want to add base classes for modeling different guidance stages.

Custom missile types can be easily implemented by sub-classing the corresponding base-classes and implementing the required interfaces. To get started more quickly, it makes sense to copy & adapt an existing missile like the Aim9.

Implementing the FDM

To implement a FDM for your missile object, simply create a new file with a .fdm extension, and add a new class that inherits from ai.FDM. You'll need to declare a few required methods and provide a corresponding method body for each:

  • update()
# filename: demo.fdm
var DemoFDM = {
    #constructor
    new: func(missile){
        var m = { parents: [ai.FDM] };
        m.obj = missile;
        return m;
    },
    # destructor
    del: func {},
    update: func {}
};

This will set up a corresponding child class implementing the required interface. In your demo.missile file you'd then merely include this file to make the class available.

Missile-specific parameters (specs) can be provided via an XML file which is loaded by the class and turned into a key/value lookup map (hash):

 
<?xml version="1.0"?>

<PropertyList>

<!-- taken from Aircraft/f-14b/f-14a-set.xml -->
<aim9>
	<count type="int">0</count>
	<nearest-target type="int">-1</nearest-target>
	<sound-on-off type="bool">false</sound-on-off>
	<sound-volume type="double">0.12</sound-volume>
	<target-range-nm type="double">0</target-range-nm>
	<!-- AIM-9L specs -->
	<max-detection-rng-nm type="int">10</max-detection-rng-nm><!--FIXME: up to 10-16 MN with best conditions-->
	<fov-deg type="int">25</fov-deg>                     <!-- seeker optical FOV -->
	<detection-fov-deg type="int">60</detection-fov-deg> <!-- Search pattern diameter (rosette scan) -->
	<track-max-deg type="int">110</track-max-deg>        <!-- Seeker max total angular rotation -->
	<max-g type="int">21</max-g>                         <!-- In turn --> 
	<thrust-lbs type="double">230</thrust-lbs>           <!-- guess -->
	<thrust-duration-sec type="int">30</thrust-duration-sec><!-- Mk.36 Mod.7,8 -->
	<weight-launch-lbs>191</weight-launch-lbs>
	<weight-warhead-lbs>20.8</weight-warhead-lbs>
	<drag-coeff type="double">0.05</drag-coeff>          <!-- guess -->
	<drag-area type="double">0.043</drag-area>           <!-- sq ft -->
</aim9>

</PropertyList>

Inside the FDM class, these specs are accessible via the me.specs hash, for example to look up the "max-g" key, you'd use this:

var maxG = me.obj.specs["max-g"];

This separation makes it possible to implement a generic FDM without it having to contain any FDM/missile specifics usually.

The specs field is a member of the main Aim9 missile object, which is accessible from the FDM via the obj field.

Implementing the Autopilot

To implement a FDM for your missile object, simply create a new file with a .autopilot extension, and add a new class that inherits from ai.Autopilot. You'll need to declare a few required methods and provide a corresponding method body fore each:

  • update()
# filename: demo.autopilot
var DemoAutopilot = {
    #constructor
    new: func(fdm){
        var m = { parents: [ai.Autopilot] };
        return m;
    },
    # destructor
    del: func {},
    update: func {}
};

This will set up a corresponding child class implementing the required interface. In your demo.missile file you'd then merely include this file to make the class available.

Implementing the Guidance system

To implement a FDM for your missile object, simply create a new file with a .guidance extension, and add a new class that inherits from ai.Guidance. You'll need to declare a few required methods and provide a corresponding method body for each:

  • update()
# filename: demo.guidance
var DemoGuidance = {
    #constructor
    new: func(autopilot){
        var m = { parents: [ai.Guidance] };
        return m;
    },
    # destructor
    del: func {},
    update: func {},
};

This will set up a corresponding child class implementing the required interface. In your demo.missile file you'd then merely include this file to make the class available.

Original Implementation

fox2.nas

The original missile system was made by Xiii. This file is fox2.nas, and it models part of the F-14B's complex weapons system, which is controlled by weapons.nas. The firing order is as follows.

  • weapons.nas creates a new missile instance using fox2.AIM9.new();.
  • The new missile searches for, and then locks onto, a target provided by the radar.
  • While locked, the position of the HUD locking diamond is updated so that it's over the target.
  • When the missile is told to release(), it calculates the position in lat/lon/alt from offsets in meters.
  • An update() loop starts, which runs a crude FDM.
  • The missile is guided by heading and pitch commands from the targets elevation and deviation from the missile.
  • The missile checks for an impact or a loss of lock.
  • If it detects an impact, it creates impact properties, which can be used Bombable to damage the target, and some other properties for the explosion animation. Also it displays the distance from the target when the missile exploded on the player screen. Optionally, this information can be displayed over Multiplayer.

Due to its OOP nature, fox2.nas can manage several independent missiles, each with its own target.

missile.nas

The evolution of fox2.nas is missile.nas. 5H1N0B1 generalized missile.nas and made it more generic than fox2.nas, and also added or changed some things.

  • Missiles specs are loaded from a nasal script. This allow different missiles of different specifications to be fired.
  • Missiles can be fired from a rail or dropped (although they aren't ejected yet).
  • Three new flight profile modes have been added.
    • A high-altitude cruise mode, for missiles like the AIM-54 Phoenix This is a link to a Wikipedia article.
    • A low-altitude cruise mode, for missiles like the Sea Eagle This is a link to a Wikipedia article.
    • Terrain-following mode, for missiles like the Storm Shadow This is a link to a Wikipedia article.
  • Anticipation of the next target position. This makes the missile more accurate.
  • Improved proximity detection
  • Maximum G-force limitation
  • The missile will not lose lock if the target goes out of the HUD. This better simulates "fire-and-forget" missiles.

Aircraft

Aircraft with a guided weapons implementation.

Aircraft using fox2.nas or fox2.nas-derived implementation
Aircraft File Notes
Grumman F-14 Tomcat fox2.nas None
Mirage 2000-5 missile.nas Evolution of fox2.nas
Saab JA-37 Viggen guided-missiles.nas Derived from fox2.nas
McDonell Douglas F-15 Eagle fox2.nas Derived from fox2.nas
BAe Sea Harrier FA2 missile.nas Derived from the Mirage 2000-5's missile.nas
Aircraft using another implementation
Aircraft File Notes
Boeing B-1B Lancer guide_to_target.nas None
Sukhoi Su-15 This is a link to the FlightGear forum. Unknown None

Brainstorming

From Bombable

Some stuff that could be reused from Bombable.

  • MP information transfer (although may be done via mp_broadcast.nas)
  • AI aircraft maneuvering code could be used for autopilots (or even virtual/ai pilots flying various maneuvers, which is the current use-case/focus).

Offset calculations

In $FG_ROOT/Nasal.tanker.nas, some calculations are made to determine whether the aircraft is in range and can therefore start refueling. These might be useful to set the correct offsets for missiles and other objects.

Firing order

The firing order should look like this:

  1. Detection
  2. Load management
  3. Firing order
  4. Release missile
  5. Update position
  6. Update loop starts
    1. Update guidance within missile capabilities
    2. Proximity detection
    3. Update position

Missile types

  • Unguided (ballistic missiles)
  • Guided
    • Engine type (solid rocket, ramjet, turbojet, none, etc.)
    • Target detection (radar, IR, laser, etc.)
    • Flightplan (high altitude cruise, terrain-following, etc.)
    • Terminal maneuver (staright in, climb & dive, etc.)
    • Launch (pylon, dropped, ejected)
    • Guidance method (proportional, bearing, lead-pursuit, etc.)
    • Fusing (delay, impact, proximity, etc.)
    • Physical (configuration, weight, etc.)
    • Other (e.g., newer missiles have all-aspect, off-boresight capabilities, while older missiles only have rear-aspect, direct-fire capabiltites.)

Evasion

Evasion/countermeasures should be integrated. ECM should be fairly easy, but IR countermeasures will be harder.

Systems

  • FDM
It would make sense to model this after the existing property interface supported by JSBSim/YASim, as that would mean that if/when AI objects can use actual FDMs, we'd have a straightforward migration path — i.e. using the existing convention of having /position, /velocities and /orientation nodes.
  • 3D model
Loading missile with smoke, without when out of fuel, and explosion. In the Mirage 2000-5's "missile.nas", the missile smoke and the explosion is generic. But 3 different 3D models are loaded at each step. Not the best way, but that was chosen to make it generic.
  • Guidance
Should be split into autopilot and "route manager". The guidance system would just expose an abstract interface so that custom heuristics can be implemented.

Related content

Wiki articles

Forum topics

External links


References

References
  1. ThomasS  (Jun 2nd, 2017).  Re: Ground services ! .
  2. Hooray  (Sep 6th, 2016).  Re: Sailing, anyone? .