Documentation Table of Contents

kOS: Kerbal Operating System

Full Documentation Download Getting started
PRINT "Hello World.".
PRINT "These are the documents for Kerbal Operating System.".

The do-it-yourself autopilot

kOS, or Kerbal Operating System, is a community-supported mod for the popular game Kerbal Space Program

kOS is an autopilot you script yourself. kOS is to programming, what Kerbal Space Program itself is to rocket science. You don’t have to know what you’re doing to get started, but you may find yourself learning a lot by accident as you play with it. And if you already know a lot about the topic, it will still be able to hold your interest. kOS is meant to scale with the skill level of the user. You can start off doing very small simple things with it, and get more and more into using its features as you go.

What it does

kOS introduces a few new parts that each contain a simulated computer capable of running programs written in its own scripting langauge called kerboscript. The computer has powerful smarts built in to the hardware that allow it to do complex spacecraft operations in one command, thus making it possible to make complex programs with only a few lines of script text.

The intent of kOS is to be a fully in-game item that lives inside the Kerbal’s universe. The program isn’t running on your own gaming computer, but rather it’s being run in a virtual machine that is simulated in the underlying Unity engine.

Get me Started!

There is a quickstart tutorial for people new to kOS that gets you off the ground with a very simple launching script, sufficient to be an introduction for people new to the mod. Go ahead and give it a try.

Installation

Like other Kerbal Space Program mods, simply copy the contents of the zip file into your Kerbal Space Program folder, where it will install into your GameData folder.

The mod is downloadable from a number of locations.

Documentation

The page you are looking at right now is the entry point into the full documentation of kOS. See the full table of contents here: Full Documentation.

Online Community

kOS has an active community of users willing to help each other with ideas and algorithms.

History

kOS was originally begun as a mod by a single author, Kevin Laity aka Nivekk. Although the project has undergone massive changes since then and now has a very different underlying archetecture and is under active development by a different set of people, none of that would have been possible without his original vision and work.

Tutorials

If you prefer the tutorial style of explanation, please see the following examples that walk you through examples:

Quick Start Tutorial

This is a quick start guide for the Kerbal Operating System (kOS). It is intended for those who are just starting with using kOS. It does presume you have played Kerbal Space Program before and know the basics of how to fly a rocket under manual control. It does NOT assume you know a lot about computer programming, and it will walk you through some basic first steps.

First example: Hello World

In the grand tradition of programming tutorials, the first example will be how to make a script that does nothing more than print the words “Hello World” on the screen. The purpose of this example is to show where you should put the files, how to move them about, and how to get one to run on the vessel.

Step 1: Start a new sandbox-mode game

(You can use kOS in a career mode game, but it requires a part that you have to research which isn’t available at the start of the tech tree, so this example will just use sandbox mode to keep it simple.)

Step 2: Make a vessel in the Vehicle Assembly Bay

Make the vessel contain any unmanned command core, a few hundred units of battery power, a means of recharging the battery such as a solar panel array, and the “Comptronix CX-4181 Scriptable Control System”. (From this point onward the CX-4181 Scriptable Control System part will be referred to by the acronym “SCS”.) The SCS part is located in the parts bin under the “Control” tab (the same place where RCS thrusters and Torque Wheels are found.)

_images/SCS_parts_bin.png
Step 3: Put the vessel on the launchpad

Put the vessel on the launchpad. For this first example it doesn’t matter if the vessel can actually liftoff or even has engines at all.

Step 4: Invoke the terminal

Right click for the SCS part on the vessel and then click the button that says “Open Terminal”.

Note that if the terminal is semi-transparent, this means it’s not currently selected. If you click on the terminal, then your keyboard input is directed to the terminal INSTEAD of to piloting. In other words if you type W A S D, you’ll actually get the word “wasd” to appear on the terminal, rather than the W A S D keys steering the ship. To switch back to manual control of the game instead of typing into the terminal, click outside the terminal window anywhere on the background of the screen.

Step 5: See what an interactive command is like

You should now see an old-school looking text terminal like the one shown below. Type the line:

CLEARSCREEN. PRINT "==HELLO WORLD==".

into the terminal (make sure to actually type the periods (”.”) as shown) and hit ENTER. Note that you can type it in uppercase or lowercase. kOS doesn’t care.

_images/terminal_open_1.png

The terminal will respond by showing you this:

_images/terminal_open_2.png
Step 6: Okay that’s great, but how can you make that happen in a program script instead?

Like so: Enter this command:

EDIT HELLO.

(Don’t forget the period (”.”). All commands in kOS are ended with a period. Again, you can type it in uppercase or lowercase. kOS doesn’t care.)

You should see an editor window appear, looking something like this (without the text inside because you’re starting a blank new file):

_images/editor.png

Type this text into the window:

PRINT "=========================================".
PRINT "      HELLO WORLD".
PRINT "THIS IS THE FIRST SCRIPT I WROTE IN kOS.".
PRINT "=========================================".

Click “Save” then “Exit” in the editor pop-up window.

  • Side Note: The editor font - Experienced programmers may have noticed that the editor’s font is proportional width rather than monospaced and that this is not ideal for programming work. You are right, but there is little that can be done about it for a variety of technical reasons that are too complex to go into right now.

Then on the main text terminal Enter:

RUN HELLO.

And you will see the program run, showing the text on the screen like so.

_images/hello_world1.png

Note

You can also type RUNPATH("hello") instead of RUN HELLO. The commands are slightly different but should have the same effect. You can learn about the specific difference between them later here.

Step 7: Okay, but where is this program?

To see where the “HELLO” program has been saved, Issue the command LIST FILES like this:

LIST FILES.

(Note, that the default for the LIST command is to list FILES, so you can leave the word “FILES” off if you like.)

It should look like this, showing you the HELLO program you just wrote:

_images/hello_list.png

This is a list of all the files on the currently selected VOLUME. By default, when you launch a new vessel, the currently selected VOLUME is called “1” and it’s the volume that’s stored on THAT SCS part that you are running all these commands in.

This is the local volume of that SCS part. Local volumes such at this tend to have very small limited storage, as you can see when you look at the space remaining in the list printout.

If you’re wondering where the file is stored physically on your computer, it’s represented by a section inside the persistence file for your saved game, as a piece of data associated with the SCS part. This is important because it means you can’t access the program from another vessel, and if this vessel ever crashes and the SCS part explodes, then you’ve lost the program.

Step 8: I don’t like the idea that the program is stored only on this vessel. Can’t I save it somewhere better? More permanent?

Yes. Yes you can.

There is another VOLUME that always exists called the Archive, which is also referred to as volume 0. (either name can be used in commands). The archive is conceptually stored somewhere back at Kerbin home base in the Space Center rather than on your vessel. It has infinite storage space, and does not disappear when your vessel is gone. ALSO, it actually exists across saved games - if you launch one saved game, put a new file in the Archive, and then later launch a different saved game, that file will be there in that game too.

To use the Archive, first we’ll have to introduce you to a new command, called SWITCH TO. The SWITCH TO command changes which VOLUME is the one that you are doing your work with.

To work with the archive, and create a second “hello world” file there, you issue these commands and see what they do:

SWITCH TO 0.
EDIT HELLO2. // Make a new file here that just says: PRINT "hi again".
LIST FILES.
RUN HELLO2.
SWITCH TO 1.
LIST FILES.
RUN HELLO.

But where is it stored behind the scenes? The archive is currently slightly violating the design of KSP mods that puts everything in the GameData folder. The kSP Archive is actually stored in the Ships/Script folder of your MAIN KSP home, not inside GameData.

If a file is stored inside the archive, it can actually be edited by an external text editor of your choice instead of using kOS‘s in-game editor. This is usually a much better practice once you start doing more complex things with kOS. You can also make new files in the archive folder. Just make sure that all the files end with a .ks file name suffix or kOS won’t use them.

Further reading about files and volumes:

Second Example: Doing something real

Okay that’s all basic setup stuff but you’re probably clamoring for a real example that actually does something nifty.

This example will show the crudest, most basic use of kOS just to get started. In this example we’ll make a program that will launch a vessel using progressively more and more complex checks. kOS can be used at any stage of a vessel’s flight - launching, circularizing, docking, landing,... and in fact launching is one of the simpler piloting tasks that you can do without much need of automation. Where kOS really shines is for writing scripts to do touchy sensitive tasks like landing or docking or hovering. These are the areas that can benefit from the faster reaction speed that a computer script can handle.

But in order to give you an example that you can start with from scratch, that’s easy to reload and retry from an initial point, we’ll use an example of launching.

Step 1: Make a vessel

This tutorial is designed to work with a very specific rocket design. You need to make the vessel you see here:

_images/example_2_0.png

If you prefer, you can instead download the .craft file here

Step 2: Make the start of the script

Okay, so type the lines below in an external text editor of your choice (i.e. Notepad on Windows, or TextEdit on Mac, or whatever you fancy):

//hellolaunch

//First, we'll clear the terminal screen to make it look nice
CLEARSCREEN.

//This is our countdown loop, which cycles from 10 to 0
PRINT "Counting down:".
FROM {local countdown is 10.} UNTIL countdown = 0 STEP {SET countdown to countdown - 1.} DO {
    PRINT "..." + countdown.
    WAIT 1. // pauses the script here for 1 second.
}

See those things with the two slashes (“//”)? Those are comments in the Kerboscript language and they’re just ways to write things in the program that don’t do anything - they’re there for humans like you to read so you understand what’s going on. In these examples you never actually have to type in the things you see after the slashes. They’re there for your benefit when reading this document but you can leave them out if you wish.

Save the file in your Ships/Script folder of your KSP installation under the filename “hellolaunch.ks”. DO NOT save it anywhere under GameData/kOS/. Do NOT. According to the KSP standard, normally KSP mods should put their files in GameData/[mod name], but kOS puts the archive outside the GameData folder because it represents content owned by you, the player, not content owned by the kOS mod.

By saving the file in Ships/Script, you have actually put it in your archive volume of kOS. kOS will see it there immediately without delay. You do not need to restart the game. If you do:

SWITCH TO 0.
LIST FILES.

after saving the file from your external text editor program, you will see a listing of your file “hellolaunch” right away. Okay, now copy it to your local drive and give it a try running it from there:

SWITCH TO 1.
COPYPATH("0:/HELLOLAUNCH", ""). // copies from 0 (archive) to current default location (local drive (1)).
RUN HELLOLAUNCH.
_images/example_2_1.png

Okay so the program doesn’t actually DO anything yet other than just countdown from 10 to 0. A bit of a disappointment, but we haven’t written the rest of the program yet.

You’ll note that what you’ve done is switch to the local volume (1) and then copy the program from the archive (0) to the local volume (1) and then run it from the local volume. Technically you didn’t need to do this. You could have just run it directly from the archive. For those looking at the KSP game as a bit of a role-play experience, it makes sense to never run programs directly from the archive, and instead live with the limitation that software should be copied to the craft for it to be able to run it.

Step 3: Make the script actually do something

Okay now go back into your text editor of choice and append a few more lines to the hellolaunch.ks file so it now looks like this:

//hellolaunch

//First, we'll clear the terminal screen to make it look nice
CLEARSCREEN.

//Next, we'll lock our throttle to 100%.
LOCK THROTTLE TO 1.0.   // 1.0 is the max, 0.0 is idle.

//This is our countdown loop, which cycles from 10 to 0
PRINT "Counting down:".
FROM {local countdown is 10.} UNTIL countdown = 0 STEP {SET countdown to countdown - 1.} DO {
    PRINT "..." + countdown.
    WAIT 1. // pauses the script here for 1 second.
}

UNTIL SHIP:MAXTHRUST > 0 {
    WAIT 0.5. // pause half a second between stage attempts.
    PRINT "Stage activated.".
    STAGE. // same as hitting the spacebar.
}

WAIT UNTIL SHIP:ALTITUDE > 70000.

// NOTE that it is vital to not just let the script end right away
// here.  Once a kOS script just ends, it releases all the controls
// back to manual piloting so that you can fly the ship by hand again.
// If the program just ended here, then that would cause the throttle
// to turn back off again right away and nothing would happen.

Save this file to hellolaunch.ks again, and re-copy it to your vessel that should still be sitting on the launchpad, then run it, like so:

COPYPATH("0:/HELLOLAUNCH", "").
RUN HELLOLAUNCH. // You could also say RUNPATH("hellolaunch") here.
_images/example_2_2.png

Hey! It does something now! It fires the first stage engine and launches!

But.. but wait... It doesn’t control the steering and it just lets it go where ever it will.

Most likely you had a crash with this script because it didn’t do anything to affect the steering at all, so it probably allowed the rocket to tilt over.

Step 4: Make the script actually control steering

So to fix that problem, let’s add steering control to the script.

The easy way to control steering is to use the LOCK STEERING command.

Once you have mastered the basics of kOS, you should go and read the documentation on ship steering techniques, but that’s a more advanced topic for later.

The way to use the LOCK STEERING command is to set it to a thing called a Vector or a Direction. There are several Directions built-in to kOS, one of which is called “UP”. “UP” is a Direction that always aims directly toward the sky (the center of the blue part of the navball).

So to steer always UP, just do this:

LOCK STEERING TO UP.

So if you just add this one line to your script, you’ll get something that should keep the craft aimed straight up and not let it tip over. Add the line just after the line that sets the THROTTLE, like so:

//hellolaunch

//First, we'll clear the terminal screen to make it look nice
CLEARSCREEN.

//Next, we'll lock our throttle to 100%.
LOCK THROTTLE TO 1.0.   // 1.0 is the max, 0.0 is idle.

//This is our countdown loop, which cycles from 10 to 0
PRINT "Counting down:".
FROM {local countdown is 10.} UNTIL countdown = 0 STEP {SET countdown to countdown - 1.} DO {
    PRINT "..." + countdown.
    WAIT 1. // pauses the script here for 1 second.
}

//This is the line we added
LOCK STEERING TO UP.

UNTIL SHIP:MAXTHRUST > 0 {
    WAIT 0.5. // pause half a second between stage attempts.
    PRINT "Stage activated.".
    STAGE. // same as hitting the spacebar.
}

WAIT UNTIL SHIP:ALTITUDE > 70000.

// NOTE that it is vital to not just let the script end right away
// here.  Once a kOS script just ends, it releases all the controls
// back to manual piloting so that you can fly the ship by hand again.
// If the program just ended here, then that would cause the throttle
// to turn back off again right away and nothing would happen.

Again, copy this and run it, like before. If your craft crashed in the previous step, which it probably did, then revert to the VAB and re-launch it.:

SWITCH TO 1. // should be the default already, but just in case.
COPYPATH("0:/HELLOLAUNCH", "").
RUN HELLOLAUNCH. // You could also say RUNPATH("hellolaunch") here.
_images/example_2_3.png

Now you should see the same thing as before, but now your craft will stay pointed up.

But wait - it only does the first stage and then it stops without doing the next stage? how do I fix that?

Step 5: Add staging logic

The logic for how and when to stage can be an interesting and fun thing to write yourself. This example will keep it very simple, and this is the part where it’s important that you are using a vessel that only contains liquidfuel engines. If your vessel has some booster engines, then it would require a more sophisticated script to launch it correctly than this tutorial gives you.

To add the logic to check when to stage, we introduce a new concept called the WHEN trigger. To see full documentation on it when you finish the tutorial, look for it on the Flow Control page

The quick and dirty explanation is that a WHEN section is a short section of code that you set up to run LATER rather than right now. It creates a check in the background that will constantly look for some condition to occur, and when it happens, it interrupts whatever else the code is doing, and it will run the body of the WHEN code before continuing from where it left off in the main script.

There are some complex dangers with writing WHEN triggers that can cause KSP itself to hang or stutter if you are not careful, but explaining them is beyond the scope of this tutorial. But when you want to start using WHEN triggers yourself, you really should read the section on WHEN in the Flow Control page before you do so.

The WHEN trigger we are going to add to the launch script looks like this:

WHEN MAXTHRUST = 0 THEN {
    PRINT "Staging".
    STAGE.
    PRESERVE.
}.

It says, “Whenever the maximum thrust of our vehicle is zero, then activate the next stage.” The PRESERVE keyword says, “don’t stop checking this condition just because it’s been triggered once. It should still keep checking for it again in the future.” If this block of code is inserted into the script, then it will set up a constant background check that will always hit the next stage as soon as the current stage has no thrust. UNLIKE with all the previous edits this tutorial has asked you to make to the script, this time you’re going to be asked to delete something and replace it. The new WHEN section above should actually REPLACE the existing “UNTIL SHIP:MAXTHRUST > 0” loop that you had before.

Now your script should look like this:

//hellolaunch

//First, we'll clear the terminal screen to make it look nice
CLEARSCREEN.

//Next, we'll lock our throttle to 100%.
LOCK THROTTLE TO 1.0.   // 1.0 is the max, 0.0 is idle.

//This is our countdown loop, which cycles from 10 to 0
PRINT "Counting down:".
FROM {local countdown is 10.} UNTIL countdown = 0 STEP {SET countdown to countdown - 1.} DO {
    PRINT "..." + countdown.
    WAIT 1. // pauses the script here for 1 second.
}

//This is a trigger that constantly checks to see if our thrust is zero.
//If it is, it will attempt to stage and then return to where the script
//left off. The PRESERVE keyword keeps the trigger active even after it
//has been triggered.
WHEN MAXTHRUST = 0 THEN {
    PRINT "Staging".
    STAGE.
    PRESERVE.
}.

LOCK STEERING TO UP.

WAIT UNTIL ALTITUDE > 70000.

// NOTE that it is vital to not just let the script end right away
// here.  Once a kOS script just ends, it releases all the controls
// back to manual piloting so that you can fly the ship by hand again.
// If the program just ended here, then that would cause the throttle
// to turn back off again right away and nothing would happen.

Again, relaunch the ship, copy the script as before, and run it again. This time you should see it activate your later upper stages correctly.

_images/example_2_4.png
Step 6: Now to make it turn

Okay that’s fine but it still just goes straight up! What about a gravity turn?

Well, a true and proper gravity turn is a very complex bit of math that is best left as an exercise for the reader, given that the goal of kOS is to let you write your OWN autopilot, not to write it for you. But to give some basic examples of commands, lets just make a crude gravity turn approximation that simply flies the ship like a lot of new KSP pilots learn to do it for the first time:

  • Fly straight up until your velocity is 100m/s.
  • Pitch ten degrees towards the East.
  • Continue to pitch 10 degrees down for each 100m/s of velocity.

To make this work, we introduce a new way to make a Direction, called the HEADING function. Whenever you call the function HEADING(a,b), it makes a Direction oriented as follows on the navball:

  • Point at the compass heading A.
  • Pitch up a number of degrees from the horizon = to B.

So for example, HEADING(45,10) would aim northeast, 10 degrees above the horizon. We can use this to easily set our orientation. For example:

//This locks our steering to due east, pitched 45 degrees above the horizon.
LOCK STEERING TO HEADING(90,45).

Instead of using WAIT UNTIL to pause the script and keep it from exiting, we can use an UNTIL loop to constantly perform actions until a certain condition is met. For example:

SET MYSTEER TO HEADING(90,90). //90 degrees east and pitched up 90 degrees (straight up)
LOCK STEERING TO MYSTEER. // from now on we'll be able to change steering by just assigning a new value to MYSTEER
UNTIL APOAPSIS > 100000 {
    SET MYSTEER TO HEADING(90,90). //90 degrees east and pitched up 90 degrees (straight up)
    PRINT ROUND(SHIP:APOAPSIS,0) AT (0,16). // prints new number, rounded to the nearest integer.
    //We use the PRINT AT() command here to keep from printing the same thing over and
    //over on a new line every time the loop iterates. Instead, this will always print
    //the apoapsis at the same point on the screen.
}.

This loop will continue to execute all of its instructions until the apoapsis reaches 100km. Once the apoapsis is past 100km, the loop exits and the rest of the code continues.

We can combine this with IF statements in order to have one main loop that only executes certain chunks of its code under certain conditions. For example:

SET MYSTEER TO HEADING(90,90).
LOCK STEERING TO MYSTEER.
UNTIL SHIP:APOAPSIS > 100000 { //Remember, all altitudes will be in meters, not kilometers

    //For the initial ascent, we want our steering to be straight
    //up and rolled due east
    IF SHIP:VELOCITY:SURFACE:MAG < 100 {
        //This sets our steering 90 degrees up and yawed to the compass
        //heading of 90 degrees (east)
        SET MYSTEER TO HEADING(90,90).

    //Once we pass 100m/s, we want to pitch down ten degrees
    } ELSE IF SHIP:VELOCITY:SURFACE:MAG >= 100 AND SHIP:VELOCITY:SURFACE:MAG < 200 {
        SET MYSTEER TO HEADING(90,80).
        PRINT "Pitching to 80 degrees" AT(0,15).
        PRINT ROUND(SHIP:APOAPSIS,0) AT (0,16).
    }.
}.

Each time this loop iterates, it will check the surface velocity. If the velocity is below 100m/s, it will continuously execute the first block of instructions. Once the velocity reaches 100m/s, it will stop executing the first block and start executing the second block, which will pitch the nose down to 80 degrees above the horizon.

Putting this into your script, it should look like this:

//hellolaunch

//First, we'll clear the terminal screen to make it look nice
CLEARSCREEN.

//Next, we'll lock our throttle to 100%.
LOCK THROTTLE TO 1.0.   // 1.0 is the max, 0.0 is idle.

//This is our countdown loop, which cycles from 10 to 0
PRINT "Counting down:".
FROM {local countdown is 10.} UNTIL countdown = 0 STEP {SET countdown to countdown - 1.} DO {
    PRINT "..." + countdown.
    WAIT 1. // pauses the script here for 1 second.
}

//This is a trigger that constantly checks to see if our thrust is zero.
//If it is, it will attempt to stage and then return to where the script
//left off. The PRESERVE keyword keeps the trigger active even after it
//has been triggered.
WHEN MAXTHRUST = 0 THEN {
    PRINT "Staging".
    STAGE.
    PRESERVE.
}.

//This will be our main control loop for the ascent. It will
//cycle through continuously until our apoapsis is greater
//than 100km. Each cycle, it will check each of the IF
//statements inside and perform them if their conditions
//are met
SET MYSTEER TO HEADING(90,90).
LOCK STEERING TO MYSTEER. // from now on we'll be able to change steering by just assigning a new value to MYSTEER
UNTIL SHIP:APOAPSIS > 100000 { //Remember, all altitudes will be in meters, not kilometers

    //For the initial ascent, we want our steering to be straight
    //up and rolled due east
    IF SHIP:VELOCITY:SURFACE:MAG < 100 {
        //This sets our steering 90 degrees up and yawed to the compass
        //heading of 90 degrees (east)
        SET MYSTEER TO HEADING(90,90).

    //Once we pass 100m/s, we want to pitch down ten degrees
    } ELSE IF SHIP:VELOCITY:SURFACE:MAG >= 100 {
        SET MYSTEER TO HEADING(90,80).
        PRINT "Pitching to 80 degrees" AT(0,15).
        PRINT ROUND(SHIP:APOAPSIS,0) AT (0,16).
    }.
}.

Again, copy this into your script and run it. You should see your countdown occur, then it will launch. Once the ship passes 100m/s surface velocity, it will pitch down to 80 degrees and continuously print the apoapsis until the apoapsis reaches 100km, staging if necessary. The script will then end.

_images/example_2_5.png
Step 7: Putting it all together

We now have every element of the script necessary to do a proper (albeit simple) gravity turn. We just need to extend it all the way through the ascent.

Adding additional IF statements inside our main loop will allow us to perform further actions based on our velocity. Each IF statement you see in the script below covers a 100m/s block of velocity, and will adjust the pitch 10 degrees farther down than the previous block.

You can see that with the AND statement, we can check multiple conditions and only execute that block when all of those conditions are true. We can carefully set up the conditions for each IF statement to allow a block of code to be executed no matter what our surface velocity is.

Copy this into your script and run it. It should take you nearly to orbit:

//hellolaunch

//First, we'll clear the terminal screen to make it look nice
CLEARSCREEN.

//Next, we'll lock our throttle to 100%.
LOCK THROTTLE TO 1.0.   // 1.0 is the max, 0.0 is idle.

//This is our countdown loop, which cycles from 10 to 0
PRINT "Counting down:".
FROM {local countdown is 10.} UNTIL countdown = 0 STEP {SET countdown to countdown - 1.} DO {
    PRINT "..." + countdown.
    WAIT 1. // pauses the script here for 1 second.
}

//This is a trigger that constantly checks to see if our thrust is zero.
//If it is, it will attempt to stage and then return to where the script
//left off. The PRESERVE keyword keeps the trigger active even after it
//has been triggered.
WHEN MAXTHRUST = 0 THEN {
    PRINT "Staging".
    STAGE.
    PRESERVE.
}.

//This will be our main control loop for the ascent. It will
//cycle through continuously until our apoapsis is greater
//than 100km. Each cycle, it will check each of the IF
//statements inside and perform them if their conditions
//are met
SET MYSTEER TO HEADING(90,90).
LOCK STEERING TO MYSTEER. // from now on we'll be able to change steering by just assigning a new value to MYSTEER
UNTIL SHIP:APOAPSIS > 100000 { //Remember, all altitudes will be in meters, not kilometers

    //For the initial ascent, we want our steering to be straight
    //up and rolled due east
    IF SHIP:VELOCITY:SURFACE:MAG < 100 {
        //This sets our steering 90 degrees up and yawed to the compass
        //heading of 90 degrees (east)
        SET MYSTEER TO HEADING(90,90).

    //Once we pass 100m/s, we want to pitch down ten degrees
    } ELSE IF SHIP:VELOCITY:SURFACE:MAG >= 100 AND SHIP:VELOCITY:SURFACE:MAG < 200 {
        SET MYSTEER TO HEADING(90,80).
        PRINT "Pitching to 80 degrees" AT(0,15).
        PRINT ROUND(SHIP:APOAPSIS,0) AT (0,16).

    //Each successive IF statement checks to see if our velocity
    //is within a 100m/s block and adjusts our heading down another
    //ten degrees if so
    } ELSE IF SHIP:VELOCITY:SURFACE:MAG >= 200 AND SHIP:VELOCITY:SURFACE:MAG < 300 {
        SET MYSTEER TO HEADING(90,70).
        PRINT "Pitching to 70 degrees" AT(0,15).
        PRINT ROUND(SHIP:APOAPSIS,0) AT (0,16).

    } ELSE IF SHIP:VELOCITY:SURFACE:MAG >= 300 AND SHIP:VELOCITY:SURFACE:MAG < 400 {
        SET MYSTEER TO HEADING(90,60).
        PRINT "Pitching to 60 degrees" AT(0,15).
        PRINT ROUND(SHIP:APOAPSIS,0) AT (0,16).

    } ELSE IF SHIP:VELOCITY:SURFACE:MAG >= 400 AND SHIP:VELOCITY:SURFACE:MAG < 500 {
        SET MYSTEER TO HEADING(90,50).
        PRINT "Pitching to 50 degrees" AT(0,15).
        PRINT ROUND(SHIP:APOAPSIS,0) AT (0,16).

    } ELSE IF SHIP:VELOCITY:SURFACE:MAG >= 500 AND SHIP:VELOCITY:SURFACE:MAG < 600 {
        SET MYSTEER TO HEADING(90,40).
        PRINT "Pitching to 40 degrees" AT(0,15).
        PRINT ROUND(SHIP:APOAPSIS,0) AT (0,16).

    } ELSE IF SHIP:VELOCITY:SURFACE:MAG >= 600 AND SHIP:VELOCITY:SURFACE:MAG < 700 {
        SET MYSTEER TO HEADING(90,30).
        PRINT "Pitching to 30 degrees" AT(0,15).
        PRINT ROUND(SHIP:APOAPSIS,0) AT (0,16).

    } ELSE IF SHIP:VELOCITY:SURFACE:MAG >= 700 AND SHIP:VELOCITY:SURFACE:MAG < 800 {
        SET MYSTEER TO HEADING(90,11).
        PRINT "Pitching to 20 degrees" AT(0,15).
        PRINT ROUND(SHIP:APOAPSIS,0) AT (0,16).

    //Beyond 800m/s, we can keep facing towards 10 degrees above the horizon and wait
    //for the main loop to recognize that our apoapsis is above 100km
    } ELSE IF SHIP:VELOCITY:SURFACE:MAG >= 800 {
        SET MYSTEER TO HEADING(90,10).
        PRINT "Pitching to 10 degrees" AT(0,15).
        PRINT ROUND(SHIP:APOAPSIS,0) AT (0,16).

    }.

}.

PRINT "100km apoapsis reached, cutting throttle".

//At this point, our apoapsis is above 100km and our main loop has ended. Next
//we'll make sure our throttle is zero and that we're pointed prograde
LOCK THROTTLE TO 0.

//This sets the user's throttle setting to zero to prevent the throttle
//from returning to the position it was at before the script was run.
SET SHIP:CONTROL:PILOTMAINTHROTTLE TO 0.

And here is it in action:

_images/example_2_6.png

And toward the end:

_images/example_2_7.png

This script should, in principle, work to get you to the point of leaving the atmosphere. It will probably still fall back down, because this script makes no attempt to ensure that the craft is going fast enough to maintain the orbit.

As you can probably see, it would still have a long way to go before it would become a really GOOD launching autopilot. Think about the following features you could add yourself as you become more familiar with kOS:

  • You could change the steering logic to make a more smooth gravity turn by constantly adjusting the pitch in the HEADING according to some math formula. The example shown here tends to create a “too high” launch that’s a bit inefficient. In addition, this method relies on velocity to determine pitch angle, which could result in some very firey launches for other ships with a higher TWR profile.
  • This script just stupidly leaves the throttle at max the whole way. You could make it more sophisticated by adjusting the throttle as necessary to avoid velocities that result in high atmospheric heating.
  • This script does not attempt to circularize. With some simple checks of the time to apoapsis and the orbital velocity, you can execute a burn that circularizes your orbit.
  • With even more sophisticated checks, the script could be made to work with fancy staging methods like asparagus.
  • Using the PRINT AT command, you can make fancier status readouts in the terminal window as the script runs.

Design Patterns and Considerations with kOS

There are many ways one can write a control program for a given scenario. The goal of this section is to help a novice kOS programmer, after having finished the Quick Start Tutorial, to develop a sense of elegance and capability when writing his or her own kOS scripts. All of the examples in this tutorial may be tested by the reader using a rocket design similar to the following. Notice it carries an accelerometer and the negative gravioli detector which are used in the second section. Don’t forget the kOS module as well!

_images/designpatterns_rocket.png

The Major Design Patterns of kOS Control Programs

The design of a program is usually determined by the flow-control statements used. I.e., the WHEN/THEN, ON, WAIT, UNTIL, IF and FOR constructs. Here is a list of the major styles of control programs that can be written in kOS:

  1. Sequential
  2. Loops with Condition Checking
  3. Loops with Triggers

Of course, one style does not fit all scenarios and the programmer will typically want to use a combination of these all at once. Also, there may be other design patterns not listed here which can be perfectly valid, but this is a start.

1. Sequential Programs

These are programs that rely almost exclusively on WAIT UNTIL statements to go from one phase to the next.

LOCK STEERING TO HEADING(0,90).
LOCK THROTTLE TO 1.
STAGE.
WAIT UNTIL SHIP:ALTITUDE > 10000.
LOCK STEERING TO HEADING(0,90) + R(0,-45,0).
WAIT UNTIL STAGE:LIQUIDFUEL < 0.1.
STAGE.
WAIT UNTIL SHIP:ALTITUDE > 20000.
LOCK THROTTLE TO 0.
WAIT UNTIL FALSE. // CTRL+C to break out

This example will take a two stage rocket up to 20km. The immediate thing to notice is that the programmer must have known that the first stage would cutoff between 10km and 20km. This is fine for a specific rocket but not too general and could end in disaster if the first stage cutoff occurs at say 5km. Certainly, one can write a program using this technique to take a specific rocket, put it into orbit and even perform a lot of fancy maneuvers, but adapting the code to different rockets may get complicated quickly.

2. Loops with Condition Checking

Here, we introduce IF/ELSE logic into UNTIL loops:

LOCK STEERING TO R(0,0,-90) + HEADING(90,90).
LOCK THROTTLE TO 1.
STAGE.
UNTIL SHIP:ALTITUDE > 20000 {
    IF SHIP:ALTITUDE > 10000 {
        LOCK STEERING TO R(0,0,-90) + HEADING(90,45).
    }
    IF STAGE:LIQUIDFUEL < 0.1 {
        STAGE.
    }
}
LOCK THROTTLE TO 0.
WAIT UNTIL FALSE.

This does the same thing as the previous example, but now it’s checking for a staging condition from the launch pad all the way to 20km. More than that, it will stage as many times as needed.

One can imagine that these types of UNTIL loops can become very complex with many layers of IF/ELSE blocks. Once this happens it is usually good to reduce the frequency of the loop by adding a WAIT statement at the end of the loop. This wait could be anywhere from 0.001 (every physics tick), to 60 (every minute) or even longer for inter-planetary transfers if desired.

3. Loops with Triggers

In the above example, once the rocket reaches 10km, the steering is constantly being re-locked to HEADING(90,45). This works, but it only needs to be locked once. A possible improvement is to set up a trigger using a WHEN/THEN statement:

LOCK STEERING TO R(0,0,-90) + HEADING(90,90).
LOCK THROTTLE TO 1.
STAGE.
WHEN SHIP:ALTITUDE > 10000 THEN {
    LOCK STEERING TO R(0,0,-90) + HEADING(90,45).
}
UNTIL SHIP:ALTITUDE > 20000 {
    IF STAGE:LIQUIDFUEL < 0.1 {
        STAGE.
    }
}
LOCK THROTTLE TO 0.
WAIT UNTIL FALSE.

Now, when the rocket reaches 10km, the steering is set once and the trigger is removed from the active list of triggers. The staging condition can also be promoted to a trigger, keeping the trigger active after every stage using the PRESERVE keyword:

WHEN STAGE:LIQUIDFUEL < 0.1 THEN {
    STAGE.
    PRESERVE.
}
LOCK STEERING TO R(0,0,-90) + HEADING(90,90).
LOCK THROTTLE TO 1.
STAGE.
WHEN SHIP:ALTITUDE > 10000 THEN {
    LOCK STEERING TO R(0,0,-90) + HEADING(90,45).
}
WAIT UNTIL SHIP:ALTITUDE > 20000.
LOCK THROTTLE TO 0.
WAIT UNTIL FALSE.

Notice that the UNTIL loop was changed to a WAIT UNTIL statement since the program is small and all the logic of the triggers can be handled in a reasonable amount of time - there will be more on this topic later.

Bringing It All Together

Typically, the programmer will find all of these constructs are useful at the same time and kOS scripts will naturally contain some sequential parts in combination with long-term and short-term triggers which can modify states in complex loops of varying frequency. If you didn’t follow that bit of gobbledygook, don’t worry. The next section will discuss a few recommendations for beginning kOS programmers to follow when setting up any program.

General Guidelines for kOS Scripts

This section discusses two general guidelines to follow when starting out with more complicated kOS scripts. These are not meant to be absolute and there will certainly be cases when they can be stretched, though one should never totally ignore them.

1. Minimize Time Spent in WHEN/THEN Blocks

Remember that WAIT statements are ignored when inside WHEN/THEN blocks. It is OK to loop over small lists (engines for example), but don’t let it get out of hand. The WHEN/THEN construct was designed to accommodate quick bits of code. Consider this bit of (non-working) code which tries to adjust the throttle based on the g-force as measured by a combination of the accelerometer and the negative gravioli detector:

SET thrott TO 1.
LOCK THROTTLE TO thrott.
LOCK STEERING TO R(0,0,-90) + HEADING(90,90).
STAGE.
WHEN SHIP:ALTITUDE > 1000 THEN {
    SET g TO KERBIN:MU / KERBIN:RADIUS^2.
    LOCK accvec TO SHIP:SENSORS:ACC - SHIP:SENSORS:GRAV.
    LOCK gforce TO accvec:MAG / g.
    LOCK dthrott TO 0.05 * (1.2 - gforce).

    UNTIL SHIP:ALTITUDE > 40000 {
        WHEN STAGE:LIQUIDFUEL < 0.1 THEN {
            STAGE.
            PRESERVE.
        }
        SET thrott to thrott + dthrott.
        WAIT 0.1.
    }
}

This looks reasonable. The throttle is set to maximum until 1km is reached at which point the throttle is adjusted every 0.1 seconds. If the gforce is off from the value of 1.2, then the throttle is either increased or decreased by a small amount. Running this on a test rocket merely produce the message “Program ended.”

Understanding why this does not work is important. Everything in a WHEN/THEN block is expected to complete in the current physics tick, but here we have a loop that is supposed to last until the ship reaches 40km. This example can be reworked by separating the triggers from the loop. The staging trigger was separated from the UNTIL loop as well - not strictly necessary, but recommended form:

WHEN STAGE:LIQUIDFUEL < 0.1 THEN {
    STAGE.
    PRESERVE.
}
SET thrott TO 1.
SET dthrott TO 0.
LOCK THROTTLE TO thrott.
LOCK STEERING TO R(0,0,-90) + HEADING(90,90).
STAGE.
WHEN SHIP:ALTITUDE > 1000 THEN {
    SET g TO KERBIN:MU / KERBIN:RADIUS^2.
    LOCK accvec TO SHIP:SENSORS:ACC - SHIP:SENSORS:GRAV.
    LOCK gforce TO accvec:MAG / g.
    LOCK dthrott TO 0.05 * (1.2 - gforce).
}
UNTIL SHIP:ALTITUDE > 40000 {
    SET thrott to thrott + dthrott.
    WAIT 0.1.
}

Now this program should work. The variable dthrott had to be set to 0 in the beginning so that the throttle is kept at maximum until 1km, the UNTIL loop operates every 0.1 seconds, and the WHEN/THEN triggers are run only once when the condition is met. The take-away from this example is to keep WHEN/THEN blocks separate from UNTIL loops. Specifically, never put an UNTIL loop inside a WHEN/THEN block and it should be extremely rare to put a WHEN/THEN statement inside an UNTIL loop.

Finally, as a bit of foreshadowing, this bit of code is actually a “proportional feedback loop.” From an altitude of 1km up to 40km, the total g-force exerted on the ship is kept near 1.2 by constantly adjusting the throttle. The value of 1.2 is called the “setpoint,” the measured g-force is called the “process variable,” and the mystical 0.05 is called the “proportional gain.” Please take a look at the PID Loop Tutorial which takes this script as a starting point and develops a full PID-loop in kOS.

2. Minimize Trigger Conditions

There is a lot of power in developing multi-level LOCK variables in combination with WHEN/THEN triggers. However, it can be easy to hit kOS’s hard limit in the number of operations allowed for trigger checking. This will happen when several WHEN/THEN triggers are dependent on the same complex LOCK variable. This results in the LOCK variable being calculated multiple times every update. If the LOCK is deep enough, the calculations become too expensive to do and kOS stops executing and complains.

With this in mind, consider an extension of the example script in the previous section. This time, the g-force setpoint changes as the rocket climbs through 10km, 20km and 30km:

WHEN STAGE:LIQUIDFUEL < 0.1 THEN {
    STAGE.
    PRESERVE.
}
SET thrott TO 1.
SET dthrott TO 0.
LOCK THROTTLE TO thrott.
LOCK STEERING TO R(0,0,-90) + HEADING(90,90).
STAGE.
WHEN SHIP:ALTITUDE > 1000 THEN {
    SET g TO KERBIN:MU / KERBIN:RADIUS^2.
    LOCK accvec TO SHIP:SENSORS:ACC - SHIP:SENSORS:GRAV.
    LOCK gforce TO accvec:MAG / g.
    LOCK dthrott TO 0.05 * (1.2 - gforce).
}
WHEN SHIP:ALTITUDE > 10000 THEN {
    LOCK dthrott TO 0.05 * (2.0 - gforce).
}
WHEN SHIP:ALTITUDE > 20000 THEN {
    LOCK dthrott TO 0.05 * (4.0 - gforce).
}
WHEN SHIP:ALTITUDE > 30000 THEN {
    LOCK dthrott TO 0.05 * (5.0 - gforce).
}
UNTIL SHIP:ALTITUDE > 40000 {
    SET thrott to thrott + dthrott.
    WAIT 0.1.
}

This example does what is expected of it without problems. But the ship’s altitude is being checked at least five times for every update, including the UNTIL loop check. Certainly, the kOS CPU can keep up with this, however, one can imagine a whole series of WHEN/THEN statements which make use of complicated calculations based on atmospheric data or orbital mechanics. One way to minimize the trigger condition checking is to take strictly-sequential triggers and nest them:

WHEN STAGE:LIQUIDFUEL < 0.1 THEN {
    STAGE.
    PRESERVE.
}
SET thrott TO 1.
SET dthrott TO 0.
LOCK THROTTLE TO thrott.
LOCK STEERING TO R(0,0,-90) + HEADING(90,90).
STAGE.
WHEN SHIP:ALTITUDE > 1000 THEN {
    SET g TO KERBIN:MU / KERBIN:RADIUS^2.
    LOCK accvec TO SHIP:SENSORS:ACC - SHIP:SENSORS:GRAV.
    LOCK gforce TO accvec:MAG / g.
    LOCK dthrott TO 0.05 * (1.2 - gforce).

    WHEN SHIP:ALTITUDE > 10000 THEN {
        LOCK dthrott TO 0.05 * (2.0 - gforce).

        WHEN SHIP:ALTITUDE > 20000 THEN {
            LOCK dthrott TO 0.05 * (4.0 - gforce).

            WHEN SHIP:ALTITUDE > 30000 THEN {
                LOCK dthrott TO 0.05 * (5.0 - gforce).
            }
        }
    }
}
UNTIL SHIP:ALTITUDE > 40000 {
    SET thrott to thrott + dthrott.
    WAIT 0.1.
}

Now this is quite elegant! The number of triggers have been reduced to two per update for the entire running of this script. The trigger at 1km sets up the next trigger which will happen at 10km which sets up then next at 20km and so on. This can save a lot of processing time for triggers that will happen sequentially. As a general rule, one should try to nest WHEN/THEN statements whenever possible. Again, both examples above will work, but when scripts start to have deep and complicated triggers, this nested construct can save it from the dreaded kOS trigger limit.

PID Loops in kOS

New in version 0.18.1: Note, this is an older tutorial. As of kOS version 0.18.1 and up, a new pidloop feature was added to kOS to allow you to use a built-in PID controller that executes very quickly in the kOS “hardware” rather than in your script code. You can use it to perform the work described in detail on this page. However, this tutorial is still quite important because it walks you through how a PID controller works and what it’s really doing under the hood. It’s probably a good idea to use the built-in pidloop instead of the program shown here, once you understand the topic this page describes. However, it’s also a good idea to have a read through this page to get an understanding of what that built-in feature is really doing.

This tutorial covers how one can implement a PID loop using kOS. A P-loop, or “proportional feedback loop” was already introduced in the second section of the Design Patterns Tutorial, and that will serve as our starting point. After some code rearrangement, the integral and derivative terms will be added and discussed in turn. Next, a couple extra features will be added to the full PID-loop. Lastly, we’ll show a case-study in tuning a full PID loop using the Ziegler-Nichols method. We’ll use the LOG method to dump telemetry from KSP into a file and our favorite graphing software to visualize the data.

The code examples in this tutorial can be tested with a similar rocket design as shown. Do not forget the accelerometer, gravioli detector or the kOS CPU module. The engine is purposefully overpowered to demonstrate the feedback in action.

_images/pidtune_rocket_design_maxtwr8.png

Those fuel-tank adapters are from the Modular Rocket Systems (MRS) addon, but stock tanks will work just fine. The design goal of this rocket is to have a TWR of 8 on the launchpad and enough fuel to make it past 30km when throttled for optimal atmospheric efficiency.

Proportional Feedback Loop (P-loop)

The example code from the Design Patterns Tutorial, with some slight modifications looks like the following:

// staging, throttle, steering, go
WHEN STAGE:LIQUIDFUEL < 0.1 THEN {
    STAGE.
    PRESERVE.
}
LOCK THROTTLE TO 1.
LOCK STEERING TO R(0,0,-90) + HEADING(90,90).
STAGE.
WAIT UNTIL SHIP:ALTITUDE > 1000.

// P-loop setup
SET g TO KERBIN:MU / KERBIN:RADIUS^2.
LOCK accvec TO SHIP:SENSORS:ACC - SHIP:SENSORS:GRAV.
LOCK gforce TO accvec:MAG / g.
LOCK dthrott TO 0.05 * (1.2 - gforce).

SET thrott TO 1.
LOCK THROTTLE to thrott.

UNTIL SHIP:ALTITUDE > 40000 {
    SET thrott to thrott + dthrott.
    WAIT 0.1.
}

The first several lines sets up a simple staging condition, puts the throttle to maximum, steers the rocket straight up and launches. The rocket is assumed to use only liquid fuel engines. After the rocket hits 1km, the script sets up the LOCK used in the P-loop which is updated every 0.1 seconds in the UNTIL loop. The use of LOCK variables makes this code fairly clean. When the script comes up to the first line in the UNTIL loop, i.e. “SET thrott TO thrott + dthrott.”, the variable dthrott is evaluated which causes the LOCK on gforce to be evaluated which in-turn causes accvec to be evaluated.

The input to this feedback loop is the acceleration experienced by the ship (gforce) in terms of Kerbin’s gravitational acceleration at sea level (g). The variable accvec is the total acceleration vector and is obtained by the accelerometer and gravioli detectors, both of which must be on the ship for this to work. The variable dthrott is the change in throttle that should be applied in a single iteration of the feedback loop.

In terms of a PID loop, the factor 1.2 is called the setpoint, gforce is the process variable and 0.05 is called the proportional gain. The setpoint and gain factors can be promoted to their own variables with names. Also, the code up to and including the “WAIT UNTIL SHIP:ALTITUDE > 1000.” will be implied for the next few examples of code:

// P-loop
SET g TO KERBIN:MU / KERBIN:RADIUS^2.
LOCK accvec TO SHIP:SENSORS:ACC - SHIP:SENSORS:GRAV.
LOCK gforce TO accvec:MAG / g.

SET gforce_setpoint TO 1.2.
SET Kp TO 0.05.
LOCK dthrott TO Kp * (gforce_setpoint - gforce).

SET thrott TO 1.
LOCK THROTTLE to thrott.

UNTIL SHIP:ALTITUDE > 40000 {
    SET thrott to thrott + dthrott.
    WAIT 0.1.
}

This is not a big change, but it will set us up to include the integral and derivative terms in the next section.

Proportional-Integral Feedback Loop (PI-loop)

Adding the integral term requires us to keep track of time. This is done by introducing a variable (t0) to store the time of the last iteration. Now, the throttle is changed only on iterations where some time has elapsed so the WAIT time in the UNTIL can be brought to 0.001. The offset of the gforce has been set to the variable P, and the integral gain to Ki.

// PI-loop
SET g TO KERBIN:MU / KERBIN:RADIUS^2.
LOCK accvec TO SHIP:SENSORS:ACC - SHIP:SENSORS:GRAV.
LOCK gforce TO accvec:MAG / g.

SET gforce_setpoint TO 1.2.

LOCK P TO gforce_setpoint - gforce.
SET I TO 0.

SET Kp TO 0.01.
SET Ki TO 0.006.

LOCK dthrott TO Kp * P + Ki * I.

SET thrott TO 1.
LOCK THROTTLE to thrott.

SET t0 TO TIME:SECONDS.
UNTIL SHIP:ALTITUDE > 40000 {
    SET dt TO TIME:SECONDS - t0.
    IF dt > 0 {
        SET I TO I + P * dt.
        SET thrott to thrott + dthrott.
        SET t0 TO TIME:SECONDS.
    }
    WAIT 0.001.
}

Adding the integral term has the general effect of stabilizing the feedback loop, making it less prone to oscillating due to rapid changes in the process variable (gforce, in this case). This is usually at the expense of a longer settling time.

Proportional-Integral-Derivative Feedback Loop (PID-loop)

Incorporating the derivative term (D) and derivative gain (Kd) requires an additional variable (P0) to keep track of the previous value of the proportional term (P).

// PID-loop
SET g TO KERBIN:MU / KERBIN:RADIUS^2.
LOCK accvec TO SHIP:SENSORS:ACC - SHIP:SENSORS:GRAV.
LOCK gforce TO accvec:MAG / g.

SET gforce_setpoint TO 1.2.

LOCK P TO gforce_setpoint - gforce.
SET I TO 0.
SET D TO 0.
SET P0 TO P.

SET Kp TO 0.01.
SET Ki TO 0.006.
SET Kd TO 0.006.

LOCK dthrott TO Kp * P + Ki * I + Kd * D.

SET thrott TO 1.
LOCK THROTTLE to thrott.

SET t0 TO TIME:SECONDS.
UNTIL SHIP:ALTITUDE > 40000 {
    SET dt TO TIME:SECONDS - t0.
    IF dt > 0 {
        SET I TO I + P * dt.
        SET D TO (P - P0) / dt.
        SET thrott to thrott + dthrott.
        SET P0 TO P.
        SET t0 TO TIME:SECONDS.
    }
    WAIT 0.001.
}

When tuned properly, the derivative term will cause the PID-loop to act quickly without causing problematic oscillations. Later in this tutorial, we will cover a way to tune a PID-loop using only the proportional term called the Zieger-Nichols method.

Using pidloop

As mentioned earlier, kOS 0.18.1 introduced a new structure called pidloop that can take the place of much of the previous code. Here is the previous script, converted to use pidloop.

// pidloop
SET g TO KERBIN:MU / KERBIN:RADIUS^2.
LOCK accvec TO SHIP:SENSORS:ACC - SHIP:SENSORS:GRAV.
LOCK gforce TO accvec:MAG / g.

SET Kp TO 0.01.
SET Ki TO 0.006.
SET Kd TO 0.006.
SET PID TO PIDLOOP(Kp, Kp, Kd).
SET PID:SETPOINT TO 1.2.

SET thrott TO 1.
LOCK THROTTLE TO thrott.

UNTIL SHIP:ALTITUDE > 40000 {
    SET thrott TO thrott + PID:UPDATE(TIME:SECONDS, gforce).
    // pid:update() is given the input time and input and returns the output. gforce is the input.
    WAIT 0.001.
}

The primary advantage to using pidloop is the reduction in the number of instructions per update (see Config:IPU). For example, this pidloop script requires approximately one-third the number of instructions needed by the script shown in the previous section. Since the number of instructions executed has a direct bearing on electrical drain as of 0.19.0, this can be a great help with power conservation.

Note that pidloop offers a great deal more options than were presented here, but nevertheless, this should provide a decent introduction to using pidloop.

Final Touches

There are a few modifications that can make PID loops very robust. The following code example adds three range limits:

  1. bounds on the Integral term which addresses possible integral windup
  2. bounds on the throttle since it must stay in the range 0 to 1
  3. a deadband to avoid changing the throttle due to small fluctuations

Of course, KSP is a simulator and small fluctuations are not observed in this particular loop. Indeed, the P-loop is sufficient in this example, but all these features are included here for illustration purposes and they could become useful for unstable aircraft or untested scenarios.

// PID-loop
SET g TO KERBIN:MU / KERBIN:RADIUS^2.
LOCK accvec TO SHIP:SENSORS:ACC - SHIP:SENSORS:GRAV.
LOCK gforce TO accvec:MAG / g.

SET gforce_setpoint TO 1.2.

LOCK P TO gforce_setpoint - gforce.
SET I TO 0.
SET D TO 0.
SET P0 TO P.

LOCK in_deadband TO ABS(P) < 0.01.

SET Kp TO 0.01.
SET Ki TO 0.006.
SET Kd TO 0.006.

LOCK dthrott TO Kp * P + Ki * I + Kd * D.

SET thrott TO 1.
LOCK THROTTLE to thrott.

SET t0 TO TIME:SECONDS.
UNTIL SHIP:ALTITUDE > 40000 {
    SET dt TO TIME:SECONDS - t0.
    IF dt > 0 {
        IF NOT in_deadband {
            SET I TO I + P * dt.
            SET D TO (P - P0) / dt.

            // If Ki is non-zero, then limit Ki*I to [-1,1]
            IF Ki > 0 {
                SET I TO MIN(1.0/Ki, MAX(-1.0/Ki, I)).
            }

            // set throttle but keep in range [0,1]
            SET thrott to MIN(1, MAX(0, thrott + dthrott)).

            SET P0 TO P.
            SET t0 TO TIME:SECONDS.
        }
    }
    WAIT 0.001.
}

Tuning a PID-loop

We are going to start with the same rocket design we have been using so far and actually tune the PID-loop using the Ziegler-Nichols method. This is where we turn off the integral and derivative terms in the loop and bring the proportional gain (Kp) up from zero to the point where the loop causes a steady oscillation with a measured period (Tu). At this point, the proportional gain is called the “ultimate gain” (Ku) and the actual gains (Kp, Ki and Kd) are set according to this table taken from wikipedia:

Control Type Kp Ki Kd
P 0.5 Ku    
PI 0.45 Ku 1.2 Kp / Tu  
PD 0.8 Ku   Kp Tu / 8
classic PID 0.6 Ku 2 Kp / Tu Kp Tu / 8
Pessen Integral Rule 0.7 Ku 0.4 Kp / Tu 0.15 Kp Tu
some overshoot 0.33 Ku 2 Kp / Tu Kp Tu / 3
no overshoot 0.2 Ku 2 Kp / Tu Kp Tu / 3

An immediate problem to overcome with this method is that it assumes a steady state can be achieved. With rockets, there is never a steady state: fuel is being consumed, altitude and therefore gravity and atmosphere is changing, staging can cause major upsets in the feedback loop. So, this tuning method will be some approximation which should come as no surprise since it will come from experimental observation. All we need is enough of a steady state that we can measure the oscillations - both the change in amplitude and the period.

The script we’ll use to tune the highly overpowered rocket shown will launch the rocket straight up (using SAS) and will log data to an output file until it reaches 30km at which point the log file will be copied to the archive and the program will terminate. Also, this time the feedback loop will be based on the more realistic “atmospheric efficiency.” The log file will contain three columns: time since launch, offset of atmospheric efficiency from the ideal (in this case, 1.0) and the ship’s maximum thrust. The maximum thrust will increase monotonically with time (this rocket has only one stage) and we’ll use both as the x-axis when plotting the offset on the y-axis.

DECLARE PARAMETER Kp.

SWITCH TO 1. // This is the default usually, but just to be sure.

LOCK g TO SHIP:BODY:MU / (SHIP:BODY:RADIUS + SHIP:ALTITUDE)^2.
LOCK maxtwr TO SHIP:MAXTHRUST / (g * SHIP:MASS).

// feedback based on atmospheric efficiency
LOCK surfspeed TO SHIP:VELOCITY:SURFACE:MAG.
LOCK atmoeff TO surfspeed / SHIP:TERMVELOCITY.
LOCK P TO 1.0 - atmoeff.

SET t0 TO TIME:SECONDS.
LOCK dthrott TO Kp*P.
SET start_time TO t0.

LOG "# Throttle PID Tuning" TO throttle_log.
LOG "# Kp: " + Kp TO throttle_log.
LOG "# t P maxtwr" TO throttle_log.

LOCK logline TO (TIME:SECONDS - start_time)
        + " " + P
        + " " + maxtwr.

SET thrott TO 1.
LOCK THROTTLE TO thrott.
SAS ON.
STAGE.
WAIT 3.

UNTIL SHIP:ALTITUDE > 30000 {
    SET dt TO TIME:SECONDS - t0.
    IF dt > 0 {
        SET thrott TO MIN(1,MAX(0,thrott + dthrott)).
        SET t0 TO TIME:SECONDS.
        LOG logline TO throttle_log.
    }
    WAIT 0.001.
}
COPYPATH("throttle_log", "0:/").

Give this script a short name, something like “tune.txt” so that running is simple:

copypath("0:/tune", "").
run tune(0.5).

After every launch completes, you’ll have to go into the archive directory and rename the output logfile. Something like “throttle_log.txt” –> “throttle.01.log” will help if you increment the index number each time. To analyze the data, plot the offset (P) as a function of time (t). Here, we show the results for three values of Kp: 0.002, 0.016 and 0.160, including the maximum TWR when Kp = 0.002 as the top x-axis. The maximum TWR dependence on time is different for the three values of Kp, but not by a lot.

_images/pidtune1.png

The value of 0.002 is obviously too low. The settling time is well over 20 seconds and the loop can’t keep up with the increase in terminal velocity at the higher altitudes reached after one minute. When Kp = 0.016, the behavior is far more well behaved, and though some oscillation exists, it’s damped and slow with a period of about 10 seconds. At Kp = 0.160, the oscillations are prominent and we can start to measure the change in amplitude along with the period of the oscillations. This plot shows the data for Kp = 0.160 from 20 to 40 seconds after ignition. The peaks are found and are fit to a line.

_images/pidtune2.png

This is done for each value of Kp and the slopes of the fitted lines are plotted as a function of Kp in the following plot:

_images/pidtune3.png

The period of oscillation was averaged over the interval and plotted on top of the amplitude change over time. Notice the turn over that occurs when Kp reaches approximately 0.26. This will mark the “ultimate gain” and 3.1 seconds will be used as the associated period of oscillation. It is left as an exercise for the reader to implement a full PID-loop using the classic PID values (see table above): Kp = 0.156, Ki = 0.101, Kd = 0.060, producing this behavior:

_images/pidtune4.png

As soon as the PID-loop was activated at 3 seconds after ignition, the throttle was cut. At approximately 7 seconds, the atmospheric efficiency dropped below 100% and the integral term started to climb back to zero. At 11 seconds, the engine was reignited and the feedback loop settled after about 20 seconds. The inset plot has the same axes as the parent and shows the long-term stability of the final PID-loop.

Final Thoughts

The classic PID values used above are fairly aggressive and there is some overshoot at the beginning. This can be dealt with in many ways and is discussed on the wikipedia page about PID controllers. For example, one might consider trying to implement a switch to a PD-loop when the integral term hits some limit, switching back once P crosses zero. The PID behavior should look like the following:

_images/pidtune5.png

Finally, Controlling the throttle of a rocket is perhaps the easiest thing to implement as a PID loop in KSP using kOS. The steering was largely ignored and the orientation was always up. When writing an autopilot for horizontal atmospheric flight, one will have to deal with the direction the ship is traveling using SHIP:HEADING as well as it’s orientation with SHIP:FACING. Additionally, there are the SHIP:ROTATION and SHIP:TRANSLATION vectors which can tell you the rate of change of the ship’s facing and heading respectively. The controls in this case are six-dimensional using SHIP:CONTROL with YAW, PITCH, ROLL, FORE, STARBOARD, TOP and MAINTHROTTLE.

The PID gain parameters are dependent on the characteristics of the ship being controlled. The size, shape, turning capability and maximum TWR should be considered when tuning a PID loop. Turning RCS on can also have an effect and you might consider changing the PID loop’s gain parameters every time to switch them on or off.

Execute Node Script

Let’s try to automate one of the most common tasks in orbital maneuvering - execution of the maneuver node. In this tutorial I’ll try to show you how to write a script for somewhat precise maneuver node execution.

So to start our script we need to get the next available maneuver node:

set nd to nextnode().

Our next step is to calculate how much time our vessel needs to burn at full throttle to execute the node:

//print out node's basic parameters - ETA and deltaV
print "Node in: " + round(nd:eta) + ", DeltaV: " + round(nd:deltav:mag).

//calculate ship's max acceleration
set max_acc to ship:maxthrust/ship:mass.

// Now we just need to divide deltav:mag by our ship's max acceleration
// to get the estimated time of the burn.
//
// Please note, this is not exactly correct.  The real calculation
// needs to take into account the fact that the mass will decrease
// as you lose fuel during the burn.  In fact throwing the fuel out
// the back of the engine very fast is the entire reason you're able
// to thrust at all in space.  The proper calculation for this
// can be found easily enough online by searching for the phrase
//   "Tsiolkovsky rocket equation".
// This example here will keep it simple for demonstration purposes,
// but if you're going to build a serious node execution script, you
// need to look into the Tsiolkovsky rocket equation to account for
// the change in mass over time as you burn.
//
set burn_duration to nd:deltav:mag/max_acc.
print "Crude Estimated burn duration: " + round(burn_duration) + "s".

So now we have our node’s deltav vector, ETA to the node and we calculated our burn duration. All that is left for us to do is wait until we are close to node’s ETA less half of our burn duration. But we want to write a universal script, and some of our current and/or future ships can be quite slow to turn, so let’s give us some time, 60 seconds, to prepare for the maneuver burn:

wait until node:eta <= (burn_duration/2 + 60).

This wait can be tedious and you’ll most likely end up warping some time, but we’ll leave kOS automation of warping for a given period of time to our readers.

The wait has finished, and now we need to start turning our ship in the direction of the burn:

set np to nd:deltav. //points to node, don't care about the roll direction.
lock steering to np.

//now we need to wait until the burn vector and ship's facing are aligned
wait until abs(np:pitch - facing:pitch) < 0.15 and abs(np:yaw - facing:yaw) < 0.15.

//the ship is facing the right direction, let's wait for our burn time
wait until node:eta <= (burn_duration/2)

Now we are ready to burn. It is usually done in the until loop, checking main parameters of the burn every iteration until the burn is complete:

//we only need to lock throttle once to a certain variable in the beginning of the loop, and adjust only the variable itself inside it
set tset to 0.
lock throttle to tset.

set done to False.
//initial deltav
set dv0 to nd:deltav.
until done
{
    //recalculate current max_acceleration, as it changes while we burn through fuel
    set max_acc to ship:maxthrust/ship:mass.

    //throttle is 100% until there is less than 1 second of time left to burn
    //when there is less than 1 second - decrease the throttle linearly
    set tset to min(nd:deltav:mag/max_acc, 1).

    //here's the tricky part, we need to cut the throttle as soon as our nd:deltav and initial deltav start facing opposite directions
    //this check is done via checking the dot product of those 2 vectors
    if vdot(dv0, nd:deltav) < 0
    {
        print "End burn, remain dv " + round(nd:deltav:mag,1) + "m/s, vdot: " + round(vdot(dv0, nd:deltav),1).
        lock throttle to 0.
        break.
    }

    //we have very little left to burn, less then 0.1m/s
    if nd:deltav:mag < 0.1
    {
        print "Finalizing burn, remain dv " + round(nd:deltav:mag,1) + "m/s, vdot: " + round(vdot(dv0, nd:deltav),1).
        //we burn slowly until our node vector starts to drift significantly from initial vector
        //this usually means we are on point
        wait until vdot(dv0, nd:deltav) < 0.5.

        lock throttle to 0.
        print "End burn, remain dv " + round(nd:deltav:mag,1) + "m/s, vdot: " + round(vdot(dv0, nd:deltav),1).
        set done to True.
    }
}
unlock steering.
unlock throttle.
wait 1.

//we no longer need the maneuver node
remove nd.

//set throttle to 0 just in case.
SET SHIP:CONTROL:PILOTMAINTHROTTLE TO 0.

That is all, this short script can execute any maneuver node with 0.1 m/s dv precision or even better.

Creating Reusable GUI Elements

In this tutorial, we describe how to make a TabWidget that can be reused as a general-purpose widget.

The End Result

Starting from the end - how the user would like to use the TabWidget - is a good way to drive good encapsulation and to avoid forcing implementation details upon the end user.

You should have an image of what the final result will look like, and a feel for how it would be used. If you’re reproducing concepts that already exist, you can use existing art as a guide. Here, we just cheat and show a picture of the end result:

_images/TabWidget.png

Now, from the coding side, how would we like to use our TabWidget?

1. “Import” the functionality into our script

The implementation is all contained in the single “TabWidget” directory. This allows us to easily share the implementation with multiple KSP installs, and with others online:

RUNONCEPATH("TabWidget/tabwidget").
2. Create a GUI

Our TabWidget should be usable in any context, but in this example, we just create it at the top-level. The AddTabWidget function should however accept any VBOX as the parameter:

LOCAL gui IS GUI(500).
LOCAL tabwidget IS AddTabWidget(gui).
3. Add tabs

We will want a function that adds a tab with a title on it, and returns a box into which we can put whatever widgets we want:

LOCAL page IS AddTab(tabwidget,"One").
page:ADDLABEL("This is page 1").
page:ADDLABEL("Put stuff here!").

LOCAL page IS AddTab(tabwidget,"Two").
page:ADDLABEL("This is page 2").
page:ADDLABEL("Put more stuff here!").

LOCAL page IS AddTab(tabwidget,"Three").
page:ADDLABEL("This is page 3").
page:ADDLABEL("Put even stuff here!").
4. Choose tab programmatically

Sometimes we might want a tab other than the first to be the one shown. For example, the first tab might be for launching into orbit from the surface, but if the vessel reboots and finds itself in space, the tab for setting up Maneuver Nodes might be a better default:

ChooseTab(tabwidget,1).
5. Handling the widgets on the tabs

The rest here is just boilerplate - we add a Close button to the top level, then show and run the GUI until the user presses the Close button:

LOCAL close IS gui:ADDBUTTON("Close").
gui:SHOW().
UNTIL close:PRESSED {
    // Handle processing of all the widgets on all the tabs.
    WAIT(0).
}
gui:HIDE().

The Implementation

We now move on to the implementation of this desired functionality. We put everything below in the “TabWidget/tabwidget.ks” file, as suggested in the RUNONCEPATH above.

1. Creating the TabWidget

First, we implement the required “AddTabWidget” function. This function takes any box as a parameter (eg. the top level GUI, or one created by GUI:ADDVLAYOUT, GUI:ADDSCROLLBOX, etc.:

DECLARE FUNCTION AddTabWidget
{
        // Any box is allowed
        DECLARE PARAMETER box.

        // See if styles for the TabWidget components (tabs and panels) has
        // already been defined elsewhere. If not, define each one

        IF NOT box:GUI:SKIN:HAS("TabWidgetTab") {

                // The style for tabs is like a button, but it should smoothly connect
                // to the panel below it, especially if it is the current selected tab.

                LOCAL style IS box:GUI:SKIN:ADD("TabWidgetTab",box:GUI:SKIN:BUTTON).

                // Images are stored alongside the code.
                SET style:BG TO "TabWidget/images/back".
                SET style:ON:BG to "TabWidget/images/front".
                // Tweak the style.
                SET style:TEXTCOLOR TO RGBA(0.7,0.75,0.7,1).
                SET style:HOVER:BG TO "".
                SET style:HOVER_ON:BG TO "".
                SET style:MARGIN:H TO 0.
                SET style:MARGIN:BOTTOM TO 0.
        }
        IF NOT box:GUI:SKIN:HAS("TabWidgetPanel") {
                LOCAL style IS box:GUI:SKIN:ADD("TabWidgetPanel",box:GUI:SKIN:WINDOW).
                SET style:BG TO "TabWidget/images/panel".
                SET style:PADDING:TOP to 0.
        }

        // Add a vlayout (in case the box is a HBOX, for example),
        // then add a hlayout for the tabs and a stack to hols all the panels.
        LOCAL vbox IS box:ADDVLAYOUT.
        LOCAL tabs IS vbox:ADDHLAYOUT.
        LOCAL panels IS vbox:ADDSTACK.

        // any other customization of tabs and panels goes here

        // Return the empty TabWidget.
        RETURN vbox.
}
2. Images for the Tabs and Panels

The images are based on other elements to make them suit the style.

The tab when in front: fronttabimage Based on Button normal state
The tab when in the back backtabimage Based on Button normal_on state
The panel below the tabs: paneltabimage Based on the GUI window background

Note that these images need to be in the “TabWidget/images” directory, as referred to in the code above.

3. Adding a Tab

Next, we implement the required “AddTab” function. This function takes a TabWidget created by the previous function and adds another tab to the end with a given name. It returns a VBOX into which widgets for that page of the TabWidget can be added.:

DECLARE FUNCTION AddTab
{
        DECLARE PARAMETER tabwidget. // (the vbox)
        DECLARE PARAMETER tabname. // title for the tab

        // Get back the two widgets we created in AddTabWidget
        LOCAL hboxes IS tabwidget:WIDGETS.
        LOCAL tabs IS hboxes[0]. // the HLAYOUT
        LOCAL panels IS hboxes[1]. // the STACK

        // Add another panel, style it correctly
        LOCAL panel IS panels:ADDVBOX.
        SET panel:STYLE TO panel:GUI:SKIN:GET("TabWidgetPanel").

        // Add another tab, style it correctly
        LOCAL tab IS tabs:ADDBUTTON(tabname).
        SET tab:STYLE TO tab:GUI:SKIN:GET("TabWidgetTab").

        // Set the tab button to be exclusive - when
        // one tab goes up, the others go down.
        SET tab:TOGGLE TO true.
        SET tab:EXCLUSIVE TO true.

        // If this is the first tab, make it start already shown (make the tab presssed)
        // Otherwise, we hide it (even though the STACK will only show the first anyway,
        // but by keeping everything "correct", we can be a little more efficient later.
        IF panels:WIDGETS:LENGTH = 1 {
                SET tab:PRESSED TO true.
                panels:SHOWONLY(panel).
        } else {
                panel:HIDE().
        }


        // Add the tab and its corresponding panel to global variables,
        // in order to handle interaction later.
        TabWidget_alltabs:ADD(tab).
        TabWidget_allpanels:ADD(panel).

        RETURN panel.
}

// Global variables to allow interaction to be done later.
GLOBAL TabWidget_alltabs TO LIST().
GLOBAL TabWidget_allpanels TO LIST().
3. Adding a Tab

We want to be able to choose a specific tab to be shown, so we add a simple function to encapsulate that:

DECLARE FUNCTION ChooseTab
{
        DECLARE PARAMETER tabwidget. // The tab
        DECLARE PARAMETER tabnum. // Which tab to choose (0 is first)
        // Find the tabs hlayout - is is the first of the two we added
        LOCAL hboxes IS tabwidget:WIDGETS.
        LOCAL tabs IS hboxes[0].
        // Find the tab, and set it to be pressed
        SET tabs:WIDGETS[tabnum]:PRESSED TO true.
}
4. Running the TabWidget

Rather than ask the user to repeatedly call a function to run the TabWidget (which would also be fine, but not in our original design), we instead use a “trick” to watch the tab buttons to see if they get pressed, and raise the corresponding tab if they are:

WHEN True THEN {
        FROM { LOCAL x IS 0.} UNTIL x >= TabWidget_alltabs:LENGTH STEP { SET x TO x+1.} DO
        {
                // Earlier, we were careful to hide the panels that were not the current
                // one when they were added, so we can test if the panel is VISIBLE
                // to avoid the more expensive call to SHOWONLY every frame.
                IF TabWidget_alltabs[x]:PRESSED AND NOT TabWidget_allpanels[x]:VISIBLE {
                        TabWidget_allpanels[x]:parent:showonly(TabWidget_allpanels[x]).
                }
        }
        PRESERVE.
}

By using “WHEN True” and “PRESERVE”, this code effectively runs once every frame. It looks at all the tabs that have been created (even in multiple TabWidgets), and calls SHOWONLY on the STACK, giving it the panel that corresponds to the tab that was pressed. Since this code is executed every frame (but only once, since RUNONCEPATH ensures this WHEN statement only starts once), we are careful to do the minimum amount of processing necessary.

5. Testing with Communication Delay

When playing with communication delay enabled (eg. with RemoteTech), you will want to ensure that the GUI is still usable with interaction delays, and ensure it does not cause continuous communication on the link. For example, if you constantly show a label with very high precision decimal, it will probably be updating the label continuously, which would mean the communication delay icon is constantly shown. We can test our GUI without flying to Jool by artificially adding extra delay, and testing on the launch pad:

...
SET gui:EXTRADELAY TO 2.
...

The widget continues to work perfectly. Even if the user rapidly changes tab, the pages change as expected, though of course delayed.

Introductory

Quick Start Tutorial
Walks you through the beginnings of making a beginner’s ship launcher script.

Intermediate

Design Patterns Tutorial
Discusses some general aspects of kOS flow control and optimizations.
PID Loop Tutorial
Starts with a basic proportional feedback loop and develops, in stages, a complete PID-loop to control the throttle of a simple rocket design.
Execute Node script
ZiwKerman describes a generic “execute manuever node” script to be a one-size-fits-all solution to many situations in KSP. If you can make a manuever node for something, exenode will execute it.
Creating Reusable GUI Elements
Creating and using a “TabWidget” to put a lot of functionality in a small amount of screenspace.

Community Examples Library

Starting with version 0.17.0 of kOS, we have decided to support a separate repository of examples and libraries that “live” entirely in Kerboscript code only. This is a useful place to find helpful code written mostly by other users of kOS, some of whom may be members of the main kOS development team, but most of whom are not and are just ordinary users of kOS like you.

The separate repository is found here:

https://github.com/KSP-KOS/KSLib

Some examples of useful things you can find there are:

  • A library for getting navball orientation information:
  • An example of how to use the sasmode feature:
  • A helpful set of routines letting you enumerate over lists, queues, and stacks, using callback delegates.
  • An example of a seven-segment display controller.
  • A keypad emulator that lets you move a ‘finger’ cursor around a visual keyboard and type letters.
  • A library for drawing chunky lines out of the character cells of the terminal.
  • A library routine to help calculate when to launch from the launchpad to match a given target inclination orbit.

General Topics

These topics discuss the interfacing between kOS and Kerbal Space Program.

Catalog of Bound Variable Names

This is the list of special reserved keyword variable names that kOS will interpret to mean something special. If they are used as normal variable names by your kOS script program they may not work. Understanding them and their meaning is crucial to creating effective kOS scripts.

NAMED VESSELS AND BODIES

SHIP:

  • Variable name: SHIP
  • Gettable: yes
  • Settable: no
  • Type: Vessel
  • Description: Whichever vessel happens to be the one containing the CPU part that is running this Kerboscript code at the moment. This is the CPU Vessel.

TARGET:

  • Variable Name: TARGET
  • Gettable: yes
  • Settable: yes
  • Type: Vessel or Body or Part
  • Description: Whichever Orbitable object happens to be the one selected as the current KSP target. If a docking port is selected as the target, it will be the corresponding part. If set to a string, it will assume the string is the name of a vessel being targeted and set it to a vessel by that name. For best results set it to Body(“some name”) or Vessel(“some name”) explicitly. This will throw an exception if called from a vessel other than the active vessel, as limitations in how KSP sets the target vessel limit the implementation to working with only the active vessel.

HASTARGET:

  • Variable Name: TARGET
  • Gettable: yes
  • Settable: no
  • Type: boolean
  • Description: Will return true if the ship has a target selected. This will always return false when not on the active vessel, due to limitations in how KSP sets the target vessel.

Alias shortcuts for SHIP fields

The following are all alias shortcuts for accessing the fields of the SHIP vessel. To see their definition, please consult the Vessel page, as they are all just instances of the standard vessel suffixes.

Variable Same as
HEADING Same as SHIP:HEADING
PROGRADE Same as SHIP:PROGRADE
RETROGRADE Same as SHIP:RETROGRADE
FACING Same as SHIP:FACING
MAXTHRUST Same as SHIP:MAXTHRUST
VELOCITY Same as SHIP:VELOCITY
GEOPOSITION Same as SHIP:GEOPOSITION
LATITUDE Same as SHIP:LATITUDE
LONGITUDE Same as SHIP:LONGITUDE
UP Same as SHIP:UP
NORTH Same as SHIP:NORTH
BODY Same as SHIP:BODY
ANGULARMOMENTUM Same as SHIP:ANGULARMOMENTUM
ANGULARVEL Same as SHIP:ANGULARVEL
ANGULARVELOCITY Same as SHIP:ANGULARVEL
MASS Same as SHIP:MASS
VERTICALSPEED Same as SHIP:VERTICALSPEED
GROUNDSPEED Same as SHIP:GROUNDSPEED
SURFACESPEED This has been obsoleted as of kOS 0.18.0. Replace it with GROUNDSPEED.
AIRSPEED Same as SHIP:AIRSPEED
ALTITUDE Same as SHIP:ALTITUDE
APOAPSIS Same as SHIP:APOAPSIS
PERIAPSIS Same as SHIP:PERIAPSIS
SENSORS Same as SHIP:SENSORS
SRFPROGRADE Same as SHIP:SRFPROGRADE
SRFREROGRADE Same as SHIP:SRFREROGRADE
OBT Same as SHIP:OBT
STATUS Same as SHIP:STATUS
SHIPNAME Same as SHIP:NAME

Constants (pi, e, etc)

Get-only.

The variable constant provides a way to access a few basic math and physics constants, such as Pi, Euler’s number, and so on.

Example:

print "Kerbin's circumference: " + (2*constant:pi*Kerbin:radius) + "meters.".

The full list is here: constants page.

Terminal

Get-only. terminal returns a terminal structure describing the attributes of the current terminal screen associated with the CPU this script is running on.

Core

Get-only. core returns a core structure referring to the CPU you are running on.

Archive

Get-only. archive returns a Volume structure referring to the archive. You can read more about what archive is on the File & volumes page.

Stage

Get-only. stage returns a stage structure used to count resources in the current stage. Not to be confused with the COMMAND stage which triggers the next stage.

NextNode

See the NEXTNODE documentation.

HasNode

See the HASNODE documentation.

AllNodes

See the ALLNODES documentation.

Resource Types

Any time there is a resource on the ship it can be queried. The resources are the values that appear when you click on the upper-right corner of the screen in the KSP window. Resources

LIQUIDFUEL
OXIDIZER
ELECTRICCHARGE
MONOPROPELLANT
INTAKEAIR
SOLIDFUEL

All of the above resources can be queried using either the prefix SHIP or STAGE, depending on whether you are trying to query how much is left in the current stage or the entire ship:

How much liquid fuel is left in the entire ship:

PRINT "There is " + SHIP:LIQUIDFUEL + " liquid fuel on the ship.".

How much liquid fuel is left in just the current stage:

PRINT "There is " + STAGE:LIQUIDFUEL + " liquid fuel in this stage.".

How much liquid fuel is left in the target vessel:

PRINT "There is " + TARGET:LIQUIDFUEL + " liquid fuel in the target ship.".

Any other resources that you have added using other mods should be query-able this way, provided that you spell the term exactly as it appears in the resources window.

You can also get a list of all resources, either in SHIP: or STAGE: with the :RESOURCES suffix.

ALT ALIAS

The special variable ALT gives you access to a few altitude predictions:

ALT:APOAPSIS

ALT:PERIAPSIS

ALT:RADAR

Further details are found on the ALT page .

ETA ALIAS

The special variable ETA gives you access to a few time predictions:

ETA:APOAPSIS

ETA:PERIAPSIS

ETA:TRANSITION

Further details are found on the ETA page .

ENCOUNTER

The orbit patch describing the next encounter with a body the current vessel will enter. If there is no such encounter coming, it will return the special string “None”. If there is an encounter coming, it will return an object of type Orbit. (i.e. to obtain the name of the planet the encounter is with, you can do: print ENCOUNTER:BODY:NAME., for example.).

BOOLEAN TOGGLE FLAGS:

These are special Boolean variables that interact with ship systems. They can be True or False, and can be set or toggled using the ON, OFF, and TOGGLE commands. Many of these are for stock action groups, while others are specific to kOS.

See also

STOCK ACTION GROUPS
Stock action groups are independent of actual part state and must be toggled to have an effect.
kOS PSEUDO ACTION GROUPS
Pseudo-action groups added by kOS which are dependent on actual part state and may still affect parts if set to the current value.
Variable Name Can Read Can Set Source What it manages
SAS yes yes stock SAS action group
RCS yes yes stock RCS thrusters action group
GEAR yes yes stock Landing gear action group
LIGHTS yes yes stock Lights action group
BRAKES yes yes stock Brakes action group
ABORT yes yes stock Abort action group
LEGS yes yes kOS The extended state of all landing legs
CHUTES yes yes kOS The armed state of all parachutes
CHUTESSAFE yes yes kOS The armed state of all “safe” parachutes
PANELS yes yes kOS The deployed state of solar panels
RADIATORS yes yes kOS The deployed state of radiators
LADDERS yes yes kOS The extended state of ladders
BAYS yes yes kOS The opened state of payload/service bays
INTAKES yes yes kOS The opened state of all intakes
DEPLOYDRILLS yes yes kOS The deployment state of all drills
DRILLS yes yes kOS The running state of all drills
FUELCELLS yes yes kOS The running state of all fuel cells
ISRU yes yes kOS The running state of all resource converters
AG1 yes yes stock Action Group 1.
AG2 yes yes stock Action Group 2.
AG3 yes yes stock Action Group 3.
AG4 yes yes stock Action Group 4.
AG5 yes yes stock Action Group 5.
AG6 yes yes stock Action Group 6.
AG7 yes yes stock Action Group 7.
AG8 yes yes stock Action Group 8.
AG9 yes yes stock Action Group 9.
AG10 yes yes stock Action Group 10.
AGn yes yes AGX ActionGroupsExtended action groups

Flight Control

There are bound variables used in controlling the flight of a ship, which can be found at the following links:

If you want to let kOS do a lot of the work of aligning to a desired heading for you, use Cooked Control.

If you want your script to manipulate the controls directly (as in “set yaw axis halfway left for a few seconds (using the ‘A’ key)”, then use Raw Control.

If you want to be able to READ what the player is attempting to do while your script is running, and perhaps respond to it, then use Reading the Pilot’s Control settings (i.e reading what the manual input is attempting) (By default your script will override manual piloting attempts, but you can read what the pilot’s controls are set at and make your autopilot take them under advisement - sort of like how a fly-by-wire plane works.)

Controls that must be used with LOCK
THROTTLE            // Lock to a decimal value between 0 and 1.
STEERING            // Lock to a direction, either a Vector or a Direction.
WHEELTHROTTLE       // Separate throttle for wheels
WHEELSTEERING       // Separate steering system for wheels

Time

MISSIONTIME

You can obtain the number of seconds it has been since the current CPU vessel has been launched with the bound global variable MISSIONTIME. In real space programs this is referred to usually as “MET” - Mission Elapsed Time, and it’s what’s being measured when you hear that familiar voice saying “T minus 10 seconds...” Point “T” is the zero point of the mission elapsed time, and everything before that is a negative number and everything after it is a positive number. kOS is only capable of returning the “T+” times, not the “T-” times, because it doesn’t read your mind to know ahead of time when you plan to launch.

Time Structure

Time is the simulated amount of time that passed since the beginning of the game’s universe epoch. (A brand new campaign that just started begins at TIME zero.)

TIME is a useful system variable for calculating the passage of time between taking physical measurements (i.e. to calculate how fast a phenomenon is changing in a loop). It returns the KSP simulated time, rather than the actual realtime sitting in the chair playing the game. If everything is running smoothly on a fast computer, one second of simulated time will match one second of real time, but if anything is causing the game to stutter or lag a bit, then the simulated time will be a bit slower than the real time. For any script program trying to calculate physical properties of the KSP universe, the time that matters is the simulated time, which is what TIME returns.

It’s important to be aware of the frozen update nature of the kOS computer when reading TIME.

System Variables

This section is about variables that describe the things that are slightly outside the simulated universe of the game and are more about the game’s user interface or the kOS mod itself. They represent things that slightly “break the fourth wall” and let your script access something entirely outside the in-character experience.

PRINT VERSION.            // Returns operating system version number. e.g. 0.8.6
PRINT VERSION:MAJOR.      // Returns major version number. e.g. 0
PRINT VERSION:MINOR.      // Returns minor version number. e.g. 8
PRINT VERSION:BUILD.      // Returns build version number. e.g. 6
PRINT SESSIONTIME.        // Returns amount of time, in seconds, from vessel load.

NOTE the following important difference:

SESSIONTIME is the time since the last time this vessel was loaded from on-rails into full physics.

TIME is the time since the entire saved game campaign started, in the kerbal universe’s time. i.e. TIME = 0 means a brand new campaign was just started.

HOMECONNECTION

See also

HOMECONNECTION
Globally bound variable for the connection to “home”.
CONTROLCONNECTION

See also

CONTROLCONNECTION
Globally bound variable for the connection to a control source.
KUNIVERSE

Kuniverse is a structure that contains many settings that break the fourth wall a little bit and control the game simulation directly. The eventual goal is probably to move many of the variables you see listed below into kuniverse.

Config

CONFIG is a special variable name that refers to the configuration settings for the kOS mod, and can be used to set or get various options.

CONFIG has its own page for further details.

WARP and WARPMODE

Time warp can be controlled with the variables WARP and WARPMODE. See WARP

MAPVIEW

A boolean that is both gettable and settable.

If you query MAPVIEW, it’s true if on the map screen, and false if on the flight view screen. If you SET MAPVIEW, you can cause the game to switch between mapview and flight view or visa versa.

LOADDISTANCE

LOADDISTANCE sets the distance from the active vessel at which vessels get removed from the full physics engine and put on-rails, or visa versa. Note that as of KSP 1.0 the stock game supports multiple different load distance settings for different situations such that the value changes depending on where you are. But kOS does not support this at the moment so in kOS if you set the LOADDISTANCE, you are setting it to the same value universally for all situations.

PROFILERESULT()

If you have the runtime statistics configuration option Config:STAT set to True, then in addition to the summary statistics after the program run, you can also see a detailed report of the “profiling” result of your most recent program run, by calling the built-in function ProfileResult(). “Profiling” is a programmer’s term that means gathering data about how long the program is spending doing each piece of the program. If you are trying to figure out whether your program spent more milliseconds printing numbers to the screen, or more milliseconds calculating a complex formula, or more milliseconds activating actions on a PartModule, and so on, then this feature may help. The ProfileResult() was meant mainly for kOS developers trying to internally determine which parts of the system could use the most optomizing. However, as long as it was implemented for that purpose, it may as well be made available to all the users of kOS as well.

To use:

SET CONFIG:STAT TO TRUE.
RUN MYPROGRAM.
PRINT PROFILERESULT().
// <or>
LOG PROFILERESULT() TO SOMEFIELNAME.csv.

The function ProfileResult() returns a string containing a formatted dump of your whole program, broken down into the more low-level instructions that make it up, with data values describing how long was spent in total on each instruction, how many times that instruction was executed, and the average time spent on a single execution of that instruction (by dividing the total time by the count of how many executions it had).

The format of ProfileResult() is designed to be suitable for importing into a spreadsheet program if you like, because it is formatted as a “comma separated values” file, or CSV for short.

SOLARPRIMEVECTOR

Gives the Prime Meridian Vector for the Solar System itself, in current Ship-Raw XYZ coordinates.

Both the Orbit:LONGITUDEOFASCENDINGNODE orbit suffix and the Body:ROTATIONANGLE body suffix are expressed in terms of degree offsets from this Prime Meridian Reference Vector.

What is the Solar Prime Reference Vector?

The solar prime vector is an arbitrary vector in space used to measure some orbital parameters that are supposed to remain fixed to space regardless of how the planets underneath the orbit rotate, or where the Sun is. In a sense it can be thought of as the celestial “prime meridian” of the entire solar system, rather than the “prime meridian” of any one particular rotating planet or moon.

In a hypothetical Earthling’s solar system our Kerbal scientists have hypothesized may exist in a galaxy far away, Earthbound astronomers use a reference they called the First Point of Aries, for this purpose.

For Kerbals, it refers to a more arbitrary line in space, pointing at a fixed point in the firmament, also known as the “skybox”.

Addons

Get-only. addons is a special variable used to access various extensions to kOS that are designed to support the features introduced by some other mods. More info can be found on the addons page.

Colors

There are several bound variables associated with hardcoded colors such as WHITE, BLACK, RED, etc. See the linked page for the full list.

CPU Vessel (SHIP)

Note

When kOS documentation refers to the “CPU vessel”, it has the following definition:

  • The “CPU Vessel” is whichever vessel happens to currently contain the CPU in which the executing code is running.

It’s important to distinguish this from “active vessel”, which is a KSP term referring to whichever vessel the camera is centered on, and therefore the vessel that will receive the keyboard controls for W A S D and so on.

The two terms can differ when you are in a situation where there are two vessels near each other, both of them within full physics range (i.e. 2.5 km), such as would happen during a docking operation. In such a situation it is possible for kOS programs to be running on one, both, or neither of the two vessels. The vessel on which a program is executing is not necessarily the vessel the KSP game is currently considering the “active” one.

Note

The built-in variable called SHIP is always set to the current CPU vessel. Whenever you see the documentation refer to CPU vessel, you can think of that as being “the SHIP variable”.

For all places where a kOS program needs to do something with this vessel, for the sake of centering SHIP-RAW coordinates, for the sake of deciding which ship is having maneuver nodes added to it and for the sake of deciding which vessel is being controlled by the autopilot. The vessel it is referring to is itself the CPU vessel and not necessarily what KSP thinks of as the “active vessel”.

The kOS CPU hardware

While it’s possible to write some software without knowing anything about the underlying computer hardware, and there are good design principles that state one should never make assumptions about the computer hardware when writing software, there are still some basic things about how computers work in general that a good programmer needs to be aware of to write good code. Along those lines, the KSP player writing a Kerboscript program needs to know a few basic things about how the simulated kOS CPU operates in order to be able to write more advanced scripts. This page contains that type of information.

Update Ticks and Physics Ticks

Note

New in version 0.17: Previous versions of kOS used to execute program code during the Update phase, rather than the more correct Physics Update phase.

Kerbal Space Program simulates the universe by running the universe in small incremental time intervals that for the purpose of this document, we will call “physics ticks”. The exact length of time for a physics tick varies as the program runs. One physics tick might take 0.09 seconds while the next one might take 0.085 seconds. (The default setting for the rate of physics ticks is 25 ticks per second, just to give a ballpark figure, but you must not write any scripts that depend on this assumption because it’s a setting the user can change, and it can also vary a bit during play depending on system load. The setting is a target goal for the game to try to achieve, not a guarantee. If it’s a fast computer with a speedy animation frame rate, it will try to run physics ticks less often than it runs animation frame updates, to try to make the physics tick rate match this setting. On the other hand, If it’s a slow computer, it will try to sacrifice animation frame rate to archive this number (meaning physics get calculated faster than you can see the effects.)

When calculating physics formulas, you need to actually measure elapsed time in the TIME:SECONDS variable in your scripts.

The entire simulated universe is utterly frozen during the duration of a physics tick. For example, if one physics tick occurs at timestamp 10.51 seconds, and the next physics tick occurs 0.08 seconds later at timestamp 10.59 seconds, then during the entire intervening time, at timestamp 10.52 seconds, 10.53 seconds, and so on, nothing moves. The clock is frozen at 10.51 seconds, and the fuel isn’t being consumed, and the vessel is at the same position. On the next physics tick at 10.59 seconds, then all the numbers are updated. The full details of the physics ticks system are more complex than that, but that quick description is enough to describe what you need to know about how kOS’s CPU works.

There is another kind of time tick called an Update tick. It is similar to, but different from, a physics tick. Update ticks often occur a bit more often than physics ticks. Update ticks are exactly the same thing as your game’s Frame Rate. Each time your game renders another animation frame, it performs another Update tick. On a good gaming computer with fast speed and a good graphics card, It is typical to have about 2 or even 3 Update ticks happen within the time it takes to have one physics tick happen. On a slower computer, it is also possible to go the other way and have Update ticks happening less frequently than physics tics. Basically, look at your frame rate. Is it higher than 25 fps? If so, then your update ticks happen faster than your physics ticks, otherwise its the other way around.

Electric Drain

New in version 0.19.0: As of version 0.19.0, the electric charge drain varies depending on CPU % usage. Prior to version 0.19.0, the CPU load made no difference and the electric drain was constant regardless of utilization.

Real world CPUs often have low power modes, and sleep modes, and these are vital to long distance probes. In these modes the computer deliberately runs slowly in order to use less power, and then the program can tell it to speed up to normal speed again when it needs to wake up and do something.

In kOS, this concept is simplified by just draining electric charge by “micropayments” of charge per instruction executed.

To change this setting if you want to re-balance the system, see the page about kOSProcessor part config values.

The shorthand version is this: The more instructions per update actually get executed, the more power is drained. This can be reduced by either lowering CONFIG:IPU or by making sure your main loop has a WAIT statement in it. (When encountering a WAIT statement, the remainder of the instructions for that update are not used and end up not counting against electric charge).

The system always costs at least 1 instruction of electric charge per update no matter what the CPU is doing, unless it’s powered down entirely, because there’s always at least 1 instruction just to check if it’s time to resume yet in a WAIT. The electric cost is never entirely zero as long as it’s turned on, but it can be very close to zero while it is stuck on a wait.

If your program spins in a busy loop, never waiting, it can consume quite a bit more power than it would if you explicitly throw in a WAIT 0.001. in the loop. Even if the wait is very small, the mere fact that it yields the remaining instructions still allowed that update can make a big difference.

Triggers

New in version 0.19.3: Note that as of version 0.19.3 and up, the entire way that triggers are dealt with by the underlying kOS CPU has been redesigned. In previous versions it was not possible to have a trigger that lasts longer than one physics tick, leading to a lot of warnings in this section of the documentation. Many of those warnings are now moot, which caused a re-write of most of this section of the documentation.

Many of the warnings and cautions mentioned below can really be boiled down to this one phrase, which is a good idea to memorize:

Main-line code gets interrupted by triggers, but triggers don’t get interrupted by main-line code (or other triggers).

There are multiple things within kerboscript that run “in the background” always updating, while the main script continues on. The way these work is a bit like a real computer’s multithreading, but not quite. Collectively all of these things are called “triggers”.

Triggers are all of the following:

  • LOCKS which are attached to flight controls (THROTTLE, STEERING, etc), but not other LOCKS.
  • ON condition { some commands }.
  • WHEN condition THEN { some commands }.

The way these work is that once per physics tick, all these trigger routines get run, including those locks that are always re-evaluated by the cooked steering, and the ON and WHEN triggers. (This isn’t quite true. The real answer is more complex than that - see CPU Update Loop elsewhere on this page).

Each of the steering locks behaves like a function that returns a value, and is re-called to get the new value for this physics tick. Each of the ON and WHEN triggers also behave much like a function, with a body like this:

if (not conditional_expression)
    return true.  // premature quit.  preserve and try again next time.
do_rest_of_trigger_body_here.
Triggers always execute at least as far as the Conditional Check

Even a trigger who’s condition isn’t true yet still needs to execute a few instructions into the trigger subroutine to discover that its condition isn’t true yet. The trigger still causes a subroutine call once per physics tick just to get far enough into the routine to reach the conditional expression check and discover that it’s not time to run the rest of the body yet, so it returns. An expensive to calculate conditional expression can really starve the system of instructions because it’s getting run every single physics tick. It’s good practice to try to keep your trigger’s conditional check short and fast to execute. If it consists of multiple clauses, try to take advantage of :ref:`short circuit boolean <short_circuit>` logic by putting the fastest part of the check first.

Wait in a Trigger

It is possible for kOS to allow a trigger that takes longer than one physics tick to execute. It just means the rest of the program is stuck until the trigger is done. Triggers can interrupt mainline code, but mainline code can’t interrupt triggers. Thus using a WAIT in a trigger, while possible, may be a bad idea because it stops the entire rest of the program, including all its triggers, from happening, unlike how waits in mainline code work. Before considering doing this, remember that a lock steering to .... command and a lock throttle to.... command are both effectively triggers too. If you wait in a trigger, you prevent the cooked steering values from updating while that wait is happening. Your ship will be stuck continuing to use whatever previous values they had just before the trigger’s wait began, and they won’t be recalculated until your trigger’s wait is over.

Short version: While WAIT is possible from inside a trigger and it won’t crash the script to use it, it’s probably not a good design choice to use WAIT inside a trigger. Triggers should be designed to execute all the way through to the end in one pass, if possible.

This is a consequence of: Main-line code gets interrupted by triggers, but triggers don’t get interrupted by main-line code (or other triggers).

Advanced topic: why not threading?

If you don’t understand the terms used below, you can safely skip this part of the explanation. It’s here for the advanced users who already know how to program and might be thinking there’s a better way to do this.

Remember that triggers aren’t quite true multi-threading. If you make a trigger WAIT UNTIL AG1., you’re making the entire program wait. If you make the main-line code WAIT, there is a mechanism to make triggers fire off during that WAIT because triggers can interrupt main line code, and in fact that’s their intended purpose - to behave as interrupts.

But main line code can’t interrupt triggers. The only way to make them both ‘equal’ citizens and be capable of interrupting each other would be to implement a form of threading inside kOS. The program context that kOS keeps track of while the program is executing consists of a stack, an array of the program opcodes, stack records that point to dictionaries of variables (on the stack so they can deal with scoping), and a current instruction pointer. It’s completely plausible that kOS could wrap all that inside a single class, and then make one instance of it per thread, and get multi-threading that way. But there is reluctance to implement this because once the kOS system can do threading, the documentation explaining how to use kOS won’t be so beginner-friendly anymore. Allowing for threading opens up a whole new can of worms to explain, including atomic sections and how concurrently accessing the same variable can break everything if you’re not careful, etc.

Do Not Loop a Long Time in a Trigger Body!

For similar reasons to the explanation above about the WAIT command used inside triggers, it’s not really a good idea for a trigger to have a long loop inside it that just keeps going and going.

The system does allow a trigger to take more than one physics tick to finish. There are cases where it is entirely legitimate to do so if the trigger’s body has too much work to do to get it all done in one update. However, all triggers should be designed to finish their tasks in finite time and return. What you should not do is design a trigger’s body to go into an infinite loop, or a long-lasting loop that you thought would run in the background while the rest of the program continues on.

This is because while you are in a trigger, ALL the other triggers aren’t being fired, and the main-line code isn’t being executed. A trigger that performs a long-running loop will starve the rest of the code in your kerboscript program from being allowed to ever run again.

This is a consequence of: Main-line code gets interrupted by triggers, but triggers don’t get interrupted by main-line code (or other triggers).

But I Want a Loop!!

If you want a trigger body that is meant to loop a long time, the only workable way to do it is to design it to execute just once, but then make it return true (or use the preserve keyword, which is basically the same thing) to keep the trigger around for the next physics tick. Thus your trigger becomes a sort of “loop” that executes one iteration per physics tick.

Wait!!!

Any WAIT statement causes the kerboscript program to immediately stop executing the main program where it is, even if far fewer than Config:IPU instructions have been executed in this physics tick. It will not continue the execution until at least the next physics tick, when it will check to see if the WAIT condition is satisfied and it’s time to wake up and continue.

Therefore ANY WAIT of any kind will guarantee that your program will allow at least one physics tick to have happened before continuing. If you attempt to:

WAIT 0.001.

But the duration of the next physics tick is actually 0.09 seconds, then you will actually end up waiting at least 0.09 seconds. It is impossible to wait a unit of time smaller than one physics tick. Using a very small unit of time in a WAIT statement is an effective way to force the CPU to allow a physics tick to occur before continuing to the next line of code. In fact, you can just tell it to wait “zero” seconds and it will still really wait the full length of a physics tick. For example:

WAIT 0.

Ends up being effectively the same thing as WAIT 0.01. or WAIT 0.001. or WAIT 0.000001.. Since they all contain a time less than a physics tick, they all “round up” to waiting a full physics tick.

Similarly, if you just say:

WAIT UNTIL TRUE.

Then even though the condition is immediately true, it will still wait one physics tick to discover this fact and continue.

CPU Update Loop

Note

As of version 0.17.0, The kOS CPU runs every physics tick, not every update tick as it did before.

New in version 0.19.3: As of version 0.19.3, the behaviour of triggers was changed dramatically to enable triggers that last longer than one physics tick, thereby causing the section of documentation that follows to be completely re-written. If you were familiar with triggers before 0.19.3, you should read the next section carefully to be aware of what changed.

On each physics tick, each kOS CPU that’s fully present “near” enough to the player’s current ship to be fully loaded, including the current ship itself, wakes up and performs the following steps, in this order:

  1. For each TRIGGER (see below) that is currently enabled, manipulate the call stack to make it look as if the program had just made a subroutine call to the trigger right now, and the current execution is now set to the start of the trigger’s code. Remeber that from the point of view of the CPU, triggers appear to be subroutines it just unconditionally calls whether or not their trigger condition is true yet. The code to decide that it’s not really time yet for the trigger to fire is contained inside the trigger subroutine itself. The first thing the trigger routine does is return prematurely if its trigger condition hasn’t been met. If more than one such trigger is enabled and needs to be set up, then the calls to the triggers will end up looking like a list of nested subroutine calls on the stack had just begun, and the current instruction is the start of the innermost nested subroutine call.
  2. Any TRIGGER which has just been set up thusly is temporarily removed from the list of enabled triggers, so it will be ignored in step (1) above should the physics tick expire before the trigger’s code had its chance to go.
  3. (THE LOOP PART): The cpu now goes on and executes the next Config:IPU number of instructions, mostly not caring about whether those instructions are ordinary main-line code or instructions that are inside of a trigger. Step (1) above has caused each trigger to look like just a normal subroutine was called from main-line code. When the nested subroutines all finish, the call stack has “popped” all the way back to where the mainline code left off, and so it just continues on from there. Warning: Advanced sentence follows. You can ignore it if you don’t understand it: Because kOS is a pure stack computer with no temporary data held in “registers”, this technique works because all relevant data must be on the stack, and thus will get returned to its original state once the interrupting triggers are done with their work and the stack has fully popped back to where it started from.
  4. While executing the instructions in Step(3) above, if any of those instructions are a WAIT command, the execution stops there for now and the full number of Config:IPU instructions won’t be used this update. This is true BOTH of wait’s in main-line code and wait’s in trigger code. Although you can wait in a trigger, doing so also stops main line code until that trigger is done waiting.
  5. One thing the CPU does keep track of while executing the instructions, though, is whether or not it got all the way back to executing mainline code again or not. It’s possible that it spent the entire Config:IPU inside triggers and never got back to mainline code. If it has gotten back to mainline code and executed at least one mainline instruction, then it re-enables all the triggers that wished to be re-enabled because they executed preserve. or did a return true. (They were temporarily disabled up in Step(2) above.) If it has not gotten back to mainline code yet, then that means it’s about to finish a physics tick while still inside a trigger, and it shouldn’t allow more triggers to re-fire yet until the main-line code has had a chance to go again.

Note that the number of instructions being executed (CONFIG:IPU) are NOT lines of code or kerboscript statements, but rather the smaller instruction opcodes that they are compiled into behind the scenes. A single kerboscript statement might become anywhere from one to ten or so instructions when compiled.

The Frozen Universe

Each physics tick, the kOS mod wakes up and runs through all the currently loaded CPU parts that are in “physics range” (i.e. 2.5 km), and executes a batch of instructions from your script code that’s on them. It is important to note that during the running of this batch of instructions, because no physics ticks are happening during it, none of the values that you might query from the KSP system will change. The clock time returned from the TIME variable will keep the same value throughout. The amount of fuel left will remain fixed throughout. The position and velocity of the vessel will remaining fixed throughout. It’s not until the next physics tick occurs that those values will change to new numbers. It’s typical that several lines of your kerboscript code will run during a single physics tick.

Effectively, as far as the simulated universe can tell, it’s as if your script runs several instructions in literally zero amount of time, and then pauses for a fraction of a second, and then runs more instructions in literally zero amount of time, then pauses for a fraction of a second, and so on, rather than running the program in a smoothed out continuous way.

This is a vital difference between how a kOS CPU behaves versus how a real world computer behaves. In a real world computer, you would know for certain that time will pass, even if it’s just a few picoseconds, between the execution of one statement and the next.

KOS Processor PartModule Configuration Fields

Note

(The most important part of this page is probably the section below on the EcPerInstruction setting.)

When using ModuleManager or directly editing the part.cfg files a mod ships with, it is useful to know what those settings mean. This page documents what some of the settings in the kOS part config files mean.

These are the settings typically found in the files named:

GameData/kOS/Parts/ name_of_part_here /part.cfg

You can add kOS to any other part in the game by adding the kOS module to the part (although this may cause strange interactions that are not officially supported).

Here is an example of the kOS processor module : the one that is attached to the small disk shaped CPU part (KR-2402 b). Optional fields have been added in comments for clarity:

MODULE
{
    name = kOSProcessor
    diskSpace = 5000
    ECPerBytePerSecond = 0
    ECPerInstruction = 0.000004
    # Optional fields shown below with default value
    # baseDiskSpace = 0
    # diskSpaceCostFactor = 0.0244140625
    # baseModuleCost = 0
    # diskSpaceMassFactor = 0.0000048829
    # baseModuleMass = 0
}

If you add a section like that to the part.cfg, via directly editing it, or via a ModuleManager configuration, then you cause that part to contain a kOS computer.

When editing these values, the case is important. You must capitalize them and lowercase them exactly as shown here.

Here is a list of all the potential fields you could set in that section:

diskSpace

  • Type: integer
  • Default if omitted: 1024
  • Effect: The disk space the part has by default if the adjustment slider in the VAB isn’t changed by the user.

baseDiskSpace

  • Type: integer
  • Default if omitted: copied from initial diskSpace setting
  • Effect: The lowest disk space the part can have at the lowest end of the slider in the VAB.

The possible choices for disk space the user can select on the slider is always one of 1x, 2x, and 4x this amount.

ECPerInstruction:

  • Type: float
  • Default if omitted: 0.000004
  • Effect: How much ElectricCharge resource is consumed per instruction the program executes.

This is a very small number so the electric charge can be payed in micro-amounts as the CPU executes. Remember that with default Unity settings (which can be changed on the KSP game’s main settings screen at the launch of the program), the game runs 25 physical updates per second. So if the setting is 0.000004, and program is executing 200 instructions per update, at 25 updates per second, then it’s consuming 0.02 Ec per second, or 1 Ec every 50 seconds.

This is the setting from which the value in the VAB/SPH info panel, 1 Electric Charge per N instructions, is derived (it’s the reciprocal of that display value).

More information about programs reducing power consumption can be found in the section of the CPU hardware description that talks about electric drain.

ECPerBytePerSecond:

  • Type: float
  • Default if omitted: 0.0
  • Effect: How much ElectricCharge resource is consumed per byte of disk space avaialable (not just used).

It is possible to make the disk cost more electricity to run the bigger it is. By default this ships as zero, but it can be changed by a re-balancing mod by changing this value. This value is multiplied by how much available space there is total (used + free), not just how much is currently in use.

diskSpaceCostFactor:

  • Type: float
  • Default if omitted: 0.0244140625
  • Effect: How much additional cost is incurred per byte of disk space added via the editor tweakable.

When using the editor tweakable to increase storage, cost is added to the module. That additional cost is found by multiplying the number of additional bytes by this factor. The default value is balanced for approximately 100 additional funds for 4096 Bytes.

baseModuleCost:

  • Type: float
  • Default if omitted: 0.0
  • Effect: How much cost is added to the part cost by including this module.

While kOS only includes kOSProcessor in dedicated parts, users may choose to add it to existing parts by editing cfg files or using a ModuleManager patch. In cases where the cost of a part may depend on multiple PartModules this allows you to specify the cost for the kOSProcessor itself without changing the part’s cost directly.

diskSpaceMassFactor:

  • Type: float
  • Default if omitted: 0.0000048829
  • Effect: How much additional cost is incurred per byte of disk space added via the editor tweakable.

When using the editor tweakable to increase storage, mass is added to the module. That additional mass is found by multiplying the number of additional bytes by this factor. The default value is balanced for approximately 0.02kg of additional mass for 4096 Bytes.

baseModuleMass:

  • Type: float
  • Default if omitted: 0.0
  • Effect: How much mass is added to the part cost by including this module.

While kOS only includes kOSProcessor in dedicated parts, users may choose to add it to existing parts by editing cfg files or using a ModuleManager patch. In cases where the mass of a part may depend on multiple PartModules this allows you to specify the mass for the kOSProcessor itself without changing the part’s mass directly.

kOS Settings Windows

kOS Control Panel

As of kOS v0.15, kOS now makes use of Kerbal Space Program’s built-in Application Launcher toolbar, to open a config/status panel for kOS.

This panel behaves like the other display panels the launcher creates, and operates mutually exclusively with them. (For example you can’t make the kOS App Control Panel appear at the same time as the stock Resource display panel. Opening one toggles the other one off, and visa versa.)

Here is an annotated image of the control panel and what it does:

_images/controlPanelWindow.png

Key Notes:

  1. Contains every vessel and associated kOS processor that is currently fully loaded into the game. kOS processors that are not currently in “physics range” because they are far enough away to be unloaded will not appear in the list, as they are not loaded and thus cannot do anything.
  2. kOS version number.
  3. kOS processor part name and name tag.
  4. Power status display. This is not an interactable button so as not to bypass attempts to lock out control of events/actions.
  5. Toggle button to open or close the terminal window.
  6. Toolbar button, click to toggle the control panel window on and off.
  7. Toggle button to activate or deactivate telnet. See Config:TELNET.
  8. Displays or sets the port that the telnet server will listen on. See Config:TPORT.
  9. Toggle button to enable or disable forcing the telnet server to only listen on the local loopback address. See Config:LOOPBACK.
  10. When you hover your cursor over a processor it will be highlighted purple.

KSP Difficulty Settings Window

Note

New in version v1.0.2: Previous versions of kOS kept all settings accessible from the App Launcher Window. KSP version 1.2.0 introduced a new way to store settings within the save file itself, and most settings were migrated to this system/window.

This settings window is accessible when you first start a new game by clicking on “Difficulty Options”, or in an existing game by clicking on “Difficulty Options” from the in game settings menu (accessed by pressing the Escape key, and then clicking “Settings” from the pop up window).

Note

The only reason these settings are on the difficulty options screen is that it’s the only place KSP allows mods like kOS to add a new section of custom settings to the user interface. Don’t think of it as “cheating” to change them mid-game because they’re not really difficulty options, despite the name.

Difficulty Buttons
New game difficulty button In game difficulty button
_images/newGameDifficultyButton.png _images/inGameDifficultyButton.png

By selecting the kOS tab of the Difficulty Settings Menu, you will be presented with the following options. All settings displayed in this window are local to the current save game.

_images/settingsWindow.png

Key Notes:

  1. All CPU’s run at a speed that executes up to this many kRISC opcodes per physics ‘tick’. See Config:IPU
  2. When storing local volumes’ data in the saved game, it will be compressed then base64 encoded. See Config:UCP
  3. After the outermost program is finished, you will see some profiling output describing how fast it ran. See Config:STAT
  4. When launching a new ship, or reloading a scene, the default volume will start as 0 instead of 1. See Config:ARCH
  5. When you press the “Hide UI” button (F2 in default bindings) kOS’s terminals will hide themselves too. See Config:OBEYHIDEUI
  6. kOS will throw an error if Infinity or Not-A-Number is the result of any expression. This ensures no such values can ever get passed in to KSP’s stock API, which doesn’t protect itself against their effects. See Config:SAFE
  7. When kOS throws an error, you hear a sound effect. See Config:AUDIOERR
  8. When kOS has an error, some error messages have alternative longer paragraph-length descriptions that this enables. See Config:VERBOSE
  9. If you have the “Blizzy Toolbar” mod installed, only put the kOS button on it instead of both it and the stock toolbar.
  10. (For mod developers) Spams the Unity log file with a message for every time an opcode is executed in the virtual machine. Very laggy. See Config:DEBUGEACHOPCODE
  11. Connectivity manager selector
  12. List of all available connectivity managers.
  13. Brightness of a kOS terminal when it appears for the first time in a scene. (You must reload the scene to see the effect of any changes to this slider.)

Making GUIs in kOS

New in version 1.1.0.

Sometimes it is useful to create an in-game set of screen widgets to let you interact with the kOS program with mouseclicks. You can create GUI widgets in kOS for this purpose, making them as complex or as simple as you like. The widgets will live inside windows that pop up on screen as you invoke them.

Be aware that these widgets cannot be operated via the telnet feature. You need to manipulate them with the mouse on-screen in the game window.

The topic of how you actually make a GUI widget in kOS is on its own separate page in these documents.. The basic object type you start with is the GUI structure.

The kOS Telnet Server

kOS now supports the ability to enable a telnet server inside Kerbal Space Program.

Telnet is an old network protocol designed in the early days of the Internet, long before World Wide Web. Its purpose was (is) to allow you to get access to the remote command line interfaces of distant server computers, acting as if the keyboard and computer screen in front of you was a terminal hooked up to a distant computer. kOS uses this protocol to let you access the kOS terminal from a program outside of Kerbal Space Program.

There are freely available programs you can use as the telnet client to behave like terminal windows outside of the KSP window. A list of them appears below in the section called Telnet clients.

There are some security implications of enabling the kOS telnet server, and the first time you turn it on you will see some warning messages to this effect. If you want to read further about these concerns before deciding to turn it on, see the section called “Security” at the bottom of this page.

_images/telnet.png

Telnet clients

The telnet server for kOS requires the use of a telnet client program. We recommend the following programs, although you can use others:

For Windows
We recommend Putty, the free terminal emulator for Windows, although any good terminal emulator should do the job, provided it is capable of operating in an “XTERM - compatible” mode.
For Mac
You shouldn’t have to install anything. There should be a telnet client already installed, which you can access by opening up your command terminal, and then running it as a command-line tool. To see how to use it, read below in the section titled “HOWTO: Command-line client”. The built-in Terminal.app for OSX understands the XTERM command sequences that kOS uses and in fact identifies itself as a type of XTERM when used with a telnet client.
For Linux
You shouldn’t have to install anything. There should be a telnet client already installed, and an xterm program already installed in most any Linux distribution. Open an xterm window, and in that window type the telnet command, as described by the section titled “HOWTO: Command-line client

Using it

  1. Turn on the telnet server by going into the app control panel and clicking on the green circle next to the word “Telnet”. Alternatively, you can issue the command:

    SET CONFIG:TELNET TO TRUE.
    

    from any terminal window in kOS.

  2. The very first time you do this, you will get a warning message, as per SQUAD’s rule number 5 about mods that run network services. After accepting and clicking “yes”, the server will be running on loopback 127.0.0.1 (if you want to make it run on a non-loopback address, you will get a secondary warning message about that too.)

  3. Launch your telnet client (there is a list of telnet clients that are known to work listed below.

  1. When you first log in to the server you should see the “Welcome menu”, which is a screen looking like this:

    Terminal: type = XTERM, size = 80x24
    ________________________________________________________________________________
                  Menu GUI   Other
                  Pick Open Telnets  Vessel Name (CPU tagname)
                  ---- ---- -------  --------------------------------
                   [1]   no    0     Randy Viewer (CX-4181())
                   [2]   no    0     Randy Viewer (CX-4181())
                   [3]   no    0     Randy Viewer (CX-4181())
                   [4]   no    0     Randy Viewer (CX-4181())
                   [5]   no    0     Randy Viewer (CX-4181())
                   [6]   no    0     Randy Viewer (CX-4181())
    --------------------------------------------------------------------------------
    Choose a CPU to attach to by typing a selection number and pressing
    return/enter. Or enter [Q] to quit terminal server.
    
    (After attaching, you can (D)etach and return to this menu by pressing Control-D
    as the first character on a new command line.)
    --------------------------------------------------------------------------------
    >_
    

    Or, if there are no CPU’s within range, it will look like this:

    Terminal: type = XTERM, size = 80x24
    ________________________________________________________________________________
                  Menu GUI   Other
                  Pick Open Telnets  Vessel Name (CPU tagname)
                  ---- ---- -------  --------------------------------
                                          <NONE>
    

    At any moment you can force a redraw of the menu by entering any gibberish non- numeric data and hitting enter.

    This menu should match 1:1 with the list of CPU’s you see on the kOS applauncher control panel.

_images/telnet_welcomemenu.png

The welcome menu, shown here in a Mac OSX terminal.

  1. Pick a CPU. Pick one of the CPU’s listed by typing its number and hitting enter.
  2. Your telnet is now connected to the server and should behave as the terminal for that CPU. You can type commands and do what you like, the same as if you had been directly on its window.
  3. See the section labeled Special Keys to see how to use the keyboard from your telnet client.
  4. It is possible to have multiple terminals hooked up to the same in-game CPU. They will all behave as clones of each other, each being an equal “first citizen”. (For example a pair of people could execute the “stage.” command by having one of them type “st”, then the other types “age”, followed by the first person typing ”.” and the return key.) All the keyboards and all the screens are slaved together to be equal. You can view the in-game gui terminal while somebody is typing on a telnet temrinal.
  5. In order to make the terminals act as clones of each other, the game will attempt to keep them all the same size. If you resize your telnet client window, it should cause the in-game window to change size to match. (If your terminal type is XTERM, then the same thing works in reverse. If it’s VT100 then it doesn’t.)

Warning

Certain implementations of the xterm terminal emulation and the telnet client have created a strange unending cascade of terminal resizes when you have two different telnet clients connected to the same GUI terminal and one of them is dragged to a new size. Because some implementations don’t wait until they’re done resizing to report their new size through telnet and instead report their intermediate sizes as they are being stretched, the attempt to keep them the same size causes them to effectively “argue” back and forth with each other, constantly changing each other’s size. If you experience this problem (your terminal window will be flipping back and forth between two different sizes, resizing itself over and over again in a neverending loop), you can try to get out of it by issuing a hardcoded command to set the terminal size, such as:

SET TERMINAL:WIDTH TO 50.

Doing this should force all the connected telnet XTERM windows to stop arguing with each other about what the size is, and get them synced up again.

  1. At any time you may disconnect your telnet client from the terminal by hitting control-D as the first character of a new line. This will bring you back to the telnet welcome menu again.

Special Keys

The following keys have special meaning in the telnet session:

Control-L
Force refresh Pressing Control-L forces the kOS telnet server to redraw your whole screen for you from scratch. This is useful if you encounter strange line noise, interrupted messages, or for just any occasion where you suspect the screen isn’t being drawn correctly. Pressing control-L will ensure your display gets fully re-synced with what’s in the buffer in memory for the terminal.
Control-C
interrupt process This is the same meaning as control-C in the normal GUI terminal - it breaks the program execution. The reason it gets a special mention here is that it also causes a flush of all the pending input you may have typed ahead in the queue. If you’ve been typing blindly ahead, and then hit Control-C, it will erase your typed-ahead keys as it sends the interrupt to the server. This is deliberate, and typical practice for an interrupt character sent over a remote shell setting.
Control-D
detach If you hit control-D as the first character of a new line, it will detach your telnet session from the CPU and return you to the welcome menu.
Cursor Keys
should be mapped If your terminal has identified itself as one of the known types that kOS supports, it should understand your arrow keys as arrow keys. If you see the text “[A” when you type up-arrow, or “[C” when you type right-arrow, this is a clue that kOS didn’t recognize your terminal type properly.
Other Keys
might be mapped Some keys like the Del (to the right), Home, and End keys are often not mapped correctly in some terminal emulator programs. If you have trouble using HOME and END, you can try Control-A and Control-E as alternates for Home and End.
Control-A
home This is an alternate way to press the “home” key, just in case your terminal emulation isn’t sending the officially understood terminal code for it.
Control-E
end This is an alternate way to press the “end” key, just in case your terminal emulation isn’t sending the officially understood terminal code for it.
Control-H
backspace This is an alternate way to press the “backspace” key, just in case your terminal emulation isn’t sending the officially understood terminal code for it.
Control-M
Return This is an alternate way to press the “enter” or “return” key, just in case your terminal emulation isn’t sending the officially understood terminal code for it.

HOWTO: Putty client

(These instructions assume you use the default kOS Telnet server settings, of the loopback address 127.0.0.1, and port number 5410. If you’ve changed those settings then alter the numbers you see here accordingly.)

  1. Run KSP, and get it into a scene where there exists a vessel with at least one kOS CPU loaded into it.
  2. Run Putty.
  3. On the first dialog you see, click the Telnet radio-button selection.
  4. Type in the number 127.0.0.1 in the large blank above the radio buttons that is labeled “Host Name (or IP address)”.
  5. Type in the number 5410 in the smaller blank to the right of it that is labeled “Port”.
  6. At the bottom of the screen, select the radio button labeled “Never” under “Close window on exit”.
  7. Click the Open button to connect to the server.

(You can also save these settings under a name for later re-use.)

Step 6 is important. Without it, Putty would just make the window disappear any time there’s a problem, making it very hard to diagnose because you can’t see what message the server was sending back to you just before the window went away.

HOWTO: Command-line client

_images/telnet_xterm.png

Showing the use of telnet in an x-term window.

_images/telnet_macterminal.png

Showing the use of telnet in a Mac OSX terminal.

(These instructions assume you use the default kOS Telnet server settings, of the loopback address 127.0.0.1, and port number 5410. If you’ve changed those settings then alter the numbers you see here accordingly.)

  1. Run KSP, and get it into a scene where there exists a vessel with at least one kOS CPU loaded into it.

  2. Open a command shell window that either IS xterm, or emulates xterm. For OSX, the default command shell should work fine. For Linux, you should actually have the xterm program itself installed that you can use.

  3. At the shell prompt in that window, enter the command:

    telnet 127.0.0.1 5410
    

HOWTO: Other client

  1. Set the IP address to 127.0.0.1 using whatever means the program has for it.
  2. Set the port number to 5410 using whatever means the program has for it.
  3. Set the terminal to XTERM emulation mode if it has it, or VT100 mode as a less good, but still perhaps workable option.
  4. Run the terminal.

Security

The telnet protocol performs no encryption of its data, and as such any attempt at securing the system using a name/password combination would have been utterly pointless. Rather than provide a false sense of security that’s not really there, we decided to make it obvious that there’s no security by not even implementing a name and password for connecting to the kOS telnet server.

The purpose is to make it clear that if you want to open up your kOS telnet server, you need to be careful about how you do it.

The default settings that kOS ships with sets your kOS telnet server to operate on the loopback address (127.0.0.1) so that you won’t accidentally open anything up to the public without thinking about it and making a conscious decision to do so. If you don’t know what that means, it means this: Any server that runs on the magic special address 127.0.0.1, known as “loopback”, is incapable of taking connections from other computers besides itself.

In order to allow your kOS telnet server to take connections from other computers, you will typically need to do one of two things:

Either set the CONFIG:IPADDRESS option to the address of your computer and then restart your telnet server (turn it off and on again using the button on the control panel), or (much better), set up a remote ssh tunnel that will map from your current machine’s loopback address on the port number of your server to some remote other computer you want to connect from, to a port on it. The ssh tunnel is the preferred method, but describing how to set one up is beyond the scope of this document. You can read more For windows or For UNIX (both Mac and Linux).

Example: Let’s say you have a remote Unix machine you’d like to enable logins from, from there and nowhere else. You can forward from your own machine’s 127.0.0.1, port 5410, to the remote machine’s, oh let’s say 127.0.0.1, port 54100. Then anyone on the remote machine could telnet to ITS 127.0.0.1, port 54100 and end up talking to your machine’s port 5410 on its loopback address.

Port forwarding

If you opt to use a non-loopback address on your kOS telnet server, then you will probably also, if you have a typical home network setup, need to enable port forwarding on your router if you want people from outside your house to connect to it. (Again, think about the implications of doing so before you do it). This is a topic beyond the scope of this document, but help can be found out on the web for it. Search for “port forward home router”. (It is probably also a good idea to include the make & model number of your router device in your search terms, to get a nicely narrowed result that’s exactly what you need.)

Why not ssh?

The original plan for kOS was to include an ssh server instead of a telnet server. However this proved problematic as open source solutions in C# for the server-side of ssh were hard to come by (there’s several for the client side only, and plenty of server-side code that’s not in C#), and implementing the entire server side of the ssh protocol from scratch is a daunting task that would have taken too much time away from other development of kOS. (While implementing from scratch the server side of the older, simpler telnet protocol, while still work, was more doable).

Homemade telnet clients

This section is only of interest to hobbyists making Kerbal console hardware rigs and software developers trying to make interface mods that pretend to be kOS terminals. If you are neither of those two, then don’t worry if this section looks like gibberish to you. It can be skipped.

TELNET PROTOCOL

If you wish to make your own homemade telnet client and connect it up to the kOS telnet server, the following is the required subset of the telnet protocol that your telnet client must speak, and the terminal requirements it must fulfill:

  1. It must suppress local character echoing, and enter character-at-a-time mode, by implementing both the ECHO negotiation described by RFC857, and the SUPPRESS GO AHEAD negotiation described by RFC858. These are used in the following way: Your client must NOT ECHo (letting the server do it), and your client must suppress go-ahead messages (allowing real-time back-and-forth).
  2. It must implement the underlying DO/DONT and WILL/WONT, and SB/SE infrastructure of the main telnet RFC854. It must send break (ctrl-C) as the IP interrupt process command (byte 255 followed by 244). kOS does not use much of the negotiations of the protocol mentioned on RFC854, other than those that are necessary to enable the other ones mentioned here.
  3. It must implement the Terminal-Type option described by RFC1091. Furthermore, as of this writing, kOS only knows how to understand two terminal types, “XTERM”, and “VT100”. If your terminal type is identified as anything else, kOS may deny your connection, or at the very least just not work right. Even terminals that are capable of emulating XTERM or VT100 commands won’t work right if they don’t identify themselves as XTERM or VT100. kOS does not know how to guess what emulation mode to enter if it doesn’t recognize your terminal type string.
  4. It must implement the NAWS, Negotiate About Terminal Size option, as described by RFC1073. kOS uses this to decide how to size its mental image of your terminal to match your terminal’s real size. Note that this negotiation is one-way. Your client can use it to tell the server about its size, but the server can’t use it to tell your client to change its size. Instead if your client can respond to changing sizes at the behest of the server, it must do so through terminal escape code characters sent back to it on the stream, above the telnet protocol layer itself. (For example, if you identify as XTERM, you will be sent the XTERM escape code pattern ESC [ 8 ; height ; width t, which is the XTERM escape code for setting the terminal size.) This is because the telnet protocol was never written to accommodate the concept of server-initiated resizes.

Making a telnet client from scratch that actually follows protocol may be a complex enough task that the smarter solution is to just use an existing telnet program, if you are trying to create some sort of hardware rig. These days a small cheap mini-hardware implementation of Linux should be doable, and could include the telnet client installed in it for very little storage cost.

TERMINAL EMULATION

As of right now, the terminal emulation of kOS only really supports XTERM or VT100 well, however the infrastructure is in place to support modifications to map to other terminal types. If you want to try a hand at adding the terminal emulation for a currently unsupported terminal, you’d do it by subclassing the kOS.UserIO.TerminalUnicodeMapper class. You can look at kOS.UserIO.TerminalXtermMapper as a sample to see what you need to do.

If you have a project where you want to just work with the terminal codes already supported, then these are the subset you need to support:

ASCII
The following terms should have their normal ASCII meaning:
0x08 (control-H)
backspace key
0x0d (control-M)
Return key. On output it means go to left edge but don’t go down a line. A typical eoln needs to occur using its ASCII standard of both a return character 0x0d AND a linefeed character 0x0a
0x0a (control-J)
On output it means go to go down a line but don’t go to the left edge A typical eoln needs to occur using its ASCII standard of both a return character 0x0d AND a linefeed character 0x0a

Terminal codes: The following terms should have their VT100/XTERM meaning

Left-Arrow
ESC [ D – both on input and on output
Right-Arrow
ESC [ C – both on input and on output
Up-Arrow
ESC [ A – both on input and on output
Down-Arrow
ESC [ B – both on input and on output
Home-key
ESC [ 1 ~ – input only
End-key
ESC [ 4 ~ – input only
Delete-to-the-right-key
ESC [ 3 ~ – input only
PageUp-key
ESC [ 5 ~ – input only
PageDown-key
ESC [ 6 ~ – input only
Move-to-home-of-screen-upper-left
ESC [ H – output only
Move-to-end-of-line
ESC [ F – output only
Teleport-cursor-to-coordinate
ESC [ row ; col H – output only: rows and cols start counting at 1, not 0
Clearscreen
ESC [ 2 J – output only
Scroll-screen-up-one-line-keeping-cursor-where-it-is
ESC [ S – output only
Scroll-screen-down-one-line-keeping-cursor-where-it-is
ESC [ T – output only
Delete-to-the-left-of-cursor-ie-backspace
ESC [ K – output only
Delete-at-the-cursor-toward-the-right
ESC [ 1 K – output only

XTERM codes: The following codes are for the XTERM emulation only

Server-telling-client-to-resize-screen
ESC [ 8 ; newheight ; newwidth t – The height/width are in chars
Server-telling-client-to-change-window-title
ESC ] 2 ; title string BEL – where BEL is the character normally used to mean beep: control-G or 0x07. But in this context it just marks the end of the title and shouldn’t cause a beep. Note this is NOT a typo that it uses a right-square-bracket (“]”) here where all the other codes used a left-square-bracket (“[”). That’s actually how the xterm control sequence for this really looks.

Any value not mentioned in the list above might still get sent, but you should be able to capture and ignore it.

Files and Volumes

Using the COPYPATH, SWITCH, DELETEPATH, and RENAMEPATH commands, you can manipulate the archive and the volumes as described in the File I/O page. But before you do that, it’s useful to know how kOS manages the archive and the volumes, and what they mean.

Script Files

There is one file per program. You can use the words “file” and “program” interchangeably in your thinking for the most part. Files are stored in volumes and there can be more than one file in a volume provided there’s enough room. Volumes have small storage and there’s no way to span a file across two volumes, so the limit to the size of a volume is also effectively a limit to the size of a program.

File Storage Behind the Scenes

In the Archive:

  • If a file is stored on the volume called “Archive” (or volume number zero to put it another way), then behind the scenes it’s really stored as an actual file, with the extension .ks, on your computer (As of right now it’s located in Ships/Script but that location is likely to change to somewhere in GameData in a future version.) Each program is a simple text file you can edit with any text editor, and your edits will be seen immediately by KOS the next time it tries running that program from the archive volume.
  • Historical note: older versions of kOS (0.14 and earlier) used the directory Plugins/PluginData/Archive for the archive.

In any other volume besides Archive:

  • If a file is stored on any volume other than archive, then behind the scenes it’s stored actually inside the saved game’s persistence file in the section for the KOS part on the vessel. What’s a Volume

A Volume is a small unit of disk storage that contains a single hard drive with very limited storage capacity. It can store more than one program on it. To simulate the sense that this game takes place at the dawn of the space race with 1960’s and 1970’s technology, the storage capacity of a volume is very limited.

For example, the CX-4181 Scriptable Control System part defaults to only allowing 1000 bytes of storage.

The byte count of a program is just the count of the characters in the source code text. Writing programs with short cryptic variable names instead of long descriptive ones does save space, although you can also save space by compiling your programs to KSM files where the variable names are only stored once in the file, but that’s another topic for another page.

Each of the computer parts that kOS supports have their own different default storage capacity limits for their local volume. As you get better parts higher up the tech tree, they come with bigger default size limits.

You can get more space by paying extra cost in money and mass

_images/disk_space_slider.png

If you wish to have more disk space on your local volume, and are willing to pay a little extra cost in money and in mass, you can use the disk space slider in the vehicle assembly building to increase the limit.

Every part comes with 3 different multiplier options:

  • 1x default size,
  • 2x default size,
  • 4x default size

The higher the multiplier the more mass it will cost you, to represent that you’re using old storage technology, so it costs a lot of mass to have more storage.

The disk size is only settable like this in the assembly building. Once you launch a vessel, its volume size is stuck the way it was when you launched it.

Multiple Volumes on One Vessel

Each kOS CX-4181 Scriptable Control System part contains ‘’‘one’‘’ such volume inside it.

If you have multiple CX-4181 parts on the same craft, they are assumed to be networked together on the same system, and capable of reading each other’s hard drives. Their disk drives each have a different Volume, and by default they are simply numbered 1,2,3, … unless you rename them.

For example, if you have two CX-4181’s on the same craft, called 1 and 2, with volumes on them called 1 and 2, respectively, it is possible for CPU 1 to run code stored on volume 2 by just having CPU number 1 issue the command ‘’SWITCH TO 2.’‘

Naming Volumes

It’s important to note that if you have multiple volumes on the same vessel, the numbering conventions for the volumes will differ on different CPUs. The same volume which was called ‘2’ when one CPU was looking at it might instead be called ‘1’ when a different CPU is looking at it. Each CPU thinks of its OWN volume as number ‘1’.

Therefore using the SET command on the volumes is useful when dealing with multiple CX-4181’s on the same vessel, so they all will refer to the volumes using the same names:

SET VOLUME("0"):NAME TO "newname".

If a kOS processor has a name tag set, then that processor’s volume will have its name initially set to the value of the name tag.

Archive

The “archive” is a special volume that behaves much like any other volume but with the following exceptions:

  • It is globally the same even across save games.
  • The archive represents the large bank of disk storage back at mission control’s mainframe, rather than the storage on an indivdual craft. While “Volume 1” on one vessel might be a different disk than “Volume 1” on another vessel, there is only one volume called “archive” in the entire solar system. Also, there’s only one “archive” across all saved universes. If you play a new campaign from scratch, your archive in that new game will still have all the files in it from your previous saved game. This is because behid the scenes it’s stored in the Ships/Script directory, not the save game directory.
  • It is infinitely large.
  • Unlike the other volumes, the archive volume does not have a byte limit. This is because the mainframe back at home base can store a lot more than the special drives sent on the vessels - so much so that it may as well be infinite by comparison.
  • Files saved there do not revert when you “revert flight”.
  • When you revert a flight, you are going back to a previous saved state of the game, and therefore any disk data on the vessels themselves also reverts to what it was at the time of the saved game. Because the archive is saved outside the normal game save, changes made there are retained even when reverting a flight.
  • Files in Archive are editable with a text editor directly and they will have the .ks extension.
  • Files in the Archive are stored on your computer in the subdirectory: Ships/Script. You can pull them up in a text editor of your choice and edit them directly, and the KOS Mod will see those changes in its archive immediately. Files stored in other volumes, on the other hand, are stored inside the vessel’s data in the persistence file of the saved game and are quite a bit harder to edit there. Editing the files in the Archive directory is allowed and in fact is an officially accepted way to use the plugin. Editing the section in a persistence file, on the other hand, is a bad idea and probably constitutes a form of cheating similar to any other edit of the persistence file.

Special handling of files in the “boot” directory

For users requiring even more automation, the feature of custom boot scripts was introduced. If you have at least 1 file in the boot directory on your Archive volume, you will be presented with the option to choose one of those files as a boot script for your kOS CPU.

http://i.imgur.com/05kp7Sy.jpg

The first time that you load kOS without a directory named boot in the archive root, kOS will prompt you for automatic migration.

Warning

New in version v1.0.0: Older versions of kOS used file names starting with the word “boot” to determine which files should be considered as boot files. When support was added for directories, it made sense to instead use a directory named boot. Care was taken to maximize backwards compatibility: if an existing craft file is opened in the editor, kOS will first look for the saved boot file name in the boot directory, then it will check the archive root, and finally it will check the boot directory again after stripping boot or boot_ from the beginning of the name. Vessels in flight will continue to work with the existing structure, so long as CONFIG:ARCH is set to false. If CONFIG:ARCH is set to true, you will need to leave copies of the originally named boot files in your archive root for ships already in flight to access.

As soon as you vessel leaves VAB/SPH and is being initialised on the launchpad (e.g. its status is PRELAUNCH) the assigned script will be copied to CPU’s local hard disk with the same name. If kOS is configured to start on the archive, the file will not be copied locally automatically. This script will be run as soon as CPU boots, e.g. as soon as you bring your CPU in physics range or power on your CPU if it was turned off. You may get or set the name of the boot file using the kOSProcessor:BOOTFILENAME suffix.

Important things to consider:

  • kOS CPU hard disk space is limited, avoid using complex boot scripts or increase disk space using MM config.
  • Boot script runs immediately on initialisation, it should avoid interaction with parts/modules until physics fully load. It is best to wait for couple seconds or until certain trigger.

Possible uses for boot scripts:

  • Automatically activate sleeper/background scripts which will run on CPU until triggered by certain condition.
  • Create basic station-keeping scripts - you will only have to focus your probes once in a while and let the boot script do the orbit adjustment automatically.
  • Create multi-CPU vessels with certain cores dedicated to specific tasks, triggered by user input or external events (Robotic-heavy Vessels)
  • Anything else you can come up with

KerboScript Machine Code

Compiling to a KSM File

When you run your Kerboscript programs, behind the scenes they get compiled into a form in memory that runs much smoother but at the same time is quite hard for a Kerbal to read and understand. The actual computer hardware built by your friends at Compotronix incorporated actually run the program using these tiny instructions called “opcodes”. In the early days of room-sized computers before we were able to get them down to the compact size of just a meter or so across, all programmers had to use this difficult system, referred to as “machine language”. Ah, those were heady days, but they were hard.

The commands you actually write when you say something like SET X TO 1.0. are really a euphemism for these “machine language” opcodes under the surface.

When you try to run your script, the first thing that kOS does is transform your script into this ancient and arcane “machine language” form, storing it in its memory, and from then on it runs using that.

This process of transforming your script into Machine Language, or “ML” is called “Compiling”.

The RUN and RUNPATH commands do this silently, without telling you. The COMPILE command explicitly compiles the file and saves it for future use.

Note

Compiling scripts takes time, and while the compiler is working it pauses execution much like the wait command. As such compiling from mainline code will pause mainline code but allow triggers to continue to execute. Compiling from within a trigger will pause both the mainline code and all trigger code. Also be aware that the universe will continue to move during the compilation, so you should not assume that any values for mass, position, velocity, or similar physical properties will remain constant through compilation.

Changed in version 1.1.0: The universe continues to update during compilation. Previous versions would freeze the universe while scripts were compiled, effectively making them instantaneous in the universe.

The Compile Keyword
COMPILE sourcePath [TO destinationPath]
Parameters:
  • sourcePath – Path information pointing to the source script file
  • destinationPath – Path information pointing to the destination compiled machine language file
Parametertype sourcePath:
 

String or bare word string

Parametertype destinationPath:
 

Optional String or bare word string

This command will compile the source script file, transforming it from raw text into machine language opcodes. These opcodes are then encoded, compressed, and saved in the destination file.

Why Do I Care?

Warning

This is an experimental feature.

The reason it matters is this: Although once it’s loaded into memory and running, these opcodes actually have a lot of baggage and take up a lot of space, when they’re stored passively on the disk not doing anything, they can be smaller than your script programs are. For one thing, they don’t care about your comments (only other Kerbals reading your script do), and they don’t care about your indenting (only other Kerbals reading your script do).

So, given that the compiled “ML” codes are the only thing your program really needs to be run, why not just store THAT instead of storing the entire script, and then you can put the ML files on your remote probes instead of putting the larger script files on them.

And THAT is the purpose of the COMPILE command.

It does some, but not all, of the compiling work that the RUN (or RUNPATH) command does, and then stores the results in a file that you can run instead of running the original script.

The output of the COMPILE command is a file in what we call KSM format.

KSM stands for “KerboScript Machine code”, and it has nearly the same information the program will have when it’s loaded and running, minus a few extra steps about relocating it in memory.

How to Use KSM Files

Let’s say that you have 3 programs your probe needs, called:

  • myprog1.ks
  • myprog2.ks
  • myprog3.ks

And that myprog1 calls myprog2 and myprog3, and you normally would call the progam this way:

SWITCH TO 1.
COPYPATH( "0:/myprog1", "" ).
COPYPATH( "0:/myprog2", "" ).
COPYPATH( "0:/myprog3", "" ).
RUNPATH("myprog1", 1, 2, "hello").

Then you can put just the compiled KSM versions of them on your vessel and run it this way:

SWITCH TO ARCHIVE.

COMPILE "myprog1.ks" to "myprog1.ksm".
COPYPATH( "0:/myprog1.ksm", "1:/" ).

COMPILE "myprog2". // If you leave the arguments off, it assumes you are going from .ks to .ksm
COPYPATH( "0:/myprog2.ksm", "1:/" ).

COMPILE "myprog3". // If you leave the arguments off, it assumes you are going from .ks to .ksm
COPYPATH( "0:/myprog2.ksm", "1:/" ).

SWITCH TO 1.
RUNPATH("myprog1", 1, 2, "hello").

Default File Naming Conventions

When you have both a .ks and a .ksm file, the RUN (or RUNPATH) command allows you to specify which one you meant explicitly, like so:

RUNPATH("myprog1.ks").
// or this alternate way to say it:
RUN myprog1.ks.

RUNPATH("myprog1.ksm").
// or this alternate way to say it:
RUM myprog1.ksm.

But if you just leave the file extension off, and do this:

RUNPATH("myprog1").
// or this alternate way to say it:
RUN myprog1.

Then the RUN command will first try to run a file called “myprog1.ksm” and if it cannot find such a file, then it will try to run one called “myprog1.ks”.

In this way, if you decide to take the plunge and attempt the use of KSM files, you shouldn’t have to change the way any of your scripts call each other, provided you just used versions of the filenames without mentioning the file extensions.

Downsides to Using KSM Files

  1. Be aware that if you use this feature, you do lose the ability to have the line of code printed out for you when the kOS computer finds an error in your program. It will still tell you what line number the error happened on, but it cannot show you the line of code. Just the number.
  2. Know that you cannot view the program inside the in-game editor anymore when you do this. A KSM file will not appear right in the editor. It requires a magic tool called a “hex editor” to properly see what’s happening inside the file.
  3. The file isn’t always smaller. There’s a threshold at which the KSM file is actually bigger than the source KS file. For large KS files, the KSM file will be smaller, but for short KS files, the KSM file will be bigger, because there’s a small amount of overhead they have to store that is only efficient if the data was large enough.

More Reading and Fiddling with Your Bits

So, if you are intrigued by all this and want to see how it all REALLY works under the hood, Computronix has deciced to make internal document MLfile-zx1/a on the basic plan of the ML file system open for public viewing, if you are one of those rare Kerbals that enjoys fiddling with your bits. No, not THOSE kind of bits, the computery kind!

Using the Sound/Kerbal Interface Device (SKID)

A hidden feature shippped with all kOS CPU’s was uncovered recently. It turns out all kOS CPU’s come with a so-called SKID chip (Sound/Kerbal Interface Device). This little integrated chip can send audio data through the terminal lines and play the sounds on your terminal’s speaker. (Note, you can’t hear the sound over telnet connections.)

This page will explain what we have discovered about how to use this interesting little device.

Quick and dirty sound example:

If you just want to hurry up and hear something, without understanding all the features of the SKID chip, type the lines below into a program file and run it:

SET V0 TO GETVOICE(0). // Gets a reference to the zero-th voice in the chip.
V0:PLAY( NOTE(400, 2.5) ).  // Starts a note at 400 Hz for 2.5 seconds.
                            // The note will play while the program continues.
PRINT "The note is still playing".
PRINT "when this prints out.".

If you wish to do something more complex than that, then read on:

Hardware Capabilities of SKID

Below we describe, to the best of our knowledge, the sound capabilities of the SKID chip. Further below after this section, we’ll describe the programming interface we’ve added to Kerboscript to let you access these abilities. For now, don’t think about how to access these abilities, just look at what they are.

Voices

The SKID chip contains up to 10 simultaneous “voices” that can emit sounds together. (The voices are numbered 0 through 9). Each of these so-called “voices” can be set up with a particular set of sound settings, which is like defining the properties of its “instrument” . Then it can be told to play one or more notes using that set of settings.

Please don’t be fooled into thinking these actually sound like Kerbals speaking just because we called them “voices”. They’re just electronic bloopity-blopity sounds.

Frequency Range

The SKID chip appears to be capable of producing sounds in the range of as low as 1 Hertz (not like you’d ever hear that) and as high as up to 14000 Hertz. Most people can hear higher notes than that, but the chip’s notes start to get a little distorted at those frequencies. At any rate, this range is plenty high enough to hear the entire range of a piano’s 88 notes, and then some.

Amplitude (volume) Control

The SKID chip can put out any range of volume levels between 0.0 and 1.0, within the accuracy of floating point numbers. It is noted that it can also amplify the volume to levels higher than 1.0, to boost the signal when needed, although this feature is not well understood.

Waveforms

Each voice can be set to one of several possible sound waveform patterns. As of this writing, our engineers have uncovered 5 such wave forms, displayed here.

These each produce a slightly different sound.

Image of a square sound wave.

"SQUARE": When this :WAVE setting is used, the sound is very electronic, and “beep”-like. This is also the default wave a voice starts off with if you never changed the setting.

Image of a triangle sound wave.

"TRIANGLE": When this :WAVE setting is used, the sound is sort of halfway between sounding electronic and sounding mellow.

Image of a sawtooth sound wave.

"SAWTOOTH": When this :WAVE setting is used, the sound is a little bit like a rasping wasp.

Image of a sine sound wave.

"SINE": When this :WAVE setting is used, the sound is a mellow and smooth tone, which also ends up seeming a bit quieter because of the smoother edges.

Image of a noise sound wave.

"NOISE": When this :WAVE setting is used, the sound is like random static on a walkie-talkie.

ADSR “Envelope”

The SKID chip will play a note in the background unattended while the CPU continues with its work. Thus you don’t have to have the main program pause while it plays a note.

When controlling the note, the SKID chip also can vary the amplitude (volume) of the note over time, to simulate the effects of several types of analog instruments. It does this using its “ADSR Envelope”, (ADSR is shorthand for “Attack, Decay, Sustain, and Release”.)

Image of the ADSR Envelope

The ADSR envelope shown graphically.

When a natural analog instrument plays a note, there is often a sharp “spike” of volume right at the start of the note, followed by a slightly quieter volume while the note is being sustained, followed by the volume fading down to zero when the note is released. The ADSR envelope lets you define this behavior by adjusting these settings differently for each of the 10 voices of the SKID chip.

Attack (a time setting)
The Attack setting is a time, expressed in seconds (usually a fraction of a second), for how long a note takes to achieve its full volume in its initial first spike when it is played. Note that the volume achieved at the top of this spike is the voice’s default volume level. This time setting is not modified by tempo, because it represents the instrument’s physical properties that don’t change when the song goes faster.
Decay (a time setting)
The Decay setting is a time, expressed in seconds (usually a fraction of a second), for how long a note takes to drop from its initial spike in volume down to its sustaining volume level. This time setting is not modified by tempo, because it represents the instrument’s physical properties that don’t change when the song goes faster.
Sustain (a volume setting)
The Sustain setting is not a time, but a volume multiplier ( usually some amount less than 1.0 but higher than 0.0). It is the lesser volume level that the note drops to after the spike of volume defined by the Attack and Decay settings is over. The note will stay held here at this level until it is released. The reason this value isn’t a time setting is because the duration of this period of the note varies depending on the note being played.
Release (a time setting)
The Release setting is a time, expressed in seconds (usually a fraction of a second), for how long a note takes to fade from its Sustain volume level back down to zero again at the end of the note when the sustained duration of the note is over. This time setting is not modified by tempo, because it represents the instrument’s physical properties that don’t change when the song goes faster.

Default settings for the ADSR envelopes for all voices in the SKID chip are:

  • Attack = 0.0s
  • Decay = 0.0s
  • Sustain = 1.0
  • Release = 0.1s

This produces a sound that will suddenly start but ever so slightly fade at the end rather than dropping off immediately. There is just enough of a Release time to make the listener hear the sound as slightly less harsh than a fast cutoff would sound.

It is possible to make the chip describe different shaped envelopes by using degenerate values for some of these settings. For some examples:

Below are settings you might use for a “staccato” type of instrument, such as a drum, that is incapable of holding a sustained note and instead just fades right away whenever you hit a note:

  • Attack = 0.0s
  • Decay = 0.2s (Note decays immediatly on being struck)
  • Sustain = 0.0 (and it decays to zero, so you can’t “hold” the note).
  • Release = 0.0s (Because Sustain is zero, this setting doesn’t matter).

Below are settings you might use for an instrument with a strong “wow wow” effect, where it takes time to reach full volume and you have to hold a note in for a half second or so before you really hear it:

  • Attack = 0.5s (takes a whole half second to reach full volume)
  • Decay = 0s (stays at full volume once it’s there)
  • Sustain = 1.0 (stays at full volume once it’s there)
  • Release = 0.2 (and fades fairly fast when released, but not immediately).
Letter Frequency Notation

The SKID chip contains an interior lookup system that makes it possible, anywhere the chip expects you to give a frequency in Hertz, to instead specify its frequency using the more familiar “letter notes”.

To do this, you use a string in the following format:

  1. Mandatory: First character is the note letter, one of “C”,”D”,”E”,”F”,”G”,”A”, “B”, or “R”(to mean “rest”).
  2. Optional: Followed by an optional character, “#” or “b” for “sharp” or “flat”. Note the ASCII characters hash (“#”) and lower-case “B” (‘b’) are used for “sharp” and “flat” in place of the Unicode characters U+266F and U+266D (which are the more proper “sharp” and “flat” characters, but they are cumbersome to type on most keyboards.)
  3. Mandatory: The last character is a digit indicating which octave number (0 through 7) this note is in. (4 is the “middle” octave that starts with “middle C” on a piano keyboard.)

Examples: "C4" is middle C. "C#4" is the C-sharp just one half step above middle C. "Db4" is the D-flat that is in fact the same thing as "C#4". "B3" is the B that is just to the left of middle C (Note the octave numbering starts with C at the bottom of the octave, not A, thus why B3 is adjacent to C4.)

Note that in all cases where you communicate a frequency to the SKID chip using one of these “letter notes”, the SKID chip merely converts these values into their equivalent Hertz value, and forgets the letter string you used after that. Thus if you set a Note’s frequency to “A4”, and then immediately query its Frequency, you’ll get 440 back, not “A4”.

Note that if you form a string indicating an unknown note in this notation scheme (for example "E#5", when there is no such thing as an E-sharp), the resulting frequency will just be zero Hertz, the same as for a rest note.

The Note format

When telling the SKID chip to begin a note, the following data is passed into it, to let it know the parameters of that one note:

Frequency (In Hertz)
Defines which note you mean, i.e. which of the keys on a synthesizer keyboard. Be aware that if you set the frequency to zero, you end up with a note that is, essentially, a “rest”, and makes no sound. The frequency can also be expressed using musical letter notation. If you define both a Frequency and an EndFrequency, then the Frequency is merely the initial frequency the note starts at.
EndFrequency (In Hertz)
The SKID chip can emit “slide” notes where the note’s frequency changes smoothly between a start and end frequency over the duration of the note without further intervention from the CPU’s program. To communicate to the chip that this is your intent, you can give it a note which has an EndFrequency value that differs from its Frequency value. When you do this, the note’s normal frequency value is merely its “start” frequency. The SKID chip will change the note’s frequency either upward or downward as needed depending on whether the EndFrequency is higher or lower than the initial frequency of the note. It will also change the frequency as quickly or slowly as needed to make the frequency reach EndFrequency at exactly the moment the note’s Duration runs out. (So giving it a shorter Duration makes it change frequencies faster).
Duration (Seconds, modified by tempo)
Defines how long this entire note lasts from the start of its Attack until the end when the next note can start. Note that by default, unless you choose to set the KeyDownLength (see next item below) to something other than the default, the note won’t quite fill the entire Duration, instead reserving a small sliver of the end of the Duration to represent the gap between this note and the next.
KeyDownLength (Seconds, modified by tempo)

Defines how long you imagine the “key” on a synthesizer keyboard is being held down for to produce this note. In terms of the ADSR Envelope, this is the time span that includes the Attack, Decay, and Sustain portion of the note, but not the Release portion of the note. The KeyDownLength must be less than or equal to the Duration (see above). If you try to set a KeyDownLength that exceeds the Duration, it will be shortened to match the Duration. Essentially the Difference between Duration and KeyDownLength is that Duration is how much time the note fills up of the song, and KeyDownLength is how much of that time is spent with the finger holding the “synthesizer key” down. The time between the end of the KeyDownLength and the end of the Duration is the gap of time from when one key is let go and the next key is begun. If there is no such gap, then two adjacent notes of the same frequency would just bleed together into sounding like one continuous note.

By default, the KeyDownLength is slightly shorter than the Duration if you don’t specify it explicitly.

The Release portion of the ADSR Envelope occurs entirely within the gap between the end of KeyDownLength and Duration. If you define the KeyDownLength to last the entire Duration, then you won’t hear the Release portion of the note’s envelope, because the note will cut off before it has a chance to start the release.

Volume (between 0.0 and 1.0, although it can go higher than 1.0)
A multiplier for the volume of this one note relative to the overall volume of the voice on which it is playing. By setting it differently per note, it’s possible to play a song in which some notes are quieter than others. This can go above 1.0 if the main volume is less than 1.0. But it is probably a good idea to make sure that this volume times the voice’s main volume doesn’t exceed 1.0 or you might get some “audio compression” effects that slightly distort the sound.
Tempo

The SKID chip allows you to set a tempo multiplier (a coefficient multiplier that can be a fractional number like 0.5, 2.0, 1.1, etc). This tempo multiplier causes all sound durations mentioned in specific notes to be sped up or slowed down by multiplying them by this number. It can be thought of as “how long is one second’s worth of sheet music going to last when played on this chip?” If you set it to 0.5, then each second’s worth of time in a Note’s Duration or KeyDownLength fields only lasts half a second for real, thus causing the notes to play twice as fast. Conversely, if you set this to 2.0, it makes notes take twice as long to play, thus slowing down the tempo.

It should be possible to transcribe sheet music into the note format the SKID chip uses, by simply using a note Duration of 0.25 for “quarter note”, 0.5 for “half note”, 1.0 for “whole note”, and so on, and then setting the chip’s Tempo to set how long you mean for a whole note to last.

Songs (Lists of Notes)

It is possible to point the SKID chip at an array of these notes to make it play several notes back to back without further intervention of the main CPU. When the chip is given such a list of notes, it is a bit like feeding “sheet music” to the chip, and letting it play the song itself.

This is where the settings such as tempo become quite relevant. The SKID chip will simply play the notes it sees in the order they’re listed, waiting for one note to finish its Duration before the next note is started. By changing the Tempo setting, you can speed up the song that is in the current note list without changing the definitions of the individual notes in the song.

Note that any note with a frequency set to zero counts as a “rest”, which is useful to know when encoding a song into a list of notes.

It is unknown if there is a limit to how long a list of notes can be. In our testing, the engineers haven’t discovered an upper limit yet.

Looping

The SKID chip contains a flag that can be used to set whether or not it should start again with the first note in a list when it reaches the last note in the list. If this flag is true, then it will continue playing the song list forever and ever until made to stop.

Note that even a single note counts as a “song” for the purpose of this looping flag. Yes, the SKID chip can play the same note over and over if that’s what you really want.

Chords

Each of the 10 voices of a SKID chip can only play a single note at a time. But you may wish to transcribe some music into a song (list of notes) in which the notes of the sheet music contained chords - that is to say, cases where more than one note is supposed to be played simultaneously. The SKID chip can support this, but the way to do it is a little bit messy.

In order to play something that has chords, you need to imagine that each of the SKID chip’s voices can be a different finger of a synthesizer keyboard player. Let’s say you want to play a song that has some 3-note chords in it. At minimum, a keyboard player would need 3 fingers to accomplish this. To do this with SKID, you’d need to dedicate 3 of the voices to the job, and make sure all 3 voices are given the same settings (Waveform, ADSR Envelope, Tempo, Volume, etc). With one of these voices, you play all the time, all the notes. (You give it a song that consists of all the notes in the sheet music). With the other two voices, you’d give them songs (note lists) that contain rests in all the places where there is only a singleton note being played, but then have them join in with with the extra notes to add to the main voice’s note in the places where a chord should be played.

Kerboscript Interface Synopsis

In order that you don’t need to send individual bits and bytes to the SKID chip, we’ve added a user interface in the Kerboscript language that interfaces with all these features for you. Keep the above technical specs in mind so you know what the settings you’re changing do.

The documentation below is just a quick synopsis of how to use the kerboscript interface to SKID. To really fully exploit it, you need to follow the links below and read the detailed documentation on the Note structure and the Voice structure.

GetVoice()

The basic starting point of any Kerboscript program that works with the SKID chip is the GetVoice() built-in function. GetVoice(n), given any N within the range of 0 through 9 for the 10 voices in the SKID chip, returns a handle you can use to send commands to that voice of the SKID chip.

For simple easy examples, you can just use voice 0 for most of your needs:

SET V0 to GetVoice(0).

GetVoice() returns an object of type Voice, that can be used to do everything else you need after that.

Voice object

If you take a moment to look at the documentation for Voice, you can see that almost everything it lets you do has a one-to-one correspondence to the hardware features mentioned above in this document.

All the features of the SKID chip can be set this way, and have a suffix that corresponds to them, as given in the example below:

SET V0:VOLUME TO 0.9.
SET V0:WAVE to "sawtooth".
SET V0:ATTACK to 0.1.
SET V0:DECAY to 0.2.
SET V0:SUSTAIN to 0.7. // 70% volume while sustaining.
SET V0:RELEASE to 0.5 // takes half a second to fade out.
Note()

When asking one of SKID’s voices to play a note, you have to specify which note you meant, and you do so by constructing a Note object using the Note() built-in function, or the SlideNote() built-in function:

// N1 is a note (also at 440 Hz because that's what "A4" means)
// that lasts 1 second overall, but only 0.8 seconds of it
// are "key down" time (i.e. the A,D,S part of the ADSR Envelope).
SET N1 to NOTE("A4", 0.8, 1).

// N2 is a note that slides from the A note in one octave to the A note
// in the next octave up, over a time span of 0.3 seconds.
// (The last 0.05 seconds of which are "release" time you won't hear
// if you have the voice's RELEASE value set to zero.):
SET N2 to SLIDENOTE("A4", "A5", 0.25, 0.3).

Once a note has been constructed, it’s components are not changable. The only way to change the note is to make a new note and use it to overwrite the previous note.

For that reason, it’s typical not to bother storing the result of a Note() or SlideNote() constructor in a variable as shown above, and instead just pass it right into the Play() method, or to make it part of a List of notes for making a song.

Voice:Play()

The heart of the Kerboscript interface to the SKID chip is the Play() suffix method of the Voice object.

You either construct a single Note and tell Play() to play it, or you construct a List of Note‘s and tell Play() to play them.

Examples:

SET V0 TO GetVoice(0).
V0:PLAY( NOTE( 440, 1) ).  // Play one note at 440 Hz for 1 second.

// Play a 'song' consisting of note, note, rest, sliding note, rest:
V0:PLAY(
    LIST(
        NOTE("A#4", 0.2,  0.25), // quarter note, of which the last 0.05s is 'release'.
        NOTE("A4",  0.2,  0.25), // quarter note, of which the last 0.05s is 'release'.
        NOTE("R",   0.2,  0.25), // rest
        SLIDENOTE("C5", "F5", 0.45, 0.5), // half note that slides from C5 to F5 as it goes.
        NOTE("R",   0.2,  0.25)  // rest.
    )
).

The Name Tag System

_images/nametag.png

One useful thing to do is to be able to give parts on a craft your own chosen names that you can use to get a reference to the parts, utterly bypassing the naming schemes used by KSP, and ignoring the complex part tree system. The Name Tag system was designed to make it possible for you to just give any part on a craft whatever arbitrary name you feel like.

Giving a Part a Name

  1. Right click on any part. **You can do this in either the editor VAB/SPH, or during flight.** It is more useful to do beforehand when making the craft, but you can do it in the midst of a flight, which is useful for experimenting and testing.
  2. Click the “change name tag” button. A small text pop-up will appear with the ability to type a name for the part. Type any value you like. In the example picture here, the value “the big tank” was typed in.
  3. From now on this part can be retrieved using either the :PARTSDUBBED or the :PARTSTAGGED methods of the vessel, as described on the Vessel Page

Cloning with Symmetry

If you name a part in the editor first, and then remove it from the vessel and place it again with symmetry, all the symmetry copies of the part will get the same nametag cloned to them. This is not an error. It is allowed to give the same nametag to more than one part. If you do this, it just means that the :PARTSTAGGED or :PARTSDUBBED methods will return lists containing more than one part.

If you want each part in a symmetrical group to get a unique nametag, you can change their names one at a time after you’ve placed the parts.

Where is it saved?

If you add a nametag to a part in the editor (VAB or SPH), then that nametag will get saved inside the craft file for that vessel design. All new instances of that craft design that you launch will get the same nametag configuration on them.

On the other hand, if you added a nametag to a part after the vessel was launched, during flight or on the launchpad, then that nametag will only be attached to that part on that one instance of the vessel. Other copies of the same design won’t have the name. In this case the name is saved inside the saved game’s persistence file, but not in the editor’s craft design file.

Examples of Using the Name

// Only if you expected to get
// exactly 1 such part, no more, no less.
SET myPart TO SHIP:PARTSDUBBED("my nametag here")[0].

// OR

// Only if you expected to get
// exactly 1 such part, no more, no less.
SET myPart TO SHIP:PARTSTAGGED("my nametag here")[0].

// Handling the case of more than
// one part with the same nametag,
// or the case of zero parts with
// the name:
SET allSuchParts TO SHIP:PARTSDUBBED("my nametag here").

// OR

SET allSuchParts TO SHIP:PARTSTAGGED("my nametag here").

// Followed by using the list returned:
FOR onePart IN allSuchParts {
  // do something with onePart here.
}

For more details on how this system works see the Parts and PartModules Section.

Ship Parts and PartModules

As of v0.15, kOS has introduced the ability to access the functionality of Parts’ actions, and right-click context menus. However, to understand how to use them it is necessary to first understand a little bit about the structure of parts in a vessel in Kerbal Space Program.

In Kerbal Space Program, a Vessel is a collection of Parts arranged in a tree Structure. In kOS, you can access the parts one of two ways:

Tutorial

If you prefer the tutorial approach to learning, the following links will walk you through a specific pair of tasks to introduce you to this system. If you prefer a methodical reference approach, skip the tutorials and read on, then come back to the tutorials afterward.

TODO: PUT LINKS POINTING TO A TUTORIAL WALKING THROUGH WHAT THIS IS TEACHING.

THERE NEEDS TO BE TWO TUTORIALS MINIMUM:

  1. A Tuturial made on an ONLY STOCK (other than kOS of course) install, using only STOCK parts to do something interesting.
  2. A Tutorial more complex showing that this can be used with mods too, probably the Leg Leveller example from the teaser video, but with the code updated to use the newer part query methods.

Parts

Note

The short and quick thing to remember

If you only remember one technique, it should be using the Part:PARTSDUBBED method described down below. It’s the most useful one that covers all the other cases.

Accessing the parts by various naming systems

Any time you have a vessel variable, you can use these suffixes to get lists of parts on it by their names using several different naming schemes:

Part Tag: A part’s tag is whatever custom name you have given it using the nametag system described here. This is probably the best naming convention to use because it lets you make up whatever name you like for the part and use it to pick the parts you want to deal with in your script.

Part Title: A part’s title is the name it has inside the GUI interface on the screen that you see as the user.

Part Name: A part’s name is the name it is given behind the scenes in KSP. It never appears in the normal GUI for the user to see, but it is used in places like Part.cfg files, the saved game persistence file, the ModuleManager mod, and so on.

Assuming you’ve done one of these:

SET somevessel to SHIP.
//    Or this:
SET somevessel to VESSEL("some vessel's name").
//    Or this:
SET somevessel to TARGET. // assuming TARGET is a vessel and not a body or docking port.

Then you can do one of these to query based on any of the above schemes:

// --------- :PARTSTAGGED -----------
// Finds all parts that have a nametag (Part:Tag suffix) matching the value given:
SET partlist to somevessel:PARTSTAGGED(nametag_of_part)

// --------- :PARTSTITLED -----------
// Finds all parts that have a title (Part:Title suffix) matching the value given:
SET partlist to somevessel:PARTSTITLED(title_of_part)

// --------- :PARTSNAMED -----------
// Finds all parts that have a name (Part:Name suffix) matching the value given:
SET partlist to somevessel:PARTSNAMED(name_of_part)

// --------- :PARTSDUBBED -----------
// Finds all parts matching the string in any naming scheme, without caring what kind of naming scheme it is
// This is essentially the combination of all the above three searches.
SET partlist to somevessel:PARTSDUBBED(any_of_the_above)

In all cases the checks are performed case-insensitively.

These are different styles of naming parts, all slightly different, and you can use any of them you like to get access to the part or parts you’re interested in.

They all return a List of Parts rather than just one single part. This is because any name could have more than one hit. If you expect to get just one single hit, you can just look at the zero-th value of the list, like so:

SET onePart TO somevessel:PARTSDUBBED("my favorite engine")[0].

If the name does not exist, you can tell by seeing if the list returned has a length of zero:

IF somevessel:PARTSDUBBED("my favorite engine"):LENGTH = 0 {
  PRINT "There is no part named 'my favorite engine'.".
}.

Examples:

// Change the altitude at which all the drouge chutes will deploy:
FOR somechute IN somevessel:PARTSNAMED("parachuteDrogue") {
  somechute:GETMODULE("ModuleParachute"):SETFIELD("DEPLOYALTITUDE", 1500).
}.
Pattern matching

For even more advanced part selection you can use the *PATTERN variants of the suffixes mentioned above (see Vessel:PARTSTAGGEDPATTERN, Vessel:PARTSTITLEDPATTERN, Vessel:PARTSNAMEDPATTERN and Vessel:PARTSDUBBEDPATTERN). They enable you to select parts based on regular expression matching, giving you ultimate power over your parts.

_images/ship_parts_tree.png
Accessing the parts list as a tree

Starting from the root part, Vessel:ROOTPART (SHIP:ROOTPART, TARGET:ROOTPART, or Vessel(“some ship name”):ROOTPART). You can get all its children parts with the Part:CHILDREN suffix. Given any Part, you can access its Parent part with Part:PARENT, and detect if it doesn’t have a parent with Part:HASPARENT. By walking this tree you can see how the parts are connected together.

The diagram here shows an example of a small vessel and how it might get represented as a tree of parts in KSP.

Accessing the parts list as a list

You can get a list of all the parts on a vessel using the suffix :PARTS, or by using the LIST PARTS IN command. When you do this, the resulting list is a “flattening” of the tree of parts, created by use of a depth-first search starting from the root part. In the diagram shown here, the red numbers indicate one possible way the parts might be represented in LIST indeces if you used SHIP:PARTS on such a vessel. Note there is no guarantee it would look exactly like this, as it depends on exactly what order the parts were attached in the VAB.

Shortcuts to smaller lists of parts

If you know some of the properties of the parts you’re interested in, you can ask kOS to give you a shorter list of parts that just includes those parts, using the following suffixes:

Return a List of just the parts who’s name is “someNameHere”:

SET ves TO SHIP. // or Target or Vessel("ship name").
SET PLIST TO ves:PARTSNAMED("someNameHere").

Return a List of just the parts that have had some sort of activity attached to action group 1:

SET ves TO SHIP. // or Target or Vessel("ship name").
SET PLIST TO ves:PARTSINGROUP(AG1).

PartModules and the right-click menu:

Each Part, in turn has a list of what are called PartModules on it. A PartModule is a collection of variables and executable program hooks that gives the part some of its behaviors and properties. Without a PartModule, a part is really nothing more than a passive bit of structure that has nothing more than a shape, a look, and a strength to it. Some of the parts in the “structure” tab of the parts bin, like pure I-beams and girders, are like this - they have no PartModules on them. But all of the interesting parts you might want to do something with will have a PartModule on them. Through PartModules, **kOS will now allow you to manipulate or query anything that any KSP programmer, stock or mod, has added to the rightclick menu**, or action group actions, for a part.

PartModules, Stock vs Mods:

It should be noted that even if you play an entirely stock installation of KSP (well, stock other than for kOS, obviously, otherwise you wouldn’t be reading this), you will still have PartModules on your Parts. Some people have muddied the terminology difference between “Mod” meaning “modification” and “Mod” meaning “module”. It should be made absolutely clear that PartModules are a feature of stock KSP, and BOTH stock KSP parts and Modded KSP Parts use them. Even if all you want to do is affect the stock behavior of stock parts in a completely unmodded way, you’ll still want to know about PartModules in order to do so.

PartModules and ModuleManager-like behavior:

Some Mods (meaning “modifications” here) operate by adding a new PartModule to every single part in the game. One example of such a mod is the Deadly Reentry mod. In order to track how fragile each part is and how well it withstands re-entry heat, the Deadly Re-entry mod adds a small module to each part in the game, even the stock parts that would normally have no mods at all on them.

Other Mods allow the user to add PartModule’s to any part they feel like, through the use of the ModuleManager mod.

Because of these, it’s impossible in this explanatory document to make blanket statements about which PartModules will exist on which Parts. Everything that is said here needs to be taken with a grain of salt, as depending on the mods you’ve installed on your game, you may find PartModules on your parts that are not normally on those parts for most other players.

What a PartModule means to a kOS script

There are 3 ways that a kOS script may interface with a PartModule.

TODO - TAKE SOME SCREENSHOTS TO PUT ALONGSIDE THIS TEXT, SHOWING EXAMPLES OF THESE THINGS IN THE USER INTERFACE. WE NEED A SCREENSHOT THAT SHOWS BOTH A KSPFIELD AND A KSPEVENT IN A PART’S RMB CONTEXT MENU, A SCREENSHOT THAT SHOWS FIELDS COMING FROM MULTIPLE PARTMODULES, AND A SCREENSHOT SHOWING THE KSPACTIONS IN THE VAB ACTION EDITOR.

KSPFields

A KSPField is a single variable that a PartModule attaches to a part. Some of the KSPFields are also displayed in the RMB context menu of a part. It has a current value, and if the field has had a “tweakable” GUI interface attached to it, then it’s also a settable field by the user manipulating the field in the context menu. In kOS, you can only access those KSPFields that are currently visible on the RMB context menu. We, the developers of kOS, instituted this rule out of respect for the developers of other mods and the stock KSP game. If they didn’t allow the user to see or manipulate the variable directly in the GUI, then we shouldn’t allow it to be manipulated or seen by a kOS script either.

KSPFields are read or manipulated by the following suffixes of PartModule

  • :GETFIELD(“name of field”).
  • :SETFIELD(“name of field”, new_value_for_field).

Note, that these are suffixes of the partmodule and NOT suffixes of the Part itself. This is because two different PartModule’s on the same Part might have used the same field name as each other, and it’s important to keep them separate.

KSPEvents

A KSPEvent, just like a KSPField, is a thing that a PartModule can put on the RMB context menu for a part. The difference is that a KSPEvent does not actually HAVE a value. It’s not a variable. Rather it’s just a button with a label next to it. When you press the button, it causes some sort of software inside the PartModule to run. An example of this is the “undock node” button you see on many of the docking ports.

Difference between a KSPEvent and a boolean KSPField: If you see a label next to a button in the RMB context menu, it might be a KSPEvent, OR it might be a boolean KSPField variable which is editable with a tweakable GUI. They look exactly the same in the user interface. To tell the difference, you need to look at what happens when you click the button. If clicking the button causes the button to depress inward and stay pressed in until you click it again, then this is a boolean value KSPField. If clicking the button pops the button in and then it pops out again right away, then this is a KSPEvent instead.

KSPEvents are manipulated by the following suffix of PartModule

  • :DOEVENT(“name of event”).

This causes the event to execute once.

KSPActions:

A KSPAction is a bit different from a KSPField or KSPEvent. A KSPAction is like a KSPEvent in that it causes some software inside the PartModule to be run. But it doesn’t work via the RMB context menu for the part. Instead KSPAction’s are those things you see being made avaiable to you as options you can assign into an Action Group in the VAB or SPH. When you have the action group editor tab enabled in the VAB or SPH, and then click on a part, that part asks all of its PartModules if they have any KSPActions they’d like to provide access to, and gathers all those answers and lists them in the user interface for you to select from and assign to the action group.

kOS now allows you to access any of those actions without necessarily having had to assign them to any action groups if you didn’t want to.

KSPActions are manipulated by the following suffix of PartModule

  • :DOACTION(“name of action”, new_boolan_value).

The name of the action is the name you see in the action group editor interface, and the new boolean value is either True or False. Unlike KSPEvents, a KSPAction has two states, true and false. When you toggle the brakes, for example, they go from on to off, or from off to on. When you call :DOACTION, you are specifying if the KSPAction should behave as if you have just toggled the group on, or just toggled the group off. But instead of actually toggling an action group - you are just telling the single PartModule on a single Part to perform the same behavior it would have performed had that action been assigned to an action group. You don’t actually have to assign the action to an action group for this to work.

Exploring what’s there to find Field/Event/Action Names:

Okay, so you understand all that, but you’re still thinking “but how do I KNOW what the names of part modules are, or what the names of the fields on them are? I didn’t write all that C# source code for all the modules.”

There are some additional suffixes that are designed to help you explore what’s available so you can learn the answers to these questions. Also, some of the questions can be answered by other means:

What PartModules are there on a part?

To answer this question you can do one of two things:

A: Use the part.cfg file All parts in KSP come with a part.cfg file defining them, both for modded parts and stock parts. If you look at this file, it will contain sections looking something like this:

// Example snippet from a Part.cfg file:
MODULE
{
    name = ModuleCommand

That would tell you that this part has a PartModule on it called ModuleCommand. there can be multiple such modules per part. But it doesn’t let you know about PartModules that get added afterward during runtime, by such things as the ModuleManager mod.

B: Use the :MODULES suffix of Part: If you have a handle on any part in kOS, you can print out the value of :MODULES and it will tell you the string names of all the modules on the part. For example:

FOR P IN SHIP:PARTS {
  LOG ("MODULES FOR PART NAMED " + P:NAME) TO MODLIST.
  LOG P:MODULES TO MODLIST.
}.

Do that, and the file MODLIST should now contain a verbose dump of all the module names of all the parts on your ship. You can get any of the modules now by using Part:GETMODULE(“module name”).

What are the names of the stuff that a PartModule can do?

These three suffixes tell you everything a part module can do:

SET MOD TO P:GETMODULE("some name here").
LOG ("These are all the things that I can currently USE GETFIELD AND SETFIELD ON IN " + MOD:NAME + ":") TO NAMELIST.
LOG MOD:ALLFIELDS TO NAMELIST.
LOG ("These are all the things that I can currently USE DOEVENT ON IN " +  MOD:NAME + ":") TO NAMELIST.
LOG MOD:ALLEVENTS TO NAMELIST.
LOG ("These are all the things that I can currently USE DOACTION ON IN " +  MOD:NAME + ":") TO NAMELIST.
LOG MOD:ALLACTIONS TO NAMELIST.

After that, the file NAMELIST would contain a dump of all the fields on this part module that you can use.

BE WARNED! Names are able to dynamically change!

Some PartModules are written to change the name of a field when something happens in the game. For example, you might find that after you’ve done this:

SomeModule:DOEVENT("Activate").

That this doesn’t work anymore after that, and the “Activate” event now causes an error.

And the reason is that the PartModule chose to change the label on the event. It changed to the word “Deactivate” now. kOS can no longer trigger an event called “Activate” because that’s no longer its name.

Be on the lookout for cases like this. Experiment with how the context menu is being manipulated and keep in mind that the list of strings you got the last time you exectued :ALLFIELDS a few minutes ago might not be the same list you’d get if you ran it now, because the PartModule has changed what is being shown on the menu.

Career Limits

The theme of KSP 0.90 is that some features of your space program don’t work until after you’ve made some upgrades. kOS now supports enforcing these checks, as described below.

Warning

The rules being enforced

These are rules inherited from the stock KSP game that kOS is simply adhering to:

  • If your tracking center is not upgraded far enough, then you cannot see future orbit patches.
  • If your mission control center AND tracking center are not upgraded enough, then you cannot add manuever nodes.

The following are rules invented by kOS that are thematically very similar to stock KSP, intended to be as close to the meaning of the stock game’s rules as possible:

  • In order to be allowed to execute the PartModule :DOACTION method, either your VAB or your SPH must be upgraded to the point where it will allow access to custom action groups. This is because otherwise the :DOACTION method would be a backdoor “cheat” past the restricted access to the action group feaures of various PartModules that the game is meant to have.
  • In order to be allowed to add a nametag to parts inside the editor so they get saved to the craft file, you must have upgraded your current editor building (VAB or SPH, depending) to the point where it allows at least stock action groups. This is because name tags are a sort of semi-advanced feature.

You can see some of these settings by reading the values of the Career() global object, for example:

print Career():PATCHLIMIT.
print Career():CANDOACTIONS.

Structure

structure Career
Members
Suffix Type Get Set
CANTRACKOBJECTS Boolean yes no
PATCHLIMIT scalar yes no
CANMAKENODES Boolean yes no
CANDOACTIONS Boolean yes no
Career:CANTRACKOBJECTS
Type:Boolean
Access:Get

If your tracking center allows the tracking of unnamed objects (asteroids, mainly) then this will return true.

Career:PATCHLIMIT
Type:scalar
Access:Get

If your tracking center allows some patched conics predictions, then this number will be greater than zero. The number represents how many patches beyond the current one that you are allowed to see. It influences attempts to call SHIP:PATCHES and SHIP:OBT:NEXTPATCH.

Career:CANMAKENODES
Type:Boolean
Access:Get

If your tracking center and mission control buildings are both upgraded enough, then the game allows you to make manuever nodes (which the game calls “flight planning”). This will return true if you can make maneuver nodes.

Career:CANDOACTIONS
Type:Boolean
Access:Get

If your VAB or SPH are upgraded enough to allow custom action groups, then you will also be allowed to execute the :DOACTION method of PartModules. Otherwise you can’t. This will return a boolean letting you know if the condition has been met.

The KerboScript Language

General Features of the KerboScript Language

Case Insensitivity

Everything in KerboScript is case-insensitive, including your own variable names and filenames. This extends to string comparison as well. ("Hello"="HELLO" will return true.)

Expressions

KerboScript uses an expression evaluation system that allows you to perform math operations on variables. Some variables are defined by you. Others are defined by the system. There are four basic types:

Numbers (Scalars)

You can use mathematical operations on numbers, like this:

SET X TO 4 + 2.5.
PRINT X.             // Outputs 6.5

The system follows the usual mathematical order of operations.

Throughout the documentation, numbers like this are referred to as Scalars to distinguish them from the many places where the mod works with Vector values instead.

Strings

Strings are pieces of text that are generally meant to be printed to the screen. For example:

PRINT "Hello World!".

To concatenate strings, you can use the + operator. This works with mixtures of numbers and strings as well:

PRINT "4 plus 3 is: " + (4+3).
Booleans

Booleans are values that can either be True or False and can be used to store the result of conditional checks:

set myValue to (x >= 10 and x <= 99).
if myValue {
  print "x is a two digit number.".
}
Structures

Structures are variables that contain more than one piece of information. For example, a Vector has an X, a Y, and a Z component. Structures can be used with SET.. TO just like any other variable. To access the sub-elements of a structure, you use the colon operator (”:”). Here are some examples:

PRINT "The Mun's periapsis altitude is: " + MUN:PERIAPSIS.
PRINT "The ship's surface velocity is: " + SHIP:VELOCITY:SURFACE.

Note

New in version 0.19.0: As of this kOS version, in fact ALL values a script can see are now a kind of Structure, even basic primitive types such as Boolean and Scalar.

Many structures also let you set a specific component of them, for example:

SET VEC TO V(10,10,10).  // A vector with x,y,z components
                         // all set to 10.
SET VEC:X to VEC:X * 4.  // multiply just the X part of VEC by 4.
PRINT VEC.               // Results in V(40,10,10).
Structure Methods

Structures also often contain methods. A method is a suffix of a structure that actually performs an activity when you mention it, and can sometimes take parameters. The following are examples of calling methods of a structure:

SET PLIST TO SHIP:PARTSDUBBED("my engines"). // calling a suffix
                                             // method with one
                                             // argument that
                                             // returns a list.
PLIST:REMOVE(0). // calling a suffix method with one argument that
                 // doesn't return anything.
PRINT PLIST:SUBLIST(0,4). // calling a suffix method with 2
                          // arguments that returns a list.

Note

New in version 0.15: Methods now perform the activity when the interpreter comes up to it. Prior to this version, execution was sometimes delayed until some later time depending on the trigger setup or flow-control.

For more information, see the Structures Section. A full list of structure types can be found on the Structures page. For a more detailed breakdown of the language, see the Language Syntax Constructs page.

Short-circuiting booleans

Further reading: https://en.wikipedia.org/wiki/Short-circuit_evaluation

When performing any boolean operation involving the use of the AND or the OR operator, kerboscript will short-circuit the boolean check. What this means is that if it gets to a point in the expression where it already knows the result is a forgone conclusion, it doesn’t bother calculating the rest of the expression and just quits there.

Example:

set x to true.
if x or y+2 > 10 {
    print "yes".
} else {
    print "no".
}.

In this case, the fact that x is true means that when evaluating the boolean expression x or y+2 > 10 it never even bothers trying to add y and 2 to find out if it’s greater than 10. It already knew as soon as it got to the x or whatever that given that x is true, the whatever doesn’t matter one bit. Once one side of an OR is true, the other side can either be true or false and it won’t change the fact that the whole expression will be true anyway.

A similar short circuiting happens with AND. Once the left side of the AND operator is false, then the entire AND expression is guaranteed to be false regardless of what’s on the right side, so kerboscript doesn’t bother calculating the righthand side once the lefthand side is false.

Read the link above for implications of why this matters in programming.

Late Typing

Kerboscript is a language in which there is only one type of variable and it just generically holds any sort of object of any kind. If you attempt to assign, for example, a string into a variable that is currently holding an integer, this does not generate an error. It simply causes the variable to change its type and no longer be an integer, becoming a string now.

In other words, the type of a variable changes dynamically at runtime depending on what you assign into it.

Lazy Globals (variable declarations optional)

Kerboscript is a language in which variables need not be declared ahead of time. If you simply set a variable to a value, that just “magically” makes the variable exist if it didn’t already. When you do this, the variable will necessarily be global in scope. kerboscript refers to these variables created implicitly this way as “lazy globals”. It’s a system designed to make kerboscript easy to use for people new to programming.

But if you are an experienced programmer you might not like this behavior, and there are good arguments for why you might want to disable it. If you wish to do so, a syntax exists to do so called @LAZYGLOBAL OFF.

User Functions

Note

New in version 0.17: This feature did not exist in prior versions of kerboscript.

Kerboscript supports user functions which you can write yourself and call from your own scripts. These are not structure methods (which as of this writing are a feature which only works for the built-in kOS types, and are not yet supported by the kerboscript language for user functions you write yourself).

Example:

DECLARE FUNCTION DEGREES_TO_RADIANS {
  DECLARE PARAMETER DEG.

  RETURN CONSTANT():PI * DEG/180.
}.

SET ALPHA TO 45.
PRINT ALPHA + " degrees is " + DEGREES_TO_RADIANS(ALPHA) + " radians.".

For a more detailed description of how to declare your own user functions, see the Language Syntax Constructs, User Functions section.

Structures

Structures, introduced above, are variable types that contain more than one piece of information. All structures contain sub-values or methods that can be accessed with a colon (:) operator. Multiple structures can be chained together with more than one colon (:) operator:

SET myCraft TO SHIP.
SET myMass TO myCraft:MASS.
SET myVel TO myCraft:VELOCITY:ORBIT.

These terms are referred to as “suffixes”. For example Velocity is a suffix of Vessel. It is possible to set some suffixes as well. The second line in the following example sets the ETA of a NODE 500 seconds into the future:

SET n TO Node( TIME:SECONDS + 60, 0, 10, 10).
SET n:ETA to 500.

The full list of available suffixes for each type can be found here.

Triggers

One useful feature of kerboscript (but a potentialy confusing one for people new to the language, so we don’t recommend you use it at first) is the “trigger”. A “trigger” is a block of statements headed by a conditional check (much like an IF condition) in which the kOS system itself will repeatdly run the conditional check for you in the background quite frequently without you having to explicitly run it yourself. When the kOS system detects that the condition just became true, it will interrupt whatever your program is doing and run the block of statements in the trigger, returning control to your main program right where it left off. Example:

// When the altitude eventually goes above 50,000 at some point later,
// interrupt whatever is going on to set off action group 1:
WHEN ship:altitude > 50000 then { ag1 on. }

Triggers are created using the when and on statements. They are complex enough that you should read the documentation for those keywords carefully to understand them before you use them.

KerboScript Syntax Specification

This describes what is and is not a syntax error in the KerboScript programming language. It does not describe what function calls exist or which commands and built-in variables are present. Those are contained in other documents.

General Rules

Whitespace consisting of consecutive spaces, tabs, and line breaks are all considered identical to each other. Because of this, indentation is up to you. You may indent however you like.

Note

Statements are ended with a period character (”.”).

The following are reserved command keywords and special operator symbols:

Arithmetic Operators:

+  -  *  /  ^  e  (  )

Logic Operators:

not  and  or  true  false  <>  >=  <=  =  >  <

Instructions and keywords:

add all at batch break clearscreen compile copy declare delete
deploy do do edit else file for from from function global if
in list local lock log off on once parameter preserve print reboot
remove rename run set shutdown stage step switch then to toggle
unlock unset until volume wait when

Other symbols:

{  }  [  ]  ,  :  //

Comments consist of everything from a “//” symbol to the end of the line:

set x to 1. // this is a comment.

Identifiers: Identifiers consist of: a string of (letter, digit, or underscore). The first character must be a letter or an underscore. The rest may be letters, digits or underscores.

Identifiers are case-insensitive. The following are identical identifiers:

my_variable
My_Variable
MY_VARIABLE

Note

New in version 1.1.0: Kerboscript accepts Unicode source code, encoded using the UTF-8 encoding method. Because of this, the definition of a “letter” character for an identifier includes letters from many languages’ alphabets, including accented Latin alphabet characters, Cyrllic characters, etc. Not all languages have been tested but in principle they should work as long as they have a Unicode standard accepted definition of what counts as a “letter”. We defer to the .NET libraries’ definition of what constitutes the “same” letter in uppercase and lowercase forms, and we hope this is right for most alphabets.

case-insensitivity

The same case-insensitivity applies throughout the entire language, with all keywords and when comparing literal strings. The values inside the strings are also case-insensitive, for example, the following will print “equal”:

if "hello" = "HELLO" {
    print "equal".
} else {
    print "unequal".
}

Note

New in version 1.1.0: Again, depending on the alphabet being used, the concept of “uppercase” and “lowercase” might not make sense in some languages. kOS defers to .NET’s interpretation of what letters in Unicode are paired together as the “upper” and “lower” versions of the same letter. For obvious reasons, the kOS developers cannot test every language and verify if this is correct or not.

Suffixes

Some variable types are structures that contain sub-portions. The separator between the main variable and the item inside it is a colon character (:). When this symbol is used, the part on the right-hand side of the colon is called the “suffix”:

list parts in mylist.
print mylist:length. // length is a suffix of mylist

Suffixes can be chained together, as in this example:

print ship:velocity:orbit:x.

In the above example you’d say “velocity is a suffix of ship”, and “orbit is a suffix of ship:velocity”, and “x is a suffix of ship:velocity:orbit”.

Numbers (scalars)

Numbers in kerboscript are referred to as “scalars”, to distinguish them from the many cases where a values will be represnted as a vectors. You are allowed to use integers, decimal fractional numbers (numbers with a decimal point and a fractional part), and scientific notation numbers.

The following are valid scalar syntax:

12345678
12_345_678 (The underscores are ignored as just visual spacers)
12345.6789
12_345.6789
-12345678
1.123e12
1.234e-12

Kerobscript does not support imaginary numbers or irrational numbers or rational numbers that cannot be represented as a finite decimal (i.e. sqrt(-1) returns a Not-a-Number error. Pi will have to be an approximation. “One third”, ends up being something like 0.333333333).)

Under the hood, these numbers are stored as either 32-bit integers or as 64-bit double floats, depending on the need, but kerboscript attempts to hide this detail from the programmer as much as possible.

Braces (statement blocks)

Anywhere you feel like, you may insert braces around a list of statements to get the language to treat them all as a single statement block.

For example: the IF statement expects one statement as its body, like so:

if x = 1
  print "it's 1".

But you can put multiple statements there as its body by surrounding them with braces, like so:

if x = 1 { print "it's 1".  print "yippieee.".  }

(Although this is usually preferred to be indented as follows):

if x = 1 {
  print "it's 1".
  print "yippieee.".
}

or:

if x = 1
{
  print "it's 1".
  print "yippieee.".
}

Kerboscript does not require proper indentation of the brace sections, but it is a good idea to make things clear.

You are allowed to just insert braces anywhere you feel like even when the language does not require it, as shown below:

declare x to 3.
print "x here is " + x.
{
  declare x to 5.
  print "x here is " + x.
  {
    declare x to 7.
    print "x here is " + x.
  }
}

The usual reason for doing this is to create a local scope section for yourself. In the above example, there are actually 3 different variables called ‘x’ - each with a different scope.

Functions (built-in)

There exist a number of built-in functions you can call using their names. When you do so, you can do it like so:

functionName( *arguments with commas between them* ).

For example, the ROUND function takes 2 arguments:

print ROUND(1230.12312, 2).

The SIN function takes 1 argument:

print SIN(45).

When a function requires zero arguments, it is legal to call it using the parentheses or not using them. You can pick either way:

// These both work:
CLEARSCREEN.
CLEARSCREEN().

Suffixes as Functions (Methods)

Some suffixes are actually functions you can call. When that is the case, these suffixes are called “method suffixes”. Here are some examples:

set x to ship:partsnamed("rtg").
print x:length().
x:remove(0).
x:clear().

User Functions

Note

New in version 0.17: This feature did not exist in prior versions of kerboscript.

Help for the new user - What is a Function?

In programming terminology, there is a commonly used feature of many programming languages that works as follows:

    1. Create a chunk of program instructions that you don’t intend to execute YET.
    1. Later, when executing other parts of the program, do the following:
      1. Remember the current location in the program.
      1. Jump to the previously created chunk of code from (1) above.
      1. Run the instructions there.
      1. Return to where you remembered from (2.A) and continue from there.

This feature goes by many different names, with slightly different precise meanings: Subroutines, Procedures, Functions, etc. For the purposes of kerboscript, we will refer to all uses of this feature with the term Function, whether it technically fits the mathematical definition of a “function” or not.

In kerboscript, you can make your own user functions using the DECLARE FUNCTION command, which is structured as follows:

declare function identifier { statements } optional dot (.)

Functions are a long enough topic as to require a separate documentation page, here.

Built-In Special Variable Names

Some variable names have special meaning and will not work as identifiers. Understanding this list is crucial to using kOS effectively, as these special variables are the usual way to query flight state information. The full list of reserved variable names is on its own page.

What does not exist (yet?)

Concepts that many other languages have, that are missing from KerboScript, are listed below. Many of these are things that could be supported some day, but at the moment with the limited amount of developer time available they haven’t become essential enough to spend the time on supporting them.

user-made structures or classes
Several of the built-in variables of kOS are essentially “classes” with methods and fields, however there’s currently no way for user code to create its own classes or structures. Supporting this would open up a large can of worms, as it would then make the kOS system more complex.

Flow Control

BREAK

Breaks out of a loop:

SET X TO 1.
UNTIL 0 {
    SET X TO X + 1.
    IF X > 10 { BREAK. } // Exits the loop when
                         // X is greater than 10
}

IF / ELSE

Checks if the expression supplied returns true. If it does, IF executes the following command block. Can also have an optional ELSE to execute when the IF condition is not true. ELSE can have another IF after it, to make a chain of IF/ELSE conditions:

SET X TO 1.
IF X = 1 { PRINT "X equals one.". }     // Prints "X equals one."
IF X > 10 { PRINT "X is greater than ten.". }  // Does nothing

// IF-ELSE structure:
IF X > 10 { PRINT "X is large".  } ELSE { PRINT "X is small".  }

// An if-else ladder:
IF X = 0 {
    PRINT "zero".
} ELSE IF X < 0 {
    PRINT "negative".
} ELSE {
    PRINT "positive".
}

Note

The period (.) is optional after the end of a set of curly braces like so:

// both of these lines are fine
IF TRUE { PRINT "Hello". }
IF TRUE { PRINT "Hello". }.

In the case where you are using the ELSE keyword, you must not end the previous IF body with a period, as that terminates the IF command and causes the ELSE keyword to be without a matching IF:

// works:
IF X > 10 { PRINT "Large". }  ELSE { PRINT "Small". }.

// syntax error - ELSE without IF.
IF X > 10 { PRINT "Large". }. ELSE { PRINT "Small". }.

LOCK

Locks an identifier to an expression. Each time the identifier is used in an expression, its value will be re-calculated on the fly:

SET X TO 1.
LOCK Y TO X + 2.
PRINT Y.         // Outputs 3
SET X TO 4.
PRINT Y.         // Outputs 6

LOCK follows the same scoping rules as the SET command. If the variable name used already exists in local scope, then the lock command creates a lock function that only lasts as long as the current scope and then becomes unreachable after that. If the variable name used does not exist in local scope, then LOCK will create it as a global variable, unless @LAZYGLOBAL is set to off, in which case it will be an error.

Note that a LOCK expression is extremely similar to a user function. Every time you read the value of the “variable”, it executes the expression again.

Note

If a LOCK expression is used with a flight control such as THROTTLE or STEERING, then it will get repeatedly evaluated in each physics tick. When used with a flight control variable, a LOCK actually becomes a trigger.

UNLOCK

Releases a lock on a variable. See LOCK:

UNLOCK X.    // Releases a lock on variable X
UNLOCK ALL.  // Releases ALL locks

UNTIL loop

Performs a loop until a certain condition is met:

SET X to 1.
UNTIL X > 10 {      // Prints the numbers 1-10
    PRINT X.
    SET X to X + 1.
}

Note

If you are writing an UNTIL loop that looks much like the example above, consider the possibility of writing it as a FROM loop instead.

Note that if you are creating a loop in which you are watching a physical value that you expect to change each iteration, it’s vital that you insert a small WAIT at the bottom of the loop like so:

SET PREV_TIME to TIME:SECONDS.
SET PREV_VEL to SHIP:VELOCITY.
SET ACCEL to V(9999,9999,9999).
PRINT "Waiting for accellerations to stop.".
UNTIL ACCEL:MAG < 0.5 {
    SET ACCEL TO (SHIP:VELOCITY - PREV_VEL) / (TIME:SECONDS - PREV_TIME).
    SET PREV_TIME to TIME:SECONDS.
    SET PREV_VEL to SHIP:VELOCITY.

    WAIT 0.001.  // This line is Vitally Important.
}

The full explanation why is in the CPU hardware description page.

FOR loop

Loops over a list collection, letting you access one element at a time. Syntax:

FOR variable1 IN variable2 { use variable1 here. }

Where:

  • variable1 is a variable to hold each element one at a time.
  • variable2 is a LIST variable to iterate over.

Example:

PRINT "Counting flamed out engines:".
SET numOUT to 0.
LIST ENGINES IN MyList.
FOR eng IN MyList {
    IF ENG:FLAMEOUT {
        set numOUT to numOUT + 1.
    }
}
PRINT "There are " + numOut + "Flamed out engines.".

Note

If you are an experienced programmer looking for something more like the for-loop from C, with its 3-part clauses of init, check, and increment in the header, see the FROM loop description. The kerboscript ‘for’ loop is more like a ‘foreach’ loop from other modern languages like C#.

FROM loop

Identical to the UNTIL loop, except that it also contains an explicit initializer and incrementer section in the header.

Syntax:
FROM { one or more statements } UNTIL Boolean_expression STEP { one or more statements } DO one statement or a block of statements inside braces ‘{}’

Quick Example:

print "Countdown initiated:".
FROM {local x is 10.} UNTIL x = 0 STEP {set x to x-1.} DO {
  print "T -" + x.
}

Note

If you are an experienced programmer, you can think of the FROM loop as just being Kerboscript’s version of the generic 3-part for-loop for( int x=10; x > 0; --x ) {...} that first appeared in C and is now so common to many programming languages, except that its Boolean check uses the reverse of that logic because it’s based on UNTIL loops instead of WHILE loops.

What the parts mean
  • FROM { one or more statements }
    • Perform these statements at the beginning before starting the first pass through the loop. They may contain local declarations of new variables. If they do, then the variables will be local to the body of the loop and won’t be visible outside the loop. In this case the braces { and } are mandatory even when there is only one statement present. To create a a null FROM clause, give it an empty set of braces.
  • UNTIL expression
    • Exactly like the UNTIL loop. The loop will run this expression at the start of each pass through the loop body, and if it’s true, it will abort and stop running the loop. It checks before the initial first pass of the loop as well, so it’s possible for the check to prevent the loop body from even executing once. Braces {..``}`` are not used here because this is not technically a complete statement. It is just an expression that evaluates to a value.
  • STEP { one or more statements }
    • Perform these statements at the bottom of each loop pass. The purpose is typically to increment or decrement the variable you declared in your FROM clause to get it ready for the next loop pass. In this case the braces { and } are mandatory even when there is only one statement present. To create a null FROM clause, give it an empty set of braces.
  • DO one statement or a block of statements inside braxes {..``}``:
    • This is where the loop body gets put. Much like with the UNTIL and FOR loops, these braces are not mandatory when there is only exactly one statement in the body, but are a very good idea to have anyway.
Why some braces are mandatory

Some braces are mandatory (for the FROM and STEP clauses) even when there is only one statement inside them, because the period that ends a single statement would look like it’s terminating the entire FROM loop if it was open and bare. Wrapping it inside braces makes it more visually obvious that it’s not the end of the FROM loop.

Why DO is mandatory

Other loop types don’t require a keyword to begin the loop body. You can just start in with the opening left-brace {. The reason the additional DO keyword exists in the FROM loop is because otherwise you’d have two back-to-back brace sections (The end of the STEP clause would abut against the start of the loop body) without any punctuation between them, and that would look too much like it was starting a brand new thing from scratch.

Other formatting examples
// prints a count from 1 to 10:
FROM {local x is 1.} UNTIL x > 10 STEP {set x to x+1.} DO { print x.}

// Entire header in one line, body indented:
// --------------------------------------------
FROM {local x is 1.} UNTIL x > 10 STEP {set x to x+1.} DO {
  print x.
}

// Each header part on its own line, body indented:
// --------------------------------------------
FROM {local x is 1.}
UNTIL x > 10
STEP {set x to x+1.}
DO {
  print x.
}

// Fully exploded out: Each header part on its own line,
//  each clause indented separately:
// --------------------------------------------
FROM
{
  local x is 1.  // x will count upward from 1.
  local y is 10. // while y is counting downward from 10.
}
UNTIL
  x > 10 or y = 0
STEP
{
  set x to x+1.
  set y to y-1.
}
DO
{
  print "x is " + x + ", y is " + y.
}

// ETC.

Any such combination of indenting styles, or mix and match of them, is understood by the compiler. The compiler ignores the spacing and indenting. It is recommended that you pick just two of them and stick with them - one compact one to use for short headers, and one longer exploded one to use for more wordy headers when you have to split it up across lines.

The literal meaning of FROM

If you have a FROM loop, it ends up being exactly identical to an UNTIL loop written as follows:

If we assume that AAAA, BBBB, CCCC, and DDDD are placeholders referring to the actual script syntax, then in the generic case, the following is how all FROM loops work:

FROM loop:

FROM { AAAA } UNTIL BBBB STEP { CCCC } DO { DDDD }

Is exactly the same as doing this:

{ // start a brace to keep the scope of AAAA local to the loop.
    AAAA
    UNTIL BBBB {
        DDDD

        CCCC
    }
} // end a brace to throw away the local scope of AAAA
An example of why the FROM loop is useful

Given that the FROM loop is really just an alternate way to write a certain format of UNTIL loop, you might ask why bother having it. The reason is that in the long run it makes your script easier to edit and maintain. It makes things more self-contained and cut-and-pasteable:

Above, in the documentation for UNTIL loops, this example was given:

SET X to 1.
UNTIL X > 10 {      // Prints the numbers 1-10
    PRINT X.
    SET X to X + 1.
}

The same example, expressed as a FROM loop is this:

FROM {SET X to 1.} UNTIL X > 10 {SET X to X + 1.} DO {
    PRINT X.
}

Kerboscript FROM loop provides a way to place those sections in the loop header so they are declared up front and let people see the layout of how the loop iterates, leaving the body to just contain the statements to be done for that iteration.

If you are editing your script and need to cut a loop section and move it elsewhere, the FROM loop makes it more visually obvious how to cut that loop and move it. It makes the important parts of the loop be self contained in the header, so you don’t leave the initializer behind when moving the loop.

WAIT

Halts execution for a specified amount of time, or until a specific set of criteria are met. Note that running a WAIT UNTIL statement can hang the machine forever if the criteria are never met. Examples:

WAIT 6.2.                     // Wait 6.2 seconds
WAIT UNTIL X > 40.            // Wait until X is greater than 40
WAIT UNTIL APOAPSIS > 150000. // You can see where this is going

Note that any WAIT statement, no matter what the actual expression is, will always result in a wait time that lasts at least one physics tick.

Difference between wait in mainline code and trigger code

When called from your mainline code, the WAIT command causes mainline code to be suspended, but does not stop triggers from interrupting this waiting period. Triggers will continue to fire off during the time that mainline code is stuck on a wait.

But when a WAIT is used in a trigger’s body (A “trigger” is any WHEN, or ON statement, or the expression in a steering control lock like lock throttle to mythrottlefunction().), it actually causes all execution including other triggers to get stuck until the wait is done. Because of this, while it is allowed, it is usually a bad idea to use WAIT inside a trigger.

Boolean Operators

All conditional statements, like IF, can make use of boolean operators. The order of operations is as follows:

  • = < > <= >= <>
  • AND
  • OR
  • NOT

Boolean is a type that can be stored in a variable and used that way as well. The constants True and False (case insensitive) may be used as values for boolean variables. If a number is used as if it was a Boolean variable, it will be interpreted in the standard way (zero means false, anything else means true):

IF X = 1 AND Y > 4 { PRINT "Both conditions are true". }
IF X = 1 OR Y > 4 { PRINT "At least one condition is true". }
IF NOT (X = 1 or Y > 4) { PRINT "Neither condition is true". }
IF X <> 1 { PRINT "X is not 1". }
SET MYCHECK TO NOT (X = 1 or Y > 4).
IF MYCHECK { PRINT "mycheck is true." }
LOCK CONTINUOUSCHECK TO X < 0.
WHEN CONTINUOUSCHECK THEN { PRINT "X has just become negative.". }
IF True { PRINT "This statement happens unconditionally." }
IF False { PRINT "This statement never happens." }
IF 1 { PRINT "This statement happens unconditionally." }
IF 0 { PRINT "This statement never happens." }
IF count { PRINT "count isn't zero.". }

DECLARE FUNCTION

Covered in more depth elsewhere in the documentation, the DECLARE FUNCTION statement creates a user-defined function that you can then call elsewhere in the code.

RETURN

Covered in more depth elsewhere in the documentation, the RETURN statement causes a user function, or a trigger body, to end, and chooses what the calling part of the program will see if it reads the value of the function.

WHEN / THEN statements, and ON statements

Note

Before going too far into this explanation, be aware that the WHEN and ON statements are rather advanced topics for a new programmer and if you’re just getting a feel for how programming works, and are using kOS as a first gentle introduction to writing programs, you might want to avoid using them until you’re more comfortable with the other features of kOS first.

The WHEN and the ON statement are very similar to each other, and so they are documented together here.

See also

General Guidelines for kOS Scripts
Before you continue, be aware that there is also a page in the tutorials section describing the best practices to use with these statements, including minimizing how long trigger bodies take. and minimizing how many trigger conditions are active. It would be a good idea to read that documentation after reading this section.

WHEN and ON both begin checking in the background for a condition that will cause some code to execute some statements later on. They do NOT cause the code to necessarily get run right now. The check will occur at regular fast intervals in the background, and the code will trigger whenever kOS next notices that the check happens to be true.

kOS has a feature known as a trigger, and a WHEN or an ON statement are two of the ways to create one. Any time you make a section of program that is meant to repeatedly run a check in the background while the main program continues on, that is called a trigger in kOS terminology. You may see the term trigger mentioned in many places in this documentation.

Syntax examples:

When and On side by side
WHEN .. THEN syntax ON syntax
WHEN boolean_expression THEN {

statements go here

}
ON any_expression {

statements go here

}
WHEN boolean_expression THEN single_statement. ON any_expression single_statement.

For historical reasons, the THEN keyword is needed for WHEN statements but not for ON statements.

Here is the difference between them:

  • WHEN statement: When kOS checks it in the background, if it notices the condition is true, the trigger fires and it performs the statements. The condition to check for must be a boolean expression.
  • ON statement: When kOS checks it in the backround, if it notices the expression is now different from what it was the last time it checked, the trigger fires and it performs the statements. The condition to check for can be any expression for which it is possible to test equality. It can be a boolean, a scalar, etc. All that matters is that kOS needs to be able to check if its new value is equal to its previous value or not.

Other than that, the two work the same way, and follow the same rules.

WHEN example:

// This example will eventually print the message
// once enough time has passed:

SET tenSecondsLater to TIME:SECONDS + 10.
WHEN TIME:SECONDS > tenSecondsLater THEN {
  PRINT "Ten seconds have passed.".
}

PRINT "now checking in the background to see if 10 seconds have passed yet.".

WAIT UNTIL FALSE. // Wait forever.  You have to end with Control-C
                  // The trigger will interrupt this waiting when it
                  // notices it should.

ON example. This style is frequently used with action groups in kOS. KSP’s action groups actually toggle from true to false or from false to true each time you press the key:

// This example will print a message whenever you toggle
// the lights, or press the '1' key.

ON AG1 {
  PRINT "You pressed '1', causing action group 1 to toggle.".
  PRINT "Action group 1 is now " + AG1.
  PRINT "No longer paying attention.".
}

WAIT UNTIL FALSE. // Wait forever.  You have to end with Control-C
                  // The trigger will interrupt this waiting when it
                  // notices it should.

For either WHEN or ON triggers, the check to see if it’s time to trigger, and the subsequent run of the statements if they do trigger, interrupts the normal flow of the program. The normal program flow will continue from where it left off, after the trigger finishes its work.

In a sense, a trigger is a bit like a user function you created and then asked the kOS system to please keep running it again and again in the background until it finally says that it fired off. In fact, it is implemented much like your own user functions.

If you run the above examples, you will see that they actually only happen once, and then stop happening again. In the ON AG1 example, it will only fire off once, no matter how many times you press the ‘1’ key. More will be covered about how to change that further down.

Warning

Do not make the body of a WHEN/THEN take a long time to execute. If you attempt to run code that lasts too long in the body of your WHEN/THEN statement, it will cause the main line code, and all other triggers (WHEN, ON, and cooked steering locks) to be stuck unable to continue until it finishes. You also probably should not make the system execute a WAIT command when inside the body of a WHEN/THEN statement.

kOS has a mechanism in place that allows triggers to interrupt mainline code that is stuck in a wait. It does not have a mechanism to go the other way around and have a trigger get interrupted. Triggers are meant to run quickly and finish so the system can get back to the mainline code.

Don’t let triggers bog down the code

If you are going to make extensive use of WHEN/THEN triggers, it’s important to understand more details of how they work in the kOS CPU.

Most importantly, be aware that since they get checked again and again in the background, having too many triggers that are “too expensive” can starve your main code of its use of the CPU, and thus slow down your program’s rate of running.

By default triggers only run once, but this can be changed

The original intention of the WHEN and ON triggers was that they create one-shot chunks of code you didn’t want to fire off repeatedly. They were intended for things like only running a piece of code when you break a threshold altitude, or detect that you’ve landed, etc.

So the default way they behave is that once the body of the trigger happens the first time, the trigger will never be checked again, and is now effectively dead for the rest of the program.

Obviously, that’s probably not the behavior you always want. Sometimes you will want them to keep repeatedly happening, as a frequent background check. One obvious example comes from the ON AG1 example above. You probably want a program that can keep re-checking to see if the action group button has been hit again and again, not just notice it once and then quit looking for it.

There are two ways to do this - the new (better) way with the return statement, and the older way, kept around for backward compatibility, of using the preserve keyword.

Preserving with return

Triggers are essentialy functions that don’t quite look like functions. They are frequently called, but they’re not called by you. They’re called by the Kerbal Operating System itself. So you can tell the Kerbal Operating System what your intentions were by simply deciding to return either a false or a true boolean value from the body of the trigger. This tells kOS if you wanted to keep the trigger around or let it get deleted.

  • return true. to tell kOS to preserve the trigger and keep checking it again next time.
  • return false. to tell kOS to disable the trigger after this check, and never use it again.

Therefore, if you want to have the ON AG1 example always respond to the keypress from now on, then change this:

ON AG1 {
  PRINT "You pressed '1', causing action group 1 to toggle.".
  PRINT "Action group 1 is now " + AG1.
  PRINT "No longer paying attention.".
}

To this instead:

ON AG1 {
  PRINT "You pressed '1', causing action group 1 to toggle.".
  PRINT "Action group 1 is now " + AG1.
  RETURN true.
}

Or, for a more complex example, if you want it to only respond to the first 5 times you press the key and then stop after that, you can conditionally decide what return value to use, like so:

SET count TO 5.
ON AG1 {
  PRINT "You pressed '1', causing action group 1 to toggle.".
  PRINT "Action group 1 is now " + AG1.
  SET count TO count - 1.
  PRINT "I will only pay attention " + count + " more times.".
  if count > 0
    RETURN true. // will keep the trigger alive.
  else
    RETURN false. // will let the trigger die.
}

There is an alternate, older syntax you can use to do the same thing, called the preserve keyword. You may see it used in a lot of older scripts, but the new way using the return keyword is cleaner.

If you never mention either a true or a false return value, the default is to behave as if you had returned false, and delete the trigger. This works because of the sort-of-secret fact that in kOS, all functions return zero if you don’t mention the return value explicitly.

They don’t last past the end of the program

A WHEN/THEN or ON trigger gets removed when the program that created it exits, even if it has not occurred yet.

PRESERVE

PRESERVE is a command keyword that is only valid inside of WHEN/THEN and ON code blocks.

When a WHEN/THEN or ON condition is triggered, the default behavior is to execute the code block body exactly once and only once, and then the trigger condition is removed and the trigger will never occur again.

To alter this, a new ability was added in kOS 0.19.3 and above to have triggers simply return a true or false value to determine if they wish to be preserved.

But prior to kOS 0.19.3, the only way to do it in kerboscript was with the PRESERVE keyword, which will likely remain in kerboscript for quite some time because it has a lot of backward compatibility legacy.

If you execute the PRESERVE command anywhere within the body of a trigger, it tells kOS that you wish the trigger to remain present and not get deleted. Choosing not to execute it, and just letting the execution fall through to the bottom of the body, has the default behavior of causing the trigger to get deleted.

For example, this:

ON AG1 {
  PRINT "You pressed '1', causing action group 1 to toggle.".
  PRINT "Action group 1 is now " + AG1.
  RETURN true.
}

could also be expressed this way:

ON AG1 {
  PRINT "You pressed '1', causing action group 1 to toggle.".
  PRINT "Action group 1 is now " + AG1.
  PRESERVE.
}

And this:

SET count TO 5.
ON AG1 {
  PRINT "You pressed '1', causing action group 1 to toggle.".
  PRINT "Action group 1 is now " + AG1.
  SET count TO count - 1.
  PRINT "I will only pay attention " + count + " more times.".
  if count > 0
    RETURN true. // will keep the trigger alive.
  else
    RETURN false. // will let the trigger die.
}

could also be expressed this way:

SET count TO 5.
ON AG1 {
  PRINT "You pressed '1', causing action group 1 to toggle.".
  PRINT "Action group 1 is now " + AG1.
  SET count TO count - 1.
  PRINT "I will only pay attention " + count + " more times.".
  if count > 0
    PRESERVE.
}

Also note that unlike using RETURN, the PRESERVE statement doesn’t actually cause the trigger to abort and return at that point. It just sets a flag for what the intended return value will be, without actually returning yet. Therefore it doesn’t actually matter where within the block of code it happens, it has the same effect.

this:

ON AG1 {
  PRINT "You pressed '1', causing action group 1 to toggle.".
  PRINT "Action group 1 is now " + AG1.
  PRESERVE.
}

has the same effect as this:

ON AG1 {
  PRESERVE. // <-- Doesn't matter where you PRESERVE within the body.
  PRINT "You pressed '1', causing action group 1 to toggle.".
  PRINT "Action group 1 is now " + AG1.
}

(If you attempt to BOTH execute PRESERVE. and provide a RETURN false. statement that contradicts it, the RETURN statement will end up overriding the effect of the PRESERVE.)

Variables & Statements

DECLARE .. TO/IS

What it does:

Declares a variable, explicitly or implicitly defining what scope it has, and gives it an initial value.

Allowed Syntax:
Detailed Description of the syntax:
  • The statement must begin with either the word DECLARE, LOCAL, or GLOBAL. If it begins with the word DECLARE it may optionally also contain the word LOCAL or GLOBAL afterward. Note that if neither GLOBAL nor LOCAL is used, the behavior of LOCAL will be assumed implicitly. Therefore DECLARE LOCAL and just DECLARE and just LOCAL mean the same thing.
  • After that it must contain an identifier.
  • After that it must contain either the word TO or the word IS, which mean the same thing here.
  • After that it must contain some expression for the initial starting value of the variable.
  • After that it must contain a dot (“period”), like all commands in Kerboscript.
// These all do the exact same thing - make a local variable:
DECLARE X TO 1. // assumes local when unspecified.
LOCAL X IS 1.
DECLARE LOCAL X IS 1.

// These do the exact same thing - make a global variable:
GLOBAL X IS 1.
DECLARE GLOBAL X IS 1.

If neither the scope word GLOBAL nor the scope word LOCAL appear, a declare statement assumes LOCAL by default.

Any variable declared with DECLARE, DECLARE LOCAL, or LOCAL will only exist inside the code block section it was created in. After that code block is finished, the variable will no longer exist.

See Scoping:
If you don’t know what the terms “global” or “local” mean, it’s important to read the section below about scoping.

Note

It is implied that the outermost scope of a program file is the global scope. Therefore if you make a LOCAL variable at the outermost nesting level of your program it really ends up being GLOBAL. Note that GLOBAL variables are not only shared between functions of your script, but also can be seen by other programs you run from the current program, and visa versa.

Alternatively, a variable can be implicitly declared by any SET or LOCK statement, however doing so causes the variable to always have global scope. The only way to make a variable be local instead of global is to declare it explicitly with one of these DECLARE statements.

Note

Terminology: “declare statement”: Note that the documentation will often refer to the phrase “declare statement” even when referring to a statement in which the optional keyword “declare” was left off. A statement such as LOCAL X IS 1. Will still be referred to as a “declare statement”, even though the word “declare” never explicitly appeared in it.

Initializer required in DECLARE

Note

New in version 0.17: The syntax without the initializer, looking like so:

DECLARE x. // no initializer like "TO 1."

is no longer legal syntax.

Kerboscript now requires the use of the initializer clause (the “TO” keyword) after the identifier name so as to make it impossible for there to exist any uninitialized variables in a script.

DECLARE PARAMETER

If you put this statement in the main part of your script, it declares variables to be used as a parameter that can be passed in using the RUN command.

If you put this statement inside of a Function body, then it declares variables to be used as a parameter that can be passed in to that function when calling the function.

Just as with a declare identifier statement, in a declare parameter statement, the actual keyword declare need not be used. The word parameter may be used alone and that is legal syntax.

Program 1:

// This is the contents of program1:
DECLARE PARAMETER X.
PARAMETER Y. // omitting the word "DECLARE" - it still means the same thing.
PRINT "X times Y is " + X*Y.

Program 2:

// This is the contents of program2, which calls program1:
SET A TO 7.
RUN PROGRAM1( A, A+1 ).

The above example would give the output:

X times Y is 56.

It is also possible to put more than one parameter into a single DECLARE PARAMETER statement, separated by commas, as shown below:

DECLARE PARAMETER X, Y, CheckFlag.

// Or you could leave "DECLARE" off like so:
PARAMETER X, Y, CheckFlag.

Either of the above is exactly equivalent to:

PARAMETER X.
PARAMETER Y.
PARAMETER CheckFlag.

Note: Unlike normal variables, Parameter variables are always local to the program. When program A calls program B and passes parameters to it, program B can alter their values without affecting the values of the variables in program A.

Caveat
This is only true if the values are primitive singleton values like numbers or booleans. If the values are Structures like Vectors or Lists, then they do end up behaving as if they were passed by reference, in the usual way that should be familiar to people who have used languages like Java or C# before.

Illegal to say DECLARE GLOBAL PARAMETER : Because parameters are always local to the location they were declared at, the keyword GLOBAL is illegal to use in a DECLARE PARAMETER statement.

The DECLARE PARAMETER statements can appear anywhere in a program as long as they are in the file at a point earlier than the point at which the parameter is being used. The order the arguments need to be passed in by the caller is the order the DECLARE PARAMETER statements appear in the program being called.

Optional Parameters (defaulted parameters)

If you wish, you may make some of the parameters of a program or a user function optional by defaulting them to a starting value with the IS keyword, as follows:

// Imagine this is a file called MYPROG

DECLARE PARAMETER P1, P2, P3 is 0, P4 is "cheese".
print P1 + ", " + P2 + ", " + P3 + ", " + P4.


// Imagine this is a different file that runs it:

run MYPROG(1,2).         // prints "1, 2, 0, cheese".
run MYPROG(1,2,3).       // prints "1, 2, 3, cheese".
run MYPROG(1,2,3,"hi").  // prints "1, 2, 3, hi".
runpath(MYPROG,1,2,3,"hi").  // also prints "1, 2, 3, hi".

Whenever arguments are missing, the system always makes up the difference by using defaults for the lastmost parameters until the correct number have been padded. (So for example, if you call MYFUNC() above with 3 arguments, it’s the last argument, P4, that gets defaulted, but P3 does not. But if you call it with 2 arguments, both P4 and P3 get defaulted.)

It is illegal to put mandatory (not defaulted) parameters after defaulted ones.

This will not work:

DECLARE PARAMETER thisIsOptional is 0,
                  thisIsOptionalToo is 0.
                  thisIsMandatory.

Because the optional parameters didn’t come at the end.

Default parameters follow short-circuit logic

Remember that if you have an optional parameter with an initializer expression, the expression will not get executed if the calling function had an argument present in that position. The expression only gets executed if the system needed to pad a missing argument.

Note

New in version 0.18.3: Optional Parameters were added as a new feature in kOS 0.18.3

Note

Pass By Value

The following paragraph is important for people familiar with other programming languages. If you are new to programming and don’t understand what it is saying, that’s okay you can ignore it.

At the moment the only kind of parameter supported is a pass-by-value parameter, and pass-by reference parameters don’t exist. Be aware, however, that due to the way kOS is implemented on top of a reference-using object-oriented language (CSharp), if you pass an argument which is a complex aggregate structure (i.e. a Vector, or a List - anything that kOS lets you use a colon suffix with), then the parameters will behave exactly like being passed by reference because all you’re passing is the handle to the object rather than the object itself. This should be familiar behavior to anyone who has written software in Java or C# before.

SET

Sets the value of a variable. Implicitly creates a global variable if it doesn’t already exist:

SET X TO 1.
SET X TO y*2 - 1.

This follows the scoping rules explained below. If the variable can be found in the current local scope, or any scope higher up, then it won’t be created and instead the existing one will be used.

DEFINED

DEFINED identifier

Returns a boolean true or false according to whether or not an identifier is defined in such a way that you can use it from this part of the program. (i.e. is it declared and is it in scope and visible right now):

// This part prints 'doesn't exist":
if defined var1 {
  print "var1 exists".
} else {
  print "var1 doesn't exist."
}

local var1 is 0.

// But now it prints that it does exist:
if defined var1 {
  print "var1 exists".
} else {
  print "var1 doesn't exist."
}

The DEFINED operator pays attention to all the normal scoping rules described in the scoping section below. If an identifier does exist but is not usable from the current scope, it will return false.

Note that DEFINED does not work well on things that are not pure identifiers. for example:

print defined var1:suffix1.

is going to end up printing “False” because it’s looking for pure identifiers, not complex suffix chains, and there’s no identifier called “var1:suffix1”.

Difference between SET and DECLARE LOCAL and DECLARE GLOBAL

The following three examples look very similar and you might ask why you’d pick one instead of the other:

SET X TO 1.
DECLARE LOCAL X TO 1.
DECLARE GLOBAL X TO 1.

They are slightly different, as follows:

SET X TO 1. Performs the following activity:

  1. Attempt to find an already existing local X. If found, set it to 1.
  2. Try again for each scoping level outside the current one.
  3. If and only if it gets all the way out to global scope and it still hasn’t found an X, then create a new X with value 1, and do so at global scope. This behavior is called making a “lazy global”.

DECLARE LOCAL X TO 1. Performs the following activity:

  1. Immediately make a new X right here at the local-most scope. Set it to 1.

DECLARE GLOBAL X TO 1. Performs the following activity:

  1. Ignore whether or not there are any existing X’s in a local scope.
  2. Immediately go all the way to global scope and make a new X there. Set it to 1.
When to use GLOBAL

You should use a DECLARE GLOBAL statement only sparingly. It mostly exists so that a function can store values “in the caller” for the caller to get its hands on. It’s generally a “sloppy” design pattern to use, and it’s much better to keep everything local and only pass back things to the caller as return values.

LOCK

Declares that the identifier will refer to an expression that is always re-evaluated on the fly every time it is used (See also Flow Control documentation):

SET Y TO 1.
LOCK X TO Y + 1.
PRINT X.    // prints "2"
SET Y TO 2.
PRINT X.    // prints "3"

Note that because of how LOCK expressions are in fact implemented as mini functions, they cannot have local scope. A LOCK always has global scope.

By default a LOCK expression is GLOBAL when made. This is necessary for backward compatibility with older scripts that use LOCK STEERING from inside triggers, loops, etc, and expect it to affect the global steering value.

Calling a LOCK that was created in another file

If you try to call a lock that is declared in another program file you run, it does not work, and has never worked prior to kOS 0.17.0:

File1.ks:

run File2.
print "x's locked value is " + x.

File2.ks:

lock x to "this is x".

But now with the Kerboscript of kOS 0.17.0, you can make it work by inserting empty parentheses after the lock name to help give the compiler the hint that you expected x to be a function call (which is what a lock really is):

Change this line:

print "x's locked value is " + x.

To this instead:

print "x's locked value is " + x().

and it should work.

Local lock

You can explicitly make a LOCK statement be LOCAL with the LOCAL keyword, like so:

LOCAL LOCK identifier TO expression.

But be aware that doing so with a cooked steering control such as THROTTLE or STEERING will not actually affect your ship. The automated cooked steering control is only reading the GLOBAL locks for these settings.

The purpose of making a LOCAL lock is if you only need to use the value temporarily for the duration of a function call, loop, or if-statement body, and then you don’t care about it anymore after that.

Why do I care about a local lock?

You care because in order to make a LOCK work even after the variables it’s using in its expression go out of scope (which is necessary for LOCK STEERING or LOCK THROTTLE to work if done from inside a user function call or trigger body), locks need to preserve a thing called a “closure”. ( http://en.wikipedia.org/wiki/Closure_(computer_programming)

When they do this, it means none of the local variables used in the function body they were declared in truly “go away” from memory. They live on, taking up space until the lock disappears. Making the lock be local tells the computer that it can make the lock disappear when it goes out of scope, and thus it doesn’t need to hold that “closure” around forever.

The tl;dr version: It’s more efficient for memory. If you know for sure that your lock isn’t getting used after your current section of code is over, make it a local lock.

TOGGLE

Toggles a variable between TRUE or FALSE. If the variable in question starts out as a number, it will be converted to a boolean and then toggled. This is useful for setting action groups, which are activated whenever their values are inverted:

TOGGLE AG1. // Fires action group 1.
TOGGLE SAS. // Toggles SAS on or off.

This follows the same rules as SET, in that if the variable in question doesn’t already exist, it will end up creating it as a global variable.

ON

Sets a variable to TRUE. This is useful for the RCS and SAS bindings:

RCS ON.  // Turns on the RCS

This follows the same rules as SET, in that if the variable in question doesn’t already exist, it will end up creating it as a global variable.

OFF

Sets a variable to FALSE. This is useful for the RCS and SAS bindings:

RCS OFF.  // Turns off the RCS

This follows the same rules as SET, in that if the variable in question doesn’t already exist, it will end up creating it as a global variable.

Scoping terms

Note

New in version 0.17: In prior versions of Kerboscript, all identifiers other than DECLARE PARAMETER identifiers were always global variables no matter what, even if you used the DECLARE statement to make them.

What is Scope?
The term Scope simply refers to asking the question “where in the code can this variable be used, and how long does it last before it goes away?” The scope of a variable is the section of the program’s code that it “works” within. Any section of the program’s code from which the variable cannot be seen is said to be “out of that variable’s scope”.
Global scope
The simplest scope is called “global”. Global scope simply means “this variable can be used from anywhere in the program”. If you never use the DECLARE statement, then your variables in Kerboscript will all be in global scope. For simple easy scripts used by beginners, this is often enough and you don’t have to read the rest of this topic until you start advancing to more intermediate scripts.
Local Scope
Kerboscript uses block scoping to keep track of local variable scope. This means you can have variables that are not only local to a function, but are in fact actually local to JUST the current curly-brace block of statements, even if that block of statements is, say, the body of an IF check, or the body of an UNTIL loop.
Why limit scope?
You might be wondering why it’s useful to limit the scope of a variable. Wouldn’t it be easier just to make all variables global? The answer is twofold: (1) Once a program becomes large enough, trying to remember the name of every variable in the program, and having to keep coming up with new names for new variables, can be a large unmanageable chore, especially with programs written by more than one person collaborating together. (2) Even if you can keep track of all that in your head, there’s a certain programming technique known as recursion ( http://en.wikipedia.org/wiki/Recursion#In_computer_science ) in which you actually NEED to have local variable scope for the technique to even work at all.

If you need to have variables that only have local scope, either just to keep your code more manageable, or because you literally need local scope to allow for recursive function calls, then you use the DECLARE LOCAL statement (or just LOCAL for short) to create the variables.

Scoping syntax

Presumed defaults

The DECLARE keyword and the LOCK keyword have some default presumed scoping behaviors:

DECLARE Is assumed to always be LOCAL when not otherwise specified.

FUNCTION Must always be LOCAL. Declaring it as global has no effect, so the only way to make it be global is to declare it while at outermost scope. This is because functions always remember their closures, and so to declare a function as global while it holds closure information about local variables would be contradictory.

PARAMETER Cannot be anything but LOCAL to the location it’s mentioned. It is an error to attempt to declare a parameter with the GLOBAL keyword.

LOCK Is assumed to always be GLOBAL when not otherwise specified. this is necessary to preserve backward compatibility with how cooked controls such as LOCK STEERING and LOCK THROTTLE work.

Explicit scoping keywords

The DECLARE, FUNCTION, and LOCK commands can be given explicit GLOBAL or LOCAL keywords to define their intended scoping level (however in the case of functions, GLOBAL will be igorned, see above under ‘Presumed defaults’.):

//
// These are all synonymous with each other:
//
DECLARE X TO 1.
DECLARE LOCAL X TO 1.
LOCAL X TO 1. // 'declare' is implied and optional when scoping words are used
LOCAL X IS 1. // 'declare' is implied and optional when scoping words are used
//
// These are all synonymous with each other:
//
DECLARE GLOBAL X TO 1.
GLOBAL X TO 1. // 'declare' is implied and optional when scoping words are used
GLOBAL X IS 1. // 'declare' is implied and optional when scoping words are used

Even when the word ‘DECLARE’ is left off, the statement can still be referred to as a “declare statement”. The word “declare” is implied by the use of LOCAL or GLOBAL and you are allowed to leave it off merely to reduce verbosity.

Explicit Scoping required for @lazyglobal off

Note that when operating under the @LAZYGLOBAL OFF directive the keywords LOCAL and GLOBAL are no longer optional for declare identifier statements, and are in fact required. You are not allowed to rely on these presumed defaults when you’ve turned off LAZYGLOBAL. (This only applies to trying to make a variable with declare identifier to value, and not to declare parameter or declare function.)

Locals stated at the global level are global

Note that if you put a statement at the outermost scope of the program, then there is effectively no difference between a DECLARE LOCAL (or just LOCAL for short) and a DECLARE GLOBAL (or just GLOBAL for short) statement. They are both going to make a variable at global scope because that’s the scope the program was in when the statement was encountered.

Examples:

GLOBAL x IS 10. // X is now a global variable with value 10,
SET y TO 20. // Y is now a global variable (implicitly) with value 20.
LOCAL z IS 0.  // Z is now a global variable
               // because even though this says LOCAL, it was
               // stated at the outermost, global scope.

SET sum to -1. // sum is now an implicitly made global variable, containing -1.

// A function to return the mean average of all the items in the list
// passed into it, under the assumption all the items in the list are
// numbers of some sort:
FUNCTION calcAverage {
  PARAMETER inputList.

  LOCAL sum IS 0. // sum is now local to this function's body.
  FOR val IN inputList {
    SET sum TO sum + val.
  }.
  print "Inside calcAverage, sum is " + sum.
  RETURN sum / inputList:LENGTH.
}.

SET testList TO LIST(5,10,15);
print "average is " + calcAverage(testList).
print "but out here where it's global, sum is still " + sum.

This example will print:

Inside calcAverage, sum is 30
average is 10
but out here where it's global, sum is still -1

Thus proving that the variable called SUM inside the function is NOT the same variable as the one called SUM out in the global main code.

Nesting

The scoping rules are nested as well. If you attempt to use a variable that doesn’t exist in the local scope, the next scope “outside” it will be used, and if it doesn’t exist there, the next scope “outside” that will be used and so on, all the way up to the global scope. Only if the variable isn’t found at the global scope either will it be implicitly created.

Scoping and Triggers:

Triggers such as:

  • WHEN <boolean expression> THEN { <statements> }.

and

  • ON <any expression> { <statements> }.

Can use local variables in their trigger expressions in thier headers or in the statements of their bodies. The local scope they were declared inside of stays present as part of their “closure”.

Example:

FUNCTION future_trigger {
  parameter delay.
  print "I will fire the trigger after " + delay + " seconds.".

  local trigger_time is time:seconds + delay.

  // Note that the variable trigger_time is local here,
  // yet this trigger still works after the function
  // has completed and returned:
  when time:seconds > trigger_time then {
    print "I am now firing the trigger off.".
  }
}
print "Before calling future_trigger(3).".
future_trigger(3).
print "After calling future_trigger(3), now waiting 5 seconds.".
print "You should see the trigger message during this wait.".
wait 5.
print "Done waiting.  Program over.".

Note

New in version 1.1.0: In the past, triggers such as WHEN and ON were not able to use local variables in their check condintions. They had to use only global variables in order to be trigger-able after the local scope goes away. Now these triggers preserve their “closure scope” so they can use any local variables.

@LAZYGLOBAL directive

Often the fact that you can get an implicit global variable declared without intending to can lead to a lot of code maintenance headaches down the road. If you make a typo in a variable name, you end up creating a new variable instead of generating an error. Or you may just forget to mark the variable as local when you intended to.

If you wish to instruct Kerboscript to alter its behavior and disable its normal implicit globals, and instead demand that all variables MUST be explicitly declared and may not use implied lazy scoping, the @LAZYGLOBAL compiler directive allows you to do that.

If you place the words:

@LAZYGLOBAL OFF.

At the start of your program, you will turn off the compiler’s lazy global feature and it will require you to explicitly mention all variables you use in a declaration somewhere (with the exception of the built-in variables such as THROTTLE, STEERING, SHIP, and so on.)

Note

The @LAZYGLOBAL directive does not affect LOCK statements. LOCKS are a special case that define new pseudo-functions when encountered and don’t quite work the same way as SET statements do. Thus even with @LAZYGLOBAL OFF, it’s still possible to make a LOCK statement with a typo in the identifier name and it will still create the new typo’ed lock that way.

@LAZYGLOBAL Can only exist at the top of your code.

The @LAZYGLOBAL compile directive is only allowed as the first non-comment thing in the program file. This is because it instructs the compiler to change its default behavior for the duration of the entire file’s compile.

@LAZYGLOBAL Makes LOCAL and GLOBAL mandatory

Normally the keywords local and global can be left off as optional in declare identifier statements. But when you turn LAZYGLOBAL off, the compiler starts requiring them to be explicitly stated for declare identifier statements, to force yourself to be clear and explicit about the difference.

For example, this program, which is valid:

function foo {print "foo ". }
declare x is 1.

print foo() + x.

Starts giving errors when you add @LAZYGLOBAL OFF to the top:

@LAZYGLOBAL OFF.
function foo {print "foo ". }
declare x is 1.

print foo() + x.

Which you fix by explicitly stating the local keyword, as follows:

@LAZYGLOBAL OFF.
function foo {print "foo ". }  // This does not need the 'local' keyword added
declare local x is 1.          // But this does because it is a declare *identifier* statement.
                               // you could have also just said:
                               //     local x is 1.
                               // without the 'declare' keyword.

print foo() + x.

If you get in the habit of just writing your declare identifier statements like local x is 1. or global x is 1., which is probably nicer to read anyway, the issue won’t come up.

Longer Example of use

Example:

@LAZYGLOBAL off.
global num TO 1.
IF TRUE {
  LOCAL Y IS 2.
  SET num TO num + Y. // This is fine.  num exists already as a global and
                      // you're adding the local Y to it.
  SET nim TO 20. // This typo generates an error.  There is
                 // no such variable "nim" and @LAZYGLOBAL OFF
                 // says not to implicitly make it.
}.
Why LAZYGLOBAL OFF?
The rationale behind LAZYGLOBAL OFF. is to primarily be used in cases where you’re writing a library of function calls you intend to use elsewhere, and want to be careful not to accidentally make them dependent on globals outside the function itself.

The @LAZYGLOBAL OFF. directive is meant to mimic Perl’s use strict; directive.


History:

Kerboscript began its life as a language in which you never have to declare a variable if you don’t want to. You can just create any variable implicitly by just using it in a SET statement.

There are a variety of programming languages that work like this, such as Perl, JavaScript, and Lua. However, they all share one thing in common - once you want to allow the possibility of having local variables, you have to figure out how this should work with the implicit variable declaration feature.

And all those languages went with the same solution, which Kerboscript now follows as well. Because implicit undeclared variables are intended to be a nice easy way for new users to ease into programming, they should always default to being global so that people who wish to keep programming that way don’t need to understand or deal with scope.

KerboScript User Functions

This page covers functions created by you, the user of kerboscript, rather than the built-in functions provided by kOS.

Help for the new user - What is a Function?

In programming terminology, there is a commonly used feature of many programming languages that works as follows:

    1. Create a chunk of program instructions that you don’t intend to execute YET.
    1. Later, when executing other parts of the program, do the following:
      1. Remember the current location in the program.
      1. Jump to the previously created chunk of code from (1) above.
      1. Run the instructions there.
      1. Return to where you remembered from (2.A) and continue from there.

This feature goes by many different names, with slightly different precise meanings: Subroutines, Procedures, Functions, etc. For the purposes of kerboscript, we will refer to all uses of this feature with the term Function, whether it technically fits the mathematical definition of a “function” or not.

Warning

Functions created in programs can only be called from programs, not from the interpreter terminal prompt.

If you attempt to create a function in a program, and then call it from the interactive prompt in the interpreter instead of calling it from inside a program, it will definitely not work properly, but the exact error you get will depend on several “random” factors. This may be fixed later by a later release, but for now, don’t do it. For further explanation, see the section entitled Functions and the interpreter terminal.

DECLARE FUNCTION

In kerboscript, you can make your own user functions using the DECLARE FUNCTION command, which has syntax as follows:

[declare] [local] function identifier { statements } optional dot (.)

The statement is called a “declare function” statement even when the optional word “declare” was left off.

The following are all identical in meaning:

declare function hi { print "hello". }
declare local function hi { print "hello". }
local function hi { print "hello". }
function hi { print "hello". }

Functions are presumed to have scope local to the location where they are declared when the explicit local scope keyword is missing.

At the moment, it is redundant to mention the local keyword, although it is allowed.

It is best to just leave all the optional keywords of and merely say function by itself.

Example:

// Print the string you pass in, in one of the 4 corners
// of the terminal:
//   mode = 1 for upper-left, 2 for upper-right, 3
//          for lower-left, and 4 for lower-right:
//
function print_corner {
  parameter mode.
  parameter text.

  local row is 0.
  local col is 0.

  if mode = 2 or mode = 4 {
    set col to terminal:width - text:length.
  }.
  if mode = 3 or mode = 4 {
    set row to terminal:height - 1.
  }.

  print text at (col, row).
}.

// An example of calling it:

print_corner(4,"That's me in the corner").

A declare function command can appear anywhere in a kerboscript program, and once its been “parsed” by the compiler, the function can be called from anywhere in the program.

The best design pattern is probably to create your library of function calls as one or more separate .ks files that contain just function definitions and not much else in them. Then when you “run” the file containing the functions, what you’re really doing is just loading the function definitions into memory so they can be called by other programs. At the top of your main script you can then “run” the other scripts containing the library of functions to get them compiled into memory.

Using RUN ONCE or RUNONCEPATH

If you want to load a library of functions that ALSO perform some initialization mainline code, but you only want the mainline code to execute once when the library is first loaded, rather than every time a subprogram runs your library, then use the ‘once’ keyword with the RUN command, or the RUNONCEPATH command, as follows:

// This will run mylib1 3 times, re-running the mainline code in it:`
run mylib1.
run mylib1.
runpath("mylib1"). // just the same thing as 'run mylib1', really.

// This will run mylib2 only one time, ignoring the additional
// instances:
run once mylib2.
run once mylib2. // mylib2 was already run, will not be run again.
runoncepath("mylib2"). // mylib2 was already run, will not be run again.

Example: Let’s say you want to have a library that keeps a counter and always returns the next number up every time it’s called. You want it initialized to start with, but not get re-initialized every time another sub-program includes the library in its code. So you have this:

prog1, which calls counterlib:

// prog1
run once counterlib.

// Get some unique IDs:
print "prog1:      next counter ID = " + counter_next().
print "prog1:      next counter ID = " + counter_next().
print "prog1:      next counter ID = " + counter_next().

run subprogram.

subprogram, which ALSO calls counterlib:

// subprogram
runoncepath("counterlib"). // same as 'run once counterlib.'

print "subprogram: next counter ID = " + counter_next().
print "subprogram: next counter ID = " + counter_next().
print "subprogram: next counter ID = " + counter_next().

counterlib

// init code:
global current_num is 0.

// counter_next()
function counter_next {
   set current_num to current_num + 1.
   return current_num.
}

The above example prints this:

prog1:      next counter ID = 1
prog1:      next counter ID = 2
prog1:      next counter ID = 3
subprogram: next counter ID = 4
subprogram: next counter ID = 5
subprogram: next counter ID = 6

whereas, had you used just run counterlib. instead of run once counterlib., then it would have printed this:

prog1:      next counter ID = 1
prog1:      next counter ID = 2
prog1:      next counter ID = 3
subprogram: next counter ID = 1
subprogram: next counter ID = 2
subprogram: next counter ID = 3

because subprogram would have run the mainline code global current_num is 0 again when it was run inside subprogram.

DECLARE PARAMETER

If your function expects to have parameters passed into it, you can use the DECLARE PARAMETER command to do so. This is the same command as is used to declare parameters for running a whole script. By putting a DECLARE PARAMETER statement inside a function, you tell the kerboscript compiler that you want the parameter to be for that function, not for the whole script.

An example of using declare parameter can be seen in the example above, where it is used for the mode and text parameters.

(Again, even when the word ‘declare’ is missing, we still call them ‘declare parameter’ commands.)

Calling a function

To call a function you created, you call it the same way you call a built-in function, by putting a pair of parentheses to the right of it, as shown here:

function example_function {
  print "hello, this is my example.".
}

example_function().

If the function takes parameters, then you put them in the parentheses just like when running a program. You can see an example of this above in the previous example where it said:

print_corner(4,"That's me in the corner").

Optional Parameters (parameter defaults)

If you wish, you may make some of the parameters of a user function optional by defaulting them to a starting value with the IS keyword, as follows:

example 1:

FUNCTION MYFUNC {
  DECLARE PARAMETER P1, P2, P3 is 0, P4 is "cheese".
  print P1 + ", " + P2 + ", " + P3 + ", " + P4.
}

example 2:

FUNCTION MYFUNC {
  PARAMETER P1, P2, P3 is 0, P4 is "cheese".

  print P1 + ", " + P2 + ", " + P3 + ", " + P4.
}

example 3:

FUNCTION MYFUNC {
  PARAMETER P1.
  PARAMETER P2.
  PARAMETER P3 is 0.
  PARAMETER P4 is "cheese".

  print P1 + ", " + P2 + ", " + P3 + ", " + P4.
}

In the above examples, all of which are the same, if you call the function with parameter 3 or 4 missing, kOS will assign it the default value mentioned in the PARAMETER statement, like in the examples below:

MYFUNC(1,2).         // prints "1, 2, 0, cheese".
MYFUNC(1,2,3).       // prints "1, 2, 3, cheese".
MYFUNC(1,2,3,"hi").  // prints "1, 2, 3, hi".

Whenever arguments are missing, the system always makes up the difference by using defaults for the lastmost parameters until the correct number have been padded. (So for example, if you call MYFUNC() above with 3 arguments, it’s the last argument, P4, that gets defaulted, but P3 does not. But if you call it with 2 arguments, both P4 and P3 get defaulted.)

It is illegal to put mandatory (not defaulted) parameters after defaulted ones.

This will not work:

DECLARE PARAMETER thisIsOptional is 0,
                  thisIsOptionalToo is 0.
                  thisIsMandatory.

Because the optional parameters didn’t come at the end.

Default parameters follow short-circuit logic

Remember that if you have an optional parameter with an initializer expression, the expression will not get executed if the calling function had an argument present in that position. The expression only gets executed if the system needed to pad a missing argument.

Note

New in version 0.18.3: Optional Parameters were added as a new feature in kOS 0.18.3

Functions and the terminal interpreter

You cannot call functions from the interpreter interactive command line if they were declared inside of script programs. If you do, you will get seemingly “random” errors. The reasons for this are complex, but the short version is because the memory the script files’ pseudo-machine language instructions live in and the memory the interpreter’s pseudo-machine langauge instructions live in are two different things.

The effect you may see if you attempt this is merely an “Unknown Identifer” error, or worse yet, it may end up jumping into random parts of your code that have nothing to do with the actual function call you’re trying to make.

As a rule of thumb, in kOS 0.17.0, make sure you only use functions from inside script programs. Don’t try to call them interactively from the interpreter prompt. You will get very strange and (seemingly) inexplicable errors.

In the future we may find a way to fix this problem, but for right now, just don’t do it.

Calling a function without parentheses (please don’t)

In some cases it is possible to call a function with the parentheses off, as shown below, but this is not recommended:

function example_function {
  print "hello, this is my example.".
}

example_function. // please don't do this, even if it works.

This is a holdover from the fact that functions and locks are really the same thing, and you need to be able to call a lock without the parentheses for old scripts written prior to kOS version 0.17.0 to continue working.

Omitting parentheses only works in the same file

One reason to avoid the above technique (of leaving the parentheses off) is that it really only works when you try to call a function that was declared in the same file. If you want to call a library function (a function you made for yourself in another file) then it does not work, for complex reason involving the compiler and late-time binding.

LOCAL .. TO

(aka: local variables)

Syntax:

  • DECLARE identifier TO expression dot
  • LOCAL identifier IS expression dot
  • DECLARE LOCAL identifier IS expression dot

The above are all the same, although the version that just says LOCAL identifier IS expr. is preferred.

Examples:

declare x to 5.
local y is 2*x - 1.
declare local halfSpeed to SHIP:VELOCITY:ORBIT:MAG / 2.

If your function needs to make a local variable, it can do so using the DECLARE command. Whenever the DECLARE command is seen inside a function, the compiler assumes the variable is meant to be local to that function’s block. This also works with recursion. If you recursively call a function again and again, there will be new copies stacked up of all the local variables made with DECLARE, but not of the variables implicitly made global without DECLARE.

An example of using local for a local variable can be seen in the example above, where it is used for the row and col variables.

A more in-depth explanation of kerboscript’s scoping rules and how they work is found on another page.

Initializers are now mandatory for the DECLARE statement

This is now illegal syntax:

declare x.  // no initial value for x given.

Warning

New in version 0.17: Breaking Change: The kerboscript from prior versions of kOS did allow you do make declare statements without any initializers in them (and in fact you couldn’t provide an initializer for them in prior versions even if you wanted to).

In order to avoid the issue of having uninitialized variables in kerboscript, any declare statement requires the use of the initializer clause.

This is especially important as kerboscript is a late typing language in which it is impossible for the compiler to choose some implied default initial value for the variable from some language spec. This is because until a value has been assigned into it, the compiler wouldn’t even know what type of default to use - a string, an integer, a floating point number, etc.
Difference between declare and set

You may think that:

local x is 5.

is identical to just not using a declare local statement at all, and just performing set x to 5. alone, but it is not. With declare local (or just declare or just local), a NEW variable called x will be made at the current local scope, temporarily hiding any existing x variables that may otherwise have been reachable in a more global scope. With set, if there already is an x variable you can use in a different scope higher than this scope, it will be used, and only if it doesn’t exist will a new x be made (and that new x will be global, not local).

RETURN

return expression(optional) dot(mandatory)

Examples:

return 3*x.

return.

If your function needs to exit early, and/or if it needs to pass a return value back to the user, you can use the RETURN statement to do so. RETURN accepts an optional argument - the value to pass back to the caller. Note that functions in kerboscript are very weakly typed with late binding. You cannot declare the expected return type for the function, and it’s up to you to ensure that all possible returned values are useful and meaningful.

Example:

// Note, in this example, the keyword 'declare' is
// spelled out explicitly.  You can choose to do so
// if you wish.  It's up to you what you aesthetically
// prefer.

// Calculate what component of a vessel's surface
// velocity is Northward:
declare function north_velocity {
  declare parameter which_vessel.

  return VDOT(which_vessel:velocity:surface, which_vessel:north:vector).
}.

Passing by value

Parameters to user functions in kerboscript are all pass-by-value, with an important caveat. “Pass by value” means that the function is working on a copy of the variable you passed in, rather than the original variable. This matters when the function tries to change the value of the parameter, as in this example:

function embiggen {
  parameter x.

  set x to x + 10.

  print "x has been embiggened to " + x.
}.

set global_val to 30.
print global_val.
embiggen(global_val).
print global_val.

The above example will print:

30
x has been embiggened to 40
30

Although the function added 10 to its OWN copy of the parameter, the caller’s copy of the parameter remained unchanged.

Important exception to passing by value - structures

If the value being sent to the function as its parameter is a complex structure consisting of sub-parts (i.e. if it has suffixes) rather than being a simple single scalar value like a number, then the copy in the function is really a copy of the reference pointing to the object, so changes you make in the object really WILL change it, as shown here:

function half_vector {
  parameter vec. //vector passed in.

  print "full vector is " + vec.

  set vec:x to vec:x/2.
  set vec:y to vec:y/2.
  set vec:z to vec:z/2.

  print "half vector is " + vec.
}.

set global_vec to V(10,20,30).
half_vector(global_vec).
print "afterward, global_vec is now " + global_vec.

This will give the following result:

full vector is v(10,20,30)
half vector is v(5,10,15)
afterward, global_vec is now v(5,10,15)

Because a vector is a suffixed structure, it effectively acts as if it was passed in by reference instead of by value, and so when it was changed in the function, the caller’s original copy is what was being changed.

This may be hard to get used to for new programmers, however experienced programmers who use some modern object-oriented languages will find this behavior very familiar. Only primitives are passed by value. Structures are passed by their reference rather than trying to make a deep copy of the object for the function to use.

This behavior is inherited from the fact that kerboscript is implemented on top of C#, which is one of several OOP languages that work like this.

Nesting functions inside functions

You are allowed to make a local function existing inside another function.

This means that the containing function is the only place the nested function can be called from.

Example:

function getMean {
  parameter aList.

  function getSum {
    parameter aList. // note, this is a local aList MASKING the other one.

    local sum is 0.
    for num in aList {
      set sum to sum + num.
    }.
    return sum.
  }.

  return getSum(aList) / aList:LENGTH.
}.

set L to LIST().
L:ADD(10).
L:ADD(9).
print "mean average is " + getMean(L).

// The following line will give an error because
// getSum is local inside of getMean, and isn't allowed
// to be called from here:
//
print "getSum is " + getSum(L).

Recursion

Recursive algorithms ( http://en.wikipedia.org/wiki/Recursion#In_computer_science ) are possible with kerboscript functions, provided you remember to always exclusively use local variables made with a declare statement in the body of the function, and never use global variables for something that you intended to be different per recursive call.

Anonymous functions

You can make Anonymous functions in kerboscript by simply leaving off the function keyword and the name of the function, and just using the curly braces ({, }) around some statements. When the compiler sees a standalone set of curly braces like this being used in the context of an expression (rather than as a standalone statement), then it will compile the contents of the braces as a function, meaning that the keywords parameter and return will work as expected inside them. Then it will leave a KOSDelegate of the function behind as the value of the expression, which can then be assigned to a variable, or passed as an argument, etc. The full details of what this means, and how to use it, is explained elsewhere.

User Function Gotchas

Calling program’s functions from the interpreter

As explained above, kOS 0.17.0 does not support the calling of a function from the interpreter console and if you attempt it you will get very strange and random errors that you might waste a lot of time trying to track down.

Inconsistent returns

Note that if you sometimes do and sometimes don’t return a value, from the same function, as in the example here:

// A badly designed function, with inconsistency
// in whether or not it returns a value:
//
DECLARE FUNCTION foo {
   DECLARE PARAMETER x.
   IF X < 0 {
     RETURN. // no return value.
   } ELSE {
     RETURN "hello". // a string return value
   }
}

Then the kerboscript compiler is not clever enough to detect this and warn you about it. However, the internal stack will not get corrupted by this error, as some experienced programmers might expect upon hearing this (because secretly all kerboscript user functions return a value of zero if they never gave an explicit return value, so there’s universally always something to pop off the stack even for the empty return statements.)

In general, it’s still a good idea to make sure that if you sometimes return a value from a user function, that you always do so in every path through your function.

Accidentally using globals

It is possible to accidentally create global variables when you didn’t meant to, just because you made a typo.

For example:

function mean {
  parameter the_list.
  local sum is 0.

  for item in the_list {
    set dum to sum + item. // typo - said 'dum' instead of 'sum'.
  }.

  return sum / the_list:length.
}.

The above example contains a typo that causes a global variable to be made where you didn’t mean to. You wanted to say “sum” but said “dum” and instead of that being an error, kerboscript happily said “okay, well since you’re setting a variable name that doesn’t exist yet, I’ll make it for you implicitly” (and it ends up being a global).

When you are writing libraries of code for yourself to call, this can really be annoying. And it’s a very common problem with “sloppy” declaration languages that allow you to use variable names without declaring them first. Most such languages have provided a way to catch the problem, and allow you to instruct the compiler, “Please don’t let me do that. Please force me to declare everything.”

The way that is done in kerboscript is by using a @LAZYGLOBAL compiler directive, as described here.

Had the function above been compiled under a @LAZYGLOBAL off. compiler directive, the typo would be noticed:

@lazyglobal off.

local function mean {
  local parameter the_list.
  local sum is 0.

  for item in the_list {
    set dum to sum + item. // error - 'dum' is an unknown identifier.
  }.

  return sum / the_list:length.
}.

Anonymous Functions (A kind of Delegate)

Note

New in version 1.0.0: The anonymous function feature described on this page did not exist prior to kOS 1.0.0

Overview

There is a certain kind of Delegate in which you don’t need to actually name the function in question, and can just assign it right into a variable as a delegate to begin with, or pass it as a delegate to another function call.

This is referred to as an Anonymous Function.

Syntax

In kerboscript, you can use this ability and it looks like this:

Given any user function like so:

function my_function_name {
  // ---.
  //    |
  //    |---  The body of the function goes here.
  //    |
  // ---'
}

You can make that function into a delegate that can be assigned into a variable, or passed as an argument to another function by simply leaving off the name and the ‘function’ keyword and just using the section in the curly braces by itself, like in this example:

set some_variable to {
  // ---.
  //    |
  //    |---  The body of the function goes here.
  //    |
  // ---'
}.   // <-- Note the period ('.') statement terminator is mandatory
     //     here because this is actually an ordinary SET statement.

// some_variable is now a KOSDelegate of this function.
// It can be called just like delegates can, like so:
some_variable().
// or like so:
some_variable:call().

Passing in to other functions

Where this is often useful is in passing a small function into another function. Here’s an example. Let’s say you have a function that returns a list of all the bodies in your game that fit some criteria that are unspecified until it gets used, like so:

function select_bodies {

  // The parameter is expected to be a
  // delegate you can call on a body, that
  // returns true if the body should be
  // included, or false if it shouldn't:
  parameter should_include.

  local all_bodies is LIST().
  local some_bodies is LIST().

  list BODIES in all_bodies.
  for bod in all_bodies {
    if should_include(bod) {
      some_bodies:ADD(bod).
    }
  }
  return some_bodies.
}

// Example of how it could have been used with a traditional named function:
// -------------------------------------------------------------------------
//
// function is_smaller_than_mun {
//   parameter b. return (b:RADIUS < Mun:RADIUS).
// }
//
// local small_bodies is select_bodies( is_smaller_than_mun@ ).

// But we're going to do the same thing using an anonymous function instead:
// -------------------------------------------------------------------------

local small_bodies is select_bodies( { parameter b. return (b:RADIUS < Mun:RADIUS).} ).



print "List of all bodies smaller than Mun is:".
print small_bodies.

Anywhere a Delegate was expected to be used, you can use an anonymous function in its place instead.

Lexicon of functions

One example of a useful way to use anonymous functions is to create for yourself a collection of delegates:

function make_vessel_utilities {
  parameter ves.

  // Create a lexicon of anonymous functions to use on vessel ves:
  //
  return LEXICON(
      "isSmall", {return ves:mass < 50.},
      "isBig", {return ves:mass > 150.},
      "circularEnough", {return ves:obt:eccentricity < 0.1.}
    ).
}

local that_ship_utils is make_vessel_utilities(Vessel("that ship")).

if that_ship_utils["isSmall"]() {
  print "that ship is small".
}

if that_ship_utils["circularEnough"]() {
  print "that ship is circularized".
}

Although kerboscript isn’t entirely “object oriented”, some kinds of object-oriented ways of thinking can be simulated with techniques like this. Once you have the ability to treat a function as being a piece of data, a lot of possibilities open up.

Delegates (function references)

Note

New in version 0.19.0: The delegate feature described on this page did not exist prior to kOS 0.19.0.

Overview

There are times it would be useful to be able to store, not the result of calling a function, but rather a reference to the function itself without calling it yet. Then you can use this value to call the function later on. Or it would be useful to choose one of several functions you might want to call, store that choice in a variable, so that you can call it multiple times later.

(If you are an experienced programmer, you have probably heard of this feature or a similar feature under one of a number of names, depending on which language you learned it in: “Function pointers”, “Function references”, “Callbacks”, “Delegates”, “Deferred execution”, etc.)

Kerboscript provides this feature with a built-in type called a KOSDelegate, which remembers the details needed to be able to call a function later on.

The topic can be a bit confusing to people new to it, but this video, produced shortly before the release of kOS 0.19.0 by one of the (then new) collaborators of the team may help:

CheersKevin’s explanation of delegates

Note

It’s important to know before going into this explanation, that the feature described here does not work on structure suffixes as of this release of kOS. See the bottom of this page for more details.

Syntax: @ symbol

To obtain a delegate of a function in kOS, you place a single at-sign (@) to the right of the function name, where the parentheses and arguments would normally have gone, as shown below:

// example function:
function myfunc { parameter a,b. return a+b. }

// example delegate of that function.
// Note the at-sign ('@'):
set aaa to myfunc@.

When you do this, you are creating a variable of type KOSDelegate, which can be passed around and copied to other variables, sent as an argument to other functions, and so on.

Then you may call the function later on by using the :call suffix, and giving it the parameters that myfunc would normally have expected, which might look something like this:

print aaa:call(1, 2).

Here’s the full example:

function myfunc {
  parameter a,b.

  return a + b.
}

print myfunc(1, 2). // Prints the number 3, by calling myfunc now.
set aaa to myfunc@. // You don't see any effect just yet from this.
print aaa:call(1, 2).  // Now you see the number 3 printed,
                       // just like calling myfunc directly.
Omitting :CALL

You can call a KOSDelegate without the use of the :call suffix, instead just using parentheses directly abutted against the variable name like a normal function call:

function testfunc {print "test".}
set del to testfunc@.

// The following two are equivalent:

del:call().
del().
Why the ‘@’ sign?

In Kerboscript, often when you mention a function’s name and don’t provide any empty parentheses, if it’s a function that takes zero arguments, it ends up being called anyway. Thus set x to myfunc. ends up doing the same thing as set x to myfunc().. It ends up calling the function right now. This is why you must append the @ (at-sign) symbol to the end of the function name to obtain a delegate of it. It tells the compiler to suppress the normal automatic calling of the function that would have occurred if you had left it bare.

Why?

There are several reasons this feature can be useful. Some experienced programmers will already know them, but here is an example of a useful case as an illustration for people new to programming. Let’s say you wanted to start from a list of numbers, and you wanted to create a subset list of just those numbers which are negative. You might write code to do so like this:

// Just a hodgepodge list of numbers to use as an example:
local numlist is LIST(5, 6, 1, 49.1, 10, -2, 0, -12, 50, 0.3, 1.2, -1, 0).

local result is list().
for num in numlist {
  if num < 0 {
    result:add(num).
  }
}
// Now result is the subset list.

Okay, but then later let’s say you want to do the same thing, but now you want to get the subset which are integers (no fractional component after the decimal point). Then you might do this:

local result is list().
for num in numlist {
  if num = round(num,0) {
    result:add(num).
  }
}
// Now result is the subset list.

Okay, but then later let’s say you want to do the same thing, but now you want to get the subset which are even numbers:

local result is list().
for num in numlist {
  if mod(num,2) = 0 {
    result:add(num).
  }
}
// Now result is the subset list.

So you look at these three cases and think “well, gee, they’re all pretty much the same thing except for what I put in the ‘if’ check. I should probably combine them into one function.” You want to make one function that does essentially this:

function make_sublist {
  parameter
    input_list, // Full list to take a subset of.
    check.      // Condition to look for.

  local result is list().
  for num in input_list {
    if check...TO-DO, how do I do this?? {
      result:add(num).
    }
  }
  return result.
}

But how do you call it telling it what condition to look for? You’re essentially not trying to pass it a value, but you’re trying to pass it some code for it to run.

And that’s what you would use a delegate for. Here’s the full example that passes in a delegate where you tell it what kind of check you want it to do by giving it a function you want it to call for the boolean check:

function make_sublist {
  parameter
    input_list, // Full list to take a subset of.
    check_func. // pass in a delegate that expects 1 number parameter and returns 1 number.

  local result is list().
  for num in input_list {
    if check_func:call(num) {
      result:add(num).
    }
  }
  return result.
}

// Just a hodgepodge list of numbers to use as an example:
local numlist is LIST(5, 6, 1, 49.1, 10, -2, 0, -12, 50, 0.3, 1.2, -1, 0).

function is_neg { parameter n. return (n < 0). }
function is_round { parameter n. return (n = round(n,0)). }
function is_even { parameter n. return (mod(n,2) = 0). }

print "A list of all the negatives:".
print make_sublist(numlist, is_neg@). // note the '@' for a delegate of the function.

print "A list of all the round numbers:".
print make_sublist(numlist, is_round@). // note the '@' for a delegate of the function.

print "A list of all the even numbers:".
print make_sublist(numlist, is_even@). // note the '@' for a delegate of the function.

This technique can be chained together to form very powerful operations on collections and enumerations of data. You can start nesting several of these types of function calls inside each other to perform a result, such as “get the average mass of the subset of the subset of the parts on my vessel that are fuel tanks that have oxidizer in them”. There is a style of programming called Functional programming in which you are meant to try to think this way about all possible problems you are trying to solve. While Kerboscript is mostly an imperative programming language, some limited concepts of functional programming style are possible through the use of these delegates.

Anonymous functions

Kerboscript also allows you to make a Delegate from an Anonymous function (see the link for the full description of the syntax and its use), as in this example below:

set add_func to { parameter a,b. return a+b. }.
// add_func is now a KOSDelegate of the anonymous function.

When using anonymous functions like this, you don’t use the ‘@’ character because an anonymous function already is a Delegate to begin with.

It is technically possible, using anonymous functions, to actually make an entire program library out of delegates rather than normal functions.

lib_enum in KSLib

There is a library in the kslib that can be used to perform many data set enumeration operations like the one described in the above section. It was written to be released coinciding with the addition of this feature to Kerboscript. In addition to being useful as a library, it also can serve as a good list of example cases for how you can use this “delegate” feature in your own code. Please have a look at the lib_enum library in KSLib to see what it has to offer. It allows you to do things such as sorting a LIST() based on whatever comparison criteria you like, finding the minimum or maximum from a list, transforming all items in the list according to a mapping rule, finding the index of the first hit in a list that matches given criteria, and so on.

Advanced topics

Can’t call dead delegates

You might store a KOSDelegate in a global variable. Global varibles continue existing even after all the programs are done and you are back at the terminal interpreter.

This makes it possible to have a (global) variable that contains a KOSDelegate that refers to user program code that no longer exists.

But you can’t actually call that delegate. If you have such a situation, that delegate is referred to as “DEAD” and trying to call it will cause an error. You can test for this with the KOSDelegate:ISDEAD suffix.

Pre-binding arguments with :bind

A KOSDelegate allows you to create another KOSDelegate that has some of its parameters bound to some pre-set values, so you then only need to supply the remaining, unbound values when you call it. This allows you to implement certain types of functional programming styles. This is done using the :bind suffix of KOSDelegate.

Let’s say you have a function you made that draws a vector arrow from one ship to another, in a color of your choice, that looks like so:

function draw_ship_to_ship {
  parameter
    ship1,
    ship2,
    drawColor.

  local vdraw is vecdraw().
  set vdraw:start to ship1:position.
  set vdraw:vec to ship2:position - ship1:position.
  set vdraw:color to drawColor.
  set vdraw:show to true.
  return vdraw.
}

You realize that you’ll be using this a lot with the same two ships over and over. You decide to create a variation of this function that already has the two ships hardcoded to begin with, only asking you for the final color parameter.

You can do that with KOSDelegates, using the :bind suffix of KOSDelegate, as follows:

local draw_delegate is draw_ship_to_ship@.
local draw_a_to_b is draw_delegate:bind(shipA, shipB).

// Then later on you can call it with the first two arguments omitted
// because you pre-loaded them with BIND:

set greenvec to draw_a_to_b(green). // note, only passing 1 arg, the color.
set tanvec to draw_a_to_b( rgb(0.7,0.6,0) ). // note, only passing 1 arg, the color.
set whitevec to draw_a_to_b(white). // note, only passing 1 arg, the color.

Note that you can combine the two lines above that looked like this:

local draw_delegate is draw_ship_to_ship@.
local draw_a_to_b is draw_delegate:bind(shipA, shipB).

into just this:

local draw_a_to_b is draw_ship_to_ship@:bind(shipA, shipB).

When you use the at-sign(@), you are returning an object of type KOSDelegate that can be used in-line right in the expression, as demonstrated above.

Currying

It is possible to shave off exactly one parameter at a time in a chain of these :bind calls. You could do this, for example:

// V() is the built-in function that makes a vector of x, y, and z
// components.  You could bind the values one at a time as follows:
local vecx is V@:bind(10). // vecx is now a KOSDelegate hardcoding x to 10 and taking just y and z args
local vecxy is vecx:bind(5).  // vecxy is a KOSDelegate hardcoding x to 10 and y to 5, taking just the z arg
local vecxyz is vecxy:bind(1).  // vecxyz is a KOSDelegate hardcoding x to 10, y to 5, and z to 1, taking no args.
local vec is vecxyz:call(). // makes a V(10, 5, 1).

// The above chain of bindings could have been chained together on one line like so:
local vec is V@:bind(10):bind(5):bind(1):call().

The technique of transforming a function that takes many arguments into a nested succession of functions that each only take one argument has a name. It’s called Currying. (It’s named after mathematician Haskell Curry and has nothing to do with delicious spicy food).

(If anyone reading this is an experienced functional programmer and is thinking, “But :bind as described here isn’t currying”, yes, we are aware that this is correct. The KOSDelegate suffix :bind is technically not a proper “curry” because it is actually a partial function application. and thus doesn’t require that you limit it to only one parameter at a time.)

Closures

Kerboscript KOSDelegates of user functions do hold their “closure” information inside themselves. What on earth does that mean? If you haven’t heard this term before, it essentially means that the KOSDelegate “remembers” what the local variables were at the location where it was created. It is possible for the KOSDelegate you make of a function to access the local variables that only that function is allowed to see, even if you call that delegate from a “foreign” location where those variables wouldn’t normally be in scope.

Kinds of Delegate (no suffixes)

Under the hood, kOS handles several different kinds of “functions” and methods that aren’t actually implemented the same way. A KOSDelegate attempts to hide the details of these differences from the user, but one difference in particular still stands out. In kOS version 0.19.0, you cannot reliably make a delegate of a suffix just yet. (This is intended as a future feature though. It’s been put off because it involves decisions that impact the future of the language and which, once made, can’t be changed easily.)

  • You can make a delegate of a user function implemented in Kerboscript code.

    function mysquarefunc { parameter a. return a*a. }
    set x to mysquarefunc@.
    set y to x:call(5). // y is now 25.
    
  • You can make a delegate of a built-in function provided by kOS itself, provided it isn’t a structure suffix.

    set r to round@.
    set s to sqrt@.
    print "square root of 7, to the nearest 2 places is: " + r:call(s:call(7), 2).
    
  • You cannot make a delegate of a suffix of a structure (yet?) in Kerboscript.

    //
    // WON'T WORK, WILL GIVE ERROR:
    //
    set altpos to latlng(10,20):altitudeposition@. // altitudeposition is a suffix of geoposition.
    print "altpos at altitude 1000 is " + altpos:call(1000).
    

    However, if you like you can make your own user function that is a wrapper around a structure suffix call, and make a delegate of that.

Mathematics and Basic Geometry

Basic Math Functions

These functions are built-in for performing basic math operations in kOS.

Fundamental Constants

There is a bound variable called CONSTANT which contains some basic fundamental constants about the universe that you may find handy in your math operations.

New in version 0.18: Prior to kOS version 0.18, constant was a function call, and therefore to say constant:pi, you had to say constant():pi. The function call constant() still exists and still works, but the new way without the parentheses is preferred going forward, and the way with the parentheses may become deprecated later. For the moment, both ways of doing it work.

Identifier Description
G Newton’s Gravitational Constant
E Base of the natural log (Euler’s number)
PI \(\pi\)
c Speed of light in a vacuum, in m/s.
AtmToKPa Conversion constant: Atmospheres to kiloPascals.
KPaToAtm Conversion constant: kiloPascals to Atmospheres.
DegToRad Conversion constant: Degrees to Radians.
RadToDeg Conversion constant: Radians to Degrees.
Constant:G

Newton’s Gravitational Constant, 6.67384E-11:

PRINT "Gravitational parameter of Kerbin is:".
PRINT constant:G * Kerbin:Mass.
Constant:E

Natural Log base “e”:

PRINT "e^2 is:".
PRINT constant:e ^ 2.
Constant:PI

Ratio of circumference of a circle to its diameter, 3.14159265...

SET diameter to 10.
PRINT "circumference is:".
PRINT constant:pi * diameter.
Constant:C

Speed of light in a vacuum, in meters per second.

SET speed to SHIP:VELOCITY:ORBIT:MAG.
SET percentOfLight to (speed / constant:c) * 100.
PRINT "We're going " + percentOfLight + "% of lightspeed!".

Note

In Kerbal Space Program, all physics motion is purely Newtonian. You can go faster than the speed of light provided you have enough delta-V, and no time dilation effects will occur. The universe will behave entirely linearly even at speeds near c.

This constant is provided mainly for the benefit of people who are playing with the mod “RemoteTech” installed, who may want to perform calculations about signal delays to hypothetical probes. (Note that if the probe already has a connection, you can ask Remotetech directly what the signal delay is.

Constant:AtmToKPa

A conversion constant.

If you have a pressure measurement expressed in atmospheres of pressure, you can multiply it by this to get the equivalent in kiloPascals (kiloNewtons per square meter).

PRINT "1 atm is:".
PRINT 1 * constant:AtmToKPa + " kPa.".
Constant:KPaToATM

A conversion constant.

If you have a pressure measurement expressed in kiloPascals (kiloNewtons per square meter), you can multiply it by this to get the equivalent in atmospheres.

PRINT "100 kPa is:".
PRINT 100 * constant:KPaToATM + " atmospheres".
Constant:DegToRad

A conversion constant.

If you have an angle measured in degrees, you can multiply it by this to get the equivalent measure in radians. It is exactly the same thing as saying constant:pi / 180, except the result is pre-recorded as a constant number and thus no division is performed at runtime.

PRINT "A right angle is:".
PRINT 90 * constant:DegToRad + " radians".
Constant:RadToDeg

A conversion constant.

If you have an angle measured in radians, you can multiply it by this to get the equivalent measure in degrees. It is exactly the same thing as saying 180 / constant:pi, except the result is pre-recorded as a constant number and thus no division is performed at runtime.

PRINT "A radian is:".
PRINT 1 * constant:RadToDeg + " degrees".

Mathematical Functions

Function Description
ABS(a) absolute value
CEILING(a) round up
FLOOR(a) round down
LN(a) natural log
LOG10(a) log base 10
MOD(a,b) modulus
MIN(a,b) minimum
MAX(a,b) maximum
RANDOM() random number
ROUND(a) round to whole number
ROUND(a,b) round to nearest place
SQRT(a) square root
CHAR(a) character from unicode
UNCHAR(a) unicode from character
ABS(a)

Returns absolute value of input:

PRINT ABS(-1). // prints 1
CEILING(a)

Rounds up to the nearest whole number:

PRINT CEILING(1.887). // prints 2
FLOOR(a)

Rounds down to the nearest whole number:

PRINT FLOOR(1.887). // prints 1
LN(a)

Gives the natural log of the provided number:

PRINT LN(2). // prints 0.6931471805599453
LOG10(a)

Gives the log base 10 of the provided number:

PRINT LOG10(2). // prints 0.30102999566398114
MOD(a,b)

Returns remainder from integer division. Keep in mind that it’s not a traditional mathematical Euclidean division where the result is always positive. The result has the same absolute value as mathematical modulo operation but the sign is the same as the sign of dividend:

PRINT MOD(21,6). // prints 3
PRINT MOD(-21,6). // prints -3
MIN(a,b)

Returns The lower of the two values:

PRINT MIN(0,100). // prints 0
MAX(a,b)

Returns The higher of the two values:

PRINT MAX(0,100). // prints 100
RANDOM()

Returns a random floating point number in the range [0,1]:

PRINT RANDOM(). //prints a random number
ROUND(a)

Rounds to the nearest whole number:

PRINT ROUND(1.887). // prints 2
ROUND(a,b)

Rounds to the nearest place value:

PRINT ROUND(1.887,2). // prints 1.89
SQRT(a)

Returns square root:

PRINT SQRT(7.89). // prints 2.80891438103763
CHAR(a)
Parameters:
  • a – (number)
Returns:

(string) single-character string containing the unicode character specified

PRINT CHAR(34) + "Apples" + CHAR(34). // prints "Apples"
UNCHAR(a)
Parameters:
  • a – (string)
Returns:

(number) unicode number representing the character specified

PRINT UNCHAR("A"). // prints 65
Trigonometric Functions
Function
SIN(a)
COS(a)
TAN(a)
ARCSIN(x)
ARCCOS(x)
ARCTAN(x)
ARCTAN2(y,x)
SIN(a)
Parameters:
  • a – (deg) angle
Returns:

sine of the angle

PRINT SIN(6). // prints 0.10452846326
COS(a)
Parameters:
  • a – (deg) angle
Returns:

cosine of the angle

PRINT COS(6). // prints 0.99452189536
TAN(a)
Parameters:
  • a – (deg) angle
Returns:

tangent of the angle

PRINT TAN(6). // prints 0.10510423526
ARCSIN(x)
Parameters:
Returns:

(deg) angle whose sine is x

PRINT ARCSIN(0.67). // prints 42.0670648
ARCCOS(x)
Parameters:
Returns:

(deg) angle whose cosine is x

PRINT ARCCOS(0.67). // prints 47.9329352
ARCTAN(x)
Parameters:
Returns:

(deg) angle whose tangent is x

PRINT ARCTAN(0.67). // prints 33.8220852
ARCTAN2(y,x)
Parameters:
Returns:

(deg) angle whose tangent is \(\frac{y}{x}\)

PRINT ARCTAN2(0.67, 0.89). // prints 36.9727625

The two parameters resolve ambiguities when taking the arctangent. See the wikipedia page about atan2 for more details.

Scalar

structure Scalar
Members
(Scalar values have no suffixes other than the ones inherited from Structure.)

A Scalar value is the kind the system gives you whenever you are working with a number of some sort which, unlike with a Vector, does not have a pointing orientation in 3D space.

In other words, it’s just a number. A plain, no-frills, ordinary number.

Experienced programmers will be aware of the concept of there being different kinds of number depending on what you want to do with it. There’s “integer” versus “floating point” versus “fixed point”, and theres single-precision, double-precision and so on.

kOS tries to be friendly to the new person just playing around with simple programming without a lot of expertise, and to that end, the difference between these types is abstracted away as much as possible.

Scalar Syntax

Scalar numbers are allowed to be represented in any number of ways, both in the source code and in strings passed in to the String:TOSCALAR method.

Underscores are allowed as visual spacers provided you don’t lead with an underscore. The purpose of the underscores is to group numbers apart, similar to how some cultures use a comma in numbers like ‘1,234,567’ (and others use a dot). There is no enforcement of rules for where you can and cannot put the underscores for grouping. kerboscript just strips them out and ignores them anyway:

// These are all the same number, shown different ways:
1234567
1_234_567
1_2__3456_7

One decimal point is allowed to show fractional parts, but you must lead with a digit, even it’s just to say “0.”:

1234
12.34
.1234
0.1234
0.123_4 // underscore ignored.

You may use scientific notation (with an ‘e’ and an optional sign, and a string of digits) to shift the decimal place:

123.4e4   // = 1234000
1.234e+4  // = 12340
1.234e-14  // = 0.00000000000001234
123_456.78e-4  // = 12.345678  (note, again, the ignored underscore)

Operators

The following basic arithmetic operators are defined when both a and b are scalars:

a ^ b exponent: a to the power b
-a negative of a
a * b a / b muiltiply or divide two numbers
a + b a - b add or subtract two numbers

The order of operations is in the order of the table listing above. For example, multiplication and division happen before subtraction and addition.

Scientific Notation

You can specify a number using scientific notation using the letter e, as shown:

set x to 1.23e5.
print x.  // prints 123000
set x to 1.23e-5.
print x.  // prints 0.0000123

Limitations of Scalars

The implementation of Scalars can currently only store values that fit the following criteria:

The value is a real number

Or “What the heck does ‘attempted to push NaN onto the stack’ mean?”.

kOS does not have a numeric type designed to deal with imaginary numbers or complex numbers. Therefore, for example, if you attempted to perform sqrt(-4), you would get a “NaN error”, rather than the imaginary number 2i. “NaN” stands for “Not a Number” and it means the system is incapable of storing the correct answer. Another example of where you will get a “NaN error” is if you attempt to perform arcsin(1.01), since there is no such thing as the angle that gives a sine of 1.01.

The value must be a rational number

When you ask kOS to tell you constant:pi, you are technically not getting the actual correct value. Instead you are getting a rational number approximation that is accurate to about 15 decimal places. In kOS, scalar values cannot store irrational numbers.

The larger the magnitude, the less the precision

For example, while it is possible to store exactly the number 99.001, it is not possible to store exactly the number 999999999999999.001, even though both numbers are only precise up to the thousandths place.

If you attempt to set x to 999999999999999.001. and then print x., you’ll find that the value you get back has been rounded off a bit.

In a nutshell, what really matters is how many significant digits there are, not how many places after the decimal point. You can’t have more than roughly 15 significant decimal digits. (It’s not exactly 15 because of differences between binary and decimal counting, but that gives you a rough estimate.)

Vectors

Creation

V(x,y,z)
Parameters:
  • x – (scalar) \(x\) coordinate
  • y – (scalar) \(y\) coordinate
  • z – (scalar) \(z\) coordinate
Returns:

Vector

This creates a new vector from 3 components in \((x,y,z)\):

SET vec TO V(x,y,z).

Here, a new Vector called vec is created . The object Vector represents a three-dimensional euclidean vector To deeply understand most vectors in kOS, you have to understand a bit about the underlying coordinate system of KSP. If you are having trouble making sense of the direction the axes point in, go read that page.

Note

Remember that the XYZ grid in Kerbal Space Program uses a left-handed coordinate system.

Structure

structure Vector
Members
Suffix Type Get Set
X scalar yes yes
Y scalar yes yes
Z scalar yes yes
MAG scalar yes yes
NORMALIZED Vector yes no
SQRMAGNITUDE scalar yes no
DIRECTION Direction yes yes
VEC Vector yes no

Note

This type is serializable.

Vector:X
Type:scalar
Access:Get/Set

The \(x\) component of the vector.

Vector:Y
Type:scalar
Access:Get/Set

The \(y\) component of the vector.

Vector:Z
Type:scalar
Access:Get/Set

The \(z\) component of the vector.

Vector:MAG
Type:scalar
Access:Get/Set

The magnitude of the vector, as a scalar number, by the Pythagorean Theorem.

Vector:NORMALIZED
Type:Vector
Access:Get only

This creates a unit vector pointing in the same direction as this vector. This is the same effect as multiplying the vector by the scalar 1 / vec:MAG.

Vector:SQRMAGNITUDE
Type:scalar
Access:Get only

The magnitude of the vector, squared. Use instead of vec:MAG^2 if you need to square of the magnitude as this skips the step in the Pythagorean formula where you take the square root in the first place. Taking the square root and then squaring that would introduce floating point error needlessly.

Vector:DIRECTION
Type:Direction
Access:Get/Set
GET:
The vector rendered into a Direction (see note in the Directions documentation about information loss when doing this).
SET:
Tells the vector to keep its magnitude as it is but point in a new direction, adjusting its \((x,y,z)\) numbers accordingly.
Vector:VEC
Type:Vector
Access:Get only

This is a suffix that creates a COPY of this vector. Useful if you want to copy a vector and then change the copy. Normally if you SET v2 TO v1, then v1 and v2 are two names for the same vector and changing one would change the other.

Operations and Methods

Method / Operator Return Type
* (asterisk) scalar or Vector
+ (plus) Vector
- (minus) Vector
- (unary) Vector
VDOT, VECTORDOTPRODUCT, * (asterisk) scalar
VCRS, VECTORCROSSPRODUCT Vector
VANG, VECTORANGLE scalar (deg)
VXCL, VECTOREXCLUDE Vector
*

Scalar multiplication or dot product of two Vectors. See also VECTORDOTPRODUCT:

SET a TO 2.
SET vec1 TO V(1,2,3).
SET vec2 TO V(2,3,4).
PRINT a * vec1.     // prints: V(2,4,6)
PRINT vec1 * vec2.  // prints: 20

Note that the unary minus operator is really a multiplication of the vector by a scalar of (-1):

PRINT -vec1.     // these two both print the
PRINT (-1)*vec1. // exact same thing.
+, -

Vector addition and subtraction by a scalar or another Vector:

SET a TO 2.
SET vec1 TO V(1,2,3).
SET vec2 TO V(2,3,4).
PRINT vec1 + vec2.  // prints: V(3,5,7)
PRINT vec2 - vec1.  // prints: V(1,1,1)

Note that the unary minus operator is the same thing as multiplying the vector by a scalar of (-1), and is not technically an addition or subtraction operator:

PRINT -vec1.     // these two both print the
PRINT (-1)*vec1. // exact same thing.
VDOT(v1,v2)

Same as VECTORDOTPRODUCT(v1,v2) and v1 * v2.

VECTORDOTPRODUCT(v1,v2)
Parameters:
Returns:

The vector dot-product

Return type:

scalar

This is the dot product of two vectors returning a scalar number. This is the same as v1 * v2:

SET vec1 TO V(1,2,3).
SET vec2 TO V(2,3,4).

// These will all print the value: 20
PRINT vec1 * vec2.
PRINT VDOT(vec1, vec2).
PRINT VECTORDOTPRODUCT(vec1, vec2).
VCRS(v1,v2)

Same as VECTORCROSSPRODUCT(v1,v2)

VECTORCROSSPRODUCT(v1,v2)
Parameters:
Returns:

The vector cross-product

Return type:

Vector

The vector cross product of two vectors in the order (v1,v2) returning a new Vector:

SET vec1 TO V(1,2,3).
SET vec2 TO V(2,3,4).

// These will both print: V(-1,2,-1)
PRINT VCRS(vec1, vec2).
PRINT VECTORCROSSPRODUCT(vec1, vec2).

When visualizing the direction that a vector cross product will point, remember that KSP is using a left-handed coordinate system, and this means a cross-product of two vectors will point in the opposite direction of what it would had KSP been using a right-handed coordinate system.

VANG(v1,v2)

Same as VECTORANGLE(v1,v2).

VECTORANGLE(v1,v2)
Parameters:
Returns:

Angle between two vectors

Return type:

scalar

This returns the angle between v1 and v2. It is the same result as:

\[\arccos\left( \frac{ \vec{v_1}\cdot\vec{v_2} }{ \left|\vec{v_1}\right|\cdot\left|\vec{v_2}\right| } \right)\]

or in KerboScript:

arccos( (VDOT(v1,v2) / (v1:MAG * v2:MAG) ) )
VXCL(v1,v2)

Same as VECTOREXCLUDE(v1,v2)

VECTOREXCLUDE(v1,v2)

This is a vector, v2 with all of v1 excluded from it. In other words, the projection of v2 onto the plane that is normal to v1.

Some examples of using the Vector object:

// initializes a vector with x=100, y=5, z=0
SET varname TO V(100,5,0).

varname:X.    // Returns 100.
V(100,5,0):Y. // Returns 5.
V(100,5,0):Z. // Returns 0.

// Returns the magnitude of the vector
varname:MAG.

// Changes x coordinate value to 111.
SET varname:X TO 111.

// Lengthen or shorten vector to make its magnitude 10.
SET varname:MAG to 10.

// get vector pointing opposite to surface velocity.
SET retroSurf to (-1)*velocity:surface.

// use cross product to find normal to the orbit plane.
SET norm to VCRS(velocity:orbit, ship:body:position).

Directions

Direction objects represent a rotation starting from an initial point in KSP‘s coordinate system where the initial state was looking down the \(+z\) axis, with the camera “up” being the \(+y\) axis. This exists primarily to enable automated steering.

In your thinking, you can largely think of Directions as being Rotations and Rotations as being Directions. The two concepts can be used interchangeably. Used on its own to steer by, a rotation from the default XYZ axes of the universe into a new rotation does in fact provide an absolute direction, thus the name Direction for these objects even though in reality they are just Rotations. It’s important to know that Directions are just rotations because you can use them to modify other directions or vectors.

Note

When dealing with Directions (which are Rotations) in kOS, it is important to remember that KSP uses a left-handed coordinate system. This affects the convention of which rotation direction is positive when calculating angles.

Creation

Method Description
R(pitch,yaw,roll) Euler rotation
Q(x,y,z,rot) Quaternion
HEADING(dir,pitch) Compass heading
LOOKDIRUP(lookAt,lookUp) Looking along vector lookAt, rolled so that lookUp is upward.
ANGLEAXIS(degrees,axisVector) A rotation that would rotate the universe around an axis
ROTATEFROMTO(fromVec,toVec) A rotation that would go from vectors fromVec to toVec
FACING From SHIP or TARGET
UP From SHIP
PROGRADE, etc. From SHIP, TARGET or BODY
R(pitch,yaw,roll)

A Direction can be created out of a Euler Rotation, indicated with the R() function, as shown below where the pitch, yaw and roll values are in degrees:

SET myDir TO R( a, b, c ).
Q(x,y,z,rot)

A Direction can also be created out of a Quaternion tuple, indicated with the Q() function, passing it the x, y, z, w values of the Quaternion. The concept of a Quaternion uses complex numbers and is beyond the scope of the kOS documentation, which is meant to be simple to understand. It is best to not use the Q() function unless Quaternions are something you already understand.

SET myDir TO Q( x, y, z, w ).
HEADING(dir,pitch)

A Direction can be created out of a HEADING() function. The first parameter is the compass heading, and the second parameter is the pitch above the horizon:

SET myDir TO HEADING(degreesFromNorth, pitchAboveHorizon).
LOOKDIRUP(lookAt,lookUp)

A Direction can be created with the LOOKDIRUP function by using two vectors. This is like converting a vector to a direction directly, except that it also provides roll information, which a single vector lacks. lookAt is a vector describing the Direction’s FORE orientation (its local Z axis), and lookUp is a vector describing the direction’s TOP orientation (its local Y axis). Note that lookAt and lookUp need not actually be perpendicualr to each other - they just need to be non-parallel in some way. When they are not perpendicular, then a vector resulting from projecting lookUp into the plane that is normal to lookAt will be used as the effective lookUp instead:

// Aim up the SOI's north axis (V(0,1,0)), rolling the roof to point to the sun.
LOCK STEERING TO LOOKDIRUP( V(0,1,0), SUN:POSITION ).
//
// A direction that aims normal to orbit, with the roof pointed down toward the planet:
LOCK normVec to VCRS(SHIP:BODY:POSITION,SHIP:VELOCITY:ORBIT).  // Cross-product these for a normal vector
LOCK STEERING TO LOOKDIRUP( normVec, SHIP:BODY:POSITION).
ANGLEAXIS(degrees,axisVector)

A Direction can be created with the ANGLEAXIS function. It represents a rotation of degrees around an axis of axisVector. To know which way a positive or negative number of degrees rotates, remember this is a left-handed coordinate system:

// Pick a new rotation that is pitched 30 degrees from the current one, taking into account
// the ship's current orientation to decide which direction is the 'pitch' rotation:
//
SET pitchUp30 to ANGLEAXIS(-30,SHIP:STARFACING).
SET newDir to pitchUp30*SHIP:FACING.
LOCK STEERING TO newDir.

Note

The fact that KSP is using a left-handed coordinate system is important to keep in mind when visualizing the meaning of an ANGLEAXIS function call. It affects which direction is positive when calculating angles.

ROTATEFROMTO(fromVec,toVec)

A Direction can be created with the ROTATEFROMTO function. It is one of the infinite number of rotations that could rotate vector fromVec to become vector toVec (or at least pointing in the same direction as toVec, since fromVec and toVec need not be the same magnitude). Note the use of the phrase “infinite number of”. Because there’s no guarantee about the roll information, there are an infinite number of rotations that could qualify as getting you from one vector to another, because there’s an infinite number of roll angles that could result and all still fit the requirement:

SET myDir to ROTATEFROMTO( v1, v2 ).
Suffix terms from other structures

A Direction can be made from many suffix terms of other structures, as shown below:

SET myDir TO SHIP:FACING.
SET myDir TO TARGET:FACING.
SET myDir TO SHIP:UP.

Whenever a Direction is printed, it always comes out showing its Euler Rotation, regardless of how it was created:

// Initializes a direction to prograde
// plus a relative pitch of 90
SET X TO SHIP:PROGRADE + R(90,0,0).

// Steer the vessel in the direction
// suggested by direction X.
LOCK STEERING TO X.

// Create a rotation facing northeast,
// 10 degrees above horizon
SET Y TO HEADING(45, 10).

// Steer the vessel in the direction
// suggested by direction X.
LOCK STEERING TO Y.

// Set by a rotation in degrees
SET Direction TO R(0,90,0).

Structure

structure Direction

The suffixes of a Direction cannot be altered, so to get a new Direction you must construct a new one.

Suffix Type Description
PITCH scalar (deg) Rotation around \(x\) axis
YAW scalar (deg) Rotation around \(y\) axis
ROLL scalar (deg) Rotation around \(z\) axis
FOREVECTOR Vector This Direction’s forward vector (z axis after rotation).
VECTOR Vector Alias synonym for FOREVECTOR
TOPVECTOR Vector This Direction’s top vector (y axis after rotation).
UPVECTOR Vector Alias synonym for TOPVECTOR
STARVECTOR Vector This Direction’s starboard vector (z axis after rotation).
RIGHTVECTOR Vector Alias synonym for STARVECTOR
INVERSE Direction The inverse of this direction.
- (unary minus) Direction Using the negation operator - on a Direction does the same thing as using the :INVERSE suffix on it.

The Direction object exists primarily to enable automated steering. You can initialize a Direction using a Vector or a Rotation. Direction objects represent a rotation starting from an initial point in KSP‘s coordinate system where the initial state was looking down the \(+z\) axis, with the camera “up” being the \(+y\) axis. So for example, a Direction pointing along the \(x\) axis might be represented as R(0,90,0), meaning the initial \(z\)-axis direction was rotated 90 degrees around the \(y\) axis.

If you are going to manipulate directions a lot, it’s important to note that the order in which the rotations occur is:

  1. First rotate around \(z\) axis.
  2. Then rotate around \(x\) axis.
  3. Then rotate around \(y\) axis.

What this means is that if you try to ROLL and YAW in the same tuple, like so: R(0,45,45), you’ll end up rolling first and then yawing, which might not be what you expected. There is little that can be done to change this as it’s the native way things are represented in the underlying Unity engine.

Also, if you are going to manipulate directions a lot, it’s important to note how KSP‘s native coordinate system works.

Direction:PITCH
Type:Scalar (deg)
Access:Get only

Rotation around the \(x\) axis.

Direction:YAW
Type:Scalar (deg)
Access:Get only

Rotation around the \(y\) axis.

Direction:ROLL
Type:Scalar (deg)
Access:Get only

Rotation around the \(z\) axis.

Direction:FOREVECTOR
Type:Vector
Access:Get only

Vector of length 1 that is in the same direction as the “look-at” of this Direction. Note that it is the same meaning as “what the Z axis of the universe would be rotated to if this rotation was applied to the basis axes of the universe”. When you LOCK STEERING to a direction, that direction’s FOREVECTOR is the vector the nose of the ship will orient to. SHIP:FACING:FOREVECTOR is the way the ship’s nose is aimed right now.

Direction:TOPVECTOR
Type:Vector
Access:Get only

Vector of length 1 that is in the same direction as the “look-up” of this Direction. Note that it is the same meaning as “what the Y axis of the universe would be rotated to if this rotation was applied to the basis axes of the universe”. When you LOCK STEERING to a direction, that direction’s TOPVECTOR is the vector the roof of the ship will orient to. SHIP:FACING:TOPVECTOR is the way the ship’s roof is aimed right now.

Direction:STARVECTOR
Type:Vector
Access:Get only

Vector of length 1 that is in the same direction as the “starboard side” of this Direction. Note that it is the same meaning as “what the X axis of the universe would be rotated to if this rotation was applied to the basis axes of the universe”. When you LOCK STEERING to a direction, that direction’s STARVECTOR is the vector the right wing of the ship will orient to. SHIP:FACING:STARVECTOR is the way the ship’s right wing is aimed right now.

Direction:INVERSE
Type:Direction
Access:Get only
Struct:Gives a Direction with the opposite rotation around its axes.

Operations and Methods

You can use math operations on Direction objects as well. The next example uses a rotation of “UP” which is a system variable describing a vector directly away from the celestial body you are under the influence of:

Supported Direction Operators:

Direction Multiplied by Direction:
 

Dir1 * Dir2 - This operator returns the result of rotating Dir2 by the rotation of Dir1. Note that the order of operations matters here. Dir1*Dir2 is not the same as Dir2*Dir1. Example:

// A direction pointing along compass heading 330, by rotating NORTH by 30 degrees around UP axis:
SET newDir TO ANGLEAXIS(30,SHIP:UP) * NORTH.
Direction Multiplied by Vector:
 

Dir * Vec - This operator returns the result of rotating the vector by Dir:

// What would the velocity of your ship be if it was angled 20 degrees to your left?
SET Vel to ANGLEAXIS(-20,SHIP:TOPVECTOR) * SHIP:VELOCITY:ORBIT.
// At this point Vel:MAG and SHIP:VELOCITY:MAG should be the same, but they don't point the same way
Direction Added to Direction:
 

Dir1 + Dir2 - This operator is less reliable because its exact behavior depends on the order of operations of the UnityEngine’s X Y and Z axis rotations, and it can result in gimbal lock.

It’s supposed to perform a Euler rotation of one direction by another, but it’s preferred to use Dir*Dir instead, as that doesn’t experience gimbal lock, and does not require that you know the exact transformation order of Unity.

For vector operations, you may use the :VECTOR suffix in combination with the regular vector methods:

SET dir TO SHIP:UP.
SET newdir TO VCRS(SHIP:PROGRADE:VECTOR, dir:VECTOR)

The Difference Between Vectors and Directions

There are some consequences when converting from a Direction to a Vector and vice versa which should not be overlooked.

A Vector and a Direction can be represented with the exact same amount of information: a tuple of 3 floating point numbers. So you might wonder why it is that a Vector can hold information about the magnitude of the line segment, while a Direction cannot, given that both have the same amount of information. The answer is that a Direction does contain one thing a Vector does not. A Direction knows which way is “up”, while a Vector does not. If you tell kOS to LOCK STEERING to a Vector, it will be able to point the nose of the vessel in the correct direction, but won’t know which way you want the roof of the craft rotated to. This works fine for axial symmetrical rockets but can be a problem for airplanes.

Therefore if you do this:

SET MyVec to V(100,200,300).
SET MyDir to MyVec:DIRECTION.

Then MyDir will be a Direction, but it will be a Direction where you have no control over which way is “up” for it.

Geographic Coordinates

The GeoCoordinates object (also LATLNG) represents a latitude and longitude pair, which is a location on the surface of a Body.

Creation

LATLNG(lat,lng)
Parameters:
  • lat – (deg) Latitude
  • lng – (deg) Longitude
Returns:

GeoCoordinates

This function creates a GeoCoordinates object with the given latitude and longitude, assuming the current SHIP’s Body is the body to make it for.

Once created it can’t be changed. The GeoCoordinates:LAT and GeoCoordinates:LNG suffixes are get-only (they cannot be set.) To switch to a new location, make a new call to LATLNG().

If you wish to create a GeoCoordinates object for a latitude and longitude around a different body than the ship’s current sphere of influence body, see Body:GEOPOSITIONLATLNG for a means to do that.

It is also possible to obtain a GeoCoordinates from some suffixes of some other structures. For example:

SET spot to SHIP:GEOPOSITION.

Structure

structure GeoCoordinates
Suffix Type Args Description
LAT scalar (deg) none Latitude
LNG scalar (deg) none Longitude
DISTANCE scalar (m) none distance from CPU Vessel
TERRAINHEIGHT scalar (m) none above or below sea level
HEADING scalar (deg) none absolute heading from CPU Vessel
BEARING scalar (deg) none relative direction from CPU Vessel
POSITION Vector (3D Ship-Raw coords) none Position of the surface point.
ALTITUDEPOSITION Vector (3D Ship-Raw coords) scalar (altitude above sea level) Position of a point above (or below) the surface point, by giving the altitude number.
VELOCITY OrbitableVelocity none Velocity of the surface at this point (due to the rotation of the planet/moon).
ALTITUDEVELOCITY OrbitableVelocity scalar (altitude above sea level) Velocity of a point above (or below) the surface point, by giving the altitude number.

Note

This type is serializable.

GeoCoordinates:LAT

The latitude of this position on the surface.

GeoCoordinates:LNG

The longitude of this position on the surface.

GeoCoordinates:DISTANCE

Distance from the CPU_Vessel to this point on the surface.

GeoCoordinates:TERRAINHEIGHT

Distance of the terrain above “sea level” at this geographical position. Negative numbers are below “sea level.”

GeoCoordinates:HEADING

The absolute compass direction from the CPU_Vessel to this point on the surface.

GeoCoordinates:BEARING

The relative compass direction from the CPU_Vessel to this point on the surface. For example, if the vessel is heading at compass heading 45, and the geo-coordinates location is at heading 30, then GeoCoordinates:BEARING will return -15.

GeoCoordinates:POSITION

The ship-raw 3D position on the surface of the body, relative to the current ship’s Center of mass.

GeoCoordinates:ALTITUDEPOSITION

The ship-raw 3D position above or below the surface of the body, relative to the current ship’s Center of mass. You pass in an altitude number for the altitude above “sea” level of the desired location.

GeoCoordinates:VELOCITY

The (linear) velocity of this spot on the surface of the planet/moon, due to the rotation of the body causing that spot to move though space. (For example, on Kerbin at a sea level location, it would be 174.95 m/s eastward, and slightly more at higher terrain spots above sea level.) Note that this is returned as an OrbitableVelocity, meaning it isn’t a vector but a pair of vectors, one called :orbit and one called :surface. Note that the surface-relative velocity you get from the :surface suffix isn’t always zero like you might intuit because :surface gives you the velocity relative to the surface reference frame where SHIP is, which might not be the same latitude/longitude/altitude as where this Geocoordinates is.

GeoCoordinates:ALTITUDEVELOCITY

This is the same as GeoCoordinates:VELOCITY, except that it lets you specify some altitude other than the surface terrain height. You specify a (sea-level) altitude, and it will calculate based on a point at that altitude which may be above or below the actual surface at this latitude and longitude. It will calculate as if you had some point fixed to the ground, like an imaginary tower bolted to the surface, but not at the ground’s altitude. (The body’s rotation will impart a larger magnitude linear velocity on a locaton affixed to the body the farther that location is from the body’s center).

Example Usage

SET spot TO LATLNG(10, 20).     // Initialize point at latitude 10,
                                // longitude 20

PRINT spot:LAT.                 // Print 10
PRINT spot:LNG.                 // Print 20

PRINT spot:DISTANCE.            // Print distance from vessel to x
PRINT spot:HEADING.             // Print the heading to the point
PRINT spot:BEARING.             // Print the heading to the point
                                // relative to vessel heading

SET spot TO SHIP:GEOPOSITION.   // Make spot into a location on the
                                // surface directly underneath the
                                // current ship

SET spot TO LATLNG(spot:LAT,spot:LNG+5). // Make spot into a new
                                         // location 5 degrees east
                                         // of the old one

// Point nose of ship at a spot 100,000 meters altitude above a
// particular known latitude of 50 east, 20.2 north:
LOCK STEERING TO LATLNG(50,20.2):ALTITUDEPOSITION(100000).

// A nice complex example:
// -------------------------
// Drawing an debug arrow in 3D space at the spot where the GeoCoordinate
// "spot" is:
// It starts at a position 100m above the ground altitude and is aimed down
// at the spot on the ground:
SET VD TO VECDRAWARGS(
              spot:ALTITUDEPOSITION(spot:TERRAINHEIGHT+100),
              spot:POSITION - spot:ALTITUDEPOSITION(TERRAINHEIGHT+100),
              red, "THIS IS THE SPOT", 1, true).

PRINT "THESE TWO NUMBERS SHOULD BE THE SAME:".
PRINT (SHIP:ALTITIUDE - SHIP:GEOPOSITION:TERRAINHEIGHT).
PRINT ALT:RADAR.

Reference Frames

This page describes the \((x,y,z)\) reference frame used for most of kOS‘s vectors. kOS inherits its reference frame mostly from the base Kerbal Space Program game itself. The coordinate system of Kerbal Space Program does some strange things that don’t make a lot of sense at first.

Note

Be aware that Kerbal Space program (and in fact many of the games based on the Unity game engine) uses a LEFT-handed coordinate system. kOS inherits this behavior from KSP.

In all the reference frames mentioned below, the orientation of the axes is left-handed. What does that mean? If you open your left palm and point your fingers along the x-axis, then curl your fingers in the direction of the y-axis and stick out your thumb, your thumb will be pointing along the z-axis. (If you do those steps with your right hand, you will get a z-axis in the opposite direction and that is known as a right handed coordinate system).

This is an important thing to keep in mind, as most mathematics and physics textbooks tend to draw examples using a right handed coordinate system, and most students become familiar with that convention first. But for a variety of reasons, many computer graphics systems have a tradition of using left-handed systems instead, and Kerbal Space Program is one of them.

SHIP-RAW

The name of the reference frame in which the origin point is CPU Vessel (SHIP), and the rotation is identical to KSP‘s native raw coordinate grid.

SOI-RAW

The name of the reference frame in which the origin point is the center of the SOI body, and the rotation is identical to KSP‘s native raw coordinate grid.

RAW-RAW

The name of the reference frame in which both the origin point and the rotation of the axes is identical to KSP‘s native raw coordinate grid. This is never exposed to the KerbalScript program, because the origin point is meaningless to work with.

Note

It is hoped that this may be expanded in the future, and conversion routines provided to let people pick a reference frame that makes sense depending on what the script is trying to do. At the moment the only reference frames used are SHIP-RAW and SOI-RAW, as they match closely to what KSP is using internally.

Raw Orientation

_images/KSP_body_coords.png

The Y axis of KSP is the only consistent thing. Imagine a ray starting in the center of the SOI body and pointing upward out through the north pole. That is the direction of the Y axis. (If you move to the SOI of a body with an inclined spin, presumably it will also change the angle of the Y axis to point in the new direction of the body’s spin axis).

The X and Z axes of the coordinate grid are then consequently aligned with the equator plane of the SOI body, 90 degrees to each other. KSP uses a left-handed coordinate system, so the Z axis will always be rotated 90 degrees to the east of the X axis.

_images/KSP_body_latlong.png

However, it’s hard to predict exactly where the X and Z axes will be. They keep moving depending on where you are, to the point where it’s impossible to get a fix on just which direction they’ll point.

Origin Position

The origin position of the \((x,y,z)\) coordinate grid in KSP is also a bit messy. It’s usually near but not exactly on the current ship. kOS performs some conversions for you to make this a bit simpler and keep everything consistent.

Regardless of where the origin of the underlying KSP system is, in kOS, whenever a POSITION is reported, it will always be reported in a frame of reference where the origin is located at the CPU Vessel.

However, for the sake of VELOCITY, the origin point of all vectors is usually not SHIP, but the SOI body’s center. This is because if the origin point was the SHIP, then the ship’s velocity would always be zero in that frame of reference, and that would not be useful.

(The makers of kOS are aware that this is not technically a proper frame of reference, because the origin point varies depending on if you’re getting POSITION or getting VELOCITY. Fixing it at this point would break a lot of existing scripts, however.)

So the rule of thumb is:

  • For POSITION returned by KSP, the SHIP-RAW reference frame is used: centered on SHIP, with raw axes rotation.
  • For VELOCITY returned by KSP, the SOI-RAW reference frame is used: centered on SOI Body, with raw axes rotation.

Converting

Converting between SHIP-RAW and SOI-RAW reference frames is a simple matter of moving the origin point by adding or subtracting the SHIP:BODY:POSITION vector from the coordinate. This works because both frames are using the same axes rotation.

  • Any SHIP-RAW position vector minus SHIP:BODY:POSITION Gives the vector in SOI-RAW coordinates.
  • Any SOI-RAW position vector plus SHIP:BODY:POSITION Gives the vector in SHIP-RAW coordinates.

Command Reference

Running Programs

KerboScript supports running executing code saved in files on the local or archive disks. The terms “program” and “script” are generally used interchangably when describing this behavior. Programs can either be run using one of the Run Functions or the Run Keyword. Supported file types are:

Raw Text KerboScript files
These files contain KerboScript text commands, as if you had typed them directly into the terminal. They are easily read, written, and edited by humans. These files traditionally have a ".ks" extension, however it is not required.
Compiled Machine Language Files
These files contain encoded and compressed machine language opcodes. They are not formatted to allow humans to read, write, or edit the files. These files traditionally have a ".ksm" extension, however it is not required.

See also

KerboScript Machine Code
An explaination of how scripts are compiled, as well as an explaination of how COMPILE stores opcodes.
Filename Warning
Warning detailing issues with case sensitive host file systems.

Note

If you attempt to run the same program twice from within another script, the previously compiled version will be executed without attempting to recompile even if the original source script has been modified. However once the program has finished executing and returns to the main terminal input, the memory containing the programs is released. This means that every time that a script file is run from the terminal it is recompiled, even if the script file has not changed.

Run Functions

Note

Changed in version 1.0.0: The RUNPATH and RUNONCEPATH functions were added in version 1.0.0. Previously, only the more limited RUN command existed.

RUNPATH(path)
RUNPATH(path, commaSeperatedArgs...)
RUNONCEPATH(path)
RUNONCEPATH(path, commaSeperatedArgs...)
Parameters:
  • path (String or Path) – Path information pointing to the script file. May be an Absolute or Relative path.
  • commaSeperatedArgs... (Optional) – A comma seperated list of arguments to pass to the program

Note

The RUNPATH or RUNONCEPATH functions are nearly identical to each other except for what is described below under the heading Running a program “ONCE”.

RUNPATH or RUNONCEPATH take a list of arguments, the first of which is the filename of the program to run, and must evaluate to a string. Any additional arguments after that are optional, and are passed in to the program as its parameters it can read:

RUNPATH( "myfile.ks" ). // Run a program called myfile.ks.
RUNPATH( "myfile" ). // Run a program called myfile, where kOS will guess
                     // the filename extension you meant, and probably
                     // pick ".ks" for you.
RUNPATH( "myfile.ks", 1, 2 ). // Run a program called myfile.ks, and
                              // pass in the values 1 and 2 as its first
                              // two parameters.

RUNPATH or RUNONCEPATH can also work with any expression for the filename as long as it results in a string:

SET file_base to "prog_num_".
SET file_num to 3.
SET file_ending to ".ks".
RUNPATH( file_base + file_num + file_ending, 1, 2 ).
    // The above has the same effect as if you had done:
    RUNPATH("prog_num_3.ks", 1, 2).

Run Keyword

Note

You should prefer RUNPATH over RUN

The RUN command is older, and less powerful than the newer Run Functions, and is kept around mostly for backward compatibility. You can’t use a variable or expression to refer to the file name. The following examples will throw exceptions (but are compatible with the Run Functions):

SET filename_variable TO "myfile.ks".
RUN filename_variable. // Error: a file called "filename_variable" not found.

RUN "my" + "file" + ".ks".  // Syntax error - a single literal string expected,
                            // not an expression that returns a string.

Due to a parsing ambiguity issue, it was impossible to make RUN work with any arbitrary expression as the filename without changing its syntax a little in a way that would break every old kOS script. Therefore it was deemed better to just add a new function that uses the new syntax instead of changing the syntax of RUN.

RUN [ONCE] path [(commaSeperatedArgs...)]
Parameters:
  • once – By using the optional ONCE keyword parameter you can modify the behavior of the RUN keyword to obey the run once logic
  • path (String or bare word literal) – The string (i.e. "filename.ks") describing the path pointing to the script file. May be an Absolute or Relative path.
  • commaSeperatedArgs... (Optional) – A comma seperated list of arguments to pass to the program, surrounded by parenthesis (i.e. (arg1, arg2))

The RUN keyword is only capable of using hard-coded program names that were known at the time you wrote the script, and expressed as a simple bare word or literal string in quotes. For example, you can do this:

RUN "myfile.ks".
RUN myfile.ks. // using a bare word literal string

If you wish to pass arguments to the program, you may optionally add a set of parentheses with an argument list to the end of the syntax, like so:

// All 3 of these work:
RUN myfile(1,2,3).
RUN myfile.ks(1,2,3).
RUM "myfile.ks"(1,2,3).

Details Of Running Programs

Running a program “ONCE”

If the RUNONCEPATH function is used instead of the RUNPATH function, or the optional ONCE keyword is added to the RUN command, it means the run will not actually occur if the program has already been run once during the current program context. This is intended mostly for loading library program files that may have mainline code in them for initialization purposes that you don’t want to get run a second time just because you use the library in two different subprograms.

RUN ONCE and RUNONCEPATH mean “Run unless it’s already been run, in which case skip it.”

Warning

The “ONCE” component has no effect on how frequently a given program is compiled. Every unique program is compiled exactly once per program context execution, and remains in memory until the program finishes and returns control to the terminal.

Automatic guessing of full filename

For all 3 types of run command (RUN, RUNPATH, and RUNONCEPATH), the following filename “guess” rules are used when the filename given is incomplete:

  • 1: If no path information was present in the filename, then assume the file is in the current directory (that’s pretty much standard for all filename commands).
  • 2: Assume if no filename extension such as ".ks" or ".ksm" was given, and there is no file found that lacks an extension in the way the filename was given, then first try to find a file with the ”.ksm” extension appended to it, and if that file is not found then try to find a file with the ”.ks” extension appended to it.
Arguments

Although the syntax is a bit different for RUN versus RUNPATH (and RUNONCEPATH), all 3 techniques allow you to pass arguments into the program that it sees as its main script parameter values.

The following commands do equivalent things:

RUN "AutoLaunch.ks"( 75000, true, "hello" ).
RUNPATH("AutoLaunch.ksm", 75000, true, "hello" ).

In both of the above examples, had the program “AutoLaunch.ks” started with these lines:

// AutoLaunch.ks program file:
parameter final_alt, do_countdown, message.
//
// rest of program not shown...
//

Then inside AutoLaunch.ks, final_alt would be 75000, and do_countdown would be true, and message would be "hello".

Flight Control

Cooked Control

For more information, check out the documentation for the SteeringManager structure.

In this style of controlling the craft, you do not steer the craft directly, but instead select a goal direction and let kOS pick the way to steer toward that goal. This method of controlling the craft consists primarily of the following two commands:

The special LOCK variables for cooked steering
LOCK THROTTLE TO expression. // value range [0.0 .. 1.0]

This sets the main throttle of the ship to expression. Where expression is a floating point number between 0.0 and 1.0. A value of 0.0 means the throttle is idle, and a value of 1.0 means the throttle is at maximum. A value of 0.5 means the throttle is at the halfway point, and so on.

The expression used in this statement can be any formula and can call your own user functions. Just make sure it returns a value in the range [0..1].

Warning

It’s a very bad idea to``WAIT`` during the execution of the expression in a LOCK THROTTLE. See the note in the next section below.

LOCK STEERING TO expression.

This sets the direction kOS should point the ship where expression is a Vector or a Direction created from a Rotation or Heading:

Rotation

A Rotation expressed as R(pitch,yaw,roll). Note that pitch, yaw and roll are not based on the horizon, but based on an internal coordinate system used by KSP that is hard to use. Thankfully, you can force the rotation into a sensible frame of reference by adding a rotation to a known direction first.

To select a direction that is 20 degrees off from straight up:

LOCK STEERING TO Up + R(20,0,0).

To select a direction that is due east, aimed at the horizon:

LOCK STEERING TO North + R(0,90,0).

UP and NORTH are the only two predefined rotations.

Heading

A heading expressed as HEADING(compass, pitch). This will aim 30 degrees above the horizon, due south:

LOCK STEERING TO HEADING(180, 30).

Vector

Any vector can also be used to lock steering:

LOCK STEERING TO V(100,50,10).

Note that the internal coordinate system for (X,Y,Z) is quite complex to explain. To aim in the opposite of the surface velocity direction:

LOCK STEERING TO (-1) * SHIP:VELOCITY:SURFACE.

The following aims at a vector which is the cross product of velocity and direction down to the SOI planet - in other words, it aims at the “normal” direction to the orbit:

LOCK STEERING TO VCRS(SHIP:VELOCITY:ORBIT, BODY:POSITION).

"kill" string

Steering may also be locked to the special string value of "kill" which tells the steering manager to attempt to stop any vessel rotation, much like the stock SAS’s stability assist mode.

Like all LOCK expressions, the steering and throttle continually update on their own when using this style of control. If you lock your steering to velocity, then as your velocity changes, your steering will change to match it. Unlike with other LOCK expressions, the steering and throttle are special in that the lock expression gets executed automatically all the time in the background, while other LOCK expressions only get executed when you try to read the value of the variable. The reason is that the kOS computer is constantly querying the lock expression multiple times per second as it adjusts the steering and throttle in the background.

Warning

It’s a very bad idea to WAIT during the execution of the expression in a LOCK STEERING. See the note in the next section below.

LOCK WHEELTHROTTLE TO expression. // value range [-1.0 .. 1.0]

(For Rovers) This is used to control the throttle that is used when driving a wheeled vehicle on the ground. It is an entirely independent control from the flight throttle used with LOCK THROTTLE above. It is analogous to holding the ‘W’ (value of +1) or ‘S’ (value of -1) key when driving a rover manually under default keybindings.

WHEELTHROTTLE allows you to set a negative value, up to -1.0, while THROTTLE can’t go below zero. A negative value means you are trying to accelerate in reverse.

Unlike trying to drive manually, using WHEELTHROTTLE in kOS does not cause the torque wheels to engage as well. In stock KSP using the ‘W’ or ‘S’ keys on a rover engages both the wheel driving AND the torque wheel rotational power. In kOS those two features are done independently.

The expression used in this statement can be any formula and can call your own user functions. Just make sure it returns a value in the range [0..1].

Warning

It’s a very bad idea to WAIT during the execution of the expression in a LOCK WHEELTHROTTLE. See the note in the next section below.

LOCK WHEELSTEERING TO expression.

(For Rovers) This is used to tell the rover’s cooked steering where to go. The rover’s cooked steering doesn’t use nearly as sophisticated a PID control system as the flight cooked steering does, but it does usually get the job done, as driving has more physical effects that help dampen the steering down automatically.

There are 3 kinds of value understood by WHEELSTEERING:

  • GeoCoordinates - If you lock wheelsteering to a GetCoordinates, that will mean the rover will try to steer in whichever compass direction will aim at that location.
  • Vessel - If you try to lock wheelsteering to a vessel, that will mean the rover will try to steer in whichever compass direction will aim at that vessel. The vessel being aimed at does not need to be landed. If it is in the sky, the rover will attempt to aim at a location directly underneath it on the ground.
  • Scalar Number - If you try to lock wheelsteering to just a plain scalar number, that will mean the rover will try to aim at that compass heading. For example lock wheelsteering to 45. will try to drive the rover northeast.

For more precise control over steering, you can use raw steering to just directly tell the rover to yaw left and right as it drives and that will translate into wheel steering provided the vessel is landed and you have a probe core aiming the right way.

A warning about WHEELSTEERING and vertically mounted probe cores:

If you built your rover in such a way that the probe core controlling it is stack-mounted facing up at the sky when the rover is driving, that will confuse the lock WHEELSTEERING cooked control mechanism. This is a common building pattern for KSP players and it seems to work okay when driving manually, but when driving by a kOS script, the fact that the vessel’s facing is officially pointing up at the sky causes it to get confused. If you notice that your rover tends to drive in the correct direction only when on a flat or slight downslope, but then turns around and around in circles when driving toward the target requires going up a slope, then this may be exactly what’s happening. When it tilted back, the ‘forward’ vector aiming up at the sky started pointing behind it, and the cooked steering thought the rover was aimed in the opposite direction to the way it was really going. To fix this problem, either mount your rover probe core facing the front of the rover, or perform a “control from here” on some forward facing docking port or something like that to get it to stop thinking of the sky as “forward”.

Warning

It’s a very bad idea to WAIT during the execution of the expression in a LOCK WHEELSTEERING. See the note in the next section below.

Don’t ‘WAIT’ during cooked control calculation

Be aware that because LOCK THROTTLE, LOCK STEERING, LOCK WHEELTHROTTLE, and LOCK WHEELSTEERING are actually triggers that cause your expression to be calculated every single physics update tick behind the scenes, you should not execute a WAIT command in the code that performs the evaluation of the value used in them, as that will effectively cheat the entire script out of the full execution speed it deserves.

For example, if you attempt this:

function get_throttle {
    wait 0.001.  // this line is a bad idea.
    return 0.5.
}
lock throttle to get_throttle().

Then kOS will attempt to call the WAIT command every single update, as the kOS system keeps trying to re-run the lock throttle expression to learn what you want the new throttle value to be. This will starve your script of the CPU time it deserves, having the effect of running the lock function every-other-tick, and the rest of your code every-other-tick on the ticks in-between. (When the system hits the wait inside the throttle expression, it will stop there, not resuming until the next update, effectively meaning it doesn’t get around to running any of your main-line code until the next tick.)

Normally when you use a LOCK command, the expression is only evaluated when it needs to be by some other part of the script that is trying to read the value. But with these special cooked control locks, remember that the kOS system itself will query the value repeatedly in the background so it knows how to adjust the piloting. Unlike normal LOCKs, these LOCKs will be executed again and again even when you’re not explicitly trying to get their values.

Unlocking controls

If you LOCK the THROTTLE or STEERING, be aware that this prevents the user from manually controlling them. Until they unlock, the manual controls are prevented from working. You can free up the controls by issuing these two commands:

UNLOCK STEERING.
UNLOCK THROTTLE.

When the program ends, these automatically unlock as well, which means that to control a craft you must make sure the program doesn’t end. The moment it ends it lets go of the controls.

Tuning cooked steering

Note

New in version 0.18.0: This version of kOS completely gutted the internals of the old steering system and replaced them with the system described below. Anything said below this point is pertinent to version 0.18 and higher only.

While cooked steering tries to come balanced to perform decently without user interaction, there are some instances where you may need to help tune the behavior. There are a number of settings you can adjust to tweak the behavior of the cooked steering if it’s not performing exactly as you’d like. It may be the case that making your own control mechanism from scratch, while entirely possible with kOS, might be unnecessary if all you really want to do is just make the cooked steering behave slightly differently.

The adjustments described below all come from the SteeringManager structure, which has its own detailed documentation page.

Some simple suggestions to try fixing common problems

If you don’t want to understand the intricate details of the cooked steering system, here’s some quick suggestions for changes to the settings that might help solve some problems, in the list below:

  • problem: A large vessel with low torque doesn’t seem to be even trying to rotate very quickly. The controls may be fluctuating around the zero point, but it doesn’t seem to want to even try to turn faster.
    • solution: Increase STEERINGMANAGER:MAXSTOPPINGTIME to about 5 or 10 seconds or so. Also, slightly increase STEERINGMANAGER:PITCHPID:KD and STEERINGMANAGER:YAWPID:KD to about 1 or 2 as well to go with it.
    • explanation: Once the steering manager gets such a ship rotating at a tiny rate, it stops trying to make it rotate any faster than that because it’s “afraid” of allowing it to obtain a larger momentum than it thinks it could quickly stop. It needs to be told that in this case it’s okay to build up more “seconds worth” of rotational velocity. The reason for increasing the Kd term as well is to tell it to anticipate the need to starting slowing down rotation sooner than it normally would.
  • problem: A vessel seems to reasonably come to the desired direction sensibly, but once it’s there the ship vibrates back and forth by about 1 degree or less excessively around the setpoint.
    • solution: Increase STEERINGMANAGER:PITCHTS and STEERINGMANAGER:YAWTS to several seconds.
    • explanation: Once it’s at the desired orientation and it has mostly zeroed the rotational velocity, it’s trying to hold it there with microadjustments to the controls, and those microadjustments are “too tight”.
  • problem: The vessel’s nose seems to be waving slowly back and forth across the set direction, taking too long to center on it, and you notice the control indicators are pushing all the way to the extremes as it does so.
    • solution: Increase STEERINGMANAGER:PITCHPID:KD and STEERINGMANGER:YAWPID:KD.
    • explanation: The ship is trying to push its rotation rate too high when almost at the setpoint. It needs to anticipate the fact that it is going to reach the desired direction and start slowing down BEFORE it gets there.
  • problem: The vessel’s nose seems to be waving slowly back and forth across the set direction, taking too long to center on it, but you notice that the control indicators are NOT pushing all the way to the extremes as it does so. Instead they seem to be staying low in magnitude, wavering around zero and may be getting smaller over time.
    • solution: Decrease STEERINGMANAGER:PITCHTS and/or STEERINGMANAGER:YAWTS
    • explanation: While larger values for the settling time on the Torque PID controller will help to smooth out spikes in the controls, it also results in a longer time period before the steering comes to a rest at the setpoint (also knows as settling). If you had previously increased the settling time to reduce oscillations, try picking a value half way between the default and the new value you previously selected.

But to understand how to tune the cooked steering in a more complex way than just with that simple list, you first have to understand what a PID controller is, at least a little bit, so you know what the settings you can tweak actually do.

If you don’t know what a PID controller is and want to learn more, you can read numerous descriptions of the concept on the internet that can be found in moments by a web search. If you just want to know a two minute explanation for the sake of tuning the cooked steering a bit, read on.

Quick and Dirty description of a PID controller

You can think of a PID controller as a magic mathematical black box that can learn where to set a control lever in order to achieve a given goal. A good example of this is cruise control on a car. You tell the cruise control what speed you’d like it to maintain, and it attempts to move the accelerator pedal to the necessary position that will maintain that constant speed.

That, in a nutshell is the goal of a PID controller - to perform tasks like that. You have control over a lever or dial of some sort, and it indirectly affects a phenomenon you can measure, and you feed the mathematical black box of the PID controller the measurement of the phenomenon, and obey its instructions of where to set the control lever. Over time, the PID controller, under the assumption that you are obeying its instructions of where to set the control lever, learns how to fine tune its commands about how to set the lever to get the measurement to settle on the value you asked for.

A more complex discussion of PID controllers than that is outside the scope of this document, but you can check out the PID Loop tutorial.

Cooked Steering’s use of PID controllers

kOS’s cooked steering uses two nested PID controllers per axis of rotation:

Seek direction   Current Direction Measurement
    |                |
    |                |
   \|/              \|/
+-seek me---------cur val---+
|                           |
|  Rotational Velocity PID  |
|                           |
+-output--------------------+
  desired
  rotational
  velocity
  (i.e. "I'd like to be rotating at 3 degrees per second downward")
    |
    |
    |           Current Rotational Velocity measurement
    |                |
    |                |
   \|/              \|/
+-seek me---------cur val---+
|                           |
|       Torque PID          |
|                           |
+-output--------------------+
  desired
  control
  setting
  (i.e. "ship:control:pitch should be -0.2")
    |
    |
    |
    |
   \|/
Feed this control value to KSP.  (This is the value you can see
on the control indicator meters in the lower-left of the screen).
The Rotational Velocity PID

The first PID controller looks at the current direction the ship is pointed, versus the direction the ship is meant to be pointed, and uses the offset between the two to decide how to set the desired rotational velocity (rate at which the angle is changing).

The suffixes to SteeringManager allow direct manipulation of the rotational velocity’s PID tuning parameters.

The Torque PID

But there is no such thing as a lever that directly controls the rotational velocity. What there is, is a lever that directly controls the rotational acceleration. When you pull on the yoke (i.e. hold down the “S” key), you are telling the ship to either rotate faster or slower than it already is.

So given a result from the Rotational Velocity PID, with a desired rotational velocity to seek, the second PID controller takes over, the Torque PID, which uses that information to choose how to set the actual controls themselves (i.e. the WASDQE controls) to accelerate toward that goal rotational velocity.

The suffixes to SteeringManager don’t quite allow direct manipulation of the torque PID tuning parameters Kp, Ki, and Kd, because they are calculated indirectly from the ship’s own attributes. However, there are several suffixes to SteeringManager that allow you to make indirect adjustments to them that are used in calculating the values it uses for Kp, Ki, and Kd.


This technique of using two different PID controllers, the first one telling the second one which seek value to use, and the second one actually being connected to the control “lever”, is one of many ways of dealing with a phenomenon with two levels of indirection from the control.

Keeping the above two things separate, the rotational velocity PID versus the Torque PID, is important in knowing which setting you need to tweak in order to achieve the desired effect.

One pair of PID’s per axis of rotation

The above pair of controllers is replicated per each of the 3 axes of rotation, for a total of 6 altogether. Some of the settings you can adjust affect all 3 axes together, while others are specific to just one. See the descriptions of each setting carefully to know which is which.

Corrects 2 axes first, then the 3rd

The cooked steering tries to correct first the pitch and yaw, to aim the rocket at the desired pointing vector, then only after it’s very close to finishing that task does it allow the 3rd axis, the roll axis, to correct itself. This is because if you try correcting all three at the same time, it causes the cooked steering to describe a curved arc toward its destination orientation, rather than rotating straight towards it.

This behavior is correct for rockets with radial symmetry, but is probably a bit wrong for trying to steer an airplane to a new heading while in atmosphere. For flying an airplane to a new heading, it’s still best to make your own control scheme from scratch with raw steering.

The settings to change

First, you can modify how kOS decides how fast the ship should turn:

// MAXSTOPPINGTIME tells kOS how to calculate the maximum allowable
// angular velocity the Rotational Velocity PID is allowed to output.
// Increasing the value will result in the ship turning
// faster, but it may introduce more overshoot.
// Adjust this setting if you have a small amount of torque on a large mass,
// or if your ship appears to oscillate back and forth rapidly without
// moving towards the target direction.
SET STEERINGMANAGER:MAXSTOPPINGTIME TO 10.

// You can also modify the PID constants that calculate desired angular
// velocity based on angular error, in the angular velocity PID controller.
// Note that changes made directly to the PIDLoop's MINIMUM and MAXIMUM
// suffixes will be overwritten based on the value MAXSTOPPINGTIME, the
// ship's torque and moment of inertia.
// These values will require precision and testing to ensure consistent
// performance.
// Beware of large KD values: Due to the way angular velocity and part
// facing directions are calculated in KSP, it is normal to have small rapid
// fluctuations which may introduce instability in the derivative component.
SET STEERINGMANAGER:PITCHPID:KP TO 0.85.
SET STEERINGMANAGER:PITCHPID:KI TO 0.5.
SET STEERINGMANAGER:PITCHPID:KD TO 0.1.

Second, you can change how the controls are manipulated to achieve the desired angular velocity. This is for the Torque PID mentioned above. Internally, kOS uses the ship’s available torque and moment of inertial to dynamically calculate the PID constants. Then the desired torque is calculated based on the desired angular velocity. The steering controls are then set based on the the percentage the desired torque is of the available torque. You can change the settling time for the torque calculation along each axis:

// Increase the settling time to slow down control reaction time and
// reduce control spikes.  This is helpful in vessels that wobble enough to
// cause fluctuations in the measured angular velocity.
// This is recommended if your ship turns towards the target direction well
// but then oscillates when close to the target direction.
SET STEERINGMANAGER:PITCHTS TO 10.
SET STEERINGMANAGER:ROLLTS TO 5.

If you find that kOS is regularly miscalculating the available torque, you can also define an adjust bias, or factor. Check out these SteeringManager suffixes for more details: PITCHTORQUEADJUST, YAWTORQUEADJUST, ROLLTORQUEADJUST, PITCHTORQUEFACTOR, YAWTORQUEFACTOR, ROLLTORQUEFACTOR

Advantages/Disadvantages

The advantage of “Cooked” control is that it is simpler to write scripts for, but the disadvantage is that you have only partial control over the details of the motion.

Cooked controls perform best on ships that do not rely heavily on control surfaces, have medium levels of torque, and are structurally stable. You can improve the control by placing the ship’s root part or control part close to the center of mass (preferably both). Adding struts to critical joints (like decouplers) or installing a mod like Kerbal Joint Reinforcement will also help.

But because of the impossibility of finding one setting that is universally correct for all possible vessels, sometimes the only way to make cooked steering work well for you is to adjust the parameters as described above, or to make your own steering control from scratch using raw steering.

Raw Control

If you wish to have your kOS script manipulate a vessel’s flight controls directly in a raw way, rather than relying on kOS to handle the flying for you, then this is the type of structure you will need to use to do it. This is offered as an alternative to using the combination of LOCK STEERING and LOCK THROTTLE commands. To obtain the CONTROL variable for a vessel, use its :CONTROL suffix:

SET controlStick to SHIP:CONTROL.
SET controlStick:PITCH to 0.2.

Unlike with so-called “Cooked” steering, “raw” steering uses the SET command, not the LOCK command. Using LOCK with these controls won’t work. When controlling the ship in a raw way, you must decide how to move the controls in detail. Here is another example:

SET SHIP:CONTROL:YAW to 0.2.

This will start pushing the ship to rotate a bit faster to the right, like pushing the D key gently. All the following values are set between \(-1\) and \(+1\). Zero means the control is neutral. You can set to values smaller in magnitude than \(-1\) and \(+1\) for gentler control:

print "Gently pushing forward for 3 seconds.".
SET SHIP:CONTROL:FORE TO 0.2.
SET now to time:seconds.
WAIT until time:seconds > now + 3.
SET SHIP:CONTROL:FORE to 0.0.

print "Gently Pushing leftward for 3 seconds.".
SET SHIP:CONTROL:STARBOARD TO -0.2.
SET now to time:seconds.
WAIT until time:seconds > now + 3.
SET SHIP:CONTROL:STARBOARD to 0.0.

print "Starting an upward rotation.".
SET SHIP:CONTROL:PITCH TO 0.2.
SET now to time:seconds.
WAIT until time:seconds > now + 0.5.
SET SHIP:CONTROL:PITCH to 0.0.

print "Giving control back to the player now.".
SET SHIP:CONTROL:NEUTRALIZE to True.

One can use SHIP:CONTROL:ROTATION and SHIP:CONTROL:TRANSLATION to see the ship’s current situation.

Raw Flight Controls Reference

These “Raw” controls allow you the direct control of flight parameters while the current program is running.

Note

The MAINTHROTTLE requires active engines and, of course, sufficient and appropriate fuel. The rotational controls YAW, PITCH and ROW require one of the following: active reaction wheels with sufficient energy, RCS to be ON with properly placed thrusters and appropriate fuel, or control surfaces with an atmosphere in which to operate. The translational controls FORE, STARBOARD and TOP only work with RCS, and require RCS to be ON with properly placed thrusters and appropriate fuel.

Suffix Type, Range Equivalent Key
MAINTHROTTLE scalar [0,1] LEFT-CTRL, LEFT-SHIFT
YAW scalar [-1,1] D, A
PITCH scalar [-1,1] W, S
ROLL scalar [-1,1] Q, E
ROTATION Vector (YAW,PITCH,ROLL)
YAWTRIM scalar [-1,1] ALT+D, ALT+A
PITCHTRIM scalar [-1,1] ALT+W, ALT+S
ROLLTRIM scalar [-1,1] ALT+Q, ALT+E
FORE scalar [-1,1] N, H
STARBOARD scalar [-1,1] L, J
TOP scalar [-1,1] I, K
TRANSLATION Vector (STARBOARD,TOP,FORE)
WHEELSTEER scalar [-1,1] A, D
WHEELTHROTTLE scalar [-1,1] W, S
WHEELSTEERTRIM scalar [-1,1] ALT+A, ALT+D
WHEELTHROTTLETRIM scalar [-1,1] ALT+W, ALT+S
NEUTRAL Boolean Is kOS Controlling?
NEUTRALIZE Boolean Releases Control
SHIP:CONTROL:MAINTHROTTLE

Set between 0 and 1 much like the cooked flying LOCK THROTTLE command.

SHIP:CONTROL:YAW

This is the rotation about the “up” vector as the pilot faces forward. Essentially left \((-1)\) or right \((+1)\).

SHIP:CONTROL:PITCH

Rotation about the starboard vector up \((+1)\) or down \((-1)\).

SHIP:CONTROL:ROLL

Rotation about the longitudinal axis of the ship left-wing-down \((-1)\) or left-wing-up \((+1)\).

SHIP:CONTROL:ROTATION

This is a Vector object containing (YAW, PITCH, ROLL) in that order.

SHIP:CONTROL:YAWTRIM

Controls the YAW of the rotational trim.

SHIP:CONTROL:PITCHTRIM

Controls the PITCH of the rotational trim.

SHIP:CONTROL:ROLLTRIM

Controls the ROLL of the rotational trim.

SHIP:CONTROL:FORE

Controls the translation of the ship forward \((+1)\) or backward \((-1)\).

SHIP:CONTROL:STARBOARD

Controls the translation of the ship to the right \((+1)\) or left \((-1)\) from the pilot’s perspective.

SHIP:CONTROL:TOP

Controls the translation of the ship up \((+1)\) or down \((-1)\) from the pilot’s perspective.

SHIP:CONTROL:TRANSLATION

Controls the translation as a Vector (STARBOARD, TOP, FORE).

SHIP:CONTROL:WHEELSTEER

Turns the wheels left \((-1)\) or right \((+1)\).

SHIP:CONTROL:WHEELTHROTTLE

Controls the wheels to move the ship forward \((+1)\) or backward \((-1)\) while on the ground.

SHIP:CONTROL:WHEELSTEERTRIM

Controls the trim of the wheel steering.

SHIP:CONTROL:WHEELTHROTTLETRIM

Controls the trim of the wheel throttle.

SHIP:CONTROL:NEUTRAL

Returns true or false depending if kOS has any set controls. This is not settable.

SHIP:CONTROL:NEUTRALIZE

This causes manual control to let go. When set to true, kOS lets go of the controls and allows the player to manually control them again. This is not gettable.

Unlocking controls

Setting any one of SHIP:CONTROL values will prevent player from manipulating that specific control manually. Other controls will not be locked. To free any single control, set it back to zero. To give all controls back to the player you must execute:

SET SHIP:CONTROL:NEUTRALIZE to TRUE.
Advantages/Disadvantages

The control over RCS translation requires the use of Raw control. Also, with raw control you can choose how gentle to be with the controls and it can be possible to control wobbly craft better with raw control than with cooked control.

Pilot Input

This is not, strictly speaking, a method of controlling the craft. “Pilot” controls are a way to read the input from the pilot. Most of these controls share the same name as their flight control, prefixed with PILOT (eg YAW and PILOTYAW) the one exception to this is the PILOTMAINTHROTTLE. This suffix has a setter and allows you to change the behavior of the throttle that persists even after the current program ends:

SET SHIP:CONTROL:PILOTMAINTHROTTLE TO 0.

Will ensure that the throttle will be 0 when execution stops. These suffixes allow you to read the input given to the system by the user.

structure Control
Suffix Type, Range Equivalent Key
PILOTMAINTHROTTLE scalar [0,1] LEFT-CTRL, LEFT-SHIFT
PILOTYAW scalar [-1,1] D, A
PILOTPITCH scalar [-1,1] W, S
PILOTROLL scalar [-1,1] Q, E
PILOTROTATION Vector (YAW,PITCH,ROLL)
PILOTYAWTRIM scalar [-1,1] ALT+D, ALT+A
PILOTPITCHTRIM scalar [-1,1] ALT+W, ALT+S
PILOTROLLTRIM scalar [-1,1] ALT+Q, ALT+E
PILOTFORE scalar [-1,1] N, H
PILOTSTARBOARD scalar [-1,1] L, J
PILOTTOP scalar [-1,1] I, K
PILOTTRANSLATION Vector (STARBOARD,TOP,FORE)
PILOTWHEELSTEER scalar [-1,1] A, D
PILOTWHEELTHROTTLE scalar [-1,1] W, S
PILOTWHEELSTEERTRIM scalar [-1,1] ALT+A, ALT+D
PILOTWHEELTHROTTLETRIM scalar [-1,1] ALT+W, ALT+S
PILOTNEUTRAL Boolean Is kOS Controlling?
SHIP:CONTROL:PILOTMAINTHROTTLE

Returns the pilot’s input for the throttle. This is the only PILOT variable that is settable and is used to set the throttle upon termination of the current kOS program.

SHIP:CONTROL:PILOTYAW

Returns the pilot’s rotation input about the “up” vector as the pilot faces forward. Essentially left \((-1)\) or right \((+1)\).

SHIP:CONTROL:PILOTPITCH

Returns the pilot’s rotation input about the starboard vector up \((+1)\) or down \((-1)\).

SHIP:CONTROL:PILOTROLL

Returns the pilot’s rotation input about the logintudinal axis of the ship left-wing-down \((-1)\) or left-wing-up \((+1)\).

SHIP:CONTROL:PILOTROTATION

Returns the pilot’s rotation input as a Vector object containing (YAW, PITCH, ROLL) in that order.

SHIP:CONTROL:PILOTYAWTRIM

Returns the pilot’s input for the YAW of the rotational trim.

SHIP:CONTROL:PILOTPITCHTRIM

Returns the pilot’s input for the PITCH of the rotational trim.

SHIP:CONTROL:PILOTROLLTRIM

Returns the pilot’s input for the ROLL of the rotational trim.

SHIP:CONTROL:PILOTFORE

Returns the the pilot’s input for the translation of the ship forward \((+1)\) or backward \((-1)\).

SHIP:CONTROL:PILOTSTARBOARD

Returns the the pilot’s input for the translation of the ship to the right \((+1)\) or left \((-1)\) from the pilot’s perspective.

SHIP:CONTROL:PILOTTOP

Returns the the pilot’s input for the translation of the ship up \((+1)\) or down \((-1)\) from the pilot’s perspective.

SHIP:CONTROL:PILOTTRANSLATION

Returns the the pilot’s input for translation as a Vector (STARBOARD, TOP, FORE).

SHIP:CONTROL:PILOTWHEELSTEER

Returns the the pilot’s input for wheel steering left \((-1)\) or right \((+1)\).

SHIP:CONTROL:PILOTWHEELTHROTTLE

Returns the the pilot’s input for the wheels to move the ship forward \((+1)\) or backward \((-1)\) while on the ground.

SHIP:CONTROL:PILOTWHEELSTEERTRIM

Returns the the pilot’s input for the trim of the wheel steering.

SHIP:CONTROL:PILOTWHEELTHROTTLETRIM

Returns the the pilot’s input for the trim of the wheel throttle.

SHIP:CONTROL:PILOTNEUTRAL

Returns true or false if the pilot is active or not.

Be aware that kOS can’t control a control at the same time that a player controls it. If kOS is taking control of the yoke, then the player can’t manually control it. Remember to run:

SET SHIP:CONTROL:NEUTRALIZE TO TRUE.

after the script is done using the controls, or the player will be locked out of control.

Ship Systems

CONTROL REFERENCE
The axes for ship control and ship-relative coordinate system are determined in relation to a “control from” part (more specifically a transform belonging to the part) on the ship. All vessels must have at least one “control from” part on them somewhere, which is why there’s no mechanism for un-setting the “control from” part. You must instead pick another part and set it as the “control from” source using the Part:CONTROLFROM method. You may retrieve the current control part using the Vessel:CONTROLPART suffix.
RCS and SAS
RCS
Access:Toggle ON/OFF; get/set
Type:Action Group, Boolean

Turns the RCS on or off, like using R at the keyboard:

RCS ON.
SAS
Access:Toggle ON/OFF; get/set
Type:Action Group, Boolean

Turns the SAS on or off, like using T at the keybaord:

SAS ON.
SASMODE
Access:Get/Set
Type:String

Getting this variable will return the currently selected SAS mode. Where value is one of the valid strings listed below, this will set the stock SAS mode for the cpu vessel:

SET SASMODE TO value.

It is the equivalent to clicking on the buttons next to the nav ball while manually piloting the craft, and will respect the current mode of the nav ball (orbital, surface, or target velocity - use NAVMODE to read or set it). Valid strings for value are "PROGRADE", "RETROGRADE", "NORMAL", "ANTINORMAL", "RADIALOUT", "RADIALIN", "TARGET", "ANTITARGET", "MANEUVER", "STABILITYASSIST", and "STABILITY". A null or empty string will default to stability assist mode, however any other invalid string will throw an exception. This feature will respect career mode limitations, and will throw an exception if the current vessel is not able to use the mode passed to the command. An exception is also thrown if "TARGET" or "ANTITARGET" are used, but no target is selected.

Note

SAS mode is reset to stability assist when toggling SAS on, however it doesn’t happen immediately. Therefore, after activating SAS, you’ll have to skip a frame before setting the SAS mode. Velocity-related modes also reset back to stability assist when the velocity gets too low.

Warning

SASMODE does not work with RemoteTech

Due to the way that RemoteTech disables flight control input, the built in SAS modes do not function properly when there is no connection to the KSC or a Command Center. If you are writing scripts for use with RemoteTech, make sure to take this into account.

NAVMODE
Access:Get/Set
Type:String

Getting this variable will return the currently selected nav ball speed display mode. Where value is one of the valid strings listed below, this will set the nav ball mode for the cpu vessel:

SET NAVMODE TO value.

It is the equivalent to changing the nav ball mode by clicking on speed display on the nav ball while manually piloting the craft, and will change the current mode of the nav ball, affecting behavior of most SAS modes. Valid strings for value are "ORBIT", "SURFACE" and "TARGET". A null or empty string will default to orbit mode, however any other invalid string will throw an exception. This feature is accessible only for the active vessel, and will throw an exception if the current vessel is not active. An exception is also thrown if "TARGET" is used, but no target is selected.

STOCK ACTION GROUPS

These action groups (including abovementioned SAS and RCS) are stored as Boolean values which can be read to determine their current state. Reading their value can be used by kOS as a form of user input:

IF RCS PRINT "RCS is on".
ON ABORT {
    PRINT "Aborting!".
}

Using the TOGGLE command will simply set the value to the opposite of the current value. These two are essentially the same:

TOGGLE AG1.
SET AG1 TO NOT AG1.

The action groups can be set both by giving ON or OFF command and by setting the Boolean value. The following commands will have the same effect:

SAS ON.
SET SAS TO TRUE.

However, using the SET command allows the use of any Boolean variable or expression, for example:

SET GEAR TO ALT:RADAR<1000.
SET LIGHTS TO GEAR.
SET BRAKES TO NOT BRAKES.

Some parts automatically add their actions to basic action groups or otherwise react to them. More actions can be added to the groups in the editor, if VAB or SPH is advanced enough.

Note

Pressing an action group’s associated key will toggle it’s value from TRUE TO FALSE or from FALSE to TRUE. If you are attempting to use action groups as user input, make sure to compare it to a stored “last value” or use the ON Trigger

Note

Assigned actions only react to changes in action group state, therefore calling GEAR ON. when it’s already on will have no effect even on undeployed landing gear. The value will first need to be set to False before setting it back to True.

Note

Some actions react differently to toggling the group on and off, other will give the same response to both. For example, landing gear will not deploy if they are currently retracted and you set GEAR OFF.. However, if an engine is off and the “Toggle Engine” action is linked to AG1 which is currently True, calling AG1 OFF. will turn on the engine.

LIGHTS
Access:Toggle ON/OFF; get/set
Type:Action Group, Boolean

Turns the lights on or off, like using the U key at the keyboard:

LIGHTS ON.
BRAKES
Access:Toggle ON/OFF; get/set
Type:Action Group, Boolean

Turns the brakes on or off, like clicking the brakes button, though not like using the B key, because they stay on:

BRAKES ON.
GEAR
Access:Toggle ON/OFF; get/set
Type:Action Group, Boolean

Deploys or retracts the landing gear, like using the G key at the keyboard:

GEAR ON.
ABORT
Access:Toggle ON/OFF; get/set
Type:Action Group, Boolean

Abort action group (no actions are automatically assigned, configurable in the editor), like using the Backspace key at the keyboard:

ABORT ON.
AG1 ... AG10
Access:Toggle ON/OFF; get/set
Type:Action Group, Boolean

10 custom action groups (no actions are automatically assigned, configurable in the editor), like using the numeric keys at the keyboard:

AG1 ON.
AG4 OFF.
SET AG10 to AG3.
kOS PSEUDO ACTION GROUPS

kOS adds several Boolean flags (bound variable fields) that can be used by scripts in the same way the stock action groups are used:

PANELS ON.
IF BAYS PRINT "Payload/service bays are ajar!".
SET RADIATORS TO LEGS.

However, unlike the stock action groups, you can’t manually assign actions to these fields in the VAB. They automatically affect all parts of the corresponding type. The biggest difference is that the values for these groups are not stored, instead, the value is directly dependent on the state of the associated parts. Another difference from stock groups is that both ON and OFF commands work independently of the initial state of the field. For example, if some of the payload bays are closed and some are open (BAYS would return true), BAYS ON will still open any bays that are currently closed, and BAYS OFF will close the ones that are opened.

Note

Because these fields return their value based on the actual status of the associated parts, it is not guaranteed that the return value will match the value you set immediately. Some parts may not report the new state until an animation has finished, or the part may not be able to perform the selected action at this time.

LEGS
Access:Toggle ON/OFF; get/set
Type:Action Group, Boolean

Deploys or retracts all the landing legs (but not wheeled landing gear):

LEGS ON.

Returns true if all the legs are deployed.

CHUTES
Access:Toggle ON; get/set
Type:Action Group, Boolean

Deploys all the parachutes (only ON command has effect):

CHUTES ON.

Returns true if all the chutes are deployed.

CHUTESSAFE
Access:Toggle ON; get/set
Type:Action Group, Boolean

Deploys all the parachutes than can be safely deployed in the current conditions (only ON command has effect):

CHUTESSAFE ON.

Returns false only if there are disarmed parachutes chutes which may be safely deployed, and true if all safe parachutes are already deployed including any time where there are no safe parachutes.

The following code will gradually deploy all the chutes as the speed drops:

WHEN (NOT CHUTESSAFE) THEN {
    CHUTESSAFE ON.
    RETURN (NOT CHUTES).
}
PANELS
Access:Toggle ON/OFF; get/set
Type:Action Group, Boolean

Extends or retracts all the deployable solar panels:

PANELS ON.

Returns true if all the panels are extended, including those inside of fairings or cargo bays.

Note

Some solar panels can’t be retracted once deployed. Consult the part’s description for details.

RADIATORS
Access:Toggle ON/OFF; get/set
Type:Action Group, Boolean

Extends or retracts all the deployable radiators and activates or deactivates all the fixed ones:

RADIATORS ON.

Returns true if all the radiators are extended (if deployable) and active.

LADDERS
Access:Toggle ON/OFF; get/set
Type:Action Group, Boolean

Extends or retracts all the extendable ladders:

LADDERS ON.

Returns true if all the ladders are extended.

BAYS
Access:Toggle ON/OFF; get/set
Type:Action Group, Boolean

Opens or closes all the payload and service bays (including the cargo ramp):

BAYS ON.

Returns true if at least one bay is open.

DEPLOYDRILLS
Access:Toggle ON/OFF; get/set
Type:Action Group, Boolean

Deploys or retracts all the mining drills:

DEPLOYDRILLS ON.

Returns true if all the drills are deployed.

DRILLS
Access:Toggle ON/OFF; get/set
Type:Action Group, Boolean

Activates (has effect only on drills that are deployed and in contact with minable surface) or stops all the mining drills:

DRILLS ON.

Returns true if at least one drill is actually mining.

FUELCELLS
Access:Toggle ON/OFF; get/set
Type:Action Group, Boolean

Activates or deactivates all the fuel cells (distingushed from other conveters by converter/action names):

FUELCELLS ON.

Returns true if at least one fuel cell is activated.

ISRU
Access:Toggle ON/OFF; get/set
Type:Action Group, Boolean

Activates or deactivates all the ISRU converters (distingushed from other conveters by converter/action names):

ISRU ON.

Returns true if at least one ISRU converter is activated.

INTAKES
Access:Toggle ON/OFF; get/set
Type:Action Group, Boolean

Opens or closes all the air intakes:

INTAKES ON.

Returns true if all the intakes are open.

TARGET
TARGET
Access:Get/Set
Type:String (set); Vessel or Body or Part (get/set)

Where name is the name of a target vessel or planet, this will set the current target:

SET TARGET TO name.

Note that the above options also can refer to a different vessel besides the current ship, for example, TARGET:THROTTLE to read the target’s throttle. But not all “set” or “lock” options will work with a different vessel other than the current one, because there’s no authority to control a craft the current program is not attached to.

Unless otherwise stated, all controls that a kOS CPU attempts will be done on the CPU Vessel. There are three styles of control:

Cooked
Give a goal direction to seek, and let kOS find the way to maneuver toward it.
Raw
Control the craft just like a manual pilot would do from a keyboard or joystick.
Pilot
This is the stock way of controlling craft, the state of which can be read in KerboScript.

Warning

SAS OVERRIDES kOS

However, SAS will tend to fight and/or override kOS’s attempts to steer. In order for kOS to be able to turn the ship, you need to set SAS OFF. You should take care in your scripts to manage the use of SAS appropriately. It is common for people writing kOS scripts to explicitly start them with a use of the SAS OFF command just in case you forgot to turn it off before running the script. You could also store the current state in a temporary variable, and re-set it at the conclusion of your script.

Predictions of Flight Path

Note

Manipulating the maneuver nodes

To alter the maneuver nodes on a vessel’s flight plan, use the ADD and REMOVE commands as described on the maneuver node manipulation page.

Using the Add and Remove commands as described on that page, you may alter the flight plan of the CPU_vessel, however kOS does not automatically execute the nodes. You still have to write the code to decide how to successfully execute a planned maneuver node.

Warning

Be aware that a limitation of KSP makes it so that some vessels’ maneuver node systems cannot be accessed. KSP appears to limit the maneuver node system to only functioning on the current PLAYER vessel, under the presumption that its the only vessel that needs them, as ever other vessel cannot be maneuvered. kOS can maneuver a vessel that is not the player vessel, but it cannot overcome this limitation of the base game that unloads the maneuver node system for other vessels.

Be aware that the effect this has is that when you try to predict another vessel’s position, it will sometimes give you answers that presume that other vessel will be purely drifting, and not following its maneuver nodes.

The following prediction functions do take into account the future maneuver nodes planned, and operate under the assumption that they will be executed as planned.

These return predicted information about the future position and velocity of an object.

POSITIONAT(orbitable,time)
Parameters:
Type orbitable:

Orbitable

Type time:

TimeSpan

Returns:

A position Vector expressed as the coordinates in the ship-center-raw-rotation frame

Returns a prediction of where the Orbitable will be at some universal Timestamp. If the Orbitable is a Vessel, and the Vessel has planned maneuver nodes, the prediction assumes they will be executed exactly as planned.

Prerequisite: If you are in a career mode game rather than a sandbox mode game, This function requires that you have your space center’s buildings advanced to the point where you can make maneuver nodes on the map view, as described in Career:CANMAKENODES.

VELOCITYAT(orbitable,time)
Parameters:
Type orbitable:

Orbitable

Type time:

TimeSpan

Returns:

An ObitalVelocity structure.

Returns a prediction of what the Orbitable’s velocity will be at some universal Timestamp. If the Orbitable is a Vessel, and the Vessel has planned maneuver nodes, the prediction assumes they will be executed exactly as planned.

Prerequisite: If you are in a career mode game rather than a sandbox mode game, This function requires that you have your space center’s buildings advanced to the point where you can make manuever nodes on the map view, as described in Career:CANMAKENODES.

ORBITAT(orbitable,time)
Parameters:
Type orbitable:

Orbitable

Type time:

TimeSpan

Returns:

An Orbit structure.

Returns the Orbit patch where the Orbitable object is predicted to be at some universal Timestamp. If the Orbitable is a Vessel, and the Vessel has planned maneuver nodes, the prediction assumes they will be executed exactly as planned.

Prerequisite: If you are in a career mode game rather than a sandbox mode game, This function requires that you have your space center’s buildings advanced to the point where you can make maneuver nodes on the map view, as described in Career:CANMAKENODES.

Examples:

//kOS
// test the future position and velocity prediction.
// Draws a position and velocity vector at a future predicted time.

declare parameter item. // thing to predict for, i.e. SHIP.
declare parameter offset. // how much time into the future to predict.
declare parameter velScale. // how big to draw the velocity vectors.
              // If they're far from the camera you should draw them bigger.


set predictUT to time + offset.
set stopProg to false.

set futurePos to positionat( item, predictUT ).
set futureVel to velocityat( item, predictUT ).

set drawPos to vecdrawargs( v(0,0,0), futurePos, green, "future position", 1, true ).
set drawVel to vecdrawargs( futurePos, velScale*futureVel:orbit, yellow, "future velocity", 1, true ).

Example Screenshot:

LIST Command

A List is a type of Structure that stores a list of variables in it. The LIST command either prints or crates a List object containing items queried from the game. For more information, see the page about the List structure.

FOR Loop

Lists need to be iterated over sometimes, to help with this we have the FOR loop, explained on the flow control page. The LIST Command comes in 4 forms:

  1. LIST.

    When no parameters are given, the LIST command is exactly equivalent to the command:

    LIST FILES.
    
  2. LIST ListKeyword.

    This variant prints items to the termianl sceen. Depending on the ListKeyword used (see below), different values are printed.

  3. LIST ListKeyword IN YourVariable.

    This variant takes the items that would otherwise have been printed to the terminal screen, and instead makes a List of them in YourVariable, that you can then iterate over with a FOR loop if you like.

  4. LIST ListKeyword FROM SomeVessel IN YourVariable.

    This variant is just like variant (3), except that it gives a list of the items that exist on some other vessel that might not necessarily be the current CPU_vessel.

Available Listable Keywords

The ListKeyword in the above command variants can be any of the following:

Universal Lists

These generate lists that are not dependent on which Vessel:

Bodies
List of Celestial Bodies
Targets
List of possible target Vessels
Vessel Lists

These generate lists of items on the Vessel:

Processors
List of Processors
Resources
List of AggregateResources
Parts
List of Parts
Engines
List of Engines
Sensors
List of Sensors
Elements
List of Elements that comprise the current vessel.
DockingPorts
list of DockingPorts <DockingPort>
File System Lists

These generate lists about the files in the system:

Files
List the items, both files and subdirectories, on the current Volume at the current directory (you have to use the cd("dir") command to change directories first if you want to get a list of files under some other location.) (note below) The list contains items of type VolumeItem
Volumes
List all the volumes that exist.

Note

LIST FILES. is the default if you give the LIST command no parameters.

Examples:

LIST.  // Prints the list of files (and subdirectories) on current volume.
LIST FILES.  // Does the same exact thing, but more explicitly.
LIST VOLUMES. // which volumes can be seen by this CPU?
LIST FILES IN fileList. // fileList is now a LIST() containing :struct:`VolumeItem` structures.

The file structures returned by LIST FILES IN fileList. are documented on a separate page. The file list contains both actual files and subdirectories under the current directory level. You can use the VolumeItem:IsFile suffix on each element to find out if it’s a file or a subdirectory. If it is a file rather than a subdirectory, then it will also have all the suffixes of VolumeFile on it.

Here are some more examples:

// Prints the list of all
// Celestial bodies in the system.
LIST BODIES.

// Puts the list of bodies into a variable.
LIST BODIES IN bodList.
// Iterate over everything in the list:
SET totMass to 0.
FOR bod in bodList {
    SET totMass to totMass + bod:MASS.
}.
PRINT "The mass of the whole solar system is " + totMass.

// Adds variable foo that contains a list of
// resources for my currently target vessel
LIST RESOURCES FROM TARGET IN foo.
FOR res IN foo {
    PRINT res:NAME. // Will print the name of every
                    // resource in the vessel
}.

Querying a vessel’s parts

This is a quick list to get the idea across fast. The actual details of the meaning of these things is complex enough to warrant its own topic.

To get the parts of a vessel (such as your current vessel, called SHIP), you can do the following things:

These are equivalent. They get the full list of all the parts:

LIST PARTS IN MyPartList.
SET MyPartlist TO SHIP:PARTS.

This gets all the parts that have the name given, as either a nametag (Part:TAG), a title (Part:TITLE), or a name, (Part:NAME):

SET MyPartList to SHIP:PARTSDUBBED("something").

These are other ways to get parts that are more specific about what exact nomenclature system is being used:

SET MyPartList to SHIP:PARTSTAGGED("something"). // only gets parts with Part:TAG = "something".
SET MyPartList to SHIP:PARTSTITLED("something"). // only gets parts with Part:TITLE = "something".
SET MyPartList to SHIP:PARTSNAMED("something"). // only gets parts with Part:NAME = "something".

This gets all the PartModules on a ship that have the same module name:

SET MyModList to SHIP:MODULESNAMED("something").

This gets all the parts that have been defined to have some sort of activity occur from a particular action group:

SET MyPartList to SHIP:PARTSINGROUP( AG1 ). // all the parts in action group 1.

This gets all the modules that have been defined to have some sort of activity occur from a particular action group:

SET MyModList to SHIP:MODULESINGROUP( AG1 ). // all the parts in action group 1.

This gets the primary root part of a vessel (the command core that you placed FIRST when building the ship in the VAB or SPH):

SET firstPart to SHIP:ROOTPART.

This lets you query all the parts that are immediate children of the current part in the tree:

SET firstPart to SHIP:ROOTPART.
FOR P IN firstPart:CHILDREN {
  print "The root part as an immediately attached part called " + P:NAME.
}.

You could keep walking down the tree this way, or go upward with PARENT and HASPARENT:

TODO - NEED TO MAKE A GOOD EXAMPLE OF WALKING THE PARTS TREE HERE WITH RECURSION ONCE THE SYNTAX IS NAILED DOWN FOR THAT.

IF thisPart:HASPARENT {
  print "This part's parent part is "+ thisPart:PARENT:NAME.
}.

File I/O

For information about where files are kept and how to deal with volumes see the Volumes page in the general topics section of this documentation.

Understanding directories

kOS, just as real life filesystems, has the ability to group files into directories. Directories can contain other directories, which can result in a tree-like structure.

Directories, contrary to files, do not take up space on the volume. That means you can have as many directories on your volume as you want.

Paths

kOS uses strings of a specific format as a way of describing the location of files and directories. We will call them path strings or simply - paths. They will look familiar to users of most real operating systems. On Windows for example you might have seen something like this:

C:\Program Files\Some Directory\SomeFile.exe

Linux users are probably more familiar with paths that look like this:

/home/user/somefile

kOS’s paths are quite similar, this is how a full path string might look like:

0:/lib/launch/base.ks

There are two types of paths in kOS. Absolute paths explicitly state all data needed to locate an item. Relative paths describe the location of an item relative to the current directory or current volume.

Warning

Limitations on file names

On case-sensitive filesystems typically found on Linux and Mac, you should name entirely with lowercase-only filenames or the system may fail to find them when you try to access the path.

Absolute paths

Absolute paths have the following format:

volumeIdOrName:[/directory/subdirectory/...]/filename

The first slash immediately after the colon is optional.

Examples of valid absolute paths:

0:flight_data/data.json
secondcpu: // refers to the root directory of a volume
1:/boot.ks

You can use a special two-dot directory name - .. - to denote the parent of a given directory. In the following example the two paths refer to the same file:

0:/directory/subdirectory/../file
0:/directory/file

A path that points to the parent of the root directory of a volume is considered invalid. Those paths are all invalid:

0:..
0:/../..
0:/directory/../..
Current directory

To facilitate the way you interact with volumes, directories and files kOS has a concept of current directory. That means you can make a certain directory a default one and kOS will look for files you pass on to kOS commands in that directory. Let’s say for example that you’re testing a script located on the Archive volume in the launch_scripts directory. Normally every time you’d like to do something with it (edit it, run it, copy it etc) you’d have to tell kOS exactly where that file is. That could be troublesome, especially when it would have to be done multiple times.

Instead you can change your current directory using cd(path) (as in change directory) command and then refer to all the files and directories you need by using their relative paths (read more below).

You can always print out the current directory’s path like this:

PRINT PATH().

Remember that you can print the contents of the current directory using the LIST command (which is a shortcut for LIST FILES).

Relative paths

Relative paths are the second way you can create paths. Those paths are transformed by kOS into absolute paths by adding them to the current directory.

Let’s say that you’ve changed your current directory to 0:/scripts. If you pass launch.ks path to any command kOS will add it to current directory and create an absolute path this way:

CD("0:/scripts").
DELETEPATH("launch.ks"). // this will remove 0:/scripts/launch.ks
COPYPATH("../launch.ks", ""). // this will copy 0:/launch.ks to 0:/scripts/launch.ks

As you can see above an empty relative path results in a path pointing to the current directory.

If a relative path starts with / kOS will only take the current directory’s volume and add it to the relative path:

CD("0:/scripts").
COPYPATH("/launch.ks", "launch_scripts"). // will copy 0:/launch.ks to 0:/scripts/launch_scripts
Paths and bareword arguments

Warning

kOS has historically always allowed you to omit quotes for file names in certain cases. Although it is still possible (explanation below) we recommend against it now. kOS 1.0 has introduced directory support and as a result the number of cases in which omitting quotes would be fine is less than before. Paths like ../file make things very confusing to the kOS parser because kerboscript uses a dot to denote the end of an expression. If you’re used to skipping quotes you might find that now you will often have to add them to make the path understandable to kOS. The only case in which you can reliably omit quotes is when you want to use simple, relative paths: RUN script., CD(dir.ext).

Any of the commands below which use path arguments follow these rules:

  • A path may be an expression which evaluates to a string.
  • A path may also be an undefined identifier which does not match a variable name, in which case the bare word name of the identifier will be used as the path. If the identifier does match a variable name, then it will be evaluated as an expression and the variable’s contents will be used as the path.
  • A bareword path may contain file extensions with dots, provided it does not end in a dot.
  • Bareword filenames containing any characters other than A-Z, 0-9, underscore, and the period extension separator (‘.’), can only be referred to using a string expression (with quotes), and cannot be used as a bareword expression (without quotes). This makes it impossible to construct valid kOS paths that contain slashes using bareword paths - you will need to use quotes.
  • If your filesystem is case-sensitive (Linux and sometimes Mac OSX, or even Windows if using some kinds of remote network drives), then bareword filenames will only work properly on filenames that are all lowercase. If you try to use a file with capital letters in the name on these systems, you will only be able to do so by quoting it.

Putting the above rules together, you can create paths in any of the following ways:

COPYPATH(myfilename, "1:"). // This is an example of a bareword filename.
COPYPATH("myfilename", "1:"). // This is an example of an EXPRESSION filename.
COPYPATH(myfilename.ks, "1:"). // This is an example of a bareword filename.
COPYPATH(myfilename.txt, "1:"). // This is an example of a bareword filename.
COPYPATH("myfilename.ks", "1:"). // This is an example of an EXPRESSION filename
SET str TO "myfile" + "name" + ".ks".
COPYPATH(str, "1:"). // This is an example of an EXPRESSION filename
COPYPATH("myfile" + "name" + ".ks", "1:"). // This is an example of an EXPRESSION filename
Other data types as paths

Whenever kOS expects a path string as an argument you can actually pass one of the following data types instead:

path(pathString)

Will create a Path structure representing the given path string. You can omit the argument to create a Path for the current directory.

scriptpath()

Will return a Path structure representing the path to the currently running script.

Volumes

volume(volumeIdOrName)

Will return a Volume structure representing the volume with a given id or name. You can omit the argument to create a Volume for the current volume.

SWITCH TO Volume|volumeId|volumeName.

Changes the current directory to the root directory of the specified volume. Volumes can be referenced by instances of Volume, their ID numbers or their names if they’ve been given one. Understanding how volumes work is important to understanding this command.

Example:

SWITCH TO 0.                          // Switch to volume 0.
SET VOLUME(1):NAME TO "AwesomeDisk".  // Name volume 1 as AwesomeDisk.
SWITCH TO "AwesomeDisk".              // Switch to volume 1.
PRINT VOLUME(1):NAME.                 // Prints "AwesomeDisk".

Files and directories

Warning

Changed in version 1.0.0: COPY, RENAME and DELETE are now deprecated

Previously you could use the aforementioned commands to manipulate files. Currently using them will result in a deprecation message being shown. After subdirectories were introduced in kOS 1.0 it was necessary to add more flexible commands that could deal with both files and directories. The old syntax was not designed with directories in mind. It would also make it difficult for the kOS parser to properly handle paths.

Please update your scripts to use the new commands: movepath(frompath, topath), copypath(frompath, topath) and deletepath(path). runpath(path).

LIST

Shows a printed list of the files and subdirectories in the current working directory.

This is actually a shorthand for the longer LIST FILES command.

To get the files into a LIST structure you can read in a script (rather than just printed to the screen), use the list files in ... command.

CD(PATH)

Changes the current directory to the one pointed to by the PATH argument. This command will fail if the path is invalid or does not point to an existing directory.

COPYPATH(FROMPATH, TOPATH)

Copies the file or directory pointed to by FROMPATH to the location pointed to TOPATH. Depending on what kind of items both paths point to the exact behaviour of this command will differ:

  1. FROMPATH points to a file

    • TOPATH points to a directory

      The file from FROMPATH will be copied to the directory.

    • TOPATH points to a file

      Contents of the file pointed to by FROMPATH will overwrite the contents of the file pointed to by TOPATH.

    • TOPATH points to a non-existing path

      New file will be created at TOPATH, along with any parent directories if necessary. Its contents will be set to the contents of the file pointed to by FROMPATH.

  2. FROMPATH points to a directory

    If FROMPATH points to a directory kOS will copy recursively all contents of that directory to the target location.

    • TOPATH points to a directory

      The directory from FROMPATH will be copied inside the directory pointed to by TOPATH.

    • TOPATH points to a file

      The command will fail.

    • TOPATH points to a non-existing path

      New directory will be created at TOPATH, along with any parent directories if necessary. Its contents will be set to the contents of the directory pointed to by FROMPATH.

  3. FROMPATH points to a non-existing path

    The command will fail.

MOVEPATH(FROMPATH, TOPATH)

Moves the file or directory pointed to by FROMPATH to the location pointed to TOPATH. Depending on what kind of items both paths point to the exact behaviour of this command will differ, see COPYPATH above.

DELETEPATH(PATH)

Deleted the file or directory pointed to by FROMPATH. Directories are removed along with all the items they contain.

EXISTS(PATH)

Returns true if there exists a file or a directory under the given path, otherwise returns false. Also see Volume:EXISTS.

CREATE(PATH)

Creates a file under the given path. Will create parent directories if needed. It will fail if a file or a directory already exists under the given path. Also see Volume:CREATE.

CREATEDIR(PATH)

Creates a directory under the given path. Will create parent directories if needed. It will fail if a file or a directory already exists under the given path. Also see Volume:CREATEDIR.

OPEN(PATH)

Will return a VolumeFile or VolumeDirectory representing the item pointed to by PATH. It will return a Boolean false if there’s nothing present under the given path. Also see Volume:OPEN.

JSON

WRITEJSON(OBJECT, PATH)

Serializes the given object to JSON format and saves it under the given path.

Go to Serialization page to read more about serialization.

Usage example:

SET L TO LEXICON().
SET NESTED TO QUEUE().

L:ADD("key1", "value1").
L:ADD("key2", NESTED).

NESTED:ADD("nestedvalue").

WRITEJSON(l, "output.json").
READJSON(PATH)

Reads the contents of a file previously created using WRITEJSON and deserializes them.

Go to Serialization page to read more about serialization.

Example:

SET L TO READJSON("output.json").
PRINT L["key1"].

Miscellaneous

Running Scripts

You may run saved script files using the various Run Command.

Examples:

RUNPATH("filename", arg1, arg2).
RUN filename(arg1, arg2).

The topic of the RUNPATH and RUN commands is complex enough to warrant its own separate Run Command Page. Consult that page for the full details of how these commands work.

LOG TEXT TO PATH

Logs the selected text to a file. Can print strings, or the result of an expression.

Arguments
  • argument 1: Value you would like to log.
  • argument 2: Path pointing to the file to log into.

Example:

LOG "Hello" to mylog.txt.    // logs to "mylog.txt".
LOG 4+1 to "mylog" .         // logs to "mylog.ks" because .ks is the default extension.
LOG "4 times 8 is: " + (4*8) to mylog.   // logs to mylog.ks because .ks is the default extension.
COMPILE PROGRAM (TO COMPILEDPROGRAM)

(experimental)

Arguments:

argument 1
Path to the source file.
argument 2
Path to the destination file. If the optional argument 2 is missing, it will assume it’s the same as argument 1, but with a file extension changed to *.ksm.

Pre-compiles a script into an Kerboscript ML Executable image that can be used instead of executing the program script directly.

The RUN, RUNPATH, or RUNONCEPATH commands (mentioned elsewhere on this page) can work with either *.ks script files or *.ksm compiled files.

The full details of this process are long and complex enough to be placed on a separate page.

Please see the details of the Kerboscript ML Executable.

EDIT PATH

Arguments:

argument 1
Path of the file for editing.

Edits or creates a program file described by filename PATH. If the file referred to by PATH already exists, then it will open that file in the built-in editor. If the file referred to by PATH does not already exist, then this command will create it from scratch and let you start editing it.

It is important to type the command using the filename’s .ks extension when using this command to create a new file. (Don’t omit it like you sometimes can in other places in kOS). The logic to automatically assume the .ks extension when the filename has no extension only works when kOS can find an existing file by doing so. If you are creating a brand new file from scratch with the EDIT command, and leave off the .ks extension, you will get a file created just like you described it (without the extension).

Note

The Edit feature was lost in version 0.11 but is back again after version 0.12.2 under a new guise. The best way to edit code is still to use a text editor external to KSP. The on-the-fly editor that this command invokes is still useful however for exploratory test editing and playing around, or for editing a file on a remote probe’s local volume (which isn’t stored as a normal file on your hard drive like files in the archive are, so it can’t be edited with an external text editor program).

Example:

EDIT filename.       // edits filename.ks
EDIT filename.ks.    // edits filename.ks
EDIT "filename.ks".  // edits filename.ks
EDIT "filename".     // edits filename.ks
EDIT "filename.txt". // edits filename.txt

Terminal and game environment

CLEARSCREEN

Clears the screen and places the cursor at the top left:

CLEARSCREEN.
PRINT

Prints the selected text to the screen. Can print strings, or the result of an expression:

PRINT "Hello".
PRINT 4+1.
PRINT "4 times 8 is: " + (4*8).
SET TERMINAL:WIDTH. GET TERMINAL:WIDTH

Gets or sets the terminal’s width in characters. For more information see terminal struct.

SET TERMINAL:HEIGHT. GET TERMINAL:HEIGHT

Gets or sets the terminal’s height in characters. For more information see terminal struct.

AT(col,line)
Parameters:
  • col – (integer) column starting with zero (left)
  • line – (integer) line starting with zero (top)

Used in combination with PRINT. Prints the selected text to the screen at specified location. Can print strings, or the result of an expression:

PRINT "Hello" AT(0,10).
PRINT 4+1 AT(0,10).
PRINT "4 times 8 is: " + (4*8) AT(0,10).
MAPVIEW
Access:Get/Set
Type:boolean

A variable that controls or queries whether or not the game is in map view:

IF MAPVIEW {
    PRINT "You are looking at the map.".
} ELSE {
    PRINT "You are looking at the flight view.".
}.

You can switch between map and flight views by setting this variable:

SET MAPVIEW TO TRUE.  // to map view
SET MAPVIEW TO FALSE. // to flight view
REBOOT

Stops the script here, and reboots the kOS module.

SHUTDOWN

Stops the script here, and causes kOS module to turn the power off.

GUI display tools

VECDRAW

See VECDRAWARGS, below

VECDRAWARGS

You can draw visual vectors on the screen in kOS to help debugging or to help show the player information. The full description can be found on the Vecdraw Page.

HUDTEXT

You can make text messages appear on the heads-up display, in the same way that the in-game stock messages appear, by calling the HUDTEXT function, as follows:

HUDTEXT( string Message,
         integer delaySeconds,
         integer style,
         integer size,
         RGBA colour,
         boolean doEcho).
Message
The message to show to the user on screen
delaySeconds
How long to make the message remain onscreen before it goes away. If another message is drawn while an old message is still displaying, both messages remain, the new message scrolls up the old message.
style
Where to show the message on the screen: - 1 = upper left - 2 = upper center - 3 = lower right - 4 = lower center Note that all these locations have their own defined slightly different fonts and default sizes, enforced by the stock KSP game.
size
A number describing the font point size: NOTE that the actual size varies depending on which of the above styles you’re using. Some of the locations have a magnifying factor attached to their fonts.
colour
The colour to show the text in, using one of the built-in colour names or the RGB constructor to make one up
doEcho
If true, then the message is also echoed to the terminal as “HUD: message”.

Examples:

HUDTEXT("Warning: Vertical Speed too High", 5, 2, 15, red, false).
HUDTEXT("docking mode begun", 8, 1, 12, rgb(1,1,0.5), false).

Serialization

kOS has the ability to transform certain data structures into a format that can be stored in a file or in memory and later reconstruct those objects without any data loss. In computer science this is usually called serialization.

Take for example a slightly complicated data structure - a list that contains other lists. In kOS it would be created like so: LIST(LIST(1,2,3), LIST(4,5)). This is a complex structure that isn’t easy for a kOS developer to store in a file. This is where kOS’s serialization comes in.

There’s no need for you to understand how it works internally. This pages exist primarily to explain two most important things about serialization:

  1. Only certain types of objects can be serialized. If a type is serializable then that fact is explicitly mentioned in the type’s documentation with a note like this one:

Note

This type is serializable.

All collection types (List, Lexicon etc.) are serializable. They can contain other serializable types or primitives (numbers, string, booleans) and still be serializable.

2. Currently there are 2 functionalities within kOS that use serialization. The first are WRITEJSON AND READJSON commands. They allow to transform data structures into JSON objects and store them in a file. The other functionality is Communication. It serializes messages currently stored on message queues to ConfigNode (KSP data format) and adds them to KSP save files.

It is important to remember that any data that you supply to WRITEJSON(OBJECT, PATH) and Connection:SENDMESSAGE must be serializable.

Communication

kOS allows you to write scripts that communicate with scripts running on other processors within the same vessel (inter-processor communication) or on other vessels (inter-vessel communication).

Limitations

While you are able to send messages to vessels that are unloaded, the receiving vessel must be loaded in order to use and reply to the message. This is because kOS is unable to run on an unloaded vessel. The loaded status of a vessel depends on proximity to the active vessel (usually a sphere a couple of kilometers in radius) as well as current situation (landed, orbit, suborbit, etc). For more information about how vessels are loaded, check the loaddistance documentation page. In order to have the receiving vessel reply when unloaded, it will need to be set to the KUNIVERSE:ACTIVEVESSEL or the load distance needs to be adjusted.

Messages

The basic unit of data that is sent between two entities (CPUs or vessels) is called a Message. Messages can contain any primitive (scalars, strings, booleans) data types as well as any serializable types. This allows for a lot of flexibility. It is up to you as a script author to decide what those messages will need to contain in order to achieve a specific task.

kOS will automatically add 3 values to every message that is sent: Message:SENTAT, Message:RECEIVEDAT and Message:SENDER. The original data that the sender has sent can be accessed using Message:CONTENT.

Message queues

Whenever a message is received by a CPU or a vessel it is added to its MessageQueue. Message queues store messages in order they were received in and allow the recipient to read them in the same order.

It is important to understand that every CPU has its own message queue, but also every vessel as a whole has its own queue. A vessel that has 2 processors has 3 message queues in total: one for each of the CPUs and one for the vessel. Why does the vessel has its own, separate queue? Well, if it hadn’t then you as a sender would have to know the names of processors on board the target vessel in order to send the message to a specific CPU. That would complicate the whole process - you would have to store those names for every vessel you plan on contacting in the future. When every vessel has a separate queue the sender doesn’t have to worry about how that message will be handled by the recipient. It also allows you to easily differentiate between messages coming from other CPUs on board the same vessel and messages coming from other vessels. The downside is of course that in certain complex cases it might be necessary for a CPU to operate on two message queues - its own and vessel’s.

There is one major difference in how CPU and vessel queues are handled. Messages on vessel queues are persisted when changing scenes in KSP while messages on CPU queues are not. This difference stems from the fact that when you’re switching from one vessel to another that is further than 2.5km away KSP actually saves the game, constructs a new scene and loads the game from that previously created save file. If messages hadn’t been added to the save file they would be lost and any kind of long distance inter-vessel communication would be impossible. Obviously in the case of inter-processor communication all processors are part of the same vessel. No scene changes are required and hence no need to persist messages.

Connections

Connection structure represents your ability to communicate with a processor or a vessel. Whenever you want to send a message you need to obtain a connection first.

Inter-vessel communication

First we’ll have a look at the scenario where we want to send messages between two processors on different vessels. As the first step we must obtain the Vessel structure associated with the target vessel. We can do that using:

SET V TO VESSEL("vesselname").
Sending messages

Once we have a Vessel structure associated with the vessel we want to send the message to we can easily obtain a Connection to that vessel using Vessel:CONNECTION. Next we’re going to send a message using Connection:SENDMESSAGE. This is an example of how the whole thing could look:

SET MESSAGE TO "HELLO". // can be any serializable value or a primitive
SET C TO VESSEL("probe"):CONNECTION.
PRINT "Delay is " + C:DELAY + " seconds".
IF C:SENDMESSAGE(MESSAGE) {
  PRINT "Message sent!".
}
Receiving messages

We now switch to the second vessel (in the example above it was named “probe.”). It should have a message in its message queue. To access the queue from the current processor we use the SHIP:MESSAGES suffix:

WHEN NOT SHIP:MESSAGES:EMPTY {
  SET RECEIVED TO SHIP:MESSAGES:POP.
  PRINT "Sent by " + RECEIVED:SENDER:NAME + " at " + RECEIVED:SENTAT.
  PRINT RECEIVED:CONTENT.
}

Inter-processor communication

This will be very similar to how inter-vessel communication was done. As the first step we must obtain the kOSProcessor structure associated with the target CPU.

Accessing processors

The easiest way of accessing the processor’s kOSProcessor structure (as long as your CPUs have their name tags set) is to use the following function:

PROCESSOR(volumeOrNameTag)
Parameters:
  • volumeOrNameTag – (Volume | String) can be either an instance of Volume or a string

Depending on the type of the parameter value will either return the processor associated with the given Volume or the processor with the given name tag.

A list of all processors can be obtained using the List command:

LIST PROCESSORS IN ALL_PROCESSORS.
PRINT ALL_PROCESSORS[0]:NAME.

Finally, processors can be accessed directly, like other parts and modules:

PRINT SHIP:MODULESNAMED("kOSProcessor")[0]:VOLUME:NAME.
Sending and receiving messages

Then we can use kOSProcessor:CONNECTION to get the connection to that processor. This is how sender’s code could look like:

SET MESSAGE TO "undock". // can be any serializable value or a primitive
SET P TO PROCESSOR("probe").
IF P:CONNECTION:SENDMESSAGE(MESSAGE) {
  PRINT "Message sent!".
}

The receiving CPU will use CORE:MESSAGES to access its message queue:

WAIT UNTIL NOT CORE:MESSAGES:EMPTY. // make sure we've received something
SET RECEIVED TO CORE:MESSAGES:POP.
IF RECEIVED:CONTENT = "undock" {
  PRINT "Undocking!!!".
  UNDOCK().
} ELSE {
  PRINT "Unexpected message: " + RECEIVED:CONTENT.
}

Connectivity Managers

Note

New in version v1.0.2: The concept of selectable connectivity managers was added after KSP introduced a stock communications system (CommNet). kOS was updated to support both CommNet and RemoteTech. Other mods may be supported or provide their own support in the future.

kOS can implement communications over a variaty of connectivity configurations. We refer to these options as “Connectivity Managers.” You can slect the active manager from the kOS section of KSP’s Difficulty Settings. If the currently selected manager no longer is available, or if a new manager becomes available, you will be prompted with a dialog box to select the manager you want to use.

Connectivity Managers

By default kOS supports the following list of managers. If a manager is not currently available (because the required mod isn’t installed, or the system is disabled in the settings) it will not be shown in lists of available managers.

PermitAllConnectivityManager
This manager will permit all connectivity at all times. Connections between vessels, home, and control always show as being connected. This is the equivalent of setting CONFIG:RT to False under the former system. It is possible kOS will be unable to use all features if this manager is selected while communication limitations are enforced by another mod or a setting.
CommNetConnectivityManager

This manager will use KSP’s stock CommNet implementation to monitor connections. It will only be available if CommNet is enabled in the KSP difficulty settings.

Note

CommNet has limitations on updating connections for vessels which are not the active vessel. The best way to ensure that a connection is updated is to include one of the kinds of antenna that can act as a relay on one or both of the vessels.

RemoteTechConnectivityManager
This manager will use the RemoteTech mod to monitor connections. It will only be available if RemoteTech is installed. You can access more detailed information and methods using the RemoteTech Addon

Warning

Take care when configuring your game for connectivity. Enabling multiple systems at the same time may result in unexpected behaviors both for the game connectivity itself, and for kOS’s connectivity manager. kOS only supports selecting one connectivity manager at a time and you should ensure that only the corresponding in game connectivity system is enabled.

You can check communication status between vessels by checking the Vessel:CONNECTION. To monitor the status for home and control connections the following bound variables are available. This allows you to monitor the basic network status using a single unified system, regardless of which connectivity manager is selected.

HOMECONNECTION

Returns a Connection representing the CPU Vessel’s communication line to a network “home” node. This home node may be the KSC ground station, or other ground stations added by the CommNet settings or RemoteTech. Functionally, this connection may be used to determine if the archive volume is accessible.

Warning

Attempting to send a message to the “home” connection will result in an error message. While this connection uses the same structure as when sending inter-vessel and inter-processor messages, message support is not included.

CONTROLCONNECTION

Returns a Connection representing the CPU Vessel’s communication line to a control source. This may be the same as the HOMECONNECTION, or it may represent a local crewed command pod, or it may represent a connection to a control station. When using the CommNetConnectivityManager this should show as connected whenever a vessel has partial manned control, or full control. Functionally this may be used to determine if terminal input is available, and what the potential signal delay may be for this input.

Warning

Attempting to send a message to the “control” connection will result in an error message. While this connection uses the same structure as when sending inter-vessel and inter-processor messages, message support is not included.

Transferring resources

Resource transfer is an important part of many missions in Kerbal Space Program, whether you are gassing up a lander or refuelling a station. Because of this, we need a way to describe a resource transfer in script and monitor that transfer to be sure it was successful.

To accomplish all of this we have two new functions

SET transferFoo TO TRANSFER(resourceName, fromParts, toParts, amount).
SET transferBar TO TRANSFERALL(resourceName, fromParts, toParts).

TRANSFER will move the specified amount of a resource, where TRANSFERALL will move the resource until the source is empty or the destination is full. Both functions return a new ResourceTransfer.

Resource

in all transfers, you must specify a resource by name (eg “oxidizer”, “LIQUIDFUEL”, etc). this term is not case sensitive.

Source and Destination

In all transfers, you must specify a source and a destination, both of these need to be one of these three types:

Examples

Building a list of Element, then transferring all of the oxidizer from one element to another:

LIST ELEMENTS IN elist.
SET foo TO TRANSFERALL("OXIDIZER", elist[0], elist[1]).
SET foo:ACTIVE to TRUE.

Finding two parts by index, then transferring all of the oxidizer from part 1 to part 2:

SET foo TO TRANSFERALL("OXIDIZER", SHIP:PARTS[0], SHIP:PARTS[1]).
SET foo:ACTIVE to TRUE.

Finding two lists of parts by tag, then transferring 100 units of liquidfuel:

SET sourceParts to SHIP:PARTSDUBBED("fooPart").
SET destinationParts to SHIP:PARTSDUBBED("barPart").
SET foo TO TRANSFER("liquidfuel", sourceParts, destinationParts, 100).
SET foo:ACTIVE to TRUE.

Structure Reference

A general discussion of structures can be found here.

Reflection

“Reflection” refers to the act of a program being self-referrential by examining information about itself. Kerboscript does not provide the full reflection characteristics of some languages, but it does allow a few reflection-like abilities via its generic Structure type.

Structure:

The root of all kOS data types.

Overview

New in version 0.19.0: This is a new feature of kOS v0.19.0. Most of what is documented on this page won’t work on earlier versions.

All types of data that a kOS program can access, either via a variable, or a suffix return value, or really just any expression’s temporary result, are now directly or indirectly derived from this one base type called just Structure.

That means that there are a few generic suffixes that you should be able to use on any value anywhere. This page documents those suffixes.

This is true even of primitive value types such as 1.0 or false or 42 or "abc". For example, you can do:

print Mun:typename().
Body   // <--- system prints this

print ("hello"):typename().
String // <--- system prints this

print (12345.678):typename().
Scalar // <--- system prints this
structure Structure
Suffix Type Description
TOSTRING String The string that gets shown on-screen when doing the PRINT command.
HASSUFFIX(name) Boolean Test whether or not this value has a suffix with the given name.
SUFFIXNAMES List of strings Gives a list of all the names of all the suffixes this thing has.
ISSERIALIZABLE Boolean Is true if this type is one that works with WRITEJSON
TYPENAME String Gives a string for the name of the type of this object.
ISTYPE(name) Boolean true if this value is of the given type name, or is derived from the given type name
INHERITANCE String Gives a string describing the kOS type, and the kOS types it is inherited from.
Structure:TOSTRING
Type:String
Access:Get only

When issuing the command PRINT aaa., the variable aaa gets converted to a string and then the string is shown on the screen. This suffix universally lets you get that string version of any item, rather than showing it on the screen.

Structure:HASSUFFIX
Parameter name:String name of the suffix being tested for
Type:Boolean
Access:Get only

Given the name of a suffix, returns true if the object has a suffix by that name. For example, if you have a variable that might be a vessel, or might be a Body, then this example:

print thingy:hassuffix("maxthrust").

would print True if thingy was a vessel of some sort, but False if thingy was a body, because there exists a maxthrust suffix for vessels but not for bodies.

When searching for suffix names, the search is performed in a case-insensitive way. Kerboscript cannot distinguish ”:AAA” and ”:aaa” as being two different suffixes. In kerboscript, they’d be the same suffix.

Structure:SUFFIXNAMES
Type:List of strings
Access:Get only

Returns a list of all the string names of the suffixes that can be used by the thing you call it on. As of this release, no information is shown about the parameters the suffix expects, or about the return value it gives. All you see is the suffix names.

If this object’s type is inherited from other types (for example, a Body is also a kind of Orbitable.) then what you see here contains the list of all the suffixes from the base type as well. (Therefore the suffixes described here on this very page always appear in the list for any type.)

Note, for some objects, like Vessels, this can be a rather long list.

The list is returned sorted in alphabetical order.

Example:

set v1 to V(12,41,0.1). // v1 is a vector
print v1:suffixnames.
List of 14 items:
[0] = DIRECTION
[1] = HASSUFFIX
[2] = ISSERIALIZABLE
[3] = ISTYPE
[4] = MAG
[5] = NORMALIZED
[6] = SQRMAGNITUDE
[7] = SUFFIXNAMES
[8] = TOSTRING
[9] = TYPENAME
[10] = VEC
[11] = X
[12] = Y
[13] = Z
Structure:TYPENAME
Type:String
Access:Get only

Gives the name of the type of the object, in kOS terminology.

Type names correspond to the types mentioned throughout these documentation pages, at the tops of the tables that list suffixes.

Examples:

set x to 1.
print x:typename
Scalar

set x to 1.1.
print x:typename
Scalar

set x to ship:parts[2].
print x:typename
Part

set x to Mun.
print x:typename
Body

The kOS types described in these documentaion pages correspond one-to-one with underlying types in the C# code the implements them. However they don’t have the same name as the underlying C# names. This returns an abstraction of the C# name. There are a few places in the C# code where an error message will mention the C# type name instead of the kOS type name. This is an issue that might be resolved in a later release.

Structure:ISTYPE
Parameter name:string name of the type being checked for
Type:Boolean
Access:Get only

This is True if the value is of the type mentioned in the name, or if it is a type that is derived from the type mentioned in the name. Otherwise it is False.

Example:

set x to SHIP.
print x:istype("Vessel").
True
print x:istype("Orbitable").
True
print x:istype("Structure").
True.
print x:istype("Body").
False
print x:istype("Vector").
False
print x:istype("Some bogus type name that doesn't exist").
False

The type name is searched in a case-insensitive way.

Structure:INHERITANCE
Type:String
Access:Get only

Gives a string describing the typename of this value, and the typename of the type this value is inherited from, and the typename of the type that type is inherited from, etc all the way to this root type of Structure that all values share.

Example:

set x to SHIP.
print x:inheritance.
Vessel derived from Orbitable derived from Structure

(The kOS types described in that string are an abstraction of the underlying C# names in the mod’s implementation, and a few of the C# types the mod uses to abstract a few things are skipped along the way, as they are types the script code can’t see directly.)

Structure:ISSERIALIZABLE
Type:Boolean
Access:Get only

Not all types can be saved using the built-in serialization function WRITEJSON. For those that can, values of that type will return True for this suffix, otherwise it returns False.

Collections

Enumerable

Enumerable is a parent structure that contains a set of suffixes common to few structures in kOS. As a user of kOS you will never handle pure instances of this structure, but rather concrete types like List, Range, Queue etc.

Structure
structure Enumerable
Members
Suffix Type Description
ITERATOR Iterator for iterating over the elements
REVERSEITERATOR Iterator for iterating over the elements in the reverse order
LENGTH scalar number of elements in the enumerable
CONTAINS(item) Boolean check if enumerable contains an item
EMPTY Boolean check if enumerable is empty
DUMP string verbose dump of all contained elements
Enumerable:ITERATOR
Type:Iterator
Access:Get only

An alternate means of iterating over an enumerable. See: Iterator.

Enumerable:REVERSEITERATOR
Type:Iterator
Access:Get only

An alternate means of iterating over an enumerable. Order of items is reversed. See: Iterator.

Enumerable:LENGTH
Type:scalar
Access:Get only

Returns the number of elements in the enumerable.

Enumerable:CONTAINS(item)
Parameters:
  • item – element whose presence in the enumerable should be checked
Returns:

Boolean

Returns true if the enumerable contains an item equal to the one passed as an argument

Enumerable:EMPTY
Type:Boolean
Access:Get only

Returns true if the enumerable has zero items in it.

Enumerable:DUMP
Type:string
Access:Get only

Returns a string containing a verbose dump of the enumerable’s contents.

Iterator

An iterator can be obtained from List:ITERATOR as well as from other places. An ITERATOR is a generic computer programming concept. In the general case it’s a variable type that allows you to get the value at a position in some collection, as well as increment to the next item in the collection in order to operate on all objects in the collection one at a time. In kOS it operates on Lists and most other collection types.

A loop using an Iterator on a List might look like this:

// Starting with a list that was built like this
SET MyList To LIST( "Hello", "Aloha", "Bonjour").

// It could be looped over like this
SET MyCurrent TO MyList:ITERATOR.
PRINT "before the first NEXT, position = " + MyCurrent:INDEX.
UNTIL NOT MyCurrent:NEXT {
    PRINT "Item at position " + MyIter:INDEX + " is [" + MyIter:VALUE + "].".
}

Which would result in this output:

before the first NEXT, position = -1.
Item at position 0 is [Hello].
Item at position 1 is [Aloha].
Item at position 2 is [Bonjour].

When you first create an iterator by using an ITERATOR suffix of some collection type like List, List, or even String, the initial position of the index is always -1, and the current value is always invalid. This represents a position just before the start of the list of items. Only after the first time NEXT is called does the value of VALUE become usable as the first thing in the collection.

Rewinding No Longer Supported

Note

There used to be a :RESET method for iterators, but it has been removed as it was not always implemented and sometimes gave an error. Now to start the enumeration over you need to obtain a new iterator.

Members
structure Iterator
Members
Suffix Type Description
RESET n/a (This method has been removed)
NEXT boolean Move iterator to the next item
ATEND boolean Check if iterator is at the end of the list
INDEX scalar Current index starting from zero
VALUE varies The object currently being pointed to
Iterator:RESET()
Returns:n/a

This suffix has been deleted from kOS.

Note

Previous versions of kOS had a :RESET suffix for Iterators. This doesn’t exist anymore and is being left in the documentation here just so people trying to search for it will find this message explaining where it went. kOS had to drop it because it’s no longer as easy to implement it under the hood with newer versions of .Net.

(If you want to restart an iteration you must call the :ITERATOR suffix of the collection again to obtain a new iterator.)

Iterator:NEXT()
Returns:boolean

Call this to move the iterator to the next item in the list. Returns true if there is such an item, or false if no such item exists because it’s already at the end of the list.

Iterator:ATEND
Access:Get only
Type:boolean

Returns true if the iterator is at the end of the list and therefore cannot be “NEXTed”, false otherwise.

Iterator:INDEX
Access:Get only
Type:scalar (integer)

Returns the numerical index of how far you are into the list, starting the counting at 0 for the first item in the list. The last item in the list is numbered N-1, where N is the number of items in the list.

Note

If you have just created the ITERATOR, then the value of Iterator:INDEX is -1. It only becomes 0 after the first call to Iterator:NEXT.

Iterator:VALUE
Access:Get only
Type:varies

Returns the thing stored at the current position in the list.

Lexicon

A Lexicon is an associative array, and is similar to the LIST type. If you are an experienced programmer who already knows what “associative array” means, you can probably skip this section and go to the next part of the page further down, otherwise read on:

In a normal array, or in kerboscript’s LIST type you specify which item in the list you want by giving its integer position in the list.

But in a Lexicon, you store pairs of keys and values, where the keys can be any type of thing you like, not just integers, and then you specify which item you want by using that key’s value.

Here’s a small example:

set arr to lexicon().
arr:add( "ABC", 1234.1 ).
arr:add( "Carmine", 4.1 ).
print arr["ABC"]. // prints 1234.1
print arr["Carmine"]. // prints 4.1

Notice how it looks a lot like a list, but the values in the index brackets are strings instead of integers. This is the most common use of a lexicon, to use strings as the key index values (and in fact why it’s called “lexicon”). However you can really use any value you feel like for the keys - strings, RGB colors, numbers, etc.

Lexicons are case-insensitive

One important difference between Lexicons in kerboscript and associative arrays in most other languages is that kerboscript Lexicons use case-insensitive keys by default (when the keys are strings). This behaviour can be changed with the :CASESENSITIVE flag described below.

Constructing a lexicon

If you wish to make your own lexicon from scratch you can do so with the LEXICON() or LEX() built-in function:

// Make an empty lexicon with zero items in it:
set mylexicon to lexicon().

If LEXICON() is given arguments then they are interpreted as alternating keys and values:

set mylexicon to lexicon("key1", "value1", "key2", "value2").

Will have the same effect as:

set mylexicon to lexicon().
mylexicon["key1"] = "value1".
mylexicon["key2"] = "value2".

Obviously when this syntax is used an even number of arguments is expected.

You can also pass any enumerable to LEXICON(). Its elements will be interpreted as alternating keys and values just like above. The following will have the same effect as the previous code fragment:

set mylist to list("key1", "value1", "key2", "value2").
set mylexicon to lexicon(mylist).

The keys and the values of a lexicon can be any type you feel like, and do not need to be of a homogeneous type.

Structure
structure Lexicon
Members
Suffix Type Description
ADD(key,value) None append an item to the lexicon
CASESENSITIVE boolean changes the behaviour of string based keys. Which are by default case insensitive. Setting this will clear the lexicon.
CASE boolean A synonym for CASESENSITIVE
CLEAR None remove all items in the lexicon
COPY Lexicon returns a (shallow) copy of the contents of the lexicon
DUMP string verbose dump of all contained elements
HASKEY(keyvalue) boolean does the lexicon have a key of the given value?
HASVALUE(value) boolean does the lexicon have a value of the given value?
KEYS List gives a flat List of the keys in the lexicon
VALUES List gives a flat List of the values in the lexicon
LENGTH scalar number of pairs in the lexicon
REMOVE(keyvalue) None removes the pair with the given key

Note

This type is serializable.

Lexicon:ADD(key, value)
Parameters:
  • key – (any type) a unique key
  • value – (any type) a value that is to be associated to the key

Adds an additional pair to the lexicon.

Lexicon:CASESENSITIVE
Type:Boolean
Access:Get or Set

The case sensitivity behaviour of the lexicon when the keys are strings. By default, all kerboscript lexicons use case-insensitive keys, at least for those keys that are string types, meaning that mylexicon[“AAA”] means the same exact thing as mylexicon[“aaa”]. If you do not want this behaviour, and instead want the key “AAA” to be different from the key “aaa”, you can set this value to true.

Be aware, however, that if you change this, it has the side effect of clearing out the entire contents of the lexicon. This is done so as to avoid any potential clashes when the rules about what constitutes a duplicate key changed after the lexicon was already populated. Therefore you should probably only set this on a brand new lexicon, right after you’ve created it, and never change it after that.

Lexicon:CASE
Type:Boolean
Access:Get or Set

Synonym for CASESENSITIVE (see above).

Lexicon:REMOVE(key)
Parameters:
  • key – the keyvalue of the pair to be removed

Remove the pair with the given key from the lexicon.

Lexicon:CLEAR()

Removes all of the pairs from the lexicon. Making it empty.

Lexicon:LENGTH
Type:scalar
Access:Get only

Returns the number of pairs in the lexicon.

Lexicon:COPY()
Return type:Lexicon
Access:Get only

Returns a new lexicon that contains the same set of pairs as this lexicon. Note that this is a “shallow” copy, meaning that if there is a value in the list that refers to, for example, another Lexicon, or a Vessel, or a Part, the new copy will still be referring to the same object as the original copy in that value.

Lexicon:HASKEY(key)
Parameters:
  • key – (any type)
Returns:

boolean

Returns true if the lexicon contains the provided key

Lexicon:HASVALUE(key)
Parameters:
  • key – (any type)
Returns:

boolean

Returns true if the lexicon contains the provided value

Lexicon:DUMP
Type:string
Access:Get only

Returns a string containing a verbose dump of the lexicon’s contents.

The difference between a DUMP and just the normal printing of a Lexicon is in whether or not it recursively shows you the contents of every complex object inside the Lexicon.

i.e:

// Just gives a shallow list:
print mylexicon.

// Walks the entire tree of contents, descending down into
// any Lists or Lexicons that are stored inside this Lexicon:
print mylexicon:dump.
Lexicon:KEYS
Type:List
Access:Get only

Returns a List of the keys stored in this lexicon.

Lexicon:VALUES
Type:List
Access:Get only

Returns a List of the values stored in this lexicon.

Access to Individual Elements
lexicon[expression]
operator: another syntax to access the element at position ‘expression’. Works for get or set. Any arbitrary complex expression may be used with this syntax, not just a number or variable name.
FOR VAR IN LEXICON:KEYS { ... }.
A type of loop in which var iterates over all the items of lexicon from item 0 to item LENGTH-1.
Implicit ADD when using index brackets with new key values

(a.k.a. The difference between GETTING and SETTING with nonexistant keys)

If you attempt to use a key that does not exist in the lexicon, to GET a value, as follows:

SET ARR TO LEXICON().
SET X TO ARR["somekey"].  // this will produce an error.

Then you will get a KOSKeyNotFoundException error, as you might expect, because the key "somekey" isn’t there in the empty lexicon you just made.

However if you use a key that does not exist yet to SET a value rather than to GET a value, you don’t get an error. Instead it actually implicitly ADDS the new value to the lexicon with that key. The example below will not give you an error:

SET ARR TO LEXICON().
SET ARR["somekey"] TO 100. // adds new value to the lexicon.

The above ends up doing the same thing as if you had done this:

SET ARR TO LEXICON().
ARR:ADD("somekey",100).

Note that while using :ADD() to make a new value in the lexicon will give you a duplicate key error if the value already does exist, using SET to create the value implicitly won’t because it simply replaces the existing value in-place rather than trying to make a new one.

This gives a duplicate key error:

SET ARR TO LEXICON().
ARR:ADD("somekey",100).
ARR:ADD("somekey",200).  // error, because "somekey" already exists.

While this does not:

SET ARR TO LEXICON().
SET ARR["somekey"] to 100.
SET ARR["somekey"] to 200. // no error, because it replaces the value 100 with a 200.

In a nutshell, using [..] to set a value in a lexicon does this: If the key already exists, replace the value with the new value. If the key does not already exist, make it exist and give it this new value.

Examples
SET BAR TO LEXICON().       // Creates a new empty lexicon in BAR variable
BAR:ADD("FIRST",10).        // Adds a new element to the lexicon with the key of "FIRST"
BAR:ADD("SECOND",20).       // Adds a new element to the lexicon with the key of "SECOND"
BAR:ADD("LAST",30).         // Adds a new element to the lexicon with the key of "LAST"

PRINT BAR["FIRST"].            // Prints 10
PRINT BAR["SECOND"].            // Prints 20
PRINT BAR["LAST"].            // Prints 30

SET FOO TO LEXICON().           // Creates a new empty lexicon in FOO variable
FOO:ADD("ALTITUDE", ALTITUDE).  // Adds current altitude number to the lexicon
FOO:ADD("ETA", ETA:APOAPSIS).   // Adds current seconds to apoapsis to the lexicon at the index "ETA"

// As a reminder, at this point your lexicon, if you did all the above
// steps in order, would look like this now:
//
//  FOO["ALTITUDE"] = 99999. // or whatever your altitude was when you added it.
//  FOO["ETA"] = 99. // or whatever your ETA:APOAPSIS was when you added it.

PRINT FOO:LENGTH.        // Prints 2
PRINT FOO:LENGTH().      // Also prints 2.  LENGTH is a method that, because it takes zero arguments, can omit the parentheses.
SET x TO "ALTITUDE". PRINT FOO[x].  // Prints the same thing as FOO["ALTITUDE"].

FOO:REMOVE("ALTITUDE").              // Removes the element at "ALTITUDE" from the lexicon.

List

A List is a collection of any type in kOS. Many places throughout the system return variables of the List type, and you can create your own List variables as well. One of the places you are likely to find that kOS gives you a List is when you use the LIST command to list some query into one of your variables.

Constructing a list

Numerous built-in functions in kOS return a list. If you wish to make your own list from scratch you can do so with the LIST() built-in function. You pass a varying number of arguments into it to pre-populate the list with an initial list of items:

// Make an empty list with zero items in it:
set mylist to list().
// Make a list with 3 numbers in it:
set mylist to list(10,20,30).
// Make a list with 3 strings in it:
set mylist to list("10","20","30").
// Make a two dimensional 2x3 list with heterogenious contents
// mixing strings and numbers:
set mylist to list( list("a","b","c"), list(1,2,3) ).

The contents of a list can be any objects you feel like, and do not need to be of a homogeneous type.

Structure
structure List
Suffix Type Description
All suffixes of Enumerable   List objects are a type of Enumerable
ADD(item) None append an item
INSERT(index,item) None insert item at index
REMOVE(index) None remove item by index
CLEAR() None remove all elements
COPY List a new copy of this list
SUBLIST(index,length) List new list of given length starting with index
JOIN(separator) string joins all list elements into a string

Note

This type is serializable.

List:ADD(item)
Parameters:
  • item – (any type) item to be added

Appends the new value given to the end of the list.

List:INSERT(index,item)
Parameters:
  • index – (integer) position in list (starting from zero)
  • item – (any type) item to be added

Inserts a new value at the position given, pushing all the other values in the list (if any) one spot to the right.

List:REMOVE(index)
Parameters:
  • index – (integer) position in list (starting from zero)

Remove the item from the list at the numeric index given, with counting starting at the first item being item zero

List:CLEAR()
Returns:none

Calling this suffix will remove all of the items currently stored in the List.

List:COPY
Type:List
Access:Get only

Returns a new list that contains the same thing as the old list.

List:SUBLIST(index,length)
Parameters:
  • index – (integer) starting index (from zero)
  • length – (integer) resulting length of returned List
Returns:

List

Returns a new list that contains a subset of this list starting at the given index number, and running for the given length of items.

List:JOIN(separator)
Parameters:
  • separator – (string) separator that will be inserted between the list items
Returns:

string

Returns a string created by converting each element of the array to a string, separated by the given separator.

Access to Individual Elements

All list indexes start counting at zero. (The list elements are numbered from 0 to N-1 rather than from 1 to N.)

list[expression]
operator: another syntax to access the element at position ‘expression’. Works for get or set. Any arbitrary complex expression may be used with this syntax, not just a number or variable name. This syntax is preferred over the older “#” syntax, which is kept only for backward compatibility.
FOR VAR IN LIST { ... }.
A type of loop in which var iterates over all the items of list from item 0 to item LENGTH-1.
ITERATOR
An alternate means of iterating over a list. See Iterator.
list#x (deprecated)
operator: access the element at postion x. Works for get or set. X must be a hardcoded number or a variable name. This is here for backward compatibility. The syntax in the next bullet point is preferred over this.

Examples:

SET BAR TO LIST(5,3,6).  // Creates a new list with 3 integers in it.
SET FOO TO LIST().       // Creates a new empty list in FOO variable
FOO:ADD(5).              // Adds a new element to the end of the list
FOO:ADD( ALTITUDE ).     // Adds current altitude number to the end of the list
FOO:ADD(ETA:APOAPSIS).   // Adds current seconds to apoapsis to the end of the list

// As a reminder, at this point your list, if you did all the above
// steps in order, would look like this now:
//
//  FOO[0] = 5.
//  FOO[1] = 99999. // or whatever your altitude was when you added it.
//  FOO[2] = 99. // or whatever your ETA:APOAPSIS was when you added it.

PRINT FOO:LENGTH.        // Prints 3
PRINT FOO:LENGTH().      // Also prints 3.  LENGTH is a method that, because it takes zero arguments, can omit the parentheses.
PRINT FOO#0.             // Prints 5, using deprecated old '#' syntax.
PRINT FOO[0].            // Prints 5, using newer preferred '[]' syntax.
PRINT FOO[1].            // Prints altitude number.
PRINT FOO[2].            // Prints eta:apoapsis number.
SET x TO 2. PRINT FOO#x. // Prints the same thing as FOO[2], using deprecated old '#' syntax.
SET x TO 2. PRINT FOO[x].// Prints the same thing as FOO[2].
SET y to 3. PRINT FOO[ y/3 + 1 ].
                         // Prints the same thing as FOO#2, using a mathematical expression as the index.
SET FOO#0 to 4.          // Replace the 5 at position 0 with a 4.
FOO:INSERT(0,"skipper 1"). // Inserts the string "skipper 1" to the start of the list, pushing the rest of the contents right.
FOO:INSERT(2,"skipper 2"). // Inserts the string "skipper 2" at position 2 of the list, pushing the rest of the contents right.

// As a reminder, at this point your list, if you did all the above
// steps in order, would look like this now:
//
//  FOO[0] = "skipper 1".
//  FOO[1] = 5.
//  FOO[2] = "skipper 2".
//  FOO[3] = 99999. // or whatever your altitude was when you added it.
//  FOO[4] = 99. // or whatever your ETA:APOAPSIS was when you added it.

FOO:REMOVE( 1).              // Removes the element at index 1 from the list, moving everything else back one.
FOO:REMOVE(FOO:LENGTH - 1).  // Removes whatever element happens to be at the end of the list, at position length-1.

// As a reminder, at this point your list, if you did all the above
// steps in order, would look like this now:
//
//  FOO[0] = "skipper 1".
//  FOO[1] = "skipper 2".
//  FOO[2] = 99999. // or whatever your altitude was when you added it.

SET BAR TO FOO:COPY.     // Makes a copy of the FOO list
FOO:CLEAR.               // Removes all elements from the FOO list.
FOO:CLEAR().             // Also removes all elements from the FOO list.  The parentheses are optional because the method takes zero arguments.
FOR var in BAR {         // --.
  print var.             //   |-- Print all the contents of FOO.
}.                       // --'
Multidimensional Arrays

A 2-D array is a List who’s elements are themselves also Lists. A 3-D array is a List of Lists of Lists. Any number of dimensions is possible.

list[x][y] (or list#x#y)
Access the element at position x,y of the 2-D array (list of lists). The use of the ‘#’ syntax is deprecated and exists for backward compatibility only. The newer ‘[]’ square-bracket syntax is preferred.
  • The elements of the array need not be uniform (any mix of strings, numbers, structures is allowed).

  • The dimensions of the array need not be uniform (row 1 might have 3 columns while row 2 has 5 columns):

    SET FOO TO LIST(). // Empty list.
    FOO:ADD( LIST() ). // Element 0 is now itself a list.
    FOO[0]:ADD( "A" ). // Element 0,0 is now "A".
    FOO[0]:ADD( "B" ). // Element 0,1 is now "B".
    FOO:ADD(LIST()).   // Element 1 is now itself a list.
    FOO[1]:ADD(10).    // Element 1,0 is now 10.
    FOO[1]:ADD(20).    // Element 1,1 is now 20.
    FOO:ADD(LIST()).   // Element 2 is now itself a list.
    
    FOO[ FOO:LENGTH -1 ]:ADD(3.14159).
        // Element 2,0 is now 3.1519, using a more complex
        //     expression to dynamically obtain the current
        //     maximum index of '2'.
    
    FOO[ FOO:LENGTH -1 ]:ADD(7).
        // Element 2,1 is now 7, using a more complex
        //     expression to dynamically obtain the current
        //     maximum index of '2'.
    
    // FOO is now a 2x3 matrix looking like this:
    //    A         B
    //    10        20
    //    3.14159   7
    
    // or like this, depending on how you want
    // to visualize it as a row-first or column-first table:
    //    A    10     3.14159
    //    B    20     7
    
    PRINT FOO[0][0]. // Prints A.
    PRINT FOO[0][1]. // Prints B.
    PRINT FOO[1][0]. // Prints 10.
    PRINT FOO[1][1]. // Prints 20.
    PRINT FOO[2][0]. // Prints 3.14159.
    PRINT FOO[2][1]. // Prints 7.
    
    PRINT FOO#2#0.   // Prints 3.14159, using deprecated syntax.
    
Comparing two lists

Note that if you have two lists, LISTA and LISTB, and you tried to compare if they were the same, in this way:

if LISTA = LISTB {
  print "they are equal".
}

Then the check will only be true if LISTA and LISTB are both actually the same list - not just two lists with equal contents, but in fact just two variables pointing to the same list.

This is because a LIST is a complex structure object, and like most complex structure objects, the equality check is just testing whether or not they refer to the same object, not whether or not they have equivalent content.

To test if the contents are equivalent, you have to check them item by item, like so:

set still_same to true.
FROM {local i is 0.}
  UNTIL i > LISTA:LENGTH or not still_same
  STEP {set i to i + 1.}
DO
{
  set still_same to (LISTA[i] = LISTB[i]).
}
if still_same {
  print "they are equal".
}

Queue

A Queue is a collection of any type in kOS. Queues work according to First-In first-out principle. It may be useful to contrast Queue with Stack to better understand how both structures work. You can read more about queues on Wikipedia.

Using a queue
SET Q TO QUEUE().
Q:PUSH("alice").
Q:PUSH("bob").

PRINT Q:POP. // will print 'alice'
PRINT Q:POP. // will print 'bob'
Structure
structure Queue
Members
Suffix Type Description
All suffixes of Enumerable   Queue objects are a type of Enumerable
PUSH(item) None add item to the end of the queue
POP() any type returns the first element in the queue and removes it
PEEK() any type returns the first element in the queue without removing it
CLEAR() None remove all elements
COPY Queue a new copy of this queue

Note

This type is serializable.

Queue:PUSH(item)
Parameters:
  • item – (any type) item to be added

Adds the item to the end of the queue.

Queue:POP()

Returns the item in the front of the queue and removes it.

Queue:PEEK()

Returns the item in the front of the queue without removing it.

Queue:CLEAR()

Removes all elements from the queue.

Queue:COPY
Type:Queue
Access:Get only

Returns a new queue that contains the same thing as the old one.

Range

Range is a type that represents a sequence of scalar whole numbers. The sequence can start and finish at any whole number, can be either descending or ascending and can skip numbers.

Note

This is one of the few places in kOS where there is a distinction between decimal (floating point, or fractional) scalar numbers and whole (integer, or round) scalar numbers. Using a decimal scalar will not throw an error, however it may give unexpected results due to rounding.

There are 3 ways of constructing a Range:

  • RANGE(START, STOP, STEP)

    Will create a sequence of numbers that starts counting with START, and stops counting just before but not including STOP, counting by increments of size STEP. In formal mathematics terms, the bounds are [START,`STOP`), rather than [START,`STOP`].

    RANGE(3,8,1) will contain numbers 3, 4, 5, 6, and 7.

    RANGE(3,8,2) will contain numbers 3, 5 and 7.

    Will count backward automatically if need be: If START > STOP then the sequence will be descending. STEP should always be > 0 even when the sequence counts backward like this.

    RANGE(2,-9,3) will contain numbers 2, -1, -4 and -7.

  • RANGE(START, STOP)

    Same as above but STEP is assumed to be 1.

  • RANGE(STOP)

    Same as above but STEP is assumed to be 1, and START is assumed to be 0.

Code examples
FOR I IN RANGE(5) {
  PRINT I.
}
// will print numbers 0,1,2,3,4

FOR I IN RANGE(2, 5) {
  PRINT I*I.
}
// will print 4, 9 and 16
Structure
structure Range
Members
Suffix Type Description
All suffixes of Enumerable   Range objects are a type of Enumerable
START scalar initial element of the range
STOP scalar range limit
STEP scalar step size

Note

This type is serializable.

Range:START
Type:scalar
Access:Get only

Returns the initial element of the range. Must be a round number.

Range:STOP
Type:scalar
Access:Get only

Returns the range limit. Must be a round number.

Range:STEP
Type:scalar
Access:Get only

Returns the step size. Must be a round number.

Stack

A Stack is a collection of any type in kOS. Stacks work according to Last-In first-out principle. It may be useful to contrast Stack with Queue to better understand how both structures work. You can read more about stacks on Wikipedia.

Using a stack
SET S TO STACK().
S:PUSH("alice").
S:PUSH("bob").

PRINT S:POP. // will print 'bob'
PRINT S:POP. // will print 'alice'
Structure
structure Stack
Members
Suffix Type Description
All suffixes of Enumerable   Stack objects are a type of Enumerable
PUSH(item) None add item to the top of the stack
POP() any type returns the item on top of the stack and removes it
PEEK() any type returns the item on top of the stack without removing it
CLEAR() None remove all elements
COPY Stack a new copy of this stack

Note

This type is serializable.

Stack:PUSH(item)
Parameters:
  • item – (any type) item to be added

Adds the item to the top of the stack.

Stack:POP()

Returns the item on top of the stack and removes it.

Stack:PEEK()

Returns the item on top of the stack without removing it.

Stack:CLEAR()

Removes all elements from the stack.

Stack:COPY
Type:Stack
Access:Get only

Returns a new stack that contains the same thing as the old one.

UniqueSet

A UniqueSet is a collection of any type in kOS. It doesn’t store items in any particular order and does not allow duplicate items. You can read more about sets on Wikipedia.

Usage example:

SET S TO UNIQUESET(1,2,3).
PRINT S:LENGTH. // will print 3
S:ADD(1). // 1 was already in the set so nothing happens
PRINT S:LENGTH. // will print 3 again
Structure
structure UniqueSet
Suffix Type Description
All suffixes of Enumerable   UniqueSet objects are a type of Enumerable
ADD(item) None append an item
REMOVE(item) None remove item
CLEAR() None remove all elements
COPY UniqueSet a new copy of this set

Note

This type is serializable.

UniqueSet:ADD(item)
Parameters:
  • item – (any type) item to be added

Appends the new value given.

UniqueSet:REMOVE(item)
Parameters:
  • item – (any type) item to be removed

Remove the item from the set.

UniqueSet:CLEAR()
Returns:none

Calling this suffix will remove all of the items currently stored in the set.

UniqueSet:COPY
Type:UniqueSet
Access:Get only

Returns a new set that contains the same thing as the old set.

Volumes and files

FileContent

Represents the contents of a file. You can obtain an instance of this class using VolumeFile:READALL.

Internally this class stores raw data (a byte array). It can be passed around as is, for example this will copy a file:

SET CONTENTS TO OPEN("filename"):READALL.
SET NEWFILE TO CREATE("newfile").
NEWFILE:WRITE(CONTENTS).

You can parse the contents to read them as a string:

SET CONTENTS_AS_STRING TO OPEN("filename"):READALL:STRING.
// do something with a string:
PRINT CONTENTS_AS_STRING:CONTAINS("test").

Instances of this class can be iterated over. In each iteration step a single line of the file will be read.

structure FileContent
Members
Suffix Type Description
LENGTH scalar File length (in bytes)
EMPTY boolean True if the file is empty
TYPE String Type of the content
STRING String Contents of the file decoded using UTF-8 encoding
ITERATOR Iterator Iterates over the lines of a file

Note

This type is serializable.

FileContent:LENGTH
Type:scalar
Access:Get only

Length of the file.

FileContent:EMPTY
Type:boolean
Access:Get only

True if the file is empty

FileContent:TYPE
Access:Get only
Type:String

Type of the content as a string. Can be one of the following:

TOOSHORT
Content too short to establish a type
ASCII
A file containing ASCII text, like the result of a LOG command.
KSM
A type of file containing KerboMachineLanguage compiled code, that was created from the COMPILE command.
BINARY
Any other type of file.
FileContent:STRING
Access:Get only
Type:String

Contents of the file decoded using UTF-8 encoding

FileContent:ITERATOR
Access:Get only
Type:Iterator

Iterates over the lines of a file

Path

Represents a path. Contains suffixes that can be helpful when using and manipulating paths. You can use path() to create new instances.

Instances of this structure can be passed as arguments instead of ordinary, string paths, for example:

copypath("../file", path()).
structure Path
Suffix Type Description
VOLUME Volume Volume this path belongs to
SEGMENTS List of String List of this path’s segments
LENGTH Scalar Number of segments in this path
NAME String Name of file or directory this path points to
HASEXTENSION Boolean True if path contains an extension
EXTENSION String This path’s extension
ROOT Path Root path of this path’s volume
PARENT Path Parent path
CHANGENAME(name) Path Returns a new path with its name (last segment) changed
CHANGEEXTENSION(extension) Path Returns a new path with extension changed
ISPARENT(path) Boolean True if path is the parent of this path
COMBINE(name1, [name2, ...]) Path Returns a new path created by adding further elements to this one
Path:VOLUME
Type:Volume
Access:Get only

Volume this path belongs to.

Path:SEGMENTS
Type:List of String
Access:Get only

List of segments this path contains. Segments are parts of the path separated by /. For example path 0:/directory/subdirectory/script.ks contains the following segments: directory, subdirectory and script.ks.

Path:LENGTH
Type:Scalar
Access:Get only

Number of this path’s segments.

Path:NAME
Type:String
Access:Get only

Name of file or directory this path points to (same as the last segment).

Path:HASEXTENSION
Type:Boolean
Access:Get only

True if the last segment of this path has an extension.

Path:EXTENSION
Type:String
Access:Get only

Extension of the last segment of this path.

Path:ROOT
Type:Path
Access:Get only

Returns a new path that points to the root directory of this path’s volume.

Path:PARENT
Type:Path
Access:Get only

Returns a new path that points to this path’s parent. This method will throw an exception if this path does not have a parent (its length is 0).

Path:CHANGENAME(name)
Parameters:
  • nameString new path name
Returns:

Path

Will return a new path with the value of the last segment of this path replaced (or added if there’s none).

Path:CHANGEEXTENSION(extension)
Parameters:
  • extensionString new path extension
Returns:

Path

Will return a new path with the extension of the last segment of this path replaced (or added if there’s none).

Path:ISPARENT(path)
Parameters:
  • pathPath path to check
Returns:

Boolean

Returns true if path is the parent of this path.

Path:COMBINE(name1, [name2, ...])
Parameters:
  • nameString segments to add
Returns:

Path

Returns a new path that represents the file or directory that would be reached by starting from this path and then appending the path elements given in the list.

e.g:

set p to path("0:/home").
set p2 to p:combine("d1", "d2", "file.ks").
print p2
0:/home/d1/d2/file.ks

Volume

Represents a kOSProcessor hard disk or the archive.

structure Volume
Suffix Type Description
FREESPACE Scalar Free space left on the volume
CAPACITY Scalar Total space on the volume
NAME String Get or set volume name
RENAMEABLE Scalar True if the name can be changed
ROOT VolumeDirectory Volume’s root directory
FILES Lexicon Lexicon of all files and directories on the volume
POWERREQUIREMENT Scalar Amount of power consumed when this volume is set as the current volume
EXISTS(path) Boolean Returns true if the given file or directory exists
CREATE(path) VolumeFile Creates a file
CREATEDIR(path) VolumeDirectory Creates a directory
OPEN(path) VolumeItem or Boolean Opens a file or directory
DELETE(path) Boolean Deletes a file or directory
Volume:FREESPACE
Type:Scalar
Access:Get only

Free space left on the volume

Volume:CAPACITY
Type:Scalar
Access:Get only

Total space on the volume

Volume:NAME
Type:String
Access:Get and Set

Gets or sets volume name. This name can be used instead of the volumeId with some file and volume-related commands

Volume:RENAMEABLE
Type:Boolean
Access:Get only

True if the name of this volume can be changed. Currently only the name of the archive can’t be changed.

Volume:FILES
Type:Lexicon of VolumeItem
Access:Get only

List of files and directories on this volume. Keys are the names of all items on this volume and values are the associated VolumeItem structures.

Volume:ROOT
Type:VolumeDirectory
Access:Get only

Returns volume’s root directory

Volume:POWERREQUIREMENT
Type:Scalar
Access:Get only

Amount of power consumed when this volume is set as the current volume

Volume:EXISTS(path)
Returns:Boolean

Returns true if the given file or directory exists. This will also return true when the given file does not exist, but there is a file with the same name and .ks or .ksm extension added. Use Volume:FILES:HASKEY(name) to perform a strict check.

Paths passed as the argument to this command should not contain a volume id or name and should not be relative.

Volume:OPEN(path)
Returns:VolumeItem or Boolean false

Opens the file or directory pointed to by the given path and returns VolumeItem. It will return a boolean false if the given file or directory does not exist.

Paths passed as the argument to this command should not contain a volume id or name and should not be relative.

Volume:CREATE(path)
Returns:VolumeFile

Creates a file under the given path and returns VolumeFile. It will fail if the file already exists.

Paths passed as the argument to this command should not contain a volume id or name and should not be relative.

Volume:CREATEDIR(path)
Returns:VolumeDirectory

Creates a directory under the given path and returns VolumeDirectory. It will fail if the directory already exists.

Paths passed as the argument to this command should not contain a volume id or name and should not be relative.

Volume:DELETE(path)
Returns:boolean

Deletes the given file or directory (recursively). It will return true if the given item was successfully deleted and false otherwise.

Paths passed as the argument to this command should not contain a volume id or name and should not be relative.

VolumeDirectory

Represents a directory on a kOS file system.

Instances of this class are enumerable, every step of iteration will provide a VolumeFile or a VolumeDirectory contained in this directory.

structure VolumeDirectory
Members
Suffix Type Description
All suffixes of VolumeItem   VolumeDirectory objects are a type of VolumeItem
LIST List of VolumeFile or VolumeDirectory Lists all files and directories
VolumeDirectory:LIST()
Returns:List of VolumeFile or VolumeDirectory

Returns a list of all files and directories in this directory.

VolumeFile

File name and size information. You can obtain a list of values of type VolumeFile using the LIST FILES command.

structure VolumeFile
Members
Suffix Type Description
All suffixes of VolumeItem   VolumeFile objects are a type of VolumeItem
READALL FileContent Reads file contents
WRITE(String|FileContent) Boolean Writes the given string to the file
WRITELN(string) Boolean Writes the given string and a newline to the file
CLEAR None Clears this file
VolumeFile:READALL()
Returns:FileContent

Reads the content of the file.

VolumeFile:WRITE(String|FileContent)
Returns:Boolean

Writes the given string or a FileContent to the file. Returns true if successful (lack of space on the Volume can cause a failure).

VolumeFile:WRITELN(string)
Returns:Boolean

Writes the given string followed by a newline to the file. Returns true if successful.

VolumeFile:CLEAR()
Returns:None

Clears this file

VolumeItem

Contains suffixes common to files and directories.

structure VolumeItem
Members
Suffix Type Description
NAME String Name of the item including extension
EXTENSION String Item extension
SIZE Scalar Size of the file
ISFILE Boolean Size of the file
VolumeItem:NAME
Access:Get only
Type:String

Name of the item, including the extension.

VolumeItem:EXTENSION
Access:Get only
Type:String

Item extension (part of the name after the last dot).

VolumeItem:SIZE
Access:Get only
Type:Scalar

Size of the item, in bytes.

VolumeItem:ISFILE
Access:Get only
Type:Boolean

True if this item is a file

Vessels and Parts

AggregateResource

A ship can have many parts that contain resources (i.e. fuel, electric charge, etc). kOS has several tools for getting the summation of each resource.

This is the type returned as the elements of the list from LIST RESOURCES.

IN MyList <list command>

PRINT "THESE ARE ALL THE RESOURCES ON THE SHIP:".
LIST RESOURCES IN RESLIST.
FOR RES IN RESLIST {
    PRINT "Resource " + RES:NAME.
    PRINT "    value = " + RES:AMOUNT.
    PRINT "    which is "
          + ROUND(100*RES:AMOUNT/RES:CAPACITY)
          + "% full.".
}.

This is also the type returned by STAGE:RESOURCES

PRINT "THESE ARE ALL THE RESOURCES active in this stage:".
SET RESLIST TO STAGE:RESOURCES.
FOR RES IN RESLIST {
    PRINT "Resource " + RES:NAME.
    PRINT "    value = " + RES:AMOUNT.
    PRINT "    which is "
          + ROUND(100*RES:AMOUNT/RES:CAPACITY)
          + "% full.".
}.
structure AggregateResource
Suffix Type Description
NAME string Resource name
AMOUNT scalar Total amount remaining
CAPACITY scalar Total amount when full
PARTS List Parts containing this resource
AggregateResource:NAME
Access:Get only
Type:string

The name of the resource, i.e. “LIQUIDFUEL”, “ELECTRICCHARGE”, “MONOPROP”.

AggregateResource:AMOUNT
Access:Get only
Type:scalar

The value of how much resource is left.

AggregateResource:CAPACITY
Access:Get only
Type:scalar

What AMOUNT would be if the resource was filled to the top.

AggregateResource:PARTS
Access:Get only
Type:List

Because this is a summation of the resources from many parts. kOS gives you the list of all parts that do or could contain the resource.

ALT

ALT is a special object that exists just to help you get the altitudes of interest for a vessel future. It is grandfathered in for the sake of backward compatibility, but this information is also available on the Vessel structure as well, which is the better new way to do it:

structure ALT
Suffix Type Description
APOAPSIS scalar, meters altitude in meters of SHIP’s apoapsis. Same as SHIP:APOAPSIS.
PERIAPSIS scalar, meters altitude in meters of SHIP’s periapsis. Same as SHIP:PERIAPSIS.
RADAR scalar, meters Altitude of SHIP above the ground terrain, rather than above sea level.

Core

Core represents your ability to identify and interact directly with the running kOS processor. You can use it to access the parent vessel, or to perform operations on the processor’s part. You obtain a CORE structure by using the bound variable core.

structure CORE
Members
Suffix Type
All suffixes of kOSProcessor CORE objects are a type of kOSProcessor
VESSEL Vessel
ELEMENT Element
VERSION Version
CURRENTVOLUME Volume
MESSAGES MessageQueue
CORE:VESSEL
Type:VesselTarget
Access:Get only

The vessel containing the current processor.

CORE:ELEMENT
Type:Element
Access:Get only

The element object containing the current processor.

CORE:VERSION
Type:VersionInfo
Access:Get only

The kOS version currently running.

CORE:CURRENTVOLUME
Type:Volume
Access:Get only

The currently selected volume for the current processor. This may be useful to prevent deleting files on the Archive, or for interacting with multiple local hard disks.

CORE:MESSAGES
Type:MessageQueue
Access:Get only

Returns this processsor’s message queue.

CraftTemplate

structure CraftTemplate

You can access CraftTemplate objects from the KUniverse bound variable. Templates can be used to launch new vessels, or read initial data about a craft, such as the description.

Suffix Type Description
NAME String Name of this craft template
FILEPATH String The path to the craft file
DESCRIPTION String The description as saved in the editor
EDITOR String The editor where this craft was saved
LAUNCHSITE String The default launch site for this craft
MASS Scalar The default mass of the craft
COST Scalar The default cost of the craft
PARTCOUNT Scalar The total number of parts in this craft.
CraftTemplate:NAME
Access:Get only
Type:String

Returns the name of the craft. It may differ from the file name.

CraftTemplate:FILEPATH
Access:Get only
Type:String

Returns the absolute file path to the craft file.

CraftTemplate:DESCRIPTION
Access:Get only
Type:String

Returns the description field of the craft, which may be edited from the drop down window below the craft name in the editor.

CraftTemplate:EDITOR
Access:Get only
Type:String

Name of the editor from which the craft file was saved. Valid values are "VAB" and "SPH".

CraftTemplate:LAUNCHSITE
Access:Get only
Type:String

Returns the name of the default launch site of the craft. Valid values are "LAUNCHPAD" and "RUNWAY".

CraftTemplate:MASS
Access:Get only
Type:Scalar

Returns the total default mass of the craft. This includes the dry mass and the mass of any resources loaded onto the craft by default.

CraftTemplate:COST
Access:Get only
Type:Scalar

Returns the total default cost of the craft. This includes the cost of the vessel itself as well as any resources loaded onto the craft by default.

CraftTemplate:PARTCOUNT
Access:Get only
Type:Scalar

Returns the total number of parts on the craft.

CrewMember

Represents a single crew member of a vessel.

structure CrewMember
Suffix Type Description
NAME string crew member’s name
GENDER string “Male” or “Female”
EXPERIENCE scalar experience level (number of stars)
TRAIT string “Pilot”, “Engineer” or “Scientist”
TOURIST Boolean true if this crew member is a tourist
PART Part part in which the crew member is located
CrewMember:NAME
Type:string
Access:Get only

crew member’s name

CrewMember:GENDER
Type:string
Access:Get only

“Male” or “Female”

CrewMember:EXPERIENCE
Type:scalar
Access:Get only

experience level (number of stars)

CrewMember:TRAIT
Type:string
Access:Get only

crew member’s trait (specialization), for example “Pilot”

CrewMember:TOURIST
Type:Boolean
Access:Get only

true if this crew member is a tourist

CrewMember:PART
Type:Part
Access:Get only

Part this crew member is located in

DockingPort

Some of the Parts returned by LIST PARTS will be of type DockingPort.

Note

New in version 0.18: The spelling of suffixes AQUIRERANGE, AQUIREFORCE, and AQUIRETORQURE on the DockingPort structure has been corrected. Please use ACQUIRERANGE, ACQUIREFORCE, and ACQUIRETORQURE instead. Using the old incorrect spelling, a deprecation exception will be thrown, with instruction to use the new spelling.

structure DockingPort
Suffix Type Description
All suffixes of Part   A DockingPort is a kind of Part
ACQUIRERANGE scalar active range of the port
ACQUIREFORCE scalar force experienced when docking
ACQUIRETORQUE scalar torque experienced when docking
REENGAGEDDISTANCE scalar distance at which the port is reset
DOCKEDSHIPNAME string name of vessel the port is docked to
NODEPOSITION vector coords of where the docking node attachment point is in SHIP-RAW xyz
NODETYPE string two nodes are only dockable together if their NODETYPE strings match
PORTFACING Direction facing of the port
STATE string current state of the port
UNDOCK   callable to release the dock
TARGETABLE boolean check if this port can be targeted

Note

DockingPort is a type of Part, and therefore can use all the suffixes of Part. Shown below are only the suffixes that are unique to DockingPort.

DockingPort:ACQUIRERANGE
Type:scalar
Access:Get only

gets the range at which the port will “notice” another port and pull on it.

DockingPort:ACQUIREFORCE
Type:scalar
Access:Get only

gets the force with which the port pulls on another port.

DockingPort:ACQUIRETORQUE
Type:scalar
Access:Get only

gets the rotational force with which the port pulls on another port.

DockingPort:REENGAGEDDISTANCE
Type:scalar
Access:Get only

how far the port has to get away after undocking in order to re-enable docking.

DockingPort:DOCKEDSHIPNAME
Type:string
Access:Get only

name of vessel on the other side of the docking port.

DockingPort:NODEPOSITION
Type:vector
Access:Get only

The coordinates of the point on the docking port part where the port attachment spot is located. This is different from the part’s position itself because that’s the position of the center of the whole part. This is the position of the face of the docking port. Coordinates are in SHIP-RAW xyz coords.

DockingPort:NODETYPE
Type:string
Access:Get only

Each docking port has a node type string that specifies its compatibility with other docking ports. In order for two docking ports to be able to attach to each other, the values for their NODETYPEs must be the same.

The base KSP stock docking port parts all use one of the following three values:

  • “size0” for all Junior-sized docking ports.
  • “size1” for all Normal-sized docking ports.
  • “size2” for all Senior-sized docking ports.

Mods that provide their own new kinds of docking port might use any other value they feel like here, but only if they are trying to declare that the new part isn’t supposed to be able to connect to stock docking ports. Any docking port that is meant to connect to stock ports will have to adhere to the above scheme.

DockingPort:PORTFACING
Type:Direction
Access:Get only

Gets the facing of this docking port which may differ from the facing of the part itself if the docking port is aimed out the side of the part, as in the case of the inline shielded docking port.

DockingPort:STATE
Type:string
Access:Get only

One of the following string values:

Ready
Docking port is not yet attached and will attach if it touches another.
Docked (docker)
One port in the joined pair is called the docker, and has this state
Docked (dockee)
One port in the joined pair is called the dockee, and has this state
Docked (same vessel)
Sometimes KSP says this instead. It’s unclear what it means.
Disabled
Docking port will refuse to dock if it bumps another docking port.
PreAttached
Temporary state during the “wobbling” while two ports are magnetically touching but not yet docked solidly. During this state the two vessels are still tracked as separate vessels and haven’t become one yet.
DockingPort:UNDOCK()

Call this to cause the docking port to detach.

DockingPort:TARGETABLE
Type:Boolean
Access:Get only

True if this part can be picked with SET TARGET TO.

Element

An element is a docked component of a Vessel. When you dock several vessels together to create one larger vessel, you can obtain the “chunks” of the larger vessel organized by which original vessel they came from. These “chunks” are called elements, and they are what the Element structure refers to.

A list of elements from the vessel can be created by using the command:

list elements in eList.
// now eList is a list of elements from the vessel.

Each item of that list is one of the elements. The rest of this page describes the elements and what they do.

Note

Element boundries are not formed between two docking ports that were launched coupled. a craft with such an arrangement will appear as one element until you uncoupled the nodes and redocked

structure Element
Suffix Type Description
NAME :struct:string The name of the docked craft
UID :struct:string Unique Identifier
PARTS List all Parts
DOCKINGPORTS List all DockingPorts
VESSEL Vessel the parent Vessel
RESOURCES List all AggrgateResources
Element:UID
Type:string
Access:Get only

A unique id

Element:NAME
Type:string
Access:Get/Set

The name of the Element element, is an artifact from the vessel the element belonged to before docking. Cannot be set to an empty string.

Element:PARTS
Type:List of Part objects
Access:Get only

A List of all the parts on the Element. SET FOO TO SHIP:PARTS. has exactly the same effect as LIST PARTS IN FOO.. For more information, see ship parts and modules.

Element:DOCKINGPORTS
Type:List of DockingPort objects
Access:Get only

A List of all the docking ports on the Element.

Element:VESSEL
Type:Vessel
Access:Get only

The parent vessel containing the element.

Element:RESOURCES
Type:List of AggregateResource objects
Access:Get only

A List of all the AggregateResources on the element.

Engine

Some of the Parts returned by LIST PARTS will be of type Engine. It is also possible to get just the Engine parts by executing LIST ENGINES, for example:

LIST ENGINES IN myVariable.
FOR eng IN myVariable {
    print "An engine exists with ISP = " + eng:ISP.
}.
structure Engine
Members
Suffix Type (units) Description
All suffixes of Part    
ACTIVATE   Turn engine on
SHUTDOWN   Turn engine off
THRUSTLIMIT scalar (%) Tweaked thrust limit
MAXTHRUST scalar (kN) Untweaked thrust limit
MAXTHRUSTAT(pressure) scalar (kN) Max thrust at the specified pressure (in standard Kerbin atmospheres).
THRUST scalar (kN) Current thrust
AVAILABLETHRUST scalar (kN) Available thrust at full throttle accounting for thrust limiter
AVAILABLETHRUSTAT(pressure) scalar (kN) Available thrust at the specified pressure (in standard Kerbin atmospheres).
FUELFLOW scalar (l/s maybe) Rate of fuel burn
ISP scalar Specific impulse
ISPAT(pressure) scalar Specific impulse at the given pressure (in standard Kerbin atmospheres).
VACUUMISP scalar Vacuum specific impulse
VISP scalar Synonym for VACUUMISP
SEALEVELISP scalar Specific impulse at Kerbin sealevel
SLISP scalar Synonym for SEALEVELISP
FLAMEOUT Boolean Check if no more fuel
IGNITION Boolean Check if engine is active
ALLOWRESTART Boolean Check if engine can be reactivated
ALLOWSHUTDOWN Boolean Check if engine can be shutdown
THROTTLELOCK Boolean Check if throttle can not be changed
MULTIMODE Boolean Check if engine has multiple modes
MODES List List (string) of the engine modes
MODE string Name of the current mode (only if multiple)
TOGGLEMODE   Switch to another mode (only if multiple)
PRIMARYMODE Boolean Is the engine in primary mode? (only if multiple)
AUTOSWITCH Boolean Can the engine switch modes automatically? (only if multiple)
HASGIMBAL Boolean Check if engine has gimbal
GIMBAL Gimbal Gimbal of this engine (only if available)

Note

Engine is a type of Part, and therefore can use all the suffixes of Part. Shown below are only the suffixes that are unique to Engine.

Engine:ACTIVATE()

Call to make the engine turn on.

Engine:SHUTDOWN()

Call to make the engine turn off.

Engine:THRUSTLIMIT
Access:Get/Set
Type:scalar (%)

If this an engine with a thrust limiter (tweakable) enabled, what percentage is it limited to? Note that this is expressed as a percentage, not a simple 0..1 coefficient. e.g. To set thrustlimit to half, you use a value of 50.0, not 0.5.

This value is not allowed to go outside the range [0..100]. If you attempt to do so, it will be clamped down into the allowed range.

Note that although a kerboscript is allowed to set the value to a very precise number (for example 10.5123), the stock in-game display widget that pops up when you right-click the engine will automatically round it to the nearest 0.5 whenever you open the panel. So if you do something like set ship:part[20]:thrustlimit to 10.5123. in your script, then look at the rightclick menu for the engine, the very act of just looking at the menu will cause it to become 10.5 instead of 10.5123. There isn’t much that kOS can to to change this. It’s a user interface decision baked into the stock game.

Engine:MAXTHRUST
Access:Get only
Type:scalar (kN)

How much thrust would this engine give at its current atmospheric pressure and velocity if the throttle was max at 1.0, and the thrust limiter was max at 100%. Note this might not be the engine’s actual max thrust it could have under other air pressure conditions. Some engines have a very different value for MAXTHRUST in vacuum as opposed to at sea level pressure. Also, some jet engines have a very different value for MAXTHRUST depending on how fast they are currently being rammed through the air.

Engine:MAXTHRUSTAT(pressure)
Parameters:
  • pressure – atmospheric pressure (in standard Kerbin atmospheres)
Return type:

scalar (kN)

How much thrust would this engine give if both the throttle and thrust limtier was max at the current velocity, and at the given atmospheric pressure. Use a pressure of 0.0 for vacuum, and 1.0 for sea level (on Kerbin) (or more than 1 for thicker atmospheres like on Eve).

Engine:THRUST
Access:Get only
Type:scalar (kN)

How much thrust is this engine giving at this very moment.

Engine:AVAILABLETHRUST
Access:Get only
Type:scalar (kN)

Taking into account the thrust limiter tweakable setting, how much thrust would this engine give if the throttle was max at its current thrust limit setting and atmospheric pressure and velocity conditions.

Engine:AVAILABLETHRUSTAT(pressure)
Parameters:
  • pressure – atmospheric pressure (in standard Kerbin atmospheres)
Return type:

scalar (kN)

Taking into account the thrust limiter tweakable setting, how much thrust would this engine give if the throttle was max at its current thrust limit setting and velocity, but at a different atmospheric pressure you pass into it. The pressure is measured in ATM’s, meaning 0.0 is a vacuum, 1.0 is seal level at Kerbin.

Engine:FUELFLOW
Access:Get only
Type:scalar (Liters/s? maybe)

Rate at which fuel is being burned. Not sure what the units are.

Engine:ISP
Access:Get only
Type:scalar

Specific impulse

Engine:ISPAT(pressure)
Parameters:
  • pressure – atmospheric pressure (in standard Kerbin atmospheres)
Return type:

scalar

Specific impulse at the given atmospheric pressure. Use a pressure of 0 for vacuum, and 1 for sea level (on Kerbin).

Engine:VACUUMISP
Access:Get only
Type:scalar

Vacuum specific impulse

Engine:VISP
Access:Get only
Type:scalar

Synonym for :VACUUMISP

Engine:SEALEVELISP
Access:Get only
Type:scalar

Specific impulse at Kerbin sealevel.

Engine:SLISP
Access:Get only
Type:scalar

Synonym for :SEALEVELISP

Engine:FLAMEOUT
Access:Get only
Type:Boolean

Is this engine failed because it is starved of a resource (liquidfuel, oxidizer, oxygen)?

Engine:IGNITION
Access:Get only
Type:Boolean

Has this engine been ignited? If both Engine:IGNITION and Engine:FLAMEOUT are true, that means the engine could start up again immediately if more resources were made available to it.

Engine:ALLOWRESTART
Access:Get only
Type:Boolean

Is this an engine that can be started again? Usually True, but false for solid boosters.

Engine:ALLOWSHUTDOWN
Access:Get only
Type:Boolean

Is this an engine that can be shut off once started? Usually True, but false for solid boosters.

Engine:THROTTLELOCK
Access:Get only
Type:Boolean

Is this an engine that is stuck at a fixed throttle? (i.e. solid boosters)

Engine:MULTIMODE
Access:Get only
Type:Boolean

Does this engine have multiple modes (i.e. RAPIER)? Check this before calling multi-mode specific suffixes.

Engine:MODES
Access:Get only
Type:List of strings

Lists names of modes of this engine if multimode, returns a list of 1 string “Single mode” otherwise.

Engine:MODE
Access:Get only
Type:string

Name of the current mode. Only assessible for multi-mode engines.

Engine:TOGGLEMODE()

Call to switch to another mode. Only assessible for multi-mode engines.

Engine:PRIMARYMODE
Access:Get/Set
Type:Boolean

True for primary mode, false for secondary. Setting to other value equals toggling the mode. Only assessible for multi-mode engines.

Engine:AUTOSWITCH
Access:Get/Set
Type:Boolean

Is automatic switching enabled? Can set to switch between manual and automatic switching. Only assessible for multi-mode engines.

Engine:HASGIMBAL
Access:Get only
Type:Boolean

Does this engine have a gimbal enabled?

Engine:GIMBAL
Access:Get only
Type:Gimbal

Returns the Gimbal attached to this engine. Only accessible if the gimbal is present (Use Engine:HASGIMBAL to check if available).

ETA

ETA is a special object that exists just to help you get the times from now to certain events in a vessel’s future. It always presumes you’re operating on the current SHIP vessel:

structure ETA
Suffix Type Description
APOAPSIS scalar, seconds Seconds until SHIP hits its apoapsis.
PERIAPSIS scalar, seconds Seconds until SHIP hits its periapsis.
TRANSITION scalar, seconds Seconds until SHIP hits the next orbit patch.
ETA:APOAPSIS
Type:scalar, seconds
Access:Get only

Seconds until SHIP hits its apoapsis. If the ship is on an escape trajectory (hyperbolic orbit) such that you will never reach apoapsis, it will return zero.

ETA:PERIAPSIS
Type:scalar, seconds
Access:Get only

Seconds until SHIP hits its periapsis. If the ship is on an intersect with the ground, such that you’ll hit the ground first before you’d get to periapsis, it will still return the hypothetical number of seconds it would have taken to get to periapsis if you had the magical ability to pass through the ground as if it wasn’t there.

ETA:TRANSITION
Type:scalar, seconds
Access:Get only

Seconds until SHIP hits its the end of its current orbit patch and transitions into another one, ignoring the effect of any intervening manuever nodes it might hit before it gets there.

Gimbal

Many engines in KSP have thrust vectoring gimbals which are handled by their own module

structure Gimbal
Suffix Type (units) Description
All suffixes of PartModule    
LOCK Boolean Is the Gimbal locked in neutral position?
PITCH Boolean Does the Gimbal respond to pitch controls?
YAW Boolean Does the Gimbal respond to yaw controls?
ROLL Boolean Does the Gimbal respond to roll controls?
LIMIT Scalar (%) Percentage of the maximum range the Gimbal is allowed to travel
RANGE Scalar (deg) The Gimbal’s Possible Range of movement
RESPONSESPEED Scalar The Gimbal’s Possible Rate of travel
PITCHANGLE Scalar Current Gimbal Pitch
YAWANGLE Scalar Current Gimbal Yaw
ROLLANGLE Scalar Current Gimbal Roll

Note

Gimbal is a type of PartModule, and therefore can use all the suffixes of PartModule. Shown below are only the suffixes that are unique to Gimbal. Gimbal can be accessed as Engine:GIMBAL atribute of Engine.

Gimbal:LOCK
Type:Boolean
Access:Get/Set

Is this gimbal locked to neutral position and not responding to steering controls right now? When you set it to true it will snap the engine back to 0s for pitch, yaw and roll

Gimbal:PITCH
Type:Boolean
Access:Get/Set

Is the gimbal responding to pitch controls? Relevant only if the gimbal is not locked.

Gimbal:YAW
Type:Boolean
Access:Get/Set

Is the gimbal responding to yaw controls? Relevant only if the gimbal is not locked.

Gimbal:ROLL
Type:Boolean
Access:Get/Set

Is the gimbal responding to roll controls? Relevant only if the gimbal is not locked.

Gimbal:LIMIT
Type:Scalar (%)
Access:Get/Set

Percentage of maximum range this gimbal is allowed to travel

Gimbal:RANGE
Type:Scalar (deg)
Access:Get only

The maximum extent of travel possible for the gimbal along all 3 axis (Pitch, Yaw, Roll)

Gimbal:RESPONSESPEED
Type:Scalar
Access:Get only

A Measure of the rate of travel for the gimbal

Gimbal:PITCHANGLE
Type:Scalar
Access:Get only

The gimbals current pitch, has a range of -1 to 1. Will always be 0 when LOCK is true

Gimbal:YAWANGLE
Type:Scalar
Access:Get only

The gimbals current yaw, has a range of -1 to 1. Will always be 0 when LOCK is true

Gimbal:ROLLANGLE
Type:Scalar
Access:Get only

The gimbals current roll, has a range of -1 to 1. Will always be 0 when LOCK is true

kOSProcessor

The type of structures returned by kOS when querying a module that contains a kOS processor.

structure kOSProcessor
Suffix Type Description
All suffixes of PartModule   kOSProcessor objects are a type of PartModule
MODE :ref:`string <string>` OFF, READY or STARVED
ACTIVATE None Activates this processor
DEACTIVATE None Deactivates this processor
TAG :ref:`string <string>` This processor’s name tag
VOLUME Volume This processor’s hard disk
BOOTFILENAME :ref:`string <string>` The filename for the boot file on this processor
CONNECTION :struct:`Connection Returns your connection to this processor

Note

A kOSProcessor is a type of PartModule, and therefore can use all the suffixes of PartModule.

kOSProcessor:MODE
Access:Get only
Type::ref:`string <string>`

Indicates the current state of this processor. OFF - deactivated, READY - active, or STARVED - no power.

kOSProcessor:ACTIVATE()
Returns:None

Activate this processor

kOSProcessor:DEACTIVATE()
Returns:None

Deactivate this processor

kOSProcessor:TAG
Access:Get only
Type::ref:`string <string>`

This processor’s name tag

kOSProcessor:VOLUME
Access:Get only
Type:Volume

This processor’s hard disk.

kOSProcessor:BOOTFILENAME
Access:Get or Set
Type::ref:`string <string>`

The filename for the boot file on this processor. This may be set to an empty string “” or to “None” to disable the use of a boot file.

kOSProcessor:CONNECTION
Return:Connection

Returns your connection to this processor.

Maneuver Node

Contents

A planned velocity change along an orbit. These are the nodes that you can set in the KSP user interface. Setting one through kOS will make it appear on the in-game map view, and creating one manually on the in-game map view will cause it to be visible to kOS.

Warning

Be aware that a limitation of KSP makes it so that some vessels’ maneuver node systems cannot be accessed. KSP appears to limit the maneuver node system to only functioning on the current PLAYER vessel, under the presumption that its the only vessel that needs them, as ever other vessel cannot be maneuvered. kOS can maneuver a vessel that is not the player vessel, but it cannot overcome this limitation of the base game that unloads the maneuver node system for other vessels.

Be aware that the effect this has is that when you try to use some of these commands on some vessels, they won’t work because those vessels do not have their maneuver node system in play. This is mostly only going to happen when you try to run a script on a vessel that is not the current player active vessel.

Creation
NODE(utime, radial, normal, prograde)
Parameters:
  • utime – (sec) Time of this maneuver
  • radial – (m/s) Delta-V in radial-out direction
  • normal – (m/s) Delta-V normal to orbital plane
  • prograde – (m/s) Delta-V in prograde direction
Returns:

ManeuverNode

You can make a maneuver node in a variable using the NODE function:

SET myNode to NODE( TIME:SECONDS+200, 0, 50, 10 ).

Once you have a maneuver node in a variable, you use the ADD and REMOVE commands to attach it to your vessel’s flight plan. A kOS CPU can only manipulate the flight plan of its CPU vessel.

Warning

When constructing a new node using the NODE function call, you use the universal time (you must add the ETA time to the current time to arrive at the value to pass in), but when using the suffix ManeuverNode:ETA, you do NOT use universal time, instead just giving the number of seconds from now.

Once you have created a node, it’s just a hypothetical node that hasn’t been attached to anything yet. To attach a node to the flight path, you must use the command ADD to attach it to the ship.

ADD

To put a maneuver node into the flight plan of the current CPU vessel (i.e. SHIP), just ADD it like so:

SET myNode to NODE( TIME:SECONDS+200, 0, 50, 10 ).
ADD myNode.

You should immediately see it appear on the map view when you do this. The ADD command can add nodes anywhere within the flight plan. To insert a node earlier in the flight than an existing node, simply give it a smaller ETA time and then ADD it.

Warning

As per the warning above at the top of the section, ADD won’t work on vessels that are not the active vessel.

REMOVE

To remove a maneuver node from the flight path of the current CPU vessel (i.e. SHIP), just REMOVE it like so:

REMOVE myNode.

Warning

As per the warning above at the top of the section, REMOVE won’t work on vessels that are not the active vessel.

NEXTNODE

NEXTNODE is a built-in variable that always refers to the next upcoming node that has been added to your flight plan:

SET MyNode to NEXTNODE.
PRINT NEXTNODE:PROGRADE.
REMOVE NEXTNODE.

Currently, if you attempt to query NEXTNODE and there is no node on your flight plan, it produces a run-time error. (This needs to be fixed in a future release so it is possible to query whether or not you have a next node).

Warning

As per the warning above at the top of the section, NEXTNODE won’t work on vessels that are not the active vessel.

The special identifier NEXTNODE is a euphemism for “whichever node is coming up soonest on my flight path”. Therefore you can remove a node even if you no longer have the maneuver node variable around, by doing this:

REMOVE NEXTNODE.
HASNODE
Type:Boolean
Access:Get only

Returns true if there is a planned maneuver ManeuverNode in the CPU vessel’s flight plan. This will always return false for the non-active vessel, as access to maneuver nodes is limited to the active vessel.

ALLNODES
Type:List of ManeuverNode elements
Access:Get only

Returns a list of all ManeuverNode objects currently on the CPU vessel’s flight plan. This list will be empty if no nodes are planned, or if the CPU vessel is currently unable to use maneuver nodes.

Note

If you store a reference to this list in a variable, the variable’s instance will not be automatically updated if you ADD or REMOVE maneuver nodes to the flight plan.

Note

Adding a ManeuverNode to this list, or a reference to this list will not add it to the flight plan. Use the ADD command instead.

Structure
structure ManeuverNode

Here are some examples of accessing the suffixes of a ManeuverNode:

// creates a node 60 seconds from now with
// prograde = 100 m/s
SET X TO NODE(TIME:SECONDS+60, 0, 0, 100).

ADD X.            // adds maneuver to flight plan

PRINT X:PROGRADE. // prints 100.
PRINT X:ETA.      // prints seconds till maneuver
PRINT X:DELTAV    // prints delta-v vector

REMOVE X.         // remove node from flight plan

// Create a blank node
SET X TO NODE(0, 0, 0, 0).

ADD X.                 // add Node to flight plan
SET X:PROGRADE to 500. // set prograde dV to 500 m/s
SET X:ETA to 30.       // Set to 30 sec from now

PRINT X:ORBIT:APOAPSIS.  // apoapsis after maneuver
PRINT X:ORBIT:PERIAPSIS. // periapsis after maneuver
Members
Suffix Type (units) Access Description
DELTAV Vector (m/s) Get only The burn vector with magnitude equal to delta-V
BURNVECTOR Vector (m/s) Get only Alias for DELTAV
ETA scalar (s) Get/Set Time until this maneuver
PROGRADE scalar (m/s) Get/Set Delta-V along prograde
RADIALOUT scalar (m/s) Get/Set Delta-V along radial to orbited Body
NORMAL scalar (m/s) Get/Set Delta-V along normal to the Vessel‘s Orbit
ORBIT Orbit Get only Expected Orbit after this maneuver
ManeuverNode:DELTAV
Access:Get only
Type:Vector

The vector giving the total burn of the node. The vector can be used to steer with, and its magnitude is the delta V of the burn.

ManeuverNode:BURNVECTOR

Alias for ManeuverNode:DELTAV.

ManeuverNode:ETA
Access:Get/Set
Type:scalar

The number of seconds until the expected burn time. If you SET this, it will actually move the maneuver node along the path in the map view, identically to grabbing the maneuver node and dragging it.

ManeuverNode:PROGRADE
Access:Get/Set
Type:scalar

The delta V in (meters/s) along just the prograde direction (the yellow and green ‘knobs’ of the maneuver node). A positive value is a prograde burn and a negative value is a retrograde burn.

ManeuverNode:RADIALOUT
Access:Get/Set
Type:scalar

The delta V in (meters/s) along just the radial direction (the cyan knobs’ of the maneuver node). A positive value is a radial out burn and a negative value is a radial in burn.

ManeuverNode:NORMAL
Access:Get/Set
Type:scalar

The delta V in (meters/s) along just the normal direction (the purple knobs’ of the maneuver node). A positive value is a normal burn and a negative value is an anti-normal burn.

ManeuverNode:ORBIT
Access:Get only
Type:Orbit

The new orbit patch that will begin starting with the burn of this node, under the assumption that the burn will occur exactly as planned.

Part

These are the generic properties every PART has. You can obtain a list of values of type Part using the LIST PARTS command.

structure Part
Members
Suffix Type Description
NAME String Name of this part
TITLE String Title as it appears in KSP
MASS Scalar Current mass of part and its resources
DRYMASS Scalar Mass of part if all resources were empty
WETMASS Scalar Mass of part if all resources were full
TAG String Name-tag if assigned by the player
CONTROLFROM None Call to control-from to this part
STAGE Scalar The stage this is associated with
UID String Unique identifying number of this part
ROTATION Direction The rotation of this part’s \(x\)-axis
POSITION Vector The location of this part in the universe
FACING Direction the direction that this part is facing
RESOURCES List list of the Resource in this part
TARGETABLE Boolean true if this part can be selected as a target
SHIP Vessel the vessel that contains this part
GETMODULE(name) PartModule Get one of the PartModules by name
GETMODULEBYINDEX(index) PartModule Get one of the PartModules by index
MODULES List Names (String) of all PartModules
ALLMODULES List Same as MODULES
PARENT Part Adjacent Part on this Vessel.
HASPARENT Boolean Check if this part has a parent Part
HASPHYSICS Boolean Does this part have mass or drag
CHILDREN List List of attached Parts
Part:NAME
Access:Get only
Type:String

Name of part as it is used behind the scenes in the game’s API code.

A part’s name is the name it is given behind the scenes in KSP. It never appears in the normal GUI for the user to see, but it is used in places like Part.cfg files, the saved game persistence file, the ModuleManager mod, and so on.

Part:TITLE
Access:Get only
Type:String

The title of the part as it appears on-screen in the gui.

A part’s title is the name it has inside the GUI interface on the screen that you see as the user.

Part:TAG
Access:Get / Set
Type:String

The name tag value that may exist on this part if you have given the part a name via the name-tag system.

A part’s tag is whatever custom name you have given it using the name-tag system described here. This is probably the best naming convention to use because it lets you make up whatever name you like for the part and use it to pick the parts you want to deal with in your script.

WARNING: This suffix is only settable for parts attached to the CPU Vessel

This example assumes you have a target vessel picked, and that the target vessel is loaded into full-physics range and not “on rails”. vessels that are “on rails” do not have their full list of parts entirely populated at the moment:

LIST PARTS FROM TARGET IN tParts.

PRINT "The target vessel has a".
PRINT "partcount of " + tParts:LENGTH.

SET totTargetable to 0.
FOR part in tParts {
    IF part:TARGETABLE {
        SET totTargetable TO totTargetable + 1.
    }
}

PRINT "...and " + totTargetable.
PRINT " of them are targetable parts.".
Part:CONTROLFROM()
Access:Callable function only
Return type:None

Call this function to cause the game to do the same thing as when you right-click a part on a vessel and select “control from here” on the menu. It rotates the control orientation so that fore/aft/left/right/up/down now match the orientation of this part. NOTE that this will not work for every type of part. It only works for those parts that KSP itself allows this for (control cores and docking ports). It accepts no arguments, and returns no value. All vessels must have at least one “control from” part on them somewhere, which is why there’s no mechanism for un-setting the “control from” setting other than to pick another part and set it to that part instead.

Warning

This suffix is only callable for parts attached to the CPU Vessel

Part:STAGE
Access:Get only
Type:Scalar

the stage this part is part of.

Part:UID
Access:Get only
Type:String

All parts have a unique ID number. Part’s uid never changes because it is the same value as stored in persistent.sfs. Although you can compare parts by comparing their uid it is recommended to compare parts directly if possible.

Part:ROTATION
Access:Get only
Type:Direction

The rotation of this part’s X-axis, which points out of its side and is probably not what you want. You probably want the Part:FACING suffix instead.

Part:POSITION
Access:Get only
Type:Vector

The location of this part in the universe. It is expressed in the same frame of reference as all the other positions in kOS, and thus can be used to help do things like navigate toward the position of a docking port.

Part:FACING
Access:Get only
Type:Direction

the direction that this part is facing.

Part:MASS
Access:Get only
Type:Scalar

The current mass or the part and its resources. If the part has no physics this will always be 0.

Part:WETMASS
Access:Get only
Type:Scalar

The mass of the part if all of its resources were full. If the part has no physics this will always be 0.

Part:DRYMASS
Access:Get only
Type:Scalar

The mass of the part if all of its resources were empty. If the part has no physics this will always be 0.

Part:RESOURCES
Access:Get only
Type:List

list of the Resource in this part.

Part:TARGETABLE
Access:Get only
Type:Boolean

true if this part can be selected by KSP as a target.

Part:SHIP
Access:Get only
Type:Vessel

the vessel that contains this part.

Part:GETMODULE(name)
Parameters:
  • name – (String) Name of the part module
Returns:

PartModule

Get one of the PartModules attached to this part, given the name of the module. (See Part:MODULES for a list of all the names available).

Part:GETMODULEBYINDEX(index)
Parameters:
  • index – (Scalar) Index number of the part module
Returns:

PartModule

Get one of the PartModules attached to this part, given the index number of the module. You can use Part:MODULES for a list of names of all modules on the part. The indexes are not guaranteed to always be in the same order. It is recommended to iterate over the indexes with a loop and verify the module name:

local moduleNames is part:modules.
for idx in range(0, moduleNames:length) {
    if moduleNames[idx] = "test module" {
        local pm is part:getmodulebyindex(idx).
        DoSomething(pm).
    }
}
Part:MODULES
Access:Get only
Type:List of strings

list of the names of PartModules enabled for this part.

Part:ALLMODULES

Same as Part:MODULES

Part:PARENT
Access:Get only
Type:Part

When walking the tree of parts, this is the part that this part is attached to on the way “up” toward the root part.

Part:HASPHYSICS
Access:Get only
Type:bool

This comes from a part’s configuration and is an artifact of the KSP simulation.

For a list of stock parts that have this attribute and a fuller explanation see the KSP wiki page about massless parts.

Part:HASPARENT
Access:Get only
Type:Boolean

When walking the tree of parts, this is true as long as there is a parent part to this part, and is false if this part has no parent (which can only happen on the root part).

Part:CHILDREN
Access:Get only
Type:List of Parts

When walking the tree of parts, this is all the parts that are attached as children of this part. It returns a list of zero length when this part is a “leaf” of the parts tree.

PartModule

Almost everything done with at right-click menus and action group action can be accessed via the PartModule objects that are attached to Parts of a Vessel.

The exact arrangement of PartModule to Parts to Vessels, and how to make use of a PartModule is a complex enough topic to warrant its own separate subject, described on the Ship parts and Modules page. If you have not read that page, it is recommended that you do so before using PartModules. The page you are reading now is meant as just a reference summary, not a tutorial.

Once you have a PartModule, you can use it to invoke the behaviors that are connected to its right-click menu and to its action groups.

structure PartModule
Members
Suffix Type Description
NAME string Name of this part module
PART Part Part attached to
ALLFIELDS List of strings Accessible fields
ALLFIELDNAMES List of strings Accessible fields (name only)
ALLEVENTS List of strings Triggerable events
ALLEVENTNAMES List of strings Triggerable event names
ALLACTIONS List of strings Triggerable actions
ALLACTIONNAMES List of strings Triggerable event names
GETFIELD(name)   Get value of a field by name
SETFIELD(name,value)   Set value of a field by name
DOEVENT(name)   Trigger an event button
DOACTION(name,bool)   Activate action by name with True or False
HASFIELD(name) Boolean Check if field exists
HASEVENT(name) Boolean Check if event exists
HASACTION(name) Boolean Check if action exists
PartModule:NAME
Access:Get only
Test:string

Get the name of the module. Note that it’s the same as the name given in the MODULE section of the Part.cfg file for the part.

PartModule:PART
Access:Get only
Test:Part

Get the Part that this PartModule is attached to.

PartModule:ALLFIELDS
Access:Get only
Test:List of strings

Get a list of all the names of KSPFields on this PartModule that the kos script is CURRENTLY allowed to get or set with :GETFIELD or :SETFIELD. Note the Security access comments below. This list can become obsolete as the game continues running depending on what the PartModule chooses to do.

PartModule:ALLFIELDNAMES
Access:Get only
Test:List of strings

Similar to :ALLFIELDS except that it returns the string without the formatting to make it easier to use in a script. This list can become obsolete as the game continues running depending on what the PartModule chooses to do.

PartModule:ALLEVENTS
Access:Get only
Test:List of strings

Get a list of all the names of KSPEvents on this PartModule that the kos script is CURRENTLY allowed to trigger with :DOEVENT. Note the Security access comments below. This list can become obsolete as the game continues running depending on what the PartModule chooses to do.

PartModule:ALLEVENTNAMES
Access:Get only
Test:List of strings

Similar to :ALLEVENTS except that it returns the string without the formatting to make it easier to use in a script. This list can become obsolete as the game continues running depending on what the PartModule chooses to do.

PartModule:ALLACTIONS
Access:Get only
Test:List of strings

Get a list of all the names of KSPActions on this PartModule that the kos script is CURRENTLY allowed to trigger with :DOACTION. Note the Security access comments below.

PartModule:ALLACTIONNAMES
Access:Get only
Test:List of strings

Similar to :ALLACTIONS except that it returns the string without the formatting to make it easier to use in a script. This list can become obsolete as the game continues running depending on what the PartModule chooses to do.

PartModule:GETFIELD(name)
Parameters:
  • name – (string) Name of the field
Returns:

varies

Get the value of one of the fields that this PartModule has placed onto the rightclick menu for the part. Note the Security comments below.

PartModule:SETFIELD(name,value)
Parameters:
  • name – (string) Name of the field

Set the value of one of the fields that this PartModule has placed onto the rightclick menu for the part. Note the Security comments below.

WARNING: This suffix is only settable for parts attached to the CPU Vessel

PartModule:DOEVENT(name)
Parameters:
  • name – (string) Name of the event

Trigger an “event button” that is on the rightclick part menu at the moment. Note the Security comments below.

WARNING: This suffix is only callable for parts attached to the CPU Vessel

PartModule:DOACTION(name,bool)
Parameters:
  • name – (string) Name of the action
  • bool – (Boolean) Value to set: True or False

Activate one of this PartModule’s action-group-able actions, bypassing the action group system entirely by just activating it for this one part directly. The Boolean value decides whether you are toggling the action ON or toggling it OFF. Note the Security comments below.

WARNING: This suffix is only callable for parts attached to the CPU Vessel

PartModule:HASFIELD(name)
Parameters:
  • name – (string) Name of the field
Returns:

Boolean

Return true if the given field name is currently available for use with :GETFIELD or :SETFIELD on this PartModule, false otherwise.

PartModule:HASEVENT(name)
Parameters:
  • name – (string) Name of the event
Returns:

Boolean

Return true if the given event name is currently available for use with :DOEVENT on this PartModule, false otherwise.

PartModule:HASACTION(name)
Parameters:
  • name – (string) Name of the action
Returns:

Boolean

Return true if the given action name is currently available for use with :DOACTION on this PartModule, false otherwise.

Notes

In all the above cases where there is a name being passed in to :GETFIELD, :SETFIELD, :DOEVENT, or :DOACTION, the name is meant to be the name that is seen by you, the user, in the GUI screen, and NOT necessarily the actual name of the variable that the programmer of that PartModule chose to call the value behind the scenes. This is so that you can view the GUI rightclick menu to see what to call things in your script.

Note

Security and Respecting other Mod Authors

There are often a lot more fields and events and actions that a partmodule can do than are usable via kOS. In designing kOS, the kOS developers have deliberately chosen NOT to expose any “hidden” fields of a partmodule that are not normally shown to the user, without the express permission of a mod’s author to do so.

The access rules that kOS uses are as follows:

KSPFields

Is this a value that the user can normally see on the right-click context menu for a part? If so, then let kOS scripts GET the value. Is this a value that the user can normally manipulate via “tweakable” adjustments on the right-click context menu for a part, AND, is that tweakable a CURRENTLY enabled one? If so, then let KOS scripts SET the value, BUT they must set it to one of the values that the GUI would normally allow, according to the following rules.

  • If the KSPField is boolean:
    • The value must be true, false, or 0 or 1.
  • If the KSPField is an integer:
    • The value must be a whole number.
  • If the KSPField is a floating point sliding number:
    • The GUI for this field will be defined as a slider with a min value, a max value, with a fixed increment interval where the detents are. When setting such a value, you will be constrained to the limits of this slider. For example: If a slider is defined to have a minimum value of 2.0, a maximum value of 5.0, and a minimum allowed delta increment of 0.1:
    • If you try to set it to 0, it will instead become 2, the minimum allowed value. If you try to set it to 9, it will instead become 5, the maximum allowed value. If you try to set it to 3.14159, it will instead become 3.1, because that’s rounding to the nearest increment step the slider supports.
  • If the KSPField is a string:
    • There is currently no way to set these because kOS uses the existence of a gui tweakable as “proof” that it’s okay to modify the field, and in the stock game there are no gui tweakables for string fields. This may change in the future if mods that extend the tweakables system are taken into account.
KSPEvents

Is this an event that has a GUI button associated with it that is currently visible on the right-click menu? If the answer is yes, then it will also be triggerable by kOSScripts, otherwise it won’t.

KSPActions

Is this an action that the KSP user would have been allowed to set as part of an action group during building in the VAB or SPH? If so, then allow a kOS script to use it, EVEN IF it has never actually been added to an action group for this vessel.

Note

If a KSPField, KSPEvent, or KSPAction has been disallowed, often in kOS it won’t even appear to be a field of the PartModule at all.

This is necessary because for some modules, the number of fields you can use are far outnumberd by the number of fields that exist but are normally hidden from view. It would become unworkable if all of the unusable ones were exposed to kOS scripts to see as fields.

Note

Which KSPFields, KSPEvents, and KSPActions exist on a PartModule can change during runtime!

A PartModule is allowed to change the look and feel of its rightclick menu fields on the fly as the game runs. Therefore a field that didn’t exist the last time you looked might now exist, and might not exist again next time. The list of what fields exist is context dependant. For example, a docking port may have an event button on it called “Undock Node”, that only exists when that port is connected to another port. If it’s not connected, the button may be gone. Similarly, a PartModule might toggle something by using a pair of two events that swap in and out depending on the current state. For example, many of the stock lights in the game have a “Turn on” button that after it’s been clicked, gets replaced with a “Turn off” button until it’s clicked again. A boolean toggle with a KSPFIeld would be simpler, but until “tweakables” existed in the main game, that wasn’t an option so a lot of older Partmodules still do things the old way with two KSPEvents that swap in and out.

Resource

A single resource value a thing holds (i.e. fuel, electric charge, etc). This is the type returned by the Part:RESOURCES suffix

structure Resource
Suffix Type Description
NAME string Resource name
AMOUNT scalar Amount of this resource left
DENSITY scalar Density of this resource
CAPACITY scalar Maximum amount of this resource
TOGGLEABLE Boolean Can this tank be removed from the fuel flow
ENABLED Boolean Is this tank currently in the fuel flow
Resource:NAME
Access:Get only
Type:string

The name of the resource, i.e. “LIQUIDFUEL”, “ELECTRICCHARGE”, “MONOPROP”.

Resource:AMOUNT
Access:Get only
Type:scalar

The value of how much resource is left.

Resource:DENSITY
Access:Get only
Type:scalar

The density value of this resource, expressed in Megagrams f mass per Unit of resource. (i.e. a value of 0.005 would mean that each unit of this resource is 5 kilograms. Megagrams [metric tonnes] is the usual unit that most mass in the game is represented in.)

Resource:CAPACITY
Access:Get only
Type:scalar

What AMOUNT would be if the resource was filled to the top.

Resource:TOGGLEABLE
Access:Get only
Type:Boolean

Many, but not all, resources can be turned on and off, this removes them from the fuel flow.

Resource:ENABLED
Access:Get/Set
Type:Boolean

If the resource is TOGGLEABLE, setting this to false will prevent the resource from being taken out normally.

ScienceData

Represents results of a science experiment.

structure ScienceData
Suffix Type Description
TITLE string Experiment title
SCIENCEVALUE scalar Amount of science that would be gained by returning this data to KSC
TRANSMITVALUE scalar Amount of science that would be gained by transmitting this data to KSC
DATAAMOUNT scalar Amount of data
ScienceData:TITLE
Access:Get only
Type:string

Experiment title

ScienceData:SCIENCEVALUE
Access:Get only
Type:scalar

Amount of science that would be gained by returning this data to KSC

ScienceData:TRANSMITVALUE
Access:Get only
Type:scalar

Amount of science that would be gained by transmitting this data to KSC

ScienceData:DATAAMOUNT
Access:Get only
Type:scalar

Amount of data

ScienceExperimentModule

The type of structures returned by kOS when querying a module that contains a science experiment.

Some of the science-related tasks are normally not available to kOS scripts. It is for example possible to deploy a science experiment:

SET P TO SHIP:PARTSNAMED("GooExperiment")[1].
SET M TO P:GETMODULE("ModuleScienceExperiment").
M:DOEVENT("observe mystery goo").

Hovewer, this results in a dialog being shown to the user. Only from that dialog it is possible to reset the experiment or transmit the experiment results back to Kerbin. ScienceExperimentModule structure introduces a few suffixes that allow the player to perform all science-related tasks without any manual intervention:

SET P TO SHIP:PARTSNAMED("GooExperiment")[0].
SET M TO P:GETMODULE("ModuleScienceExperiment").
M:DEPLOY.
WAIT UNTIL M:HASDATA.
M:TRANSMIT.

Please note the use of WAIT UNTIL M:HASDATA.

This structure should work well with stock science experiments. Mods that introduce their own science parts might not be compatible with it. One notable example is SCANsat. Even though SCANsat parts look and behave very similarly to stock science experiments under the hood they work very differently. Other mods can cause problems as well, please test them before use.

DMagic Orbital Science has dedicated support in kOS and should work properly.

structure ScienceExperimentModule
Suffix Type Description
All suffixes of PartModule   ScienceExperimentModule objects are a type of PartModule
DEPLOY()   Deploy and run the science experiment
RESET()   Reset this experiment if possible
TRANSMIT()   Transmit the scientific data back to Kerbin
DUMP()   Discard the data
INOPERABLE Boolean Is this experiment inoperable
RERUNNABLE Boolean Can this experiment be run multiple times
DEPLOYED Boolean Is this experiment deployed
HASDATA Boolean Does the experiment have scientific data
DATA List of ScienceData List of scientific data obtained by this experiment

Note

A ScienceExperimentModule is a type of PartModule, and therefore can use all the suffixes of PartModule.

ScienceExperimentModule:DEPLOY()

Call this method to deploy and run this science experiment. This method will fail if the experiment already contains scientific data or is inoperable.

ScienceExperimentModule:RESET()

Call this method to reset this experiment. This method will fail if the experiment is inoperable.

ScienceExperimentModule:TRANSMIT()

Call this method to transmit the results of the experiment back to Kerbin. This will render the experiment inoperable if it is not rerunnable. This method will fail if there is no data to send.

ScienceExperimentModule:DUMP()

Call this method to discard the data obtained as a result of running this experiment.

ScienceExperimentModule:INOPERABLE
Access:Get only
Type:Boolean

True if this experiment is no longer operable.

ScienceExperimentModule:RERUNNABLE
Access:Get only
Type:Boolean

True if this experiment can be run multiple times.

ScienceExperimentModule:DEPLOYED
Access:Get only
Type:Boolean

True if this experiment is deployed.

ScienceExperimentModule:HASDATA
Access:Get only
Type:Boolean

True if this experiment has scientific data stored.

ScienceExperimentModule:DATA
Access:Get only
Type:List of ScienceData

List of scientific data obtained by this experiment

Sensor

The type of structures returned by LIST SENSORS IN SOMEVARIABLE. This is not fully understood because the type of PartModule in the KSP API called ModuleEnviroSensor, which all Sensors are a kind of, is not well documented. Here is an example of using Sensor:

PRINT "Full Sensor Dump:".
LIST SENSORS IN SENSELIST.

// TURN EVERY SINGLE SENSOR ON
FOR S IN SENSELIST {
  PRINT "SENSOR: " + S:TYPE.
  PRINT "VALUE:  " + S:DISPLAY.
  IF S:ACTIVE {
    PRINT "     SENSOR IS ALREADY ON.".
  } ELSE {
    PRINT "     SENSOR WAS OFF.  TURNING IT ON.".
    S:TOGGLE().
  }
}
structure Sensor
Suffix Type Description
All suffixes of Part   Sensor objects are a type of Part
ACTIVE Boolean Check if this sensor is active
TYPE    
DISPLAY string Value of the readout
POWERCONSUMPTION scalar Rate of required electric charge
TOGGLE()   Call to activate/deactivate

Note

A Sensor is a type of Part, and therefore can use all the suffixes of Part.

Sensor:ACTIVE
Access:Get only
Type:Boolean

True of the sensor is enabled. Can SET to cause the sensor to activate or de-activate.

Sensor:TYPE
Access:Get only
Sensor:DISPLAY
Access:Get only
Type:string

The value of the sensor’s readout, usualy including the units.

Sensor:POWERCONSUMPTION
Access:Get only
Type:scalar

The rate at which this sensor drains ElectricCharge.

Sensor:TOGGLE()

Call this method to cause the sensor to switch between active and deactivated or visa versa.

Stage

Contents

Staging Example

A very simple auto-stager using :READY

LIST ENGINES IN elist.

UNTIL false {
    PRINT "Stage: " + STAGE:NUMBER AT (0,0).
    FOR e IN elist {
        IF e:FLAMEOUT {
            STAGE.
            PRINT "STAGING!" AT (0,0).

            UNTIL STAGE:READY {
                WAIT 0.
            }

            LIST ENGINES IN elist.
            CLEARSCREEN.
            BREAK.
        }
    }
}
Stage Function
Stage
Return:None

Activates the next stage if the cpu vessel is the active vessel. This will trigger engines, decouplers, and any other parts that would normally be triggered by manually staging. The default equivalent key binding is the space bar. As with other parameter-less functions, both STAGE. and STAGE(). are acceptable ways to call the function.

Note

Changed in version 1.0.1: The stage function will automatically pause execution until the next tick. This is because some of the results of the staging event take effect immediately, while others do not update until the next time that physics are calculated. Calling STAGE. is essentially equivalent to:

STAGE.
WAIT 0.

Warning

Calling the Stage function on a vessel other than the active vessel will throw an exception.

Stage Structure
structure Stage
Members
Suffix Type (units) Access Description
READY Boolean Get only Is the craft ready to activate the next stage.
NUMBER Scalar Get only The current stage number for the craft
RESOURCES List Get only the List of AggregateResource in the current stage
RESOURCESLEX Lexicon Get only the Lexicon of name String keyed AggregateResource values in the current stage
Stage:READY
Access:Get only
Type:Boolean

Kerbal Space Program enforces a small delay between staging commands, this is to allow the last staging command to complete. This bool value will let you know if kOS can activate the next stage.

Stage:NUMBER
Access:Get only
Type:Scalar

Every craft has a current stage, and that stage is represented by a number, this is it!

Stage:Resources
Access:Get
Type:List

This is a collection of the available AggregateResource for the current stage.

Stage:Resourceslex
Access:Get
Type:Lexicon

This is a dictionary style collection of the available Resource for the current stage. The String key in the lexicon will match the name suffix on the AggregateResource. This suffix walks the parts list entirely on every call, so it is recommended that you cache the value if it will be reference repeatedly.

Vessel

All vessels share a structure. To get a variable referring to any vessel you can do this:

// Get a vessel by it's name.
// The name is Case Sensitive.
SET MY_VESS TO VESSEL("Some Ship Name").

// Save the current vessel in a variable,
// in case the current vessel changes.
SET MY_VESS TO SHIP.

// Save the target vessel in a variable,
// in case the target vessel changes.
SET MY_VESS TO TARGET.

Note

New in version 0.13: A vessel is now a type of Orbitable. Much of what a Vessel can do can now by done by any orbitable object. The documentation for those abilities has been moved to the orbitable page.

structure Vessel
Suffix Type Description
Every suffix of Orbitable
CONTROL Control Raw flight controls
BEARING scalar (deg) relative heading to this vessel
HEADING scalar (deg) Absolute heading to this vessel
MAXTHRUST scalar Sum of active maximum thrusts
MAXTHRUSTAT(pressure) scalar Sum of active maximum thrusts at the given atmospheric pressure
AVAILABLETHRUST scalar Sum of active limited maximum thrusts
AVAILABLETHRUSTAT(pressure) scalar Sum of active limited maximum thrusts at the given atmospheric pressure
FACING Direction The way the vessel is pointed
MASS scalar (metric tons) Mass of the ship
WETMASS scalar (metric tons) Mass of the ship fully fuelled
DRYMASS scalar (metric tons) Mass of the ship with no resources
DYNAMICPRESSURE scalar (ATM’s) Air Pressure surrounding the vessel
Q scalar (ATM’s) Alias name for DYNAMICPRESSURE
VERTICALSPEED scalar (m/s) How fast the ship is moving “up”
GROUNDSPEED scalar (m/s) How fast the ship is moving “horizontally”
AIRSPEED scalar (m/s) How fast the ship is moving relative to the air
TERMVELOCITY scalar (m/s) terminal velocity of the vessel
SHIPNAME string The name of the vessel
NAME string Synonym for SHIPNAME
STATUS string Current ship status
TYPE string Ship type
ANGULARMOMENTUM Vector In SHIP_RAW
ANGULARVEL Vector In SHIP_RAW
SENSORS VesselSensors Sensor data
LOADED Boolean loaded into KSP physics engine or “on rails”
UNPACKED Boolean The ship has individual parts unpacked
LOADDISTANCE LoadDistance the LoadDistance object for this vessel
ISDEAD Boolean True if the vessel refers to a ship that has gone away.
PATCHES List Orbit patches
ROOTPART Part Root Part of this vessel
CONTROLPART Part Control reference Part of this vessel
PARTS List all Parts
DOCKINGPORTS List all DockingPorts
ELEMENTS List all Elements
RESOURCES List all AggrgateResources
PARTSNAMED(name) List Parts by NAME
PARTSNAMEDPATTERN(namePattern) List Parts by NAME regex pattern
PARTSTITLED(title) List Parts by TITLE
PARTSTITLEDPATTERN(titlePattern) List Parts by TITLE regex pattern
PARTSTAGGED(tag) List Parts by TAG
PARTSTAGGEDPATTERN(tagPattern) List Parts by TAG regex pattern
PARTSDUBBED(name) List Parts by NAME, TITLE or TAG
PARTSDUBBEDPATTERN(namePattern) List Parts by NAME, TITLE or TAG regex pattern
MODULESNAMED(name) List PartModules by NAME
PARTSINGROUP(group) List Parts by action group
MODULESINGROUP(group) List PartModules by action group
ALLPARTSTAGGED() List Parts that have non-blank nametags
CREWCAPACITY scalar Crew capacity of this vessel
CREW() List all CrewMembers
CONNECTION Connection Returns your connection to this vessel
MESSAGES MessageQueue This vessel’s message queue

Note

This type is serializable.

Vessel:CONTROL
Type:Control
Access:Get only

The structure representing the raw flight controls for the vessel.

WARNING: This suffix is only gettable for CPU Vessel

Vessel:BEARING
Type:scalar
Access:Get only

relative compass heading (degrees) to this vessel from the CPU Vessel, taking into account the CPU Vessel’s own heading.

Vessel:HEADING
Type:scalar
Access:Get only

absolute compass heading (degrees) to this vessel from the CPU Vessel

Vessel:MAXTHRUST
Type:scalar
Access:Get only

Sum of all the engines’ MAXTHRUSTs of all the currently active engines In Kilonewtons.

Vessel:MAXTHRUSTAT(pressure)
Parameters:
  • pressure – atmospheric pressure (in standard Kerbin atmospheres)
Return type:

scalar (kN)

Sum of all the engines’ MAXTHRUSTATs of all the currently active engines In Kilonewtons at the given atmospheric pressure. Use a pressure of 0 for vacuum, and 1 for sea level (on Kerbin).

Vessel:AVAILABLETHRUST
Type:scalar
Access:Get only

Sum of all the engines’ AVAILABLETHRUSTs of all the currently active engines taking into account their throttlelimits. Result is in Kilonewtons.

Vessel:AVAILABLETHRUSTAT(pressure)
Parameters:
  • pressure – atmospheric pressure (in standard Kerbin atmospheres)
Return type:

scalar (kN)

Sum of all the engines’ AVAILABLETHRUSTATs of all the currently active engines taking into account their throttlelimits at the given atmospheric pressure. Result is in Kilonewtons. Use a pressure of 0 for vacuum, and 1 for sea level (on Kerbin).

Vessel:FACING
Type:Direction
Access:Get only

The way the vessel is pointed.

Vessel:MASS
Type:scalar (metric tons)
Access:Get only

The mass of the ship

Vessel:WETMASS
Type:scalar (metric tons)
Access:Get only

The mass of the ship if all resources were full

Vessel:DRYMASS
Type:scalar (metric tons)
Access:Get only

The mass of the ship if all resources were empty

Vessel:DYNAMICPRESSURE
Type:scalar (ATM’s)
Access:Get only

Returns what the air pressure is in the atmosphere surrounding the vessel. The value is returned in units of sea-level Kerbin atmospheres. Many formulae expect you to plug in a value expressed in kiloPascals, or kPA. You can convert this value into kPa by multiplying it by constant:ATMtokPa.

Vessel:Q
Type:scalar (ATM’s)
Access:Get only

Alias for DYNAMICPRESSURE

Vessel:VERTICALSPEED
Type:scalar (m/s)
Access:Get only

How fast the ship is moving. in the “up” direction relative to the SOI Body’s sea level surface.

Vessel:GROUNDSPEED
Type:scalar (m/s)
Access:Get only

How fast the ship is moving in the two dimensional plane horizontal to the SOI body’s sea level surface. The vertical component of the ship’s velocity is ignored when calculating this.

Note

New in version 0.18: The old name for this value was SURFACESPEED. The name was changed because it was confusing before. “surface speed” implied it’s the scalar magnitude of “surface velocity”, but it wasn’t, because of how it ignores the vertical component.

Vessel:AIRSPEED
Type:scalar (m/s)
Access:Get only

How fast the ship is moving relative to the air. KSP models atmosphere as simply a solid block of air “glued” to the planet surface (the weather on Kerbin is boring and there’s no wind). Therefore airspeed is generally the same thing as as the magnitude of the surface velocity.

Vessel:TERMVELOCITY
Type:scalar (m/s)
Access:Get only

terminal velocity of the vessel in freefall through atmosphere, based on the vessel’s current altitude above sea level, and its drag properties. Warning, can cause values of Infinity if used in a vacuum, and kOS sometimes does not let you store Infinity in a variable.

Vessel:SHIPNAME
Type:string
Access:Get/Set

The name of the vessel as it appears in the tracking station. When you set this, it cannot be empty.

Vessel:NAME

Same as Vessel:SHIPNAME.

Vessel:STATUS
Type:string
Access:get only

The current status of the vessel possible results are: LANDED, SPLASHED, PRELAUNCH, FLYING, SUB_ORBITAL, ORBITING, ESCAPING and DOCKED.

Vessel:TYPE
Type:string
Access:Get/Set

The ship’s type as described on the KSP wiki.

Vessel:ANGULARMOMENTUM
Type:Direction
Access:Get only

Given in SHIP_RAW reference frame. The vector represents the axis of the rotation (in left-handed convention, not right handed as most physics textbooks show it), and its magnitude is the angular momentum of the rotation, which varies not only with the speed of the rotation, but also with the angular inertia of the vessel.

Units are expressed in: (Megagrams * meters^2) / (seconds * radians)

(Normal SI units would use kilograms, but in KSP all masses use a 1000x scaling factor.)

Justification for radians here: Unlike the trigonometry functions in kOS, this value uses radians rather than degrees. The convention of always expressing angular momentum using a formula that assumes you’re using radians is a very strongly adhered to universal convention, for... reasons. It’s so common that it’s often not even explicitly mentioned in information you may find when doing a web search on helpful formulae about angular momentum. This is why kOS doesn’t use degrees here. (That an backward compatibility for old scripts. It’s been like this for quite a while.).

Note

Changed in version 0.15.4: This has been changed to a vector, as it should have been all along.

Vessel:ANGULARVEL

Angular velocity of the body’s rotation about its axis (its day) expressed as a vector.

The direction the angular velocity points is in Ship-Raw orientation, and represents the axis of rotation. Remember that everything in Kerbal Space Program uses a left-handed coordinate system, which affects which way the angular velocity vector will point. If you curl the fingers of your left hand in the direction of the rotation, and stick out your thumb, the thumb’s direction is the way the angular velocity vector will point.

The magnitude of the vector is the speed of the rotation.

Note, unlike many of the other parts of kOS, the rotation speed is expressed in radians rather than degrees. This is to make it congruent with how VESSEL:ANGULARMOMENTUM is expressed, and for backward compatibility with older kOS scripts.

Vessel:SENSORS
Type:VesselSensors
Access:Get only

Structure holding summary information of sensor data for the vessel

Vessel:LOADED
Type:Boolean
Access:Get only

True if the vessel is fully loaded into the complete KSP physics engine (false if it’s “on rails”). See LoadDistance for details.

Vessel:UNPACKED
Type:Boolean
Access:Get only

True if the vessel is fully unpacked. That is to say that all of the individual parts are loaded and can be interacted with. This allows docking ports to be targeted, and controls if some actions/events on parts will actually trigger. See LoadDistance for details.

Vessel:LOADDISTANCE
Type:LoadDistance
Access:Get only

Returns the load distance object for this vessel. The suffixes of this object may be adjusted to change the loading behavior of this vessel. Note: these settings are not persistent across flight instances, and will reset the next time you launch a craft from an editor or the tracking station.

Vessel:ISDEAD
Type:Boolean
Access:Get only

It is possible to have a variable that refers to a vessel that doesn’t exist in the Kerbal Space Program universe anymore, but did back when you first got it. For example: you could do: SET VES TO VESSEL(“OTHER”). WAIT 10. And in that intervening waiting time, the vessel might have crashed into the ground. Checking :ISDEAD lets you see if the vessel that was previously valid isn’t valid anymore.

Vessel:PATCHES
Type:List
Access:Get only

The list of orbit patches that describe this vessel’s current travel path based on momentum alone with no thrusting changes. If the current path has no transitions to other bodies, then this will be a list of only one orbit. If the current path intersects other bodies, then this will be a list describing the transitions into and out of the intersecting body’s sphere of influence. SHIP:PATCHES[0] is always exactly the same as SHIP:OBT, SHIP:PATCHES[1] is the same as SHIP:OBT:NEXTPATCH, SHIP:PATCHES[2] is the same as SHIP:OBT:NEXTPATCH:NEXTPATCH, and so on. Note that you will only see as far into the future as your KSP settings allow. (See the setting CONIC_PATCH_LIMIT in your settings.cfg file)

Vessel:ROOTPART
Type:Part
Access:Get only

The ROOTPART is usually the first Part that was used to begin the ship design - the command core. Vessels in KSP are built in a tree-structure, and the first part that was placed is the root of that tree. It is possible to change the root part in VAB/SPH by using Root tool, so ROOTPART does not always point to command core or command pod. Vessel:ROOTPART may change in flight as a result of docking/undocking or decoupling of some part of a Vessel.

Vessel:CONTROLPART
Type:Part
Access:Get only

Returns the Part serving as the control reference, relative to which the directions (as displayed on the navball and returned in FACING) are determined. A part may be set as the control reference part by “Control From Here” action or PART:CONTROLFROM command (available for parts of specific types). NOTE: It is possible for this to return unexpected values if the root part of the vessel cannot serve as a control reference, and the control has not been directly selected.

Vessel:PARTS
Type:List of Part objects
Access:Get only

A List of all the parts on the vessel. SET FOO TO SHIP:PARTS. has exactly the same effect as LIST PARTS IN FOO.. For more information, see ship parts and modules.

Vessel:DOCKINGPORTS
Type:List of DockingPort objects
Access:Get only

A List of all the docking ports on the Vessel.

Vessel:ELEMENTS
Type:List of Element objects
Access:Get only

A List of all the elements on the Vessel.

Vessel:RESOURCES
Type:List of AggregateResource objects
Access:Get only

A List of all the AggregateResources on the vessel. SET FOO TO SHIP:RESOURCES. has exactly the same effect as LIST RESOURCES IN FOO..

Vessel:PARTSNAMED(name)
Parameters:
  • name – (string) Name of the parts
Returns:

List of Part objects

Part:NAME. The matching is done case-insensitively. For more information, see ship parts and modules.

Vessel:PARTSNAMEDPATTERN(namePattern)
Parameters:
  • namePattern – (string) Pattern of the name of the parts
Returns:

List of Part objects

Part:NAME. The matching is done identically as in String:MATCHESPATTERN. For more information, see ship parts and modules.

Vessel:PARTSTITLED(title)
Parameters:
  • title – (string) Title of the parts
Returns:

List of Part objects

Part:TITLE. The matching is done case-insensitively. For more information, see ship parts and modules.

Vessel:PARTSTITLEDPATTERN(titlePattern)
Parameters:
  • titlePattern – (string) Patern of the title of the parts
Returns:

List of Part objects

Part:TITLE. The matching is done identically as in String:MATCHESPATTERN. For more information, see ship parts and modules.

Vessel:PARTSTAGGED(tag)
Parameters:
  • tag – (string) Tag of the parts
Returns:

List of Part objects

Part:TAG value. The matching is done case-insensitively. For more information, see ship parts and modules.

Vessel:PARTSTAGGEDPATTERN(tagPattern)
Parameters:
  • tagPattern – (string) Pattern of the tag of the parts
Returns:

List of Part objects

Part:TAG value. The matching is done identically as in String:MATCHESPATTERN. For more information, see ship parts and modules.

Vessel:PARTSDUBBED(name)
Parameters:
  • name – (string) name, title or tag of the parts
Returns:

List of Part objects

name regardless of whether that name is the Part:Name, the Part:Tag, or the Part:Title. It is effectively the distinct union of :PARTSNAMED(val), :PARTSTITLED(val), :PARTSTAGGED(val). The matching is done case-insensitively. For more information, see ship parts and modules.

Vessel:PARTSDUBBEDPATTERN(namePattern)
Parameters:
  • namePattern – (string) Pattern of the name, title or tag of the parts
Returns:

List of Part objects

name regardless of whether that name is the Part:Name, the Part:Tag, or the Part:Title. It is effectively the distinct union of :PARTSNAMEDPATTERN(val), :PARTSTITLEDPATTERN(val), :PARTSTAGGEDPATTERN(val). The matching is done identically as in String:MATCHESPATTERN. For more information, see ship parts and modules.

Vessel:MODULESNAMED(name)
Parameters:
  • name – (string) Name of the part modules
Returns:

List of PartModule objects

match the given name. The matching is done case-insensitively. For more information, see ship parts and modules.

Vessel:PARTSINGROUP(group)
Parameters:
  • group – (integer) the action group number
Returns:

List of Part objects

one action triggered by the given action group. For more information, see ship parts and modules.

Vessel:MODULESINGROUP(group)
Parameters:
  • group – (integer) the action group number
Returns:

List of PartModule objects

have at least one action triggered by the given action group. For more information, see ship parts and modules.

Vessel:ALLPARTSTAGGED()
Returns:List of Part objects

nametag on them of any sort that is nonblank. For more information, see ship parts and modules.

Vessel:CREWCAPACITY
Type:scalar
Access:Get only

crew capacity of this vessel

Vessel:CREW()
Returns:List of CrewMember objects

list of all kerbonauts aboard this vessel

Vessel:CONNECTION
Return:Connection

Returns your connection to this vessel.

Vessel:MESSAGES
Return:MessageQueue

Returns this vessel’s message queue. You can only access this attribute for your current vessel (using for example SHIP:MESSAGES).

VesselSensors

When you ask a Vessel to tell you its Vessel:SENSORS suffix, it returns an object of this type. It is a snapshot of sensor data taken at the moment the sensor reading was requested.

Note

These values are only enabled if you have the proper type of sensor on board the vessel. If you don’t have a thermometer, for example, then the :TEMP suffix will always read zero.

If you store this in a variable and wait, the numbers are frozen in time and won’t change as the vessel’s condition changes.

structure VesselSensors
Members
Suffix Type Description
ACC Vector Acceleration experienced by the Vessel
PRES scalar Atmospheric Pressure outside this Vessel
TEMP scalar Temperature outside this Vessel
GRAV Vector (g’s) Gravitational acceleration
LIGHT scalar Sun exposure on the solar panels of this Vessel
VesselSensors:ACC
Access:Get only
Type:Vector

Accelleration the vessel is undergoing. A combination of both the gravitational pull and the engine thrust.

VesselSensors:PRES
Access:Get only
Type:scalar

The current pressure of this ship.

VesselSensors:TEMP
Access:Get only
Type:scalar

The current temperature.

VesselSensors:GRAV
Access:Get only
Type:Vector

Magnitude and direction of gravity acceleration where the vessel currently is. Magnitude is expressed in “G“‘s (multiples of 9.802 m/s^2).

VesselSensors:LIGHT
Access:Get only
Type:scalar

The total amount of sun exposure that exists here - only readable if there are solar panels on the vessel.

Communication

Connection

Connections represent your ability to communicate with other processors or vessels. You can use them to find out whether such communication is currently possible and send messages.

Obtaining a connection

There are 2 types of connections. The first type is used to communicate with other processors within the same vessel. You can obtain a connection by using CONNECTION suffix. Assuming your vessel has a second processor tagged ‘second’ it could look like this:

SET MY_PROCESSOR TO PROCESSOR("second").
SET MY_CONNECTION TO MY_PROCESSOR:CONNECTION.

The second type are connections to other vessels. Assuming you have a rover on duna named ‘dunarover’ you could obtain a connection to it like this:

SET MY_VESSEL TO VESSEL("dunarover").
SET MY_CONNECTION TO MY_VESSEL:CONNECTION.
Structure
structure Connection
Suffix Type Description
ISCONNECTED Boolean true if this connection is currently opened
DELAY Scalar delay in seconds
DESTINATION Vessel or kOSProcessor destination of this connection
SENDMESSAGE(message) Boolean Sends a message using this connection
Connection:ISCONNECTED
Type:Boolean

True if the connection is opened and messages can be sent. For CPU connections this will be always true if the destionation CPU belongs to the same vessel as the current CPU. For vessel connections this will be always true in stock game. RemoteTech introduces the concept of connectivity and may cause this to be false. The connection has to be opened only in the moment of sending the message in order for it to arrive. If connection is lost after the message was sent, but before it arrives at its destination it will have no effect on whether the message will reach its destination or not.

Connection:DELAY

The number of seconds that it will take for messages sent using this connection to arrive at their destination. This value will be equal to -1 if connection is not opened. For CPU connections this will be always equal to 0 if the destination CPU belongs to the same vessel as the current CPU. Otherwise it will be equal to -1. For vessel connections this will be always zero in stock game as messages arrive instantaneously. RemoteTech introduces the concept of connectivity and may cause this to be a positive number (if there is some signal delay due to the large distance between the vessels) or -1 (if there is no connection between the vessels).

Connection:DESTINATION
Type:Vessel or kOSProcessor

Destination of this connection. Will be either a vessel or a processor.

Connection:SENDMESSAGE(message)
Parameters:
Returns:

(Boolean) true if the message was successfully sent.

Send a message using this connection. Any serializable structure or a primitive (String, Scalar or Boolean) can be given as an argument. It is always worth checking the return value of this function. A returned false value would indicate that the message was not sent for some reason. This method will fail to send the message and return false if Connection:ISCONNECTED is false.

Message

Represents a single message stored in a CPU’s or vessel’s MessageQueue.

The main message content that the sender intended to send can be retrieved using Message:CONTENT attribute. Other suffixes are automatically added to every message by kOS.

Messages are serializable and thus can be passed along:

// if there is a message in the ship's message queue
// we can forward it to a different CPU

// cpu1
SET CPU2 TO PROCESSOR("cpu2").
CPU2:CONNECTION:SENDMESSAGE(SHIP:MESSAGES:POP).

// cpu2
SET RECEIVED TO CORE:MESSAGES:POP.
PRINT "Original message sent at: " + RECEIVED:CONTENT:SENTAT.
Structure
structure Message
Suffix Type Description
SENTAT TimeSpan date this message was sent at
RECEIVEDAT TimeSpan date this message was received at
SENDER Vessel or Boolean vessel which has sent this message, or Boolean false if sender vessel is now gone
HASSENDER Boolean Tests whether or not the sender vessel still exists.
CONTENT Structure message content

Note

This type is serializable.

Message:SENTAT
Type:TimeSpan

Date this message was sent at.

Message:RECEIVEDAT
Type:TimeSpan

Date this message was received at.

Message:SENDER
Type:Vessel or Boolean

Vessel which has sent this message, or a boolean false value if the sender vessel no longer exists.

If the sender of the message doesn’t exist anymore (see the explanation for HASSENDER), this suffix will return a different type altogether. It will be a Boolean (which is false).

You can check for this condition either by using the HASSENDER suffix, or by checking the :ISTYPE suffix of the sender to detect if it’s really a vessel or not.

Message:HASSENDER
Type:Boolean

Because there can be a delay between when the message was sent and when it was processed by the receiving script, it’s possibile that the vessel that sent the message might not exist anymore. It could have either exploded, or been recovered, or been merged into another vessel via docking. You can check the value of the :HASSENDER suffix to find out if the sender of the message is still a valid vessel. If HASSENDER is false, then SENDER won’t give you an object of type Vessel and instead will give you just a Boolean false.

Message:CONTENT
Type:Structure

Content of this message.

MessageQueue

Just like ordinary queues message queues work according to First-In first-out principle. You can read more about queues on Wikipedia.

Whenever you send a message to a CPU or a vessel it gets added to the end of that CPU’s or vessel’s message queue. The recipient can then read those messages from the queue.

Accessing message queues

You can access the current processor’s message queue using CORE:MESSAGES:

SET QUEUE TO CORE:MESSAGES.
PRINT "Number of messages on the queue: " + QUEUE:LENGTH.

The current vessel’s message queue can be accessed using Vessel:MESSAGES:

SET QUEUE TO SHIP:MESSAGES.
Structure
structure MessageQueue
Suffix Type Description
EMPTY Boolean true if there are messages in the queue
LENGTH Scalar number of messages in the queue
POP() Message returns the oldest element in the queue and removes it
PEEK() Message returns the oldest element in the queue without removing it
CLEAR() None remove all messages
PUSH(message) None explicitly append a message
MessageQueue:EMPTY
Type:Boolean

True if there are no messages in this queue.

MessageQueue:LENGTH
Type:Scalar

Number of messages in this queue.

MessageQueue:POP()

Returns the first (oldest) message in the queue and removes it. Messages in the queue are always ordered by their arrival date.

MessageQueue:PEEK()
Returns:Message

Returns the oldest message in the queue without removing it from the queue.

MessageQueue:CLEAR()

Removes all messages from the queue.

MessageQueue:PUSH(message)
Parameters:
  • messageMessage message to be added

You can use this message to explicitly add a message to this queue. This will insert this exact message to the queue, all attributes that are normally added automatically by kOS (Message:SENTAT, Message:RECEIVEDAT and Message:SENDER) will not be changed.

Orbits

Orbit

Variables of type Orbit hold descriptive information about the elliptical shape of a predicted orbit. Whenever there are multiple patches of orbit ellipses strung together, for example, when an encounter with a body is expected to alter the path, or when a maneuver node is planned, then each individual patch of the path is represented by one Orbit object.

Each Orbitable item such as a Vessel or celestial Body has an :ORBIT suffix that can be used to obtain its current Orbit.

Whenever you get the Orbit of a Vessel, be aware that its just the current Orbit patch that doesn’t take into account any planetary encounters (slingshots) or maneuver nodes that may occur. For example, your vessel might never reach SHIP:ORBIT:APOAPSIS if you’re going to intersect the Mun and be flung by it into a new orbit.

Warning

Some of the parameters listed here come directly from KSP’s API and there is a bit of inconsistency with whether it uses radians or degrees for angles. As much as possible we have tried to present everything in kOS as degrees for consistency, but some of these may have slipped through. If you see any of these being reported in radians, please make a bug report.

Structure
structure Orbit
Members
Suffix Type (units) Description
NAME String name of this orbit
APOAPSIS Scalar (m) Maximum altitude
PERIAPSIS Scalar (m) Minimum altitude
BODY Body Focal body of orbit
PERIOD Scalar (s) orbital period
INCLINATION Scalar (deg) orbital inclination
ECCENTRICITY Scalar orbital eccentricity
SEMIMAJORAXIS Scalar (m) semi-major axis
SEMIMINORAXIS Scalar (m) semi-minor axis
LAN Scalar (deg) Same as LONGITUDEOFASCENDINGNODE
LONGITUDEOFASCENDINGNODE Scalar (deg) Longitude of the ascending node
ARGUMENTOFPERIAPSIS Scalar argument of periapsis
TRUEANOMALY Scalar true anomaly in degrees (not radians)
MEANANOMALYATEPOCH Scalar mean anomaly in degrees (not radians) at a specific fixed time called EPOCH
EPOCH Scalar The universal timestamp at which MEANANOMALYATEPOCH is measured.
TRANSITION String Transition from this orbit
POSITION Vector The current position
VELOCITY OrbitableVelocity The current velocity
NEXTPATCH Orbit Next Orbit
NEXTPATCHETA Scalar ETA to next Orbit
HASNEXTPATCH Boolean Has a next Orbit
Orbit:NAME
Type:String
Access:Get only

a name for this orbit.

Orbit:APOAPSIS
Type:Scalar (m)
Access:Get only

The max altitude expected to be reached.

Orbit:PERIAPSIS
Type:Scalar (m)
Access:Get only

The min altitude expected to be reached.

Orbit:BODY
Type:Body
Access:Get only

The celestial body this orbit is orbiting.

Orbit:PERIOD
Type:Scalar (seconds)
Access:Get only

orbital period

Orbit:INCLINATION
Type:Scalar (degree)
Access:Get only

orbital inclination

Orbit:ECCENTRICITY
Type:Scalar
Access:Get only

orbital eccentricity

Orbit:SEMIMAJORAXIS
Type:Scalar (m)
Access:Get only

semi-major axis

Orbit:SEMIMINORAXIS
Type:Scalar (m)
Access:Get only

semi-minor axis

Orbit:LAN

Same as Orbit:LONGITUDEOFASCENDINGNODE.

Orbit:LONGITUDEOFASCENDINGNODE
Type:Scalar (deg)
Access:Get only

The Longitude of the ascening node is the “celestial longitude” where the orbit crosses the body’s equator from its southern hemisphere to its northern hemisphere

Note that the “celestial longitude” in this case is NOT the planetary longitude of the orbit body. “Celestial longitudes” are expressed as the angle from the Solar Prime Vector, not from the body’s longitude. In order to find out where it is relative to the body’s longitude, you will have to take into account body:rotationangle, and take into account that the body will rotate by the time you get there.

Orbit:ARGUMENTOFPERIAPSIS
Type:Scalar
Access:Get only

argument of periapsis

Orbit:TRUEANOMALY
Type:Scalar
Access:Get only

true anomaly in degrees. Even though orbital parameters are traditionally done in radians, in keeping with the kOS standard of making everything into degrees, they are given as degrees by kOS.

Orbit:MEANANOMALYATEPOCH
Type:Scalar degrees
Access:Get only

mean anomaly in degrees. Even though orbital parameters are traditionally done in radians, in keeping with the kOS standard of making everything into degrees, they are given as degrees by kOS.

Internally, KSP tracks orbit position using MEANANOMALYATEPOCH and EPOCH. “Epoch” is an arbitrary timestamp expressed in universal time (gameworld seconds from game start, same as TIME:SECONDS uses) at which the mean anomaly of the orbit would be MEANANOMALYATEPOCH.

Given the mean anomaly at epoch, and the epoch time, and the current time, and the orbital period, it’s possible to find out the current mean anomaly. Kerbal Space Program uses this internally to track orbit positions while under time warp without using the full physics system.

Orbit:EPOCH
Type:Scalar universal timestamp (seconds)
Access:Get only

Internally, KSP tracks orbit position using MEANANOMALYATEPOCH and EPOCH. “Epoch” is an arbitrary timestamp expressed in universal time (gameworld seconds from game start, same as TIME:SECONDS uses) at which the mean anomaly of the orbit would be MEANANOMALYATEPOCH.

Beware, if you are an experienced programmer, you may be aware of the word “Epoch” being used to mean a fixed point in time that never ever changes throughout an entire system. For example, the Unix timestamp system refers to Jan 1, 1970 as the “epoch”. This is NOT how the word is used in KSP’s orbit system. In Kerbal Space Program, the “epoch” is not a true “epoch”, in that it often moves and you have to re-check what it is. It’s not a hardcoded constant.

(The epoch timestamp seems to change when you go on or off from time warp.)

Orbit:TRANSITION
Type:String
Access:Get only

Describes the way in which this orbit will end and become a different orbit, with a value taken from this list.

Orbit:POSITION
Type:Vector
Access:Get only

The current position of whatever the object is that is in this orbit.

Orbit:VELOCITY
Type:OrbitableVelocity
Access:Get only

The current velocity of whatever the object is that is in this orbit. Be aware that this is not just a velocity vector, but a structure containing both the orbital and surface velocity vectors as a pair. (See OrbitableVelocity).

Orbit:NEXTPATCH
Type:Orbit
Access:Get only

When this orbit has a transition to another orbit coming up, this suffix returns the next Orbit patch after this one. For example, when escaping from a Mun orbit into a Kerbin orbit from which you will escape and hit a Solar orbit, then the current orbit’s :NEXTPATCH will show the Kerbin orbit, and :NEXTPATCH:NEXTPATCH will show the solar orbit. The number of patches into the future that you can peek depends on your conic patches setting in your Kerbal Space Program Settings.cfg file.

Orbit:NEXTPATCHETA
Type:Scalar
Access:Get only

When this orbit has a transition to another orbit coming up, this suffix returns the eta to that transition. This is different from the value provided by the ETA:TRANSITION suffix as it is not limited to the patch following the current orbit, but rather may be chained to multiple patch transitions. The number of patches depends on your conic patches setting in your Kerbal Space Program Settings.cfg file.

Orbit:HASNEXTPATCH
Type:Boolean
Access:Get only

If :NEXTPATCH will return a valid patch, this is true. If :NEXTPATCH will not return a valid patch because there are no transitions occurring in the future, then HASNEXTPATCH <Orbit:HASNEXTPATCH will be false.

Both NEXTPATCH and HASNEXTPATCH both only operate on the current momentum of the object, and do not take into account any potential changes planned with maneuver nodes. To see the possible new path you would have if a maneuver node gets executed exactly as planned, you need to first get the orbit that follows the manuever node, by looking at the maneuver node’s :ORBIT suffix, and then look at its :NEXTPATCH and :HASNEXTPATCH.

Deprecated Suffix
Orbit:PATCHES
Type:List of Orbit Objects
Access:Get only

Note

Deprecated since version 0.15: To get the same functionality, you must use Vessel:PATCHES which is a suffix of the Vessel itself.

Transition Names
INITIAL
Refers to the pure of a new orbit, which is a value you will never see from the Orbit:TRANSITION suffix (it refers to the start of the orbit patch, and Orbit:TRANSITION only refers to the end of the patch.
FINAL
Means that no transition to a new orbit is expected. It this orbit is the orbit that will remain forever.
ENCOUNTER
Means that this orbit will enter a new SOI of another orbital body that is smaller in scope and is “inside” the current one. (example: currently in Sun orbit, will enter Duna Orbit.)
ESCAPE
Means that this orbit will enter a new SOI of another orbital body that is larger in scope and is “outside” the current one. (example: currently in Kerbin orbit, will enter Sun Orbit.)
MANEUVER
Means that this orbit will end due to a manuever node that starts a new orbit?

Orbitable (Vessels and Bodies)

All objects that can move in orbit around other objects share some similar structure. To help keep things as consistent as possible, that similar structure is defined here. Everything you see here works for both Vessels and Bodies.

Note

SOI Body

Every where you see the term SOI Body in the descriptions below, it refers to the body at the center of the orbit of this object - the body in who’s sphere of influence this object is located. It is important to make the distinction that if this object is itself a Body, the SOI body is the body being orbited, not the body doing the orbiting. I.e. When talking about the Mun, the SOI body means “Kerbin”. When talking about Kerbin, the SOI body means “Sun”.

structure Orbitable

These terms are all read-only.

Suffix Type (units)
NAME String
BODY Body
HASBODY boolean
HASORBIT boolean
HASOBT boolean
OBT Orbit
ORBIT Orbit
UP Direction
NORTH Direction
PROGRADE Direction
SRFPROGRADE Direction
RETROGRADE Direction
SRFRETROGRADE Direction
POSITION Vector
VELOCITY OrbitableVelocity
DISTANCE scalar (m)
DIRECTION Direction
LATITUDE scalar (deg)
LONGITUDE scalar (deg)
ALTITUDE scalar (m)
GEOPOSITION GeoCoordinates
PATCHES List of Orbits
The Following are deprecated (use apoapsis and periapsis on OBT)
APOAPSIS scalar (m)
PERIAPSIS scalar (m)
Orbitable:NAME
Type:string
Access:Get only

Name of this vessel or body.

Orbitable:HASBODY
Type:boolean
Access:Get only

True if this object has a body it orbits (false only when this object is the Sun, pretty much).

Orbitable:HASORBIT
Type:boolean
Access:Get only

Alias for HASBODY.

Orbitable:HASOBT
Type:boolean
Access:Get only

Alias for HASBODY.

Orbitable:BODY
Type:Body
Access:Get only

The Body that this object is orbiting. I.e. Mun:BODY returns Kerbin.

Orbitable:OBT
Type:Orbit
Access:Get only

The current single orbit “patch” that this object is on (not the future orbits it might be expected to achieve after maneuver nodes or encounter transitions, but what the current orbit would be if nothing changed and no encounters perturbed the orbit.

Orbitable:ORBIT
Type:Orbit
Access:Get only

This is an alias for OBT, as described above.

Orbitable:UP
Type:Direction
Access:Get only

pointing straight up away from the SOI body.

Orbitable:NORTH
Type:Direction
Access:Get only

pointing straight north on the SOI body, parallel to the surface of the SOI body.

Orbitable:PROGRADE
Type:Direction
Access:Get only

pointing in the direction of this object’s orbitable-frame velocity

Orbitable:SRFPROGRADE
Type:Direction
Access:Get only

pointing in the direction of this object’s surface-frame velocity. Note that if this Orbitable is itself a body, remember that this is relative to the surface of the SOI body, not this body.

Orbitable:RETROGRADE
Type:Direction
Access:Get only

pointing in the opposite of the direction of this object’s orbitable-frame velocity

Orbitable:SRFRETROGRADE
Type:Direction
Access:Get only

pointing in the opposite of the direction of this object’s surface-frame velocity. Note that this is relative to the surface of the SOI body.

Orbitable:POSITION
Type:Vector
Access:Get only

The position of this object in the SHIP-RAW reference frame

Orbitable:VELOCITY
Type:OrbitableVelocity
Access:Get only

The orbitable velocity of this object in the SHIP-RAW reference frame

Orbitable:DISTANCE
Type:scalar (m)
Access:Get only

The scalar distance between this object and the center of SHIP.

Orbitable:DIRECTION
Type:Direction
Access:Get only

pointing in the direction of this object from SHIP.

Orbitable:LATITUDE
Type:scalar (deg)
Access:Get only

The latitude in degrees of the spot on the surface of the SOI body directly under this object.

Orbitable:LONGITUDE
Type:scalar (deg)
Access:Get only

The longitude in degrees of the spot on the surface of the SOI body directly under this object. Longitude returned will always be normalized to be in the range [-180,180].

Orbitable:ALTITUDE
Type:scalar (m)
Access:Get only

The altitude in meters above the sea level surface of the SOI body (not the center of the SOI body. To get the true radius of the orbit for proper math calculations remember to add altitude to the SOI body’s radius.)

Orbitable:GEOPOSITION
Type:GeoCoordinates
Access:Get only

A combined structure of the latitude and longitude numbers.

Orbitable:PATCHES
Type:List of Orbit “patches”
Access:Get only

The list of all the orbit patches that this object will transition to, not taking into account maneuver nodes. The zero-th patch of the list is the current orbit.

Orbitable:APOAPSIS
Type:scalar (deg)
Access:Get only

Deprecated since version 0.15: Use OBT:APOAPSIS instead.

Orbitable:PERIAPSIS
Type:scalar (deg)
Access:Get only

Deprecated since version 0.15: Use OBT:PERIAPSIS instead.

OrbitableVelocity

When any Orbitable object returns its VELOCITY suffix, it returns it as a structure containing a pair of both its orbit-frame velocity and its surface-frame velocity at the same instant of time. To obtain its velocity as a vector you must pick whether you want the oribtal or surface velocities by giving a further suffix:

structure OrbitableVelocity
Members
Suffix Type
ORBIT Vector
SURFACE Vector
OrbitableVelocity:ORBIT
Type:Vector
Access:Get only

Returns the orbital velocity.

OrbitableVelocity:SURFACE
Type:Vector
Access:Get only

Returns the surface-frame velocity. Note that this is the surface velocity relative to the surface of the SOI body, not the orbiting object itself. (i.e. Mun:VELOCITY:SURFACE returns the Mun’s velocity relative to the surface of its SOI body, Kerbin).

Note

Special case instance, the Sun: Because the Sun has no parent SoI body that it orbits around (Kerbal Space Program does not simulate the existence of anything outside the one solar system), that means that the Sun’s surface velocity is just hardcoded to be the same thing as its orbital velocity. This may or may not be entirely correct, but the “correct” answer to the question, “What is sun:velocity:surface?” would technically be “I refuse to answer. That’s an invalid question.” Rather than crash or throw an exception, kOS just returns the same as the orbital velocity in this case.

Examples:

SET VORB TO SHIP:VELOCITY:ORBIT
SET VSRF TO SHIP:VELOCITY:SURFACE
SET MUNORB TO MUN:VELOCITY:ORBIT
SET MUNSRF TO MUN:VELOCITY:SURFACE

Note

At first glance it may seem that Mun:VELOCITY:SURFACE is wrong because it creates a vector in the opposite direction from Mun:VELOCITY:ORBIT, but this is actually correct. Kerbin’s surface rotates once every 6 hours, and the Mun takes a lot longer than 6 hours to orbit Kerbin. Therefore, relative to Kerbin’s surface, the Mun is going backward.

Celestial Bodies

Atmosphere

A Structure closely tied to Body A variable of type Atmosphere usually is obtained by the :ATM suffix of a Body. ALL The following values are read-only. You can’t change the value of a body’s atmosphere.

structure Atmosphere
Suffix Type Description
BODY string Name of the celestial body
EXISTS boolean True if this body has an atmosphere
OXYGEN boolean True if oxygen is present
SCALE scalar Used to find atmospheric density
SEALEVELPRESSURE scalar (atm) pressure at sea level
ALTITUDEPRESSURE(altitude) scalar (atm) pressure at the givel altitude
HEIGHT scalar (m) advertised atmospheric height
Atmosphere:BODY
Type:string
Access:Get only

The Body that this atmosphere is around - as a STRING NAME, not a Body object.

Atmosphere:EXISTS
Type:boolean
Access:Get only

True if this atmosphere is “real” and not just a dummy placeholder.

Atmosphere:OXYGEN
Type:boolean
Access:Get only

True if the air has oxygen and could therefore be used by a jet engine’s intake.

Atmosphere:SCALE
Type:scalar
Access:Get only

A math constant plugged into a formula to find atmosphere density.

Atmosphere:SEALEVELPRESSURE
Type:scalar (atm)
Access:Get only

Pressure at the body’s sea level.

Result is returned in Atmospheres. 1.0 Atmosphere = same as Kerbin or Earth. If you prefer to see the answer in KiloPascals, multiply the answer by Constant:AtmToKPa.

Warning

Changed in version 1.1.0: Previous versions returned this value in KiloPascals by mistake, which has now been changed to Atmospheres.

Atmosphere:ALTITUDEPRESSURE(altitude)
Parameters:
  • altitude – The altitude above sea level (in meters) you want to know the pressure for.
Return type:

scalar (atm)

Number of Atm’s of atmospheric pressure at the given altitude. If you pass in zero, you should get the sea level pressure. If you pass in 10000, you get the pressure at altitude=10,000m. This will return zero if the body has no atmosphere, or if the altitude you pass in is above the max atmosphere altitude for the body.

Result is returned in Atmospheres. 1.0 Atmosphere = same as Kerbin or Earth. If you prefer to see the answer in KiloPascals, multiply the answer by Constant:AtmToKPa.

Atmosphere:HEIGHT
Type:scalar (m)
Access:Get only

The altitude at which the atmosphere is “officially” advertised as ending. (actual ending value differs, see below).

Atmospheric Math

Note

[Section deleted]

This documentation used to contain a description of how the math for Kerbal Space Program’s default stock atmospheric model works, but everything that was mentioned here became utterly false when KSP 1.0 was released with a brand new atmospheric model that invalided pretty much everything that was said here. Rather than teach people incorrect information, it was deemed that no documentation is better than misleading documentation, so this section below this point has been removed.

Body

This is any sort of planet or moon. To get a variable referring to a Body, you can do this:

// "name" is the name of the body,
// like "Mun" for example.
SET MY_VAR TO BODY("name").

Note

Changed in version 0.13: A Celestial Body is now also an Orbitable, and can use all the terms described for these objects too.

Bodies’ names are added to the kerboscript language as variable names as well. This means you can use the variable Mun to mean the same thing as BODY("Mun"), and the variable Kerbin to mean the same thing as BODY("Kerbin"), and so on.

Note

Exception: If you are using a mod that replaces the stock game’s planets and moons with new bodies with new names, then there is a chance a body’s name will match an existing bound variable name in kOS and we cannot control this. Therefore if this happens, that body name will NOT become a variable name, so you can only refer to that body with the expression BODY(name). (For example, this occurred when Galileo Planet Pack had a planet called “Eta” which has the same name as the bound variable “ETA”).

Changed in version 1.0.2: This behavior was only added in kOS 1.0.2. Using a version of kOS prior to 1.0.2 will cause a name clash and broken behavior if a planet or moon exists that overrides a keyword name.

Predefined Celestial Bodies

All of the main celestial bodies in the game are reserved variable names. The following two lines do the exactly the same thing:

SET the_mun TO Mun.
SET the_mun TO Body("Mun").
  • Sun
  • Moho
  • Eve
    • Gilly
  • Kerbin
    • Mun
    • Minmus
  • Duna
    • Ike
  • Jool
    • Laythe
    • Vall
    • Tylo
    • Bop
    • Pol
  • Eeloo
structure Body
Suffix Type (units)
Every Suffix of Orbitable
NAME string
DESCRIPTION string
MASS scalar (kg)
ALTITUDE scalar (m)
ROTATIONPERIOD scalar (s)
RADIUS scalar (m)
MU scalar (\(m^3 s^{−2}\))
ATM Atmosphere
ANGULARVEL Vector in SHIP-RAW
GEOPOSITIONOF GeoCoordinates given SHIP-RAW position vector
GEOPOSITIONLATLNG GeoCoordinates given latitude and longitude values
ALTITUDEOF scalar (m)
SOIRADIUS scalar (m)
ROTATIONANGLE scalar (deg)

Note

This type is serializable.

Body:NAME

The name of the body. Example: “Mun”.

Body:DESCRIPTION

Longer description of the body, often just a duplicate of the name.

Body:MASS

The mass of the body in kilograms.

Body:ALTITUDE

The altitude of this body above the sea level surface of its parent body. I.e. the altitude of Mun above Kerbin.

Body:ROTATIONPERIOD

The length of the body’s day in seconds. I.e. how long it takes for it to make one rotation.

Body:RADIUS

The radius from the body’s center to its sea level.

Body:MU

The Gravitational Parameter of the body.

Body:ATM

A variable that describes the atmosphere of this body.

Body:ANGULARVEL

Angular velocity of the body’s rotation about its axis (its day) expressed as a vector.

The direction the angular velocity points is in Ship-Raw orientation, and represents the axis of rotation. Remember that everything in Kerbal Space Program uses a left-handed coordinate system, which affects which way the angular velocity vector will point. If you curl the fingers of your left hand in the direction of the rotation, and stick out your thumb, the thumb’s direction is the way the angular velocity vector will point.

The magnitude of the vector is the speed of the rotation.

Note, unlike many of the other parts of kOS, the rotation speed is expressed in radians rather than degrees. This is to make it congruent with how VESSEL:ANGULARMOMENTUM is expressed, and for backward compatibility with older kOS scripts.

Body:GEOPOSITIONOF(vectorPos)
Parameters:
  • vectorPosVector input position in XYZ space.

The geoposition underneath the given vector position. SHIP:BODY:GEOPOSITIONOF(SHIP:POSITION) should, in principle, give the same thing as SHIP:GEOPOSITION, while SHIP:BODY:GEOPOSITIONOF(SHIP:POSITION + 1000*SHIP:NORTH) would give you the lat/lng of the position 1 kilometer north of you. Be careful not to confuse this with :GEOPOSITION (no “OF” in the name), which is also a suffix of Body by virtue of the fact that Body is an Orbitable, but it doesn’t mean the same thing.

(Not to be confused with the Orbitable:GEOPOSITION suffix, which Body inherits from Orbitable, and which gives the position that this body is directly above on the surface of its parent body.)

Body:GEOPOSITIONLATLNG(latitude, longitude)
Parameters:
  • latitudeScalar input latitude
  • longitudeScalar input longitude
Return type:

GeoCoordinates

Given a latitude and longitude, this returns a GeoCoordinates structure for that position on this body.

(Not to be confused with the Orbitable:GEOPOSITION suffix, which Body inherits from Orbitable, and which gives the position that this body is directly above on the surface of its parent body.)

Body:ALTITUDEOF

The altitude of the given vector position, above this body’s ‘sea level’. SHIP:BODY:ALTITUDEOF(SHIP:POSITION) should, in principle, give the same thing as SHIP:ALTITUDE. Example: Eve:ALTITUDEOF(GILLY:POSITION) gives the altitude of gilly’s current position above Eve, even if you’re not actually anywhere near the SOI of Eve at the time. Be careful not to confuse this with :ALTITUDE (no “OF” in the name), which is also a suffix of Body by virtue of the fact that Body is an Orbitable, but it doesn’t mean the same thing.

Body:SOIRADIUS

The radius of the body’s sphere of influence. Measured from the body’s center.

Body:ROTATIONANGLE

The rotation angle is the number of degrees between the Solar Prime Vector and the current positon of the body’s prime meridian (body longitude of zero).

The value is in constant motion, and once per body’s day, its :rotationangle will wrap around through a full 360 degrees.

Creating GUIs

You can create an object that represents a GUI drawn on the user’s screen (not in the terminal window). It can have buttons, labels, and the usual GUI elements. In combination with a boot file, entirely GUI-driven vessel controls can be developed.

_images/gui-HelloWorld.png

GUI Callbacks versus Polling

There are two general styles of interacting with GUI widgets, called “callbacks” and “polling”.

The callback technique is when you create KOSDelegate objects that are either anonymous functions or named user functions, and then assign them to different widgets’ “hook suffixes”. In this technique you are telling the widget “Here is a function in my program that I want you to call whenever you notice this particular thing has happened.” For example:

set thisButton:ONCLICK to myclickFunction@.
// Program continues on, executing further commands after this.

will interrupt whatever else you are doing and call a function you wrote called myClickFunction whenever that button is clicked.

The polling technique is when you actively keep checking the widget again and again in your own script, to see if anything has happened. In this technique, you are choosing when to pay attention to the GUI widget. For example:

until thisButton:TAKEPRESS {
  // button still isn't pressed yet, let's keep waiting.
  wait 0.
}

In general, if you are trying to decide between using the callback or the polling technique, you should prefer using the callback technique most of the time. It takes less CPU time away from the rest of your program and is less of a burden on the universe simulation.

Below are longer examples of the two techniques, and how the scripts that use them would look. The suffixes and built-in functions used in these examples will be explained in detail later.

The “Hello World” program, version 1 with “callbacks”:

// "Hello World" program for kOS GUI.
//
// Create a GUI window
LOCAL gui IS GUI(200).
// Add widgets to the GUI
LOCAL label IS gui:ADDLABEL("Hello world!").
SET label:STYLE:ALIGN TO "CENTER".
SET label:STYLE:HSTRETCH TO True. // Fill horizontally
LOCAL ok TO gui:ADDBUTTON("OK").
// Show the GUI.
gui:SHOW().
// Handle GUI widget interactions.
//
// This is the technique known as "callbacks" - instead
// of actively looking again and again to see if a button was
// pressed, the script just tells kOS that it should call a
// delegate function when it notices the button has done
// something, and then the program passively waits for that
// to happen:
LOCAL isDone IS FALSE.
function myClickChecker {
  SET isDone TO TRUE.
}
SET ok:ONCLICK TO myClickChecker@. // This could also be an anonymous function instead.
wait until isDone.

print "OK pressed.  Now closing demo.".
// Hide when done (will also hide if power lost).
gui:HIDE().

The same “Hello World” program, version 2 with “polling”:

// "Hello World" program for kOS GUI.
//
// Create a GUI window
LOCAL gui IS GUI(200).
// Add widgets to the GUI
LOCAL label IS gui:ADDLABEL("Hello world!").
SET label:STYLE:ALIGN TO "CENTER".
SET label:STYLE:HSTRETCH TO True. // Fill horizontally
LOCAL ok TO gui:ADDBUTTON("OK").
// Show the GUI.
gui:SHOW().
// Handle GUI widget interactions.
//
// This is the technique known as "polling" - In a loop you
// continually check to see if something has happened:
LOCAL isDone IS FALSE.
UNTIL isDone
{
  if (ok:TAKEPRESS)
    SET isDone TO TRUE.
  WAIT 0.1. // No need to waste CPU time checking too often.
}
print "OK pressed.  Now closing demo.".
// Hide when done (will also hide if power lost).
gui:HIDE().

Creating a Window

GUI(width [, height])

This is the first place any GUI control panel starts.

The GUI built-in function creates a new GUI object that you can then manipulate to build up a GUI. If no height is specified, it will resize automatically to fit the contents you put inside it. The width can be set to 0 to force automatic width resizing too:

SET gui TO GUI(200).
SET button TO gui:ADDBUTTON("OK").
gui:SHOW().
UNTIL button:TAKEPRESS WAIT(0.1).
gui:HIDE().

See the “ADD” functions in the BOX structure for the other widgets you can add.

Warning: Setting BOTH width and height to 0 to let it choose automatic resizing in both dimensions will often lead to a look you won’t like. You may find that to have some control over the layout you will need to specify one of the two dimensions and only let it resize the other.

Removing all Windows

CLEARGUIS()

If you want to conveniently clear away all GUI windows that you created from this CPU, you can do so with the CLEARGUIS() built-in function. It will call GUI:HIDE and GUI:DISPOSE for all the gui windows that were made using this particular CPU part. (If you have multiple kOS CPUs, and some GUIs are showing that were made by other kOS CPUs, those will not be cleared by this.)

Note

This built-in function was added mainly so you have a way to easily clean up after a program has crashed which left behind some GUI windows that are now unresponsive because the program isn’t running anymore.

Communication Delay

If communication delay is enabled (eg. using RemoteTech), you will still be able to interact with a GUI, but changes to values and messages will incur the same sort of signal delay that interactive control over the vessel would incur. (If your vessel can be controlled immediately because there’s a kerbal on board, then your GUI for the vessel can be controlled immediately, but if your attempts to control the vessel are being subject to a signal delay, then your attempts to click on the GUI elements will get the same delay). Similarly, changes to values in the GUI will be delayed coming back by the same rules. Some things such as GUI creation, adding widgets, etc. are immediate for simplicity.

If you want to test or experiment with what your GUI would be like under a signal delay even though you don’t really have a signal delay, you can simulate the effect by setting the GUI:EXTRADELAY suffix of the GUI window.

Structure Reference

The GUI elements, including the GUI type itself are in the following hierarchy:

  • WIDGET - base type of all other elements.
    • BOX - a rectangular widget that contains other widgets
      • GUI - the outermost Box that represents the GUI window panel.
      • SCROLLBOX - a Box that shows only a subset of itself at a time and can be scrolled.
    • LABEL - text (or image) for display
      • BUTTON - label that notices when it’s clicked or toggled.
        • POPUPMENU - button that when clicked shows a list to pick from.
      • TEXTFIELD - label that is edit-able by the user.
    • SLIDER - vertical or horizontal movable handle that edits a Scalar value.
    • SPACING - empty whitespace area within the box for layout reasons.
Box
structure Box

Box objects are a type of WIDGET.

A Box is a rectangular widget that holds other widgets inside it.

A GUI window is a kind of Box, and is created from the GUI built-in function. You always need at least one Box created this way in order to have any GUI at all show up, and then you add the widgets you want to that Box.

Since a Box is a WIDGET, and a Box is a rectangle that contains widgets, that means you can also put a Box` inside of another ``Box. You do so by using the ADDHBOX or ADDVBOX suffixes of a box. Usually the reason to do this is to define exactly how you want a set of widgets laid out.

Suffix Type Description
Every suffix of WIDGET
ADDLABEL(text) Label Creates a label in the Box.
ADDBUTTON(text) Button Creates a clickable button in the Box.
ADDCHECKBOX(text,on) Button Creates a toggleable button in the Box.
ADDRADIOBUTTON(text,on) Button Creates an exclusive toggleable button in the Box.
ADDTEXTFIELD(text) TextField Creates an editable text field in the Box.
ADDPOPUPMENU PopupMenu Creates a popup menu.
ADDHSLIDER(init,min,max) Slider Creates a horizontal slider in the Box.
ADDVSLIDER(init,min,max) Slider Creates a vertical slider in the Box.
ADDHLAYOUT Box Creates an undecorated invisible Box in the Box, with horizontal flow.
ADDVLAYOUT Box Creates an undecorated invisible Box in the Box, with vertical flow.
ADDHBOX Box Creates a visible Box in the Box, with horizontal flow.
ADDVBOX Box Creates a visible Box in the Box, with vertical flow.
ADDSTACK Box Creates a nested stacked Box in the Box. Only one such box is shown at a time.
ADDSCROLLBOX ScrollBox Creates a nested scrollable Box of widgets.
ADDSPACING(size) Spacing Creates a blank space of the given size (flexible if -1).
WIDGETS List(Widget) Returns a LIST of the widgets that have been added to the Box.
RADIOVALUE String The string name of the currently selected radio button.
ONRADIOCHANGE KOSDelegate (button) A callback you want kOS to call whenever the radio button selection changes.
SHOWONLY(widget)   Hide all but the given widget.
CLEAR   Dispose all child widgets.
Box:ADDLABEL(text)
Parameters:
Returns:

Label

Creates a Label widget in this Box. The label will display the text message given in the parameter.

Box:ADDBUTTON(text)
Parameters:
Returns:

Button

Creates a clickable Button widget in this Box.

Box:ADDCHECKBOX(text, on)
Parameters:
  • textString text to display
  • onBoolean state of the checkbox initially
Returns:

Button

Creates a toggle-able Button widget in this Box. The Button will display the text message given in the parameter. The Button will initially start off turned on or turned off depending on the state of the on parameter.

Box:ADDRADIOBUTTON(text, on)
Parameters:
  • textString text to display
  • onBoolean state of the checkbox initially
Returns:

Button

Creates an exclusive toggle-able Button widget in this Box. The Button will display the text message given in the parameter. The Button will initially start off turned on or turned off depending on the state of the on parameter.

This button will be set to be exclusive, which means all other buttons in this Box which are also exclusive will be turned off when this button is turned on. All these “radio” buttons within this same box are considered to be in the same group for the sake of this check. In order to make two different radio button groups, you would need to create a box for each with BOX:ADDHBOX or BOX:ADDVBOX, and then add radio buttons to each of those boxes.

To read which radio button value is the one that is currently on, among the whole set of buttons, you can use BOX:RADIOVALUE.

Box:ADDTEXTFIELD(text)
Parameters:
  • text – struct:String initial starting text in the field.
Returns:

TextField

Creates a TextField widget in this Box. The textfield will allow the user to type a string into the field that you can read. The field will be a one-line string input.

Box:ADDPOPUPMENU()
Returns:PopupMenu

Creates a special kind of button known as a PopupMenu in the Box. This is a button that, when clicked, brings up a list of values to choose from. When the user picks a value, the popup list goes away and the button will be labeled with the selection from the list that was picked.

The list of values that will pop up are in the suffix PopupMenu:Options, which you must populate after having called ADDPOPUPMENU.

Example:

set mygui to GUI(100).
// Make a popup menu that lets you choose one of 4 color names:
set mypopup mygui:addpopupmenu().
set mypopup:options to LIST("red", "green", "yellow", "white").

mygui:show().
wait 15. // let you play with it for 15 seconds.
mygui:dispose(). // ditch the gui before leaving this example.
Box:ADDHSLIDER(init, min, max)
Parameters:
  • initScalar starting value
  • minScalar left endpoint value
  • maxScalar right endpoint value
Returns:

Slider

Creates a horizontal Slider in the Box that adjusts a Scalar value. The value can take on any fractional amount between the minimum and maximum values given. Despite the names it is possible to make the min parameter larger than the max parameter, in which case the direction of the slider will be inverted, with the largest value at the left and the smallest at the right.

Box:ADDVSLIDER(init, min, max)
Parameters:
  • initScalar starting value
  • minScalar top endpoint value
  • maxScalar bottom endpoint value
Returns:

Slider

Creates a vertical Slider in the Box that adjusts a Scalar value. The value can take on any fractional amount between the minimum and maximum values given. Despite the names it is possible to make the min parameter larger than the max parameter, in which case the direction of the slider will be inverted, with the largest value at the bottom and the smallest at the top.

Box:ADDHLAYOUT()
Returns:Box

Creates a nested transparent horizontally-arranged Box in this Box. You can’t see any visual evidence of this box other than how it forces the widgets inside it to get arranged. (The box has no borders showing, no background color, etc).

All the widgets added to such a box will arrange themselves horizontally (the more widgets you add, the wider the box gets).

There are three reasons you might want to nest one Box inside another Box:

  • You wish to isolate some radio buttons into their own Box so they form one radio button group.
  • You wish to force the GUI automatic layout system to place widgets in a particular arrangement by making it treat a group of widgets as a single rectangular chunk that gets arranged together as a unit.
Box:ADDVLAYOUT()
Returns:Box

Creates a nested transparent vertically-arranged Box in this Box. You can’t see any visual evidence of this box other than how it forces the widgets inside it to get arranged. (The box has no borders showing, no background color, etc).

All the widgets added to such a box will arrange themselves vertically (the more widgets you add, the taller the box gets).

(The Box returned by calling the built-in function Gui is a “VLayout” box which arranges things vertically like this.)

There are three reasons you might want to nest one Box inside another Box:

  • You wish to isolate some radio buttons into their own Box so they form one radio button group.
  • You wish to force the GUI automatic layout system to place widgets in a particular arrangement by making it treat a group of widgets as a single rectangular chunk that gets arranged together as a unit.
Box:ADDHBOX()
Returns:Box

This is identical to BOX:ADDHLAYOUT, other than the fact that it uses a different graphical style which lets you see the box.

Box:ADDVBOX()
Returns:Box

This is identical to BOX:ADDVLAYOUT, other than the fact that it uses a different graphical style which lets you see the box.

Box:ADDSTACK()
Returns:Box

Creates a nested stacked Box in this Box. (a Box which can be swapped for other similarly created boxes that occupy the same space on the screen.)

When you add several such boxes with multiple calls to BOX:ADDSTACK, then instead of these boxes being laid you horizontally or vertically next to each other as widgets would normally be, they all occupy the same space of the screen. However, only one such box in the set of stacked boxes will be visible at a time.

This is how you can implement a pane which has its contents replaced with several different variants depending on what variant you want to see at a time. (i.e. a window with an area who’s contents are toggled by hitting some “tab” buttons that change which version of the contents get shown.)

When several such boxes have been added, you can individually choose which one is shown, by which one is enabled. If two of them are enabled at the same time, then only the first enabled one it finds gets shown.

See SHOWONLY below for more information on how to manipulate these kinds of sub-boxes.

Box:ADDSCROLLBOX()
Returns:ScrollBox

Creates a nested scrollable box of widgets.

Using this kind of box, you can create an area of the Gui which holds contents bigger than it can show at once. It will add scrollbars to let you pan the view to see the rest of the content that is outside the visible box size.

To make this work, you will need to specify the size limits of the viewable area, otherwise the layout system will simply make the ScrollBox big enough to hold all the content, and thus it won’t need the scrollbars.

More details on how to do this can be found in the documentation for ScrollBox.

Box:ADDSPACING(size)
Parameters:
  • sizeScalar the size of the area to take up with empty space.
Returns:

Spacing

Creates blank space of the given size in pixels (flexible if -1).

This is used for cases where you’d like to force a widget to get indented, or pushed further down.

Whether this is horizontal or vertical space depends on whether it is inside a horizontal arrangement box or a vertical arrangement box. (myBox:ADDSPACING(20). is 20 pixels of width if myBox was a BOX:ADDHLAYOUT, but it’s 20 pixels of height if it was a BOX:ADDVLAYOUT.)

Example:

set mygui to GUI(400).
set mytitle to mygui:addlabel("This is my Panel").
set box1 to mygui:ADDHLAYOUT().
box1:addspacing(50). // 50 pixels indent inside horizontal box 1
set button1 to box1:addbutton("indented").
set box2 to mygui:ADDHLAYOUT().
box2:addspacing(100). // 100 pixels indent inside horizontal box 2
set button2 to box2:addbutton("indented more").
myGui:show().
print "Play with buttons for 15 seconds.".
wait 15.
myGui:dispose(). // get rid of the GUI before quitting the program.
Box:WIDGETS
Type:List(Widget)
Access:Get-only

Returns a LIST of the widgets that have been added to the Box, so that you may examine them. If you think of the GUI as a tree of widgets (which is what it is), then this is how you find the children of this box. It’s sort of the opposite of Widget:PARENT.

Note that adding or deleting from this list will not actually add or remove widgets from the box itself. (This list is an exported copy of the list of widgets, and not the actual list the box itself uses internally.)

Box:RADIOVALUE
Type:String
Access:Get-only

The text label of whichever radiobutton is turned on among all the radio buttons you’ve added with BOX:ADDRADIOBUTTON(text, on) to this box.

Because only one of the radio buttons within this box can be on at a time, this can be a faster way to see which has been selected than reading each button one a time to see which one is on.

If none of the buttons are turned on (for example, if the user hasn’t selected anything yet since the box was displayed), then this will return a value of "" (an empty string).

Box:ONRADIOCHANGE
Type:KOSDelegate
Access:Get/Set

The KOSDelegate accepts 1 parameter, a Button, and returns nothing.

A callback hook you want kOS to call whenever the radio button selection within this box changes.

Because a radio button set is defined at the level of a Box (see BOX:ADDRADIOBUTTON(text, on)), the callback hook you would like to be called whenever that radio button set changes which button is the selected one is also here on the Box widget.

The KOSDelegate must be a function (or anonymous function) that behaves as follows:

function myradiochangehook {
  parameter whichButton.

  // Do something here.  "whichButton" will be a variable set to
  // whichever radio button is the one that has just been switched
  // on.
}
set someBox:onradiochange to myradiochange@.

Example, using an anonymous function:

set someBox:onRadioChange to { parameter B.  print "You selected:  " + B:text. }.
Box:SHOWONLY(widget)
Parameters:
  • widget
Type widget:

Widget

When multiple widgets have been placed inside this Box, this suffix is used to choose just one of them to be the one you want being shown at the moment. All other widgets within this box will be immediately hidden.

This is useful when you have several stacked boxes made with calls to BOX:ADDSTACK, and want to choose which one of them you are making visible at the moment.

Box:CLEAR()
Returns:none

Calling BOX:CLEAR() will get rid of all widgets you have added to this box by use of any of the above “ADD.....” suffixes. It will also call Widget:DISPOSE() on all of them.

Button
structure Button

A Button is a widget that can have script activity occur when the user presses it.

Button widgets are created inside Box objects via one of these three methods:

  • Box:ADDBUTTON - for a button that pops back out again on its own after being clicked.
  • Box:ADDCHECKBOX - for a toggle button that stays on when clicked and doesn’t turn off until clicked again.
  • Box:ADDRADIOBUTTON - A kind of checkbox that forms part of a set of checkboxes that only allow one of themselves to be on at a time.

The differences between how these types of button behave come from how they will have their default TOGGLE and EXCLUSIVE suffixes set when they are created.

Buttons are a special case of Label, and can use all the features of Label to define how their text looks.

Suffix Type Description
Every suffix of LABEL
PRESSED Boolean Is the button currently down?
TAKEPRESS Boolean Return the PRESSED value AND release the button if it’s down.
TOGGLE Boolean Is this button into a toggle-style button?
EXCLUSIVE Boolean Does turning this button on cause other buttons to turn off?
ONCLICK KOSDelegate (no args) Your function called whenever the button gets clicked.
ONTOGGLE KOSDelegate (Boolean) Your function called whenever the button’s PRESSED state changes.
Button:PRESSED
Type:Boolean
Access:Get/Set

You can read this value to see if the button is currently on (true) or off (false). You can set this value to cause the button to become on or off.

Button:TAKEPRESS
Type:Boolean
Access:Get-only

You can read this value to see if the button is currently on (true) or off (false), however reading this value has a side-effect. When you read this value, if it said the button was on (pressed in), then reading it will cause the button to become off (popped out).

This is useful only for normal buttons (buttons that have TOGGLE set to false). It allows you to use the polling technique to repeatedly check to see if the button is on, and as soon as your script notices that it’s on, it will pop it back out again so the user sees the proper visual feedback.

Button:TOGGLE
Type:Boolean
Access:Get/Set

This suffix determines whether this button has toggle behavior or button behaviour. (Whether it stays pressed in until pressed a second time). By default, :meth`BOX:ADDBUTTON` will create a button with TOGGLE set to false, while BOX:ADDCHECKBOX and BOX:ADDRADIOBUTTON will create buttons which have their TOGGLE suffixes set to true.

Behaviour when TOGGLE is false (the default):

The conditions under which a button will automatically release itself when TOGGLE is set to False are:

  • When the script calls the TAKEPRESS suffix method. When this is done, the button will become false even if it was was previously true.
  • If the script defines an ONCLICK user delegate. (Then when the PRESSED value becomes true, kOS will immediately set it back to false (too fast for the kerboscript to see it) and instead call the ONCLICK callback delegate you gave it.)

Behaviour when TOGGLE is true:

If TOGGLE is set to True, then the button will not automatically release after it is read by the script. Instead it will need to be clicked by the user a second time to make it pop back out. In this mode, the button’s PRESSED value will never automatically reset to false on its own.

If the Button is created by Button:ADDCHECKBOX, or by Button:ADDRADIOBUTTON, it will have a different visual style (the style called “toggle”) and it will start already in TOGGLE mode.

Button:EXCLUSIVE
Type:Boolean
Access:Get/Set

If the Button is created by Button:ADDRADIOBUTTON, it will have its EXCLUSIVE suffix set to true by default.

If EXCLUSIVE is set to True, when the button is clicked (or changed programmatically), other buttons with the same parent Box will be set to False (regardless of if they are EXCLUSIVE).

Button:ONCLICK
Type:KOSDelegate
Access:Get/Set

This is a KOSDelegate that takes no parameters and returns nothing.

ONCLICK is what is known as a “callback hook”. This suffix allows you to use the callback technique of widget interaction.

You can assign ONCLICK to a KOSDelegate of one of your functions (named or anonymous) and from then on kOS will call that function whenever the button becomes clicked by the user.

The ONCLICK suffix is intended to be used for non-toggle buttons.

Example:

set mybutton:ONCLICK to { print "Do something here.". }.

ONCLICK is called with no parameters. To use it, your function must be written to expect no parameters.

Button:ONTOGGLE
Type:KOSDelegate
Access:Get/Set

This is a KOSDelegate taking one parameter (new boolean state) and returning nothing

ONTOGGLE is what is known as a “callback hook”. This suffix allows you to use the callback technique of widget interaction.

The ONTOGGLE delegate you assign will get called whenver kOS notices that this button has changed from false to true or from true to false.

To use ONTOGGLE, your function must be written to expect a single boolean parameter, which is the new state the button has just be changed to.

Example:

set mybutton:ONTOGGLE to { parameter val. print "Button value just became " + val. }.

ONTOGGLE is really only useful with buttons where TOGGLE is true.

Example

Here is a longer example of buttons using the button callback hooks:

LOCAL doneYet is FALSE.
LOCAL g IS GUI(200).

// b1 is a normal button that auto-releases itself:
// Note that the callback hook, myButtonDetector, is
// a named function found elsewhere in this same program:
LOCAL b1 IS g:ADDBUTTON("button 1").
SET b1:ONCLICK TO myButtonDetector@.

// b2 is also a normal button that auto-releases itself,
// but this time we'll use an anonymous callback hook for it:
LOCAL b2 IS g:ADDBUTTON("button 2").
SET b2:ONCLICK TO { print "Button Two got pressed". }

// b3 is a toggle button.
// We'll use it to demonstrate how ONTOGGLE callback hooks look:
LOCAL b3 IS g:ADDBUTTON("button 3 (toggles)").
set b3:style to g:skin:button.
SET b3:TOGGLE TO TRUE.
SET b3:ONTOGGLE TO myToggleDetector@.

// b4 is the exit button.  For this we'll use another
// anonymous function that just sets a boolean variable
// to signal the end of the program:
LOCAL b4 IS g:ADDBUTTON("EXIT DEMO").
SET b4:ONCLICK TO { set doneYet to true. }

g:show(). // Start showing the window.

wait until doneYet. // program will stay here until exit clicked.

g:hide(). // Finish the demo and close the window.

//END.

function myButtonDetector {
  print "Button One got clicked.".
}
function myToggleDetector {
  parameter newState.
  print "Button Three has just become " + newState.
}
GUI structure
structure GUI

This object is created with the GUI(width,height) function.

A GUI object is a kind of Box that is the outermost window that holds all the other widgets. In order to work at all, all widgets must be put inside of a GUI box, or in inside of another Box which in turn is inside a GUI box, etc.

(To get rid of all GUIs that you created from this CPU, you can use the CLEARGUIS built-in-function, which is a shorthand for calling Widget:HIDE and Widget:DISPOSE on each of them.)

Suffix Type Description
Every suffix of BOX. Note, to add widgets to this window, see the BOX suffixes.
X scalar (pixels) X-position of the window. Negative values measure from the right.
Y scalar (pixels) Y-position of the window. Negative values measure from the bottom.
DRAGGABLE Boolean True = user can move window.
EXTRADELAY scalar (seconds) Add artificial delay to all communication with this GUI (good for testing before you get into deep space)
SKIN Skin The skin defining the default style of widgets in this GUI.
SHOW none Call to make the gui appear
HIDE none Call to make the gui disappear
GUI:X
Type:scalar
Access:Get/Set

This is the X position of upper-left corner of window, in pixels.

You can alter this value to move the window.

If you use a negative value for the coordinate, then the coordiante will be measured in the reverse direction, from the right edge of the screen. (i.e. setting it to -200 means 200 pixels away from the right edge of the screen.)

GUI:Y
Type:scalar
Access:Get/Set

This is the Y position of upper-left corner of window, in pixels.

You can alter this value to move the window.

If you use a negative value for the coordinate, then the coordiante will be measured in the reverse direction, from the bottom edge of the screen. (i.e. setting it to -200 means 200 pixels away from the bottom edge of the screen.)

GUI:DRAGGABLE
Type:Boolean
Access:Get/Set

Set to true to allow the window to be moved by the user dragging it. If set to false, it can still be moved by the script setting GUI:X and GUI:Y, but can’t be moved by the user.

GUI:EXTRADELAY
Type:scalar
Access:Get/Set

This is the number of extra seconds of delay to add to the GUI for testing purposes.

If Remote Tech is installed, the GUI system obeys the signal delay of the Remote Tech mod such that when you click a widget it can take time before the script notices you did so. If you want to test how your GUI will work under a signal delay you can use this suffix to force a simulated additional signal delay even if you are not using RemoteTech. (Or when you are using RemoteTech but are testing your GUI in situations where there isn’t a noticable signal delay, like in Kerbin low orbit).

GUI:SKIN
Type:Skin
Access:Get/Set

A Skin is a collection of Style objects to be used by different types of widgets within the GUI window. With this suffix you can assign a different Skin to the window, which will then be used by default by all the widgets of the appropriate type inside the window.

GUI:SHOW()

Synopsis:

set g to gui(200).
// .. call G:addbutton, G:addslider, etc etc here
g:show().

Call this suffix to make the GUI appear. (Note this is really just Widget:Show but it’s mentioned again here because it’s vital when making a GUI to know that it won’t show up if you don’t call this.)

GUI:HIDE()

Synopsis:

set g to gui(200).
// .. call G:addbutton, G:addslider, etc etc here
g:show().
wait until done. // whatever you decide "done" is.
g:hide().

Call this suffix to make the GUI disappear. (Note this is really just Widget:Show but it’s mentioned again here.)

Label
structure Label

Label widgets are created inside Box objects via BOX:ADDLABEL.

A Label is a widget that just shows a bit of text or an image. The base type of Label is just used for passive content that can’t be edited or interacted with.

(However, other widgets which are interactive are derived from Label, such as Button and TextField.)

Suffix Type Description
Every suffix of WIDGET
TEXT string The text on the label.
IMAGE string Filename of an image for the label.
TOOLTIP string A tooltip for the label.
Label:TEXT
Type:String
Access:Get/Set

The text which is shown the label.

This text can contain some limited richtext markup, described below, unless you have suppressed it using Style:RICHTEXT as follows:

set thislabel:RICHTEXT to false. // prevent richtext markup in the label
Label:IMAGE
Type:string
Access:Get/Set

This is the filename of an image file to use in the label’s background.

If you prefer an image to a string label, you can set this suffix. The filenames you use must be contained in the Archive (i.e. “/Ships/Script”) volume, but are allowed to disobey the normal rules about reaching the archive with comms. This is because these images conceptually represent the look and feel of control panels in the ship and not necessarily something that takes up “space” on the disk.

PNG format images usually work best, although any format Unity is capable of reading can work here.

You can leave off the ".png" ending on the filename if you like and this suffix will presume you meant to read a .png file. If you wish to read a file in some other format than PNG, you will need to give its filename extension explicitly.

Label:TOOLTIP
Type:String
Access:Get/Set

String which you wish to appear in a tooltip when the user hovers the mouse pointer over this widget.

Rich Text

Labels (and several other widgets that take text strings) can use a limited markup system called Rich Text. (This comes from Unity itself).

It looks slightly like HTML, but with only a very small number of tags supported. The list of supported tags is shown below:

  • <b>string</b> - Shows the string in bold face.
  • <i>string</i> - Shows the string in italic face.
  • <size=nnn>string</size> - Changes the font size to a number (Unity is unclear whether this is in pixels or points).
  • <color=name>string</color> - Selects a color, which can be expressed by name, and is assumed to be opaque.
  • <color=#nnnnnnnn>string</color> - Selects a color, expressed using 8 hexidecimal digits in pairs representing red, green, blue, and alpha. (For example, all red, fully opaque would be #ff0000ff, while all-red half-transparent would be #ff000080.)

This feature can be suppressed in a widget if you don’t like it. You suppress it by setting that widget’s Style:RICHTEXT suffix to false, for example:

set mylabel:style:richtext to false.

(Doing so can be useful if you’re trying to display text which contains the punctuation marks "<", or ">", and want to prevent them from being interpreted as markup tags.)

Examples of usage:

set mylabel1:text to "This is <b>important</b>.". // boldface
set mylabel2:text to "This is <i>important</i>.". // italic
set mylabel3:text to "This is <size=30>important</size>.". // enlarged font
set mylabel4:text to "This is <color=orange>important</color>.". // orange by name
set mylabel5:text to "This is <color=#ffaa00FF>important</color>.". // orange by hex code, opaque
set mylabel6:text to "This is <color=#ffaa0080>important</color>.". // orange by hex code, halfway transparent
PopupMenu
structure PopupMenu

PopupMenu objects are created by calling BOX:ADDPOPUPMENU.

A PopupMenu is a special kind of button for choosing from a list of things. It looks like a button who’s face displays the currently selected thing. When a user clicks on the button, it pops up a list of displayed strings to choose from, and when one is selected the popup goes away and the new choice is displayed on the button.

The menu displays the string values in the OPTIONS property. If OPTIONS contains items that are not strings, then by default their TOSTRING suffixes will be used to display them as strings. You can change this default behaviour by setting the popupmenu’s OPTIONSUFFIX.

Example:

local popup is gui:addpopupmenu().

// Make the popup display the Body:NAME's instead of the Body:TOSTRING's:
set popup:OPTIONSUFFIX to "NAME".

list bodies in bodies.
for planet in bodies {
        if planet:hasbody and planet:body = Sun {
                popup:addoption(planet).
        }
}
set popup:value to body.
Suffix Type Description
Every suffix of BUTTON
OPTIONS List List of options to display.
OPTIONSUFFIX string Name of the suffix used for display names. Default = TOSTRING.
ADDOPTION(value)   Add a value to the end of the list of options.
VALUE Any Returns the current selected value.
INDEX Scalar Returns the index of the current selected value.
CHANGED Boolean Has the user chosen something?
ONCHANGE KOSDelegate (String) Your function called whenever the CHANGED state changes.
CLEAR   Removes all options.
MAXVISIBLE Scalar (integer) How many choices to show at once in the list (if more exist, it makes it scrollable).
PopupMenu:OPTIONS
Type:List (of any Structure)
Access:Get/Set

This is the list of options the user has to choose from. They don’t need to be Strings, but they must be capable of having a string extracted from them for display on the list, by use of the :attr”OPTIONSSUFFIX suffix.

PopupMenu:OPTIONSUFFIX
Type:String
Access:Get/Set

This decides how you get strings from the list of items in OPTIONS. The usual way to use a PopupMenu would be to have it select from a list of strings. But you can use any other kind of object you want in the OPTIONS list, provided all of them share a common suffix name that builds a string from them. The default value for this is "TOSTRING:, which is a suffix that all things in kOS have. If you wish to use something other than Structure:TOSTRING, you can set this to that suffix’s string name.

This page begins with a good example of using this. See above.

PopupMenu:ADDOPTION(value)
Parameters:
  • value
    • any kind of kOS type, provided it has the suffix mentioned in OPTIONSSUFFIX on it.
Type value:

Structure

Access:

Get/Set

This appends another choice to the OPTIONS list.

PopupMenu:VALUE
Type:Structure
Access:Get/Set

Returns the value currently chosen from the list. If no selection has been made, it will return an empty String ("").

If you set it, you are choosing which item is selected from the list. If you set this to something that wasn’t in the list, the attempt to set it will be rejected and instead the choice will become de-selected.

PopupMenu:INDEX
Type:Scalar
Access:Get/Set

Returns the number index into the OPTIONS list that goes with the current choice. If this is set to -1, that means nothing has been selected.

Setting this value causes the selected choice to change. Setting it to -1 will de-select the choice.

PopupMenu:CHANGED
Type:Boolean
Access:Get/Set

Has the choice been changed since the last time this was checked?

Note that reading this has a side effect. When you read this value, you cause it to become false if it had been true. (The system assumes that “last time this was checked” means “now” after you’ve read the value of this suffix.)

This is intended to be used with the polling technique of reading the widget. You can query this field until it says it’s true, at which point you know to go have a look at the current value to see what it is.

PopupMenu:ONCHANGE
Type:KOSDelegate
Access:Get/Set

This is a KOSDelegate that expects one parameter, the new value, and returns nothing.

Sets a callback hook you want called when a new selection has been made. This is for use with the callback technique of reading the widget.

The function you specify must be designed to take one parameter, which is the new value (same as reading the VALUE suffix) of this widget, and return nothing.

Example:

set myPopupMenu:ONCHANGE to { parameter choice. print "You have selected: " + choice:TOSTRING. }.
PopupMenu:CLEAR()
Returns:(nothing)

Calling this causes the PopupMenu to wipe out all the contents of its OPTIONS list.

PopupMenu:MAXVISIBLE
Type:Scalar
Access:Get/Set

(Default value is 15).

This sets the largest number of choices (roughly) the layout system will be willing to grow the popup window to support before it resorts to using a scrollbar to show more choices, instead of letting the window get any bigger. This value is only a rough hint.

If this is set too large, it can become possible to make the popup menu so large it won’t fit on the screen, if you give it a lot of items in the options list.

Scrollbox
structure ScrollBox

ScrollBox objects are created by using BOX:ADDSCROLLBOX.

A scollbox is a box who’s contents can be bigger than it is, accessable via scrollbars.

To constrain the actual size of the box, you can use the :style suffix of the box. For example, this code:

set sb to mygui:addscrollbox().
set sb:style:width to 200.
set sb:style:height to 200.

would make a scrollbox whose visible part is limited to 200 pixels by 200 pixels.

By default, the GUI layout manager would attempt to make the scrollbox as big as it can, within the constraints of the containing window.

Suffix Type Description
Every suffix of BOX. :attr:
HALWAYS Boolean Always show the horizontal scrollbar.
VALWAYS Boolean Always show the vertical scrollbar.
POSITION Vector The position of the scrolled content (Z is ignored).
ScrollBox:HALWAYS
Type:Boolean
Access:Get/Set

Set to true if you want the horizontal scrollbar to always appear for the box regardless of whether the contents are large enough to require it.

ScrollBox:VALWAYS
Type:Boolean
Access:Get/Set

Set to true if you want the vertical scrollbar to always appear for the box regardless of whether the contents are large enough to require it.

ScrollBox:POSITION
Type:Vector
Access:Get/Set

This value tells you where within the window’s content the currently visible portion is. The Vector’s X component tells you the X coordinate of the upper-left corner of the visible portion within the content. The Vector’s Y component tells you the Y coordinate of the upper-left corner of the visible portion within the content. The Vector’s Z component is irrelevant and ignored. (This is really an X/Y pair stored inside a 3D vector).

You can set this value to force the window to scroll to a new position.

Skin
structure Skin

A Skin is a set of Style settings defined for various widget types. It defines what default style will be used for each type of widget inside the GUI. Changes to the styles on a GUI:SKIN will affect the subsequently created widgets inside that GUI window. Note that some of the styles are used by subparts of widgets, such as the HORIZONTALSLIDERTHUMB, which is used by a SLIDER when oriented horizontally.

If you create your own composite widgets, you can use ADD and GET to centralize setting up the style of your composite widgets.

If you wish to make a complete new Skin, the cleanest method would be to put all the graphics in a directory, along with a kOS script that given a GUI:SKIN, changes everything in that skin as needed, allowing users to run your script with their GUI:SKIN to make it use your custom skin.

Suffix Type Description
BOX Style Style for Box widgets.
BUTTON Style Style for Button widgets.
HORIZONTALSCROLLBAR Style Style for the horizontal scrollbar of ScrollBox widgets.
HORIZONTALSCROLLBARLEFTBUTTON Style Style for the horizontal scrollbar left button of ScrollBox widgets.
HORIZONTALSCROLLBARRIGHTBUTTON Style Style for the horizontal scrollbar right button of ScrollBox widgets.
HORIZONTALSCROLLBARTHUMB Style Style for the horizontal scrollbar thumb of ScrollBox widgets.
HORIZONTALSLIDER Style Style for horizontal Slider widgets.
HORIZONTALSLIDERTHUMB Style Style for the thumb of horizontal Slider widgets.
VERTICALSCROLLBAR Style Style for the vertical scrollbar of ScrollBox widgets.
VERTICALSCROLLBARLEFTBUTTON Style Style for the vertical scrollbar left button of ScrollBox widgets.
VERTICALSCROLLBARRIGHTBUTTON Style Style for the vertical scrollbar right button of ScrollBox widgets.
VERTICALSCROLLBARTHUMB Style Style for the vertical scrollbar thumb of ScrollBox widgets.
VERTICALSLIDER Style Style for vertical Slider widgets.
VERTICALSLIDERTHUMB Style Style for the thumb of vertical Slider widgets.
LABEL Style Style for Label widgets.
SCROLLVIEW Style Style for ScrollBox widgets.
TEXTFIELD Style Style for TextField widgets.
TOGGLE Style Style for Button widgets in toggle mode (GUI:ADDCHECKBOX and GUI:ADDRADIOBUTTON).
FLATLAYOUT Style Style for Box transparent widgets (GUI:ADDHLAYOUT and GUI:ADDVLAYOUT).
POPUPMENU Style Style for PopupMenu widgets.
POPUPWINDOW Style Style for the popup window of PopupMenu widgets.
POPUPMENUITEM Style Style for the menu items of PopupMenu widgets.
LABELTIPOVERLAY Style Style for tooltips overlayed on Label widgets.
WINDOW Style Style for GUI windows.
FONT string The name of the font used (if STYLE:FONT does not change it for an element).
SELECTIONCOLOR Color The background color of selected text (eg. TEXTFIELD).
ADD(name) Style Adds a new style.
HAS(name) Boolean Does the skin have the named style?
GET(name) Style Gets a style by name (including ADDed styles).
Skin:BOX
Type:Style
Access:Get/Set

Style for Box widgets.

Skin:BUTTON
Type:Style
Access:Get/Set

Style for Button widgets.

Skin:HORIZONTALSCROLLBAR
Type:Style
Access:Get/Set

Style for the horizontal scrollbar of ScrollBox widgets.

Skin:HORIZONTALSCROLLBARLEFTBUTTON
Type:Style
Access:Get/Set

Style for the horizontal scrollbar left button of ScrollBox widgets.

Skin:HORIZONTALSCROLLBARRIGHTBUTTON
Type:Style
Access:Get/Set

Style for the horizontal scrollbar right button of ScrollBox widgets.

Skin:HORIZONTALSCROLLBARTHUMB
Type:Style
Access:Get/Set

Style for the horizontal scrollbar thumb of ScrollBox widgets.

Skin:HORIZONTALSLIDER
Type:Style
Access:Get/Set

Style for horizontal Slider widgets.

Skin:HORIZONTALSLIDERTHUMB
Type:Style
Access:Get/Set

Style for the thumb of horizontal Slider widgets.

Skin:VERTICALSCROLLBAR
Type:Style
Access:Get/Set

Style for the vertical scrollbar of ScrollBox widgets.

Skin:VERTICALSCROLLBARLEFTBUTTON
Type:Style
Access:Get/Set

Style for the vertical scrollbar left button of ScrollBox widgets.

Skin:VERTICALSCROLLBARRIGHTBUTTON
Type:Style
Access:Get/Set

Style for the vertical scrollbar right button of ScrollBox widgets.

Skin:VERTICALSCROLLBARTHUMB
Type:Style
Access:Get/Set

Style for the vertical scrollbar thumb of ScrollBox widgets.

Skin:VERTICALSLIDER
Type:Style
Access:Get/Set

Style for vertical Slider widgets.

Skin:VERTICALSLIDERTHUMB
Type:Style
Access:Get/Set

Style for the thumb of vertical Slider widgets.

Skin:LABEL
Type:Style
Access:Get/Set

Style for Label widgets.

Skin:SCROLLVIEW
Type:Style
Access:Get/Set

Style for ScrollBox widgets.

Skin:TEXTFIELD
Type:Style
Access:Get/Set

Style for TextField widgets.

Skin:TOGGLE
Type:Style
Access:Get/Set

Style for Button widgets in toggle mode (GUI:ADDCHECKBOX and GUI:ADDRADIOBUTTON).

Skin:FLATLAYOUT
Type:Style
Access:Get/Set

Style for Box transparent widgets (GUI:ADDHLAYOUT and GUI:ADDVLAYOUT).

Skin:POPUPMENU
Type:Style
Access:Get/Set

Style for PopupMenu widgets.

Skin:POPUPWINDOW
Type:Style
Access:Get/Set

Style for the popup window of PopupMenu widgets.

Skin:POPUPMENUITEM
Type:Style
Access:Get/Set

Style for the menu items of PopupMenu widgets.

Skin:LABELTIPOVERLAY
Type:Style
Access:Get/Set

Style for tooltips overlayed on Label widgets.

Skin:WINDOW
Type:Style
Access:Get/Set

Style for GUI windows.

Skin:FONT
Type:string
Access:Get/Set

The name of the font used (if STYLE:FONT does not change it for an element).

Skin:SELECTIONCOLOR
Type:Color
Access:Get/Set

The background color of selected text (eg. TEXTFIELD).

Skin:ADD(name)
Parameters:
Returns:

Style

Adds a new style to the skin and names it. The skin holds a list of styles by name which you can retrieve later.

Skin:HAS(name)
Parameters:
Returns:

Style

Does the skin have the named style?

Skin:GET(name)
Parameters:
Returns:

Style

Gets a style by name (including ADDed styles).

Slider
structure Slider

Slider widgets are created via BOX:ADDHSLIDER and BOX:ADDVSLIDER.

A Slider is a widget that holds the value of a Scalar that the user can adjust by moving a sliding marker along a line.

It is suited for real-number varying values, but not well suited for integer values.

Suffix Type Description
Every suffix of WIDGET
VALUE scalar The current value. Initially set to MIN.
ONCHANGE KOSDelegate (Scalar) Your function called whenever the VALUE changes.
MIN scalar The minimum value (leftmost on horizontal slider).
MAX scalar The maximum value (bottom on vertical slider).
Slider:VALUE
Type:Scalar
Access:Get/Set

The current value of the slider.

Slider:ONCHANGE
Type:KOSDelegate
Access:Get/Set

This KOSDelegate takes one parmaeter, the value, and returns nothing.

This allows you to set a callback delegate to be called whenever the user has moved the slider to a new value. Note that as the user moves the slider to a new position, this will get called several times along the way, giving sevearl intermediate values on the way to the final value the user leaves the slider at.

Example:

set mySlider:ONCHANGE to whenMySliderChanges@.

function whenMySliderChanges {
  parameter newValue.

  print "Value is " +
         round(100*(newValue-mySlider:min)/(mySlider:max-mySlider:min)) +
         "percent of the way between min and max.".
}

This suffix is intended to be used with the callback technique of widget interaction.

Slider:MIN
Type:Scalar
Access:Get/Set

The “left” (for horizontal sliders) or “top” (for vertical sliders) endpoint value of the slider.

Note that despite the name, MIN doesn’t have to be smaller than MAX. If MIN is larger than MAX, then that causes the slider to swap their meaning, and reverse its direction. (i.e. where numbers normally get larger when you slide to the right, inverting MIN and MAX causes the numbers to get larger when you slide to the left.)

Slider:MAX
Type:Scalar
Access:Get/Set

The “right” (for horizontal sliders) or “bottom” (for vertical sliders) endpoint value of the slider.

Note that despite the name, MIN doesn’t have to be smaller than MAX. If MIN is larger than MAX, then that causes the slider to swapr their meaning, and reverse its direction. (i.e. where numbers normally get larger when you slide to the right, inverting MIN and MAX causes the numbers to get larger when you slide to the left.)

Spacing
structure Spacing

Spacing widgets are created via BOX:ADDSPACING.

A Spacing is just an invisible space for the purpose of pushing other widgets further to the right or further down, forcing the layout to come out the way you like.

Suffix Type Description
Every suffix of WIDGET
AMOUNT scalar The amount of space, or -1 for flexible spacing.
Spacing:AMOUNT
Type:Scalar
Access:Get/Set

The number of pixels for this spacing to take up. Whether this is horizontal or vertial space depends on whether this is being added to a horizontal-layout box or a vertical-layout box.

Style
structure Style

This object represents the style of a widget. Styles can be either changed directly on a Widget, or changed on the GUI:SKIN so as to affect all subsequently created widgets of a particular type inside that GUI.

Suffix Type Description
HSTRETCH Boolean Should the widget stretch horizontally? (default depends on widget subclass)
VSTRETCH Boolean Should the widget stretch vertically?
WIDTH scalar (pixels) Fixed width (or 0 if flexible).
HEIGHT scalar (pixels) Fixed height (or 0 if flexible).
MARGIN StyleRectOffset Spacing between this and other widgets.
PADDING StyleRectOffset Spacing between the outside of the widget and its contents (text, etc.).
BORDER StyleRectOffset Size of the edges in the 9-slice image for BG images in NORMAL, HOVER, etc.
OVERFLOW StyleRectOffset Extra space added to the area of the background image. Allows the background to go beyond the widget’s rectangle.
ALIGN string One of “CENTER”, “LEFT”, or “RIGHT”. See note below.
FONT string The name of the font of the text on the content or “” if the default.
FONTSIZE scalar The size of the text on the content.
RICHTEXT Boolean Set to False to disable rich-text (<i>...</i>, etc.)
NORMAL StyleState Properties for the widget normally.
ON StyleState Properties for when the widget is under the mouse and “on”.
NORMAL_ON StyleState Alias for ON.
HOVER StyleState Properties for when the widget is under the mouse.
HOVER_ON StyleState Properties for when the widget is under the mouse and “on”.
ACTIVE StyleState Properties for when the widget is active (eg. button being held down).
ACTIVE_ON StyleState Properties for when the widget is active and “on”.
FOCUSED StyleState Properties for when the widget has keyboard focus.
FOCUSED_ON StyleState Properties for when the widget has keyboard focus and is “on”.
BG string The same as NORMAL:BG. Name of a “9-slice” image file.
TEXTCOLOR Color The same as NORMAL:TEXTCOLOR. The color of the text on the label.
Style:HSTRETCH
Type:Boolean
Access:Get/Set

Should the widget stretch horizontally? (default depends on widget subclass)

Style:VSTRETCH
Type:Boolean
Access:Get/Set

Should the widget stretch vertically?

Style:WIDTH
Type:scalar
Access:Get/Set

(pixels) Fixed width (or 0 if flexible).

Style:HEIGHT
Type:scalar
Access:Get/Set

(pixels) Fixed height (or 0 if flexible).

Style:MARGIN
Type:StyleRectOffset
Access:Get/Set

Spacing between this and other widgets.

Style:PADDING
Type:StyleRectOffset
Access:Get/Set

Spacing between the outside of the widget and its contents (text, etc.).

Style:BORDER
Type:StyleRectOffset
Access:Get/Set

Size of the edges in the 9-slice image for BG images in NORMAL, HOVER, etc.

Style:OVERFLOW
Type:StyleRectOffset
Access:Get/Set

Extra space added to the area of the background image. Allows the background to go beyond the widget’s rectangle.

Style:ALIGN
Type:string
Access:Get/Set

One of “CENTER”, “LEFT”, or “RIGHT”.

Note

The ALIGN attribute will not do anything useful unless either HSTRETCH is set to true or a fixed WIDTH is set, since otherwise it will be exactly the right size to fit the content of the widget with no alignment within that space being necessary.

It is currently only relevant for the widgets that have scalar content (Label and subclasses).

Style:FONT
Type:string
Access:Get/Set

The name of the font of the text on the content or “” if the default.

Style:FONTSIZE
Type:scalar
Access:Get/Set

The size of the text on the content.

Style:RICHTEXT
Type:Boolean
Access:Get/Set

Set to False to disable rich-text (<i>...</i>, etc.)

Style:NORMAL
Type:StyleState
Access:Get/Set

Properties for the widget normally.

Style:ON
Type:StyleState
Access:Get/Set

Properties for when the widget is under the mouse and “on”.

Style:NORMAL_ON
Type:StyleState
Access:Get/Set

Alias for ON.

Style:HOVER
Type:StyleState
Access:Get/Set

Properties for when the widget is under the mouse.

Style:HOVER_ON
Type:StyleState
Access:Get/Set

Properties for when the widget is under the mouse and “on”.

Style:ACTIVE
Type:StyleState
Access:Get/Set

Properties for when the widget is active (eg. button being held down).

Style:ACTIVE_ON
Type:StyleState
Access:Get/Set

Properties for when the widget is active and “on”.

Style:FOCUSED
Type:StyleState
Access:Get/Set

Properties for when the widget has keyboard focus.

Style:FOCUSED_ON
Type:StyleState
Access:Get/Set

Properties for when the widget has keyboard focus and is “on”.

Style:BG
Type:string
Access:Get/Set

The same as NORMAL:BG. Name of a “9-slice” image file.

Style:TEXTCOLOR
Type:color

The same as NORMAL:TEXTCOLOR. The color of the text on the label.

StyleRectOffset
structure StyleRectOffset

A sub-structure of Style.

This is used in places where you need to define a zone around the edges of a widget. (Margins, padding, defining the segments of a 9-segment stretchable image, etc).

Suffix Type Description
LEFT Scalar Number of pixels on the left.
RIGHT Scalar Number of pixels on the right.
TOP Scalar Number of pixels on the top.
BOTTOM Scalar Number of pixels on the bottom.
H Scalar Sets the number of pixels on both the left and right. Reading returns LEFT.
V Scalar Sets the number of pixels on both the top and bottom. Reading returns TOP.
StyleRectOffset:LEFT
Type:Scalar
Access:Get/Set

Number of Pixels on the left

StyleRectOffset:RIGHT
Type:Scalar
Access:Get/Set

Number of Pixels on the right

StyleRectOffset:TOP
Type:Scalar
Access:Get/Set

Number of Pixels on the top

StyleRectOffset:BOTTOM
Type:Scalar
Access:Get/Set

Number of Pixels on the bottom

StyleRectOffset:H
Type:Scalar
Access:Get/Set

Sets the number of pixels on both the left and right to this same value. Getting the value returns just the value of LEFT (it does not test to see if RIGHT is the same value).

StyleRectOffset:V
Type:Scalar
Access:Get/Set

Sets the number of pixels on both the top and bottom to this same value. Getting the value returns just the value of TOP (it does not test to see if BOTTOM is the same value).

StyleState
structure StyleState

A sub-structure of Style, used to define some properties of a style that only are applied under some dynamically changing conditions. (For example, to set the color a widget will have when focused to be different from the color it will have when not focused.)

Suffix Type Description
BG string Name of a “9-slice” image file. See note below.
TEXTCOLOR Color The color of the text on the label.
StyleState:BG
Type:String
Access:Get/Set

This string is an image filename that must be stored in the archive folder (it cannot be on a local drive). The image files are always found relative to volume 0 (the Ships/Scripts directory) and specifying a ”.png” extension is optional. Note, that this ignores the normal rules about finding the archive within comms range. You are allowed to access these files even when not in range of the archive, because they represent the visual look of your ship’s control panels, not actual files sent on the ship.

This image is what is called a “9-slice image”. This is a kind of image designed to handle the difficulty of stretching an image properly to any size. When you stretch an image for a background, you usually only want to stretch the middle part of the image in width and height, and not stretch the edges and corners of the image the same way.

_images/9-slice.png

The four corner pieces of the image are used as-is without stretching.

The edge pieces of the image on the top and bottom are stretched horizontally but not vertically.

The edge pieces of the image on the left and right are stretched vertically but not horizontally.

Only the pixels in the center piece of the image are stretched both vertically and horizontally.

The Style:BORDER attribute of the style for the widget defines where the left, right, top and bottom coordinates are to mark these 9 sections of the image.

If set to "", these background images will default to the corresponding normal image and if that is also "", it will default to the normal BG image, and if that is also "", then it will default to completely transparent.

StyleState:TEXTCOLOR
Type:Color
Access:Get/Set

The color of foreground text within this widget when it is in this state.

TextField
structure TextField

TextField objects are created via BOX:ADDTEXTFIELD.

A TextField is a special kind of Label that can be edited by the user. Unlike a normal Label, a TextField can only be textual (it can’t be used for image files).

A TextField has a default style that looks different from a passive Label. In the default style, a TextField shows the area the user can click on and type into, using a recessed background.

Suffix Type Description
Every suffix of LABEL. Note you read Label:TEXT to see the TextField’s current value.
CHANGED Boolean Has the text been edited?
ONCHANGE KOSDelegate (String) Your function called whenever the CHANGED state changes.
CONFIRMED Boolean Has the user pressed Return in the field?
ONCONFIRM KOSDelegate (String) Your function called whenever the CONFIRMED state changes.
TextField:CHANGED
Type:Boolean
Access:Get/Set

Tells you whether Label:TEXT has been edited at all since the last time you checked. Note that any edit counts. If a user is trying to type “123” into the TextField and has so far written “1” and has just pressed the “2”, then this will be true. If they then press “4” this will be true again. If they then press “backspace” because this was type, this will be true again. If they then press “3” this will be true again. Literally every edit to the text counts, even if the user has not finished using the textfield.

As soon as you read this suffix and it returns true, it will be reset to false again until the next time an edit happens.

This suffix is intended to be used with the polling technique of widget interaction.

TextField:ONCHANGE
Type:KOSDelegate
Access:Get/Set

This KOSDelegate expects one parameter, a String, and returns nothing.

This allows you to set a callback delegate to be called whenever the value of Label:TEXT changes in any way, whether that’s inserting a character or deleting a character.

The KOSDelegate you use must be made to expect one parameter, the new string value, and return nothing.

Example:

set myTextField:ONCHANGE to {parameter str. print "Value is now: " + str.}.

This suffix is intended to be used with the callback technique of widget interaction.

TextField:CONFIRMED
Type:Boolean
Access:Get/Set

Tells you whether the user is finished editing Label:TEXT since the last time you checked. This does not become true merely because the user typed one character into the field or deleted one character (unlike CHANGED, which does). This only becomes true when the user does one of the following things:

  • Presses Enter or Return on the field.
  • Leaves the field (clicks on another field, tabs out, etc).

As soon as you read this suffix and it returns true, it will be reset to false again until the next time the user commits a change to this field.

This suffix is intended to be used with the polling technique of widget interaction.

TextField:ONCONFIRM
Type:KOSDelegate
Access:Get/Set

This KOSDelegate expects one parameter, a String, and returns nothing.

This allows you to set a callback delegate to be called whenever the user has finished editing Label:TEXT. Unlike CHANGED, this does not get called every time the user types a key into the field. It only gets called when one of the following things happens reasons:

  • User presses Enter or Return on the field.
  • User leaves the field (clicks on another field, tabs out, etc).

The KOSDelegate you use must be made to expect one parameter, the new string value, and return nothing.

Example:

set myTextField:ONCONFIRM to {parameter str. print "Value is now: " + str.}.

This suffix is intended to be used with the callback technique of widget interaction.

Note

The values of CHANGED and CONFIRMED reset to False as soon as their value is accessed.

Widget
structure Widget

This object is the base class of all GUI elements. No matter which GUI element you are dealing with, it will have these properties at minimum.

Suffix Type Description
SHOW   Show the widget.
HIDE   Hide the widget.
VISIBLE   SET: Show or hide the widget. GET: see if it’s showing.
DISPOSE   Remove the widget permanently.
ENABLED Boolean Set to False to “grey out” the widget, preventing user interaction.
STYLE Style The style of the widget.
GUI GUI The GUI ultimately containing this widget.
PARENT BOX The Box containing this widget.
HASPARENT Boolean If this widget has no parent, returns false.
Widget:SHOW()

(No parameters, no return value).

Call Widget:show() when you need to make the widget in question start appearing on the screen. This is identical to setting Widget:VISIBLE to true.

See Widget:VISIBLE below for further documentation.

Note: Unless you use show() (or set the Widget:VISIBLE suffix to true) on the outermost Box of the GUI panel (the one you obtained from calling built-in function GUI), nothing will ever be visible from your GUI.

Widget:HIDE()

(No parameters, no return value).

Call Widget:hide() when you need to make the widget in question disappear from the screen. This is identical to setting Widget:VISIBLE to false.

See Widget:VISIBLE below for further documentation.

Widget:VISIBLE
Type:Scalar
Access:Get/Set

This is the setting which can also be changed by calling Widget:show() and Widget:hide().

Most new widgets are set to be visible by default, except for the outermost Box that represents a GUI panel window. (The kind you can obtain by calling built-in function Gui.) Because of this, you generally only need to set the outermost GUI panel window to visible and then all the widgets inside of it should appear.

The typical pattern is this:

set panel to GUI(200).

// <--- Add widgets to panel by calling things like panel:addbutton,
//      panel:addhslider, panel:addvslider, panel:addlabel, etc...

panel:show(). // or 'set panel:visible to true.' does the same thing.

Note that the showing of a widget requires the showing of all widgets it’s contained inside of. Hiding a widget will hide all widgets inside it, regardless of their infividual visibility settings. This is what is happening when you make a GUI Box with a call to GUI, fill it with widgets, and then show it. The widgets inside it were already set to “visible”, but their visibility was suppressed by the fact that the GUI they were inside of was not visible. Once you made the GUI panel visible, all the widgets inside it (that were already set to be visible) appeared with it.

Widget:DISPOSE()

(no parameters, no return value)

Call Widget:DISPOSE() to permanenly make this widget go away. Not only will it make it invisible, but it will make it impossible to set it to visible again later.

Widget:ENABLED
Type:Boolean
Access:get/set

(This is true by default for all newly created widgets.)

When this is true, then the widget can be used by the user.

When this is false, then the widget becomes read-only and its skin takes on a “greyed-out” theme. The user cannot interact with it, even though it may still be visible on the screen.

Widget:STYLE
Type:Style
Access:Get/Set

The style of the widget.

A reasonable style will be chosen by default for most widgets. It will be one that is copied from the default style used in KSP’s standard stock GUI skin. But if you wish to change the appearance of the GUI widgets that kOS provides, you can create a modified style and set the widget’s style to that style here, or you can “swap” styles by assigning this widget to the style usually used by a different widget. (For example, making a button look like it’s just a passive text label.) Such changes should be carefully thought-out if you do them at all, because they can very easily confuse a user with conflicting visual cues.

To see how to make a modified style, see the documentation for Style.

Widget:GUI
Type:GUI
Access:Get-only

To be useful, all widgets (buttons, labels, textfields, etc) must either be contained inside a GUI widget directly, or be contained inside another Widget which in turn is also contained inside a GUI widget. (Or contained inside a widget contained inside a widget contained inside a GUI, etc..)

This suffix will find which GUI is the one which ultimately is the one holding this widget.

Widget:PARENT
Type:Box
Access::Get-only

Widgets can be contained inside Boxes that are contained inside other Boxes, etc. This suffix tells you which Box contains this one. If you attempt to call this suffix on the outermost GUI Box that contains all the others in a panel, you may find that kOS throws a complaining error because there is no parent to the outermost widget. To protect your code against this, use the Widget:HASPARENT suffix.

Widget:HASPARENT
Type:Boolean
Access::Get-only

If trying to use Widget:PARENT would generate an error because this widget has no parent, then HASPARENT will be false. Otherwise it will be true.

Configurations and Miscellany

Boolean

structure Boolean
Members
(Boolean values have no suffixes, other than the Structure suffixes all values have.)
 

A Boolean value is the smallest unit of data possible in a computer program. It can contain one of exactly two values, either True, or False.

When setting a Boolean value, you can use the special keywords true or false to give it a value:

set myVariable to true.
set myVariable to false.

You can also set it equal to the value of any true/false expression, for example:

set x to 781.
set itHas3Digits to (x >= 100 and x <= 999).
print itHas3Digits.
True.

If printed to the terminal, a Boolean value will return the string "True" or "False".

Operators

Boolean expressions can use any of the following operators:

These all assume both a and b are Boolean values:

  • not a returns true if a is false, or false if a is true.
  • a and b returns true if and only if both a and b are true, else returns false.
  • a or b returns false if either a is true, b is true, or both are true. Only returns false with both a and b are false.

The order of operations is as shown above. First it performs not, then and, then or. Parentheses can be used to force the order of operations to be the way you want, as usual.

Example

Boolean values stored in a variable can be used in place of any conditional check syntax anywhere. Example:

set should_stage to false.

// set should_stage to true if the ship has no active engines right now:
//
set should_stage to (ship:maxthrust = 0).

// set should_stage to true if any of the active engines are flamed out,
// which should cover most "asparagus staging" strategies:
//
list engines in englist.
for eng in englist {

  // note, eng:flameout is a Boolean value here, being used as the
  // conditional check of this if-statement:
  //
  if eng:flameout {
    set should_stage to true.
  }
}

// Note 'should_stage' is a Boolean value here, being used as the
// conditional check of this if-statement:
//
if should_stage {
  stage.
}

Colors

Any place you need to specify a color in the game (at the moment this is just with VECDRAW, HIGHLIGHT. and HUDTEXT) You do so with a rgba color structure defined as follows:

Method 1: Use one of these pre-arranged named colors:

RGB(r,g,b)

This global function creates a color from red green and blue values:

SET myColor TO RGB(r,g,b).

where:

r
A floating point number from 0.0 to 1.0 for the red component.
g
A floating point number from 0.0 to 1.0 for the green component.
b
A floating point number from 0.0 to 1.0 for the blue component.
RGBA(r,g,b,a)

Same as RGB() but with an alpha (transparency) channel:

SET myColor TO RGBA(r,g,b,a).

r, g, b are the same as above.

a
A floating point number from 0.0 to 1.0 for the alpha component. (1.0 means opaque, 0.0 means invisibly transparent).
structure RGBA
Members
Suffix Type Description
:R or :RED scalar the red component of the color
:G or :GREEN scalar the green component of the color
:B or :BLUE scalar the blue component of the color
:A or :ALPHA scalar the alpha (how opaque: 1 = opaque, 0 = transparent) component of the color
:HTML or :HEX string the color rendered into a HTML tag string i.e. “#ff0000”. This format ignores the alpha channel and treats all colors as opaque.

Examples:

SET myarrow TO VECDRAW.
SET myarrow:VEC to V(10,10,10).
SET myarrow:COLOR to YELLOW.
SET mycolor TO YELLOW.
SET myarrow:COLOR to mycolor.
SET myarrow:COLOR to RGB(1.0,1.0,0.0).

// COLOUR spelling works too
SET myarrow:COLOUR to RGB(1.0,1.0,0.0).

// half transparent yellow.
SET myarrow:COLOR to RGBA(1.0,1.0,0.0,0.5).

PRINT GREEN:HTML. // prints #00ff00
HSV(h,s,v)

This global function creates a color from hue, saturation and value:

SET myColor TO HSV(h,s,v).

`More Information about HSV <http://en.wikipedia.org/wiki/HSL_and_HSV>`_,

where:

h
A floating point number from 0.0 to 360.0 for the hue component.
s
A floating point number from 0.0 to 1.0 for the saturation component.
v
A floating point number from 0.0 to 1.0 for the value component.
HSVA(h,s,v,a)

Same as HSV() but with an alpha (transparency) channel:

SET myColor TO HSVA(h,s,v,a).

h, s, v are the same as above.

a
A floating point number from 0.0 to 1.0 for the alpha component. (1.0 means opaque, 0.0 means invisibly transparent).
structure HSVA

The HSVA structure contains all of the suffixes from the RGBA structure in addition to these

Members
Suffix Type Description
:H or :HUE scalar the hue component of the color. It is a value from 0.0 to 360.0
:S or :SATURATION scalar the saturation component of the color. It has a value from 0.0 to 1.0
:V or :VALUE scalar the value component of the color. It has a value from 0.0 to 1.0

Examples:

SET myarrow TO VECDRAW.
SET myarrow:VEC to V(10,10,10).
SET myarrow:COLOR to HSV(60,1,1). // Yellow
SET myarrow:COLOR:S to 0.5. // Light yellow
SET myarrow:COLOR:H to 0. // pink

Configuration of kOS

structure Config

Config is a special structure that allows your kerboscript programs to set or get the values stored in the kOS plugin’s configuration. Some of the global values are stored in an external file, while save game specific values are stored in that save file.

Note

New in version v1.0.2: Prior to this version of kOS, all settings were stored globally in a single external file. KSP version 1.2.0 introduced a new way to store settings within the save file itself, and most settings were migrated to this system.

Note

If your save file has not yet migrated to the new settings storage system and an old config file is present, you will be prompted with a dialog box offering to migrate the old settings or use the defaults. You may also choose to prevent further attempts to migrate settings. If you do so, kOS will set the InstructionsPerUpdate to a negative value in the old config file, as a flag to indicate no further migrations should happen. (Note the old config file is still actively used for global settings such as the telnet settings, even after you’ve done this, so don’t delete it.)

The options here can also be set by using the App Control Panel or the kOS section of KSP’s Difficulty Settings

Because the Telnet server runs as a global instance for KSP, the telnet specific settings are stored globally in kOS’s external config file. These values are noted as global below, but all other values may be presumed to be local to the current save file.

The config file may be found at [KSP Directory]/GameData/kOS/Plugins/PluginData/kOS/

Members (all Gettable and Settable)
Suffix Type Default Description
IPU Scalar (integer) 150 Instructions per update
UCP Boolean False Use compressed persistence
STAT Boolean False Print statistics to screen
RT Boolean False Enable RemoteTech2 integration
ARCH Boolean False Start on archive (instead of volume 1)
OBEYHIDEUI Boolean True Obey the KSP Hide user interface key (usually mapped to F2).
SAFE Boolean False Enable safe mode
AUDIOERR Boolean False Enable sound effect on kOS error
VERBOSE Boolean False Enable verbose exceptions
TELNET Boolean False activate the telnet server
TPORT Scalar (integer) 5410 set the port the telnet server will run on
IPADDRESS String “127.0.0.1” The IP address the telnet server will try to use.
BRIGHTNESS Scalar 0.7 (from range [0.0 .. 1.0]) Default brightness setting of new instances of the in-game terminal
DEFAULTFONTSIZE Scalar 12 (from range [6 .. 20], integers only) Default font size in pixel height for new instances of the in-game terminal
DEBUGEACHOPCODE Boolean false Unholy debug spam used by the kOS developers
Config:IPU
Access:Get/Set
Type:Scalar integer. range = [50,2000]

Configures the InstructionsPerUpdate setting.

This is the number of kRISC psuedo-machine-langauge instructions that each kOS CPU will attempt to execute from the main program per physics update tick.

This value is constrained to stay within the range [50..2000]. If you set it to a value outside that range, it will reset itself to remain in that range.

Config:UCP
Access:Get/Set
Type:Boolean

Configures the useCompressedPersistence setting.

If true, then the contents of the kOS local volume ‘files’ stored inside the campaign save’s persistence file will be stored using a compression algorithm that has the advantage of making them take less space, but at the cost of making the data impossible to decipher with the naked human eye when looking at the persistence file.

Config:STAT
Access:Get/Set
Type:Boolean

Configures the showStatistics setting.

If true, then executing a program will log numbers to the screen showing execution speed statistics.

When this is set to true, it also makes the use of the ProfileResult() function available, for deep analysis of your program run, if you are so inclined.

Config:RT
Access:Get/Set
Type:Boolean

Configures the enableRTIntegration setting.

If true, then the kOS mod will attempt to interact with the Remote Tech 2 mod, letting RT2 make decisions about whether or not a vessel is within communications range rather than having kOS use its own more primitive algorithm for it.

Due to a long stall in the development of the RT2 mod, this setting should still be considered experimental at this point.

Config:ARCH
Access:Get/Set
Type:Boolean

Configures the startOnArchive setting.

If true, then when a vessel is first loaded onto the launchpad or runway, the initial default volume will be set to volume 0, the archive, instead of volume 1, the local drive.

Config:OBEYHIDEUI
Access:Get/Set
Type:Boolean

Configures the obeyHideUI setting.

If true, then the kOS terminals will all hide when you toggle the user interface widgets with Kerbal Space Program’s Hide UI key (it is set to F2 by default key bindings).

Config:SAFE
Access:Get/Set
Type:Boolean

Configures the enableSafeMode setting. If true, then it enables the following error messages:

Tried to push NaN into the stack.
Tried to push Infinity into the stack.

They will be triggered any time any mathematical operation would result in something that is not a real number, such as dividing by zero, or trying to take the square root of a negative number, or the arccos of a number larger than 1. Performing such an operation will immediately terminate the program with one of the error messages shown above.

If false, then these operations are permitted, but the result may lead to code that does not function correctly if you are not careful about how you use it. Using a value that is not a real number may result in freezing Kerbal Space Program itself if that value is used in a variable that is passed into Kerbal Space Program’s API routines. KSP’s own API interface does not seem to have any protective checks in place and will faithfully try to use whatever values its given.

Config:AUDIOERR
Access:Get/Set
Type:Boolean

Configures the audibleExceptions setting.

If true, then it enables a mode in which errors coming from kOS will generte a sound effect of a short little warning bleep to remind you that an exception occurred. This can be useful when you are flying hands-off and need to realize your autopilot script just died so you can take over.

Config:VERBOSE
Access:Get/Set
Type:Boolean

Configures the verboseExceptions setting.

If true, then it enables a mode in which errors coming from kOS are very long and verbose, trying to explain every detail of the problem.

Config:TELNET
Access:Get/Set
Type:Boolean

GLOBAL SETTING

Configures the EnableTelnet setting.

When set to true, it activates a kOS telnet server in game that allows you to connect external terminal programs like Putty and Xterm to it. Turning the option off or on immediately toggles the server. (When you change it from false to true, it will start the server right then. When you change it from true to false, it will stop the server right then.) Therefore to restart the server after changing a setting like TPORT, DO this:

// Restart telnet server:
SET CONFIG:TELNET TO FALSE.
WAIT 0.5. // important to give kOS a moment to notice and kill the old server.
SET CONFIG:TELNET TO TRUE.

Of course, you can do the equivalent of that by using the GUI config panel and just clicking the button off then clicking it on.

Config:TPORT
Access:Get/Set
Type:Scalar (integer)

GLOBAL SETTING

Configures the TelnetPort setting.

Changes the TCP/IP port number that the kOS telnet server in game will listen to.

To make the change take effect you may have to stop, then restart the telnet server, as described above.

Config:IPADDRESS
Access:Get/Set
Type:String

GLOBAL SETTING

Configures the TelnetIPAddrString setting.

This is the IP address the telnet server will attempt to use when it is enabled. By default it will use the loopback address of “127.0.0.1” unless you change this setting to the computer’s actual IP address. Because most modern PC’s have multiple IP addresses, no attempt is made by kOS to guess which of them is “the” right one. You must tell kOS which one to use if you don’t want it to use the loopback address.

To make the change take effect you may have to stop, then restart the telnet server, as described above.

Config:BRIGHTNESS
Access:Get/Set
Type:Scalar. range = [0,1]

Configures the Brightness setting.

This is the default starting brightness setting a new kOS in-game terminal will have when it is invoked. This is just the default for new terminals. Individual terminals can have different settings, either by setting the value Terminal:BRIGHTNESS in a script, or by manually moving the brightness slider widget on that terminal.

The value here must be between 0 (invisible) and 1 (Max brightness).

Config:DEFAULTFONTSIZE
Access:Get/Set
Type:Scalar integer-only. range = [6,20]

Configures the TerminalFontDefaultSize setting.

This is the default starting font height (in pixels. not “points”) for all newly created kOS in-game terminals. This is just the default for new terminals. Individual terminals can have different settings, either by setting the value Terminal:CHARHEIGHT in a script, or by manually clicking the font adjustment buttons on that terminal.

The value here must be at least 6 (nearly impossible to read) and no greater than 30 (very big). It will be rounded to the nearest integer when setting the value.

Config:DEBUGEACHOPCODE
Access:Get/Set
Type:Boolean

Configures the debugEachOpcode setting.

NOTE: This makes the game VERY slow, use with caution.

If true, each opcode that is executed by the CPU will be accompanied by an entry in the KSP log. This is a debugging tool for those who are very familiar with the inner workings of kOS and should rarely be used outside the kOS dev team.

This change takes effect immediately.

Part Highlighting

Being able to color tint a part or a collection of parts can be a powerful visualization to show their placement and status. The part highlighting structure is defined as follows:

HIGHLIGHT(p,c)

This global function creates a part highlight:

SET foo TO HIGHLIGHT(p,c).

where:

p
A single part, a list of parts or an element
c
A color
structure HIGHLIGHT
Members
Suffix Type Description
:COLOR color the color that will be used by the highlight
:ENABLED boolean controls the visibility of the highlight

Example:

list elements in elist.

// Color the first element pink
SET foo TO HIGHLIGHT( elist[0], HSV(350,0.25,1) ).

// Turn the highlight off
SET foo:ENABLED TO FALSE


// Turn the highlight back on
SET foo:ENABLED TO TRUE

KOSDelegate

The structure KOSDelegate is what you get when you use the Delegate at-sign syntax, as in this example:

function myfunc { print "hello, there". }

local print_a_thing is myfunc@. // <--- Note the at-sign '@'.
// print_a_thing is now a KOSDelegate of myfunc.

You also get a KOSDelegate when you use the Anonymous function syntax like so:

set del1 to { print "hello, there". }.
// del1 is now a KOSDelegate.

A KOSDelegate is a reference to the function that can be used to call the function later elsewhere in the code.

Be aware, however, that it will not let you call the function after the program is gone and you are back at the interactive prompt again. (See ISDEAD).

The full explanation of the delegate feature is explained elsewhere. This page just documents the members of the KOSDelegate structure for completeness. The full explanation of how this structure works and what it’s for can be found on the delegate page.

Structure
structure KOSDelegate
Members
Suffix Type Description
CALL(varying arguments) same as the function this is a delegate for. calls the function this delegate wraps.
BIND(varying arguments) another KOSDelegate creates a new KOSDelegate with some arguments predefined.
ISDEAD Boolean True if the delegate refers to a program that’s gone.
KOSDelegate:CALL(varying arguments)

Calls the function this KOSDelegate is set up to call.

The varying arguments you pass in to this are passed on to the function this KOSDelegate is calling. The exact number of arguments you pass should match the number the function expects, minus any that you have pre-set with the :BIND suffix.

This is further explained elsewhere.

Note that in some cases you can omit the use of the explicit suffix :CALL and just use parentheses abutted against the KOSDelegate variable itself to indicate that you wish to call the function.

KOSDelegate:BIND(varying arguments)

Creates a new KOSDelegate from the current one, which will call the same function, but in which some or all of the parameters the function will be passed are pre-set. The arguments you pass in will be bound to the leftmost parameters of the function. When using this new KOSDelegate to call the function, you pass in only the remaining arguments that were not designated in the call to :BIND.

This is further explained elsewhere.

KOSDelegate:ISDEAD
Type:Boolean
Access:Get only

It is possible for a KOSDelegate to refer to some user code that no longer exists in memory because that program completed and exited. If so, then ISDEAD will be true.

This can happen because kOS lets global variables continue to live past the end of the program that made them. So you can do something like this:

function some_function {
    print "hello".
}
// NOTE: my_delegate is global so it keeps existing
// after this program ends:
set my_delegate to some_function@.

If you run that program and get back to the interactive terminal prompt, then my_delegate is still a KOSDelegate, but now it refers to some code that is gone. The body of some_function isn’t there anymore.

If you attempt to call my_delegate() at this point from the interpreter, it will complain with an error message because it knows the function it’s trying to call isn’t there.

DONOTHING (NODELEGATE)
structure NoDelegate
Suffix Type Description
Every suffix of KOSDelegate
     
DONOTHING

There is a special keyword DONOTHING that refers to a special kind of KosDelegate called a NoDelegate.

The type string returned by DONOTHING:TYPENAME is "NoDelegate". Otherwise an instance of NoDelegate has the same suffixes as one of KOSDelegate, although you’re not usually expected to ever use them, except maybe TYPENAME to discover that it is a NoDelegate.

DONOTHING is used when you’re in a situation where you had previously assigned a KosDelegate to some callback hook the kOS system provides, but now you want the kOS system to stop calling it. To do so, you assign that callback hook to the value DONOTHING.

DONOTHING is similar to making a KosDelegate that consists of just {return.}. If you attempt to call it from your own code, that’s how it will behave. But the one extra feature it has is that it allows kOS to understand your intent that you wish to disable a callback hook. kOS can detect when the KosDelegate you assign to something happens to be the DONOTHING delegate. When it is, kOS knows to not even bother calling the delegate at all anymore.

KUniverse 4th wall methods

structure KUniverse

KUniverse is a special structure that allows your Kerboscript programs to access some of the functions that break the “4th Wall”. It serves as a place to access object directly connected to the KSP game itself, rather than the interaction with the KSP world (vessels, planets, orbits, etc.).

Suffix Type Get/Set Description
CANREVERT Boolean Get Is any revert possible?
CANREVERTTOLAUNCH Boolean Get Is revert to launch possible?
CANREVERTTOEDITOR Boolean Get Is revert to editor possible?
REVERTTOLAUNCH None Method Invoke revert to launch
REVERTTOEDITOR None Method Invoke revert to editor
REVERTTO(name) String Method Invoke revert to the named editor
ORIGINEDITOR String Get Returns the name of this vessel’s editor, “SPH” or “VAB”.
CANQUICKSAVE Boolean Get Returns true if quicksave is currently enabled and available.
QUICKSAVE() None Method Invoke KSP’s built in quicksave.
QUICKLOAD() None Method Invoke KSP’s built in quickload.
QUICKSAVETO(name) String Method Perform quicksave to the save with the given name.
QUICKLOADFROM(name) None Method Perform quickload from the save with the given name.
QUICKSAVELIST List of String Get A list of all quicksave files for this game.
HOURSPERDAY Scalar Get Number of hours per day (6 or 24) according to your game settings.
DEBUGLOG(message) None Method Causes a String to append to the Unity debug log file.
DEFAULTLOADDISTANCE LoadDistance Get Returns the set of default load and pack distances for the game.
TIMEWARP TimeWarp Get Returns a value you can use to manipulate Kerbal Space Program’s time warp.
ACTIVEVESSEL Vessel Get/Set Returns the active vessel, or lets you set the active vessel.
FORCESETACTIVEVESSEL(vessel) None Method Lets you switch active vessels even when the game refuses to allow it.
FORCEACTIVE(vessel) None Method Same as FORCESETACTIVEVESSEL
GETCRAFT(name, editor) CraftTemplate Method Get the file path for the craft with the given name, saved in the given editor.
LAUNCHCRAFT(template) None Method Launch a new instance of the given craft at it’s default launch site.
LAUNCHCRAFTFROM(template, site) None Method Launch a new instance of the given craft at the given site.
CRAFTLIST() List of CraftTemplate Method A list of all craft templates in the save specific and stock folders.
KUniverse:CANREVERT
Access:Get
Type:Boolean.

Returns true if either revert to launch or revert to editor is available. Note: either option may still be unavailable, use the specific methods below to check the exact option you are looking for.

KUniverse:CANREVERTTOLAUNCH
Access:Get
Type:Boolean.

Returns true if either revert to launch is available.

KUniverse:CANREVERTTOEDITOR
Access:Get
Type:Boolean.

Returns true if either revert to the editor is available. This tends to be false after reloading from a saved game where the vessel was already in existence in the saved file when you loaded the game.

KUniverse:REVERTTOLAUNCH()
Access:Method
Return type:None.

Initiate the KSP game’s revert to launch function. All progress so far will be lost, and the vessel will be returned to the launch pad or runway at the time it was initially launched.

KUniverse:REVERTTOEDITOR()
Access:Method
Return type:None.

Initiate the KSP game’s revert to editor function. The game will revert to the editor, as selected based on the vessel type.

KUniverse:REVERTTO(editor)
Parameters:
  • editor – The editor identifier
Returns:

None

Revert to the provided editor. Valid inputs are “VAB” and “SPH”.

KUniverse:ORIGINEDITOR
Access:Get
Type:String.

Returns the name of the originating editor based on the vessel type. The value is one of:

  • “SPH” for things built in the space plane hangar,
  • “VAB” for things built in the vehicle assembly building.
  • “” (empty String) for cases where the vehicle cannot remember its editor (when KUniverse:CANREVERTTOEDITOR is false.)
KUniverse:CANQUICKSAVE
Access:Get
Type:Boolean

Returns true if KSP’s quicksave feature is enabled and available.

KUniverse:QUICKSAVE()
Access:Method
Return type:None.

Initiate the KSP game’s quicksave function. The game will save the current state to the default quicksave file.

KUniverse:QUICKLOAD()
Access:Method
Return type:None.

Initiate the KSP game’s quickload function. The game will load the game state from the default quickload file.

KUniverse:QUICKSAVETO(name)
Parameters:
  • name – The name of the save file
Returns:

None

Initiate the KSP game’s quicksave function. The game will save the current state to a quicksave file matching the name parameter.

KUniverse:QUICKLOADFROM(name)
Parameters:
  • name – The name of the save file
Returns:

None

Initiate the KSP game’s quickload function. The game will load the game state from the quicksave file matching the name parameter.

KUniverse:QUICKSAVELIST
Access:Get
Type:List of String

Returns a list of names of all quicksave file in this KSP game.

KUniverse:DEFAULTLOADDISTANCE
Access:Get
Type:LoadDistance.

Get or set the default loading distances for vessels loaded in the future. Note: this setting will not affect any vessel currently in the universe for the current flight session. It will take effect the next time you enter a flight scene from the editor or tracking station, even on vessels that have already existed beforehand. The act of loading a new scene causes all the vessels in that scene to inherit these new default values, forgetting the values they may have had before.

(To affect the value on a vessel already existing in the current scene you have to use the :LOADDISTANCE suffix of the Vessel structure.)

KUniverse:TIMEWARP
Access:Get
Type:TimeWarp.

Returns the TimeWarp structure that you can use to manipulate Kerbal Space Program’s time warping features. See the documentation on TimeWarp for more details.

example: set kuniverse:timewarp:rate to 50.

KUniverse:ACTIVEVESSEL
Access:Get/Set
Type:Vessel.

Returns the active vessel object and allows you to set the active vessel. Note: KSP will not allow you to change vessels by default when the current active vessel is in the atmosphere or under acceleration. Use FORCEACTIVE under those circumstances.

KUniverse:FORCESETACTIVEVESSEL(vessel)
Parameters:
  • vesselVessel to switch to.
Returns:

None

Force KSP to change the active vessel to the one specified. Note: Switching the active vessel under conditions that KSP normally disallows may cause unexpected results on the initial vessel. It is possible that the vessel will be treated as if it is re-entering the atmosphere and deleted.

KUniverse:FORCEACTIVE(vessel)
Parameters:
  • vesselVessel to switch to.
Returns:

None

Same as FORCESETACTIVEVESSEL.

KUniverse:HOURSPERDAY
Access:Get
Type:Scalar (integer)

Has the value of either 6 or 24, depending on what setting you used on Kerbal Space Program’s main settings screen for whether you wanted to think in terms of Kerbal days (6 hours) or Kerbin days (24 hours). This only affects what the clock format looks like and doesn’t change the actual time in game, which is stored purely as a number of seconds since epoch anyway and is unaffected by how the time is presented to the human being watching the game. (i.e. if you allow 25 hours to pass in the game, the game merely tracks that 39000 seconds have passed (25 x 60 x 60). It doesn’t care how that translates into minutes, hours, days, and years until showing it on screen to the player.)

This setting also affects how values from :struct:Timespan calculate the :hours, :days, and :years suffixes.

Note that this setting is not settable. This decision was made because the main stock KSP game only ever changes the setting on the main settings menu, which isn’t accessible during play. It’s entirely possible for kOS to support changing the value mid-game, but we’ve decided to deliberately avoid doing so because there may be other mods with code that only reads the setting once up front and then assumes it never changes after that. Because in the stock game, that assumption would be true.

KUniverse:GETCRAFT(name, editor)
Parameters:
  • nameString craft name.
  • facilityString editor name.
Returns:

CraftTemplate

Returns the CraftTemplate matching the given craft name saved from the given editor. Valid values for editor include "VAB" and "SPH".

KUniverse:LAUNCHCRAFT(template)
Parameters:

Launch a new instance of the given CraftTemplate from the template’s default launch site.

NOTE: The craft will be launched with the KSP default crew assignment, as if you had clicked launch from the editor without manually adjusting the crew.

NOTE: Due to how KSP handles launching a new craft, this will end the current program even if the currently active vessel is located within physics range of the launch site.

KUniverse:LAUNCHCRAFTFROM(template, site)
Parameters:

Launch a new instance of the given CraftTemplate from the given launch site. Valid values for site include "RUNWAY" and "LAUNCHPAD".

NOTE: The craft will be launched with the KSP default crew assignment, as if you had clicked launch from the editor without manually adjusting the crew.

NOTE: Due to how KSP handles launching a new craft, this will end the current program even if the currently active vessel is located within physics range of the launch site.

KUniverse:CRAFTLIST()
Returns:List of CraftTemplate

Returns a list of all CraftTemplate templates stored in the VAB and SPH folders of the stock Ships folder and the save specific Ships folder.

KUniverse:DEBUGLOG(message)
Parameters:
  • messageString message to append to the log.
Returns:

None

All Unity games (Kerbal Space Program included) have a standard “log” file where they can store a lot of verbose messages that help developers trying to debug their games. Sometimes it may be useful to make your script log a message to THAT debug file, instead of using kOS’s normal Log function to append a message to some file of your own making.

This is useful for cases where you are trying to work with a kOS developer to trace the cause of a problem and you want your script to mark the moments when it hit different parts of the program, and have those messages get embedded in the log interleaved with the game’s own diagnostic messages.

Here is an example. Say you suspected the game was throwing an error every time you tried to lock steering to up. So you experiment with this bit of code:

kuniverse:debuglog("=== Now starting test ===").
kuniverse:debuglog("--- Locking steering to up----").
lock steering to up.
kuniverse:debuglog("--- Now forcing a physics tick ----").
wait 0.001.
kuniverse:debuglog("--- Now unlocking steering again ----").
unlock steering.
wait 0.001.
kuniverse:debuglog("=== Now done with test ===").

This would cause the messages you wrote to appear in the debug log, interleaved with any error messages kOS, and any other parts of the entire Kerbal Space Program game, dump into the same log.

The location of this log varies depending on your platform. For some reason, Unity chooses a different filename convention for each OS. Consult the list below to see where it is on your platform.

  • Windows 32-bit: [install_dir]KSP_Dataoutput_log.txt
  • Windows 64-bit: [install_dir]KSP_x64_Dataoutput_log.txt (not officially supported)
  • Mac OS X: ~/Library/Logs/Unity/Player.log
  • Linux: ~/.config/unity3d/Squad/”Kerbal Space Program”/Player.log

For an example of what it looks like in the log, this:

kuniverse:debuglog("this is my message").

ends up resulting in this in the KSP output log:

kOS: (KUNIVERSE:DEBUGLOG) this is my message

Examples

Switch to an active vessel called “vessel 2”:

SET KUNIVERSE:ACTIVEVESSEL TO VESSEL("vessel 2").

Revert to VAB, but only if allowed:

PRINT "ATTEMPTING TO REVERT TO THE Vehicle Assembly Building."
IF KUNIVERSE:CANREVERTTOEDITOR {
  IF KUNIVERSE:ORIGINEDITOR = "VAB" {
    PRINT "REVERTING TO VAB.".
    KUNIVERSE:REVERTTOEDITOR().
  } ELSE {
    PRINT "COULD REVERT, But only to space plane hanger, so I won't.".
  }
} ELSE {
  PRINT "Cannot revert to any editor.".
}

Vessel Load Distance

structure LoadDistance

(The “on rails” settings)

LoadDistance describes the set of distances at which the game causes vessels to unload, and the distances that cause a vessel to become “packed”. This requires some explanation.

Before entering into that explanation, first here’s a list of example cases where you might want to use this feature to change these values:

  • Trying to have one airplane follow another.
  • Trying to have two rockets fly in formation into orbit together.
  • Trying to have a rover race to a flag, with several rovers seeing who gets there first.

Basically, any time you might have more than just the current active vessel running a kOS script, this is a setting you probably will want to understand and tweak.

The explanation:

Most players of KSP eventually discover a concept called being “on rails”. This is a term used by the player community to describe the fact that vessels that are far away from the active vessel aren’t being micro-managed under the full physics engine. At that distance, changes in movement due to things like the atmosphere, are not applied. The vessel’s physics are calculated based only on orbital motion, much like the effects of using timewarp (not physics warp).

But the actual behavior in the game is a bit more complex than that, and understanding it is necessary to use this structure.

The term “on rails” actually refers to two entirely different things that are controlled by separate settings, as described below:

loaded : A vessel is LOADED when all its parts are being rendered by the graphics engine and it’s possible to actually see what it looks like. A vessel that is UNLOADED doesn’t even have its parts in memory and is just a single dot in space with no dimensions. An unloaded vessel is literally impossible to see on your screen no matter how much you squint because it’s not even being rendered on camera at all. Unloaded vessels only exist as a marker icon in space, with a possible label text.

packed : A vessel is PACKED when it is close enough to be loaded (see above), but still far enough away that its full capabilities aren’t enabled. A vessel that is loaded, but still packed will be unable to have its parts interact, and the vessel will appear stuck in the same location, unmoving. You can see a vessel that is loaded but packed, but the vessel won’t actually be able to do anything. In this state, the game is still treating the entire object as if it was one single part. It’s added all the graphic models of all the parts together into one conglomerate “object” (thus the term “packed”) that exists purely so you can look at it, even though it doesn’t actually work until you get closer and it becomes unpacked.

THE NEXT SENTENCE IS VERY IMPORTANT AND VITAL:

The kOS processor is able to run scripts any time that a vessel is loaded, but the script is not guaranteed access to all features when a ship is LOADED but not UNPACKED. KSP limits some features (like throttle control) to only vessels that are unpacked. You may check the UNPACKED suffix of the SHIP variable to determine if these features are available.

This structure allows you to read or change the stock KSP game’s distance settings for how far a vessel has to get from the active vessel in order for it to trigger its UNLOAD or PACK states.

The distance settings are different for different vessel situations. It’s important to first read the existing values before changing them, to see what the stock game thought were reasonable for them.

Some distances are very short. For example, the fact that the pack distance for a landed vessel is short is what allows landers to stay “parked” in place without tipping over when you leave them on a long distance EVA.

Each of these suffixes returns a SituationLoadDistance, which is a tuple of values for the loading and packing distances in that situation.

Wait between LOAD and PACK changes!

Due to a strange way the game behaves, it is unsafe to change both the load/unload distance and the unpack/pack distance at the same time in the same physics tick. If you are going to increase both, then increase the load/unload distances first, followed by a WAIT 0.001. to force a new physics tick and let the change take effect, then increase the unpack/pack distances after the wait is over.

Beware the space kraken when changing these:

There’s a reason the stock game has these distance limitations. Setting them very large can degrade your performance, and can cause buggy inaccuracies in the position and velocity calculations that cause the game to think things have collided together when they haven’t. This is the classic “space kraken” that KSP players talk about a lot. Computer floating point numbers get less precise the farther from zero they are. So allowing the game to try to perform microcalculations on tiny time scales using floating point numbers that have imprecision because they are large in magnitude (i.e. the positions of parts that are many kilometers away from you), can cause phantom collisions, which make the game explode things for “no reason”.

These distance limits were put in place by SQUAD specifically for the purpose of trying to avoid the space kraken. If you set them too large again, you can risk invoking the Kraken again. They typically CAN be enlarged some, because the settings are very low to provide a overly large safety margin, but be careful with it. Don’t go overboard and set the ranges to several thousand kilometers.

Also, don’t set the PACK distance to be higher than the LOAD distance, as that is undefined behavior in the main game. Always keep the LOAD distance higher than or equal to the PACK distance.

Members and Methods
Suffix Type Get/Set Description
ESCAPING SituationLoadDistance Get Load and pack Distances while escaping the current body
FLYING SituationLoadDistance Get Load and pack Distances while flying in atmosphere
LANDED SituationLoadDistance Get Load and pack Distances while landed on the surface
ORBIT SituationLoadDistance Get Load and pack Distances while in orbit
PRELAUNCH SituationLoadDistance Get Load and pack Distances while on the launch pad or runway
SPLASHED SituationLoadDistance Get Load and pack Distances while splashed in water
SUBORBITAL SituationLoadDistance Get Load and pack Distances while on a suborbital trajectory

Situation Load Distance

Each of the above

structure SituationLoadDistance

SituationLoadDistance is what is returned by each of the above suffixes mentioned in the LoadDistance suffix list above.

Order Matters. - Becuse of the protections in place to prevent you from setting some values bigger than others (see the descriptions below), sometimes the order in which you change the values matters and you have to be careful to change them in the correct order, or else the attempt to change them will be denied.

Members and Methods
Suffix Type Get/Set Description
LOAD scalar, in meters Get/Set The load distance
UNLOAD scalar, in meters Get/Set The unload distance
UNPACK scalar, in meters Get/Set The unpack distance
PACK scalar, in meters Get/Set The pack distance
SituationLoadDistance:LOAD
Access:Get/Set
Type:scalar, in meters

Get or set the load distance. When another vessel is getting closer to you, because you are moving toward it or it is moving toward you, when that vessel becomes this distance or closer to the active vessel, it will transition from being unloaded to being loaded. See the description above for what it means for a vessel to be loaded.

This value must be less than UNLOAD, and will automatically be adjusted accordingly.

SituationLoadDistance:UNLOAD
Access:Get/Set
Type:scalar, in meters

Get or set the unload distance. When another vessel is becoming more distant as you move away from it, or it moves away from you, when that vessel becomes this distance or greater from the active vessel, it will transition from being loaded to being unloaded. See the description above for what it means for a vessel to be loaded.

This value must be greater than LOAD, and will automatically be adjusted accordingly.

SituationLoadDistance:UNPACK
Access:Get/Set
Type:scalar, in meters

Get or set the unpack distance. When another vessel is getting closer to you, because you are moving toward it or it is moving toward you, when that vessel becomes this distance or closer to the active vessel, it will transition from being packed to being unpacked. See the description above for what it means for a vessel to be packed.

This value must be less than PACK, and will automatically be adjusted accordingly.

SituationLoadDistance:PACK
Access:Get/Set
Type:scalar, in meters

Get or set the pack distance. When another vessel is getting farther away from you, because you are moving away from it or it is moving away from you, when that vessel becomes this distance or greater from the active vessel, it will transition from being unpacked to being packed. See the description above for what it means for a vessel to be packed.

This value must be greater than UNPACK, and will automatically be adjusted accordingly.

===Examples===

Print out all the current settings:

SET distances TO KUNIVERSE:DEFAULTLOADDISTANCE.

PRINT "escaping distances:".
print "    load: " + distances:ESCAPING:LOAD + "m".
print "  unload: " + distances:ESCAPING:UNLOAD + "m".
print "  unpack: " + distances:ESCAPING:UNPACK + "m".
print "    pack: " + distances:ESCAPING:PACK + "m".
PRINT "flying distances:".
print "    load: " + distances:FLYING:LOAD + "m".
print "  unload: " + distances:FLYING:UNLOAD + "m".
print "  unpack: " + distances:FLYING:UNPACK + "m".
print "    pack: " + distances:FLYING:PACK + "m".
PRINT "landed distances:".
print "    load: " + distances:LANDED:LOAD + "m".
print "  unload: " + distances:LANDED:UNLOAD + "m".
print "  unpack: " + distances:LANDED:UNPACK + "m".
print "    pack: " + distances:LANDED:PACK + "m".
PRINT "orbit distances:".
print "    load: " + distances:ORBIT:LOAD + "m".
print "  unload: " + distances:ORBIT:UNLOAD + "m".
print "  unpack: " + distances:ORBIT:UNPACK + "m".
print "    pack: " + distances:ORBIT:PACK + "m".
PRINT "prelaunch distances:".
print "    load: " + distances:PRELAUNCH:LOAD + "m".
print "  unload: " + distances:PRELAUNCH:UNLOAD + "m".
print "  unpack: " + distances:PRELAUNCH:UNPACK + "m".
print "    pack: " + distances:PRELAUNCH:PACK + "m".
PRINT "splashed distances:".
print "    load: " + distances:SPLASHED:LOAD + "m".
print "  unload: " + distances:SPLASHED:UNLOAD + "m".
print "  unpack: " + distances:SPLASHED:UNPACK + "m".
print "    pack: " + distances:SPLASHED:PACK + "m".
PRINT "suborbital distances:".
print "    load: " + distances:SUBORBITAL:LOAD + "m".
print "  unload: " + distances:SUBORBITAL:UNLOAD + "m".
print "  unpack: " + distances:SUBORBITAL:UNPACK + "m".
print "    pack: " + distances:SUBORBITAL:PACK + "m".

Change the settings while flying or landed or splashed or on launchpad or runway, For the purpose of allowing more vessels to fly around the Kerbal Space Center at a greater distances from each other:

// 30 km for in-flight
// Note the order is important.  set UNLOAD BEFORE LOAD,
// and PACK before UNPACK.  Otherwise the protections in
// place to prevent invalid values will deny your attempt
// to change some of the values:
SET KUNIVERSE:DEFAULTLOADDISTANCE:FLYING:UNLOAD TO 30000.
SET KUNIVERSE:DEFAULTLOADDISTANCE:FLYING:LOAD TO 29500.
WAIT 0.001. // See paragraph above: "wait between load and pack changes"
SET KUNIVERSE:DEFAULTLOADDISTANCE:FLYING:PACK TO 29999.
SET KUNIVERSE:DEFAULTLOADDISTANCE:FLYING:UNPACK TO 29000.
WAIT 0.001. // See paragraph above: "wait between load and pack changes"

// 30 km for parked on the ground:
// Note the order is important.  set UNLOAD BEFORE LOAD,
// and PACK before UNPACK.  Otherwise the protections in
// place to prevent invalid values will deny your attempt
// to change some of the values:
SET KUNIVERSE:DEFAULTLOADDISTANCE:LANDED:UNLOAD TO 30000.
SET KUNIVERSE:DEFAULTLOADDISTANCE:LANDED:LOAD TO 29500.
WAIT 0.001. // See paragraph above: "wait between load and pack changes"
SET KUNIVERSE:DEFAULTLOADDISTANCE:LANDED:PACK TO 39999.
SET KUNIVERSE:DEFAULTLOADDISTANCE:LANDED:UNPACK TO 29000.
WAIT 0.001. // See paragraph above: "wait between load and pack changes"

// 30 km for parked in the sea:
// Note the order is important.  set UNLOAD BEFORE LOAD,
// and PACK before UNPACK.  Otherwise the protections in
// place to prevent invalid values will deny your attempt
// to change some of the values:
SET KUNIVERSE:DEFAULTLOADDISTANCE:SPLASHED:UNLOAD TO 30000.
SET KUNIVERSE:DEFAULTLOADDISTANCE:SPLASHED:LOAD TO 29500.
WAIT 0.001. // See paragraph above: "wait between load and pack changes"
SET KUNIVERSE:DEFAULTLOADDISTANCE:SPLASHED:PACK TO 29999.
SET KUNIVERSE:DEFAULTLOADDISTANCE:SPLASHED:UNPACK TO 29000.
WAIT 0.001. // See paragraph above: "wait between load and pack changes"

// 30 km for being on the launchpad or runway
// Note the order is important.  set UNLOAD BEFORE LOAD,
// and PACK before UNPACK.  Otherwise the protections in
// place to prevent invalid values will deny your attempt
// to change some of the values:
SET KUNIVERSE:DEFAULTLOADDISTANCE:PRELAUNCH:UNLOAD TO 30000.
SET KUNIVERSE:DEFAULTLOADDISTANCE:PRELAUNCH:LOAD TO 29500.
WAIT 0.001. // See paragraph above: "wait between load and pack changes"
SET KUNIVERSE:DEFAULTLOADDISTANCE:PRELAUNCH:PACK TO 29999.
SET KUNIVERSE:DEFAULTLOADDISTANCE:PRELAUNCH:UNPACK TO 29000.
WAIT 0.001. // See paragraph above: "wait between load and pack changes"

Note

Notes are structures that get passed in to the SKID chip to tell it what tone to play and for how long.

All the suffixes of a Note are read-only, and you’re only ever expected to set them by constructing a new Note using the built-in Note() function, or the built-in SlideNote() function:

NOTE(frequency, duration, keyDownLength, volume)

This global function creates a note object from the given values.

where:

frequency
Mandatory: The frequency can be given as either a number or a string. If it is a number, then it is the frequency in Hertz. If it is a string, then it’s using the letter notation described here.
duration
Mandatory: The total amount of time the note takes up before the next note can begin, including the small gap between the end of its keyDownLength and the start of the next note. Note that the value here gets multiplied by the voice’s TEMPO to decide the actual duration in seconds when it gets played.
keyDownLength
Optional: The amount of time the note takes up before the “synthesizer key” is released. In terms of the ADSR Envelope, this is the portion of the note’s time taken up by the Attack, Decay, and Sustain part of the note, but not including the Release part of the note. In order to hear the note fade away during its Release portion, the keyDownLength must be shorter than the Duration, or else there’s no gap of time to fit the release in before the next note starts. By default, if you leave the KeyDownLength off, you get a default KeyDownLength of 90% of the Duration, leaving 10% of the Duration left to hear the “Release” time before the next note starts. If you wish to force the notes to immediately blend from one to the next with no audible gaps between them, then for each note you need to specify a keyDownLength that is equal to the Duration. Note that the value here gets multiplied by the voice’s TEMPO to decide the actual duration in seconds when it gets played.
volume
Optional: If present, then the note can be given a different volume than the default for the voice it’s being played on, to make it louder or quieter than the other notes this voice is playing. This setting is a relative multiplier applied to the voice’s volume. (i.e. 1.0 means play at the same volume as the voice’s setting, 1.1 means play a bit louder than the voice’s setting, and 0.9 means play a bit quieter than the voice’s setting).

This is an example of it being used in conjunction with the Voice’s PLAY() suffix method:

SET V1 TO GETVOICE(0).
V1:PLAY( NOTE(440, 0.2, 0.25, 1) ).
SLIDENOTE(frequency, endFrequency, duration, keyDownLength, volume)

This global function creates a note object that makes a sliding note that changes linearly from the start frequency to the end frequency across the duration of the note.

where:

frequency
Mandatory: This is the frequency the sliding note begins at. If it is a number, then it is the frequency in Hertz. If it is a string, then it’s using the letter notation described here.
endFrequency
Mandatory: This is the frequency the sliding note ends at. If it is a number, then it is the frequency in Hertz. If it is a string, then it’s using the letter notation described here.
duration
Mandatory: Same as the duration for the NOTE() built-in function. If it is missing it will be the same thing as the keyDownLength.
keyDownLength
Optional: Same as the keyDownLength for the NOTE() built-in function.
volume
Optional: Same as the volume for the NOTE() built-in function.

The note’s frequency will change linearly from the starting to the ending frequency over the note’s duration. (For example, If the duration is shorter, but all the other values are the kept the same, that makes the frequency change go faster so it can all fit within the given duration.)

You can make the note pitch up over time or pitch down over time depending on whether the endFrequency is higher or lower than the initial frequency.

This is an example of it being used in conjunction with the Voice’s PLAY() suffix method:

SET V1 TO GETVOICE(0).
// A fast "whoop" sound that pitches up from 300 Hz to 600 Hz quickly:
V1:PLAY( SLIDENOTE(300, 600, 0.2, 0.25, 1) ).
structure Note
Members
Suffix Type Description
FREQUENCY Scalar initial frequency of the note in Hertz
ENDFREQUENCY Scalar final frequency of the note in Hertz
KEYDOWNLENGTH Scalar time to hold the “synthesizer key” down for
DURATION Scalar total time of the note including
VOLUME Scalar multiplier for how loud this note is relative to others notes
Note:FREQUENCY
Access:Get Only
Type:Scalar (Hertz)

The initial frequency of the note in Hertz.

Note:ENDFREQUENCY
Access:Get Only
Type:Scalar (Hertz)

If the note was created using SlideNote() this is the final frequency of the note, in Hertz. Otherwise the value is identical to FREQUENCY.

Note:KEYDOWNLENGTH
Access:Get Only
Type:Scalar (seconds)

The amount of time that the “synthesizer key” is held down for. In the ADSR Envelope this represents the total of the “attack”, “decay”, and “sustain” components.

Note:DURATION
Access:Get Only
Type:Scalar (seconds)

The total time of the note, encompassing the entire ADSR Envelope including the “release” component.

Note:VOLUME
Access:Get Only
Type:Scalar

The multiplier which effects how loud this note is relative to other notes played on this voice. Smaller values are quieter an larger values are louder. While values greater than 1 are allowed, increasing this value excessively may result in audio distortion.

PIDLoop

The PIDLoop has multiple constructors available. Valid syntax can be seen here:

// Create a loop with default parameters
// kp = 1, ki = 0, kd = 0
// maxoutput = maximum number value
// minoutput = minimum number value
SET PID TO PIDLOOP().

// Other constructors include:
SET PID TO PIDLOOP(KP).
SET PID TO PIDLOOP(KP, KI, KD).
// you must specify both minimum and maximum output directly.
SET PID TO PIDLOOP(KP, KI, KD, MINOUTPUT, MAXOUTPUT).

// remember to update both minimum and maximum output if the value changes symmetrically
SET LIMIT TO 0.5.
SET PID:MAXOUTPUT TO LIMIT.
SET PID:MINOUTPUT TO -LIMIT.

// call the update suffix to get the output
SET OUT TO PID:UPDATE(TIME:SECONDS, IN).

// you can also get the output value later from the PIDLoop object
SET OUT TO PID:OUTPUT.

Please see the bottom of this page for information on the derivation of the loop’s output.

Note

New in version 0.18: While the PIDLOOP structure was added in version 0.18, you may feel free to continue to use any previously implemented PID logic. This loop is intended to be a basic and flexible PID, but you may still find benefit in using customized logic.

structure PIDLoop
Suffix Type Description
LASTSAMPLETIME scalar decimal value of the last sample time
KP scalar The proportional gain factor
KI scalar The integral gain factor
KD scalar The derivative gain factor
INPUT scalar The most recent input value
SETPOINT scalar The current setpoint
ERROR scalar The most recent error value
OUTPUT scalar The most recent output value
MAXOUTPUT scalar The maximum output value
MINOUTPUT scalar The maximum output value
ERRORSUM scalar The time weighted sum of error
PTERM scalar The proportional component of output
ITERM scalar The integral component of output
DTERM scalar The derivative component of output
CHANGERATE scalar (/s) The most recent input rate of change
RESET none Reset the integral component
UPDATE(time, input) scalar Returns output based on time and input
PIDLoop:LASTSAMPLETIME
Type:scalar
Access:Get only

The value representing the time of the last sample. This value is equal to the time parameter of the UPDATE method.

PIDLoop:KP
Type:scalar
Access:Get/Set

The proportional gain factor.

PIDLoop:KI
Type:scalar
Access:Get/Set

The integral gain factor.

PIDLoop:KD
Type:scalar
Access:Get/Set

The derivative gain factor

PIDLoop:INPUT
Type:scalar
Access:Get only

The value representing the input of the last sample. This value is equal to the input parameter of the UPDATE method.

PIDLoop:SETPOINT
Type:scalar
Access:Get/Set

The current setpoint. This is the value to which input is compared when UPDATE is called. It may not be synced with the last sample.

PIDLoop:ERROR
Type:scalar
Access:Get only

The calculated error from the last sample (setpoint - input).

PIDLoop:OUTPUT
Type:scalar
Access:Get only

The calculated output from the last sample.

PIDLoop:MAXOUTPUT
Type:scalar
Access:Get/Set

The current maximum output value. This value also helps with regulating integral wind up mitigation.

PIDLoop:MINOUTPUT
Type:scalar
Access:Get/Set

The current minimum output value. This value also helps with regulating integral wind up mitigation.

PIDLoop:ERRORSUM
Type:scalar
Access:Get only

The value representing the time weighted sum of all errrors. It will be equal to ITERM / KI. This value is adjusted by the integral windup mitigation logic.

PIDLoop:PTERM
Type:scalar
Access:Get only

The value representing the proportional component of OUTPUT.

PIDLoop:ITERM
Type:scalar
Access:Get only

The value representing the integral component of OUTPUT. This value is adjusted by the integral windup mitigation logic.

PIDLoop:DTERM
Type:scalar
Access:Get only

The value representing the derivative component of OUTPUT.

PIDLoop:CHANGERATE
Type:scalar
Access:Get only

The rate of change of the INPUT during the last sample. It will be equal to (input - last input) / (change in time).

PIDLoop:RESET()
Returns:none

Call this method to clear the ERRORSUM and ITERM components of the PID calculation.

PIDLoop:UPDATE(time, input)
Parameters:
  • time – (scalar) the decimal time in seconds
  • input – (scalar) the input variable to compare to the setpoint
Returns:

scalar representing the calculated output

Upon calling this method, the PIDLoop will calculate the output based on this this basic framework (see below for detailed derivation): output = error * kp + errorsum * ki + (change in input) / (change in time) * kd. This method is usually called with the current time in seconds (TIME:SECONDS), however it may be called using whatever rate measurement you prefer. Windup mitigation is included, based on MAXOUTPUT and MINOUTPUT. Both integral components and derivative components are guarded against a change in time greater than 1s, and will not be calculated on the first iteration.

PIDLoop Update Derivation

The internal update method of the PIDLoop structure is the equivalent of the following in kerboscript

// assume that the terms LastInput, LastSampleTime, ErrorSum, Kp, Ki, Kd, Setpoint, MinOutput, and MaxOutput are previously defined
declare function Update {
    declare parameter sampleTime, input.
    set Error to Setpoint - input.
    set PTerm to error * Kp.
    set ITerm to 0.
    set DTerm to 0.
    if (LastSampleTime < sampleTime) {
        set dt to sampleTime - LastSampleTime.
        // only calculate integral and derivative if their gain is not 0.
        if Ki <> 0 {
            set ITerm to (ErrorSum + Error * dt) * Ki.
        }
        set ChangeRate to (input - LastInput) / dt.
        if Kd <> 0 {
            set DTerm to -ChangeRate * Kd.
        }
    }
    set Output to pTerm + iTerm + dTerm.
    // if the output goes beyond the max/min limits, reset it and adjust ITerm.
    if Output > MaxOutput {
        set Output to MaxOutput.
        // adjust the value of ITerm as well to prevent the value
        // from winding up out of control.
        if (Ki <> 0) and (LastSampleTime < sampleTime) {
            set ITerm to Output - min(Pterm + DTerm, MaxOutput).
        }
    }
    else if Output < MinOutput {
        set Output to MinOutput.
        // adjust the value of ITerm as well to prevent the value
        // from winding up out of control.
        if (Ki <> 0) and (LastSampleTime < sampleTime) {
            set ITerm to Output - max(Pterm + DTerm, MinOutput).
        }
    }
    set LastSampleTime to sampleTime.
    set LastInput to input.
    if Ki <> 0 set ErrorSum to ITerm / Ki.
    else set ErrorSum to 0.
    return Output.
}

Resource Transfer

Usage is covered elsewhere <../commands/resource_transfer.html>`__

Structure
structure ResourceTransfer
Members
Suffix Type (units) Access Description
STATUS string Get only The string status of the transfer (eg “Inactive”, “Transferring”, “Failed”, “Finished”)
MESSAGE string Get only A message about the current status
GOAL scalar Get only This is how much of the resource will be transferred.
TRANSFERRED scalar Get only This is how much of the resource has been transferred.
RESOURCE string Get only The name of the resource (eg oxidizer, liquidfuel)
ACTIVE boolean Get / Set Setting this value will either start, pause or restart a transfer. Default is false.
RESOURCETRANSFER:STATUS
Access:Get only
Type:string

This enumerated type shows the status of the transfer. the possible values are:

  • Inactive (default)
    • Transfer is stopped
  • Finished
    • Transfer has reached its goal
  • Failed
    • There was an error in the transfer, see MESSAGE for details
  • Transferring
    • The transfer is in progress.
RESOURCETRANSFER:MESSAGE
Access:Get only
Type:string

This shows the detail related to STATUS

RESOURCETRANSFER:GOAL
Access:Get only
Type:scalar

If you specified an amount to transfer in your transfer request, it will be shown here. If you did not, this will return the sentinel value -1.

RESOURCETRANSFER:TRANSFERRED
Access:Get only
Type:scalar

Returns the amount of the specified resource that has been transferred by this resource transfer.

RESOURCETRANSFER:RESOURCE
Access:Get only
Type:string

The name of the resource that will be transferred. (eg, oxidizer, liquidfuel)

RESOURCETRANSFER:ACTIVE
Access:Get / Set
Type:boolean

When getting, this suffix is simply a shortcut to tell you if STATUS is Transferring. Setting true will change the status of the transfer to Transferring, setting false will change status to inactive.

SteeringManager

See the cooked control tuning explanation for information to help with tuning the steering manager. It’s important to read that section first to understand which setting below is affecting which portion of the steering system.

The SteeringManager is a bound variable, not a suffix to a specific vessel. This prevents access to the SteeringManager of other vessels. You can access the steering manager as shown below:

// Display the ship facing, target facing, and world coordinates vectors.
SET STEERINGMANAGER:SHOWFACINGVECTORS TO TRUE.

// Change the torque calculation to multiply the available torque by 1.5.
SET STEERINGMANAGER:ROLLTORQUEFACTOR TO 1.5.

Note

New in version 0.18: The SteeringManager was added to improve the accuracy of kOS’s cooked steering. While this code is a significant improvement over the old system, it is not perfect. Specifically it does not properly calculate the effects of control surfaces, nor does it account for atmospheric drag. It also does not adjust for asymmetric RCS or Engine thrust. It does allow for some modifications to the built in logic through the torque adjustments and factors. However, if there is a condition for which the new steering manager is unable to provide accurate control, you should continue to fall back to raw controls.

structure SteeringManager
Suffix Type Description
PITCHPID PIDLoop The PIDLoop for the pitch rotational velocity PID.
YAWPID PIDLoop The PIDLoop for the yaw rotational velocity PID.
ROLLPID PIDLoop The PIDLoop for the roll rotational velocity PID.
ENABLED boolean Returns true if the SteeringManager is currently controlling the vessel
TARGET Direction The direction that the vessel is currently steering towards
RESETPIDS() none Called to call RESET on all steering PID loops.
SHOWFACINGVECTORS boolean Enable/disable display of ship facing, target, and world coordinates vectors.
SHOWANGULARVECTORS boolean Enable/disable display of angular rotation vectors
SHOWSTEERINGSTATS boolean Enable/disable printing of the steering information on the terminal
WRITECSVFILES boolean Enable/disable logging steering to csv files.
PITCHTS scalar (s) Settling time for the pitch torque calculation.
YAWTS scalar (s) Settling time for the yaw torque calculation.
ROLLTS scalar (s) Settling time for the roll torque calculation.
MAXSTOPPINGTIME scalar (s) The maximum amount of stopping time to limit angular turn rate.
ROLLCONTROLANGLERANGE scalar (deg) The maximum value of ANGLEERROR for which to control roll.
ANGLEERROR scalar (deg) The angle between vessel:facing and target directions
PITCHERROR scalar (deg) The angular error in the pitch direction
YAWERROR scalar (deg) The angular error in the yaw direction
ROLLERROR scalar (deg) The angular error in the roll direction
PITCHTORQUEADJUST scalar (kN) Additive adjustment to pitch torque (calculated)
YAWTORQUEADJUST scalar (kN) Additive adjustment to yaw torque (calculated)
ROLLTORQUEADJUST scalar (kN) Additive adjustment to roll torque (calculated)
PITCHTORQUEFACTOR scalar Multiplicative adjustment to pitch torque (calculated)
YAWTORQUEFACTOR scalar Multiplicative adjustment to yaw torque (calculated)
ROLLTORQUEFACTOR scalar Multiplicative adjustment to roll torque (calculated)

Warning

New in version v0.20.1: The suffixes SHOWRCSVECTORS and SHOWTHRUSTVECTORS were deprecated with the move to using stock torque calculation with KSP 1.1.

SteeringManager:PITCHPID
Type:PIDLoop
Access:Get only

Returns the PIDLoop object responsible for calculating the target angular velocity in the pitch direction. This allows direct manipulation of the gain parameters, and other components of the PIDLoop structure. Changing the loop’s MAXOUTPUT or MINOUTPUT values will have no effect as they are overwritten every physics frame. They are set to limit the maximum turning rate to that which can be stopped in a MAXSTOPPINGTIME seconds (calculated based on available torque, and the ship’s moment of inertia).

SteeringManager:YAWPID
Type:PIDLoop
Access:Get only

Returns the PIDLoop object responsible for calculating the target angular velocity in the yaw direction. This allows direct manipulation of the gain parameters, and other components of the PIDLoop structure. Changing the loop’s MAXOUTPUT or MINOUTPUT values will have no effect as they are overwritten every physics frame. They are set to limit the maximum turning rate to that which can be stopped in a MAXSTOPPINGTIME seconds (calculated based on available torque, and the ship’s moment of inertia).

SteeringManager:ROLLPID
Type:PIDLoop
Access:Get only

Returns the PIDLoop object responsible for calculating the target angular velocity in the roll direction. This allows direct manipulation of the gain parameters, and other components of the PIDLoop structure. Changing the loop’s MAXOUTPUT or MINOUTPUT values will have no effect as they are overwritten every physics frame. They are set to limit the maximum turning rate to that which can be stopped in a MAXSTOPPINGTIME seconds (calculated based on available torque, and the ship’s moment of inertia).

Note

The SteeringManager will ignore the roll component of steering until after both the pitch and yaw components are close to being correct. In other words it will try to point the nose of the craft in the right direction first, before it makes any attempt to roll the craft into the right orientation. As long as the pitch or yaw is still far off from the target aim, this PIDloop won’t be getting used at all.

SteeringManager:ENABLED
Type:boolean
Access:Get only

Returns true if the SteeringManager is currently controlling the vessel steering.

SteeringManager:TARGET
Type:Direction
Access:Get only

Returns direction that the is currently being targeted. If steering is locked to a vector, this will return the calculated direction in which kOS chose an arbitrary roll to go with the vector. If steering is locked to “kill”, this will return the vessel’s last facing direction.

SteeringManager:RESETPIDS()
Returns:none

Resets the integral sum to zero for all six steering PID Loops.

SteeringManager:SHOWFACINGVECTORS
Type:boolean
Access:Get/Set

Setting this suffix to true will cause the steering manager to display graphical vectors (see VecDraw) representing the forward, top, and starboard of the facing direction, as well as the world x, y, and z axis orientation (centered on the vessel). Setting to false will hide the vectors, as will disabling locked steering.

SteeringManager:SHOWANGULARVECTORS
Type:boolean
Access:Get/Set

Setting this suffix to true will cause the steering manager to display graphical vectors (see VecDraw) representing the current and target angular velocities in the pitch, yaw, and roll directions. Setting to false will hide the vectors, as will disabling locked steering.

SteeringManager:SHOWSTEERINGSTATS
Type:boolean
Access:Get/Set

Setting this suffix to true will cause the steering manager to clear the terminal screen and print steering data each update.

SteeringManager:WRITECSVFILES
Type:boolean
Access:Get/Set

Setting this suffix to true will cause the steering manager log the data from all 6 PIDLoops calculating target angular velocity and target torque. The files are stored in the [KSP Root]GameDatakOSPluginsPluginDatakOS folder, with one file per loop and a new file created for each new manager instance (i.e. every launch, every revert, and every vessel load). These files can grow quite large, and add up quickly, so it is recommended to only set this value to true for testing or debugging and not normal operation.

SteeringManager:PITCHTS
Type:scalar
Access:Get/Set

Represents the settling time for the PID calculating pitch torque based on target angular velocity. The proportional and integral gain is calculated based on the settling time and the moment of inertia in the pitch direction. Ki = (moment of inertia) * (4 / (settling time)) ^ 2. Kp = 2 * sqrt((moment of inertia) * Ki).

SteeringManager:YAWTS
Type:scalar
Access:Get/Set

Represents the settling time for the PID calculating yaw torque based on target angular velocity. The proportional and integral gain is calculated based on the settling time and the moment of inertia in the yaw direction. Ki = (moment of inertia) * (4 / (settling time)) ^ 2. Kp = 2 * sqrt((moment of inertia) * Ki).

SteeringManager:ROLLTS
Type:scalar
Access:Get/Set

Represents the settling time for the PID calculating roll torque based on target angular velocity. The proportional and integral gain is calculated based on the settling time and the moment of inertia in the roll direction. Ki = (moment of inertia) * (4 / (settling time)) ^ 2. Kp = 2 * sqrt((moment of inertia) * Ki).

SteeringManager:MAXSTOPPINGTIME
Type:scalar (s)
Access:Get/Set

This value is used to limit the turning rate when calculating target angular velocity. The ship will not turn faster than what it can stop in this amount of time. The maximum angular velocity about each axis is calculated as: (max angular velocity) = MAXSTOPPINGTIME * (available torque) / (moment of inertia).

Note

This setting affects all three of the rotational velocity PID’s at once (pitch, yaw, and roll), rather than affecting the three axes individually one at a time.

SteeringManager:ROLLCONTROLANGLERANGE
Type:scalar (deg)
Access:Get/Set

The maximum value of ANGLEERROR for which kOS will attempt to respond to error along the roll axis. If this is set to 5 (the default value), the facing direction will need to be within 5 degrees of the target direction before it actually attempts to roll the ship. Setting the value to 180 will effectivelly allow roll control at any error amount. When ANGLEERROR is greater than this value, kOS will only attempt to kill all roll angular velocity. The value is clamped between 180 and 1e-16.

SteeringManager:ANGLEERROR
Type:scalar (deg)
Access:Get only

The angle between the ship’s facing direction forward vector and the target direction’s forward. This is the combined pitch and yaw error.

SteeringManager:PITCHERROR
Type:scalar (deg)
Access:Get only

The pitch angle between the ship’s facing direction and the target direction.

SteeringManager:YAWERROR
Type:scalar (deg)
Access:Get only

The yaw angle between the ship’s facing direction and the target direction.

SteeringManager:ROLLERROR
Type:scalar (deg)
Access:Get only

The roll angle between the ship’s facing direction and the target direction.

SteeringManager:PITCHTORQUEADJUST
Type:scalar (kNm)
Access:Get/Set

You can set this value to provide an additive bias to the calculated available pitch torque used in the pitch torque PID. (available torque) = ((calculated torque) + PITCHTORQUEADJUST) * PITCHTORQUEFACTOR.

SteeringManager:YAWTORQUEADJUST
Type:scalar (kNm)
Access:Get/Set

You can set this value to provide an additive bias to the calculated available yaw torque used in the yaw torque PID. (available torque) = ((calculated torque) + YAWTORQUEADJUST) * YAWTORQUEFACTOR.

SteeringManager:ROLLTORQUEADJUST
Type:scalar (kNm)
Access:Get/Set

You can set this value to provide an additive bias to the calculated available roll torque used in the roll torque PID. (available torque) = ((calculated torque) + ROLLTORQUEADJUST) * ROLLTORQUEFACTOR.

SteeringManager:PITCHTORQUEFACTOR
Type:scalar (kNm)
Access:Get/Set

You can set this value to provide an multiplicative factor bias to the calculated available pitch torque used in the torque PID. (available torque) = ((calculated torque) + PITCHTORQUEADJUST) * PITCHTORQUEFACTOR.

SteeringManager:YAWTORQUEFACTOR
Type:scalar (kNm)
Access:Get/Set

You can set this value to provide an multiplicative factor bias to the calculated available yaw torque used in the torque PID. (available torque) = ((calculated torque) + YAWTORQUEADJUST) * YAWTORQUEFACTOR.

SteeringManager:ROLLTORQUEFACTOR
Type:scalar (kNm)
Access:Get/Set

You can set this value to provide an multiplicative factor bias to the calculated available roll torque used in the torque PID. (available torque) = ((calculated torque) + ROLLTORQUEADJUST) * ROLLTORQUEFACTOR.

String

A String is an immutable sequence of characters in kOS.

Creating strings

Unlike other structures, strings are created with a special syntax:

// Create a new string
SET s TO "Hello, Strings!".

Strings are immutable. This means, once a string has been created, it can not be directly modified. However, new strings can be created out of existing strings. For example:

// Create a new string with "Hello" replaced with "Goodbye"
SET s TO "Hello, Strings!".
SET t TO s:REPLACE("Hello", "Goodbye").
ACCESSING INDIVIDUAL CHARACTERS

There’s two main ways to access the individual characters of a string - using an iterator or using index numbers:

Using an Iterator (FOR)

Strings can be treated a little bit like iterable lists of characters. This allows them to be used in FOR loops as in the example below:

LOCAL str is "abcde".

FOR c IN str {
  PRINT c.  // prints "a" the first time, then "b", etc.
}

The reason you can use Strings with the FOR loop like this is because you can obatain an Iterator of a string with the ITERATOR suffix mentioned below. (Any type that implements the ITERATOR suffix can do this.)

Using an Index ( [i] )

Strings can also be treated a little bit like lists in that they allow you to use the square-brackets operator [..`]` to choose one character by its index number (numbers start counting at zero). Here’s an example that does the same thing as the FOR loop above, but using index notation:

LOCAL str is "abcde".
local index is 0.
until index = str:LENGTH {
  print str[index].
  set index to index + 1.
}

Be aware that despite being able to read the characters this way, you cannot set them this way. The following will give an error:

LOCAL str is "abcde".

// The following line gives an error because you can't
// change the characters inside a string:
set str[0] to "X".
Boolean Operators
Equality

Using the = and <> operators, two strings are equal if and only if they are the same length and have letters that differ only in capitalization (a and A are considered the same letter for this test).

Ordering

Using the <, >, <=, and >= operators, one string is considered to be less than the other if it is alphabetically sooner according to the ordering of its Unicode mapping, with the exception that capitalization is ingored (a and A are considered the same letter). Starting from the lefthand side of the two strings, the characters are compered one at a time until the first difference is found, and that first difference decides the ordering. If one of the strings is shorter length than the other, and the characters are all equal up until one of the two strings runs out of characters, then the shorter string will be considered “less than” the longer one.

Mixtures of strings and non-strings

If you attempt to compare two things only one of which is a string and the other is not, then the non-string will be converted into a string first, (Giving the same string as its :TOSTRING suffix would give), and the two will be compared as strings. Example:

print (1234 < 99).    // prints "False"
print ("1234" < 99).  // prints "True"

In the first example, both sides of the < operator are scalars, so the comparison is done numerically, and 1234 is much bigger than 99.

In the second example, one side of the < operator is a string, so the other side is converted from the scalar 99 into the string "99" to perform the comparison, and then the string comparison looks one character at a time and notices that “1” is less than “9” and calls “1234” the lesser value.

CASE SENSITIVIY

NOTE: All string comparisons for equality and ordering, all substring matches, all pattern matches, and all string searches, are currently case in sensive, meaning that for example the letter “A” and the letter “a” are indistinguishable. There are future plans to add mechanisms that will let you choose case-sensitivity when you prefer.

At the moment the only way to force a case-sensitive comparison is to look at the characters one at a time and obtain their numerical ordinal Unicode value with the unchar(a) function.

Structure
structure String
Members
Suffix Type Description
CONTAINS(string) Boolean True if the given string is contained within this string
ENDSWITH(string) Boolean True if this string ends with the given string
FIND(string) Scalar Returns the index of the first occurrence of the given string in this string (starting from 0)
FINDAT(string, startAt) Scalar Returns the index of the first occurrence of the given string in this string (starting from startAt)
FINDLAST(string) Scalar Returns the index of the last occurrence of the given string in this string (starting from 0)
FINDLASTAT(string, startAt) Scalar Returns the index of the last occurrence of the given string in this string (starting from startAt)
INDEXOF(string) Scalar Alias for FIND(string)
INSERT(index, string) String Returns a new string with the given string inserted at the given index into this string
ITERATOR Iterator generates an iterator object the elements
LASTINDEXOF(string) Scalar Alias for FINDLAST(string)
LENGTH Scalar Number of characters in the string
MATCHESPATTERN(pattern) Boolean Tests whether the string matches the given regex pattern.
PADLEFT(width) String Returns a new right-aligned version of this string padded to the given width by spaces
PADRIGHT(width) String Returns a new left-aligned version of this string padded to the given width by spaces
REMOVE(index,count) String Returns a new string out of this string with the given count of characters removed starting at the given index
REPLACE(oldString, newString) String Returns a new string out of this string with any occurrences of oldString replaced with newString
SPLIT(separator) String Breaks this string up into a list of smaller strings on each occurrence of the given separator
STARTSWITH(string) Boolean True if this string starts with the given string
SUBSTRING(start, count) String Returns a new string with the given count of characters from this string starting from the given start position
TOLOWER String Returns a new string with all characters in this string replaced with their lower case versions
TOUPPER String Returns a new string with all characters in this string replaced with their upper case versions
TRIM String returns a new string with no leading or trailing whitespace
TRIMEND String returns a new string with no trailing whitespace
TRIMSTART String returns a new string with no leading whitespace
TONUMBER(defaultIfError) Scalar Parse the string into a number that can be used for mathematics.
TOSCALAR(defaultIfError) Scalar Alias for TONUMBER
String:CONTAINS(string)
Parameters:
  • stringString to look for
Return type:

Boolean

True if the given string is contained within this string.

String:ENDSWITH(string)
Parameters:
  • stringString to look for
Return type:

Boolean

True if this string ends with the given string.

String:FIND(string)
Parameters:
  • stringString to look for
Return type:

String

Returns the index of the first occurrence of the given string in this string (starting from 0).

String:FINDAT(string, startAt)
Parameters:
  • stringString to look for
  • startAtScalar (integer) index to start searching at
Return type:

String

Returns the index of the first occurrence of the given string in this string (starting from startAt).

String:FINDLAST(string)
Parameters:
  • stringString to look for
Return type:

String

Returns the index of the last occurrence of the given string in this string (starting from 0)

String:FINDLASTAT(string, startAt)
Parameters:
  • stringString to look for
  • startAtScalar (integer) index to start searching at
Return type:

String

Returns the index of the last occurrence of the given string in this string (starting from startAt)

String:INDEXOF(string)

Alias for FIND(string)

String:INSERT(index, string)
Parameters:
  • indexScalar (integer) index to add the string at
  • stringString to insert
Return type:

String

Returns a new string with the given string inserted at the given index into this string

String:ITERATOR
Type:Iterator
Access:Get only

An alternate means of iterating over a string’s characters (See: Iterator).

For most programs you won’t have to use this directly. It’s just what enables you to use a string with a FOR loop to get access to its characters one at a time.

String:LASTINDEXOF(string)

Alias for FINDLAST(string)

String:LENGTH
Type:Scalar (integer)
Access:Get only

Number of characters in the string

String:MATCHESPATTERN(pattern)
Parameters:
  • patternString pattern to be matched against the string
Return type:

Boolean

True if the string matches the given pattern (regular expression). The match is not anchored to neither the start nor the end of the string. That means that pattern "foo" will match "foobar", "barfoo" and "barfoobar" too. If you want to match from the start, you have to explicitly specify the start of the string in the pattern, i.e. for example to match strings starting with "foo" you need to use the pattern "^foo" (or equivalently "^foo.*" or even "^foo.*$").

Regular expressions are beyond the scope of this documentation. For reference see Regular Expression Language - Quick Reference.

String:PADLEFT(width)
Parameters:
  • widthScalar (integer) number of characters the resulting string will contain
Return type:

String

Returns a new right-aligned version of this string padded to the given width by spaces.

String:PADRIGHT(width)
Parameters:
  • widthScalar (integer) number of characters the resulting string will contain
Return type:

String

Returns a new left-aligned version of this string padded to the given width by spaces.

String:REMOVE(index,count)
Parameters:
  • indexScalar (integer) position of the string from which characters will be removed from the resulting string
  • countScalar (integer) number of characters that will be removing from the resulting string
Return type:

String

Returns a new string out of this string with the given count of characters removed starting at the given index.

String:REPLACE(oldString,newString)
Parameters:
  • oldStringString to search for
  • newStringString that all occurances of oldString will be replaced with
Return type:

String

Returns a new string out of this string with any occurrences of oldString replaced with newString.

String:SPLIT(separator)
Parameters:
  • separatorString delimiter on which this string will be split
Returns:

List

Breaks this string up into a list of smaller strings on each occurrence of the given separator. This will return a list of strings, none of which will contain the separator character(s).

String:STARTSWITH(string)
Parameters:
  • stringString to look for
Return type:

Boolean

True if this string starts with the given string .

String:SUBSTRING(start,count)
Parameters:
  • startScalar (integer) starting index (from zero)
  • countScalar (integer) resulting length of returned String
Returns:

String

Returns a new string with the given count of characters from this string starting from the given start position.

String:TOLOWER
Type:String
Access:Get only

Returns a new string with all characters in this string replaced with their lower case versions

String:TOUPPER
Type:String
Access:Get only

Returns a new string with all characters in this string replaced with their upper case versions

String:TRIM
Type:String
Access:Get only

returns a new string with no leading or trailing whitespace

String:TRIMEND
Type:String
Access:Get only

returns a new string with no trailing whitespace

String:TRIMSTART
Type:String
Access:Get only

returns a new string with no leading whitespace

String:TONUMBER(defaultIfError)
Parameters:
  • defaultIfError – (optional argument) Scalar to return as a default value if the string format is in error.
Returns:

Scalar

Returns the numeric version of the string, as a number that can be used for mathematics or anywhere a Scalar is expected. If the string is not in a format that kOS is able to convert into a number, then the value defaultIfError is returned instead. You can use this to either select a sane default, or to deliberately select a value you never expect to get in normal circumstances so you can use it as a test to see if the string was formatted well.

The argument defaultIfError is optional. If it is left off, then when there is a problem in the format of the string, you will get an error that stops the script instead of returning a value.

The valid understood format allows an optional leading sign, a decimal point with fractional part, and scientific notation using “e” as in “1.23e3” for “1230” or “1.23e-3” for “0.00123”.

You may also include optional underscores in the string to help space groups of digits, and they will be ignored. (For example you may write “one thousand” as “1_000” instead of as “1000” if you like”.)

Example - using with math:

set str to "16.8".
print "half of " + str + " is " + str:tonumber() / 2.
half of 16.8 is 8.4

Example - checking for bad values by using defaultIfError:

set str to "Garbage 123 that is not a proper number".
set val to str:tonumber(-9999).
if val = -9999 {
  print "that string isn't a number".
} else {
  print "the string is a number: " + val.
}

Example - not setting a default value can throw an error:

set str to "Garbage".
set val to str:tonumber().  // the script dies with error here.
print "value is " + val. // the script never gets this far.
String:TOSCALAR(defaultIfError)

Alias for String:TONUMBER(defaultIfError)

Access to Individual Characters

All string indexes start counting at zero. (The characters are numbered from 0 to N-1 rather than from 1 to N.)

string[expression]

  • operator: access the character at position ‘expression’. Any arbitrary complex expression may be used with this syntax, not just a number or variable name.

FOR VAR IN STRING { ... }.

  • A type of loop in which var iterates over all the characters of the string from 0 to LENGTH-1.

Examples:

                                                                // CORRECT OUTPUTS
SET s TO "Hello, Strings!".                                     // ---------------
PRINT "Original String:               " + s.                    // Hello, Strings!
PRINT "string[7]:                     " + s[7].                 // S
PRINT "LENGTH:                        " + s:LENGTH.             // 15
PRINT "SUBSTRING(7, 6):               " + s:SUBSTRING(7, 6).    // String
PRINT "CONTAINS(''ring''):            " + s:CONTAINS("ring").   // True
PRINT "CONTAINS(''bling''):           " + s:CONTAINS("bling").  // False
PRINT "ENDSWITH(''ings!''):           " + s:ENDSWITH("ings!").  // True
PRINT "ENDSWITH(''outs!''):           " + s:ENDSWITH("outs").   // False
PRINT "FIND(''l''):                   " + s:FIND("l").          // 2
PRINT "FINDLAST(''l''):               " + s:FINDLAST("l").      // 3
PRINT "FINDAT(''l'', 0):              " + s:FINDAT("l", 0).     // 2
PRINT "FINDAT(''l'', 3):              " + s:FINDAT("l", 3).     // 3
PRINT "FINDLASTAT(''l'', 9):          " + s:FINDLASTAT("l", 9). // 3
PRINT "FINDLASTAT(''l'', 2):          " + s:FINDLASTAT("l", 2). // 2
PRINT "INSERT(7, ''Big ''):           " + s:INSERT(7, "Big ").  // Hello, Big Strings!

PRINT " ".
PRINT "                               |------ 18 ------|".
PRINT "PADLEFT(18):                   " + s:PADLEFT(18).        //    Hello, Strings!
PRINT "PADRIGHT(18):                  " + s:PADRIGHT(18).       // Hello, Strings!
PRINT " ".

PRINT "REMOVE(1, 3):                  " + s:REMOVE(1, 3).               // Ho, Strings!
PRINT "REPLACE(''Hell'', ''Heaven''): " + s:REPLACE("Hell", "Heaven").  // Heaveno, Strings!
PRINT "STARTSWITH(''Hell''):          " + s:STARTSWITH("Hell").         // True
PRINT "STARTSWITH(''Heaven''):        " + s:STARTSWITH("Heaven").       // False
PRINT "TOUPPER:                       " + s:TOUPPER().                  // HELLO, STRINGS!
PRINT "TOLOWER:                       " + s:TOLOWER().                  // hello, strings!

PRINT " ".
PRINT "''  Hello!  '':TRIM():         " + "  Hello!  ":TRIM().          // Hello!
PRINT "''  Hello!  '':TRIMSTART():    " + "  Hello!  ":TRIMSTART().     // Hello!
PRINT "''  Hello!  '':TRIMEND():      " + "  Hello!  ":TRIMEND().       //   Hello!

PRINT " ".
PRINT "Chained: " + "Hello!":SUBSTRING(0, 4):TOUPPER():REPLACE("ELL", "ELEPHANT").  // HELEPHANT

Terminal

The TERMINAL identifier refers to a special structure that lets you access some of the information about the screen you are running on.

Warning: Features related to the in-game terminal GUI may change when KSP 1.1 comes out, as we may redesign some of the user interface.

Structure
structure Terminal
Members
Suffix Type Get/Set Description
WIDTH Scalar get and set Terminal width in characters
HEIGHT Scalar get and set Terminal height in characters
REVERSE Boolean get and set Determines if the screen is displayed with foreground and background colors swapped.
VISUALBEEP Boolean get and set Turns beeps into silent visual screen flashes instead.
BRIGHTNESS Scalar get and set Adjusts brightness slider of the terminal between 0.0 (min) and 1.0 (max).
CHARWIDTH Scalar get and set Width of a character cell in pixels.
CHARHEIGHT Scalar get and set Height of a character cell in pixels.
INPUT TerminalInput get Used to read user’s input into the terminal.
Terminal:WIDTH
Access:Get/Set
Type:Scalar.

If you read the width it will return a number of character cells wide the terminal is. If you set this value, it will cause the terminal to resize. If there’s multiple terminals connected to the same CPU part via telnet clients, then kOS will attempt to keep them all the same size, and one terminal being resized will resize them all. (caveat: Some terminal types cannot be resized from the server side, and therefore this doesn’t always work in both directions).

This setting is different per kOS CPU part. Different terminal windows can have different settings for this value.

Terminal:HEIGHT
Access:Get/Set
Type:Scalar.

If you read the width it will return a number of character cells tall the terminal is. If you set this value, it will cause the terminal to resize. If there’s multiple terminals connected to the same CPU part via telnet clients, then kOS will attempt to keep them all the same size, and one terminal being resized will resize them all. (caveat: Some terminal types cannot be resized from the server side, and therefore this doesn’t always work in both directions).

This setting is different per kOS CPU part. Different terminal windows can have different settings for this value.

Terminal:REVERSE
Access:Get/Set
Type:Boolean.

If true, then the terminal window is currently set to show the whole screen in reversed color - swapping the background and foreground colors. Both the telnet terminals and the in-game GUI terminal respond to this setting equally.

Note, this setting can also be toggled with a radio-button on the in-game GUI terminal window.

This setting is different per kOS CPU part. Different terminal windows can have different settings for this value.

Terminal:VISUALBEEP
Access:Get/Set
Type:Boolean.

If true, then the terminal window is currently set to show any BEEP characters by silently flashing the screen for a moment (inverting the background/foreground for a fraction of a second), instead of making a sound.

Note, this setting can also be toggled with a radio-button on the in-game GUI terminal window.

This will only typically affect the in-game GUI terminal window, and not a telnet client’s terminal window.

To affect the window you are using in a telnet session, you will have to use whatever your terminal or terminal emulator’s local settings panel has for it. Most do have some sort of visual beep setting, but it is usually not settable via a control character sequence sent across the connection. The terminals are designed to assume it’s a local user preference that isn’t overridable by the software you are running.

This setting is different per kOS CPU part. Different terminal windows can have different settings for this value.

Terminal:BRIGHTNESS
Access:Get/Set
Type:Scalar

The same thing as the brightness slider on the terminal GUI. The values range from 0.0 (minimum) to 1.0 (maximum). At zero, the effect is to entirely hide the letters altogether.

Warning: Features related to the in-game terminal GUI may change when KSP 1.1 comes out, as we may redesign some of the user interface.

Terminal:CHARWIDTH
Access:Get/Set
Type:Scalar

Width of a character cell in the display terminal, in pixels. The value is forced to remain in the range [4..24] and be divisible by 2. If you try to set it to any other value, it will snap to the allowed range and increment.

Warning: Features related to the in-game terminal GUI may change when KSP 1.1 comes out, as we may redesign some of the user interface.

Terminal:CHARHEIGHT
Access:Get/Set
Type:Scalar

Height of a character cell in the display terminal, in pixels. The value is forced to remain in the range [4..24] and be divisible by 2. If you try to set it to any other value, it will snap to the allowed range and increment.

Warning: Features related to the in-game terminal GUI may change when KSP 1.1 comes out, as we may redesign some of the user interface.

Terminal:INPUT
Access:Get
Type:TerminalInput

This gives you a TerminalInput structure, which can be used to read user’s input into the kOS terminal.

Terminal Input

You can read the user’s keyboard input into the kOS terminal using this structure. You obtain this structure by calling Terminal:INPUT.

Input is buffered

Input is buffered if the user types faster than you process the input. (For example, if you have code that reads 1 character per second, and the user types faster than 1 character per second, then the letters they typed “in between” your reads are not lost. It just takes time for your program to catch up to the backlog and finish processing them all.) This buffer is active for the entire duration of the program, which means that you must clear the buffer using TerminalInput:CLEAR if you need to ensure that the contents are in response to a prompt.

Input is blocking

If you attempt to read a character of input and there are none available (because the user hasn’t typed anything yet for you to read), then your program will pause and get stuck there until the user presses a key. If you want to check first to find out if a key is available before you read it, use the HASCHAR suffix described below.

Detecting special keys

You can detect some special keys that don’t form normal ASCII codes, such as the Left arrow, Page Up, and so on.

Internally KOS uses its own mapping of these characters to its own Unicode codes. (This is part of the system it uses to support a few different types of terminal in the telnet module). You can see some of these code names and use them to test against your input characters. Some of the suffixes to TerminalInput are for this purpose.

Example:

set ch to terminal:input:getchar().

if ch = terminal:input:DOWNCURSORONE {
  print "You typed the down-arrow key.".
}
if ch = terminal:input:UPCURSORONE {
  print "You typed the up-arrow key.".
}
Cannot read control-C

You cannot read the control-C character in your program, because it causes your program to break.

Cannot read “shift” or “alt”

You cannot read the “shift” or “alt” keypresses pressed by themselves because they send no characters to the terminal until combined with other characters. (For example “shift A” sends an “A” character, while “A” without shift sends a “a” character, but you can’t just read the shift key itself.) This is a deliberate decision because the kOS terminal in the game is supposed to be identical to a telnet terminal window, and you can’t “send” these sorts of keypresses as characters down a stream.

Structure
structure TerminalInput
Members
Suffix Type Get/Set Description
GETCHAR String Get (Blocking) I/O to read the next character of terminal input.
HASCHAR Boolean Get True if there is at least 1 character of input waiting.
CLEAR None Method Call Call this method to throw away all waiting input characters, flushing the input queue.
BACKSPACE String Get A string for testing if the character read is a backspace.
DELETERIGHT String Get A string for testing if the character read is the delete (to the right) key.
RETURN String Get A string for testing if the character read is the return key.
ENTER String Get An alias for RETURN
UPCURSORONE String Get A string for testing if the character read is the up-arrow key.
DOWNCURSORONE String Get A string for testing if the character read is the down-arrow key.
LEFTCURSORONE String Get A string for testing if the character read is the left-arrow key.
RIGHTCURSORONE String Get A string for testing if the character read is the right-arrow key.
HOMECURSOR String Get A string for testing if the character read is the HOME key.
ENDCURSOR String Get A string for testing if the character read is the END key.
PAGEUPCURSOR String Get A string for testing if the character read is the PageUp key.
PAGEDOWNCURSOR String Get A string for testing if the character read is the PageDown key.
TerminalInput:GETCHAR()
Access:Get (Method call)
Returns:String

Read the next character of terminal input. If the user hasn’t typed anything in that is still waiting to be read, then this will “block” (meaning it will pause the execution of the program) until there is a character that has been typed that can be processed.

The character will be expressed in a string containing 1 char.

If you need to check against “unprintable” characters such as backspace (control-H) and so on, you can do so with the unchar function, or by using the aliases described elsewhere in this structure.

TerminalInput:HASCHAR
Access:Get (method call)
Type:Boolean

True if there is at least 1 character of input waiting. If this is false then that would mean that an attempt to call GETCHAR would block and wait for user input. If this is true then an attempt to call GETCHAR would return immediately with an answer.

You can simulate non-blocking I/O like so:

// Read a char if it exists, else just keep going:
if terminal:input:haschar {
  process_one_char(terminal:input:getchar()).
}
TerminalInput:CLEAR()
Access:Get (method call)
Returns:None

Call this method to throw away all waiting input characters, flushing the input queue.

TerminalInput:BACKSPACE
Access:Get
Type:String

A string for testing if the character read is a backspace.

TerminalInput:DELETERIGHT
Access:Get
Type:String

A string for testing if the character read is the delete (to the right) key.

TerminalInput:RETURN
Access:Get
Type:String

A string for testing if the character read is the return key.

TerminalInput:ENTER
Access:Get
Type:String
TerminalInput:UPCURSORONE
Access:Get
Type:String

A string for testing if the character read is the up-arrow key.

TerminalInput:DOWNCURSORONE
Access:Get
Type:String

A string for testing if the character read is the down-arrow key.

TerminalInput:LEFTCURSORONE
Access:Get
Type:String

A string for testing if the character read is the left-arrow key.

TerminalInput:RIGHTCURSORONE
Access:Get
Type:String

A string for testing if the character read is the right-arrow key.

TerminalInput:HOMECURSOR
Access:Get
Type:String

A string for testing if the character read is the HOME key.

TerminalInput:ENDCURSOR
Access:Get
Type:String

A string for testing if the character read is the END key.

TerminalInput:PAGEUPCURSOR
Access:Get
Type:String

A string for testing if the character read is the PageUp key.

TerminalInput:PAGEDOWNCURSOR
Access:Get
Type:String

A string for testing if the character read is the PageDown key.

(For the documentation on controlling time warp, please see the timewarp page. This page is the documentation on the structure that holds an individual timestamp representing some universal moment in time.)

Time Span

In several places the game uses a TimeSpan format. This is a structure that gives the time in various formats. It also allows you to perform arithmetic on the time.

TimeSpan represents SIMULATED time

When you are examining a TimeSpan you are looking at the “in character” simulated time, not the “out of character” real world time. This is a very important distinction to remember, as the following points illustrate:

  • A TimeSpan does not count the time that was passing while the game was paused.
  • If you turn off your computer and don’t play the game for several days, the TimeSpan does not count this time.
  • If your game lags and stutters such that the simulation is taking 2 seconds of real time to calculate 1 second of game time, then the number of seconds that have passed according to a TimeSpan will be fewer than the number of seconds that have passed in the real world.

This allows you to use a TimeSpan such as is returned by the TIME special variable to make correct physics calculations.

Special variable TIME
TIME
Access:Get only
Type:TimeSpan

The special variable TIME is used to get the current time.

Any time you perform arithmetic on TIME you get a result back that is also a TimeSpan. In other words, TIME is a TimeSpan, but TIME + 100 is also a TimeSpan.

Note that Kerbals do not have the concept of “months”:

TIME                // Gets the current universal time
TIME:CLOCK          // Universal time in H:M:S format(1:50:26)
TIME:CALENDAR       // Year 1, day 134
TIME:YEAR           // 1
TIME:DAY            // 134 : changes depending on KUNIVERSE:HOURSPERDAY
TIME:HOUR           // 1
TIME:MINUTE         // 50
TIME:SECOND         // 26
TIME:SECONDS        // Total Seconds since campaign began
Using TIME to detect when the physics have been updated ‘one tick’

kOS programs run however fast your computer’s animation rate will allow, which can flow and change from one moment to the next depending on load. However, the physics of the universe get updated at a fixed rate according to your game settings (the default, as of KSP 0.25, is 25 physics updates per second)

You can use the TIME special variable to detect whether or not a real physics ‘tic’ has occurred yet, which can be important for scripts that need to take measurements from the simulated universe. If no physics tic has occurred, then TIME will still be exactly the same value.

Warning

Please be aware that the kind of calendar TimeSpan‘s use will depend on your KSP settings. The main KSP game supports both Kerbin time and Earth time and changing that setting will affect how TimeSpan works in kOS.

The difference is whether 1 day = 6 hours or 1 day = 24 hours.

You can access this setting from your script by using Kuniverse:HOURSPERDAY.

Warning

Beware the pitfall of confusing the TimeSpan:SECOND (singular) suffix with the TimeSpan:SECONDS (plural) suffix.

TimeSpan:SECOND

This is the whole number of remainder seconds leftover after all whole-number minutes, hours, days, and years have been subtracted out, and it’s never outside the range [0..60). It’s essentially the ‘seconds hand’ on a clock.

TimeSpan:SECONDS

This is the number of seconds total if you want to represent time as just a simple flat number without all the components. It’s the total count of the number of seconds since the beginning of time (Epoch). Because it’s a floating point number, it can store times less than 1 second. Note this is a measure of how much simulated Kerbal time has passed since the game began. People experienced at programming will be familiar with this concept. It’s the Kerbal’s version of “unix time”.

The epoch (time zero) in the KSP game is the time at which you first started the new campaign. All campaign games begin with the planets in precisely the same position and the clock set to zero years, zero days, zero hours, and so on.

structure TimeSpan
Suffix Type Description
CLOCK String “HH:MM:SS”
CALENDAR String “Year YYYY, day DDD”
SECOND Scalar (0-59) Second-hand number
MINUTE Scalar (0-59) Minute-hand number
HOUR Scalar (0-5) Hour-hand number
DAY Scalar (1-426) Day-hand number
YEAR Scalar Year-hand number
SECONDS Scalar (fractional) Total Seconds since Epoch (includes fractional partial seconds)

Note

This type is serializable.

TimeSpan:CLOCK
Access:Get only
Type:String

Time in (HH:MM:SS) format.

TimeSpan:CALENDAR
Access:Get only
Type:String

Day in “Year YYYY, day DDD” format. (Kerbals don’t have ‘months’.)

TimeSpan:SECOND
Access:Get only
Type:Scalar (0-59)

Second-hand number.

TimeSpan:MINUTE
Access:Get only
Type:Scalar (0-59)

Minute-hand number

TimeSpan:HOUR
Access:Get only
Type:Scalar (0-5) or (0-23)

Hour-hand number. Kerbin has six hours in its day.

TimeSpan:DAY
Access:Get only
Type:Scalar (1-426) or (1-356)

Day-hand number. Kerbin has 426 days in its year.

TimeSpan:YEAR
Access:Get only
Type:Scalar

Year-hand number

TimeSpan:SECONDS
Access:Get only
Type:Scalar (float)

Total Seconds since Epoch. Epoch is defined as the moment your current saved game’s universe began (the point where you started your campaign). Can be very precise.

WARP

TimeWarp structure
structure TimeWarp

TimeWarp allows you to control and query the KSP game’s time warping abilities. You can obtain the TimeWarp structure by using the TimeWarp suffix of kuniverse.

Members and Methods
Suffix Type Get/Set Description
RATELIST List of Scalar values Get returns either RAILSRATELIST or PHYSICSRATELIST depending on which matches the current mode.
RAILSRATELIST List of Scalar values Get The list of allowed multiplier rates for on-rails-warp modes.
PHYSICSRATELIST List of Scalar values Get The list of allowed multiplier rates for physics-warp modes.
MODE String Get/Set Time warp mode. values are “PHYSICS” or “RAILS”.
WARP Scalar Get/Set Time warp integer index. Values go 0,1,2,3, etc.
RATE Scalar Get/Set The current multiplier timescale rate (i.e. 100 for 100x warp)
WARPTO None Callable method Call to warp forward to a known timestamp.
CANCELWARP None Callable method Cancel current warping (including WARPTO)
PHYSICSDELTAT SCALAR Get Physics Delta-T. How much time should pass between ticks.
ISSETTLED Boolean Get True once the actual rate finally arrives at the commanded rate.
Suffixes
TimeWarp:RATELIST
Access:Get
Type:Scalar

If MODE is “PHYSICS”, this will return PHYSICSRATELIST. If MODE is “RAILS”, this will return RAILSRATELIST.

It’s always the list that goes with the current warping mode.

TimeWarp:RAILSRATELIST
Access:Get
Type:List of Scalar values

The list of the legal values that the game lets you set the warp rate to when in “on rails” warping mode (“normal” time warp).

(As of this writing of the documents, the values come out like the table below, but the base KSP game could change these at any time. The following table is not a guarantee.)

RAILS WARP RATE LIST
WARP RATE
0 1x
1 5x
2 10x
3 50x
4 100x
5 1000x
6 10000x
7 100000x
TimeWarp:PHYSICSRATELIST
Access:Get
Type:List of Scalar values

The list of the legal values that the game lets you set the warp rate to when in “physics warp” warping mode.

(As of this writing of the documents, the values come out like the table below, but the base KSP game could change these at any time. The following table is not a guarantee.)

PHYSICS WARP RATE LIST
WARP RATE
0 1x
1 2x
2 3x
3 4x
TimeWarp:MODE
Access:Get/Set
Type:String

The string value indicating whether we are in “PHYSICS” or “RAILS” warping mode right now. You can set this value to change which warp mode the game will perform.

(Any experienced player of KSP should be aware of what the difference between physics warp and “time warp” (rails) is. In “physics” warp, all the normal things work, and the game simulates the entire physics engine with longer coarser delta-T time steps to achieve a faster simulation rate. In “rails” warp, many of the calculations are not working, the vessel is not controllable, and the game calculates positions of objects based on the Keplerian elliptical parameters only.)

TimeWarp:WARP
Access:Get/Set
Type:Scalar

Time warp as an integer index. In the tables listed above for RAILSRATELIST and PHYSICSRATELIST, this is the number on the lefthand side of those tables. (i.e. if MODE is “RAILS” and RATE is 50, then that means WARP is 3.)

If you set either WARP or RATE, the other will change along with it to agree with it. (See the full explanation in RATE below).

TimeWarp:RATE
Access:Get/Set
Type:Scalar

The current multiplier timescale rate (i.e. 1000 if current rate is 1000x as much as normal, etc).

If you have just changed the time warp, it takes a few moments for the game to “catch up” and achieve the desired warp rate. You can query this value to find out what the current rate is the game is operating under during this physics tick. It often takes almost a whole second of game time to finally arrive at the destination rate.

When you set the :RATE equal to a new value, then instead of directly setting the rate to that value, kOS will set the WARP to whatever value it would need to have to end up with that rate. The rate itself won’t change right away. For example, the following two commands are equivalent:

// This will eventually give you a rate of 100, after several
// update ticks have passed, but not right away:
set kuniverse:timewarp:warp to 4.

// This will *also* do the same thing, and not set the rate
// to 100 right away, but instead tells kOS indirectly
// to set the WARP to 4, so as to target a destination
// rate of 100.
set kuniverse:timewarp:rate to 100.

If you set the rate to a value that isn’t on the allowed list that the KSP game interface normally lets you pick, then kOS will pick whichever WARP value will get you closest to the requested rate. For example:

// If you do any of these, then the effect is the same:
set kuniverse:timewarp:rate to 89.
set kuniverse:timewarp:rate to 145.
set kuniverse:timewarp:rate to 100.
// Because the game only allows permanent rates of 1,5,10,50,100,1000, etc.
// A rate of 100 was the closest match it could allow.

Note, the game is actually capable of warping at arbitrary rates in between these values, and it does so temporarily when transitioning to a new warp rate, but it doesn’t allow you to hold the rate at those in-between values indefintiely.

TimeWarp:WARPTO(timestamp)
Access:

Method

Parameters:
Returns:

None

Call this method to warp time forward to a universal time stamp. The argument you pass in should be a universal timestamp in seconds. Example: To warp 120 seconds into the future: kuniverse:timewarp:warpto(time:seconds + 120).

Obviously this alters the values of WARP and RATE while the warping is happening.

TimeWarp:CANCELWARP()
Access:Method
Returns:None

Call this method to cancel any active warp. This will both interupt any current automated warp (such as one using WARPTO or the “Warp Here” user interface) and a manual warp setting (as if you had used the SET WARP TO 0. command).

TimeWarp:PHYSICSDELTAT
Access:Get
Type:Scalar

Physics Delta-T. How much time should pass between ticks. Note this is not the actual time that has passed. For that you should query time:seconds regularly and store the timestamps it returns, and compare those timestamps. This value is just the “expected” physics delta-T that you should get if everything is running smoothly and your computer can keep up with everything the game is doing.

This value changes depending on your physics warp. Note, if you query it during on-rails warping, it can return some very strange values you shouldn’t trust.

TimeWarp:ISSETTLED
Access:Get
Type:Boolean

When you have just changed the warp speed, the game takes time to “catch up” and achieve the new desired speed. (i.e. if you change your rate from 100x up to 1000x, and you look at the screen, you will see the numbers in the display saying things like “Warp 123x” then “Warp 344x” then “Warp 432x”, etc. There are several “ticks” during which the warp hasn’t yet achieved the desired 1000x level.) This can take a “long” time in computer terms to happen.

You can query this value to find out whether or not the actual warp rate has finally settled on the desired amount yet.

For example:

set kuniverse:timewarp:mode to "RAILS".
set kuniverse:timewarp:rate to 1000.
print "starting to change warp".
until kuniverse:timewarp:issettled {
    print "rate = " + round(rate,1).
    wait 0.
}
print "warp is now 1000x".

// The above would output something like this to the screen:
starting to change warp.
rate = 113.5
rate = 143.2
rate = 213.1
rate = 233.2
rate = 250.0
rate = 264.1
rate = 301.5
rate = 320.5
rate = 361.5
rate = 391.3
rate = 421.5
rate = 430.0
rate = 450.5
rate = 471.5
rate = 490.1
rate = 501.5
rate = 613.5
rate = 643.2
rate = 713.1
rate = 733.2
rate = 750.0
rate = 764.1
rate = 801.5
rate = 820.5
rate = 861.5
rate = 891.3
rate = 921.5
rate = 930.0
rate = 950.5
rate = 971.5
rate = 990.1
rate = 1000
warp is now 1000x.
Backward Compatible Time Warping

Time warping is accomplished using the TimeWarp structure described above on this page.

But, for backward compatibility, the following shortcut bound names exist as aliases for the functionality in the TimeWarp structure.

WARPMODE

This is identical to MODE above.

// These two do the same thing:
SET WARPMODE TO "PHYSICS".
SET KUNIVERSE:TIMEWARP:MODE TO "PHYSICS".

// These two do the same thing:
SET WARPMODE TO "RAILS".
SET KUNIVERSE:TIMEWARP:MODE TO "RAILS".
WARP

This is identical to WARP above.

// These do the same thing:
SET WARP TO 3.
SET KUNIVERSE:TIMEWARP:WARP to 3.
WARPTO(timestamp)

This is identical to WARPTO above.

// These two do the same thing:
WARPTO(time:seconds + 60*60). // warp 1 hour into the future.
KUNIVERSE:TIMEWARP:WARPTO(time:seconds + 60*60).

Drawing Vectors on the Screen

You can create an object that represents a vector drawn in the flight view, as a holographic image in flight. You may move it to a new location, make it appear or disappear, change its color, and label. This page describes how to do that.
VECDRAW(start, vec, color, label, scale, show, width)
VECDRAWARGS(start, vec, color, label, scale, show, width)

Both these two function names do the same thing. For historical reasons both names exist, but now they both do the same thing. They create a new vecdraw object that you can then manipulate to show things on the screen:

SET anArrow TO VECDRAW(
      V(0,0,0),
      V(a,b,c),
      RGB(1,0,0),
      "See the arrow?",
      1.0,
      TRUE,
      0.2
    ).

SET anArrow TO VECDRAWARGS(
      V(0,0,0),
      V(a,b,c),
      RGB(1,0,0),
      "See the arrow?",
      1.0,
      TRUE,
      0.2
    ).

All the parameters of the VECDRAW() and VECDRAWARGS() are optional. You can leave any of the lastmost parameters off and they will be given a default:

Set anArrow TO VECDRAW().

Causes it to have these defaults:

Defaults
Suffix Default
START V(0,0,0) (center of the ship is the origin)
VEC V(0,0,0) (no length, so nothing appears)
COLO[U]R White
LABEL Empty string “”
SCALE 1.0
SHOW false
WIDTH 0.2

Examples:

// Makes a red vecdraw at the origin, pointing 5 meters north,
// with defaults for the un-mentioned
// paramters LABEL, SCALE, SHOW, and WIDTH.
SET vd TO VECDRAW(V(0,0,0), 5*north:vector, red).

To make a VecDraw disappear, you can either set its VecDraw:SHOW to false or just UNSET the variable, or re-assign it. An example using VecDraw can be seen in the documentation for POSITIONAT().

CLEARVECDRAWS()

Sets all visible vecdraws to invisible, everywhere in this kOS CPU. This is useful if you have lost track of the handles to them and can’t turn them off one by one, or if you don’t have the variable scopes present anymore to access the variables that hold them. The system does attempt to clear any vecdraws that go “out of scope”, however the “closures” that keep local variables alive for LOCK statements and for other reasons can keep them from every truely going away in some circumstances. To make the arrow drawings all go away, just call CLEARVECDRAWS() and it will have the same effect as if you had done SET varname:show to FALSE for all vecdraw varnames in the entire system.

structure VecDraw

This is a structure that allows you to make a drawing of a vector on the screen in map view or in flight view.

Members
Suffix Type Description
START Vector Start position of the vector
VEC Vector The vector to draw
COLOR Color Color of the vector
COLOUR   Same as COLOR
LABEL string Text to show next to vector
SCALE scalar Scale VEC and WIDTH but not START
SHOW boolean True to enable display to screen
WIDTH scalar width of vector, default is 0.2
STARTUPDATER KosDelegate assigns a delegate to auto-update the START attribute.
VECUPDATER KosDelegate assigns a delegate to auto-update the VEC attribute.
VECTORUPDATER   Same as VECUPDATER
COLORUPDATER KosDelegate assigns a delegate to auto-update the COLOR attribute.
COLOURUPDATER   Same as COLORUPDATER
VecDraw:START
Access:Get/Set
Type:Vector

Optional, defaults to V(0,0,0) - position of the tail of the vector to draw in SHIP-RAW coords. V(0,0,0) means the ship Center of Mass.

VecDraw:VEC
Access:Get/Set
Type:Vector

Mandatory - The vector to draw, SHIP-RAW Coords.

VecDraw:COLOR
Access:Get/Set
Type:Color

Optional, defaults to white. This is the color to draw the vector. There is a hard-coded fade effect where the tail is a bit more transparent than the head.

VecDraw:COLOUR
Access:Get/Set
Type:Color

Alias for VecDraw:COLOR

VecDraw:LABEL
Access:Get/Set
Type:string

Optional, defaults to “”. Text to show on-screen at the midpoint of the vector. Note the font size the label is displayed in gets stretched when you change the SCALE or the WIDTH values.

VecDraw:SCALE
Access:Get/Set
Type:scalar

Optional, defaults to 1.0. Scalar to multiply the VEC by, and the WIDTH, but not the START.

Changed in version 0.19.0: In previous versions, this also moved the start location, but most users found that useless and confusing and that has been changed as described above.

VecDraw:SHOW
Access:Get/Set
Type:boolean

Set to true to make the arrow appear, false to hide it. Defaults to false until you’re ready to set it to true.

VecDraw:WIDTH
Access:Get/Set
Type:scalar

Define the width of the drawn line, in meters. The deafult is 0.2 if left off. Note, this also causes the font of the label to be enlarged to match if set to a value larger than 0.2.

New in version 0.19.0: This parameter didn’t exist before kOS 0.19.0.

VecDraw:STARTUPDATER
Access:Get/Set
Type:KosDelegate with no parameters, returning a Vector

This allows you to tell the VecDraw that you’d like it to update the START position of the vector regularly every update, according to your own scripted code.

You create a KosDelegate that takes no parameters, and returns a vector, which the system will automatically assign to the START suffix every update. Be aware that this system does eat into the instructions available per update, so if you make this delegate do too much work, it will slow down your script’s performance.

To make the system stop calling your delegate, set this suffix to the magic keyword DONOTHING.

Example:

// This example will bounce the arrow up and down over time for a few seconds,
// moving the location of the vector's start according to a sine wave over time:
set vd to vecdraw(v(0,0,0), ship:north:vector*5, green, "bouncing arrow", 1.0, true, 0.2).
print "Moving the arrow up and down for a few seconds.".
set vd:startupdater to { return ship:up:vector*3*sin(time:seconds*180). }.
wait 5.
print "Stopping the arrow movement.".
set vd:startupdater to DONOTHING.
wait 3.
print "Removing the arrow.".
set vd to 0.

New in version 1.1.0: scripted Delegate callbacks such as this did not exist prior to kOS version 1.1.0

VecDraw:VECUPDATER
Access:Get/Set
Type:KosDelegate with no parameters, returning a Vector

This allows you to tell the VecDraw that you’d like it to update the VEC suffix of the vector regularly every update, according to your own scripted code.

You create a KosDelegate that takes no parameters, and returns a vector, which the system will automatically assign to the VEC suffix every update. Be aware that this system does eat into the instructions available per update, so if you make this delegate do too much work, it will slow down your script’s performance.

To make the system stop calling your delegate, set this suffix to the magic keyword DONOTHING.

Example:

// This example will spin the arrow around in a circle by leaving the start
// where it is but moving the tip by trig functions:
set vd to vecdraw(v(0,0,0), v(5,0,0), green, "spinning arrow", 1.0, true, 0.2).
print "Moving the arrow in a circle for a few seconds.".
set vd:vecupdater to {
   return ship:up:vector*5*sin(time:seconds*180) + ship:north:vector*5*cos(time:seconds*180). }.
wait 5.
print "Stopping the arrow movement.".
set vd:vecupdater to DONOTHING.
wait 3.
print "Removing the arrow.".
set vd to 0.

New in version 1.1.0: scripted Delegate callbacks such as this did not exist prior to kOS version 1.1.0

VecDraw:VECTORUPDATER

This is just an alias for VecDraw:VECUPDATER.

VecDraw:COLORUPDATER
Access:Get/Set
Type:KosDelegate with no parameters, returning a Color

This allows you to tell the VecDraw that you’d like it to update the COLOR/COLOUR suffix of the vector regularly every update, according to your own scripted code.

You create a KosDelegate that takes no parameters, and returns a Color, which the system will automatically assign to the COLOR suffix every update. Be aware that this system does eat into the instructions available per update, so if you make this delegate do too much work, it will slow down your script’s performance.

To make the system stop calling your delegate, set this suffix to the magic keyword DONOTHING.

Example:

// This example will change how opaque the arrow is over time by changing
// the 'alpha' of its color:
set vd to vecdraw(v(0,0,0), ship:north:vector*5, green, "fading arrow", 1.0, true, 0.2).
print "Fading the arrow in and out for a few seconds.".
set vd:colorupdater to { return RGBA(0,1,0,sin(time:seconds*180)). }.
wait 5.
print "Stopping the color change.".
set vd:colorupdater to DONOTHING.
wait 3.
print "Removing the arrow.".
set vd to 0.

New in version 1.1.0: scripted Delegate callbacks such as this did not exist prior to kOS version 1.1.0

VecDraw:COLOURUPDATER

This is just an alias for VecDraw:COLORUPDATER.

Voice

This structure represents a single one of the ‘voices’ in the built-in sound chip called SKID in the kOS CPU. Please refer to the SKID chip documentation page if you have not already done so before reading this page. The things mentioned here are just ways to access the features of the SKID chip so they won’t be fully explained on this page, instead just referring to the SKID documentation with links.

Functions
GETVOICE(num)
Returns:Voice

To access one of the voices of the SKID chip, you use the GetVoice(num) built-in function.

Where num is the number of the hardware voice you’re interested in accessing. (The numbering starts with the first voice being called 0).

STOPALLVOICES()
Returns:None

This will stop all voices. If the voice is scheduled to play additional notes, they will not be played. If the voice in the middle of playing a note, that note will be stopped.

Each voice is capable of playing one note at a time, or a series of notes from a song (a List of Note‘s), but what matters is that one voice can’t play two notes at once. To do that you need to use multiple voices. For simple one-voice situations, you probably only need to ever use voice 0.

Structure
structure Voice
Members
Suffix Type Get/Set/Call Description
ATTACK scalar Get/Set The Attack setting of the SKID voice’s ADSR Envelope
DECAY scalar Get/Set The Decay setting of the SKID voice’s ADSR Envelope
SUSTAIN scalar Get/Set The Sustain setting of the SKID voice’s ADSR Envelope
RELEASE scalar Get/Set The Release setting of the SKID voice’s ADSR Envelope
VOLUME scalar Get/Set The default volume to play the notes on this voice.
WAVE string Get/Set The name for the waveform you want this voice to use.
PLAY(note_or_list) None Call The method that actually causes the voice to make some sound.
STOP() None Call Stop playing note on this voice instance.
LOOP boolean Get/Set Whether or not the voice should keep re-playing the song that was queued with PLAY().
ISPLAYING boolean Get/Set The playing status of voice.
TEMPO scalar Get/Set Stretches or shrinks the duration of the notes to speed up or slow down the song.
Voice:ATTACK
Access:Get/Set
Type:Scalar (seconds)

The Attack setting of the SKID voice’s ADSR Envelope. This value is in seconds (usually a fractional portion of a second).

Voice:DECAY
Access:Get/Set
Type:Scalar (seconds)

The Decay setting of the SKID voice’s ADSR Envelope. This value is in seconds (usually a fractional portion of a second).

Voice:SUSTAIN
Access:Get/Set
Type:Scalar in the range [0..1]

The Sustain setting of the SKID voice’s ADSR Envelope. Unlike the other values in the ASDR Envelope, this setting is NOT a measure of time. This is a coefficient to multiply the volume by during the sustain portion of the notes that are being played on this voice. (i.e. 0.5 would mean “sustain at half volume”).

Voice:RELEASE
Access:Get/Set
Type:Scalar (seconds)

The Release setting of the SKID voice’s ADSR Envelope. This value is in seconds (usually a fractional portion of a second). Note, that in order for this setting to have any real effect, the notes that are being played have to have their KeyDownLength set to be shorter than their Duration<Note:DURATION, otherwise the notes will still cut off before the Release has a chance to happen.

Voice:VOLUME
Access:Get/Set
Type:Scalar

The “peak” volume of the notes played on this voice, when they hit the top of their initial spike in the ADSR Envelope. While conceptually the max value is 1.0, in practice it can often go higher because the KSP game setting for User Interface volume is usually only at 50%, and in that scenario putting a 1.0 here would put the max at 50%, really. Setting this value to 0 will silence the voice.

Voice:WAVE
Access:Get/Set
Type:string

To select which of the SKID chip’s waveform generators you want this voice to use, set this to the string name of that waveform. If you use a string that isn’t one of the ones listed there (i.e. “triangle”, “noise”, “square”, etc) then the attempt to set this value will be ignored and it will remain at its previous value.

Voice:PLAY(note_or_list)
Access:

Call (method)

Parameters:
Returns:

None

To cause the SKID chip to actually emit a sound, you need to use this suffix method. There are two ways it can be called:

Play just one note : To play a single note, you can call PLAY(), passing it one note object. Usually you construct the note object on the fly as you call Play, like so:

SET V0 to GetVoice(0).
V0:PLAY(NOTE(440,0.5)).

Play a list of notes : To play a full list of notes (which could even encode an entire song), you can call PLAY, passing it a List of Note‘s. It will recognize that it is receiving a list of notes, and begin playing through them one at a time, only playing the next note when the previous note’s DURATION is finished:

SET V0 to GetVoice(0).
V0:PLAY(
    LIST(
        NOTE(440, 0.5),
        NOTE(400, 0.2),
        SLIDENOTE(410, 350, 0.3)
        )
    ).

Notes play in the background: In either case, whether playing a single note or a list of notes, the PLAY() method will return immediately, before even the first note has begun playing. It queues the note(s) to play, rather than waiting for them to finish. This lets your main program continue doing its work without waiting for the sound to finish.

Calling PLAY() again on the same voice aborts the previous PLAY(): Because the notes play in the background, it’s possible to execute another PLAY() call while a previous one hasn’t finished its work yet. If you do this, then the previous thing that was playing will quit, to be replaced by the new thing.

But PLAY() can be called simultaneously on different voices: (In fact that’s the whole point of having different voices.). Calling PLAY() again on a different voice number will not abort the previous call to PLAY(). It only aborts the previous PLAY() when it’s being done on the same voice.

Voice:STOP()
Access:Call (method)
Returns:None

Calling this method will tell the voice to stop playing notes. If there are any notes queued to be played, they will not be played. If a note is currently being played, that note will be stopped.

Voice:LOOP
Access:Get/Set
Type:boolean

If this is set to true, then the PLAY() method of this voice will keep on playing the same list of notes continually (starting over with the first note after the last note has finished). Note that for the purpose of this, a play command that was only given a single note to play still counts as a ‘song’ that is one note long (i.e. it will keep repeating the same note continually).

Voice:ISPLAYING
Access:Get/Set
Type:boolean

Get: If this voice is currently playing a note or list of notes that was previously passed in to the PLAY() method, then this returns true. Note that if LOOP is true, then this will never become false unless you set it to become false.

Set: If you set this value to FALSE, that will force the voice to stop playing whatever it was playing, and shut it up. (Setting it to true doesn’t really mean anything. It becomes true because the PLAY() method was called. You can’t restart a song just by setting this to true because when it becomes false, the voice “throws away” its memory of the song it was playing.)

Voice:TEMPO
Access:Get/Set
Type:scalar

When the voice is playing a Note or (more usefully) a List of Note‘s, it will stretch or shrink the durations of those notes by multiplying them by this scaling factor. At 1.0 (the default), that means that when a note says it lasts for 1 second, then it really does. But if this tempo was set to, say 1.5, then that would mean that each time a note claims it wants to play for 1 second, it would really end up playing for 1.5 seconds on this voice. (or if you set the tempo to 0.5, then all songs will play their notes at double speed (each note only lasting half as long as it “should”).)

In other words, setting this to a value less than 1.0 will speed up the song, and setting it to a value greater than 1.0 will slow it down (which might be the opposite of what you’d expect with it being called “tempo”, but what else should we have called it? “slowpo”?)

Changes to this value take effect as soon as the next note in the song starts. (You do not need to re-run the PLAY() method. It will change the speed in mid-song.)

Be aware that this only scales the timings of the Note‘s KEYDOWNLENGTH and DURATION timings. It does not affect the timings in the ADSR Envelope, as those represent what are meant to be physical properties of the “instrument” the voice is playing on. This means if you set the tempo too fast, it will start cutting off the full duration of the “envelope” of the notes, if you are playing the notes with settings that have a slow attack or decay.

Example Song

Below is a more complex full example that demonstrates the chip a bit more. Type it in (or cut and paste it) to see the system at work:

brakes on.
set song to list().
song:add(note("b4", 0.25, 0.20)). // Ma-
song:add(note("a4", 0.25, 0.20)). // -ry
song:add(note("g4", 0.25, 0.20)). // had
song:add(note("a4", 0.25, 0.20)). // a
song:add(note("b4", 0.25, 0.20)). // lit-
song:add(note("b4", 0.25, 0.20)). // -tle
song:add(note("b4", 0.5 , 0.45)). // lamb,
song:add(note("a4", 0.25, 0.20)). // lit-
song:add(note("a4", 0.25, 0.20)). // -tle
song:add(note("a4", 0.5 , 0.45)). // lamb
song:add(note("b4", 0.25, 0.20)). // lit-
song:add(note("b4", 0.25, 0.20)). // -tle
song:add(note("b4", 0.5 , 0.45)). // lamb

song:add(note("b4", 0.25, 0.20)). // Ma-
song:add(note("a4", 0.25, 0.20)). // -ry
song:add(note("g4", 0.25, 0.20)). // had
song:add(note("a4", 0.25, 0.20)). // a
song:add(note("b4", 0.25, 0.20)). // lit-
song:add(note("b4", 0.25, 0.20)). // -tle
song:add(note("b4", 0.25, 0.20)). // lamb,
song:add(note("b4", 0.25, 0.20)). // Its
song:add(note("a4", 0.25, 0.20)). // fleece
song:add(note("a4", 0.25, 0.20)). // was
song:add(note("b4", 0.25, 0.20)). // white
song:add(note("a4", 0.25, 0.20)). // as
song:add(note("g4", 1   , 0.95)). // snow

set v0 to getvoice(0).

set v0:attack to 0.0333. // take 1/30 th of a second to max volume.
set v0:decay to 0.02.  // take 1/50th second to drop back down to sustain.
set v0:sustain to 0.80. // sustain at 80% of max vol.
set v0:release to 0.05. // takes 1/20th of a second to fall to zero volume at the end.

for wavename in LIST("square", "triangle", "sawtooth", "sine") { // Let's not do "noise" - it sounds dumb for music
  set v0:wave to wavename.
  v0:play(song).
  print "Playing song in waveform : " + wavename.
  wait until not v0:isplaying.
  wait 1.
}

Waypoints

Waypoints are the location markers you can see on the map view showing you where contracts are targeted for. With this structure, you can obtain coordinate data for the locations of these waypoints.

WAYPOINT(name)
Parameters:
  • name – (string) Name of the waypoint as it appears on the map or in the contract description
Returns:

Waypoint

This creates a new Waypoint from a name of a waypoint you read from the contract paramters. Note that this only works on contracts you’ve accpted. Waypoints for proposed contracts haven’t accepted yet do not actually work in kOS.

SET spot TO WAYPOINT(“herman’s folly beta”).

The name match is case-insensitive.

ALLWAYPOINTS()
Returns:List of Waypoint

This creates a List of Waypoint structures for all accepted contracts. Waypoints for proposed contracts you haven’t accepted yet do not appear in the list.

structure Waypoint
Members
Suffix Type
NAME string
BODY BodyTarget
GEOPOSITION GeoCoordinates
POSITION Vector
ALTITUDE scalar
AGL scalar
NEARSURFACE boolean
GROUNDED boolean
INDEX scalar
CLUSTERED boolean
Waypoint:NAME
Type:string
Access:Get only

Name of waypoint as it appears on the map and contract

Waypoint:BODY
Type:BodyTarget
Access:Get only

Celestial body the waypoint is attached to

Waypoint:GEOPOSITION
Type:GeoCoordinates
Access:Get only

The LATLNG of this waypoint

Waypoint:POSITION
Type:Vector
Access:Get only

The Vector position of this waypoint in 3D space, in ship-raw coords.

Waypoint:ALTITUDE
Type:scalar
Access:Get only

Altitude of waypoint above “sea” level. Warning, this a point somewhere in the midst of the contract altitude range, not the edge of the altitude range. It corresponds towhere the marker tip hovers on the map, which is not actually at the very edge of the contract condition’s range. It represents a typical midling location inside the contract’s altitude range.

Waypoint:AGL
Type:scalar
Access:Get only

Altitude of waypoint above ground. Warning, this a point somewhere in the midst of the contract altitude range, not the edge of the altitude range. It corresponds to where the marker tip hovers on the map, which is not actually at the very edge of the contract condition’s range. It represents a typical midling location inside the contract’s altitude range.

Waypoint:NEARSURFACE
Type:boolean
Access:Get only

True if waypoint is a point near or on the body rather than high in orbit.

Waypoint:GROUNDED
Type:boolean
Access:Get only

True if waypoint is actually glued to the ground.

Waypoint:INDEX
Type:scalar
Access:Get only

The integer index of this waypoint amongst its cluster of sibling waypoints. In other words, when you have a cluster of waypoints called “Somewhere Alpha”, “Somewhere Beta”, and “Somewhere Gamma”, then the alpha site has index 0, the beta site has index 1 and the gamma site has index 2. When Waypoint:CLUSTERED is false, this value is zero but meaningless.

Waypoint:CLUSTERED
Type:boolean
Access:Get only

True if this waypoint is part of a set of clustered waypoints with greek letter names appended (Alpha, Beta, Gamma, etc). If true, there should be a one-to-one correspondence with the greek letter name and the :INDEX suffix. (0 = Alpha, 1 = Beta, 2 = Gamma, etc).

Addon Reference

This section is for ways in which kOS has special case exceptions to its normal generic behaviours, in order to accommodate other KSP mods. If you don’t use any of KSP mods mentioned, you don’t need to read this section.

Action Groups Extended

Increase the action groups available to kOS from 10 to 250. Also adds the ability to edit actions in flight as well as the ability to name action groups so you can describe what a group does.

Includes a Script Trigger action that can be used to control a running program and visual feedback if an action group is currently activated.

Usage: Adds action groups AG11 through AG250 to kOS that are interacted with the same way as the AG1 through AG10 bindings in base kOS are.

Anywhere you use AG1, you can use AG15 in the same way. (AG11 through AG250 explicitly behave the same as the 10 stock groups. Please file a bug report if they do not.)

Script Trigger action: Installing AGX adds the “Script Trigger” action to all kOS computer parts. This action is a null action that does not activate anything but serves as a placeholder to enhance action groups in kOS.

When an action group has the Script Trigger action assigned, on that action group you can now:

  • Name the action group so you remember what that action group does in your code when you trigger it.
  • Activate the action group with a mouse click on-screen, no more tying up your entire keyboard with various script trigger keys.
  • Enable group state feedback so you can have your script change the groups state as feedback as to what the script is doing. Green being On and Red being Off. (Toggle option in AGX.)

Basic Quick Start:

_images/AGExtQuickStart1.jpg
_images/AGExtQuickStart2.jpg

Note that this mod only adds action groups 11 through 250, it does not change how action groups 1 through 10 behave in any way and groups 11 through 250 should behave the same way.

Known limitations (Action groups 11 through 250 only):

  • On a nearby vessel that is not your current focus, an action group with no actions assigned will always return a state of False and can not be set to a state of true via the “AG15 on.” command. Assign the Script Trigger action as a work-around for this.
  • At this point, AG11 through AG250 do not officially support RemoteTech through kOS. (Support will happen once all three mods involved have updated to KSP version 1.0 and made any internal changes necessary.) All three mods can be installed at the same time without issue, just be aware there may be unexpected behaviour when using action groups 11 through 250 from a kOS script in terms of RemoteTech signal delay and connection state.

Action state monitoring

Note that the state of action groups is tracked on a per-action basis, rather then on a per-group basis. This results in the group state being handled differently.

  • The Script Trigger action found on the kOS computer module is not subject to the below considerations and is the recommended action to use when interacting with a running kOS script.
  • The state of actions are monitored on the part and updated automatically. A closed solar panel will return a state of false for all it’s actions. (Extend Panels, Retract Panels, Toggle Panels) When you extend the solar panel with either the Extend Panels or Toggle Panels action, all three actions will change to a state of True. Retract the panels and the state of all three actions will become False. Note that this state will update in any action group that contains that action, not just the action group that was activated.
  • This can result in an action group have actions in a mixed state where some actions are on and some are off. In this case querying the group state will result in a state of False. For the purposes of the group state being True or False, if all actions in the action group are true, the group state will return true. If any actions in the group are false,the group state with return False.
  • When an action triggers an animation, the state of the action will be uncertain until the animation finishes playing. Some parts will report True during the animation and some will report False. It depends on how the part creator set things up and not something AGX can control.
  • For clarity, visual feedback can be provided of the current state of an action group. When editing action groups, find the “Toggle Grp.” button just below the text entry field for the group name in the main AGX window and enable it. (It is enabled/disabled for the current action group when you click the button.) Once you do this, the text displaying that group will change from gray to colored. Green: Group is activated (state True). Red: Group is deactivated (state False). Yellow: Group is in a mixed state, will return a state False when queried.
  • It is okay to activate an already activated group and deactivate a non-activated group. Actions in the group will still try to execute as normal. Exact behaviour of a specific action will depend on how the action’s creator set things up.

Example code:

Print to the terminal any time you activate action group 15. Use this to change variables within a running kOS script and the “Script Trigger” action found on the kOS computer part:

AG15 on. // Activate action group 15.
print AG15. // Print action group 15's state to the terminal. (True/False)

on AG15 { //Prints "Action group 15 clicked!" to the console when AG15 is toggled, either via "AG15 on." or in-game with an assigned key.
  print "Action group 15 clicked!".
  preserve.
}

Animation Delay:

  • Using the above code on a stock solar panel’s Toggle Panels action, the player activates AG15, AG15’s state goes from false to true and the actions are triggered. AG15 False -> True and prints to the terminal.
  • On it’s next update pass (100ms to 250ms later), AGX checks AG15’s state and sees the solar panel is still deploying which means that AG15’s state is false and so sets it that way. AG15 True -> False and prints to the terminal.
  • A few seconds later, the solar panel finishes it’s deployment animation. On it’s next update pass AGX checks AG15’s state and sees the solar panel is now deployed which means that AG15’s state is now true and so sets it that way. AG15 False -> True and prints to the terminal a third time.

As a workaround, you need to add a cooldown:

declare cooldownTimeAG15 to 0.
on AG15 {
  if cooldownTimeAG15 + 10 < time:seconds {
    print "Solar Panel Toggled!".
    set cooldownTimeAG15 to time.
  }
  preserve.
}

Note the 10 in the second line, that is your cooldown time in seconds. Set this to a number of seconds that is longer then your animation time and the above code will limit whatever is inside the IF statement so it can only activate after 10 seconds have passed since the previous activation and will not try to activate a second time while the solar panel animation is still playing.

RemoteTech

RemoteTech is a modification for Squad’s “Kerbal Space Program” (KSP) which overhauls the unmanned space program. It does this by requiring unmanned vessels have a connection to Kerbal Space Center (KSC) to be able to be controlled. This adds a new layer of difficulty that compensates for the lack of live crew members.

You can find out if the RemoteTech addon is available in the current game installation by usng the boolean expression addons:available("RT").

Interaction with kOS

Note

New in version v1.0.2: kOS now supports access to connection informaion from a unified location. See Connectivity Managers for more information. All of the previous implementation as detailed on this page remains supported.

When you have RemoteTech installed you can only interact with the core’s terminal when you have a connection to KSC on any unmanned craft. Scripts launched when you still had a connection will continue to execute even if your unmanned craft loses connection to KSC. But you should note, that when there is no connection to KSC the archive volume is inaccessible. This will require you to plan ahead and copy necessary scripts for your mission to probe hard disk, if your kerbals and/or other scripts need to use them while not connected.

If you launch a manned craft while using RemoteTech, you are still able to input commands from the terminal even if you do not have a connection to the KSC. The archive will still be inaccessible without a connection to the KSC. Under the current implementation, there is no delay when accessing the archive with a local terminal. This implementation may change in the future to account for delays in reading and writing data over the connection.

Remote Tech and the kOS GUI widgets

New in version v1.1.0.

The kOS GUI widget system tries to obey the signal delay imposed by RemoteTech, when Remote Tech is installed. A user’s interaction with GUI widgets on the screen will be subject to the same signal delay rules as the interactive flying controls of the ship.

Antennas

It is possible to activate/deactivate RT antennas, as well as set their targets using kOS:

SET P TO SHIP:PARTSNAMED("mediumDishAntenna")[0].
SET M to p:GETMODULE("ModuleRTAntenna").
M:DOEVENT("activate").
M:SETFIELD("target", "Mission Control").
M:SETFIELD("target", mun).
M:SETFIELD("target", somevessel).
M:SETFIELD("target", "minmus").

Acceptable values for “target” are:

  • “no-target”
  • “active-vessel”
  • a Body
  • a Vessel
  • a string containing the name of a body or vessel
  • a string containing the name of a ground station (case-sensitive)

You can use RTADDON:GROUNDSTATIONS to get a list of all ground stations. The default ground station is called “Mission Control”.

Communication

When installed RemoteTech will influence communication between vessels. In order to send a message to another vessel a valid RemoteTech connection will have to exist between them and of course messages will arrive to their destination with a proper delay. Documentation of Connection class contains further information on how RemoteTech will change the behaviour of some of its suffixes.

RTAddon

Starting version 0.17 of kOS you can access structure RTAddon via ADDONS:RT.

structure RTAddon
Suffix Type Description
AVAILABLE Boolean (readonly) True if RT is installed and RT integration enabled. It is better to use addons:available("IR") for this.
DELAY(vessel) Scalar Get shortest possible delay to given Vessel
KSCDELAY(vessel) Boolean Get delay from KSC to given Vessel
ANTENNAHASCONNECTION(part) Boolean True if given Part has any connection
HASCONNECTION(vessel) Boolean True if given Vessel has any connection
HASKSCCONNECTION(vessel) Boolean True if given Vessel has connection to KSC
HASLOCALCONTROL(vessel) Boolean True if given Vessel has local control
GROUNDSTATIONS() List of String Get names of all ground stations
RTADDON:AVAILABLE
Type:Boolean
Access:Get only

True if RT is installed and RT integration enabled.

It is better to use ADDONS:AVAILABLE("RT") first to discover if RemoteTech is installed.

RTAddon:DELAY(vessel)
Parameters:
Returns:

(Scalar) seconds

Returns shortest possible delay for vessel (Will be less than KSC delay if you have a local command post).

RTAddon:KSCDELAY(vessel)
Parameters:
Returns:

(Scalar) seconds

Returns delay in seconds from KSC to vessel.

RTAddon:ANTENNAHASCONNECTION(part)
Parameters:
Returns:

Boolean

Returns True if part has any connection (including to local command posts).

RTAddon:HASCONNECTION(vessel)
Parameters:
Returns:

Boolean

Returns True if vessel has any connection (including to local command posts).

RTAddon:HASKSCCONNECTION(vessel)
Parameters:
Returns:

Boolean

Returns True if vessel has connection to KSC.

RTAddon:HASLOCALCONTROL(vessel)
Parameters:
Returns:

Boolean

Returns True if vessel has local control (and thus not requiring a RemoteTech connection).

RTAddon:GROUNDSTATIONS()
Returns:List of String

Returns names of all RT ground stations

Kerbal Alarm Clock

You can find out if Kerbal Alarm Clock addon is available in the current game installation by usng the boolean expression addons:available("KAC").

The Kerbal Alarm Clock is a plugin that allows you to create reminder alarms at future periods to help you manage your flights and not warp past important times.

http://triggerau.github.io/KerbalAlarmClock/images/KACForumPic.png

Creator of the KAC provides API for integration with other mods. In KOS we provide limited access to KAC alarms via following structure and functions.

Access structure KACAddon via ADDONS:KAC.

structure KACAddon
Suffix Type Description
AVAILABLE bool(readonly) True if KAC is installed and KAC integration enabled. It is better to use addons:available("KAC") for this purpose.
ALARMS() List List all alarms
KACAddon:AVAILABLE
Type:bool
Access:Get only

It is better to use ADDONS:AVAILABLE("KAC") first to discover if KAC is installed.

True if KAC is installed and KAC integration enabled. Example of use:

if ADDONS:KAC:AVAILABLE
{
    //some KAC dependent code
}
KACAddon:ALARMS()
Returns:List of KACAlarm objects

List all the alarms set up in Kerbal Alarm Clock. Example of use:

for i in ADDONS:KAC:ALARMS
{
        print i:NAME + " - " + i:REMAINING + " - " + i:TYPE+ " - " + i:ACTION.
}
structure KACAlarm
Suffix Type Description
ID string (readonly) Unique identifier
NAME string Name of the alarm
ACTION string What should the Alarm Clock do when the alarm fires
TYPE string (readonly) What type of Alarm is this - affects icon displayed and some calc options
NOTES string Long description of the alarm (optional)
REMAINING scalar (s) Time remaining until alarm is triggered
REPEAT boolean Should the alarm be repeated once it fires
REPEATPERIOD scalar (s) How long after the alarm fires should the next alarm be set up
ORIGINBODY string Name of the body the vessel is departing from
TARGETBODY string Name of the body the vessel is arriving at
KACAlarm:ID
Type:string
Access:Get only

Unique identifier of the alarm.

KACAlarm:NAME
Type:string
Access:Get/Set

Name of the alarm. Displayed in main KAC window.

KACAlarm:ACTION
Type:string
Access:Get/Set

Should be one of the following

  • MessageOnly - Message Only-No Affect on warp
  • KillWarpOnly - Kill Warp Only-No Message
  • KillWarp - Kill Warp and Message
  • PauseGame - Pause Game and Message

If set incorrectly will log a warning in Debug log and revert to previous or default value.

KACAlarm:TYPE
Type:string
Access:Get only

Can only be set at Alarm creation. Could be one of the following as per API

  • Raw (default)
  • Maneuver
  • ManeuverAuto
  • Apoapsis
  • Periapsis
  • AscendingNode
  • DescendingNode
  • LaunchRendevous
  • Closest
  • SOIChange
  • SOIChangeAuto
  • Transfer
  • TransferModelled
  • Distance
  • Crew
  • EarthTime

Warning: Unless you are 100% certain you know what you’re doing, create only “Raw” AlarmTypes to avoid unnecessary complications.

KACAlarm:NOTES
Type:string
Access:Get/Set

Long description of the alarm. Can be seen when alarm pops or by double-clicking alarm in UI.

Warning: This field may be reserved in the future version of KAC-KOS integration for automated script execution upon triggering of the alarm.

KACAlarm:REMAINING
Type:scalar
Access:Get only

Time remaining until alarm is triggered.

KACAlarm:REPEAT
Type:boolean
Access:Get/Set

Should the alarm be repeated once it fires.

KACAlarm:REPEATPERIOD
Type:scalar
Access:Get/Set

How long after the alarm fires should the next alarm be set up.

KACAlarm:ORIGINBODY
Type:string
Access:Get/Set

Name of the body the vessel is departing from.

KACAlarm:TARGETBODY
Type:string
Access:Get/Set

Name of the body the vessel is arriving to.

Available Functions

Function Description
ADDALARM(AlarmType, UT, Name, Notes) Create new alarm of AlarmType at UT
LISTALARMS(alarmType) List alarms with type alarmType.
DELETEALARM(alarmID) Delete alarm with ID = alarmID
ADDALARM(AlarmType, UT, Name, Notes)

Creates alarm of type KACAlarm:ALARMTYPE at UT with Name and Notes attributes set. Attaches alarm to current CPU Vessel. Returns KACAlarm object if creation was successful and empty string otherwise:

set na to addAlarm("Raw",time:seconds+300, "Test", "Notes").
print na:NAME. //prints 'Test'
set na:NOTES to "New Description".
print na:NOTES. //prints 'New Description'
LISTALARMS(alarmType)

If alarmType equals “All”, returns List of all KACAlarm objects attached to current vessel or have no vessel attached. Otherwise returns List of all KACAlarm objects with KACAlarm:TYPE equeal to alarmType and attached to current vessel or have no vessel attached.:

set al to listAlarms("All").
for i in al
{
    print i:ID + " - " + i:name.
}
DELETEALARM(alarmID)

Deletes alarm with ID equal to alarmID. Returns True if successful, false otherwise:

set na to addAlarm("Raw",time:seconds+300, "Test", "Notes").
if (DELETEALARM(na:ID))
{
    print "Alarm Deleted".
}

Infernal Robotics

Infernal Robotics might not be installed on your copy of the game. Your script can test whether or not it’s installed by using the boolean expression addons:available("ir").

Infernal Robotics introduces robotics parts to the game, letting you create moving or spinning contraptions that just aren’t possible under stock KSP.

http://i.imgur.com/O94LBvF.png

Starting version 0.20 of the Infernal Robotics, mod creators introduced API to for easier access to robotic features.

Access structure IRAddon via ADDONS:IR.

structure IRAddon
Suffix Type Description
AVAILABLE boolean (readonly) Returns True if mod Infernal Robotics is installed, available to KOS and applicable to current craft. It is better to use addons:available("rt").
GROUPS List of IRControlGroup Lists all Servo Groups for the Vessel on which CPU runs this command (see details below).
ALLSERVOS List of IRServo Lists all Servos for the Vessel on which CPU runs this command (see details below).
PARTSERVOS(Part) List of IRServo Lists all Servos for the provided part
IRAddon:AVAILABLE
Type:Boolean
Access:Get only

It is better to first call ADDONS:AVAILABLE("IR") to find out if the plugin exists.

Returns True if mod Infernal Robotics is installed, available to KOS and applicable to current craft. Example of use:

if ADDONS:IR:AVAILABLE
{
    //some IR dependent code
}
IRAddon:GROUPS
Type:List of IRControlGroup objects
Access:Get only

Lists all Servo Groups for the Vessel on which the script is being executed. On IR versions prior to 0.21.5 will always return servo groups for current focused vessel. Example of use:

for g in ADDONS:IR:GROUPS
{
    Print g:NAME + " contains " + g:SERVOS:LENGTH + " servos".
}
IRAddon:ALLSERVOS
Type:List of IRServo objects
Access:Get only

Lists all Servos for the Vessel on which the script is being executed. On IR versions prior to 0.21.5 will always return servos for current focused vessel. Example of use:

for s in ADDONS:IR:ALLSERVOS
{
    print "Name: " + s:NAME + ", position: " + s:POSITION.
}
IRAddon:PARTSERVOS(part)
Parameters:
  • partPart for which to return servos
Return type:

List of IRServo objects

Lists all Servos found on the given Part.

structure IRControlGroup
Suffix Type Description
NAME string Name of the Control Group
SPEED scalar Speed multiplier set in the IR UI
EXPANDED Boolean True if Group is expanded in IR UI
FORWARDKEY string Key assigned to forward movement
REVERSEKEY string Key assigned to reverse movement
SERVOS List (readonly) List of servos in the group
VESSEL Vessel Vessel object, owning this servo group. Readonly, requires IR version 0.21.5 or later.
MOVERIGHT() void Commands servos in the group to move in positive direction
MOVELEFT() void Commands servos in the group to move in negative direction
MOVECENTER() void Commands servos in the group to move to default position
MOVENEXTPRESET() void Commands servos in the group to move to next preset
MOVEPREVPRESET() void Commands servos in the group to move to previous preset
STOP() void Commands servos in the group to stop
IRControlGroup:NAME
Type:string
Access:Get/Set

Name of the Control Group (cannot be empty).

IRControlGroup:SPEED
Type:scalar
Access:Get/Set

Speed multiplier as set in the IR user interface. Avoid setting it to 0.

IRControlGroup:EXPANDED
Type:Boolean
Access:Get/Set

True if Group is expanded in IR UI

IRControlGroup:FORWARDKEY
Type:string
Access:Get/Set

Key assigned to forward movement. Can be empty.

IRControlGroup:REVERSEKEY
Type:string
Access:Get/Set

Key assigned to reverse movement. Can be empty.

IRControlGroup:SERVOS
Type:List of IRServo objects
Access:Get only

Lists Servos in the Group. Example of use:

for g in ADDONS:IR:GROUPS
{
    Print g:NAME + " contains " + g:SERVOS:LENGTH + " servos:".
    for s in g:servos
    {
        print "    " + s:NAME + ", position: " + s:POSITION.
    }
}
IRControlGroup:VESSEL
Type:Vessel
Access:Get only

If IR 0.21.5 or later is installed will return a Vessel that owns this ServoGroup, otherwise will return current focused Vessel

IRControlGroup:MOVERIGHT()
Returns:void

Commands servos in the group to move in positive direction.

IRControlGroup:MOVELEFT()
Returns:void

Commands servos in the group to move in negative direction.

IRControlGroup:MOVECENTER()
Returns:void

Commands servos in the group to move to default position.

IRControlGroup:MOVENEXTPRESET()
Returns:void

Commands servos in the group to move to next preset

IRControlGroup:MOVEPREVPRESET()
Returns:void

Commands servos in the group to move to previous preset

IRControlGroup:STOP()
Returns:void

Commands servos in the group to stop

structure IRServo
Suffix Type Description
NAME string Name of the Servo
UID scalar (int) Unique ID of the servo part (part.flightID).
HIGHLIGHT Boolean (set-only) Set Hightlight status of the part.
POSITION scalar (readonly) Current position of the servo.
MINCFGPOSITION scalar (readonly) Minimum position for servo as defined by part creator in part.cfg
MAXCFGPOSITION scalar (readonly) Maximum position for servo as defined by part creator in part.cfg
MINPOSITION scalar Minimum position for servo, from tweakable.
MAXPOSITION scalar Maximum position for servo, from tweakable.
CONFIGSPEED scalar (readonly) Servo movement speed as defined by part creator in part.cfg
SPEED scalar Servo speed multiplier, from tweakable.
CURRENTSPEED scalar (readonly) Current Servo speed.
ACCELERATION scalar Servo acceleration multiplier, from tweakable.
ISMOVING Boolean (readonly) True if Servo is moving
ISFREEMOVING Boolean (readonly) True if Servo is uncontrollable (ex. docking washer)
LOCKED Boolean Servo’s locked status, set true to lock servo.
INVERTED Boolean Servo’s inverted status, set true to invert servo’s axis.
PART Part A reference to a Part containing servo module.
MOVERIGHT() void Commands servo to move in positive direction
MOVELEFT() void Commands servo to move in negative direction
MOVECENTER() void Commands servo to move to default position
MOVENEXTPRESET() void Commands servo to move to next preset
MOVEPREVPRESET() void Commands servo to move to previous preset
STOP() void Commands servo to stop
MOVETO(position, speedMult) void Commands servo to move to position with speedMult multiplier
IRServo:NAME
Type:string
Access:Get/Set

Name of the Control Group (cannot be empty).

IRServo:UID
Type:scalar
Access:Get

Unique ID of the servo part (part.flightID).

IRServo:HIGHLIGHT
Type:Boolean
Access:Set

Set Hightlight status of the part.

IRServo:POSITION
Type:scalar
Access:Get

Current position of the servo.

IRServo:MINCFGPOSITION
Type:scalar
Access:Get

Minimum position for servo as defined by part creator in part.cfg

IRServo:MAXCFGPOSITION
Type:scalar
Access:Get

Maximum position for servo as defined by part creator in part.cfg

IRServo:MINPOSITION
Type:scalar
Access:Get/Set

Minimum position for servo, from tweakable.

IRServo:MAXPOSITION
Type:scalar
Access:Get/Set

Maximum position for servo, from tweakable.

IRServo:CONFIGSPEED
Type:scalar
Access:Get

Servo movement speed as defined by part creator in part.cfg

IRServo:SPEED
Type:scalar
Access:Get/Set

Servo speed multiplier, from tweakable.

IRServo:CURRENTSPEED
Type:scalar
Access:Get

Current Servo speed.

IRServo:ACCELERATION
Type:scalar
Access:Get/Set

Servo acceleration multiplier, from tweakable.

IRServo:ISMOVING
Type:Boolean
Access:Get

True if Servo is moving

IRServo:ISFREEMOVING
Type:Boolean
Access:Get

True if Servo is uncontrollable (ex. docking washer)

IRServo:LOCKED
Type:Boolean
Access:Get/Set

Servo’s locked status, set true to lock servo.

IRServo:INVERTED
Type:Boolean
Access:Get/Set

Servo’s inverted status, set true to invert servo’s axis.

IRServo:PART
Type:Part
Access:Get

Returns reference to the Part containing servo module. Please note that Part:UID does not equal IRServo:UID.

IRServo:MOVERIGHT()
Returns:void

Commands servo to move in positive direction

IRServo:MOVELEFT()
Returns:void

Commands servo to move in negative direction

IRServo:MOVECENTER()
Returns:void

Commands servo to move to default position

IRServo:MOVENEXTPRESET()
Returns:void

Commands servo to move to next preset

IRServo:MOVEPREVPRESET()
Returns:void

Commands servo to move to previous preset

IRServo:STOP()
Returns:void

Commands servo to stop

IRServo:MOVETO(position, speedMult)
Parameters:
  • position – (float) Position to move to
  • speedMult – (float) Speed multiplier
Returns:

void

Commands servo to move to position with speedMult multiplier.

Example code:

print "IR Iavailable: " + ADDONS:IR:AVAILABLE.

Print "Groups:".

for g in ADDONS:IR:GROUPS
{
    Print g:NAME + " contains " + g:SERVOS:LENGTH + " servos:".
    for s in g:servos
    {
        print "    " + s:NAME + ", position: " + s:POSITION.
        if (g:NAME = "Hinges" and s:POSITION = 0)
        {
            s:MOVETO(30, 2).
        }
        else if (g:NAME = "Hinges" and s:POSITION > 0)
        {
            s:MOVETO(0, 1).
        }
    }
}

DMagic Orbital Science

DMagic Orbital Science is a modification for Squad’s “Kerbal Space Program” (KSP) which adds extra science experiments to the game. Those experiments under the hood work differently than stock ones and require dedicated support (see ScienceExperimentModule).

Most of the time Orbital Science experiments should work exactly like stock ones, they inherit all suffixes from ScienceExperimentModule:

SET P TO SHIP:PARTSTAGGED("")[0].
SET M TO P:GETMODULE("dmmodulescienceanimate").

PRINT M:RERUNNABLE.
PRINT M:INOPERABLE.
M:DEPLOY.
WAIT UNTIL M:HASDATA.
M:TRANSMIT.

All Orbital Science experiments do get an extra TOGGLE suffix that activates and deactivates them:

SET P TO SHIP:PARTSTAGGED("collector")[0].
SET M TO P:GETMODULE("dmsolarcollector").

M:TOGGLE.

Submersible Oceanography and Bathymetry has two extra suffixes that turn the experiment’s lights on and off:

SET P TO SHIP:PARTSTAGGED("bathymetry")[0].
SET M TO P:GETMODULE("dmbathymetry").

M:LIGHTSON.
WAIT 3.
M:LIGHTSOFF.

Trajectories

Trajectories is a mod that displays trajectory predictions, accounting for atmospheric drag, lift, etc.. See the forum thread for more details.

This addon is not associated with and not supported by the creator of Trajectories.

The Trajectories API is accessed throgh C# reflection, and is designed for the current version. This means that future Trajectories updates may break this addon, in which case ADDONS:TR:AVAILABLE will return false. It is also possible for future versions of Trjectories to remain fully compatible.

Note

Trajectories only predicts the trajectory of the “Active Vessel,” which is the vessel with the camera focused on it. IMPACTPOS, PLANNEDVEC, SETTARGET, and CORRECTEDVEC will throw exceptions if you try to call them from an inactive vessel or if Trajectories has not calculated an impact position. You should always check if HASIMPACT is true before accessing these suffixes.

For example:

if ADDONS:TR:AVAILABLE {
    if ADDONS:TR:HASIMPACT {
        PRINT ADDONS:TR:IMPACTPOS.
    } else {
        PRINT "Impact position is not available".
    }
} else {
    PRINT "Trajectories is not available.".
}

Trajectories does its calculation based on the vessel’s current orientation. Any tiny change in orientation will change the prediction.

Accuracy is not guaranteed.

See this repository for an example of this addon being used to land a rocket on the launch pad: https://github.com/CalebJ2/kOS-landing-script

Access structure TRAddon via ADDONS:TR.

structure TRAddon
Suffix Type Description
AVAILABLE Boolean (readonly) True if a compatible Trajectories version is installed.
HASIMPACT Boolean (readonly) True if Trajectories has calculated an impact position for the current vessel.
IMPACTPOS GeoCoordinates (readonly) Returns a GeoCoordinates with the predicted impact position.
PLANNEDVEC Vector (readonly) Vector at which to point to follow predicted trajectory.
PLANNEDVECTOR Vector (readonly) Alias for PLANNEDVEC
SETTARGET(position) None Set Trajectories target.
CORRECTEDVEC Vector (readonly) Offset plus PLANNEDVEC to correct path for targeted impact.
CORRECTEDVECTOR Vector (readonly) Alias for CORRECTEDVEC
TRAddon:AVAILABLE
Type:Boolean
Access:Get

True if a compatible Trajectories version is installed.

TRAddon:HASIMPACT
Type:Boolean
Access:Get

True if Trajectories has calculated an impact position for the current Vessel. You should always check this before using impactPos, plannedVect, setTarget, or correctedVect to avoid exceptions.

TRAddon:IMPACTPOS
Type:GeoCoordinates
Access:Get

Estimated impact position.

TRAddon:PLANNEDVEC
Type:Vector
Access:Get

Vector pointing the direction your vessel should face to follow the predicted trajectory, based on the angle of attack selected in the Trajectories user interface.

TRAddon:PLANNEDVECTOR
Type:Vector
Access:Get

Alias for PLANNEDVEC

TRAddon:SETTARGET(position)
Parameters:
Returns:

None

Sets the Trajectories target landing position to the given position.

TRAddon:CORRECTEDVEC
Type:Vector
Access:Get

A vector that applies an offset to PLANNEDVEC intended to correct the predicted trajectory to impact at the selected target position. This vector does not use any aerodynamic prediction and is a very simplistic representation. Accuracy is not guaranteed, but it should at least help determine if you need to pitch the nose up or down.

TRAddon:CORRECTEDVECTOR
Type:Vector
Access:Get

Alias for CORRECTEDVEC

To help KOS scripts identify whether or not certain mod is installed and available following suffixed functions were introduced in version 0.17

ADDONS:AVAILABLE("AGX")

Returns True if mod Action Group Extended is installed and available to KOS.

ADDONS:AVAILABLE("RT")

Returns True if mod RemoteTech is installed and available to KOS. See more RemoteTech functions here.

ADDONS:AVAILABLE("KAC")

Returns True if mod Kerbal Alarm Clock is installed and available to KOS.

ADDONS:AVAILABLE("IR")

Returns True if mod Infernal Robotics is installed, available to KOS and applicable to current craft. See more here.

ADDONS:TR:AVAILABLE

Returns True if a compatible version of the mod Trajectories is installed. See more here.

Contribute

How to Contribute to this Project

Do you know or are willing to learn C# and the KSP public API? Great, we could use your help! The source code for kOS is kept on github under http://github.io/KSP-KOS/KOS.

If you are already quite familiar with git and Github, the usual Github project development path is used:

  • Tell github to fork the main repository to your own github clone of it.
  • Clone your fork to your local computer.
  • On your local computer, make a branch from develop (don’t edit develop directly) and make your changes in your branch.
  • Commit your changes and push them up to the same branch name on your github fork.
  • Make a Pull Request on Github to merge the branch from your fork to the develop branch of the main repository.
  • Wait for a developer to notice the Pull Request and start examining it. There should be at the very least a comment letting you know it’s being looked at, within a short time. KSP-KOS is quite actively developed and someone should notice it soon.
  • Your request is more likely to get merged quickly if you make sure the develop branch you start from is always up to date with the latest upstream develop when you first split your branch from it. If it takes a long time to finish, it may be a good idea to check again before making the Pull Request to see if there’s been any new upstream develop changes, and merge them into your branch yourself so the rest of the team has an easier time deciphering the git diff output.

If you do know how to program on large projects and would like to contribute, but just aren’t familiar with how git and Github do repository management, contact one of the developers and ask for help on how to get started, or ask to be added to the Slack channel first.

Slack Chat

There is an active Slack chat channel where the developers often discuss complex ideas before even mentioning them in a github issue or pull request. If you wish to be added to this channel, please contact one of the main developers to ask to be invited to the channel.

How to get credited in the next Release

After version 0.19.0, Only people who opt-in to being credited will be mentioned in the release notes.

When you contribute to the development of the mod, if you wish to be named a certain way in the next release notes, then add your edit to the ### Contributors section of the CHANGELOG.md file in your pull request. In past releases we have tried to scour the github history to find all authors and it’s a bit of a pain to pull the data together. In future releases we will simply rely on this opt-in technique. If you don’t edit the file, you won’t be opted-in to the contributors section. This also avoids the hassle of having to ask everyone’s permission in the last days of putting a release out, and then waiting for people’s responses.

How to Edit this Documentation

This documentation was written using reStructuredText and compiled into HTML using Sphinx and the Read The Docs Theme.

To re-build the documentation tree locally, get a local clone of the project, cd into the doc/ directory, and do these two commands:

make clean
make html

Note, this requires you set up Sphinx and Read-the-Docs first, as described in the links above.

This documentation system was first set up for us by Johann Goetz, to whom we are grateful:

Changes from version to version

This is a slightly more verbose version of the new features mentioned in the CHANGELOG, specifically for new features and for users familiar with older versions of the documentation who want only a quick update to the docs without reading the entire set of documentation again from scratch.

This list is NOT a comprehensive list of everything. Specifically, minor one-line changes, or bug fixes, are not mentioned here.

Most importantly, changes that might have broken previously working scripts are not always signposted here. To be sure, you should read the change log in the main github repository, which is repeated in the release announcements that are made in various places with each release.


Changes in 1.1.0

GUI

The GUI system was added new with version 1.1.0.

Terminal Font

Now that the terminal can display any font from your OS, you can now display any Unicode character you like.

Regex Part Searches

You may now use Vessel:PARTSTAGGEDPATTERN to perform regular expression searches for part tags.

Triggers take locals

The previous restriction that triggers such as WHEN and ON must only use global variables in their check expressions has been removed. Now they can use local variables and will remember their closures.

LATLNG of other body

New suffix Body:GEOPOSITIONLATLNG lets you get a LATLNG from a body other than the current body you are orbiting.

Changes in 1.0.3

No significant changes, compiled for KSP v1.2.2.

Changes in 1.0.2

Sound/Kerbal Interface Device (SKID)

The SKID chip allows scripts to output procedural sound clips. Great for custom error tones, or for playing simple music. A basic example would be:

SET V0 TO GETVOICE(0).      // Gets a reference to the zero-th voice in the chip.
V0:PLAY( NOTE(400, 2.5) ).  // Starts a note at 400 Hz for 2.5 seconds.
                            // The note will play while the program continues.
PRINT "The note is still playing".
PRINT "when this prints out.".

For an example of a song, check out the Example song section of voice documentation

Also check out the SKID chip documentation for an indepth explaination.

CommNet Support

kOS now supports communications networks through KSP’s stock CommNet system as well as RemoteTech (only one networking system may be enabled at a time). The underlying system was modified and abstracted to allow both systems to use a common interface. Other mods that would like to add network support can implement this system as well without a need to update kOS itself.

Check out the Connectivity Managers documentation here

Trajectories Support

If you have the Trajectories mod for KSP installed, you can now access data from that structure using ADDONS:TR. This provides access to impact prediction through the Trajectories mod. For example:

if ADDONS:TR:AVAILABLE {
    if ADDONS:TR:HASIMPACT {
        PRINT ADDONS:TR:IMPACTPOS.
    } else {
        PRINT "Impact position is not available".
    }
} else {
    PRINT "Trajectories is not available.".
}

For more information see the Trajectories Addon Documentation

Changes in 1.0.1

Terminal Input

A new structure TerminalInput is available as a suffix of Terminal, allowing scripts to respond to user input.

Example:

terminal:input:clear().
print "Press any key to continue...".
terminal:input:getchar(). // blocking callback
print "Input will be echoed back to you.  Press q to quit".
set done to false.
until done {
    if (terminal:input:haschar) {
        set input to terminal:input:getchar().
        if input = "q" {
            set done to true.
        }
        else {
            print "Input read was: " + input + " (ascii " + unchar(input) + ")".
        }
    }
    wait 0.
}

Timewarp

The new TimeWarp structure provides better access to information about timewarp. It provides lists of warp rates, information about the physics timestep, and can tell you if the warp rate has settled.

Example:

print kuniverse:timewarp:ratelist. // prints the rates available in the current mode
set eta to 150 * 6 * 60 * 60. // 150 days
kuniverse:timewarp:warpto(time:seconds + eta).
print "delta t: " + kuniverse:timewarp:physicsdeltat.  // see the step change
wait 0.
print "delta t: " + kuniverse:timewarp:physicsdeltat.  // see the step change
wait 0.
print "delta t: " + kuniverse:timewarp:physicsdeltat.  // see the step change
wait 0.
print "delta t: " + kuniverse:timewarp:physicsdeltat.  // see the step change
wait 0.
print "delta t: " + kuniverse:timewarp:physicsdeltat.  // see the step change
wait 60 * 60.
kuniverse:timewarp:cancelwarp().
print "delta t: " + kuniverse:timewarp:physicsdeltat.  // see the step change
print "rate:    " + kuniverse:timewarp:rate.
wait until kuniverse:timewarp:issettled.
print "delta t: " + kuniverse:timewarp:physicsdeltat.  // see the step change
print "rate:    " + kuniverse:timewarp:rate.

Changes in 1.0.0

Subdirectories

See Understanding directories.

You are now able to store subdirectories (“folders”) in your volumes, both in the archive and in local volumes. To accomodate the new feature new versions of the file manipulation commands had to be made (please go over the documentation in the link given above).

Boot Subdirectory

See Special Handing of files in the “boot” directory.

To go with Subdirectories, now you make a subdirectory in your archive called boot/, and put all the candidate boot files there.

PATH structure

You can now get information about a file’s path and location.

New RUNPATH command

New RUNPATH command lest you make the program to run be a varying expression.

Communications

Communication between scripts on different CPUs of the same vessel or between different vessels.

Message Structure

A Message structure added to be used with the new communications system.

Allow scripted vessel launches

GETCRAFT(), LAUNCHCRAFT(), CRAFTLIST(), LAUNCHCRAFTFROM() were added as new suffixes to the Kuniverse structure.

ETA to SOI change

ORBIT:NEXTPATCHETA to get the time to the next orbit patch
transition (SOI change).

VESSEL:CONTROLPART

VESSEL:CONTROLPART to get the part which has been used as the current “control from here”.

Maneuver nodes as a list

ALLNODES bound variable added.

More pseudo-action-groups

Some new Pseudo-Action-Groups added for handling a lot of new groups of parts.

Get Navball Mode

NAVMODE bound variable:

UniqueSet

Added a UniqueSet collection for holding a generic set of things where order is irrelevant and duplicates are guaranteed not to exist.

Changes in 0.20.1

This release is just a bug fix release for the most part, with only just one new feature:

3-axis Gimbal Disabling

You can now selectively choose which of the 3-axes of an engine gimbal you want to lock, rather than having to lock the entire gimbal or none of it.

(See suffixes “PITCH”, “YAW”, and “ROLL” of the gimbal documentation.)

Changes in 0.20.0

This release is functionally identical to v0.19.3, it is recompiled against the KSP 1.1 release binaries (build 1230)

Changes in 0.19.3

Interuptable Triggers

Triggers are no longer required to complete within a single update frame, allowing them to be more than the IPU instructions long. This also means that they are no longer guaranteed to be atomic, and that long running triggers may prevent the execution of other triggers or the mainline code. See the trigger documentation for details.

Script Profiling

You may now profile the performance of your scripts to better understand how the underlying opcodes operate, as well as to identify slow executing sections of code. See the function ProfileResult for more information.

Compiled LOCK

In previous versions, attempting to create a lock with a duplicate identifier from within a compiled script would throw an error regarding label replacement. In this version, the handling of lock objects is updated to be more flexible at run-time, instead of relying on compile-time information.

ON Using Expressions

In previous versions, ON would not accept an expression as a parameter like this:

ON STAGE:READY {
    PRINT "STAGE: " + STAGE:READY.
}
ON ROUND(MAX(2000, ALT:RADAR)) {
    PRINT ROUND(ALT:RADAR).
}

ON will now evaluate the expression instead of treating it like a variable identifer.

Changes in 0.19.2

This was mostly a bug fix release. Not much changed in the documentation.

FORCEACTIVE

New alias KUNIVERSE:FORCEACTIVE() can be used instead of the longer name KUNIVERSE:FORCESETACTIVEVESSEL().

Changes in 0.19.1

This change was mostly for small bug fixes and didn’t affect the documentation much.

Mentioned PIDLoop() function in tutorial

Added section to PID loop tutorial that explains better that there’s a new function for doing PID loops. The tutorial had been originally written before that function existed.

New Terminal brightness and char size features

Terminal structure now has suffixes, TERMINAL:BRIGHTNESS, TERMINAL:CHARWIDTH, and TERMINAL:CHARHEIGHT to go with the new widgets on the terminal GUI.

Changes in 0.19.0

Art asset changes

Though not represented in these documents, numerous changes to the part models and artwork are included as part of this update, including the new KAL9000 high-end computer part.

Varying Power Consumption

Electrical drain is now handled in a dynamically changing way that actually notices how much you are using the CPU and uses less power if the CPU is mostly idling (if it spends most of its time on WAIT statements).

For mods that want to re-balance the meaning of electric charge units, the drain factor is also editable in module config fields in the various part.cfg files the mod ships with. This opens them up to being changed by ModuleManager rules.

Delegates (function pointers)

User functions and built-in functions (but not suffixes yet) can now be referred to with function pointers called delegates along with “currying” of pre-loaded arguments.

Optional Defaulted Parameters

User functions and user programs can now be configured to have optional trailing parameters that receive unmentioned when calling them.

File I/O

VolumeFile now lets you read and write arbitrary strings in files in a more natural way than using the LOG command, and allows you to read the whole file into one big string in one go.

Serialization in JSON

Automatic serialization system added to the file operations to save/load some kinds of data values to JSON-format files.

Universal Object Suffixes

All user values now are a kind of structure and thus there are a few universal suffixes that can be used to query what type of data a thing is (:ISTYPE and :TYPENAME).

Multimode Engine and Gimbal Support

Engines can now support multiple-mode information, and can acces thei gimbal information in the :GIMBAL suffix.

Range

New Range type for getting arbitrary iterable collections of ranges of integers.

Char and Unchar

CHAR(a) and UNCHAR(a) functions for getting the Unicode value of a character or making a character from its Unicode value.

For loop on string chars

The for loop can now iterate over the characters of a string.

JOIN

Join suffix on lists now lets you make a string with a delimeter of the list’s elements.

Hours per day

KUniverse now has a suffix to let you read the user setting for whether the clock is using a 24 hour day or a Kerbin 6 hour day.

Archive

The reserved word Archive is now a first class citizen so that SET FOO TO ARCHIVE. works like you’d expect it to.

Changes in 0.18.2

Queue and Stack

Queues and Stacks are now a feature you can use along with lists.

Volumes and Processors integration

Volumes now get a default name equal to the core processor’s nametag, and have several suffixes that can be queried.

Get the volume that goes with a core

Debuglog

Debuglog suffix of KUNIVERSE for writing messages to the Unity log file.

Changes in 0.18.1

(This update had only bug fixes and nothing that affected these user documentation pages.)

Changes in 0.18 - Steering Much Betterer

Steering Overhaul

A major change to Cooked Steering!

Should help people using torque-less craft like with Realism Overhaul. Removed the old steering logic and replaced it with a nice auto-tuning system.

SteeringManager structure now lests you acccess and alter parts of the cooked steering system.

PIDLoop structure now lets you borrow the PID mechanism used by the new cooked steering, for your own purposes.

Lexicon

New Lexicon structure now allows associative arrays.

String methods

New String structure now allows string manipulations.

Science Experiment Control

New ScienceExperimentModule allows you to fire off science experiments bypassing the user interface dialog.

Crew Member API

New CrewMember structure allows you to query the registered crew - their class, gender, and skill.

LOADISTANCE

New LOADDISTANCE obsoletes the previous way it worked.

Renamed built-ins

“AQUIRE” on docking ports is now “ACQUIRE”. “SURFACESPEED” is now “GROUNDSPEED” instead.

Enforces control of own-vessel only

It was previously possible to control vessels that weren’t attached to the kOS computer running the script. This has been corrected.

Dynamic pressure

DYNAMICPRESSURE, or Q, a new suffix of Vessel.

DEFINED keyword

DEFINED keyword that can be used to check if a variable has been declared.

KUNIVERSE

KUniverse structure letting you break the 4th wall and revert from a script

SolarPrimeVector

SolarPrimeVector, a bound variable to provide universal longitude direction.


Changes in 0.17.3

New Looping control flow, the FROM loop

There is now a new kind of loop, the FROM loop, which is a bit like the typical 3-part for-loop seen in a lot of other languages with a separate init, check, and increment section.

Short-Circuit Booleans

Previously, kerboscript’s AND and OR operators were not short-circuiting. Now they are.

New Infernal Robotics interface

There are a few new helper addon utilities for the Infernal Robotics mod, on the IR addon page.

New RemoteTech interface

There are a few new helper addon utilities for the RemoteTech mod, on the RemoteTech addon page.

Deprecated INCOMMRANGE

Reading from the INCOMMRANGE bound variable will now throw a deprecation exception with instructions to use the new RTAddon structure for the RT mod.

Updated thrust calculations for 1.0.x

KSP 1.0 caused the thrust calculations to become a LOT more complex than they used to be and kOS hadn’t caught up yet. For a lot of scripts, trying to figure out a good throttle setting is no longer a matter of just taking a fraction of the engine’s MAXTHRUST.

We fixed the existing suffixes of MAXTHRUST and AVAILABLETHRUST for engine and vessel to account for the new changes in thrust based on ISP at different altitudes. MAXTHRUST is now the max the engine can put out at the CURRENT atmospheric pressure and current velocity. It might not be the maximum it could put out under other conditions. The AVAILABLETHRUST suffix is now implemented for engines (it was previously only available on vessels). There are also new suffixes MAXTHRUSTAT (engines and vessels), AVAILABLETHRUSTAT (engines and vessels), and ISPAT (engines only) to read the applicable value at a given atmospheric pressure.

New CORE struct

The core bound variable gives you a structure you can use to access properties of the current in-game CPU the script is running on, including the vessel part it’s inside of, and the vessel it’s inside of, as well as the currently selected volume. Moving forward this will be the struct where we enable features that interact with the processor itself, like local configuration or current operational status.

Updated boot file name handling

Boot files are now copied to the local hard disk using their original file name. This allows for uniform file name access either on the archive or local drive and fixes boot files not working when kOS is configured to start on the Archive. You can also get or set the boot file using the BOOTFILENAME suffix of the CORE bound variable.

Docking port, element, and vessel references

You can now get a list of docking ports on any element or vessel using the DOCKINGPORTS suffix. Vessels also expose a list of their elements (the ELEMENTS suffix) and an element will refernce it’s parent vessel (the VESSEL suffix).

New sounds and terminal features

For purely cosmetic purpopses, there are new sound features and
a few terminal tweaks.
  • A terminal keyclick option for the in-game GUI terminal.
  • The ability to BEEP when printing ascii code 7 (BEL), although the only way currently to achieve this is with the KSlib’s spec_char.ksm file, as kOS has no BEL char, but this will be addressed later.
  • A sound effect on exceptions, which can be turned off on the CONFIG panel.

Clear vecdraws all at once

For convenience, you can clear all vecdraws off the screen at once now with the clearvecdraws() function.


Changes in 0.17.0

Variables can now be local

Previously, the kOS runtime had a serious limitation in which it could only support one flat namespace of global-only variables. Considerable archetecture re-work has been done to now support block-scoping in the underlying runtime, which can be controlled through the use of local declarations in your kerboscript files.

Kerboscript has User Functions

The primary reason for the local scope variables rework was in support of the new user functions feature which has been a long-wished-for feature for kOS to support.

Community Examples Library

There is now a new fledgling repository of examples and library scripts that we hope to be something the user community contributes to. Some of the examples shown in the kOS 0.17.0 release video are located there. The addition of the ability to make user functions now makes the creation of such a library a viable option.

Physics Ticks not Update Ticks

The updates have been moved to the physics update portion of Unity, instead of the animation frame rate updates. This may affect your preferred CONFIG:IPU setting. The new move creates a much more uniform performance across all users, without penalizing the users of faster computers anymore. (Previously, if your computer was faster, you’d be charged more electricity as the updates came more often).

Ability to use SAS modes from KSP 0.90

Added a new third way to control the ship, by leaving SAS on, and just telling KSP which mode (prograde, retrograde, normal, etc) to put the SAS into.

Blizzy ToolBar Support

If you have the Blizzy Toolbar mod installed, you should be able to put the kOS control panel window under its control.

Ability to define colors using HSV

When a color is called for, such as with VECDRAW or HIGHLIGHT, you can now use the HSV color system (hue, saturation, value) instead of RGB, if you prefer.

Ability to highlight a part in color

Any time your script needs to communicate something to the user about which part or parts it’s dealing with, it can use KSP’s part highlighting feature to show a part.

Disks can be made bigger with tweakable slider

All parts that have disk space now have a slider you can use in the VAB or SPH editors to tweak the disk space to choose whether you want it to have 1x, 2x, or 4x as much as its default size. Increasing the size increases its price and its weight cost.

You Can Transfer Resources

You can now use kOS scripts to transfer resources between parts for things like fuel, in the same way that a manual user can do by using the right-click menus.

Kerbal Alarm Clock support

If you have the Kerbal Alarm Clock Mod isntalled, you can now query and manipulate its alarms from within your kOS scripts.

Query the docked elements of a vessel

You can get the docked components of a joined-together vessel as separate collections of parts now.

Support for Action Groups Extended

While there was some support for the Action Groups Extended mod before, it has been greatly improved.

LIST constructor can now initialize lists

You can now do this:

set mylist to list(2,6,1,6,21).

to initialize a list of values from the start, so you no longer have to have a long list of list:ADD commands to populate it.

ISDEAD suffix for Vessel

Vessels now have an :ISDEAD suffix you can use to detect if the vessel has gone away since the last time you got the handle to it. (for example, you LIST TARGETS IN FOO, then the ship foo[3] blows up, then foo[3]:ISDEAD should become true to clue you in to this fact.)

About kOS and KerboScript

kOS was originally created by Nivekk. It is under active development by the kOS Team and is licensed under terms of GNU General Public License Version 3, 29 June 2007, Copyright © 2007 Free Software Foundation, Inc.

Indices and tables