Астра покоряет космос

Прокомментировать Просмотры: 5

В последнее время я стал замечать, что мои коллеги-программисты — как в ближнем круге, так и на просторах сети — всё чаще создают что-то для души, используя ИИ-ассистентов.

Недавно товарищ прислал ссылку на ресурс с интерактивной 3D-моделью Земли, окруженной орбитальными спутниками. Фильтры, траектории, данные из открытых источников — всё как положено. По его словам, на эту работу ушло всего недели две-три. А спустя пару дней он предложил оценить прототип игры: модуль орбитальных миссий с управлением космическим аппаратом на основе реальных физических формул, геймплеем, достижениями вроде минимального импульса, зачетами и памятными звездами. И тут я вспомнил о его давней юношеской мечте разрабатывать игры.

Эта история заставила меня задуматься о собственных нереализованных порывах. В период увлечения первой частью «Мафии» я грезил созданием масштабного симулятора мегаполиса, где каждый житель проживает уникальную жизнь: отводит детей в учебные заведения, спешит на службу, ужинает в кругу семьи, водит автомобиль и посещает врача. А классические научно-фантастические стратегии с глубокой экономической и исследовательской механикой, такие как Master of Orion или X-COM UFO Defense, вдохновляли меня навязчивой идеей переписать их с современной трехмерной графикой.

Однако любые творческие порывы неизменно разбивались о суровую реальность: осознание колоссального объема задач по проектированию и программированию, которые пришлось бы выполнять вечерами, после основной работы (где я, по сути, занимался тем же самым, но исключительно для корпоративного софта).

И вот, спустя полтора-два десятилетия ежедневного перекладывания мыслей в программный код с помощью клавиатуры, меня, судя по всему, настиг классический кризис среднего возраста.

И что теперь делать…? А теперь я Тони Старк, а мой персональный кодекс — это Джарвис.

Идея

Если со вторичной концепцией — технической стороной вопроса — всё предельно ясно даже новичку: я выступаю в роли владельца продукта, применяю гибкие методологии, ставлю задачи нейросети и абсолютно не зацикливаюсь на строках кода, сместив фокус исключительно на ценность для пользователя. Ну разве что попрошу её: «Сделай мне нормальный ООП, слабую связанность элементов, высокую когезию, чистую архитектуру и прочие правильные вещи». И хотя я сам профессиональный разработчик, наверняка буду иногда заглядывать в коммиты, чтобы оценить применимость такого подхода в продакшене.

То с первичной задумкой — определением того, чего именно я хочу — всё оказалось значительно сложнее. Симуляцию мегаполиса со сложным поведением граждан? Космическую экспансию с разветвленным деревом технологий и экономикой? Или детективный триллер? А что если совместить городскую рутину с детективными сюжетными линиями? Но какой в итоге будет игровой процесс? Спровоцирует ли это подлинный интерес у игрока? Литературу по гейм-дизайну я пока не изучал, поэтому придется двигаться исключительно методом проб и ошибок.

И, как следует из заголовка, мой выбор пал на космическую тематику. Поскольку в свое время я зачитывался произведениями Артура Кларка и Станислава Лема (а «Непобедимый» стал одним из первых романов, поразивших меня своей научной достоверностью), мне захотелось придать будущему проекту максимальную реалистичность и прочную научно-фантастическую базу. Что ж, приступим.

Вечер #1: Астра конструирует космос

Сначала был первозданный вакуум.

Во всех известных мне космических стратегиях базовым интерфейсом всегда служит карта звездного неба. Поскольку из меня никудышный гейм-дизайнер, я решил не изобретать велосипед и опереться на проверенную классику, но сразу задать высокую планку достоверности. Что если взять из открытых источников реальные астрономические каталоги с координатами светил, их физическими параметрами и экзопланетами (тут кстати вспомнилась миссия телескопа «Кеплер») и разместить их в трехмерном пространстве? Искусственный интеллект подсказал направления для поиска, попутно уточнив, что главная задача телескопа «Кеплер» заключалась именно в обнаружении экзопланет у удаленных звезд (ссылка — миссия Kepler). В итоге были отобраны два ключевых массива данных: каталог светил в радиусе 100 парсек от Солнца (ссылка — каталог Gaia) и архивы экзопланет (ссылка на экзопланеты или архив NASA).

Для старта этого вполне достаточно. Астра предложила для начала ограничиться сектором в 30 световых лет от Солнца. Возражать я не стал — остальные зоны приберегу для будущих платных дополнений (DLC). В конце концов, в Млечном Пути миллиарды световых лет, стану долларовым миллиардером (…мечтательно закатывает глаза…) 😌

Параллельно я размышлял над концепцией геймплея.

Я решил остановиться на браузерном формате (его проще запускать) в полуторамерной (2.5D) проекции с возможностью вращения камеры. За единицу расстояния принимается один световой год с соблюдением пропорциональных расстояний между звездами. Астра порекомендовала стек технологий: TypeScript + React + Vite + Three.js. Хозяин — барин, возражений нет.

Что же дальше? В памяти всплыла старая статья из журнала «Игромания» рубежа веков (возможно, эта) о Питере Молиньё: трехмерный движок он создал, а вот чем занять игрока внутри него, пришлось придумывать на ходу, в результате чего родилась Fable.

Итак, игровой процесс. Разумеется, в специализированных учебниках по гейм-дизайну этот вопрос детально разобран. Опираясь же на собственный геймерский опыт, отмечу: любая игра базируется на глобальной цели или условиях победы (уничтожение главного босса, мировое господство, экономическое превосходство над конкурентами), которые подкреплены сюжетом и искусственными препятствиями, обеспечивающими необходимый уровень вызова и динамику.

Что сильнее всего цепляло меня в видеоиграх?

  • Реиграбельность. Шахматная доска и набор фигур остаются неизменными на протяжении полутора тысячелетий, однако интерес к игре не угасает. Генератор случайных чисел и комбинаторика в Master of Orion гарантируют колоссальную вариативность партий при неизменных условиях победы, древе технологий и конструкторе кораблей.

  • Атмосфера. Я неоднократно перепроходил криминальную дилогию Mafia (включая Definitive Edition), несмотря на линейность сюжета и жестко фиксированную последовательность событий. Меня привлекала сама возможность погрузиться в эстетику той эпохи.

Пока шел процесс обсуждения, я делал пометки в блокноте относительно того, как эти аспекты могут повлиять на игровую механики:

  • Мы будем снаряжать научно-исследовательские экспедиции к соседним звездным системам. Пока туманно представляю дальнейшее развитие, но, вероятно, добавлю экономическую модель или симуляцию популяции с итоговой целью расселить человечество по галактике и системой достижений.

  • Древесные структуры исследований? Скажем, двигательные установки: от ядерных реакторов к термоядерным, затем фотонным и, наконец, к варп-двигателям. Это напрямую повлияет на экономику и темпы колонизации благодаря сокращению времени в пути.

  • Возможно, стоит ввести иные ветки открытий. Например, сверхсветовую связь — в духе фильма «Пассажиры», где ответ на сообщение идет полвека. Разумеется, здесь я осознанно отхожу от строгой науки, но проект изначально позиционируется как фантастический, так что допустимо.

Джарвис, отфильтровав неизбежный информационный шум, сухо заметил, что при условной скорости крейсерского полета 0.01c путь в 10 световых лет займет около тысячелетия, а при 0.1c — примерно сто лет. Логично… Цифры зацепили мозг: человек едва доживает до векового рубежа. Я решил погрузиться в эту тему глубже.

Немного здравого смысла, физики, математики и личного опыта

Для успешного межзвездного перелета в пределах человеческой жизни (с запасом на исследование планеты назначения) необходимы колоссальные скорости.

Чтобы разогнаться до околосветовых значений и не погибнуть от перегрузок, разгон должен быть плавно распределен во времени — например, с комфортным ускорением в 1g. Это означает 10 лет непрерывного разгона, 10 лет торможения — и мы у цели, например у Проксимы Центавра. Приятный бонус: на протяжении всего полета экипаж чувствует привычную земную гравитацию.

(Допустимы ли перегрузки в 2g или 4g? Кратковременно — да. Мне довелось летать пассажиром на учебно-тренировочном самолете Як-52 при нагрузках около 4g. Сомневаюсь, что даже физически подготовленный человек способен выдерживать такой режим непрерывно 24 часа в сутки на протяжении года или двух. В кабине самолета дыхание сильно затруднено, хотя пилотаж длился всего 15–20 секунд с перерывами. Ощущения в голове специфические, а мышечная боль давала о себе знать еще несколько дней после приземления).

Двадцать лет непрерывной работы двигателей требуют огромных запасов рабочего тела. Чем массивнее топливные баки, тем тяжелее корабль, что в свою очередь требует еще большего объема топлива.

Перед нами классическая формула Циолковского (в вариации через эффективную скорость истечения реактивной струи).

Астра покоряет космос

Для скоростей, близких к скорости света, используется релятивистское уравнение Аккеррета:

\eta=\frac{v_e}{c}\ln\frac{m_0}{m_1}\eta=\operatorname{atanh}\frac vc

Некоторые исследователи (назову их так, допуская студенческий характер источников) в теоретических работах предполагают следующие характеристики гипотетических двигателей:

(Прекрасные базовые параметры для проектирования игрового древа технологий. Кстати, в упомянутой второй статье в качестве топлива рассматривается смесь протия и лития-7 — отличный повод внедрить в игру различные топливные элементы, влияющие на эффективность двигателя).

Если рассчитать параметры полета до Сириуса (~8.6 световых лет) с ускорением 1g при скорости истечения реактивной струи 10 000 км/с для различных режимов, мы получим следующую сводную таблицу:

Макс. скорость

Разгон до скорости, корабль

Крейсерский участок, земное время

Весь полёт, Земля

Весь полёт, корабль

Топливо на 1 усл. ед. конечной массы

0.98295c

2.304 г.

~0

10.36 г.

4.61 г.

≈ 8.41 10^61

0.95c

1.774 г.

4.56 г.

10.46 г.

4.97 г.

≈ 5.00 10^47

0.90c

1.426 г.

6.77 г.

10.77 г.

5.80 г.

≈ 2.17 10^38

0.70c

0.840 г.

11.18 г.

13.08 г.

9.66 г.

≈ 3.84 10^22

0.50c

0.532 г.

16.60 г.

17.72 г.

15.44 г.

≈ 2.01 10^14

0.10c

0.0972 г. ≈ 35.5 дней

85.90 г.

86.10 г.

85.67 г.

408.90

0.01c

0.00969 г. ≈ 3.54 дня

859.99 г.

860.01 г.

859.97 г.

0.82

Выходит, на термоядерном двигателе мы никуда далеко не улетим? Брать на борт объем топлива, в 400 раз превышающий массу самого корабля, ради 86 лет в пути — сомнительная стратегия.

Как это обыграть в геймплее? Разбить процесс на эпохи, по аналогии с Age of Empires?

  • Досветовая / межпланетная эра (термоядерные двигатели и освоение Солнечной системы)

  • Световая / межзвездная эра (фотонные двигатели и выход за пределы родной системы)

  • Сверхсветовая / галактическая эра (варп-технологии)

Хотя зачем искусственно ограничивать игрока на термояде? Можно добавить ветку исследований анабиоза. Но как смоделировать развитие цивилизации на столетия вперед? Не наскучит ли игроку ожидание? Успеет ли первый экипаж добраться до Сириуса до того, как земляне изобретут варп-двигатель и мгновенно окажутся там раньше пионеров? Это было бы несправедливо.

Вспомнился первый Master of Orion: расстояния там измерялись в парсеках, а базовый двигатель на старте преодолевал ровно один парсек за ход. Учитывая, что летоисчисление начиналось с 2300 года и каждый ход равнялся году, перед нами явно представал сверхсветовой привод… то есть то самое рояльное фантастическое допущение ради игрового процесса.

Ладно, подумаем об этом позже… А пока Астра сгенерировала звездную карту в радиусе 30 световых лет от Солнца. Двадцать минут ожидания — и результат налицо:

Скриншот

Зрелище впечатляет. Я покрутил камеру, проверил интерфейс — критических багов не обнаружилось, в код пока даже не заглядывал.

Выяснился забавный факт: далеко не всем звездам ученые присвоили благозвучные имена, многие обозначены лишь буквенно-цифровыми индексами.

Вечер #2: Астра реализует MVP специальной теории относительности

Итак, звездная карта готова, пора переходить к первому элементу геймплея — межзвездным перелетам. Очевидно, это станет фундаментом всей игры, поэтому мне необходима математическая модель движения, и кое-какие наработки у меня уже есть.

Вчерашние расчеты показали, что термояд мало пригоден для межзвездных расстояний (х400 по топливу и 86 лет до Сириуса). Поэтому я решил сразу стартовать со второй эпохи, опирающейся на гипотетический фотонный двигатель на антивеществе с определенными упрощениями. А гибкая методология позволит внедрить остальные эпохи позже.

Порция теоретической физики и расчетов

Аннигиляция пары «электрон-позитрон» — классический процесс квантовой электродинамики, порождающий два фотона (ссылка — рождение двух фотонов)

e^+ + e^- \to 2\gamma

А квант света по воле судеб 😅 распространяется со скоростью света. Иными словами, в идеальном фотонном двигателе скорость истечения реактивной массы в вакууме равна скорости света.

Пересчитаем нашу таблицу для перелета к Сириусу с ускорением 1g:

Макс. скорость

Разгон до скорости, корабль

Крейсерский участок, земное время

Весь полёт, Земля

Весь полёт, корабль

Топливо на 1 усл. ед. конечной массы

0.98295c

2.304 г.

~0

10.36 г.

4.61 г.

115.30

0.95c

1.774 г.

4.56 г.

10.46 г.

4.97 г.

38.00

0.90c

1.426 г.

6.77 г.

10.77 г.

5.80 г.

18.00

0.80c

1.064 г.

9.14 г.

11.72 г.

7.61 г.

8.00

0.70c

0.840 г.

11.18 г.

13.08 г.

9.66 г.

4.67

0.60c

0.671 г.

13.53 г.

14.98 г.

12.16 г.

3.00

0.50c

0.532 г.

16.60 г.

17.72 г.

15.44 г.

2.00

0.40c

0.410 г.

21.06 г.

21.90 г.

20.12 г.

1.33

0.30c

0.300 г.

28.35 г.

28.96 г.

27.65 г.

0.86

0.20c

0.196 г.

42.80 г.

43.20 г.

42.33 г.

0.50

0.10c

0.097 г. ≈ 35.5 дн.

85.90 г.

86.10 г.

85.67 г.

0.22

Совсем другое дело! Крейсерская скорость 0.80c выглядит оптимальной точкой баланса: прирост скорости на следующие 0.10c требует увеличения массы топлива на 71% по сравнению с предыдущим шагом, а еще через 0.10c — уже на 125%. Делаю пометку в блокноте.

В каком формате я вижу реализацию?

Прежде всего, необходима шкала времени. На данном этапе я использую пошаговую систему, где один шаг равен одному земному году. Пространственная шкала привязана к световым годам, что позволяет проецировать координаты корабля на трехмерную карту с учетом базовых принципов специальной теории относительности Эйнштейновского толка, включая релятивистское замедление времени на борту.

Хотелось бы интерактивности. Конструктор космических аппаратов, заимствованный из других космических стратегий, здесь придется ко двору. В нем можно компоновать судно, добавляя топливные баки, и на наглядных графиках оценивать максимальную скорость и дальность полета.

Разумеется, воспринимать корабль как безликую точку с абстрактной массой скучно. Что если внедрить модульную систему? Корпус, командный отсек, двигатель, топливные модули. А жилой модуль жизнеобеспечения сделать отдельным компонентом.

Это позволит конструировать специализированные беспилотные зонды, разрешая им перегрузки до 10g (поскольку хрупких людей на борту нет) и отправляя их к целевым звездам первыми. Зонд прибудет гораздо быстрее, соберет данные о планетах, после чего игрок сможет снарядить более медленный колонизационный флот с экипажем.

Кроме того, нужен выпадающий список силовых установок с настраиваемыми параметрами (включая скорость истечения). Пока доступен только фотонный привод, но по мере технологического прогресса список пополнится. Условимся для простоты, что каждый модуль весит 1 условную единицу, топливный бак содержит 1 условную единицу топлива, а масса самой конструкции бака нулевая. Двигатели обладают бесконечной мощностью, 100% КПД и способны перемещать корабль любой массы. В дальнейшем эти допущения можно скорректировать ради усложнения игрового процесса.

В завершение интерфейса необходима кнопка «Запустить экспедицию» с выводом телеметрии текущего полета.

Инструкции переданы Астре, 18 минут автономной работы генератора — и результат перед нами:

Скриншоты
Конструктор — 1
Конструктор — 1
Конструктор — 2
Конструктор — 2
Конструктор — 3
Конструктор — 3
Полет на 30 световых лет: на борту пройдет 14 лет, максимальная скорость — 0.89c
Полет на 30 световых лет: на борту пройдет 14 лет, максимальная скорость — 0.89c

Потрясающе… Графики выглядят ровно так, как я себе и представлял.

Заберите мои деньги
Заберите мои деньги

Я немного пощелкал интерфейс: зафиксирован единичный сбой (корабль сформировался, но старт не состоялся), однако воспроизвести ошибку не удалось. По-хорошему стоить перепроверить математику и графические зависимости, исключив вольности со стороны Джарвиса. На первый взгляд всё корректно:

  • Налицо временной сдвиг между земными и бортовыми часами

  • Фазы разгона, торможения и инерциального движения отражены верно (участок инерции — прямая линия)

  • Расход топлива при разгоне выше, чем при торможении, поскольку общая масса корабля к концу маршрута снизилась

Какие еще задумки роились в голове относительно механик?

  • Не каждая звездная система обязана содержать пригодные планеты в зоне обитаемости. Возможно, стоит отказаться от жесткого каталога в пользу процедурной генерации планетных систем, дополнив объект «Звезда» параметром «зона обитаемости», рассчитываемым на основе светимости, массы и температуры (ссылка — расчет обитаемых зон)

  • Интерфейс может ограничивать доступ к информации о звездной системе, эмулируя время распространения радиосигнала со скоростью света от зонда до ближайшей колонии. Преодоление этого ограничения с помощью сверхсветовой связи станет частью древа исследований.

  • Классификация типов планет (каменистые, газовые гиганты, пояса астероидов) с процедурным распределением. Каменистые планеты в зоне обитаемости пригодны для колонизации; пояса астероидов служат богатейшим источником редких химических элементов; газовые гиганты выступают поставщиками водорода и гелия для двигателей и фабрик антиматерии.

    Инфраструктура внутри систем по аналогии с классическими стратегиями: научные комплексы для ускорения исследований, верфи для сборки кораблей, добывающие комплексы.

  • Демографическая модель. При реалистичной временной шкале (один ход равен году) и глобальной цели расселения человечества можно внедрить простые законы — например, удвоение популяции каждые 25 лет при ограничении максимальной продолжительности жизни. Если взять текущий каталог из 270 звезд и стартовую базу в 8 миллиардов человек, то через 150 лет человечество вырастет до 512 миллиардов, то есть примерно по 1.89 миллиарда на звездную систему. Добавим сюда релятивистское замедление времени и условие глобального кризиса: если игрок слишком медлителен, ресурсы Земли исчерпываются, и цивилизация гибнет. В этом определенно что-то есть…

  • Отдельная ветка биологических исследований, где логично разместить технологию анабиоза.

  • Галактическая эра с чистой фантастикой:

    • Антигравитация: нулевая масса корабля и топлива, позволяющая перемещаться на любые расстояния с минимальными энергозатратами

    • Струнный двигатель, игнорирующий массу и цепляющийся за пространственно-временные струны для перемещения объектов со скоростью 0.999999999c

    • Классический варп-привод, обеспечивающий мгновенное перемещение в любую точку пространства как для экипажа, так и для жителей Земли, в обход релятивистских эффектов.

Заключение

Немного программного кода, чтобы не складывалось впечатление, будто ИИ генерировал исключительно картинки

Код выглядит как классическая процедурщина, заботливо завернутая в классы и интерфейсы по первому требованию 😅

src/domain/flight/Physics.ts

export const PHYSICS = Object.freeze({
  c: 299792458,
  g0: 9.80665,
  year: 365.25 * 86400,
});
export const LIGHT_YEAR = PHYSICS.c * PHYSICS.year;

export interface EngineDefinition { readonly id: string; readonly name: string; readonly mass: number; readonly exhaustSpeed: number; readonly thrust: | { readonly kind: "unlimited" } | { readonly kind: "limited"; readonly newtons: number }; readonly model: string; }

/* Mass is in the game's common mass unit; only dimensionless ratios enter this model. / export interface PropulsionModel { readonly id: string; budget( engine: EngineDefinition, initialMass: number, dryMass: number, ): number; massAfter( engine: EngineDefinition, initialMass: number, rapiditySpent: number, ): number; }

export class IdealExhaustModel implements PropulsionModel { readonly id = "ideal-relativistic"; budget(engine: EngineDefinition, initialMass: number, dryMass: number) { return ( (engine.exhaustSpeed / PHYSICS.c) Math.log1p((initialMass - dryMass) / dryMass) ); } massAfter( engine: EngineDefinition, initialMass: number, rapiditySpent: number, ) { return ( initialMass Math.exp((-PHYSICS.c / engine.exhaustSpeed) * rapiditySpent) ); } }

// Stable even for very short routes where cosh(eta) - 1 loses precision. export const coshMinusOne = (eta: number) => 2 * Math.sinh(eta / 2) * 2; export const acoshOnePlus = (x: number) => Math.log1p(x + Math.sqrt(x (x + 2)));

src/domain/flight/FlightPlanner.ts

Здесь вместо atanh используется asinh. Функции вычисляют эквивалентные величины на основе разных входных данных:

  • atanh — отталкивается от скорости судна

  • asinh — отталкивается от прошедшего земного времени

import {
PHYSICS,
LIGHT_YEAR,
acoshOnePlus,
coshMinusOne,
type PropulsionModel,
} from "./Physics";
import type { ShipSpecification } from "./Ship";

export type FlightMode = "stop" | "flyby"; export type FlightPhase = "acceleration" | "coast" | "braking" | "arrived" | "passed"; export interface FlightSample { readonly time: number; readonly properTime: number; readonly distance: number; readonly velocity: number; readonly fuel: number; readonly phase: FlightPhase; } export interface FlightStage { readonly phase: "acceleration" | "coast" | "braking"; readonly start: number; readonly duration: number; }

/* All flight times in seconds and distances in metres. Analytic evaluation, no stepping. / export class FlightPlan { readonly acceleration: number; readonly burnTime: number; readonly burnDistance: number; readonly coastTime: number; readonly duration: number; readonly properDuration: number; readonly stages: readonly FlightStage[]; constructor( readonly ship: ShipSpecification, readonly distance: number, readonly mode: FlightMode, readonly peakRapidity: number, private readonly model: PropulsionModel, ) { this.acceleration = ship.design.accelerationG PHYSICS.g0; const c = PHYSICS.c, a = this.acceleration, eta = peakRapidity; this.burnTime = (c / a) Math.sinh(eta); this.burnDistance = ((c c) / a) coshMinusOne(eta); const burns = mode === "stop" ? 2 : 1; const coastDistance = distance - burns this.burnDistance; this.coastTime = eta > 0 && coastDistance > distance Number.EPSILON 16 ? coastDistance / (c Math.tanh(eta)) : 0; this.duration = burns this.burnTime + this.coastTime; this.properDuration = ((burns c) / a) * eta + this.coastTime / Math.cosh(eta); this.stages = Object.freeze( [ { phase: "acceleration" as const, start: 0, duration: this.burnTime }, { phase: "coast" as const, start: this.burnTime, duration: this.coastTime, }, ...(mode === "stop" ? [ { phase: "braking" as const, start: this.burnTime + this.coastTime, duration: this.burnTime, }, ] : []), ] .filter((stage) => stage.duration > 0) .map((stage) => Object.freeze(stage)), ); Object.freeze(this); }

sample(elapsed: number): FlightSample { if (!Number.isFinite(elapsed) || elapsed < 0) throw new Error("Время должно быть конечным и неотрицательным."); const t = Math.min(elapsed, this.duration), c = PHYSICS.c, a = this.acceleration, p = this.peakRapidity; let eta = 0, spent = 0, x = 0, tau = 0; let phase: FlightPhase = "acceleration"; if (t >= this.duration) { eta = this.mode === "stop" ? 0 : p; spent = (this.mode === "stop" ? 2 : 1) p; x = this.distance; tau = this.properDuration; phase = this.mode === "stop" ? "arrived" : "passed"; } else if (t < this.burnTime) { eta = Math.asinh((a t) / c); spent = eta; x = ((c c) / a) coshMinusOne(eta); tau = (c / a) eta; } else if (t < this.burnTime + this.coastTime) { eta = p; spent = p; phase = "coast"; x = this.burnDistance + c Math.tanh(p) (t - this.burnTime); tau = (c / a) p + (t - this.burnTime) / Math.cosh(p); } else { // Evaluate backwards from arrival to avoid cancellation near the end. eta = Math.asinh((a (this.duration - t)) / c); spent = 2 p - eta; phase = "braking"; x = this.distance - ((c c) / a) coshMinusOne(eta); tau = this.properDuration - (c / a) eta; } return Object.freeze({ time: t, properTime: tau, distance: Math.max(0, Math.min(this.distance, x)), velocity: c Math.tanh(eta), fuel: Math.min( this.ship.fuel, Math.max( 0, this.model.massAfter(this.ship.engine, this.ship.initialMass, spent) - this.ship.dryMass, ), ), phase, }); } }

export class FlightPlanner { constructor(private readonly model: PropulsionModel) {} plan( ship: ShipSpecification, distanceLy: number, mode: FlightMode = "stop", ): FlightPlan { if ( !Number.isFinite(distanceLy) || distanceLy < 0 || (mode !== "stop" && mode !== "flyby") ) throw new Error("Некорректный маршрут."); if (distanceLy > 0 && ship.budget <= 0) throw new Error("Для старта необходимо топливо."); const d = distanceLy LIGHT_YEAR, a = ship.design.accelerationG PHYSICS.g0; if (!Number.isFinite(d) || !Number.isFinite(a)) throw new Error("Маршрут выходит за численные пределы модели."); const burns = mode === "stop" ? 2 : 1; const peak = Math.min( ship.budget / burns, acoshOnePlus((a d) / (burns PHYSICS.c * 2)), ); const result = new FlightPlan(ship, d, mode, peak, this.model); if (!Number.isFinite(result.duration)) throw new Error("Маршрут выходит за численные пределы модели."); return result; } rangeWithStop(ship: ShipSpecification, years: number): number { if (!Number.isFinite(years) || years < 0) throw new Error("Некорректный горизонт прогноза."); const a = ship.design.accelerationG PHYSICS.g0, c = PHYSICS.c, t = years PHYSICS.year; const eta = Math.min(ship.budget / 2, Math.asinh((a t) / (2 c))); const burn = (c / a) Math.sinh(eta); const distance = (((2 c c) / a) coshMinusOne(eta) + Math.max(0, t - 2 burn) c Math.tanh(eta)) / LIGHT_YEAR; if (!Number.isFinite(distance)) throw new Error("Горизонт выходит за численные пределы модели."); return distance; } }

Продолжение следует, если проект не наскучит…

 

Источник

Поделиться:

Похожие статьи

Поиск по играм, новостям и статьям…

Введите не менее двух символов

Введите не менее двух символов