Skip to content

Instantly share code, notes, and snippets.

@agoodman
Created June 14, 2022 01:40
Show Gist options
  • Save agoodman/2341c28156e7537edb4effb0fe8f8948 to your computer and use it in GitHub Desktop.
Save agoodman/2341c28156e7537edb4effb0fe8f8948 to your computer and use it in GitHub Desktop.
Second stage kOS script for reusable orbital operations
// SpaceX-style starship orbital ascent and mun transfer and landing
// by aubranium
//
// designed to be used with FatBoyMk2.craft
//
// hoverslam code borrowed from an unknown SpaceX engineer on the internet and modified
set runmode to 1.
clearscreen.
set targetAltitude to 80000.
set munarAltitude to 25000.
set targetLng to -30.
set maxTWR to 3.
set targetTWR to 1.25.
set circularThreshold to 0.05.
set decelMult to 1.0.
set landingOffset to 36.8.
set iTWR to 0.
set steering to up.
set throttle to 0.
wait 2.
// TODO
//function secantSolver {
// parameter
//}.
function optCircularNode {
parameter targetPe.
if hasnode = false {
print "no node!".
return.
}
lock periapsisError to targetPe - nextnode:orbit:periapsis.
lock periapsisCondition to periapsisError < 100.
lock pFactor to 1 - (nextnode:orbit:periapsis / targetPe).
print "step, P, Pe, error".
set ki to 0.
until periapsisCondition {
set nextnode:prograde to nextnode:prograde * (1 + 0.1 * pFactor).
print ki + ", " + pFactor + ", " + nextnode:prograde + ", " + periapsisError.
set ki to ki + 1.
}
return.
}.
function optCircularNodeBisect {
// given an existing node, optimize it for circular orbit result
parameter targetAp, targetPe.
if hasnode = false {
print "no node!".
return.
}
set scoreFunction to {
return abs(ship:orbit:apoapsis-targetAp) + abs(ship:orbit:periapsis-targetPe).
}.
set maxPrograde to 2300.
set minPrograde to 1.
set ki to 0.
print "step, min, max, lower, upper".
until ki > 1000 OR (maxPrograde - minPrograde)<5 {
// compute top half score
set nextnode:prograde to maxPrograde.
set upperScore to scoreFunction:call().
// compute bottom half score
set nextnode:prograde to minPrograde.
set lowerScore to scoreFunction:call().
if upperScore < lowerScore {
set maxPrograde to (maxPrograde + minPrograde)/2.
} else {
set minPrograde to (maxPrograde + minPrograde)/2.
}
print ki + ", " + minPrograde + ", " + maxPrograde + ", " + lowerScore + ", " + upperScore.
set ki to ki + 1.
}
return.
}.
function optNextNodeCircular {
// given an existing node, optimize it for circular orbit result
parameter targetAp, targetPe.
if hasnode = false {
print "no node!".
return.
}
// score function is abs(ap-targetAp)+abs(pe-targetPe)
set scoreFunction to {
return abs(ship:orbit:apoapsis-targetAp) + abs(ship:orbit:periapsis-targetPe).
}.
set initialGuess to list(nextnode:eta, 1, 1, 1).
optNode(scoreFunction, initialGuess).
return.
}.
function optNextNodeApoapsis {
parameter targetAp.
if hasnode = false {
print "no node!".
return.
}
set scoreFunction to {
return abs(nextnode:orbit:apoapsis-targetAp).
}.
set initialGuess to list(nextnode:eta, 1, 1, 1).
optNode(scoreFunction, initialGuess).
return.
}.
function optNextNodePeriapsis {
parameter targetPe.
if hasnode = false {
print "no node!".
return.
}
// score function is abs(pe-targetPe)
set scoreFunction to {
return abs(nextnode:orbit:periapsis-targetPe).
}.
set initialGuess to list(nextnode:eta, 1, 1, 1).
optNode(scoreFunction, initialGuess).
return.
}.
function optNodeRandomWalkPeriapsis {
parameter targetPe.
if hasnode = false {
print "no node!".
return.
}
set scoreFunction to {
return abs(nextnode:orbit:periapsis-targetPe). // + nextnode:burnvector:mag.
}.
set steps to list(1, 1, 1, 1).
set mask to list(0, 1, 1, 1).
optNodeRandomWalk(scoreFunction, steps, mask).
return.
}.
function optNodeRandomWalkNextPeriapsis {
parameter targetPe.
if hasnode = false {
print "no node!".
return.
}
set scoreFunction to {
return abs(orbit:nextpatch:periapsis-targetPe).
}.
set steps to list(1, 1, 1, 1).
set mask to list(1, 1, 1, 1).
optNodeRandomWalk(scoreFunction, steps, mask).
return.
}.
function optNodeRandomWalkCircular {
parameter targetAp, targetPe.
if hasnode = false {
print "no node!".
return.
}
set scoreFunction to {
return abs(nextnode:orbit:apoapsis-targetAp) + abs(nextnode:orbit:periapsis-targetPe). // + nextnode:burnvector:mag.
}.
set steps to list(1, 1, 1, 1).
set mask to list(0, 1, 1, 1).
optNodeRandomWalk(scoreFunction, steps, mask).
return.
}.
function optNodeRandomWalk {
parameter scoreFunction, steps, mask.
set referenceScore to scoreFunction:call().
set axes to list("ETA", "RADIAL", "NORMAL", "PROGRADE").
set maxStep to 1000.
set ka to 0.
set ki to 0.
set kt to 0.
set magnifier to 1.
set direction to 1.
print "step, ka, kt, axis, delta, eta, rad, norm, pro, score".
until ki > maxStep OR kt > 12 {
if mask[ka] = 1 {
set cache to nextnode.
set nextnode to node(cache:time, cache:radialout, cache:normal, cache:prograde).
if ka = 0 {
set nextnode:eta to nextnode:eta + steps[ka] * direction * magnifier.
} else if ka = 1 {
set nextnode:radialout to nextnode:radialout + steps[ka] * direction * magnifier.
} else if ka = 2 {
set nextnode:normal to nextnode:normal + steps[ka] * direction * magnifier.
} else if ka = 3 {
set nextnode:prograde to nextnode:prograde + steps[ka] * direction * magnifier.
}
set score to scoreFunction:call().
if score > referenceScore {
set nextnode to cache.
if ka + 1 < axes:length {
set ka to ka + 1.
} else {
set direction to -direction.
if direction < 0 {
set ka to 1.
} else {
set ka to 0.
set magnifier to magnifier * 0.5.
}
}
set kt to kt + 1.
} else {
set kt to 0.
set referenceScore to score.
}
print ki + ", " + ka + ", " + kt + ", " + axes[ka] + ", " + steps[ka] * direction + ", " + nextnode:eta + ", " + nextnode:radialout + ", " + nextnode:normal + ", " + nextnode:prograde + ", " + referenceScore.
set ki to ki + 1.
} else {
if ka + 1 < axes:length {
set ka to ka + 1.
} else {
set ka to 0.
}
}
wait 0.05.
}.
return.
}.
function optNode {
parameter scoreFunction, axisGuesses.
// input axes are [eta, radial, normal, prograde].
set axes to list("ETA", "RADIAL", "NORMAL", "PROGRADE").
set axisValues to axisGuesses.
// initial weights are uniform
set axisWeights to list(1, 1, 1, 1).
set axisFeedbacks to list(0, 0, 0, 0).
// initial scores are zero
set axisScores to list(0, 0, 0, 0).
// set large initial reference score
set referenceScore to 999.
set dipoles to list(-1, 1).
set magnifier to 0.1.
set maxStep to 20.
set ki to 0.
print "step, ref, seta, srad, snor, spro, weta, wrad, wnor, wpro".
until ki > maxStep {
// establish reference score
set referenceScore to scoreFunction:call().
// synthesize variants for each axis and collect their scores
from { local ka is 0. } until ka >= axes:length step { set ka to ka + 1. } do {
set axis to axes[ka].
set scores to list(0,0).
from { local kd is 0. } until kd >= dipoles:length step { set kd to kd + 1. } do {
// configure node with variant and compute score
set multiplier to 1 + (axisWeights[ka] * dipoles[kd] * magnifier).
if axis = "ETA" {
set nextnode:eta to nextnode:eta * multiplier.
} else if axis = "RADIAL" {
set nextnode:radialout to nextnode:radialout * multiplier.
} else if axis = "NORMAL" {
set nextnode:normal to nextnode:normal * multiplier.
} else if axis = "PROGRADE" {
set nextnode:prograde to nextnode:prograde * multiplier.
}
set scores[kd] to scoreFunction:call().
}
set nd to scores[0] - referenceScore.
set pd to scores[1] - referenceScore.
if (pd > 0 AND nd < 0) OR (pd < 0 AND nd > 0) {
set axisFeedbacks[ka] to 0.25.
} else {
set axisFeedbacks[ka] to -0.25.
}
}
// interpret results
from { local ka is 0. } until ka >= axes:length step { set ka to ka + 1. } do {
set axis to axes[ka].
// compute new weighted centroid
set axisValues[ka] to axisValues[ka] + axisScores[ka].
if axis = "ETA" {
set nextnode:eta to axisValues[ka].
} else if axis = "RADIAL" {
set nextnode:radialout to axisValues[ka].
} else if axis = "NORMAL" {
set nextnode:normal to axisValues[ka].
} else if axis = "PROGRADE" {
set nextnode:prograde to axisValues[ka].
}
// adjust weights based on feedback
set axisWeights[ka] to axisWeights[ka] * (1 + axisFeedbacks[ka]).
}
print ki + ", " + referenceScore + ", " + axisScores[0] + ", " + axisScores[1] + ", " + axisScores[2] + ", " + axisScores[3] + ", " + axisWeights[0] + ", " + axisWeights[1] + ", " + axisWeights[2] + ", " + axisWeights[3].
set ki to ki + 1.
wait 0.05.
}.
return.
}.
until runmode = 0 {
if runmode = 1 { // booster stage
// wait for separation
print "Waiting for deployment".
// TODO: probably should use available fuel capacity instead of mass
lock deployedCondition to ship:mass < 280.
wait until deployedCondition.
set runmode to 2.
}
if runmode = 2 { // separation
print "Waiting for separation".
rcs on.
lock steering to prograde.
wait 30.
set runmode to 3.
}
if runmode = 3 { // orbital ascent
print "Orbital Ascent".
set throttle to 1.
lock apoapsisCondition to ship:apoapsis > 55000.
wait until apoapsisCondition.
set throttle to 0.
wait ship:orbit:eta:apoapsis - 30.
lock apoapsisCondition to ship:apoapsis > 75000.
set throttle to 1.
wait until apoapsisCondition.
set throttle to 0.
set runmode to 4.
}
if runmode = 4 { // plan LKO maneuver
print "Plan LKO Maneuver".
add node(time:seconds + ship:orbit:eta:apoapsis, 0, 0, 0).
optNodeRandomWalkPeriapsis(74000).
print "Found: " + nextnode.
set runmode to 5.
}
if runmode = 5 { // execute LKO maneuver
print "Execute LKO Maneuver".
lock steering to nextnode:burnvector.
set maxAccel to ship:availablethrust / ship:mass.
set burnTime to nextnode:burnvector:mag / maxAccel.
set halfBurn to nextnode:burnvector:mag / 2.
wait nextnode:eta - burnTime / 2.
print "Burning for " + burnTime + "sec".
set throttle to 1.
// when nextnode:burnvector:mag < halfBurn then { lock steering to prograde. }
when nextnode:burnvector:mag < halfBurn / 4 then {
lock throttle to nextnode:burnvector:mag / (halfBurn / 4).
}
lock burnCondition to nextnode:burnvector:mag < 10.
lock periapsisCondition to ship:orbit:periapsis > 74000.
wait until burnCondition OR periapsisCondition.
wait nextnode:eta.
lock burnCondition to nextnode:burnvector:mag < 1.
set throttle to 0.1.
wait until burnCondition OR periapsisCondition.
set throttle to 0.
print "Burn complete".
remove nextnode.
set runmode to 0.
}
if runmode = 6 { // plan MTO maneuver
print "Plan MTO Manveuver".
set target to Mun.
// crude encounter planning
add node(time:seconds + 10, 0, 0, 850).
wait 1.
lock encounterCondition to nextnode:orbit:hasnextpatch.
lock periapsisCondition to nextnode:orbit:nextpatch:periapsis < 800000.
until encounterCondition AND periapsisCondition {
set nextnode:eta to nextnode:eta + 10.
}
print "Found: " + nextnode.
set runmode to 7.
}
if runmode = 7 { // execute MTO maneuver
print "Execute MTO Maneuver".
lock steering to nextnode:burnvector.
set maxAccel to ship:availablethrust / ship:mass.
set burnTime to nextnode:burnvector:mag / maxAccel.
set halfBurn to nextnode:burnvector:mag / 2.
set targetPe to nextnode:orbit:periapsis.
wait nextnode:eta - burnTime / 2.
print "Burning for " + burnTime + "sec".
set throttle to 1.
// when nextnode:burnvector:mag < halfBurn then { lock steering to prograde. }
when nextnode:burnvector:mag < halfBurn / 4 then {
lock throttle to nextnode:burnvector:mag / (halfBurn / 4).
}
lock burnCondition to nextnode:burnvector:mag < 1.
lock encounterCondition to ship:orbit:hasnextpatch.
lock periapsisCondition to ship:orbit:nextpatch:periapsis < targetPe.
wait until burnCondition OR (encounterCondition AND periapsisCondition).
set throttle to 0.
print "Burn complete".
remove nextnode.
set runmode to 8.
}
if runmode = 8 { // plan LMO maneuver
print "Plan LMO Maneuver".
set targetPe to 12000.
add node(time:seconds + 10, 0, 0, 0).
wait 1.
optNodeRandomWalkNextPeriapsis(targetPe).
print "Found: " + nextnode.
set runmode to 9.
}
if runmode = 9 { // execute LMO maneuver
print "Execute LMO Maneuver".
wait ship:orbit:eta:transition.
set maxAccel to ship:availablethrust / ship:mass.
set burnTime to nextnode:burnvector:mag / maxAccel.
set halfBurn to nextnode:burnvector:mag / 2.
wait nextnode:eta - burnTime / 2.
print "Burning for " + burnTime + "sec".
set throttle to 1.
when nextnode:burnvector:mag < halfBurn then {
lock steering to nextnode:burnvector.
}
when nextnode:burnvector:mag < halfBurn / 4 then {
lock throttle to nextnode:burnvector:mag / (halfBurn / 4).
}
lock burnCondition to nextnode:burnvector:mag < 1.
lock periapsisCondition to ship:orbit:periapsis < 12000.
wait until burnCondition OR periapsisCondition.
set throttle to 0.
print "Burn complete".
remove nextnode.
set runmode to 0.
}
if runmode = 15 { // hoverslam
print "Hoverslam".
brakes on.
// lock g to body:mu / (alt:radar + body:radius)^2.
lock g to body:mu / (ship:altitude + body:radius)^2.
lock maxDecel to (ship:availablethrust / ship:mass) - g. // Maximum deceleration possible (m/s^2)
lock stopDist to ship:verticalspeed^2 / (2 * maxDecel * decelMult). // The distance the burn will require
lock idealThrottle to stopDist / alt:radar. // Throttle required for perfect hoverslam
lock impactTime to alt:radar / abs(ship:verticalspeed). // Time until impact, used for landing gear
lock steering to srfretrograde.
wait until alt:radar < 15000.
lock startCondition to alt:radar < stopDist.
wait until startCondition.
lock throttle to idealThrottle.
lock altitudeCondition to alt:radar < landingOffset.
lock verticalSpeedCondition to verticalspeed > -0.1.
when impactTime < 5 then { gear on. }
// wait until altitudeCondition or verticalSpeedCondition.
wait until verticalSpeedCondition.
set throttle to 0.
set runmode to 0.
}
}
print "Landed!".
print ship:geoposition.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment