В этой части будем рассматривать простейший перелёт к другому небесному телу - Муне. Прост он потому, что этот спутник находится в экваториальной плоскости, как и наш космодром. А значит, для перелёта не потребуется подгадывать стартовое окно и выходить на наклонённую орбиту. К тому же, орбита Муны круговая, что ещё сильнее упрощает расчёты.
Естественно, для понимания программы полёта нужно понимать также:
Приступать имеет смысл после того, как
развёрнута ретрансляционная сеть, иначе с передачей науки обратно домой могут возникать существенные трудности.
Межпланетные миссии делятся на
- пролётные
- орбитальные
- импактные
- посадочные
В этой части разберём только первые три типа. Посадка потребует, по-видимому, ещё нескольких статей.
Базовая теория
Сколько нужно дельты?Чтобы попасть в сферу действия Муны, нужно - сюрприз - выйти на орбиту с апоцентром не менее
HMun - RSOI, где
HMun = 12 Мм - высота орбиты Муны (от центра Кербина),
RSOI = 2,4 Мм - радиус её сферы влияния. C опорной орбиты высотой 80 км на такой манёвр потребуется 835 м/с.
Куда целиться?Планировать трансфер, конечно, нужно так, чтобы сама Муна при этом оказалась где-то рядом. Что значит "где-то рядом", можно понять, сравнив радиус сферы влияния с высотой орбиты. Легко рассчитать, что угловое расстояние между центром Муны и краем сферы влияния при взгляде из центра Кербина есть arcsin(2,4/12) = 11,5
o. Таким образом, если планировать трансфер аналогично тому, как планировался трансфер к другому аппарату в предыдущей части, то допустимо в апоцентре оказаться на 10-11 градусов впереди или позади Муны (см. рис. 1). Для "идеальной" трансферной орбиты (зелёная на рис. 1) угол упреждения φ
0 равен 121
o. Формулы для расчёта этого угла приведены в предыдущей части.
Рисунок 1. Углы для трансфера к Муне. φ0 = 121o, φ1 = 11o.Прямая или ретроградная орбита?Для давно играющих не секрет, что выход на ретроградную орбиту Муны требует больше дельты, чем выход на прямую. Почему так? Наклонение орбиты определяется вектором момента импульса аппарата в муноцентрической системе. Поскольку при пересечении границы сферы влияния скорость в кербиноцентрической системе отсчета (КЦСО) не должна меняться, то скорость в муноцентрической СО должна быть равна векторной разности скорости аппарата в КЦСО и скорости самой Муны. Момент импульса в муноцентрической системе тогда будет равен
I = m ([
RM × VK ] - [
RM × VMun ]),
где
RM - вектор от центра Муны к аппарату,
VK и
VMun - скорости аппарата и Муны в КЦСО.
Поскольку скорость Муны относительно Кербина
VMun направлена на восток, то при входе в сферу влияния Муны со стороны Кербина аппарат получает от неё "в подарок" момент, направленный на север, т.е. в направлении положительной орбиты. Скорость же в КЦСО в момент пересечения сферы влияния может быть направлена так, что даёт момент, направленный на юг (рис. 2), и величина этого момента тем больше, чем больше синус угла между скоростью в КЦСО и радиус-вектором "Муна-аппарат". Поскольку этот угол меньше, когда Муна находится впереди аппарата, то при заходе с этого направления орбита относительно Муны всегда будет прямая.
Рисунок 2. Сложение скоростей при пересечении сферы влияния.Оценим минимальную скорость, необходимую для попадания на ретроградную орбиту. Будем считать, что кеоцентрическая скорость в момент входа в сферу действия Муны практически перпендикулярна радиус-вектору "Муна-аппарат", а скорость Муны составляет с ним угол около 45
o. Это значит, что скорость аппарата в момент подхода к границе сферы влияния Муны должна быть не менее
VMun/√2 = 384 м/с. Отсюда можем найти величину большой полуоси орбиты, принимая точку входа на расстоянии примерно
HMun - RSOI/2. Тогда
A ≈ 7,15 Мм. Чтобы перейти на такой эллипс с орбиты 80 км, требуется 865 м/с, апоцентр кеплеровой орбиты будет на высоте около 13,6 Мм.
Выход на ретроградные орбиты полезен, если хочется получить траекторию свободного возврата - когда аппарат, облетев Муну, возвращается обратно на Кербин.
Программируем зонд-импактор
Для этой задачи функция
transfernode, написанная в прошлой части гайда, при вызове без параметров создаёт узел манёвра для траектории столкновения с Муной. Навешивание дополнительной логики работы зонда читателям предоставляется для самостоятельной работы.
Программируем пролётный зонд
При планировании манёвра для выхода на пролётную траекторию наша цель - не просто добиться попадания в сферу действия Муны, но также пройти на заданном расстоянии от естественного спутника. Можно это сделать, конечно, через ту же функцию
transfernode, аккуратно подобрав угол прицеливания, но мы воспользуемся более научным методом.
Дело в том, что kOS может получить траектории после манёвра, которые показываются в игре. Используем их для выхода на нужную высоту аналогично тому, как это делается при планировании манёвров вручную.
Орбиты представлены в виде структуры
ORBIT, имеющей множество полей, из которых в данной части будут интересны следующие:
:apoapsis
:periapsis
:hasnextpatch // тип boolean - есть ли по ходу траектории переход в другую сферу влияния
:nextpatch // тип ORBIT - орбита после перехода
:nextpatcheta // время до перехода в секундах
NB: Попытка получить суффиксы
:nextpatch и
:nextpatcheta приводят к ошибкам, если на орбите в пределах горизонта планирования нет никаких переходов - об этом не нужно забывать! Прежде чем получать
orbit:nextpatch, нужно всегда проверять наличие перехода через
orbit:hasnextpatch.
Напоминаю, что в предыдущих частях также использовался суффикс
orbit:period для получения периода орбиты.
Орбиту можно получить по суффиксу
:orbit для любого объекта, относящегося к классу
ORBITABLE (т.е. для кораблей и планет). Примеры:
print Mun:orbit:apoapsis. // напечатает 11400000
print Kerbin:orbit:hasnextpatch. // если напишет True, то что-то серьёзно не так
if ship:orbit:hasnextpatch {
print ship:orbit:nextpatcheta. // вывод времени до перехода в следующую сферу влияния
}
Просто
orbit означает то же самое, что
ship:orbit, т.е. орбиту текущего аппарата.
Получение орбиты после (идеально выполненного) манёвра производится по суффиксу
:orbit маневрового узла. Чтобы этот суффикс был доступен, узел должен быть добавлен в план полёта командой
add.
//Как сделать не получится
set nd to node(time:seconds + 120, 0, 0, 50). // 50 м/с вперёд
print nd:orbit:periapsis. // будет ошибка, т.к. узел не добавлен в план
//Как надо делать
set nd to node(time:seconds + 120, 0, 0, 50).
add nd.
print nd:orbit:periapsis. // печать перицентра после манёвра
Теперь напишем функцию перехода на орбиту с заданным перицентром у Муны. Делать это будем так: поставим манёвр подъёма апоцентра до орбиты Муны на ближайшее время, а затем будем двигать его вперёд по времени, пока он не приведёт к нужной орбите.
function TransferToMunPe {
parameter targetpe is 50000.
// расчёт dV
local munsma to Mun:orbit:semimajoraxis.
local r0 to orbit:semimajoraxis.
local v0 to velocity:orbit:mag.
local a1 to (munsma + r0)/2. // большая полуось переходной орбиты
local Vpe to sqrt( body:mu * ( 2/r0 - 1/a1 ) ).
local deltav to Vpe - v0.
// поставим манёвр так, чтобы только успеть его выполнить
local dob to deltav*mass/ship:availablethrust.
local nd to node(time:seconds + dob/2 + 30, 0, 0, deltav).
add nd.
// сдвигаем до тех пор, пока не получим нужный перицентр у Муны
until nd:orbit:hasnextpatch and abs(nd:orbit:nextpatch:periapsis - targetpe) < 2500 {
set nd:eta to nd:eta + 0.2.
}
}
"Закороченная" логикаХоть это сразу и не заметно, в функции
TransferToMunPe есть довольно грязный хак. Дело в том, что при входе в цикл
until мы не знаем, попали ли вообще начальным манёвром в Муну, а значит, и не можем знать, не вызовем ли ошибку попыткой получить
nd:orbit:nextpatch:periapsis. Но нас спасает способ вычисления логических выражений, применяемый в kOS (и во многих других языках программирования, включая C). Используется то, что операция "и" возвращает истину только тогда, когда истинны оба аргумента. Это означает, что при вычислении выражения
А и В, если
А проверено и ложно - то
В можно уже не проверять! Поэтому до вычисления
abs(nd:orbit:nextpatch:periapsis - targetpe) < 2500 в примере дело доходит лишь в том случае, если истинно
nd:orbit:hasnextpatch, т.е. орбита после манёвра переходит в сферу влияния другого тела.
Программируем орбитальный зонд
Для попадания на стабильную орбиту достаточно добавить скругление орбиты где-то внутри сферы действия Муны. Программа работы аппарата после отделения от носителя будет выглядеть примерно так:
TransferToMunPe(50000).
exenode().
wait until body:name = "Mun".
wait until eta:periapsis < 30.
circularize().
open_antennae(). // открыть антенны для установления связи с ЦУП; предлагается для самостоятельного написания
Следует заметить, что здесь нет автоматического контроля варпа, т.е. этот код в полностью атоматическом режиме будет выполняться о-о-очень долго. Имеет смысл воспользоваться описанной в прошлых частях функцией warpfor, которая использует встроенную переменную
warp для быстрой промотки длинных промежутков времени. Тогда полная циклограмма работы спутника с момента отделения от носителя выглядит так:
TransferToMunPe(50000).
exenode().
warpfor(orbit:nextpatcheta).
wait until body:name = "Mun". // убеждаемся, что действительно попали в сферу влияния Муны
warpfor(eta:periapsis - 30).
circularize().
Для безопасности имеет смысл, как и для коммуникационных спутников, для каждого этапа выполнения выделить свой режим работы.
Пример, показывающий, зачем это надо: предположим, аппарат по дороге попал в тень достаточно надолго, чтобы разрядились батареи. После выхода из тени он заново начнёт работу, но скрипт перезагрузится и начнётся сначала. При этом аппарат будет пытаться заново найти трансфер (который, вообще-то, рассчитывался в предположении круговой орбиты, что уже неверно), затем выполнить манёвр (на что может уже не хватать дельты), затем заново ждать попадания в сферу действия Муны, а по дороге он может снова разрядиться, что приведёт к повторению круга заново.
Опробуем методику путём запуска спутника
KommSat из предыдущей части на орбиту Муны высотой 150 км.
Загрузочный скрипт
Ships/Script/boot/KommSatMun-boot.ks:
if status = "prelaunch" {
copypath("0:/KommSatMun.ks","satprogram.ks").
log "local satmode to 0." to "mode.ks".
}
runpath("mode.ks").
runpath("satprogram.ks").
Ships/Script/KommSatMun.ks:
function TransferToMunPe {
parameter targetpe is 50000.
// расчёт dV
local munsma to Mun:orbit:semimajoraxis.
local r0 to orbit:semimajoraxis.
local v0 to velocity:orbit:mag.
local a1 to (munsma + r0)/2. // большая полуось переходной орбиты
local Vpe to sqrt( body:mu * ( 2/r0 - 1/a1 ) ).
local deltav to Vpe - v0.
// для начала поставим манёвр так, чтобы только успеть его выполнить
local dob to deltav*mass/ship:availablethrust.
local nd to node(time:seconds + dob/2 + 30, 0, 0, deltav).
add nd.
// сдвигаем до тех пор, пока не получим нужный перицентр у Муны
until nd:orbit:hasnextpatch and abs(nd:orbit:nextpatch:periapsis - targetpe) < 2500 {
set nd:eta to nd:eta + 0.2.
}
}
function circularize {
local th to 0.
local Vcircdir to vxcl( up:vector, velocity:orbit ):normalized.
local Vcircmag to sqrt(body:mu / body:position:mag).
local Vcirc to Vcircmag*Vcircdir.
local deltav to Vcirc - velocity:orbit.
lock steering to lookdirup( deltav, up:vector).
wait until vang( facing:vector, deltav ) < 1.
lock throttle to th.
until deltav:mag < 0.05 {
set Vcircdir to vxcl( up:vector, velocity:orbit ):normalized.
set Vcircmag to sqrt(body:mu / body:position:mag).
set Vcirc to Vcircmag*Vcircdir.
set deltav to Vcirc - velocity:orbit.
if vang( facing:vector, deltav ) > 5 {
set th to 0.
}
else {
set th to min( 1, deltav:mag * ship:mass / ship:availablethrust ).
}
wait 0.1.
}
set th to 0.
set ship:control:pilotmainthrottle to 0.
unlock throttle.
}
function AlignToSun {
lock steering to lookdirup(north:vector,Sun:position) + R(0,0,90).
}
function warpfor {
// warp:xTime (0:1) (1:5) (2:10) (3:50) (4:100) (5:1000) (6:10000) (7:100000)
parameter dt.
set t1 to time:seconds + dt.
if dt < 0 {
print "WARNING: wait time " + round(dt) + " is in the past.".
}
until time:seconds >= t1 {
set warp to round( log10( 0.356*(t1 - time:seconds) ) * 2 ).
wait 0.01.
}
}
function exenode {
local nd to nextnode.
local done to False.
local g0 to Kerbin:mu/Kerbin:radius^2.
list engines in el.
local ispeff to 0.
local ff to 0. // fuel flow - потребление топлива
local tt to 0. // total thrust - тяга
for e in el {
set ff to ff + e:availablethrust/max(e:isp,0.01)/g0.
set tt to tt + e:availablethrust.
}
if tt = 0 {
print "ERROR: No active engines!".
set ship:control:pilotmainthrottle to 0.
return.
}
set ispeff to tt/ff.
set dob to mass/ff*(1 - constant:e^(-nd:deltav:mag/ispeff)).
print "Burn duration: " + round(dob) + " s, Max acc: " + round(ship:availablethrust/(mass - dob*ff),1) + " m/s^2 ".
warpfor(nd:eta-dob/2-60).
sas off.
print "Turning ship to burn direction.".
local np to lookdirup(nd:deltav,up:vector).
lock steering to np.
wait until vang( np:vector,facing:vector ) < 0.05 and ship:angularvel:mag < 0.05.
warpfor(nd:eta-dob/2-7).
print "Burn start " + round(dob/2) + " s before node.".
lock throttle to 0.
lock steering to lookdirup(nd:deltav,up:vector).
wait until nd:eta <= dob/2.
local dv0 to nd:deltav.
until done {
lock throttle to min( nd:deltav:mag * mass / ship:availablethrust, 1).
if vdot(dv0, nd:deltav) < 0 {
print "Overshoot, remain dv " + round(nd:deltav:mag,1) + "m/s, vdot: " + round(vdot(dv0, nd:deltav),1).
lock throttle to 0.
break.
}
if nd:deltav:mag < 0.1 {
wait until vdot(dv0:normalized, nd:deltav) < 0.01.
lock throttle to 0.
print "End burn, remain dv " + round(nd:deltav:mag,1) + "m/s ".
set done to True.
}
}
unlock steering.
set ship:control:pilotmainthrottle to 0.
remove nd.
unlock throttle.
}
function open_antennae {
set dish to ship:partsnamed("HighGainAntenna5")[0].
set d to dish:getmodule("ModuleRTAntenna").
d:doevent("activate").
d:setfield("target", Kerbin).
print "Satellite deployed to operational orbit.".
}
function nextmode {
parameter newmode is satmode+1.
set satmode to newmode.
log "set satmode to " + newmode + "." to "mode.ks".
AlignToSun().
}
if body:name = "Mun" { nextmode(4). } // на случай перезагрузки по дороге и включения уже около Муны
function satprogram {
if satmode = 0 {
wait until status="orbiting".
stage.
nextmode().
}
if satmode = 1 {
TransferToMunPe(150000).
nextmode().
}
if satmode = 2 {
exenode().
nextmode().
}
if satmode = 3 {
wait 15. // чтобы успеть развернуться солнечными панелями к свету
warpfor(orbit:nextpatcheta).
nextmode().
}
if satmode = 4 {
wait until body:name = "Mun". // убеждаемся, что действительно попали в сферу влияния Муны
wait 2.
warpfor(eta:periapsis - 30).
nextmode().
}
if satmode = 5 {
circularize().
nextmode().
}
if satmode = 6 {
open_antennae().
nextmode().
}
until false AlignToSun().
}
satprogram().
Скрипты написаны для помещения спутника на опорную орбиту через Cheat Menu (по Alt+F12 в Windows / RShift + F12 в Linux). Для вывода ракетой всё модифицируется аналогично тому, как это организовано в
части 2.
В этом тесте методика в целом работает, но из-за низкой тяговооружённости манёвр выполняется слишком долго, и реальная высота орбиты оказывается ниже желаемых 150 км. В таких случаях имеет смысл сделать коррекции курса для подстройки орбиты. В продолжении разберём, как выполнять эти коррекции и как вычислить время начала коррекции по высоте, на которой она должна производиться.
Скрипты и крафт: