KOS Wiki
Advertisement
Kerbal_Space_Program_w_KOS_mod_Descend_with_skycrane.

Kerbal Space Program w KOS mod Descend with skycrane.

New version for KOS 0.92 Here.[]

The code has been updated to KOS 0.92 and will no longer run on versions older than that.

Complex[]

This is a complex example attempting to do a lot of work. Do not start with it as your first example. It is meant to demonstrate how complex the system can get.

WARNING: CODE IS TOO BIG - MUST STRIP COMMENTS[]

The code shown here will not fit in a 10k SCS module unless comments are stripped. This is because it was written with very large amounts of comments in it precisely because it is an example to look at.

To run it you need to either edit out the comments or run it on a craft that has range to see the archive back home and can run it from there.

To strip comments you can simply do a regular-expression search and replace (assuming your text editor has that feature) to replace [ \t]*\/\/.*$ with null-string.

(And while you're at it, you can strip the leading indent spaces with the regular expression search for ^[ \t]* and replace with null-string.)

How To Use[]

  1. Make sure all the example code at the bottom of this page exists where you can call it.
  2. There are two ways to handle the large size of the code. Do either one of these two things:
    1. Make sure the craft can see radio range to the archive to run the code from there. <or>
    2. Make sure you have stripped out the comments from the "descend" code to bring it down within the size limits of the SCS module.
  3. Put your vessel into a configuration where it will dip low near the SOI body, or even impact it. As long as your altitude will dip below the setting descendTop from the bodystats program it should work.
  4. This example only supports Kerbin, Mun, and Minmus. To use elsewhere you'll have to edit the 'bodystats' program appropriately first.
  5. Make sure you've gotten your vessel to the point in its staging list where it has the landing engines active but not throttled up yet. The script cannot "see" the configuration of your vessel's parts and doesn't know which stage is the one you want it to be on, so it doesn't try to guess.
  6. run bodystats(body).
  7. run descend( type, slopeMax ). (where type is one of: "skycrane", "lander", or "hover", and slopeMax is the maximum slope you'll accept for a landing site (where slope is measured as height/horizontaldist, such that a slope of 1.0 means the land is at a 45 degree angle for example).
  8. Design of the vessel:
    1. Your Vessel must be able to rotate toward the retrograde vector without the use of RCS thrusters. (Make sure it has adequate torque wheels). The script does not use RCS and assumes you can rotate without it.
    2. For skycrane use only: Make sure the vessel is designed so that the very next staging list activation after the current one will be the one that drops the payload. The script performs a single "stage" activation at the point it tries to drop the payload.

Version problems[]

This was first written for KOS 0.65. Some new changes in 0.7 and 0.8 make it not work so well:

  • 0.71 has a bug with the missionTime value such that trying to perform math operations on it produces the wrong results. In this case it causes the script to believe zero time has passed between loop iterations, which in turn makes it think the slope of terrain under the craft is infinity, making it never satisfied with a place to land. When using this in OS 0.71 you may need to hardcode the slope to just be zero all the time and ignore that part of the code's features.
  • 0.8x is too broken to test with at this moment. Assume the example won't work at all with 0.8x until further notice.

The actual files[]

Alternate file location[]

Alternately instead of looking here, the files are hosted on a google drive of the author, where they are public and more likely to get updated. However Wikia is quite spam-tastic and therefore I (the author) don't relish the idea of posting a link to the drive in the text here. Make a comment at the bottom of the page if you want to get to the google drive and I will send you a private notification to your Wikia account with the URL.

Files stored here[]

"database" files[]

These files are here to obtain data that KOS currently (0.92) doesn't give about the KSP game - the stats on bodies, the stats on parachutes, etc). They both work on the principle that the archive back home contains the full data and the smaller program stored locally on the volume just gets the data from the home archive file:

File 1.1: call it "Note: This file is publicly editable and might contain malicious code.
Please exercise caution when using.
bodydata.txt view"



//KOS
// Call home to the archive and obtain the stats
// about a body.
// ASSUMPTION: YOU WANT TO END UP ON VOLUME 1 WHEN
// DONE.  (Future upgrade: when and if it becomes
// possible to query "what is my selected volume?",
// change this code to remember the original volume,
// and switch back to whatever it was at the end.)
//
declare parameter bName.
// Change this one next line when querying volume name in a future release:
set prevVol to "1".
print "Contacting mission control to get stats.".
switch to archive.
run bodyDB(bName).
print "Returning to local volume: " + prevVol.
switch to 1. // Use local drive to store self-modifying code.
log "dummy" to tmpCmd.
delete tmpCmd.
set cmd to "switch t" + "o " + prevVol + ".".
log cmd to tmpCmd.
run tmpCmd.



File 1.2: call it "Note: This file is publicly editable and might contain malicious code.
Please exercise caution when using.
bodyDB.txt view"



//KOS bodystats: sets up variables for use in programs
// that need stats about the orbital body that is the
// current SOI.  Looks up the body name and fills
// the appropriate variables.
declare parameter forBody.

print " ".
print "Home base Body Database successfully contacted.".
print " ".

// Constants for important things that DON'T change per body:
// ==========================================================
set gConst to 6.67384*10^(0-11). // The Gravitational constant
set pi to 3.14159265359 .
set e to 2.718281828459 .
set atmToPres to 1.2230948554874 . // presure at 1.0 Atm's.

// Starting defaults for constants that DO change per body:
// ========================================================
set bodySurfaceGrav to 10.   // The m/s^2 at the body's "sea level".
set bodyMass to 5.2915793*10^22. // Kilograms for body mass.
set bodyRadius to 500000 .   // The radius from center to equator.
set bodyMaxElev to 4000.     // The peak of the highest mountain.
set bodyAtmSea to 0.         // sea level atmosphere pressure in units of Kerbin atm's.
set bodyAtmScale to 0.       // atmospheric scale height.
set descendTop to 50000 .    // The highest AGL at which descents might start.
set descendBot to 100 .      // the AGL where a descending craft should hover.
set descendTopSpeed to 2000.0 . // Desired speed at top of descent profile.
set descendBotSpeed to 4.0 .    // Desried speed at bottom of desecnt profile.
set bodyLandingSpeed to 4.0.    // Desried speed to come down from hover to.

// Overrides for each body:
// ==========================

if forBody = "Kerbin" {
  set bodySurfaceGrav to 9.802 .
  set bodyRadius to 600000 .
  set bodyMass to 5.2915793*10^22. 
  set bodyMaxElev to 6761 .
  set bodyScaleHeight to 5000 . 
  set bodyAtmSea to 1.0 .
  set bodyAtmScale to 5000 .
  set descendTop to 70000 .
  set descendBot to 150.
  set descendTopSpeed to 2100.0 .
  set descendBotSpeed to 6.0 .
  set descendLandingSpeed to 4.0 .
}.
if forBody = "Mun" {
  set bodySurfaceGrav to 1.63 .
  set bodyRadius to 200000 .
  set bodyMass to 9.7600236*10^20.
  set bodyMaxElev to 7061 .
  set descendTop to 20000 .
  set descendBot to 80 .
  set descendTopSpeed to 542.0 .
  set descendBotSpeed to 6.0 .
  set bodyLandingSpeed to 3.0 .
}.
if forBody = "Minmus" {
  set bodySurfaceGrav to 0.491 .
  set bodyRadius to 60000 .
  set bodyMass to 2.6457897*10^19.
  set bodyMaxElev to 5725 .
  set descendTop to 20000 .
  set descendBot to 50 .
  set descendTopSpeed to 274.0 .
  set descendBotSpeed to 5.0 .
  set bodyLandingSpeed to 2.0 .
}.
if forBody = "Duna" {
  set bodySurfaceGrav to 2.94 .
  set bodyRadius to 320000 .
  set bodyMass to 4.5154812*10^21.
  set bodyMaxElev to 8264.
  set bodyAtmSea to 0.2 .
  set bodyAtmScale to 3000.
  set descendTop to 40000 .
  set descendBot to 100 .
  set descendTopSpeed to 1000.0 .
  set descendBotSpeed to 5.0 .
  set bodyLandingSpeed to 3.0 .
}.
if forBody = "Ike" {
  set bodySurfaceGrav to 1.10 .
  set bodyRadius to 130000 .
  set bodyMass to 2.7821949*10^20.
  set bodyMaxElev to 12750 .
  set descendTop to 12000 .
  set descendBot to 50 .
  set descendTopSpeed to 534 .
  set descendBotSpeed to 5.0 .
  set bodyLandingSpeed to 3.0 .
}.

print "======== " + forBody + " ===================".
print "bodySurfaceGrav  = " + bodySurfaceGrav.
print "bodyRadius       = " + bodyRadius.
print "bodyMass         = " + bodyMass.
print "bodyAtmSea       = " + bodyAtmSea.
print "bodyAtmScale     = " + bodyAtmScale.
print "descendTop       = " + descendTop.
print "descendBot       = " + descendBot.
print "descendTopSpeed  = " + descendTopSpeed.
print "descendBotSpeed  = " + descendBotSpeed.
print "bodyLandingSpeed = " + bodyLandingSpeed.
print "=============================================".
print " ".
print "'AGL' means Above Ground Level to distinguish ".
print "from sea level altitude.".
print " ".
print "You may change these variables with the 'set' ".
print "command before running other programs to try ".
print "other settings.".
print " ".



File 1.3: call it "Note: This file is publicly editable and might contain malicious code.
Please exercise caution when using.
chutedata.txt view"



//KOS: Call home to the archive and obtain the stats
// about a named parachute.
// ASSUMPTION: YOU WANT TO END UP ON VOLUME 1 WHEN
// DONE.
//
declare parameter chName.
// Change this one next line when querying volume name in a future release:
set prevVol to "1".
print "Contacting mission control to get stats.".
switch to archive.
run chuteDB(chName).
print "Returning to local volume: " + prevVol.
switch to 1. // Use local drive to store self-modifying code.
log "dummy" to tmpCmd.
delete tmpCmd.
set cmd to "switch t" + "o " + prevVol + ".".
log cmd to tmpCmd.
run tmpCmd.



File 1.4: call it "Note: This file is publicly editable and might contain malicious code.
Please exercise caution when using.
chuteDB.txt view"



//KOS
declare parameter chName . // parachute name to look up, must be full name.
set chMass to 0.
set chSemiPres to 0.
set chSemiDrag to 0.
set chFullAGL to 0.
set chFullDrag to 0.

if chName = "Mk16 Parachute" {
  set chMass to 0.1 .
  set chSemiPres to 0.01 .
  set chSemiDrag to 1 .
  set chFullAGL to 500 .
  set chFullDrag to 500 .
}.
if chName = "Mk16-XL Parachute" {
  set chMass to 0.3 .
  set chSemiPres to 0.01 .
  set chSemiDrag to 1 .
  set chFullAGL to 500 .
  set chFullDrag to 500 .
}.
if chName = "Mk2-R Radial-Mount Parachute" {
  set chMass to 0.15 .
  set chSemiPres to 0.01 .
  set chSemiDrag to 1 .
  set chFullAGL to 500 .
  set chFullDrag to 500 .
}.
if chName = "Mk25 Parachute" {
  set chMass to 0.2 .
  set chSemiPres to 0.007 .
  set chSemiDrag to 4 .
  set chFullAGL to 2500 .
  set chFullDrag to 170 .
}.
if chMass = 0 {
  print " ".
  print "DB lookup error: No data for parachute named: ".
  print "    '" + chName + "'.".
  print " ".
}.



Utility file for matrix mathy stuff[]

File 2: call it "Note: This file is publicly editable and might contain malicious code.
Please exercise caution when using.
tfXYZtoENU.txt view"



// Given an XYZ coord in the KSP native coord system,
// calculate the same coord in terms of the ENU
// system (ENU is a term I made up for:
// "East North Up".  It's the system with an origin
// point on the surface of the SOI body directly 
// beneath the vessel, and with X=east, Y=north,
// and Z=up.
//
// Because you can't pass things into or out of
// a program, global variables must be used here
// to simulate that.
// INPUT:  x,y,z,e,n,u
// OUTPUT: tfE,tfN,tfU as global variables.
//   (As of KOS 0.65 there is no way to return a
//   value or pass a variable by reference so globals
//   have to be used for the return values.)
//
// All "local" variables begin with "tf" to help
// prevent them from clashing with the other
// variables you might have used in the global
// namespace of KOS.

declare parameter tfX,tfY,tfZ.

// Rotation angles for rotation matrix:
set tfA to latitude. 
set tfCosA to cos(tfA).
set tfSinA to sin(tfA).

set tfB to (0 - up:yaw).  // use UP:yaw like it was longitude
set tfCosB to cos(tfB).
set tfSinB to sin(tfB).

// The rotation matrix around z axis (latitude) then y axis (longitude):
set tfW to tfX*tfCosB            + 0          + tfZ*tfSinB            .
set tfN to tfX*tfSinA*tfSinB     + tfY*tfCosA + tfZ*(0-tfSinA*tfCosB) .
set tfU to tfX*(0-tfCosA*tfSinB) + tfY*tfSinA + tfZ*tfCosA*tfCosB     .

// Native XYZ is left-handed-system.  BUT ENU matches the
// Lat/Lon system which is right-handed. So the above rotation
// is calculated for a westerly axis then flipped to east here:
set tfE to 0 - tfW.



The actual bulk of the code[]

File 3: Call it "Note: This file is publicly editable and might contain malicious code.
Please exercise caution when using.
descend.txt view"



  //KOS
  declare parameter descType. // "hover", "lander", or "skycrane", or "skycrane/lander".
  declare parameter seekFlat. // Flatness slope level acceptable for landing.
  declare parameter numCh. // Number of parachute parts on lander.
  declare parameter chName. // Name of the parachute parts on lander.
  
  // Some sanity checks to stop the program if the user is trying to
  // use it wrong:
  //
  sanityOK on.
  if maxthrust = 0 {
    print "ABORTING 'descend' PROGRAM: ".
    print "  No active engines right now.".
    sanityOK off.
  }.  
  if alt:periapsis > descendTop {
    print "ABORTING 'descend' PROGRAM: ".
    print "  Get your periapsis below " + descendTop + "m before running this program".
    sanityOK off.
  }.
  set chMass to 0.
  if numCh > 0 {
    run chutedata(chName).
    if chMass = 0 {
      sanityOK off.
    }.
  }.
  // This would be cleaner if KOSscript had a premature
  // exit or quit or return statement of some sort, rather
  // than wrapping the majority of the code in one long IF
  // body:
  //
  if sanityOK {
    print "Descend mode : " + descType .
  
    // Smallest payload mass the script will handle.
    // If the loss of mass between iterations is less
    // than this, it assumes that's just from
    // spending fuel.  If it's bigger, it assumes 
    // the payload has been dropped:
    set minPayload to 0.1 .
  
    print "Descend program initialised. ".
    print "---------------------------- ".
    print " ".
    print "Program will start operations when ".
    print "your AGL is under " + descendTop + " meters.".
    wait until ( altitude < descendTop ) .
  
    print "Taking over rotation control.".
  
    SAS OFF.
    // Steer orbital retrograde for now.  Will use surface further
    // down.  This is a quick starter to get KOS's rotation control
    // mostly in the right direction before the thrusters kick in.
    set mySteer to retrograde.
    lock steering to mySteer.
  
    // Difference between my desired orientation and my actual
    // orientation.  As this approaches zero I'm closer to being
    // aligned to my desired steering:
    // The reason for all the ugly trig is that this is a way to recognize
    // that, for example, 1 degree and 359 degrees are really only 2 degrees
    // apart, not 358 degrees apart).
    lock align to abs( cos(facing:pitch) - cos(mySteer:pitch) )
                  + abs( sin(facing:pitch) - sin(mySteer:pitch) )
                  + abs( cos(facing:yaw) - cos(mySteer:yaw) )
                  + abs( sin(facing:yaw) - sin(mySteer:yaw) ) .
    // Roll had to be taken out because KOS doesn't align to it:
    // + abs( cos(facing:roll) - cos(mySteer:roll) )
    // + abs( sin(facing:roll) - sin(mySteer:roll) ).
  
    wait until align < 0.2.
  
    set myTh to 0.0 .
    lock throttle to myTh.
  
    print "ENTERING DESCENT MODE.".
  
    // dModes are as follows:
    // dMode 0 = descending to descendBot AGL.
    // dMode 1 = hovering at descendBot AGL.
    // dMode 2 = skycrane drop payload and escape.
    // dMode 3 = coming down for final touchdown from descendBot.
    set dMode to 0.
  
    set beDone to 0.
  
    set chutesYet to 0.
  
    // variables for slope seeking:
    set slope to 0.  // Slope detected under lander.
    set gHeight to 0.  // Height of ground above sea level.
    set pgHeight to 0.  // previous Height of ground above sea.
    set pTime to missiontime. // Previous elapsed time.
  
    set needNewAG9 to 1.
  
    clearscreen.
    print "*== MODE: ==========================*".
    print "|     AGL Altitude =                |".
    print "|    Periapsis alt =                |".
    print "| Term. vel at Pe  =                |".
    print "|   Current Thrust =                |".
    print "|    Waiting for areobrake ? =      |".
    print "|   Neutral Thrust =                |".
    print "|              TWR =                |".
    print "|    Current Speed =                |".
    print "|  Preferred Speed =                *----------*".
    print "| Ground Slope Here=                           |".
    print "| Duration of prev iteration =                 |".
    print "*----------------------------------------------*".
    print " ========= Descend Type: " + descType + " ====".
  
    // Some lock expressions I will be using to cut down on
    // number of statements in the loop:
    // ====================================================
    lock heregrav to gConst*bodyMass/((altitude+bodyRadius)^2).
    lock twr to maxthrust/(heregrav*mass).
    set tfE to 9999999. set tfN to 9999999. set tfU to 9999999. // east,north,up vector
    lock absspd to (tfE^2 + tfN^2 + tfU^2) ^ 0.5 .
    set petermv to 999999.
    set usepe to 999999.
  
    // How much of my current aiming direction is vertical?
    // (i.e. cosine of angle between steering and straight up):
    set absvsup to abs(tfU).
    lock cossteerup to absvsup / ( (tfE^2+tfN^2+absvsup^2)^0.5 ).
    lock sinsteerup to ((tfE^2+tfN^2)^0.5) / ( (tfE^2+tfN^2+absvsup^2)^0.5 ).
  
    // If there's no atmosphere now, or if there is but later on
    // we slow down below terminal velocity, stop calcluating
    // the atmo drag as it tends to be an expensive calculation:
    loopedOnce off.
    when bodyAtmSea = 0 or (loopedOnce and (absspd < petermv) and usepe <= bodyMaxElev ) then {
      unlock fdrag. set fdrag to 0.
      unlock usepe. set usepe to 0.
      unlock pegrav. set pegrav to gConst*bodyMass/(bodyRadius^2).
      unlock pepress. set pepres to 0.
      unlock vdrag. set vdrag to 0.2 .
      unlock petermv. set petermv to 999999.
    }.
    // A lot of this stuff is only useful if there's air and
    // therefore drag needs to be taken into account.  It's a lot
    // of time consuming calculation so it should be eliminated
    // when there's no air, to speed up the loop:
    // ----------------------------------------------------------
    if bodyAtmSea > 0 {
      // Which periapsis to use: real or ground level?
      lock usepe to alt:periapsis. 
      when usepe < bodyMaxElev then {
        lock usepe to bodyMaxElev.
      }.
      // gravitational accelleration and pressure at periapsis:
      lock pegrav to gConst*bodyMass/((usepe+bodyRadius)^2).
      lock pepress to (atmToPres*bodyAtmSea) * e ^ ( (0-usepe)/bodyAtmScale) .
      // The current drag to calculate with:
      set useDrag to 0.2 .
      // The drag number to use will change when we hit certain conditions:
      if numCh > 0 {
        when pepress > chSemiPres then { set useDrag to chSemiDrag. }.
        when alt:periapsis < chFullAGL then { set useDrag to chFullDrag. }.
      }.
      lock vdrag to ( (mass-(numCh*chMass))*0.2 + (chMass*useDrag) ) / mass .
      // Force of drag from air pressure here:
      // current velocity when we got there:
      lock fdrag to 0.5*( (atmToPres*bodyAtmSea) * e^( (0-altitude)/bodyAtmScale) )*absspd^2*vdrag*0.008*mass.
      // Terminal velocity at periapsis:
      lock petermv to ( (250*pegrav)/( pepress * vdrag ) ) ^ 0.5 .
    }.
  
    // Hover throttle setting that would make my rate of descent constant:
    lock hovth to ((mass*heregrav)-fdrag) * (1/cossteerup) / maxthrust .
  
    // The acceleration I can do above and beyond what is needed to hover:
    lock extraac to (twr - 1) * heregrav.
  
    // My current stopping distance I need at that accelleration, with a 1.2x fudge
    // factor for a safety margin:
    lock stopdist to 1.2 * ( (absvsup-descendBotSpeed)^2)/(2*extraac).
  
    // naptime = Seconds to slow down the loop execution by.
    // Fast execution speed is only needed when near the bottom of the descent.
    // At the top it's safe to execute slowly and give more time to other KSP things:
    lock naptime to 10 * (alt:radar - descendBot)/(descendTop-descendBot).
  
    run tfXYZtoENU( velocity:surface:x, velocity:surface:y, velocity:surface:z ).
    set surfGrav to gConst*bodyMass/(bodyRadius^2).
    set surfExtraAc to ( (maxthrust/(mass*surfGrav) ) - 1 ) * surfGrav .
  
    until beDone {
      set airbrakeMult to 1. // 1 = not areobraking, 0 = areobraking.
  
      // Get surface velocity in terms of a coordinate system
      // based on the current East,North, and Up axes:
      // ...................................................
      run tfXYZtoENU( velocity:surface:x, velocity:surface:y, velocity:surface:z ).
      set absvsup to abs(tfU).
  
      // Try to save a snapshot of the dynamic data at one 
      // instant of time or as close as possible to that:
      // ...................................................
      // set absspd to (tfE^2 + tfN^2 + tfU^2) ^ 0.5 .
      set altAGL to alt:radar .
      set altSea to altitude .
      set spdSurf to surfacespeed.
      set dTime to missiontime - pTime.
      set pTime to missiontime.
  
      if bodyAtmSea > 0 {
        print "                          " at (22,2).
        if pepress > 0 and petermv < 10000 {
          print round( usepe* 10 ) / 10 + " m" at (22,2).
        }.
        if usepe = bodyMaxElev {
          print "(est max elev)" at (22,12).
        }.
        print "              " at (22,3).
        if pepress > 0 and petermv < 10000 {
          print round( petermv* 10 ) / 10 + " m/s" at (22,3).
        }.
      }.
  
      if dMode = 0 { print "  DESCENDING   " at (10,0). }.
      if dMode = 1 { print "   HOVERING    " at (10,0). }.
      if dMode = 2 { print "DEPLOYED/ESCAPE" at (10,0). }.
      if dMode = 3 { print "    LANDING    " at (10,0). }.
  
      print "        " at (32,11).
      print ( round( dTime * 100 ) / 100 ) + " s" at (32,11).
  
      // Guess AGL if we're too high to tell:
      if altAGL = altSea and altSea > 10000 {
        set altAGL to altSea - (bodyMaxElev/2).
      }.
      print "              " at (22,1).
      print ( round( altAGL * 10 ) / 10 ) + " m" at (22,1).
  
  
      set gHeight to (altSea-altAGL).
      set slope to 0.
      // Avoid calculating ground slope if not moving horizontally
      // fast enough to get a reliable reading.  If moving nearly
      // vertically, then the arithmetic gets chaotic and swingy:
      if spdSurf > 0.1 {
        set slope to (gHeight-pgHeight)/(dTime*spdSurf).
      }
      set pgHeight to gHeight.
      print "                        " at (22,10).
      print ( round( slope * 100 ) / 100 )  at (22,10).
      if abs(slope) > abs(seekFlat) {
        print "(seeking flatter)" at (28,10).
      }.
      print "            " at (22,7).
      print ( round( twr * 100) / 100 ) + " (at here)" at (22,7).
      print "            " at (22,8).
      print ( round( absspd * 100 ) / 100 ) + " m/s" at (22,8).
  
      if altAGL < descendBot*3 {
        // Check to see if we need to scope on further for
        // flatter land.  Only do this if landing has not
        // alrady begun.  Once it's begun commit to it:
        if dMode < 2 and abs(slope) > abs(seekFlat) {
          // Pretend the centered direction is a bit off from
          // what it really is, to make the code steer a bit
          // off on purpose, to make it seek a different
          // landing spot.
          if tfE > 0 { set tfE to tfE-10 . }.
          if tfE < 0 { set tfE to tfE+10 . }.
          if tfN > 0 { set tfN to tfN-10 . }.
          if tfN < 0 { set tfN to tfN+10 . }.
        }.
        // When near the bottom of the descent,
        // and the vertical speed gets slow, never
        // allow the sideways-pointing nature
        // of the velocity vector to cause the vessel
        // to steer too far off from upward.  Put an
        // upper limit on how much its allowed to tilt
        // sideways:
        set oldAbsVsUp to absvsup.
        if dMode = 1 or dMode = 3 { set absvsup to absspd + 1.0 . }.
      }.
      set mySteer to up * V( tfE, 0 - tfN, absvsup ).
      
      print "          " at (22,6).
      print ( round( hovth * 1000 ) / 10 ) + " %"  at (22,6).
  
      if dMode = 0 and altAGL > 0 and altAGL < descendBot {
        // Regardless of mode, go to hover mode first, and
        // stay there until slope is acceptable before
        // going to land or skycrane mode.
        set dMode to 1.
        BRAKES ON.
        LEGS ON.
        LIGHTS ON.
      }.
      if dMode = 1 {
        set pDesSpd to 5 * ( altAGL - descendBot ) / descendBot  .
        if needNewAG9 = 1 {
          on AG9 set dMode to 3. 
          on AG9 set needNewAG9 to 1.
          set needNewAG9 to 0.
        }.
        // If we really aren't supposed to hover but instead
        // are supposed to land or deploy, then only continue
        // to hover as long as slope is unacceptable for landing:
        if abs(slope) < abs(seekFlat) {
          if descType = "skycrane" or descType = "skycrane/lander" or descType = "lander" {
            set dMode to 3.
          }.
        }.
      }.
      if dMode = 0 and altAGL > descendBot {
  
        // desired speed is based on estimate of the
        // suicide burn stopping distance, plus a bit of fudge
        // factor to handle not being able to see how high the terrain
        // is downrange from here where the landing will be:
        // (i.e. I might CURRENTLY be 2000 meters above the ground HERE,
        // but only 500 meters above the ground up ahead where the landing
        // will be.  The likelyhood of this problem goes down the more
        // and more vertical I am going, so use cossteerup to minimize
        // the magnitude of this extra guess the closer to vertical I am:
        set guessMoreH to (bodyMaxElev-gHeight).
        if guessMoreH > altAGL { set guessMoreH to 0. }.
        // Height to use to calculate how much distance I have available to stop:
        set H to (altAGL-descendBot) - (guessMoreH*sinsteerup). 
        // The correct formula is
        //                 ______
        //    pDesSpd =  \/ 2aH
        // But I changed the 2 to 1.8 to compensate for the delay of KOS
        // code always being a bit behind the curve.
        if H < 0  { set H to 0.  } // if dipping negative, don't allow sqrt to give NAN result.
        set pDesSpd to sqrt( 1.8 * surfExtraAc * H ) + descendBotSpeed.
  
        // Special case: If currently on a path that will bypass the ground (i.e.
        // periapsis wasn't set low enough when the program was started) then
        // force a burn until the periapsis is low enough:
        if periapsis > 0 and petermv > (absspd*1.3) {
          set pDesSpd to 0.
        }.
  
        // The decision whether or not to rely on areobraking
        // is complex.  For it to be a good idea not to use the
        // engines yet the following must be true:
        // - There is air at periopsis altitude.
        // - I am going a good deal faster than whatever the
        //     terminal velocity at periapsis is.
        // - My distance to the bottom is long enough to allow me
        //     to stop if I had to rely entirely on engines at my
        //     current TWR (this check is what causes engines to
        //     turn on when low to the ground but still going faster
        //     than terminal velocity).
        if  bodyAtmSea > 0 and absspd > petermv*1.25 and stopdist < (altAGL-descendBot)/cossteerup {
          set airbrakeMult to 0.
          if numCh > 0 and chutesYet = 0 {
            CHUTES ON.
            set chutesYet to 1.
          }.
        }.
        if airbrakeMult = 1 { print "no " at (32,5).  }.
        if airbrakeMult = 0 { print "yes" at (32,5).  }.
      }.
      if dMode = 2 {
        // Make darn sure the craft isn't still trying to
        // compensate for horizontal motion when it
        // drops the payload.  It MUST be vertical and
        // not lifting.
        set mySteer to up.
        set myTh to hovth*2/3.
        SAS ON. // So the SAS ON will be inherited by the payload before dropping it.
        // Because "stage" sometimes doesn't do anything the first time,
        // This tries until it noticibly changes the vessel mass to prove
        // it really did break a piece off:
        set oldMass to 0.
        set n to 0.
        until n > 8 or mass < (oldmass-minPayload) {
          set oldMass to mass.
          print "Trying to drop stage.".
          wait 0.5 .
          STAGE.
          set n to n + 1.
        }.
        if n > 8 { print "I can't seem to stage a payload.  Giving up.".  }.
        SAS OFF.
  
        // Clear the payload drop zone straight up, and gently
        // so as not to tip the payload with my exhaust:
        set myTh to 1.2 * hovth . 
        if myTh > 1 { set myTh to 1. }.
        set mySteer to up.
        wait 2.
  
        // Thrust away for a bit, either to escape the payload drop
        // area or to gently to go a short distance away and land:
        if descType = "skycrane" {
          set mySteer to up + R(30,0,0).
          set myTh to 1.5*hovth.
          if myTh > 1 { set myTh to 1. }.
            set beDone to 1.
            wait 5.
          }.
        if descType = "skycrane/lander" {
          set mySteer to up + R(20,0,0).
          set myTh to hovth .
          if myTh > 1 { set myTh to 1. }.
          // Become a lander now:
          set descType to "lander".
          set dMode to 3.
          wait 3 .
        }.
      }.
      if dMode = 3 {
        set pDesSpd to bodyLandingSpeed.
        if STATUS = "LANDED" or STATUS = "SPLASHED" {
          if descType = "hover" or descType = "lander" {
            // Stop moving:
            set mySteer to up.
            lock throttle to 0.
            wait 10.
            set beDone to 1. 
          }
          if descType = "skycrane" or descType = "skycrane/lander" {
            SAS ON. // To ensure the payload is holding itself stable.
            set dMode to 2.
          }.
        }.
      }.
  
      print "             " at (22,9).
      print ( round( pDesSpd * 100 ) /  100 ) + " m/s" at (22,9).
      set spd to absspd.
      if verticalspeed > 0.0 { set spd to 0 - absspd. }.
  
      // How far to offset the throttle depends on relatively how far
      // off we are from the desired speed, and how good the craft is at
      // thrusting, and how slowly this loop is running.
      // The goal being seeked is to use a setting that would achive the
      // desired speed in 1 iteration:
      set thOff to ( ( spd - pDesSpd ) / dTime ) / (maxthrust/mass).
  
      // Make the throttle less gentle when too close to the ground and going down
      // fast - go ahead and throttle highly if in that scenario:
      if altAGL < (descendBot*3) and spd > (pDesSpd*2) {
        set thOff to 1.5*thOff.
      }.
      
      set newTh to ( hovth + thOff ) * airbrakeMult .
      if newTh < 0.0 { set newTh to 0.0 . }.
      if newTh > 1.0 { set newTh to 1.0 . }.
  
      // If not even remotely close to the desired direction, then reduce
      // throttle, but not all the way to zero just in case the craft
      // depends on vectored thrust to help steer it:
      if align > 1.0 {
        set newTh to newTh / 3.
      }.
      set myTh to newTh.
  
      print "              " at (22,4).
      print ( round( myTh * 1000 ) / 10 ) + " %"  at (22,4).
  
      // extra delay when up high and not thrusting right now:
      if naptime > 0 and myTh = 0 { wait naptime. }.
      
      loopedOnce on.
    }.
  
  }.
  
  print "DONE.".
Advertisement