Jump to content

Experiments in long-distance Junos


numerobis

Recommended Posts

This post is for aircraft lovers only! I'm trying to push the limits of what planes can do in KSP.

I had a pair of contracts in early career mode that required me crew reports below X meters in six or seven far-away locations. I had the first planes, so I inspired myself from the Rutan Voyager:

p3URk7l.png

I had done a first contract with the pair of Junos on small pods, with big control surfaces for elevators and a rudder. From that success, I extrapolated that if I put on a lot more fuel on the pods a bit more wing, and then saved some weight on the tail by cutting it down to a tail fin - no rudder - and a single elevon, I'd be OK. Then I noticed that three rocket fins were lighter but had the same area as one tail fin (in retrospect, I really only needed one tail fin). That plane, flown manually, made it a bit over 5,000 km. This plane is nearly half fuel on liftoff.

Next I decided to see what I could do in sandbox with the Juno. This plane has nearly the same amount of fuel (just 10 units more):

8D00KDB.png

I chose to make it a single-engine plane. It still has 2t of fuel -- actually, 2.05t -- so it's supposed to take off with half the thrust and almost all the weight. I cut all the dry mass I could, and some of the drag:

  • Replace the cockpit (1.25t) with an inline cockpit (1t), save 250kg and also allow placing an intake up front.
  • Replace the tail connector with front and back adapters. Same mass (200kg) but they carry 160 fuel between them and allow putting an intake in front and juno in back. That fuel means we can spare three Mk0 tanks, saving 75kg -- and still have extra fuel on board.
  • Drop down to just one juno and intake from two, saving 109 kg and reducing drag substantially.
  • Replace two pairs of structural wing E with a pair of Big-S strakes. Same mass, but they carry 200 fuel between them, so we can remove four Mk0 tanks, saving 100kg.
  • Replace the fixed wheels with retractable wheels. Lower drag, just 20kg extra mass.
  • Remove two of the tail pieces, leaving just one rocket fin as my vertical stabilizer. Save 20kg mass, offsetting the wheels exactly, and reducing drag.

All in all I saved 250 + 75 + 109 + 100 = 534kg on my airframe, and was able to carry an extra 50kg fuel with that, so my launch mass is 484kg lighter. Bonus, it looks like some scifi movie fighter. But how did I do?

 

AeWQtSH.png

Way better than a mere 5,000km! This little thing is only a bit lighter, but has half the thrust of the Voyager-styled plane, so I was surprised to even see it take off, and I was worried I wouldn't be able to climb efficiently.

For both planes, the flight plan is simple: fly prograde, but don't let yourself pitch up so high that you slow down, and don't let yourself pitch down. That way you'll climb nicely out of the airport, then when your lift is only barely sufficient, you will naturally level out. As you burn fuel, you'll slowly climb, naturally, because you're lighter but have the same lift. And you'll speed up a bit, and burn less fuel per time unit -- which is a double-whammy for fuel per distance unit.

For my single-engine plane I wrote a kOS script. I've since improved it, so that it can glide much further and splash the plane down without losing the intake. The tiny single-engine plane is remarkable: I was able to splash down nearly with full fuel, then lift off from the water. I'm testing it now on the twin-engine Voyager-style plane. Feel free to copy it for your own purposes:

Spoiler

print "Level-flight is active, waiting...".
wait until ship:altitude > 100.

parameter glideAngle is 5.

local state is "climbing".

local targetPitch is glideAngle.
lock steering to heading(90, targetPitch).

local speed0 is ship:velocity:surface:mag.

until false {
  local flightPathAngle is 90 - vang(ship:up:vector, ship:velocity:surface).
  local speed1 is ship:velocity:surface:mag.

  local frameTime is 0.1.
  if state = "climbing" {
    set targetPitch to max(0, flightPathAngle).
    if speed1 < speed0 { set state to "topping". }
    if flightPathAngle < 0 { set state to "falling". }
    if ship:availableThrust = 0 { set state to "gliding". }
  } else if state = "topping" {
    // don't change targetPitch.
    if flightPathAngle < targetPitch { set state to "climbing". }
  } else if state = "falling" {    set targetPitch to min(0, flightPathAngle + glideAngle).
    if flightPathAngle > 0 { set state to "climbing". }
    if ship:availableThrust = 0 { set state to "gliding". }
  } else if state = "gliding" {
    set frameTime to 1. // save power!
    if flightPathAngle > -glideAngle { set targetPitch to flightPathAngle. }
    else { set targetPitch to min(-glideAngle, flightPathAngle + glideAngle). }
    if ship:availableThrust > 0 { set state to "falling". }
  }

  set speed0 to speed1.
  print state + " / pitch " + round(targetPitch, 2) + " / path " + round(flightPathAngle, 2).
  wait frameTime.
}

 

Link to comment
Share on other sites

I have improved on the autopilot. Now it takes off automatically, throttles back to avoid flying too fast (about 200 m/s is just about right), and can pretty much land automatically (if I lower the gear manually). Bit of a rough landing, but hey, nothing broke. 11,263km -- nearly 3,000 km farther on the same amount of fuel, just with a better autopilot.

NFXgTXM.png

 

Also, here's a better picture of the bird, gliding at dawn, with Minmus in the background:

A5ULSEg.png

 

The code to go with that flight:

Spoiler

// m/s. How fast should we go at rollback (on liftoff)?
parameter rollbackSpeed is 35.

// Degrees. When we rollback, how far should we pull up?
// Don't set it so high we get a tail strike.
parameter rollbackAngle is 10.

// Degrees. How far below the horizon should we point when gliding?
// Also: what's our max AoA when we can't keep our altitude?
parameter glideAngle is 5.

// Degrees. What AoA should we keep when in level flight?
// Strongly recommended you build a plane that works at 0 AoA!
parameter levelAngle is 0.

// m/s. How fast should we target? We'll be at full throttle until we reach
// this speed; then we'll throttle back to match it.
parameter levelSpeed is 200.

// (0,1]. What's the minimum throttle we should use?
// We internally enforce that the minThrottle is at least 1e-3 because
// otherwise the math goes wonky and even the physics explodes.
parameter minThrottle is 0.1.

print "Level-flight is active.".
print "Glide angle: " + glideAngle.
print "Level angle: " + levelAngle.
print "Level speed: " + levelSpeed.
print "Min throttle:" + minThrottle * 100 + " %".

local state is "".
local speed0 is 0.

// Update targetPitch and targetHeading to change the cooked steering.
// If you don't update them, we use last frame's version.
// We don't do anything smart about using roll to turn.
local targetPitch is glideAngle.
local targetHeading is 90.

// In phases other than gliding, we lock throttle to the targetThrottle.
// In gliding we save power and unlock the throttle since we can't thrust anyway
local targetThrottle is 1.

// If min throttle falls to 1e-15, sometimes, somehow, we break physics and
// get runaway phantom forces. So don't let that happen.
set minThrottle to max(1e-3, minThrottle).
// Normally we refresh at 10 Hz (0.1s) but when gliding we go into power-saving
// mode at 1 Hz.
local refreshTime is 0.1.

function horizontalSpeed2 {
  local y2 is ship:velocity:surface:y ^ 2.
  local v2 is ship:velocity:surface:sqrmagnitude.
  return v2 - y2.
}
function horizontalSpeed {
  return sqrt(horizontalSpeed2).
}
function controlThrottle {
  local v2 is horizontalSpeed2().
  local v_desired2 is levelSpeed ^ 2.
  local throttle_desired is targetThrottle * v_desired2 / v2.
  local throttle_diff is throttle_desired - targetThrottle.
  local throttle_diff_per_refresh is (throttle_diff / 10.0) * refreshTime.
  local new_throttle is targetThrottle + throttle_diff_per_refresh.
  set targetThrottle to max(min(new_throttle, 1), minThrottle).
}

function switchToGliding {
  set state to "gliding".
  set refreshTime to 1.
  unlock throttle.
}

// function gliding() { inlined below }

function switchToClimbing {
  set state to "climbing".
  set refreshTime to 0.1.
  set speed0 to horizontalSpeed().
  lock throttle to targetThrottle.
}

function execClimbing {
  parameter flightPathAngle.
  set targetPitch to max(levelAngle, flightPathAngle).
  controlThrottle().
  printPoweredState(flightPathAngle).

  local speed1 is horizontalSpeed().
  if ship:availableThrust = 0 {
    switchToGliding().
  } else if flightPathAngle < levelAngle {
    switchToFalling().
  } else if targetThrottle > 0.99 and speed1 < speed0 {
    switchToToppingOut().
  }
  set speed0 to speed1.
}

function switchToToppingOut {
  set state to "topping-out".
  set refreshTime to 0.1.
  lock throttle to targetThrottle.
}
function execToppingOut {
  parameter flightPathAngle.
  // don't set targetPitch.
  controlThrottle().
  printPoweredState(flightPathAngle).

  if ship:availableThrust = 0 {
    switchToGliding().
  } else if flightPathAngle < targetPitch {
    switchToClimbing().
  }
}

function switchToFalling {
  set state to "falling".
  set refreshTime to 0.05.
  lock throttle to targetThrottle.
}

function execFalling {
  parameter flightPathAngle.
  set targetPitch to min(levelAngle, flightPathAngle + glideAngle).
  controlThrottle().
  printPoweredState(flightPathAngle).

  if ship:availableThrust = 0 {
    switchToGliding().
  } else if flightPathAngle > 0 {
    switchToClimbing().
  }
}

function printPoweredState {
  parameter flightPathAngle.
  print state + " / pitch " + round(targetPitch, 2) + " / path " + round(flightPathAngle, 2) + " / T " + round(targetThrottle*100) + "%".
}
///////////////////////////////////////////////////////////////////////////
// Implementing the autopilot.

// Time for liftoff!
if ship:status = "LANDED" or ship:status = "SPLASHED" or ship:status = "PRELAUNCH" {
  print "lifting off; takeoff speed " + rollbackSpeed + " at pitch " + rollbackAngle.
  sas on.
  lock throttle to 1.
  stage.
  wait until horizontalSpeed() > rollbackSpeed.
  print "rolling back to " + rollbackAngle.
  sas off.
  set targetPitch to rollbackAngle.
  lock steering to heading(90, targetPitch).
  wait until ship:verticalspeed > 1 or ship:altitude > 100.
  print "liftoff!".
  gear off.
  wait until ship:altitude > 100.
}

sas off.
lock steering to heading(targetHeading, targetPitch).

switchToClimbing().
until false {
  local flightPathAngle is 90 - vang(ship:up:vector, ship:velocity:surface).

  if state = "gliding" {
    // If flightpathangle > -glideAngle then flightPathAngle,
    // else if flightPathAngle + glideAngle > -glideAngle then -glideAngle,
    // else flightPathAngle + glideAngle
    set targetPitch to min(max(flightPathAngle, -glideAngle), flightPathAngle + glideAngle).
    print "gliding pitch " + round(targetPitch, 2).
    if ship:availableThrust > 0 { switchToFalling(). }
  }
  else if state = "climbing" { execClimbing(flightPathAngle). }
  else if state = "topping-out" { execToppingOut(flightPathAngle). }
  else if state = "falling" { execFalling(flightPathAngle). }

  wait refreshTime.
}

 

 

Link to comment
Share on other sites

This thread is quite old. Please consider starting a new thread rather than reviving this one.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...