Facebook
From Corrupt Hamster, 3 Years ago, written in Plain Text.
Embed
Download Paste or View Raw
Hits: 187
  1.     {"slots":{"0":{"name":"slot1","type":{"methods":[],"events":[]}},"1":{"name":"slot2","type":{"methods":[],"events":[]}},"2":{"name":"slot3","type":{"methods":[],"events":[]}},"3":{"name":"slot4","type":{"methods":[],"events":[]}},"4":{"name":"slot5","type":{"methods":[],"events":[]}},"5":{"name":"slot6","type":{"methods":[],"events":[]}},"6":{"name":"slot7","type":{"methods":[],"events":[]}},"7":{"name":"slot8","type":{"methods":[],"events":[]}},"8":{"name":"slot9","type":{"methods":[],"events":[]}},"9":{"name":"slot10","type":{"methods":[],"events":[]}},"-1":{"name":"unit","type":{"methods":[],"events":[]},"_elementType":"control"},"-2":{"name":"system","type":{"methods":[],"events":[]},"_elementType":"system"},"-3":{"name":"library","type":{"methods":[],"events":[]},"_elementType":"library"}},"handlers":[{"key":"0","filter":{"slotKey":-1,"signature":"start()","args":[]},"code":"-- Garbage collection fix added by wrap.lua\ndo\n  -- Set GC pause. This more or less means by how many % memory use should increase before a garbage collection is started. Lua default is 200\n  local newPause = 110\n  local oldPause = collectgarbage(\"setpause\", newPause)\n\n  if oldPause < newPause then\n    -- DU now has a different default GC pause which is even lower. Revert back to it.\n    collectgarbage(\"setpause\", oldPause)\n  end\nend\n-- error handling code added by wrap.lua\n__wrap_lua__stopped = false\n__wrap_lua__stopOnError = false\n__wrap_lua__rethrowErrorAlways = false\n__wrap_lua__rethrowErrorIfStopped = true\n__wrap_lua__printError = true\n__wrap_lua__showErrorOnScreens = true\n\nfunction __wrap_lua__error (message)\n  if __wrap_lua__stopped then return end\n\n  -- make the traceback more readable and escape HTML syntax characters\n  message = tostring(message):gsub('\"%-%- |STDERROR%-EVENTHANDLER[^\"]*\"', 'chunk'):gsub(\"&\", \"&amp;\"):gsub(\"<\", \"&lt;\"):gsub(\">\", \"&gt;\")\n\n  local unit = unit or self or {}\n\n  if __wrap_lua__showErrorOnScreens then\n    for _, value in pairs(unit) do\n      if type(value) == \"table\" and value.setCenteredText and value.setHTML then -- value is a screen\n        if message:match(\"\\n\") then\n          value.setHTML([[\n<pre style=\"color: white; background-color: black; font-family: Consolas,monospace; font-size: 4vh; white-space: pre-wrap; margin: 1em\">\nError: ]] .. message .. [[\n</pre>]])\n        else\n          value.setCenteredText(message)\n        end\n      end\n    end\n  end\n\n  if __wrap_lua__printError and system and system.print then\n    system.print(\"Error: \" .. message:gsub(\"\\n\", \"<br>\"))\n  end\n\n  if __wrap_lua__stopOnError then\n    __wrap_lua__stopped = true\n  end\n\n  if __wrap_lua__stopped and unit and unit.exit then\n    unit.exit()\n  end\n\n  if __wrap_lua__rethrowErrorAlways or (__wrap_lua__stopped and __wrap_lua__rethrowErrorIfStopped) then\n    error(message)\n  end\nend\n\n-- in case traceback is removed or renamed\n__wrap_lua__traceback = traceback or (debug and debug.traceback) or function (arg1, arg2) return arg2 or arg1 end\n\nlocal ok, message = xpcall(function ()\n\n-- script code\n\n--------------------------------------------------------------------------------\n-- basic space autopilot bundle begins\n-- version: 2020-05-13 82cd7bd\n-- content sha256: 73a16d5558\n--------------------------------------------------------------------------------\n__lbs__version = \"2020-05-13 82cd7bd\"\ndo\n\ndo\nlocal _ENV = _ENV\npackage.preload[ \"common.Switcher\" ] = function( ... ) _ENV = _ENV;\n---@generic TItem\n---@param items TItem[]\n---@param onSwitched nil | fun(item:TItem,index:number)\nlocal function createSwitcher (items, onSwitched)\n  local self = {} ---@class Switcher\n\n  local index = 1\n\n  function self.switchToNext ()\n    if index == #items then\n      self.switchToIndex(1)\n    else\n      self.switchToIndex(index + 1)\n    end\n  end\n\n  function self.switchToPrevious ()\n    if index == 1 then\n      self.switchToIndex(#items)\n    else\n      self.switchToIndex(index - 1)\n    end\n  end\n\n  ---@param newIndex number\n  function self.switchToIndex (newIndex)\n    local previousIndex = index\n    index = newIndex\n\n    if onSwitched then\n      onSwitched(items[index], items[previousIndex])\n    end\n  end\n\n  ---@param item TItem\n  function self.switchToItem (item)\n    for itemIndex = 1, #items do\n      if items[itemIndex] == item then\n        return self.switchToIndex(itemIndex)\n      end\n    end\n\n    error(\"item was not found and cannot be switched to\")\n  end\n\n  function self.getIndex ()\n    return index\n  end\n\n  function self.getCurrent ()\n    return items[index]\n  end\n\n  return self\nend\n\nreturn { new = createSwitcher }\n\nend\nend\n\ndo\nlocal _ENV = _ENV\npackage.preload[ \"common.utils.array.add\" ] = function( ... ) _ENV = _ENV;\n--- Creates a new array that has items from all argument arrays added in sequence.\n--- concat would be a better name, but there already is table.concat that does something else.\n---@generic TItem\n---@vararg TItem[]\n---@return TItem[]\nlocal function add (...)\n  local result = {}\n  local arrays = { ... }\n\n  for arrayIndex = 1, #arrays do\n    local array = arrays[arrayIndex]\n\n    for elementIndex = 1, #array do\n      result[#result + 1] = array[elementIndex]\n    end\n  end\n\n  return result\nend\n\nreturn add\n\nend\nend\n\ndo\nlocal _ENV = _ENV\npackage.preload[ \"common.utils.array.filter\" ] = function( ... ) _ENV = _ENV;\n---@generic TItem\n---@param arr TItem[]\n---@param predicate fun(item:TItem,index:number):boolean\nlocal function filter (arr, predicate)\n  local result = {} ---@type TItem[]\n\n  for index = 1, #arr do\n    local item = arr[index]\n    if predicate(item, index) then\n      result[#result + 1] = item\n    end\n  end\n\n  return result\nend\n\nreturn filter\n\nend\nend\n\ndo\nlocal _ENV = _ENV\npackage.preload[ \"common.utils.array.findOneMatching\" ] = function( ... ) _ENV = _ENV;\n--- Returns the first item matching the predicate.\n---@generic TItem\n---@param items TItem[]\n---@param predicate fun(item:TItem):boolean\n---@return TItem|nil, number|nil\nlocal function findOneMatching (items, predicate)\n  for index, item in ipairs(items) do\n    if predicate(item) then\n      return item, index\n    end\n  end\nend\n\nreturn findOneMatching\n\nend\nend\n\ndo\nlocal _ENV = _ENV\npackage.preload[ \"common.utils.array.map\" ] = function( ... ) _ENV = _ENV;\n-- https://en.wikibooks.org/wiki/Lua_Functional_Programming/Functions\n\n---@generic TSource, TResult\n---@param array TSource[]\n---@param func fun(item:TSource,index:number):TResult\n---@return TResult[]\nlocal function map (array, func)\n  local new_array = {}\n\n  for index, value in ipairs(array) do\n    new_array[index] = func(value,index)\n  end\n\n  return new_array\nend\n\nreturn map\n\nend\nend\n\ndo\nlocal _ENV = _ENV\npackage.preload[ \"common.utils.array.orderedInsert\" ] = function( ... ) _ENV = _ENV;\nlocal format = string.format\nlocal insert = table.insert\n\n--- Insert one value into an already ordered array to a position determined by a weights dictionary.\n--- This might be a bit more efficient than (re-)sorting a short array with a custom compare function.\n--- For efficiency with larger arrays this function could use binary search (not implemented).\n---@generic TItem, TWeight\n---@param arr TItem[]\n---@param weights table<TItem, TWeight>\n---@param val TItem\n---@return number Inserted value's index in the array.\nlocal function orderedInsert (arr, weights, val)\n  if #arr == 0 then\n    arr[1] = val\n    return 1\n  end\n\n  local valWeight = weights[val]\n  if not valWeight then\n    error(format(\"value to be inserted (%s) is not in the weights table\", val))\n  end\n\n  for i = 1, #arr do\n    local nextVal = arr[i]\n    local nextWeight = weights[nextVal]\n\n    if not nextWeight then\n      error(format(\"value at index %d (%s) is not in the weights table\", i, nextVal))\n    end\n\n    if nextWeight > valWeight then\n      insert(arr, i, val)\n      return i\n    end\n  end\n\n  arr[#arr + 1] = val\n  return #arr\nend\n\nreturn orderedInsert\n\nend\nend\n\ndo\nlocal _ENV = _ENV\npackage.preload[ \"common.utils.formatDecimal\" ] = function( ... ) _ENV = _ENV;\nlocal format, match = string.format, string.match\n\n-- like string format \"%.5f\", except that trailing zeroes are removed\n---@param number number\n---@param maxDecimalDigits number\n---@return string\nlocal function formatDecimal (number, maxDecimalDigits)\n  local formatString = format(\"%%.%df\", maxDecimalDigits)\n  local numberString = format(formatString, number)\n\n  if maxDecimalDigits < 2 then\n    return numberString\n  end\n\n  return match(numberString, \"^([^.]*...-)0*$\")\nend\n\nreturn formatDecimal\n\nend\nend\n\ndo\nlocal _ENV = _ENV\npackage.preload[ \"common.utils.formatTimeWithUnits\" ] = function( ... ) _ENV = _ENV;\nlocal floor = math.floor\nlocal concat = table.concat\n\nlocal secondsInMinute = 60\nlocal secondsInHour = secondsInMinute * 60\nlocal secondsInDay = secondsInHour * 24\nlocal secondsInYear = 365.2419 * secondsInDay\n\nlocal minTotalSecondsToShowOnlyYears = secondsInYear * 10\n\n---@param totalSeconds number\n---@param maxComponents nil|number\nlocal function formatTimeWithUnits (totalSeconds, maxComponents)\n  maxComponents = maxComponents or 2\n\n  local buffer = {}\n\n  if totalSeconds < 0 then\n    buffer[#buffer + 1] = \"-\"\n    totalSeconds = -totalSeconds\n    maxComponents = maxComponents + 1\n  end\n\n  local showOnlyYears = totalSeconds > minTotalSecondsToShowOnlyYears\n\n  local years = floor(totalSeconds / secondsInYear)\n  if years > 0 then buffer[#buffer + 1] = years .. \"y\" end\n\n  if #buffer < maxComponents and not showOnlyYears then\n    local days = floor(totalSeconds % secondsInYear / secondsInDay)\n    if days > 0 then buffer[#buffer + 1] = days .. \"d\" end\n  end\n\n  if #buffer < maxComponents and not showOnlyYears then\n    local hours = floor(totalSeconds % secondsInDay / secondsInHour)\n    if hours > 0 then buffer[#buffer + 1] = hours .. \"h\" end\n  end\n\n  if #buffer < maxComponents and not showOnlyYears then\n    local minutes = floor(totalSeconds % secondsInHour / secondsInMinute)\n    if minutes > 0 then buffer[#buffer + 1] = minutes .. \"m\" end\n  end\n\n  if #buffer < maxComponents and not showOnlyYears then\n    local seconds = floor(totalSeconds % secondsInMinute)\n    if seconds > 0 then buffer[#buffer + 1] = seconds .. \"s\" end\n  end\n\n  if #buffer == 0 then return \"0s\" end\n\n  return concat(buffer, \" \")\nend\n\nreturn formatTimeWithUnits\n\nend\nend\n\ndo\nlocal _ENV = _ENV\npackage.preload[ \"common.utils.json\" ] = function( ... ) _ENV = _ENV;\n-- Extracts values from a JSON string with pattern matching\n-- This is faster than using dkjson when only a few fields are needed\n\n-- Use this only with trusted data sources! Limitations:\n-- * Character escapes are not supported\n-- * Field nesting is ignored\n\nlocal find, gsub = string.find, string.gsub\n\n---@param json string\n---@param key string\n---@param init number|nil\n---@return string|nil, number|nil, number|nil\nlocal function extractStringJsonValue (json, key, init)\n  local pattern = [[\"]] .. key .. [[\"%s*:%s*\"([^\"]*)\"]]\n  local startIndex, endIndex, valueStr = find(json, pattern, init)\n  return valueStr, startIndex, endIndex\nend\n\n---@param json string\n---@param key string\n---@param init number|nil\n---@return number|nil, number|nil, number|nil\nlocal function extractNumberJsonValue (json, key, init)\n  local pattern = [[\"]] .. key .. [[\"%s*:%s*(-?[0-9.e-]+)]]\n  local startIndex, endIndex, valueStr = find(json, pattern, init)\n  return tonumber(valueStr), startIndex, endIndex\nend\n\n---@param json string\n---@param key string\n---@param init number|nil\n---@return boolean|nil, number|nil, number|nil\nlocal function extractBooleanJsonValue (json, key, init)\n  local pattern = [[\"]] .. key .. [[\"%s*:%s*([truefals]+)]]\n  local startIndex, endIndex, valueStr = find(json, pattern, init)\n\n  if valueStr == \"true\" then\n    return true, startIndex, endIndex\n  elseif valueStr == \"false\" then\n    return false, startIndex, endIndex\n  else\n    return nil\n  end\nend\n\n---@param extractJsonValue function\n---@param json string\n---@param key string\n---@param stopAfterIndex number|nil\n---@param stopAfterValue any|nil\n---@return any[]\nlocal function extractAllJsonValues (extractJsonValue, json, key, stopAfterIndex, stopAfterValue)\n  local values = {}\n  local valuesLen = 0\n\n  local jsonPos = 1\n  local value, valueStartIndex, valueEndIndex -- luacheck: ignore valueStartIndex -- unused\n\n  repeat\n    value, valueStartIndex, valueEndIndex = extractJsonValue(json, key, jsonPos)\n\n    if value ~= nil then\n      valuesLen = valuesLen + 1\n      values[valuesLen] = value\n\n      jsonPos = valueEndIndex + 1\n    end\n\n    if value == stopAfterValue then break end\n    if valuesLen == stopAfterIndex then break end\n  until value == nil\n\n  return values\nend\n\n---@param json string\n---@param key string\n---@param stopAfterIndex number|nil\n---@param stopAfterValue string|nil\n---@return string[]\nlocal function extractAllStringJsonValues (json, key, stopAfterIndex, stopAfterValue)\n  return extractAllJsonValues(extractStringJsonValue, json, key, stopAfterIndex, stopAfterValue)\nend\n\n---@param json string\n---@param key string\n---@param stopAfterIndex number|nil\n---@param stopAfterValue number|nil\n---@return number[]\nlocal function extractAllNumberJsonValues (json, key, stopAfterIndex, stopAfterValue)\n  return extractAllJsonValues(extractNumberJsonValue, json, key, stopAfterIndex, stopAfterValue)\nend\n\n---@param json string\n---@param key string\n---@param stopAfterIndex number|nil\n---@param stopAfterValue boolean|nil\n---@return boolean[]\nlocal function extractAllBooleanJsonValues (json, key, stopAfterIndex, stopAfterValue)\n  return extractAllJsonValues(extractBooleanJsonValue, json, key, stopAfterIndex, stopAfterValue)\nend\n\n---@param json string\n---@param key string\n---@return string\nlocal function deleteAllStringJsonValues (json, key)\n  local pattern = [[%s*\"]] .. key .. [[\"%s*:%s*\"[^\"]*\"%s*,?]]\n  return (gsub(json, pattern, \"\"))\nend\n\n---@param json string\n---@param key string\n---@return string\nlocal function deleteAllNumberJsonValues (json, key)\n  local pattern = [[%s*\"]] .. key .. [[\"%s*:%s*-?[0-9.e-]+%s*,?]]\n  return (gsub(json, pattern, \"\"))\nend\n\n---@param json string\n---@param key string\n---@return string\nlocal function deleteAllBooleanJsonValue (json, key)\n  local pattern = [[%s*\"]] .. key .. [[\"%s*:%s*[truefals]+%s*,?]]\n  return (gsub(json, pattern, \"\"))\nend\n\nreturn {\n  extractStringJsonValue = extractStringJsonValue,\n  extractNumberJsonValue = extractNumberJsonValue,\n  extractBooleanJsonValue = extractBooleanJsonValue,\n\n  extractAllStringJsonValues = extractAllStringJsonValues,\n  extractAllNumberJsonValues = extractAllNumberJsonValues,\n  extractAllBooleanJsonValues = extractAllBooleanJsonValues,\n\n  deleteAllStringJsonValues = deleteAllStringJsonValues,\n  deleteAllNumberJsonValues = deleteAllNumberJsonValues,\n  deleteAllBooleanJsonValue = deleteAllBooleanJsonValue\n}\n\nend\nend\n\ndo\nlocal _ENV = _ENV\npackage.preload[ \"common.utils.makeCounter\" ] = function( ... ) _ENV = _ENV;\nlocal maxinteger = math.maxinteger\n\n---@param startValue number\nreturn function (startValue)\n  startValue = startValue or 0\n  assert(type(startValue) == \"number\", \"startValue must be a number\")\n\n  local value = startValue\n\n  return function ()\n    local valueToReturn = value\n\n    if value < maxinteger then\n      value = value + 1\n    else\n      value = 0\n    end\n\n    return valueToReturn\n  end\nend\n\nend\nend\n\ndo\nlocal _ENV = _ENV\npackage.preload[ \"common.utils.math.signedAngleBetween\" ] = function( ... ) _ENV = _ENV;\nlocal acos = math.acos\n\n-- angle is positive if the cross product is in the same direction as the plane normal\nlocal function signedAngleBetween(vec1, vec2, planeNormal)\n  local normVec1 = vec1:normalize()\n  local normVec2 = vec2:normalize()\n\n  local cosAngle = normVec1:dot(normVec2)\n\n  -- due to floating point inaccuracy dot product can end up slightly outside acos domain\n  if cosAngle > 1 then\n    cosAngle = 1\n  elseif cosAngle < -1 then\n    cosAngle = -1\n  end\n\n  local angle = acos(cosAngle)\n  local crossProduct = vec1:cross(vec2)\n\n  if crossProduct:dot(planeNormal) < 0 then\n    return -angle\n  else\n    return angle\n  end\nend\n\nreturn signedAngleBetween\n\nend\nend\n\ndo\nlocal _ENV = _ENV\npackage.preload[ \"common.utils.string.firstLetterToUpperCase\" ] = function( ... ) _ENV = _ENV;\n---@param str string\nlocal function firstLetterToUpperCase (str)\n  local result = str:gsub(\"^%l\", function (ch)\n    return ch:upper()\n  end)\n\n  return result\nend\n\nreturn firstLetterToUpperCase\n\nend\nend\n\ndo\nlocal _ENV = _ENV\npackage.preload[ \"common.utils.string.wordsToCamelCase\" ] = function( ... ) _ENV = _ENV;\n---@param str string\nlocal function wordsToCamelCase (str)\n  local filteredStr = str\n    :lower()\n    :gsub(\"[^%a%d ]\", \" \")\n    :gsub(\" +\", \" \")\n\n  local result = filteredStr:gsub(\" (.)\", function (ch)\n    return ch:upper()\n  end)\n\n  return result\nend\n\nreturn wordsToCamelCase\n\nend\nend\n\ndo\nlocal _ENV = _ENV\npackage.preload[ \"common.utils.table.assign\" ] = function( ... ) _ENV = _ENV;\n-- Copies values of source tables into the target table and returns the target table\n-- (like JavaScript's Object.assign)\n\n---@generic TTargetTable, TCopiedTable\n---@param targetTable TTargetTable\n---@vararg TCopiedTable\n---@return TTargetTable | TCopiedTable\nlocal function assign (targetTable, ...)\n  local sourceTables = { ... }\n\n  for i = 1, #sourceTables do\n    for key, value in pairs(sourceTables[i]) do\n      targetTable[key] = value\n    end\n  end\n\n  return targetTable\nend\n\nreturn assign\n\nend\nend\n\ndo\nlocal _ENV = _ENV\npackage.preload[ \"du.CoordinateConverter\" ] = function( ... ) _ENV = _ENV;\nlocal unpack = table.unpack\n\nlocal function createCoordinateConverter (params)\n  params = params or {}\n\n  local library = params.library or library\n\n  local rightAxis = { 0, 0, 0 }\n  local forwardAxis = { 0, 0, 0 }\n  local upAxis = { 0, 0, 0 }\n\n  local self = {} ---@class CoordinateConverter\n\n  ---@param core Core\n  function self.setAxesFromCore (core)\n    rightAxis = core.getConstructWorldOrientationRight()\n    forwardAxis = core.getConstructWorldOrientationForward()\n    upAxis = core.getConstructWorldOrientationUp()\n  end\n\n  ---@param gyro Gyro\n  function self.setAxesFromGyro (gyro)\n    rightAxis = gyro.worldRight()\n    forwardAxis = gyro.worldForward()\n    upAxis = gyro.worldUp()\n  end\n\n  function self.relWorldToRightForwardUp (relWorldCoords)\n    if relWorldCoords.x then\n      relWorldCoords = { relWorldCoords:unpack() }\n    end\n\n    return library.systemResolution3(rightAxis, forwardAxis, upAxis, relWorldCoords)\n  end\n\n  function self.rightForwardUpToRelWorld (rightForwardUpCoords)\n    if rightForwardUpCoords.x then\n      rightForwardUpCoords = { rightForwardUpCoords:unpack() }\n    end\n\n    local rightX, rightY, rightZ = unpack(rightAxis)\n    local forwardX, forwardY, forwardZ = unpack(forwardAxis)\n    local upX, upY, upZ = unpack(upAxis)\n\n    local rfuX, rfuY, rfuZ = unpack(rightForwardUpCoords)\n\n    -- rel = rfuX * right + rfuY * fwd + rfuZ * up\n\n    local relX = rfuX * rightX + rfuY * forwardX + rfuZ * upX\n    local relY = rfuX * rightY + rfuY * forwardY + rfuZ * upY\n    local relZ = rfuX * rightZ + rfuY * forwardZ + rfuZ * upZ\n\n    return { relX, relY, relZ }\n  end\n\n  return self\nend\n\nreturn { new = createCoordinateConverter }\n\nend\nend\n\ndo\nlocal _ENV = _ENV\npackage.preload[ \"du.Hal\" ] = function( ... ) _ENV = _ENV;\n-- \"hardware abstraction layer\" ;)\n-- more exactly, a slot abstraction layer\n\nlocal filter = require \"common.utils.array.filter\"\nlocal findOneMatching = require \"common.utils.array.findOneMatching\"\nlocal firstLetterToUpperCase = require \"common.utils.string.firstLetterToUpperCase\"\nlocal orderedInsert = require \"common.utils.array.orderedInsert\"\nlocal wordsToCamelCase = require \"common.utils.string.wordsToCamelCase\"\n\nlocal sort = table.sort\n\n---@type Control\nlocal self = self or unit or {} -- in-game, self is the active control unit\n\n---@class Hal\nlocal Hal = {\n  classes = {\n    AtmoFuelContainer = \"AtmoFuelContainer\",\n    RocketFuelContainer = \"RocketFuelContainer\",\n    SpaceFuelContainer = \"SpaceFuelContainer\"\n  },\n\n  slotNames = {},\n\n  elementType = {},\n  elementInSlot = {},\n  elementSlotName = {},\n\n  elements = {}\n  -- containers, databanks, etc. are added later\n}\n\nlocal function isAntiGravityGenerator (element) return element.setBaseAltitude end\nlocal function isControl (element) return element.setTimer and element.exit end\nlocal function isContainer (element) return element.getItemsMass end\nlocal function isCore (element) return element.spawnNumberSticker end\nlocal function isDatabank (element) return element.getNbKeys end\nlocal function isDynamicCore (element) return isCore(element) and element.getConstructCrossSection end\nlocal function isElementWithState (element) return element.getState end\nlocal function isEngine (element) return element.getMaxThrust end\nlocal function isGyro (element) return element.worldUp end\nlocal function isIndustry (element) return element.getCycleCountSinceStartup end\nlocal function isLibrary (element) return element.systemResolution3 end\nlocal function isPvpRadar (element) return element.getWidgetType and element.getWidgetType() == \"radar\" end\nlocal function isRadar (element) return element.getEntries and element.getConstructWorldPos end\nlocal function isScreen (element) return element.setCenteredText and element.setHTML end\nlocal function isSystem (element) return element.getTime end\nlocal function isTelemeter (element) return element.getDistance and not isEngine(element) end\nlocal function isWeapon (element) return element.getWidgetType and element.getWidgetType() == \"weapon\" end\nlocal function isMaybePressableElement (element)\n  return\n    isElementWithState(element)\n    and not isAntiGravityGenerator(element)\n    and not isEngine(element)\n    and not isGyro(element)\n    and not isScreen(element)\n    and not isTelemeter(element)\nend\n\nlocal elementTypes = {\n  { predicate = isAntiGravityGenerator, singular = \"anti gravity generator\", plural = \"anti gravity generators\" },\n  { predicate = isControl, singular = \"control\", plural = \"controls\" },\n  { predicate = isContainer,  singular = \"container\", plural = \"containers\" },\n  { predicate = isCore, singular = \"core\", plural = \"cores\" },\n  { predicate = isDatabank,  singular = \"databank\", plural = \"databanks\" },\n  { predicate = isDynamicCore, singular = \"dynamic core\", plural = \"dynamic cores\" },\n  { predicate = isElementWithState, singular = \"element with state\", plural = \"elements with state\" },\n  { predicate = isEngine, singular = \"engine\", plural = \"engines\" },\n  { predicate = isGyro, singular = \"gyro\", plural = \"gyros\" },\n  { predicate = isIndustry, singular = \"industry\", plural = \"industries\" },\n  { predicate = isLibrary, singular = \"library\", plural = \"libraries\" },\n  { predicate = isPvpRadar, singular = \"PVP radar\", plural = \"PVP radars\" },\n  { predicate = isRadar, singular = \"radar\", plural = \"radars\" },\n  { predicate = isScreen, singular = \"screen\", plural = \"screens\" },\n  { predicate = isSystem, singular = \"system\", plural = \"systems\" },\n  { predicate = isTelemeter, singular = \"telemeter\", plural = \"telemeters\" },\n  { predicate = isWeapon, singular = \"weapon\", plural = \"weapons\" },\n  { predicate = isMaybePressableElement, singular = \"maybe pressable element\", plural = \"maybe pressable elements\" }\n}\n\n-- set table names, getter function names and error messages for each element type\n\nfor _, elementType in pairs(elementTypes) do\n  elementType.singularCamelCase = wordsToCamelCase(elementType.singular)\n  elementType.singularPascalCase = firstLetterToUpperCase(elementType.singularCamelCase)\n\n  elementType.pluralCamelCase = wordsToCamelCase(elementType.plural)\n  elementType.pluralPascalCase = firstLetterToUpperCase(elementType.pluralCamelCase)\n\n  elementType.typeName = elementType.singularCamelCase\n  elementType.tableName = elementType.pluralCamelCase\n\n  elementType.requireOneFunctionName = \"require\" .. elementType.singularPascalCase\n  elementType.requireAtLeastOneFunctionName = \"require\" .. elementType.pluralPascalCase\n\n  elementType.requireOneErrorMessage = firstLetterToUpperCase(elementType.singular) .. \" is not connected.\"\n  elementType.requireAtLeastOneErrorMessage = \"No \" .. elementType.plural .. \" are connected.\"\nend\n\n-- add getter functions\n\nfor _, elementType in pairs(elementTypes) do\n  Hal[elementType.requireOneFunctionName] = function ()\n    return Hal[elementType.tableName][1] or error(elementType.requireOneErrorMessage)\n  end\n\n  Hal[elementType.requireAtLeastOneFunctionName] = function ()\n    local elements = Hal[elementType.tableName]\n    if #elements < 1 then error(elementType.requireAtLeastOneErrorMessage) end\n    return elements\n  end\nend\n\n-- detect elements and slot names\n\nlocal unsortedElements = {}\n\nfor key, value in pairs(self) do\n  if type(key) == \"string\" and type(value) == \"table\" and type(value.export) == \"table\" then\n    local slotName, element = key, value\n\n    Hal.slotNames[#Hal.slotNames + 1] = slotName\n    Hal.elementInSlot[slotName] = element\n    Hal.elementSlotName[element] = slotName\n\n    unsortedElements[#unsortedElements + 1] = element\n  end\nend\n\n-- sort elements and slot names\n\nsort(Hal.slotNames)\n\nfor _, element in ipairs(unsortedElements) do\n  orderedInsert(Hal.elements, Hal.elementSlotName, element)\nend\n\n-- organize elements by type\n\nfor _, elementType in ipairs(elementTypes) do\n  local elementTable = {}\n  local elementTypePredicate = elementType.predicate\n\n  Hal[elementType.tableName] = elementTable\n\n  for _, element in ipairs(Hal.elements) do\n    if elementTypePredicate(element) then\n      elementTable[#elementTable + 1] = element\n      Hal.elementType[element] = elementType.typeName\n    end\n  end\nend\n\n--- used by the annotation generator\n---@private\nHal._elementTypes = elementTypes\n\n---@param class string\nfunction Hal.getElementClassPredicate (class)\n  ---@param element Element\n  return function (element)\n    return element.getElementClass and element.getElementClass():match(class) and true or false\n  end\nend\n\n---@param class string\n---@return Element\nfunction Hal.getElementWithClass (class)\n  return findOneMatching(Hal.elements, Hal.getElementClassPredicate(class))\nend\n\n---@param class string\n---@return Element[]\nfunction Hal.getElementsWithClass (class)\n  return filter(Hal.elements, Hal.getElementClassPredicate(class))\nend\n\nreturn Hal\n\nend\nend\n\ndo\nlocal _ENV = _ENV\npackage.preload[ \"du.Timer\" ] = function( ... ) _ENV = _ENV;\n-- Sets a boolean to true each time the timer ticks\n-- This allows, for example, handling all ticked timers in system update and calling system.setScreen only once\n\nlocal getNextTimerId = require \"du.getNextTimerId\"\n\n---@param timerPeriod number\n---@param startDeactivated boolean\nlocal function createTimer (timerPeriod, startDeactivated)\n  local self = { ticked = false } ---@class Timer\n\n  local timerId\n\n  function self.getIsActive ()\n    return timerId and true or false\n  end\n\n  function self.activate ()\n    if not timerId then\n      timerId = getNextTimerId()\n      unit.setTimer(timerId, timerPeriod)\n    end\n  end\n\n  function self.deactivate ()\n    if timerId then\n      unit.stopTimer(timerId)\n      timerId = nil\n    end\n  end\n\n  function self.toggle ()\n    if self.getIsActive() then\n      self.deactivate()\n    else\n      self.activate()\n    end\n  end\n\n  function self.onStart ()\n    if not startDeactivated then\n      self.activate()\n    end\n  end\n\n  function self.onStop ()\n    self.deactivate()\n  end\n\n  function self.onTick (tickedTimerId)\n    if tostring(tickedTimerId) == tostring(timerId) then\n      self.ticked = true\n    end\n  end\n\n  return self\nend\n\nreturn { new = createTimer }\n\nend\nend\n\ndo\nlocal _ENV = _ENV\npackage.preload[ \"du.data.planets\" ] = function( ... ) _ENV = _ENV;\n-- This file was generated automatically. Do not edit.\nreturn {\n  {\n    id = 1,\n    name = \"Madis\",\n    type = \"planet\",\n    class = \"hT\",\n    gravity = 3.5325,\n    radius = 44300,\n    pos = {17465536, 22665536, -34464}\n  },\n  {\n    id = 10,\n    parentId = 1,\n    name = \"Madis Moon 1\",\n    type = \"moon\",\n    gravity = 0.785,\n    radius = 10000,\n    pos = {17448118.86, 22966848.03, 143079.98}\n  },\n  {\n    id = 11,\n    parentId = 1,\n    name = \"Madis Moon 2\",\n    type = \"moon\",\n    gravity = 0.942,\n    radius = 12000,\n    pos = {17194626, 22243633.88, -214962.81}\n  },\n  {\n    id = 12,\n    parentId = 1,\n    name = \"Madis Moon 3\",\n    type = \"moon\",\n    gravity = 1.1775,\n    radius = 15000,\n    pos = {17520617.44, 22184726.9, -309986.22}\n  },\n  {\n    id = 2,\n    name = \"Alioth\",\n    type = \"planet\",\n    class = \"M\",\n    gravity = 9.891,\n    radius = 126068,\n    pos = {-8, -8, -126303},\n    standardGravitationalParameter = 155900000000\n  },\n  {\n    id = 21,\n    parentId = 2,\n    name = \"Alioth Moon 1\",\n    type = \"moon\",\n    gravity = 2.355,\n    radius = 30000,\n    pos = {-564185.78, 233791, -167448}\n  },\n  {\n    id = 22,\n    parentId = 2,\n    name = \"Alioth Moon 4\",\n    type = \"moon\",\n    gravity = 2.380905,\n    radius = 30330,\n    pos = {-895203, 358389, -225602}\n  },\n  {\n    id = 3,\n    name = \"Thades\",\n    type = \"planet\",\n    class = \"T\",\n    gravity = 4.867,\n    radius = 49000,\n    pos = {29165536, 10865536, 65536}\n  },\n  {\n    id = 30,\n    parentId = 3,\n    name = \"Thades Moon 1\",\n    type = \"moon\",\n    gravity = 1.099,\n    radius = 14000,\n    pos = {29214403.49, 10907080.695, 433861.28}\n  },\n  {\n    id = 31,\n    parentId = 3,\n    name = \"Thades Moon 2\",\n    type = \"moon\",\n    gravity = 1.1775,\n    radius = 15000,\n    pos = {29404194.34, 10432766.6, 19553.824}\n  },\n  {\n    id = 4,\n    name = \"Talemai\",\n    type = \"planet\",\n    class = \"M\",\n    gravity = 4.553,\n    radius = 57500,\n    pos = {-13234464, 55765536, 465536}\n  },\n  {\n    id = 5,\n    name = \"Feli\",\n    type = \"planet\",\n    class = \"M\",\n    gravity = 4.71,\n    radius = 41800,\n    pos = {-43534464, 22565536, -48934464}\n  },\n  {\n    id = 50,\n    parentId = 5,\n    name = \"Feli Moon 1\",\n    type = \"moon\",\n    gravity = 1.099,\n    radius = 14000,\n    pos = {-43902841.78, 22261034.7, -48862386}\n  },\n  {\n    id = 6,\n    name = \"Sicari\",\n    type = \"planet\",\n    class = \"M\",\n    gravity = 4.0035,\n    radius = 51100,\n    pos = {52765536, 27165536, 52065536}\n  },\n  {\n    id = 7,\n    name = \"Sinnen\",\n    type = \"planet\",\n    class = \"hT\",\n    gravity = 4.3175,\n    radius = 54950,\n    pos = {58665536, 29665536, 58165536}\n  },\n  {\n    id = 70,\n    parentId = 7,\n    name = \"Sinnen Moon 1\",\n    type = \"moon\",\n    gravity = 1.3344999551773071,\n    radius = 17000,\n    pos = {58969618.12, 29797943.44, 57969448.98}\n  },\n  {\n    id = 8,\n    name = \"Teoma\",\n    type = \"planet\",\n    class = \"M\",\n    gravity = 4.7885,\n    radius = 62000,\n    pos = {80865536, 54665536, -934464}\n  },\n  {\n    id = 9,\n    name = \"Jago\",\n    type = \"planet\",\n    class = \"M\",\n    gravity = 4.9455,\n    radius = 61590,\n    pos = {-94134464, 12765536, -3634464}\n  },\n  {\n    id = 100,\n    name = \"Lacobus\",\n    type = \"planet\",\n    class = \"hP\",\n    gravity = 4.4745,\n    radius = 55650,\n    pos = {98865536, -13534464, -934464}\n  },\n  {\n    id = 101,\n    parentId = 100,\n    name = \"Lacobus Moon 3\",\n    type = \"moon\",\n    gravity = 1.1775,\n    radius = 15000,\n    pos = {98905290.17, -13950923.06, -647589.28}\n  },\n  {\n    id = 102,\n    parentId = 100,\n    name = \"Lacobus Moon 1\",\n    type = \"moon\",\n    gravity = 1.413,\n    radius = 18000,\n    pos = {99180967.44, -13783860.94, -926156.934}\n  },\n  {\n    id = 103,\n    parentId = 100,\n    name = \"Lacobus Moon 2\",\n    type = \"moon\",\n    gravity = 1.099,\n    radius = 14000,\n    pos = {99250054.22, -13629215.266, -1059341.74}\n  },\n  {\n    id = 110,\n    name = \"Symeon\",\n    type = \"planet\",\n    class = \"hP\",\n    gravity = 3.8465,\n    radius = 49050,\n    pos = {14165536, -85634464, -934464}\n  },\n  {\n    id = 120,\n    name = \"Ion\",\n    type = \"planet\",\n    class = \"hP\",\n    gravity = 3.5325,\n    radius = 44950,\n    pos = {2865536, -99034464, -934464}\n  },\n  {\n    id = 121,\n    parentId = 120,\n    name = \"Ion Moon 1\",\n    type = \"moon\",\n    gravity = 0.8635,\n    radius = 11000,\n    pos = {2472917.9, -99133746.266, -1133581.06}\n  },\n  {\n    id = 122,\n    parentId = 120,\n    name = \"Ion Moon 2\",\n    type = \"moon\",\n    gravity = 1.1775,\n    radius = 15000,\n    pos = {2995424.17, -99275008.73, -1378482.03}\n  }\n}\nend\nend\n\ndo\nlocal _ENV = _ENV\npackage.preload[ \"du.formatDistance\" ] = function( ... ) _ENV = _ENV;\nlocal formatDecimal = require \"common.utils.formatDecimal\"\n\nlocal M_IN_KM = 1000\nlocal M_IN_SU = 200000\n\n---@param distanceInMeters number\n---@param kmDisplayThreshold nil|number\n---@param suDisplayThreshold nil|number\nlocal function formatDistance (distanceInMeters, kmDisplayThreshold, suDisplayThreshold)\n  kmDisplayThreshold = kmDisplayThreshold or M_IN_KM\n  suDisplayThreshold = suDisplayThreshold or M_IN_SU\n\n  if distanceInMeters > suDisplayThreshold or distanceInMeters < -suDisplayThreshold then\n    local distanceInSu = distanceInMeters / M_IN_SU\n    return formatDecimal(distanceInSu, 2) .. \" su\"\n  elseif distanceInMeters > kmDisplayThreshold or distanceInMeters < -kmDisplayThreshold then\n    local distanceInKm = distanceInMeters / M_IN_KM\n    return formatDecimal(distanceInKm, 2) .. \" km\"\n  else\n    return formatDecimal(distanceInMeters, 2) .. \" m\"\n  end\nend\n\nreturn formatDistance\n\nend\nend\n\ndo\nlocal _ENV = _ENV\npackage.preload[ \"du.getKnownPlanets\" ] = function( ... ) _ENV = _ENV;\nlocal assign = require \"common.utils.table.assign\"\nlocal filter = require \"common.utils.array.filter\"\nlocal map = require \"common.utils.array.map\"\nlocal planetsData = require \"du.data.planets\"\n\n---@class Planet\n---@field id number\n---@field parentId nil|number\n---@field name string\n---@field type string\n---@field class nil|string\n---@field gravity number\n---@field radius number\n---@field pos table vec3\n---@field coreAltitudeOffset nil|number the difference between the altitude reported by the core and the \"real\" altitude\n---@field standardGravitationalParameter nil|number a better estimate of the standard gravitational parameter\n\nlocal function isPlanetDataPresent (planetData)\n  return\n    planetData and\n    planetData.gravity and\n    planetData.name and\n    planetData.radius and\n    planetData.pos\nend\n\n---@return Planet[]\nlocal function getKnownPlanets ()\n  local planetsWithoutMissingAttributes = filter(planetsData, isPlanetDataPresent)\n\n  return map(planetsWithoutMissingAttributes, function (planet)\n    return assign({}, planet, {\n      pos = vec3(planet.pos)\n    })\n  end)\nend\n\nreturn getKnownPlanets\n\nend\nend\n\ndo\nlocal _ENV = _ENV\npackage.preload[ \"du.getNextTimerId\" ] = function( ... ) _ENV = _ENV;\n-- In v0.8, timer ids were shared by locally running scripts and max timer id was 2147483647 (2^31-1).\n-- Since v0.10, timer ids are local to control units and are strings.\n-- Integer ids still work and can be used with * filters, but need to be compared like tostring(setTimerId) == tostring(tickedTimerId).\n\nif _timerIdCounter then\n  return _timerIdCounter\nend\n\nlocal makeCounter = require \"common.utils.makeCounter\"\n\nlocal firstTimerId = (__sessionId or math.random(0, 999999999)) % 2000000000 // 10000 * 10000\n_timerIdCounter = makeCounter(firstTimerId)\n\nreturn function ()\n  return tostring(_timerIdCounter())\nend\n\nend\nend\n\ndo\nlocal _ENV = _ENV\npackage.preload[ \"du.math.estimateBrakingDistance\" ] = function( ... ) _ENV = _ENV;\nlocal cos, sin, sqrt = math.cos, math.sin, math.sqrt\n\nlocal c = 30000 / 3.6 -- m/s\n\n---@param force number\n---@param restMass number\n---@param currentSpeed number\n---@param brakeTime number\n---@return number\nlocal function estimateBrakingDistance (force, restMass, currentSpeed, brakeTime)\n  local F, m0, v0, u = force, restMass, currentSpeed, brakeTime\n\n  -- integrate [ c sin((F t)/(c m0) + sin^(-1)(v0/c)) dt ] t=0..u\n  -- d = (c (c m0 sqrt(1 - v0^2/c^2) - c m0 sqrt(1 - v0^2/c^2) cos((F u)/(c m0)) + m0 v0 sin((F u)/(c m0))))/F\n  -- a = c m0 sqrt(1 - v0^2/c^2); b = (F u)/(c m0); d = (c (a - a cos(b) + m0 v0 sin(b)))/F\n\n  local a = c * m0 * sqrt(1 - v0 * v0 / c / c)\n  local b = (F * u) / (c * m0)\n  return (c * (a - a * cos(b) + m0 * v0 * sin(b))) / F\nend\n\nreturn estimateBrakingDistance\n\nend\nend\n\ndo\nlocal _ENV = _ENV\npackage.preload[ \"du.math.estimateBrakingTime\" ] = function( ... ) _ENV = _ENV;\nlocal asin = math.asin\n\nlocal c = 30000 / 3.6 -- m/s\n\n---@param force number\n---@param restMass number\n---@param currentSpeed number\n---@param targetSpeed number\n---@return number\nlocal function estimateBrakingTime (force, restMass, currentSpeed, targetSpeed)\n  local F, m0, v0, v_target = force, restMass, currentSpeed, targetSpeed\n\n  -- v'(t)=F / (m0 / sqrt(1 - ( (v(t))/c)**2) ), v(0)=v0\n  -- v(t)=c sin((F t)/(c m0) + sin^(-1)(v0/c))\n\n  -- solve [ c sin((F t)/(c m0) + sin^(-1)(v0/c)) = k ] for t\n  -- t = (c m0 sin^(-1)(k/c) - c m0 sin^(-1)(v0/c))/F\n\n  return (c * m0 * asin(v_target / c) - c * m0 * asin(v0 / c) ) / F\nend\n\nreturn estimateBrakingTime\n\nend\nend\n\ndo\nlocal _ENV = _ENV\npackage.preload[ \"pilot.components.CompositeComponent\" ] = function( ... ) _ENV = _ENV;\n---@param components table[]\nlocal function createCompositeComponent (components)\n  assert(type(components) == \"table\", \"components must be table\")\n\n  local self = {} ---@class CompositeComponent\n  setmetatable(self, self)\n\n  self.__index = function (tbl, key)\n    local function callEventHandlers (...)\n      for i = 1, #components do\n        local eventHandler = components[i][key]\n        if eventHandler then eventHandler(...) end\n      end\n    end\n\n    tbl[key] = callEventHandlers\n    return callEventHandlers\n  end\n\n  return self\nend\n\nreturn { new = createCompositeComponent }\n\nend\nend\n\ndo\nlocal _ENV = _ENV\npackage.preload[ \"pilot.components.DefaultWidgetComponent\" ] = function( ... ) _ENV = _ENV;\n---@class DefaultWidgetGroup\n---@field panelLabel string\n---@field widgetType string\n---@field elements Element[]\n---@field widgetPerData boolean\n\n--- Shows/hides default element widgets using the widget API.\n--- This is similar to the _autoconf.displayCategoryPanel and hideCategoryPanel helper functions that are automatically prepended after running autoconf.\n---@param system System\n---@param groups DefaultWidgetGroup[]\nlocal function createDefaultWidgetComponent (system, groups)\n  local panelIds = {} ---@type string[]\n\n  local self = {} --- @class DefaultWidgetComponent\n\n  self.onStart = function ()\n    for _, group in ipairs(groups) do\n      local panelId = system.createWidgetPanel(group.panelLabel)\n\n      if group.widgetPerData then\n        -- separate widget for each element\n        for _, element in ipairs(group.elements) do\n          local widgetId = system.createWidget(panelId, group.widgetType)\n          system.addDataToWidget(element.getDataId(), widgetId)\n        end\n      else\n        -- same widget for all elements\n        local widgetId = system.createWidget(panelId, group.widgetType)\n        for _, element in ipairs(group.elements) do\n          system.addDataToWidget(element.getDataId(), widgetId)\n        end\n      end\n\n      panelIds[#panelIds + 1] = panelId\n    end\n  end\n\n  self.onStop = function ()\n    for _, panelId in ipairs(panelIds) do\n      system.destroyWidgetPanel(panelId)\n    end\n  end\n\n  return self\nend\n\nreturn { new = createDefaultWidgetComponent }\n\nend\nend\n\ndo\nlocal _ENV = _ENV\npackage.preload[ \"pilot2.ConstructState\" ] = function( ... ) _ENV = _ENV;\n-- Represents construct's state during the flush event\n-- Caches control/core/gyro function return values until reset\n\nlocal signedAngleBetween = require \"common.utils.math.signedAngleBetween\"\n\nlocal epsilon = constants.epsilon\n\nlocal function makeReturnVec3 (fn)\n  if not fn then error(\"fn must be not be nil\") end\n  return function () return vec3(fn()) end\nend\n\nlocal ConstructState = {}\n\nfunction ConstructState.new (options)\n  options = options or {}\n\n  local self = setmetatable({ getters = {} }, ConstructState) -- lua-somewhat-minify: skip getters\n\n  if options.control then self:addControlGetters(options.control) end\n  if options.core then self:addCoreGetters(options.core) end\n  if options.gyro then self:addGyroGetters(options.gyro) end\n  if options.system then self:addSystemGetters(options.system) end\n\n  return self\nend\n\n---@param control Control\nfunction ConstructState:addControlGetters (control)\n  local getters = self.getters\n\n  getters.atmosphereDensity = control.getAtmosphereDensity\n  getters.closestPlanetInfluence = control.getClosestPlanetInfluence\nend\n\n---@param core Core\nfunction ConstructState:addCoreGetters (core)\n  local getters = self.getters\n\n  getters.constructMass = core.getConstructMass\n  getters.constructIMass = core.getConstructIMass\n  getters.constructId = core.getConstructId\n  getters.constructWorldPos = makeReturnVec3(core.getConstructWorldPos)\n  getters.constructCrossSection = makeReturnVec3(core.getConstructCrossSection)\n\n  getters.altitude = core.getAltitude\n  getters.g = core.g\n  getters.worldGravity = makeReturnVec3(core.getWorldGravity)\n  getters.worldVertical = makeReturnVec3(core.getWorldVertical)\n\n  getters.angularVelocity = makeReturnVec3(core.getAngularVelocity)\n  getters.worldAngularVelocity = makeReturnVec3(core.getWorldAngularVelocity)\n  getters.angularAcceleration = makeReturnVec3(core.getAngularAcceleration)\n  getters.worldAngularAcceleration = makeReturnVec3(core.getWorldAngularAcceleration)\n  getters.velocity = makeReturnVec3(core.getVelocity)\n  getters.worldVelocity = makeReturnVec3(core.getWorldVelocity)\n  getters.acceleration = makeReturnVec3(core.getAcceleration)\n  getters.worldAcceleration = makeReturnVec3(core.getWorldAcceleration)\n\n  getters.constructOrientationUp = makeReturnVec3(core.getConstructOrientationUp)\n  getters.constructOrientationRight = makeReturnVec3(core.getConstructOrientationRight)\n  getters.constructOrientationForward = makeReturnVec3(core.getConstructOrientationForward)\n  getters.constructWorldOrientationUp = makeReturnVec3(core.getConstructWorldOrientationUp)\n  getters.constructWorldOrientationRight = makeReturnVec3(core.getConstructWorldOrientationRight)\n  getters.constructWorldOrientationForward = makeReturnVec3(core.getConstructWorldOrientationForward)\n\n  getters.worldAirFrictionAcceleration = makeReturnVec3(core.getWorldAirFrictionAcceleration)\n  getters.worldAirFrictionAngularAcceleration = makeReturnVec3(core.getWorldAirFrictionAngularAcceleration)\n\n  -- TODO: getters for max KP for each axis\n  getters.maxKinematicsParameters = function () return core.getMaxKinematicsParametersAlongAxis(\"thrust analog longitudinal\", { self.constructOrientationForward:unpack() } ) end\n\n  getters.atmoFMaxPlus = function () return self.maxKinematicsParameters[1] end\n  getters.atmoFMaxMinus = function () return self.maxKinematicsParameters[2] end\n  getters.spaceFMaxPlus = function () return self.maxKinematicsParameters[3] end\n  getters.spaceFMaxMinus = function () return self.maxKinematicsParameters[4] end\n\n  getters.accelerationMagnitude = function () return self.worldAcceleration:len() end\n  getters.velocityMagnitude = function () return self.worldVelocity:len() end\n\n  getters.worldVelocityDirection = function () return self.worldVelocity / self.velocityMagnitude end\n\n  getters.constructOrientationDown = function () return -self.constructOrientationUp end\n  getters.constructOrientationLeft = function () return -self.constructOrientationLeft end\n  getters.constructOrientationBackward = function () return -self.constrructOrientationForward end\n  getters.constructWorldOrientationDown = function () return -self.constructWorldOrientationUp end\n  getters.constructWorldOrientationLeft = function () return -self.constructWorldOrientationRight end\n  getters.constructWorldOrientationBackward = function () return -self.constructWorldOrientationForward end\nend\n\n---@param gyro Gyro\nfunction ConstructState:addGyroGetters (gyro)\n  local getters = self.getters\n\n  getters.worldUp = makeReturnVec3(gyro.worldUp)\n  getters.worldForward = makeReturnVec3(gyro.worldForward)\n  getters.worldRight = makeReturnVec3(gyro.worldRight)\n\n  getters.worldDown = function () return -self.worldUp end\n  getters.worldBackward = function () return -self.worldForward end\n  getters.worldLeft = function () return -self.worldRight end\nend\n\n---@param system System\nfunction ConstructState:addSystemGetters (system)\n  local getters = self.getters\n\n  getters.time = system.getTime\nend\n\nfunction ConstructState:addDerivedGetters ()\n  local getters = self.getters\n\n  getters.isMovingBackward = function ()\n    return self.worldVelocity:dot(self.constructWorldOrientationForward) < 0\n  end\n  getters.isMovingTowardsGravity = function ()\n    return self.worldVelocity:dot(self.worldGravity) > 0\n  end\n  getters.isUpsideDown = function ()\n    return self.constructWorldOrientationUp:dot(self.worldVertical) > 0\n  end\n\n  getters.speed = function ()\n    return self.velocityMagnitude * (self.isMovingBackward and -1 or 1)\n  end\n\n  getters.forwardVelocity = function ()\n    return self.worldVelocity:project_on(self.constructWorldOrientationForward)\n  end\n  getters.forwardSpeed = function ()\n    return self.forwardVelocity:len() * (self.isMovingBackward and -1 or 1)\n  end\n\n  getters.verticalVelocity = function ()\n    return self.worldVelocity:project_on(self.worldGravity)\n  end\n  getters.verticalSpeed = function ()\n    return self.verticalVelocity:len() * (self.isMovingTowardsGravity and -1 or 1)\n  end\n\n  getters.groundVelocity = function ()\n    if self.g < epsilon then return vec3.zero end\n    return self.worldVelocity:project_on_plane(self.worldVertical)\n  end\n  getters.groundSpeed = function ()\n    if self.g < epsilon then return 0 end\n    return self.groundVelocity:len() * (self.isMovingBackward and -1 or 1)\n  end\n\n  getters.groundRight = function ()\n    if self.g < epsilon then return vec3.zero end\n    return self.worldVertical:cross(self.constructWorldOrientationForward)\n  end\n  getters.groundForward = function ()\n    if self.g < epsilon then return vec3.zero end\n    return -self.worldVertical:cross(self.constructWorldOrientationRight)\n  end\n\n  getters.pitch = function () -- pitch up is positive\n    return signedAngleBetween(self.groundForward, self.constructWorldOrientationForward, self.constructWorldOrientationRight)\n  end\n  getters.roll = function () -- left roll is positive\n    return signedAngleBetween(self.constructWorldOrientationRight, self.groundRight, self.constructWorldOrientationForward)\n  end\nend\n\nfunction ConstructState:__index (key)\n  local getters = rawget(self, \"getters\") -- rawget prevents infinite recursion if getters get accidentally deleted\n  local getter = getters[key]\n\n  if not getter then\n    return rawget(ConstructState, key)\n  end\n\n  local value = getter()\n  self[key] = value\n  return value\nend\n\nfunction ConstructState:reset ()\n  for key, _ in pairs(self) do\n    if key ~= \"getters\" then\n      self[key] = nil\n    end\n  end\nend\n\nreturn ConstructState\n\nend\nend\n\ndo\nlocal _ENV = _ENV\npackage.preload[ \"scripts.flight-space-autopilot.BasicAutopilotFlightMode\" ] = function( ... ) _ENV = _ENV;\nlocal json = require \"common.utils.json\"\nlocal CoordinateConverter = require \"du.CoordinateConverter\"\nlocal ConstructState = require \"pilot2.ConstructState\"\n\nlocal deg2rad, epsilon = constants.deg2rad, constants.epsilon\nlocal extractNumberJsonValue = json.extractNumberJsonValue\nlocal rangeMap = utils.map\n\nlocal KM_H_TO_M_S = 1 / 3.6\n\n-- auto-alignment settings\nlocal minAlignmentVectorLen = 0.001\nlocal radiansToSlowdownAfter = 90 * deg2rad\nlocal rotationSpeed = 0.5\n\n-- settings for converting angular velocity to angular acceleration\nlocal torqueFactor = 10 --export: Force factor applied to reach the target angular velocity.\nlocal maxAngularAcceleration = 1 --export: Decrease this value to prevent \"torque overload\" messages.\n\n-- speed correction with engines\nlocal minSpeedErrorToCorrect = 0.4 * KM_H_TO_M_S\nlocal minSpeedErrorForMaxAcceleration = 7 * KM_H_TO_M_S\n\n-- brake settings\nlocal brakeSpeedFactor = 10 --export: When braking, brake acceleration will be equal to velocity multiplied by this number.\nlocal minSpeedErrorForBraking = 0.1 * KM_H_TO_M_S\n\n---@param input number\n---@param inputForNonZeroOutput number\n---@param inputForMaxOutput number\n---@param maxOutput number\n---@return number\nlocal function getSpeedCorrection (input, inputForNonZeroOutput, inputForMaxOutput, maxOutput)\n  if input < inputForNonZeroOutput and input > -inputForNonZeroOutput then return 0 end\n\n  if input > inputForMaxOutput then return maxOutput end\n  if -input > inputForMaxOutput then return -maxOutput end\n\n  return rangeMap(input, inputForNonZeroOutput, inputForMaxOutput, 0, maxOutput)\nend\n\n--- Navigator.lua replacement for simple autopilot scripts.\n---@param control Control\n---@param core Core\n---@param system System\n---@param maxSpeed number|nil\nlocal function createAutopilotFlightMode (control, core, system, maxSpeed)\n  maxSpeed = maxSpeed or 29999 * KM_H_TO_M_S\n\n  local state = ConstructState.new {\n    control = control,\n    core = core,\n    system = system\n  }\n  state:addDerivedGetters()\n\n  local coordConverter = CoordinateConverter.new()\n\n  local self = {\n    state = state,\n    coordConverter = coordConverter\n  } ---@class BasicAutopilotFlightMode\n\n  -- functions for computing angular acceleration\n\n  ---@param targetAngularVelocity table vec3\n  function self.composeAngularAccelerationForAngularVelocity (targetAngularVelocity)\n    local currentAngularVelocity = state.worldAngularVelocity\n    local airFriction = state.worldAirFrictionAngularAcceleration\n\n    local angularVelocityError = targetAngularVelocity - currentAngularVelocity\n    local angularAcceleration = angularVelocityError * torqueFactor - airFriction\n    local angularAccelerationLength = angularAcceleration:len()\n\n    if angularAccelerationLength > maxAngularAcceleration then\n      angularAcceleration = angularAcceleration * (maxAngularAcceleration / angularAccelerationLength)\n    end\n\n    return angularAcceleration\n  end\n\n  ---@param currentVector table vec3\n  ---@param targetVector table vec3\n  ---@param angularVelocityMultiplier number|nil\n  function self.composeAngularVelocityForAxisAlignment (currentVector, targetVector, angularVelocityMultiplier)\n    angularVelocityMultiplier = angularVelocityMultiplier or 1\n\n    local vectorsAvailable = currentVector:len() >= minAlignmentVectorLen and targetVector:len() >= minAlignmentVectorLen\n    if not vectorsAvailable then return vec3.zero, nil end\n\n    local rotationVector = currentVector:cross(targetVector):normalize_inplace()\n\n    local radiansToAlignment = vectorsAvailable and currentVector:angle_between(targetVector) or 0\n    local absRadiansToAlignment = radiansToAlignment < 0 and -radiansToAlignment or radiansToAlignment\n\n    local rotationIntensity = absRadiansToAlignment / radiansToSlowdownAfter\n    if rotationIntensity > 1 then rotationIntensity = 1 end\n\n    local angularVelocity = rotationVector * rotationSpeed * rotationIntensity * angularVelocityMultiplier\n    return angularVelocity, radiansToAlignment\n  end\n\n  -- functions for computing engine acceleration\n\n  ---@param localAxis table vec3\n  ---@param worldAxis table vec3\n  ---@param isMainAxis boolean whether this is the main (usually longitudinal) axis\n  ---@param currentSpeed number current speed along the axis\n  ---@param targetSpeed number target speed alond the axis\n  function self.composeAxisAccelerationToSpeed (localAxis, worldAxis, isMainAxis, currentSpeed, targetSpeed)\n    local speedError = targetSpeed - currentSpeed\n\n    local maxKP = core.getMaxKinematicsParametersAlongAxis(\"thrust analog space_engine\", { localAxis:unpack() })\n\n    local maxPlusThrust = maxKP[3]\n    local maxMinusThrust = -maxKP[4]\n\n    local maxThrust = speedError < 0 and maxMinusThrust or maxPlusThrust\n    if maxThrust < epsilon then maxThrust = maxPlusThrust > maxMinusThrust and maxPlusThrust or maxMinusThrust end\n\n    local maxAcceleration = maxThrust / state.constructMass\n\n    local speedCorrection = getSpeedCorrection(speedError, minSpeedErrorToCorrect, minSpeedErrorForMaxAcceleration, maxAcceleration)\n    local speedCorrectionSign = speedCorrection < 0 and -1 or 1\n\n    do\n      -- acceleration at near max speed works strangely. sometimes speed will not increase past 29998.4 km/h\n\n      local atNearMaxSpeed =\n        state.velocityMagnitude > maxSpeed - 1 * KM_H_TO_M_S and\n        state.worldVelocityDirection:dot(worldAxis * speedCorrectionSign) > -0.1\n\n      if atNearMaxSpeed then\n        if isMainAxis and state.velocityMagnitude < maxSpeed - 0.2 * KM_H_TO_M_S then\n          speedCorrection = maxAcceleration\n        else\n          speedCorrection = 0\n        end\n      end\n    end\n\n    return speedCorrection * worldAxis\n  end\n\n  ---@param worldAxis table vec3\n  function self.composeAxisLiftAcceleration (worldAxis)\n    local gravityOnAxis = state.worldGravity:project_on(worldAxis)\n    return -gravityOnAxis\n  end\n\n  -- functions for computing brake acceleration\n\n  ---@param currentSpeed number\n  ---@param targetSpeed number\n  function self.composeBrakingAccelerationToSpeed (currentSpeed, targetSpeed)\n    local speedError = targetSpeed - currentSpeed\n\n    local shouldBrake =\n      -- moving too fast forward\n      targetSpeed >= 0 and currentSpeed > targetSpeed + minSpeedErrorForBraking or\n      -- moving too fast backward\n      targetSpeed <= 0 and currentSpeed < targetSpeed - minSpeedErrorForBraking or\n      -- should be moving forward, but moving backward\n      targetSpeed >= 0 and currentSpeed < -minSpeedErrorForBraking or\n      -- should be moving backward, but moving forward\n      targetSpeed <= 0 and currentSpeed > minSpeedErrorForBraking\n\n    if not shouldBrake then return vec3.zero end\n\n    local absSpeedError = speedError < 0 and -speedError or speedError\n    return -state.worldVelocityDirection * absSpeedError * brakeSpeedFactor\n  end\n\n  function self.composeBrakingAccelerationAgainstVelocity ()\n    return -state.worldVelocity * brakeSpeedFactor\n  end\n\n  function self.composeBrakingAccelerationAgainstGravity ()\n    local brakingAcceleration = -state.worldGravity\n\n    if state.verticalSpeed < 0 and state.g > epsilon then\n      brakingAcceleration = brakingAcceleration + state.worldVertical * state.verticalSpeed * brakeSpeedFactor\n    end\n\n    return brakingAcceleration\n  end\n\n  function self.getMaxBrakeForce ()\n    local controlData = control.getData()\n    return extractNumberJsonValue(controlData, \"maxBrake\") or 0 -- maxBrake is missing from getData() in r0.18 until the construct moves\n  end\n\n  function self.getMaxVerticalSpaceForce ()\n    local maxVerticalKP = core.getMaxKinematicsParametersAlongAxis(\"thrust analog vertical space_engine\", core.getConstructOrientationUp())\n    return maxVerticalKP[3]\n  end\n\n  ---@param tags string\n  ---@param acceleration table\n  ---@param angularAcceleration table\n  ---@param keepForceCollinearity boolean|nil\n  ---@param keepTorqueCollinearity boolean|nil\n  ---@param priority1SubTags string|nil\n  ---@param priority2SubTags string|nil\n  ---@param priority3SubTags string|nil\n  ---@param toleranceRatioToStopCommand number|nil\n  function self.setEngineCommand (tags, acceleration, angularAcceleration, keepForceCollinearity, keepTorqueCollinearity, priority1SubTags, priority2SubTags, priority3SubTags, toleranceRatioToStopCommand)\n    if acceleration.x then acceleration = { acceleration:unpack() } end\n    if angularAcceleration.x then angularAcceleration = { angularAcceleration:unpack() }end\n    if keepForceCollinearity == nil then keepForceCollinearity = true end\n    if keepTorqueCollinearity == nil then keepTorqueCollinearity = true end\n    if priority1SubTags == nil then priority1SubTags = \"\" end\n    if priority2SubTags == nil then priority2SubTags = \"\" end\n    if priority3SubTags == nil then priority3SubTags = \"\" end\n    if toleranceRatioToStopCommand == nil then toleranceRatioToStopCommand = 0.01 end\n\n    return control.setEngineCommand(tags, acceleration, angularAcceleration, keepForceCollinearity and 1 or 0, keepTorqueCollinearity and 1 or 0, priority1SubTags, priority2SubTags, priority3SubTags, toleranceRatioToStopCommand)\n  end\n\n  ---@param tags string\n  ---@param acceleration table\n  ---@param keepForceCollinearity boolean|nil\n  ---@param priority1SubTags string|nil\n  ---@param priority2SubTags string|nil\n  ---@param priority3SubTags string|nil\n  ---@param toleranceRatioToStopCommand number|nil\n  function self.setEngineForceCommand (tags, acceleration, keepForceCollinearity, priority1SubTags, priority2SubTags, priority3SubTags, toleranceRatioToStopCommand)\n    return self.setEngineCommand(tags, acceleration, vec3.zero, keepForceCollinearity, true, priority1SubTags, priority2SubTags, priority3SubTags, toleranceRatioToStopCommand)\n  end\n\n  ---@param tags string\n  ---@param angularAcceleration table\n  ---@param keepTorqueCollinearity boolean|nil\n  ---@param priority1SubTags string|nil\n  ---@param priority2SubTags string|nil\n  ---@param priority3SubTags string|nil\n  ---@param toleranceRatioToStopCommand number|nil\n  function self.setEngineTorqueCommand (tags, angularAcceleration, keepTorqueCollinearity, priority1SubTags, priority2SubTags, priority3SubTags, toleranceRatioToStopCommand)\n    return self.setEngineCommand(tags, vec3.zero, angularAcceleration, true, keepTorqueCollinearity, priority1SubTags, priority2SubTags, priority3SubTags, toleranceRatioToStopCommand)\n  end\n\n  function self.onBeforeFlush ()\n    state:reset()\n    coordConverter.setAxesFromCore(core)\n  end\n\n  return self\nend\n\nreturn { new = createAutopilotFlightMode }\n\nend\nend\n\ndo\nlocal _ENV = _ENV\npackage.preload[ \"scripts.flight-space-autopilot.Script\" ] = function( ... ) _ENV = _ENV;\nlocal add = require \"common.utils.array.add\"\nlocal estimateBrakingDistance = require \"du.math.estimateBrakingDistance\"\nlocal estimateBrakingTime = require \"du.math.estimateBrakingTime\"\nlocal formatDecimal = require \"common.utils.formatDecimal\"\nlocal formatDistance = require \"du.formatDistance\"\nlocal formatTimeWithUnits = require \"common.utils.formatTimeWithUnits\"\nlocal getKnownPlantets = require \"du.getKnownPlanets\"\nlocal BasicAutopilotFlightMode = require \"scripts.flight-space-autopilot.BasicAutopilotFlightMode\"\nlocal Switcher = require \"common.Switcher\"\nlocal Timer = require \"du.Timer\"\n\nlocal deg2rad, epsilon, rad2deg = constants.deg2rad, constants.epsilon, constants.rad2deg\n\nlocal clamp, rangeMap = utils.clamp, utils.map\n\nlocal M_S_TO_KM_H = 3.6\nlocal KM_H_TO_M_S = 1 / 3.6\n\nlocal minDistanceToDestination = 200000 -- 1 su (200 km)\n\nlocal targetPlanetAltitude = 80000 --export: Target altitude when autopiloting to a planet. Note that some planets (such as Feli) have a very high atmosphere.\nlocal targetMoonAltitude = 20000 --export: Target altitude when autopiloting to a moon.\nlocal targetOtherDistance = 2000 --export: Target distance when autopiloting to something that is not a moon or a planet.\n\nlocal minVerticalSpaceForce = 10000 -- this script requires vertical space engines for trajectory alignment\n\nlocal maxBrakeForceSafetyMultiplier = 0.8 -- pretend that max brake force is smaller than it is when calculating braking distance and time\nlocal extraSecondsToStop = 2 -- when calculating distance to start braking, subtract velocity multiplied by this number\n\nlocal maxSpeed = 29999 * KM_H_TO_M_S\n\nlocal destinations = add(\n  getKnownPlantets(),\n  {\n    -- {\n    --   name = 'Station \"Aspire\"',\n    --   pos = vec3(39878.2726, 142748.6596, 4991603.1015)\n    -- },\n    -- {\n    --   name = 'Station \"Port Albatross\"',\n    --   pos = vec3(358557.0366, 1837825.7063, -175376.8116)\n    -- },\n    -- {\n    --   name = 'Station \"Myriad\"',\n    --   pos = vec3(-127283.7579, 138622.4415, -80552.2078)\n    -- },\n    -- {\n    --   name = 'Station \"Themis\"',\n    --   pos = vec3(-378454.6922, 157235.9721, -155970.3064)\n    -- },\n    -- {\n    --   name = 'Station \"ICSS\"',\n    --   pos = vec3(15506477.2973, 11177900.3132, -10115.5026)\n    -- },\n    -- {\n    --   name = 'Station \"Gravity Maze\"',\n    --   pos = vec3(-414142.7786, 217120.8101, -166107.6882)\n    -- }\n  }\n)\n\n---@param control Control\n---@param core Core\n---@param system System\nlocal function createScript (control, core, system)\n  local destSwitcher = Switcher.new(destinations)\n\n  local selectPrevAction = \"option1\"\n  local selectNextAction = \"option2\"\n  local confirmSelectionAction = \"option3\"\n\n  local screenUpdateTimer = Timer.new(1)\n\n  local flight = BasicAutopilotFlightMode.new(control, core, system, maxSpeed)\n  local coordConverter = flight.coordConverter\n  local state = flight.state\n\n  local targetDest\n  local targetPos\n\n  local rfuVelocity\n  local driftLen\n\n  local radiansToTargetVelocity\n  local radiansToDrift\n  local radiansToForwardAlignment\n\n  local distanceToDest\n  local distanceToBraking\n\n  local startedBraking = false\n  local arrived = false\n\n  ---@param dest table\n  local function getTargetDistance (dest)\n    local distance = targetOtherDistance\n    if dest.type == \"planet\" then distance = targetPlanetAltitude end\n    if dest.type == \"moon\" then distance = targetMoonAltitude end\n\n    return distance + (dest.radius or 0)\n  end\n\n  ---@param dest table\n  local function getPosNearDestination (dest)\n    local constructToDest = dest.pos - state.constructWorldPos\n    local constructToDestLen = constructToDest:len()\n\n    if constructToDestLen < (dest.radius or 0) + minDistanceToDestination then\n      return false, \"too close\"\n    end\n\n    local destToResultDir\n    if state.velocityMagnitude > 5000 * KM_H_TO_M_S then\n      destToResultDir = state.worldVelocity:project_on_plane(constructToDest)\n    end\n    if not destToResultDir or destToResultDir:len() < epsilon then\n      destToResultDir = state.constructWorldOrientationUp:cross(constructToDest)\n    end\n    destToResultDir:normalize_inplace()\n\n    local targetDistance = getTargetDistance(dest)\n    return dest.pos + destToResultDir * targetDistance\n  end\n\n  local function checkAutopilotForDeparture ()\n    if flight.getMaxVerticalSpaceForce() < minVerticalSpaceForce then\n      return false, \"not enough vertical space engines\"\n    end\n\n    if control.getAtmosphereDensity() > epsilon then\n      return false, \"in atmo\"\n    end\n\n    if control.getClosestPlanetInfluence() > 0.95 then\n      return false, \"near surface\"\n    end\n\n    return true\n  end\n\n  local function getDestinationSelectionHtml ()\n    local canAutopilot, reasonCannotAutopilot = checkAutopilotForDeparture()\n    if not canAutopilot then\n      return \"Cannot autopilot (\" .. reasonCannotAutopilot .. \")\"\n    end\n\n    local gotPos, reasonForNoPos = getPosNearDestination(destSwitcher.getCurrent())\n\n    return [[\n      Select destination:<br>\n      <div style=\"font-weight: bold; padding: 0.2em 0\">]] .. destSwitcher.getCurrent().name .. (gotPos and \"\" or \" - \" .. reasonForNoPos) .. [[</div>\n      []] .. system.getActionKeyName(selectPrevAction) .. [[] Previous<br>\n      []] .. system.getActionKeyName(selectNextAction) .. [[] Next<br>\n      []] .. system.getActionKeyName(confirmSelectionAction) .. [[] Confirm<br>\n    ]]\n  end\n\n  local function updateScreen ()\n    local versionStr = __lbs__version or \"Unknown version\"\n\n    local rfuVelocityStr = rfuVelocity and tostring(rfuVelocity) or \"-\"\n    local speedStr = formatDecimal(state.velocityMagnitude * M_S_TO_KM_H, 2)\n    local driftLenStr = driftLen and formatDecimal(driftLen * M_S_TO_KM_H, 2) or \"-\"\n\n    local gravityStr = formatDecimal(state.g, 3)\n\n    local radiansToTargetVelocityStr = radiansToTargetVelocity and formatDecimal(radiansToTargetVelocity * rad2deg, 2) or \"-\"\n    local radiansToForwardAlignmentStr = radiansToForwardAlignment and formatDecimal(radiansToForwardAlignment * rad2deg, 2) or \"-\"\n    local radiansToDriftStr = radiansToDrift and formatDecimal(radiansToDrift * rad2deg, 2) or \"-\"\n\n    local selectedDestStr = targetDest and targetDest.name or \"-\"\n    local distanceToDestStr = distanceToDest and formatDistance(distanceToDest) or \"-\"\n    local distanceToBrakingStr = distanceToBraking and formatDistance(distanceToBraking) or \"-\"\n\n    local timeToBrakingStr\n    if distanceToBraking and not startedBraking and state.speed > maxSpeed * 0.9 then\n      local timeToBraking = distanceToBraking / state.speed\n      timeToBrakingStr = formatTimeWithUnits(timeToBraking)\n    end\n\n    local brakingStr = timeToBrakingStr or distanceToBrakingStr\n\n    local stateHtml = not targetDest and getDestinationSelectionHtml() or \"\"\n    local screenHtml = [[\n      <style>\n        .space-autopilot-hud {\n          position: fixed;\n          left: 3vw;\n          top: 5vh;\n          margin: 0;\n          padding: 0;\n          font-size: 1.5vh;\n        }\n        .space-autopilot-hud span {\n          font-family: Consolas, monospace;\n        }\n      </style>\n      <div class=\"space-autopilot-hud\">\n      <strong>Basic space autopilot</strong><br>\n      <span>]] .. versionStr .. [[</span><br>\n      <br>\n      Velocity: <span>]] .. rfuVelocityStr .. [[ m/s</span><br>\n      Speed: <span>]] .. speedStr .. [[ km/h</span><br>\n      Drift: <span>]] .. driftLenStr .. [[ km/h</span><br>\n      <br>\n      Gravity: <span>]] .. gravityStr .. [[ m/s<sup>2</sup></span><br>\n      <br>\n      Velocity to target: <span>]] .. radiansToTargetVelocityStr .. [[&deg;</span><br>\n      Forward to target: <span>]] .. radiansToForwardAlignmentStr .. [[&deg;</span><br>\n      Vertical to drift: <span>]] .. radiansToDriftStr .. [[&deg;</span><br>\n      <br>\n      Destination: <span>]] .. selectedDestStr .. [[</span><br>\n      Distance: <span>]] .. distanceToDestStr .. [[</span><br>\n      Braking in: <span>]] .. brakingStr .. [[</span><br>\n      <br>\n      Started braking: <span>]] .. tostring(startedBraking) ..[[</span><br>\n      Arrived: <span>]] .. tostring(arrived) .. [[</span><br>\n      <br>\n      ]] .. stateHtml .. [[</div>]]\n    system.setScreen(screenHtml)\n  end\n\n  local script = {} ---@class BasicSpaceAutopilotScript\n\n  function script.onStart ()\n    control.hide()\n\n    system.showScreen(1)\n    updateScreen()\n\n    screenUpdateTimer.onStart()\n  end\n\n  function script.onStop ()\n    system.showScreen(0)\n    system.setScreen(\"\")\n\n    screenUpdateTimer.onStop()\n  end\n\n  ---@param action string\n  function script.onActionStart (action)\n    if targetDest ~= nil or not checkAutopilotForDeparture() then return end\n\n    if action == selectPrevAction then\n      destSwitcher.switchToPrevious()\n    elseif action == selectNextAction then\n      destSwitcher.switchToNext()\n    elseif action == confirmSelectionAction then\n      local dest = destSwitcher.getCurrent()\n\n      local posNearDest = getPosNearDestination(dest)\n      if not posNearDest then return end\n\n      targetPos = posNearDest\n      targetDest = dest\n    end\n\n    updateScreen()\n  end\n\n  function script.onFlush ()\n    flight.onBeforeFlush()\n\n    rfuVelocity = vec3(coordConverter.relWorldToRightForwardUp(state.worldVelocity))\n\n    driftLen = nil\n\n    distanceToDest = nil\n    distanceToBraking = nil\n\n    radiansToTargetVelocity = nil\n    radiansToDrift = nil\n    radiansToForwardAlignment = nil\n\n    if not targetDest or arrived then\n      local brakingAcceleration = vec3.zero\n      if arrived or state.velocityMagnitude < maxSpeed * 0.5 then\n        brakingAcceleration = brakingAcceleration + flight.composeBrakingAccelerationAgainstGravity()\n      end\n      if arrived then\n        brakingAcceleration = brakingAcceleration + flight.composeBrakingAccelerationAgainstVelocity()\n      end\n\n      if state.atmosphereDensity > 0.8 then -- prevent adjustors from firing when the construct is probably landed\n        flight.setEngineTorqueCommand(\"torque\", vec3.zero)\n      else\n        flight.setEngineTorqueCommand('torque', flight.composeAngularAccelerationForAngularVelocity(vec3.zero), true)\n      end\n\n      flight.setEngineForceCommand(\"thrust analog\", vec3.zero, false)\n      flight.setEngineForceCommand(\"brake\", brakingAcceleration, false)\n\n      return\n    end\n\n    local constructToTargetPos = targetPos - state.constructWorldPos\n    local constructToTargetPosLen = constructToTargetPos:len()\n    local constructToTargetPosDir = constructToTargetPos / constructToTargetPosLen\n\n    -- compute target speed for each axis\n\n    local targetVelocity = constructToTargetPosDir * maxSpeed\n    radiansToTargetVelocity = targetVelocity:angle_between(state.worldVelocity)\n\n    local drift = state.worldVelocity:project_on_plane(targetVelocity)\n    driftLen = drift:len()\n    radiansToDrift = state.constructWorldOrientationUp:angle_between(-drift)\n\n    local rfuTargetVelocity = vec3(coordConverter.relWorldToRightForwardUp(targetVelocity))\n    local targetLateralSpeed = rfuTargetVelocity.x\n    local targetVerticalSpeed = rfuTargetVelocity.z\n    local targetLongitudinalSpeed = not startedBraking and rfuTargetVelocity.y or nil\n\n    -- check braking distance\n\n    do\n      distanceToDest = constructToTargetPosLen\n\n      local speedToDest = state.worldVelocity:project_on(constructToTargetPos):len()\n      if state.worldVelocity:dot(constructToTargetPos) < 0 then speedToDest = speedToDest * -1 end\n\n      local brakeForce = flight.getMaxBrakeForce() * maxBrakeForceSafetyMultiplier\n      local brakingTime = estimateBrakingTime(-brakeForce, state.constructMass, state.velocityMagnitude, 0)\n      local brakingDistance = estimateBrakingDistance(-brakeForce, state.constructMass, state.velocityMagnitude, brakingTime)\n\n      distanceToBraking = distanceToDest - speedToDest * extraSecondsToStop - brakingDistance\n\n      arrived = startedBraking and (\n        distanceToDest < 100 or\n        radiansToTargetVelocity > 90 * deg2rad\n      )\n    end\n\n    -- compute braking acceleration\n\n    local brakingAcceleration = vec3.zero\n    if distanceToBraking <= 0 then\n      startedBraking = true\n      brakingAcceleration = flight.composeBrakingAccelerationAgainstVelocity()\n    elseif not startedBraking then\n      brakingAcceleration = flight.composeBrakingAccelerationToSpeed(state.speed, maxSpeed) -- brake if moving backward\n\n      -- reduce forward speed if velocity is not aligned with target\n      if rfuVelocity.y > maxSpeed * 0.95 and radiansToTargetVelocity > 2 * deg2rad then\n        brakingAcceleration = flight.composeBrakingAccelerationToSpeed(rfuVelocity.y, maxSpeed * 0.95)\n      end\n\n      -- greatly reduce speed if drifting a lot\n      if state.velocityMagnitude > maxSpeed * 0.5 and driftLen > 2500 * KM_H_TO_M_S then\n        brakingAcceleration = flight.composeBrakingAccelerationToSpeed(state.velocityMagnitude, maxSpeed * 0.5)\n      end\n    end\n\n    -- compute engine acceleration\n\n    local lateralAcceleration =\n      flight.composeAxisAccelerationToSpeed(state.constructOrientationRight, state.constructWorldOrientationRight, false, rfuVelocity.x, targetLateralSpeed) +\n      flight.composeAxisLiftAcceleration(state.constructWorldOrientationRight)\n\n    local verticalAcceleration = flight.composeAxisLiftAcceleration(state.constructWorldOrientationUp)\n    if radiansToDrift < 30 * deg2rad or radiansToDrift > (180 - 30) * deg2rad then\n      verticalAcceleration = verticalAcceleration +\n        flight.composeAxisAccelerationToSpeed(state.constructOrientationUp, state.constructWorldOrientationUp, false, rfuVelocity.z, targetVerticalSpeed)\n    end\n\n\n    local longitudinalAcceleration = flight.composeAxisLiftAcceleration(state.constructWorldOrientationForward)\n    if targetLongitudinalSpeed then\n      local shouldStopAcceleratingForward = state.speed > maxSpeed * 2/3 and driftLen > 10 * KM_H_TO_M_S\n      if not shouldStopAcceleratingForward then\n        longitudinalAcceleration = longitudinalAcceleration +\n          flight.composeAxisAccelerationToSpeed(state.constructOrientationForward, state.constructWorldOrientationForward, true, rfuVelocity.y, targetLongitudinalSpeed)\n      end\n    end\n\n    -- compute angular acceleration\n\n    local forwardAlignmentAngularVelocity\n    local verticalAlignmentAngularVelocity\n\n    local alignmentSpeedFactor = clamp(rangeMap(state.velocityMagnitude, maxSpeed * 0.6, maxSpeed, 1, 0.2), 0.2, 1)\n    local driftCorrectionFactor = clamp(rangeMap(driftLen, 0.1 * KM_H_TO_M_S, 10 * KM_H_TO_M_S, 0, 1), 0, 1)\n\n    forwardAlignmentAngularVelocity, radiansToForwardAlignment = flight.composeAngularVelocityForAxisAlignment(\n      state.constructWorldOrientationForward,\n      targetVelocity,\n      alignmentSpeedFactor)\n\n    verticalAlignmentAngularVelocity = flight.composeAngularVelocityForAxisAlignment(\n      state.constructWorldOrientationUp,\n      -drift * 1 - state.worldGravity * 15 + state.constructWorldOrientationUp * 2,\n      alignmentSpeedFactor * driftCorrectionFactor)\n\n    local angularVelocity = forwardAlignmentAngularVelocity + verticalAlignmentAngularVelocity:project_on(state.constructWorldOrientationForward)\n    local angularAcceleration = flight.composeAngularAccelerationForAngularVelocity(angularVelocity)\n\n    -- apply thrust and torque to engines\n\n    flight.setEngineForceCommand(\"brake\", brakingAcceleration, false)\n\n    flight.setEngineForceCommand(\"thrust analog lateral\", lateralAcceleration, true)\n    flight.setEngineForceCommand(\"thrust analog longitudinal\", longitudinalAcceleration, true)\n    flight.setEngineForceCommand(\"thrust analog vertical\", verticalAcceleration, true)\n\n    flight.setEngineTorqueCommand('torque', angularAcceleration, true)\n  end\n\n  function script.onUpdate()\n    if screenUpdateTimer.ticked then\n      screenUpdateTimer.ticked = false\n      updateScreen()\n    end\n  end\n\n  function script.onTick (timerId)\n    screenUpdateTimer.onTick(timerId)\n  end\n\n  return script\nend\n\nreturn { new = createScript }\n\nend\nend\n\nend\n\nlocal CompositeComponent = require \"pilot.components.CompositeComponent\"\nlocal DefaultWidgetComponent = require \"pilot.components.DefaultWidgetComponent\"\nlocal Script = require \"scripts.flight-space-autopilot.Script\"\nlocal Hal = require \"du.Hal\"\n\nlocal control = Hal.requireControl()\nlocal core = Hal.requireDynamicCore()\nlocal system = Hal.requireSystem()\n\nscript = CompositeComponent.new {\n  Script.new(control, core, system),\n  DefaultWidgetComponent.new(system, {\n    {\n      panelLabel = \"Core\",\n      widgetType = \"core\",\n      elements = { core }\n    },\n    {\n      panelLabel = \"Space Fuel\",\n      widgetType = \"fuel_container\",\n      elements = Hal.getElementsWithClass(Hal.classes.SpaceFuelContainer)\n    }\n  })\n}\nscript.onStart()\n--------------------------------------------------------------------------------\n-- basic space autopilot bundle ends\n--------------------------------------------------------------------------------\n\n\n-- error handling code added by wrap.lua\nend, __wrap_lua__traceback)\nif not ok then\n  __wrap_lua__error(message)\n  if not script then script = {} end\nend"},{"key":"1","filter":{"slotKey":-1,"signature":"stop()","args":[]},"code":"if not __wrap_lua__stopped and script.onStop then\n  local ok, message = xpcall(script.onStop,__wrap_lua__traceback,unit)\n  if not ok then __wrap_lua__error(message) end\nend"},{"key":"2","filter":{"slotKey":-1,"signature":"tick(timerId)","args":[{"variable":"*"}]},"code":"if not __wrap_lua__stopped and script.onTick then\n  local ok, message = xpcall(script.onTick,__wrap_lua__traceback,timerId,unit)\n  if not ok then __wrap_lua__error(message) end\nend"},{"key":"3","filter":{"slotKey":-2,"signature":"actionStart(action)","args":[{"variable":"*"}]},"code":"if not __wrap_lua__stopped and script.onActionStart then\n  local ok, message = xpcall(script.onActionStart,__wrap_lua__traceback,action,system)\n  if not ok then __wrap_lua__error(message) end\nend"},{"key":"4","filter":{"slotKey":-2,"signature":"actionStop(action)","args":[{"variable":"*"}]},"code":"if not __wrap_lua__stopped and script.onActionStop then\n  local ok, message = xpcall(script.onActionStop,__wrap_lua__traceback,action,system)\n  if not ok then __wrap_lua__error(message) end\nend"},{"key":"5","filter":{"slotKey":-2,"signature":"actionLoop(action)","args":[{"variable":"*"}]},"code":"if not __wrap_lua__stopped and script.onActionLoop then\n  local ok, message = xpcall(script.onActionLoop,__wrap_lua__traceback,action,system)\n  if not ok then __wrap_lua__error(message) end\nend"},{"key":"6","filter":{"slotKey":-2,"signature":"update()","args":[]},"code":"if not __wrap_lua__stopped and script.onUpdate then\n  local ok, message = xpcall(script.onUpdate,__wrap_lua__traceback,system)\n  if not ok then __wrap_lua__error(message) end\nend"},{"key":"7","filter":{"slotKey":-2,"signature":"flush()","args":[]},"code":"if not __wrap_lua__stopped and script.onFlush then\n  local ok, message = xpcall(script.onFlush,__wrap_lua__traceback,system)\n  if not ok then __wrap_lua__error(message) end\nend"}],"methods":[],"events":[]}
  2.