Custom Criteria
Custom criteria are a powerful feature of Observatory's Explorer plugin which allows users to create and share their own criteria for what is flagged as interesting and notifies the player.
The Scripting Language
Custom Criteria are written in Lua (opens in a new tab), and should be easy to pick up for anyone with a modicum of scripting experience. Explorer's Lua execution is handled by the NLua (opens in a new tab) library, and includes a handful of annotations and functions specific to Observatory Explorer.
One subtle "gotcha" for those who might be already familiar with Lua is that several of the objects passed into the Lua scripts from Explorer are .NET collections rather than Lua tables, and as such are zero-indexed userdata objects, in contrast to the more common one-indexing in Lua. This also means that Lua's truthiness rules might not apply in all situations (though generally should). Such objects will also use upper CamelCase
for their member properties, rather than the more typical snake_case
convention of Lua.
The Criteria File
To get started creating criteria all you need to do is create a text file and select it as your custom criteria file in your Explorer settings. A .lua
extension is preferable (many editors support syntax highlighting for .lua
files), but not strictly necessary.
Simple Criteria
Simple criteria can be written as single-line expressions. Criteria in this category are typically straightforward tests of a value against a condition, //e.g.// "is this body landable with a surface temperature above 1500 K"? Here is one way that particular check could be written:
---@Simple Hot Landable
scan.Landable and scan.SurfaceTemperature > 1500
The first line (---@Simple Hot Landable
) tells Explorer that this is a simple one-line criteria, and what text to put in the "Description" column when this criteria is found. You can put any text you want here.
The second line is a true or false Lua expression which is evaluated for each scan received. scan
in this context is the scan event read from the Elite Dangerous player journal, and contains all the properties that can be found there and is detailed below.
Optionally the "Detail" column can be populated as well. If we want to include the specific temperature of the hot landable bodies found by the above criteria then it could be done like this:
---@Simple Hot Landable
scan.Landable and scan.SurfaceTemperature > 1500
---@Detail
'Temperature: ' .. math.floor(scan.SurfaceTemperature) .. ' K'
A ---@Detail
annotation on the line //immediately following// a simple criteria expression indicates that the next line will be a string valued expression which is to be displayed in the "Detail" column.
The string expression itself is a single-line Lua expression which must result in a single string. In this example we use Lua's concatenation operator, ..
, to build more presentable output than just tossing the temperature value there by itself. We also use math.floor
, a function from Lua's built-in math
library (opens in a new tab), to remove unnecessary decimal places from the original journal values. If more control over how numbers are displayed is desired then string.format
can be used instead. You can find a section detailing its use below.
Complex Criteria
For criteria which might be unwieldy to write out in a single line you can create more complex multi-line Lua scripts. Generally this becomes necessary if you have a large chain of different values you want to test, or need to iterate through multiple values, such as planetary rings or parent bodies.
For example, if we want our hot landable check to only trigger if it's the moon of a ringed planet, then this is one approach:
---@Complex
if scan.Landable and scan.SurfaceTemperature > 1000 and parents then
if parents[0].Scan and parents[0].ParentType == 'Planet' and parents[0].Scan.Rings then
return true, 'Hot Landable Moon of Ringed Parent', 'Temperature: ' .. math.floor(scan.SurfaceTemperature) .. ' K'
end
end
---@End
Unlike in simple one line criteria where the initial directive contains the description, for multi-line scripts the parsing directives are purely used for demarcation. With ---@Complex
indicating the beginning on the script, and ---@End
terminating it.
Rather than simply wanting a true/false expression we now use the values we're checking to construct an if
statement. (Note that we've also lowered the temperature threshold here, as these are now going to be somewhat rarer than the original check.)
We've also split the conditions into two separate checks. While there's no technical reason it could not be done on one line, breaking them up this way improves readability, and conceptually separates the checks concerning the body itself (it is landable, is it hot, does it have parents), from the checks against the parent's data (do we have its scan data, is it a planet, does it have rings).
If all of that passes then we return our successful results, which consists of three values. true
, indicating that the check passed, the description string, and the detail string. In version v0.2.23019.109 and later the leading true
can be optionally omitted and is simply inferred from there being any return value at all.
There are of course different ways to construct this exact same check, depending largely on personal preference.
All in one if
:
---@Complex
if scan.Landable and scan.SurfaceTemperature > 1000 and parents and parents[0].Scan and parents[0].ParentType == 'Planet' and parents[0].Scan.Rings then
return true, 'Hot Landable Moon of Ringed Parent', 'Temperature: ' .. math.floor(scan.SurfaceTemperature) .. ' K'
end
---@End
Evaluating parent
details inside return
statement:
---@Complex
if scan.Landable and scan.SurfaceTemperature > 1000 and parents then
return
parents[0].Scan and parents[0].ParentType == 'Planet' and parents[0].Scan.Rings ~= nil,
'Hot Landable Moon of Ringed Parent',
'Temperature: ' .. math.floor(scan.SurfaceTemperature) .. ' K'
end
---@End
As a simple criteria:
---@Simple Hot Landable Moon of Ringed Parent
scan.Landable and scan.SurfaceTemperature > 1000 and parents and parents[0].Scan and parents[0].ParentType == 'Planet' and parents[0].Scan.Rings ~= nil
---@Detail
'Temperature: ' .. math.floor(scan.SurfaceTemperature) .. ' K'
Note the change in how parents[0].Scan.Rings
is checked in the last two examples. Since this is the final result being returned back to the .NET environment we can't count on Lua's "truthiness" to silently consider the existence of the Rings
object to be true
.
A multi-line criteria is a fully featured Lua function, and can use just about any feature of the Lua language. For example, variable declaration and for
loops:
---@Complex
if scan.Landable and scan.AtmosphereComposition then
local CO2 = 0
local N = 0
for mat in materials(scan.AtmosphereComposition) do
if mat.name == 'CarbonDioxide' then
CO2 = mat.percent
elseif mat.name == 'Nitrogen' then
N = mat.percent
end
end
if CO2 > 0 and N > 0 then
return
true,
'Landable CO₂ + Nitrogen atmosphere',
'CO₂: ' .. math.floor(CO2) .. '%, N: ' .. math.floor(N) .. '%'
end
end
---@End
Labels can be used as well, with the exception of ::Criteria::
, ::End::
, ::Detail::
, and ::Global::
which are reserved for legacy reasons.
For those of you wondering about the materials
function being used at the beginning of the for
loop, head down to the Collections and Iterators section below.
Available Values
All criteria have access to five objects which are passed in from Explorer to the Lua script: scan
, parents
, system
, biosignals
, and geosignals
.
biosignals
and geosignals
are simple integer values containing the number of surface signals reported in the FSSBodySignals
event.
parents
and system
will be detailed in the Collections and Iterators section.
scan
is a representation of the journal Scan
event, and contains the following member properties:
scan.StarSystem
(string): Name of the current system.scan.SystemAddress
(ulong): 64-bit unsigned integer uniquely identifying the current system.scan.ScanType
(string): Type of scan from which the data originates, e.g. "Detailed", "AutoScan", or "NavBeaconData".scan.BodyName
(string): Name of the scanned object.scan.BodyID
(int): System specific ID number of body.scan.DistanceFromArrivalLS
(double): Distance from point of arrival in light-seconds.scan.TidalLock
(bool): Tidally locked true/false.scan.TerraformState
(string): Terraform state string from journal, empty for non-terraformable, otherwise "Terraformable", "Terraforming", or "Terraformed".scan.PlanetClass
(string): Type of planet, e.g. "High metal content body". (See: PlanetClass Values)scan.Atmosphere
(string): Descriptive string for planetary atmosphere, e.g. "hot thick carbon dioxide atmosphere".scan.AtmosphereType
(string): Simple string for planetary atmosphere, e.g. "CarbonDioxide".scan.AtmosphereComposition
(collection): See Collections and Iterators.scan.Volcanism
(string): Description of type of volcanic activity, e.g. "major silicate vapour geysers volcanism".scan.MassEM
(float): Mass in Earth-masses (5.972 × 10²⁴ kg).scan.Radius
(float): Radius in metres.scan.SurfaceGravity
(float): Surface gravity in m/s².scan.SurfaceTemperature
(float): Average surface temperature in Kelvin.scan.SurfacePressure
(float): Average surface pressure in Pascals.scan.Landable
(bool): Landable true/false.scan.Materials
(collection): See Collections and Iterators.scan.Composition.Ice
(float): Percentage of content.scan.Composition.Rock
(float): Percentage of content.scan.Composition.Metal
(float): Percentage of content.scan.SemiMajorAxis
(float): Orbital semi-major axis in metres.scan.Eccentricity
(float): Orbital eccentricity.scan.OrbitalInclination
(float): Orbital inclination in degrees.scan.Periapsis
(float): Argument of periapsis in degrees.scan.OrbitalPeriod
(float): Orbital period in seconds.scan.RotationPeriod
(float): Rotational period in seconds.scan.AscendingNode
(float): Longitude of ascending node in degrees.*scan.MeanAnomaly
(float): Mean anomaly in degrees.*scan.AxialTilt
(float): Axial tilt in radians.scan.Rings
(collection): See Collections and Iterators.scan.ReserveLevel
(string): Mineral reserve level description.scan.StarType
(string): Type of star, e.g. "M". (See: StarType Values)scan.Subclass
(int): Star subclass.scan.StellarMass
(float): Mass in solar masses (2×10³⁰ kg).scan.AbsoluteMagnitude
(float): Absolute magnitude of star.scan.Age_MY
(int): Age of star in millions of years.scan.Luminosity
(string): Luminosity class of star, e.g., "Va".scan.WasDiscovered
(bool): Previously discovered body true/false.scan.WasMapped
(bool): Previously mapped body true/false.
Collections and Iterators
There are several collections available when creating criteria, as well as specific Lua iterators to assist working with them. The scan
object itself contains Materials
, AtmosphereComposition
, and Rings
collections. In addition parents
and system
collections are available as independent objects.
Keep in mind these are all zero-indexed. However there are iterators provided to simplify working with them. Member properties of the objects returned by iterators are not the original objects, and are not CamelCased
.
The Materials
and AtmosphereComposition
collections within the scan
object are both similarly structured collections of materials, each of which contains a Name
and Percent
member property. They can be iterated on using the materials
iterator which returns each item in the collection as a Lua table with named indices.
for material in materials(scan.Materials) do
--Available in loop body:
--material.name
--material.percent
end
The scan.Rings
collection is a list of Ring
objects, each of which has the following member properties:
scan.Rings[n].Name
(string): Name of ring.scan.Rings[n].RingClass
(string): Type of ring.scan.Rings[n].MassMT
(float): Mass of ring in megatonnes.scan.Rings[n].InnerRad
(float): Orbital radius of inner edge of ring in metres.scan.Rings[n].OuterRad
(float): Orbital radius of outer edge of ring in metres.
As with materials, there is a rings
iterator to assist working with this collection:
for ring in rings(scan.Rings) do
--Available in loop body:
--ring.name
--ring.ringclass
--ring.massmt
--ring.innerrad
--ring.outerrad
end
The system
collection is the set of all scan events previously seen in this system, each of which is a complete scan event with all of its original member properties, as detailed above. The bodies
iterator will give you each individual scan.
for body in bodies(system) do
--Available in loop body:
--body.BodyName
--body.BodyID
--body.PlanetClass
--body.Radius
--etc.
end
Finally the parents
object is a restructuring of the "Parents" journal property. Each item in the collection has the following member properties:
ParentType
(string): "Null", "Planet", or "Star". "Null" in this case means the orbital parent is the barycenter of a binary pair.Body
(int): System specific Body ID of the parent object.Scan
(scan): Complete scan event of the parent object, if available.
The allparents
iterator is used for the parents
collection.
for parent in allparents(parents) do
--Available in loop body:
--parent.parenttype
--parent.body
--parent.scan (possibly null)
end
Global Block
Another annotation which can be used in a criteria file is ---@Global
. Like the ---@Criteria
annotation it is paired with ---@End
to create a code block.
Any Lua script contained within a ---@Global
block is executed in advance of all processing and exists within the same global state as all subsequent criteria execution. This allows you to set global variables or define your own custom functions that can be used in your criteria. While not strictly necessary it is recommended that only a single ---@Global
block be used. For details on why, see the Under The Hood section.
---@Global
function systemHasAmmoniaWorld(system)
for body in bodies(system) do
if body.PlanetClass == 'Ammonia world' then return true end
end
end
function systemHasEarthlikeWorld(system)
for body in bodies(system) do
if body.PlanetClass == 'Earthlike body' then return true end
end
end
function systemHasWaterWorld(system)
for body in bodies(system) do
if body.PlanetClass == 'Water world' then return true end
end
end
ammoniaEarthlikePair = `
ammoniaWaterPair = `
---@End
---@Complex
if ammoniaWaterPair ~= scan.StarSystem then
if (scan.PlanetClass == 'Water world' and systemHasAmmoniaWorld(system))
or (scan.PlanetClass == 'Ammonia world' and systemHasWaterWorld(system)) then
ammoniaWaterPair = scan.StarSystem
return true, 'Ammonia and Water World in same system', `
end
end
---@End
---@Complex
if ammoniaEarthlikePair ~= scan.StarSystem then
if (scan.PlanetClass == 'Earthlike body' and systemHasAmmoniaWorld(system))
or (scan.PlanetClass == 'Ammonia world' and systemHasEarthlikeWorld(system)) then
ammoniaEarthlikePair = scan.StarSystem
return true, 'Ammonia and Earth-like in same system', `
end
end
---@End
Other Events
As of Explorer 1.1 (included with Observatory 1.2) additional events can be handled by custom criteria. Each of these has its own annotation for defining its handler function. These can either be used to populate global variables that are subsequently used by other criteria, or send their own notifications directly.
Jump
Handles the FSDJump
journal event which contains the following travel and exploration related values:
jump.StarSystem
(string): Name of target system.jump.SystemAddress
(ulong): 64-bit unsigned integer uniquely identifying the current system.jump.StarPos.X
(float): X-coordinate of systemjump.StarPos.Y
(float): Y-coordinate of systemjump.StarPos.Z
(float): Z-coordinate of systemjump.Body
(string): Name of arrival bodyjump.BodyID
(int): ID of arrival bodyjump.BodyType
(string): Type of arrival bodyjump.JumpDist
(float): Distance jumpedjump.FuelUsed
(float): Fuel usedjump.FuelLevel
(float): Fuel remaining after jumpjump.BoostUsed
(int): If present indicates type of boost used for jump.- 1 - Basic Synthesis (x1.25)
- 2 - Standard Synthesis or White Dwarf (x1.5)
- 3 - Premium Synthesis (x2)
- 4 - Neutron Star (x4)
In addition to these values the jump event contains information about system economy, government, etc. For the complete details refer to the event description in Frontier's journal documentation (opens in a new tab), or Observatory's source repo (opens in a new tab).
-- Simple example storing galaxy coordinates for use in other criteria.
---@Jump
starX = jump.StarPos.X
starY = jump.StarPos.Y
starZ = jump.StarPos.Z
---@End
AllBodies
Handles the FSSAllBodiesFound
journal event which indicates all system bodies have now been scanned. Contains the following values:
allBodies.SystemName
(string): Name of system.allBodies.SystemAddress
(ulong): 64-bit unsigned integer uniquely identifying the current system.allBodies.Count
(int): Number of bodies in system.
-- Records number of bodies in current system to a table
---@AllBodies
fullyScanned.insert(allBodies.SystemName, allBodies.Count)
---@End
BodySignals
Handles the SAASignalsFound
event which occurs after surface scanning a body. Contains the following values:
bodySignals.SystemAddress
(ulong): 64-bit unsigned integer uniquely identifying the current system.bodySignals.BodyName
(string): Name of scanned body.bodySignals.BodyID
(int): ID of scanned body.bodySignals.Signals.Count
(int): Number of signal types foundbodySignals.Signals[x].Type
(string): Internal (non-localised) name of signal type, e.g.: "$SAA_SignalType_Guardian;"bodySignals.Signals[x].Type_Localised
(string): Name of signal type localised for player locale, e.g.: "Guardian"bodySignals.Signals[x].Count
(int): Total number of this signal typebodySignals.Genuses.Count
(int): Number of biological genuses foundbodySignals.Genuses[x].Genus
(string): Internal (non-localised) name of genus, e.g.: "$Codex_Ent_Stratum_Genus_Name"bodySignals.Genuses[x].Genus_Localised
(string): Name of genus localised for player locale, e.g.: "Stratum"
Note:
Signals
andGenuses
are zero-indexed .NET Lists.
-- Notifies if you find bacteria
---@BodySignals
if bodySignals.Genuses then
for i = 0, bodySignals.Genuses.Count - 1 do
if bodySignals.Genuses[i].Genus == "$Codex_Ent_Bacterial_Genus_Name;" then
notify(
"Bacteria Found",
bodySignals.BodyName,
"Why are you even notifying for this?")
end
end
end
---@End
Discovery
Handles the FSSDiscoveryScan
event which occurs after performing the initial FSS "honk". Contains the following values:
discovery.SystemName
(string): Name of systemdiscovery.SystemAddress
(ulong): 64-bit unsigned integer uniquely identifying the current systemdiscovery.BodyCount
(int): Number of bodies in systemdiscovery.NonBodyCount
(int): Number of non-body locations found by scandiscovery.Progress
(float): Percentage of system already scanned.
---@Discovery
-- HONK
notify("🪿", "Honk!", string.format("%i bodies in %s", discovery.BodyCount, discovery.SystemName))
---@End
Notifications
Explorer 1.1 also adds a new function for sending arbitraray notifications from custom criteria. These are both added to the Explorer results table and sent out to notifier plugins.
The function signature is notify(string title, string detail, string extendedDetail)
.
title
is used as the title/header of notification and also appears in the "Body Name" column in the UI.
detail
is the body of the notification and in the "Description" column of the UI.
extendedDetail
is additional notification detail that does not typically appear in popup or voice notifications, but may be handled by other notifier plugins. It is displayed in the "Details" column of the UI.
Formatting Strings
You can use string.format
for greater control over the display of numbers than the simple "largest whole number" approach of math.floor
.
string.format
accepts a string as its first argument into which you want to insert your formatted values using a set of placeholder tokens to denote where those values will be placed and how to format them, followed by the value(s) to insert.
A simple example:
string.format('Temperature: %f K, Pressure: %fPa', scan.SurfaceTemperature, scan.SurfacePressure)
This results in a string similar to "Temperature: 42.87649 K, Pressure: 15.32398Pa". Generally you won't need that level of precision in your output, so you can further refine that with something like:
string.format('Temperature: %.1f K, Pressure: %.1fPa', scan.SurfaceTemperature, scan.SurfacePressure)
Which limits the output to a single decimal place, resulting in "Temperature: 42.9 K, Pressure: 15.3Pa" for the same input. Notice that the output is rounded, rather than being truncated.
Placeholder tokens start with %
, followed by an optional format specification (.1
in the above example), and are terminated by a character indicating the type of value to expect (in this case f
for float
).
The complete set of tokens available is beyond the scope of this documentation, but those most useful within custom criteria are:
%f
- float%i
- integer%e
- exponential float%s
- string%%
- inserts a "%" symbol (a single%
is interpreted as the start of a token)
The options to format the output can specify padding, sign inclusion, and decimal places to express, and can be succinctly summarised as %{sign inclusion}{padding amount}{.decimal places}f
.
Specifying the number of decimal places works as one might expect, with the number being rounded to the places specified, as in the above example.
Padding amount is the minimum number of characters long the output will be. For example an integer padded with %5i
will insert with three leading spaces to make a total of five characters, e.g. " 26". Padding will not truncate, and numbers that exceed the padding amount will still be retained. Prefixing the padding amount with a 0
will pad with zeroes instead of spaces (e.g., %05i
resulting in "00026"), while a negative number can be specified for padding to left-justify the number, if desired (e.g., %-5i
resulting in "26 ").
A +
can be prefixed to explicity include the positive sign in your output. For example, string.format('Ascending node: %+f°', scan.AscendingNode)
to output "Ascending node: +27.15675°".
Finally, all of these can be combined to simultaneously specify sign, padding, and decimal precision:
string.format('Ascending node: %+06.1f°', scan.AscendingNode)
This results in an explictly signed number to one decimal place zero-padded to 6 characters (including the sign and decimal point):
"Ascending node: +027.2°"
As an added treat this is not custom criteria or Lua specific, and many other languages use this same style of string value insertion (C, Java, Python, PHP, Ruby, and many others!) So congratulations, you've learned something potentially useful elsewhere!
Errors
There are two wide categories that errors in scripts can fall into, syntax errors in which the script itself is not valid Lua code, and data based errors where the script language can be considered correct, but is not able to produce a usable result for a particular set of scan data.
Each of these is reported as a row in the Explorer results grid, and when encountered will automatically disable further custom criteria processing to avoid flooding the grid with a potentially very large number of error messages. In both cases you will need to go back into your settings and re-enable custom criteria after you've corrected your script.
Syntax errors are displayed by listing the description of the error, as reported by NLua, in the "Description" column, with the text of the criteria as it was presented to the Lua compiler in the "Detail" column. For simple criteria this text will differ slightly from the content of the criteria file as they are rearranged into a Lua function body. Complex criteria should be largely unchanged.
Common syntax errors include statements such as if
or for
missing their closing end
statement, or the misuse of a comparison operator (==
) when you need assignment (=
), or vise versa.
Data errors report differently in that while they still list the NLua error description they instead provide the original scan event JSON, so that the actual data being processed can be directly examined.
The most likely reason for a data error is a misspelled or incorrectly capitalised variable or property name, so check those first. Another possible cause is an attempt to access properties of objects that don't exist, such as checking parents[2].ParentType
of a scan with only one parent. These can be handled by testing for the existence of the base object before trying to access its member properties, e.g. if parents[2] and parents[2].ParentType == 'Planet' then ...
.
In both cases errors can require a close reading of the criteria script to determine the issue. If you need additional help feel free to reach out on the forums (opens in a new tab) or discord (opens in a new tab), where many people are willing to help.
Under The Hood
For those curious about the nitty gritty details of how the criteria file is manipulated into a set of Lua functions, continue reading. For those who just want to write criteria this section will probably not help, and is purely here to satisfy academic interest.
The very first step is that the initial Lua global state is created, some minor housekeeping is done (text encoding is set to UTF-8 and NLua's common language runtime package is loaded), then the iterator functions are defined. This state is torn down and refreshed from scratch any time the criteria file needs to be re-processed.
The specific code of the iterators is as follows:
function materials (material_list)
local i = 0
local count = material_list.Count
return function ()
i = i + 1
if i <= count then
return { name = material_list[i - 1].Name, percent = material_list[i - 1].Percent }
end
end
end
function rings (ring_list)
local i = 0
local count = ring_list.Count
return function ()
i = i + 1
if i <= count then
local ring = ring_list[i - 1]
return { name = ring.Name, ringclass = ring.RingClass, massmt = ring.MassMT, innerrad = ring.InnerRad, outerrad = ring.OuterRad }
end
end
end
function bodies (system_list)
local i = 0
local count = system_list.Count
return function ()
i = i + 1
if i <= count then
return system_list[i - 1]
end
end
end
function allparents (parent_list)
local i = 0
local count
if parent_list then count = parent_list.Count else count = 0 end
return function ()
i = i + 1
if i <= count then
return { parenttype = parent_list[i - 1].ParentType, body = parent_list[i - 1].Body, scan = parent_list[i - 1].Scan }
end
end
end
Once the iterator functions are created criteria file reading starts. All directives are processed, compiled, and executed against the global state immediately and sequentially in order. While this is largely immaterial for most criteria it is potentially important for ---@Global
blocks, as functions defined inside them cannot be used until after the block is processed. So if for some reason you have a function defined in a second ---@Global
block that is called in the first, it will fail.
---@Global
directive blocks are compiled as-is, with no additional processing by Observatory Explorer.
---@Complex
blocks are mostly left untouched, but are wrapped in the following function definition, where n
is simply the line number of the file that the initial directive appeared on:
function Criteria{n} (scan, parents, system, biosignals, geosignals)
--text inside ---@Complex block goes here
end
---@Simple
criteria blocks use slightly more involved parsing to build a similar function, with the values read from the initial description directive, the criteria expression, and the detail expression (if present) filled in as follows:
function Criteria{n} (scan, parents, system, biosignals, geosignals)
local result = {criteria expression here}
local detail = {detail expression here}
return result, {description here}, detail
end
result
and detail
are evaluated in advance here rather than as part of the return
statement to take advantage of Lua's more relaxed truthiness as compared to .NET, which makes building expressions slightly friendlier for most users.
Both simple and complex criteria have the same function signature within Lua, accepting the same five arguments, and returning bool, string, string
values. In the case of complex criteria no return at all is also acceptable in lieu of a false
result.
Finally, when each scan is received the system
and parents
objects are created so they can be used within the criteria function.
The system
object is a simple .NET dictionary key-value lookup, as Observatory Explorer already maintains a scan history by system.
The parents
object requires a bit more data munging, on account of the original scan.Parents
object being a pain to work with (for which reason I leave it undocumented above, despite existing as part of the scan object passed into the criteria functions). The brunt of the work is already done inside Observatory Framework, rearranging the original JSON object into something more sensible for use in various Observatory plugins. As a final step the parent body IDs are looked up in the scan history, and if found the scan is added to the parent collection that is passed to the criteria. This greatly simplifies any criteria that wants to look at parent body scan data.
Appendix
PlanetClass Values
- Metal rich body
- High metal content body
- Rocky body
- Icy body
- Rocky ice body
- Earthlike body
- Water world
- Ammonia world
- Water giant
- Water giant with life
- Gas giant with water based life
- Gas giant with ammonia based life
- Sudarsky class I gas giant
- Sudarsky class II gas giant
- Sudarsky class III gas giant
- Sudarsky class IV gas giant
- Sudarsky class V gas giant
- Helium rich gas giant
- Helium gas giant
- Barycentre*
StarType Values
- Main sequence types: O B A F G K M
- Brown dwarfs: L T Y
- Protostars: TTS AeBe
- Wolf-Rayet: W WN WNC WC WO
- Carbon stars: CS C CN CJ CH CHd
- S-type: MS S
- White dwarfs: D DA DAB DAO DAZ DAV DB DBZ DBV DO DOV DQ DC DCV DX
- Neutron star: N
- Black hole: H
- Exotic: X
- Self-descriptive:
- SupermassiveBlackHole
- A_BlueWhiteSuperGiant
- B_BlueWhiteSuperGiant
- F_WhiteSuperGiant
- G_WhiteSuperGiant
- M_RedSuperGiant
- M_RedGiant
- K_OrangeGiant
- RoguePlanet
- Nebula
- StellarRemnantNebula
Ring Classes
- eRingClass_Rocky
- eRingClass_Icy
- eRingClass_MetalRich
- eRingClass_Metalic