Эта статья — не рецепт, а разбор реальных отказов. Скрипт вывода спутника на орбиту 4758 × 4592 км писался в четыре захода, и каждый заход упирался в отдельную проблему. Ошибки типовые: на них наступит любой, кто попробует автоматизировать подъём.
Готовые рабочие куски — в kOS: готовые рецепты, отладка языка — в kOS: отладка и лимиты.
Заход 1: ракета кувыркается
Симптом. На подъёме угол атаки уходит за 90°, аппарат теряется.
Что было в скрипте. Разворот начинался с высоты 250 м, тангаж задавался формулой от высоты, без оглядки на то, куда аппарат летит.
Разбор. Две ошибки, обе мои:
- Разворот по высоте, а не по скорости. На 250 м скорость ещё мизерная, рули и подвес не работают — команда наклона просто разворачивает корпус боком к потоку.
- Нет ограничения угла атаки. Скрипт командовал абсолютный тангаж независимо от вектора скорости. Это и есть способ сломать ракету.
Лечение. Разворот начинать по скорости (70 м/с) и никогда не отходить от вектора скорости дальше допуска:
LOCAL vp IS 90 - VANG(SHIP:UP:VECTOR, SHIP:VELOCITY:SURFACE). // тангаж потока
SET cmd TO MAX(vp - AOA, MIN(vp + AOA, want)). // зажали
Это и есть настоящий гравитационный разворот: аппарат не спорит с набегающим потоком.
Заход 2: кувыркается снова, до 147°
Ограничение угла атаки помогло не полностью. Причина оказалась не в коде.
Аэродинамическая неустойчивость. На аппарате стояли рули — и стояли они на верхней ступени. Центр давления оказался впереди центра масс. Такая ракета неустойчива принципиально: отклонение растёт само, а не гасится. Ни маховики, ни подвес, ни РСУ в плотном воздухе этого не удержат.
Правило: аэродинамические поверхности ставятся ниже центра масс. Оперение сверху — это дротик, воткнутый наоборот.
Скрипт может лишь смягчить последствия:
- держать угол атаки около нуля;
- ограничить скоростной напор — чем меньше напор, тем меньше валящий момент;
- держать РСУ включённым на атмосферном участке;
- смягчить рулевой менеджер:
SET STEERINGMANAGER:MAXSTOPPINGTIME TO 4.— жёсткие команды сами раскачивают неустойчивый аппарат.
Отдельная ошибка режима восстановления. При срабатывании сторожа я ронял тягу до 0,25 — и вместе с ней пропадала управляющая сила подвеса. Восстанавливаться становилось нечем. Правильно — резать тягу не ниже 0,45.
Заход 3: не хватило топлива
Управление наконец сработало, ракета вышла на орбиту — и встала с пустыми баками, не добрав перицентр.
Телеметрия говорит точно:
t=16с 1,2 км 167 м/с тяга 0.65
t=40с 7,1 км 308 м/с тяга 0.54
t=88с 28,1 км 672 м/с тяга 0.39
t=112с 45,4 км 811 м/с апоцентр 80 км
Разбор. Ограничитель перегрузки держал TWR = 2,0 и душил тягу до 0,36–0,65 всю дорогу. Ракета лезла вверх слишком круто и медленно: на 36 км всего 807 м/с при наклоне 66°. К апоцентру она подошла с горизонтальной скоростью 460 м/с вместо нужных 2280.
Итог — циркуляризация в 1816 м/с вместо нормальных 250. Полторы тысячи метров в секунду выброшено, ровно их и не хватило.
Урок. Перестраховка в управлении оплачивается топливом. Ограничитель перегрузки нужен только в плотном воздухе; выше 20 км он вредит.
Лечение — расписание тангажа, а не следование за потоком:
1 км → 85° 10 км → 55° 30 км → 25°
5 км → 70° 20 км → 40° 45 км → 12° → 60 км → 5°
При этом ограничение угла атаки остаётся: расписание задаёт цель, а зажим не даёт с ней воевать.
Ошибки, всплывшие попутно
Автостейджер вис на ступени-переходнике
// БЫЛО — ловит только выгорание зажжённого двигателя
FOR e IN el { IF e:IGNITION AND e:FLAMEOUT { RETURN TRUE. } }
Ступень, где двигателя нет вообще — только разделитель или обтекатель, — так не отрабатывает никогда. Аппарат зависал без тяги, и снаружи это выглядело как «почему мы не жжём двигатель».
// СТАЛО — ступень нужна, когда живых двигателей не осталось
FUNCTION needStage {
IF STAGE:NUMBER = 0 { RETURN FALSE. }
IF NOT STAGE:READY { RETURN FALSE. }
RETURN liveEngines() = 0.
}
Антенны сорвало потоком
Скрипт снимал науку и сразу передавал её на подъёме. В KSP складная антенна раскрывается сама, когда через неё идёт передача. HG-5 и зонд Zebulon раскрылись на скорости в плотном воздухе и были сорваны.
Правило: в атмосфере науку только собираем, данные лежат в приборах. Передаём в вакууме, раскрыв антенны штатно.
То же касается панелей: PANELS ON только после выхода за границу атмосферы.
Один прибор — два эксперимента
p:GETMODULE("ModuleScienceExperiment") // вернёт только ПЕРВЫЙ
У мачты CA-AMA два научных модуля в одной детали — магнитометр и датчик радиации. Поиск по имени видел один, половина науки терялась. Перебирать надо по индексу:
LOCAL i IS 0.
UNTIL i >= p:MODULES:LENGTH {
IF p:MODULES[i] = "ModuleScienceExperiment" {
LOCAL m IS p:GETMODULEBYINDEX(i).
...
}
SET i TO i + 1.
}
Перицентр круговой орбиты не определён
Классическая схема «круговая опорная → разгон из перицентра» опирается на ETA:PERIAPSIS. У круговой орбиты перицентра фактически нет, и значение шумит.
Лучше один совмещённый прожиг прямо в апоцентре подъёма: эта точка сама становится перицентром переходной орбиты. По топливу то же самое (Гоман есть Гоман), но на один прожиг и одну неопределённость меньше.
Азимут без поправки на вращение
Наивная формула sin(A) = cos(i) / cos(широта) для наклонения 8,5° даёт азимут 81,5°. Но планета уже везёт аппарат на восток со скоростью 175 м/с:
восток = 2279·sin(81,5°) − 175 = 2081 м/с
север = 2279·cos(81,5°) = 337 м/с
азимут = atan2(2081, 337) = 80,8°
Скрипт душил сам себя
Два эффекта, которые легко проглядеть:
- Лимит инструкций.
CONFIG:IPUпо умолчанию 200. Рулевой закон с векторной математикой вLOCK STEERINGне успевает считаться каждый физический тик, и управление отстаёт. На время полёта поднимаем до 800. - Лог на каждом тике. Один полёт записал 9738 строк CSV. Файловый ввод-вывод отъедал у цикла управления больше, чем сам расчёт. Пишем раз в полсекунды.
Левая система координат: ошибка ровно на 180°
Контракт требовал аргумент перицентра 29,6° — то есть чтобы низшая точка орбиты стояла под определённым углом от восходящего узла. Мы вышли на 209,52°. Апоцентр, перицентр, наклонение и долгота узла при этом попали в допуск.
Разница ровно 180° — верный признак перепутанного знака, а не накопленной погрешности.
Причина: в учебниках вектор восходящего узла считается как n⃗ = ẑ × h⃗, где ẑ — ось вращения тела, h⃗ — момент импульса орбиты. Но KSP и kOS работают в левой системе координат, унаследованной от Unity. Векторное произведение там меняет знак, и школьная формула честно указывает на нисходящий узел.
LOCAL n IS VCRS(kNorth, h). // ✗ нисходящий узел — ошибка на 180°
LOCAL n IS VCRS(h, kNorth). // ✓ восходящий
Проверяется мгновенно, если знать, с чем сравнивать: сумма ARGUMENTOFPERIAPSIS + TRUEANOMALY и есть аргумент широты. Свою функцию сверяешь с ней — расхождение либо 0°, либо 180°, третьего не дано.
Правило: любую векторную геометрию в kOS сверяй со штатными элементами орбиты, прежде чем закладывать в манёвр. Знак в левой системе — не то, что стоит выводить в уме.
Чек-лист скрипта вывода
- Разворот начинать по скорости, не по высоте.
- Угол атаки зажат: 3–5° в плотном воздухе.
- Расписание тангажа, а не «лечу по потоку» — иначе заплатишь топливом.
- Ограничитель перегрузки — только ниже 20 км.
- Автостейдж по отсутствию живых двигателей.
- Раскрытие панелей и антенн — только в вакууме.
- Передача науки — только в вакууме.
- Приборы перебирать по индексу модуля.
CONFIG:IPUподнять, лог придушить.- Аэродинамика важнее кода: оперение ниже центра масс.
- Векторную геометрию сверять с эталоном: система координат левая.
Всё перечисленное собрано в готовые модули — структура и правила в kOS: библиотека модулей.