- BrainTools - https://www.braintools.ru -

Астра пилит космос

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

Недавно друг скинул ссылку на сайт, где нарисована 3д модель Земли и спутники вокруг нее. Фильтры там, орбиты всякие. Данные из открытых источников в интернетах. Сказал что завайбкодил за пару-тройку недель. А еще через пару дней попросил потестить игру. Сделал раздел с орбитальными миссиями, где нужно управлять спутником и его орбитой, на базе некоторой физической модели с формулами. Геймплей, ачивки (уложится в минимальный импульс), звездочки. И тут я вспомнил, что у него была мечта кодить игры.

И тут я подумал – а ведь у меня тоже были какие-то подобные мечты. Когда я играл и восхищался первой Мафией, думал “вот бы закодить симуляцию города, чтобы каждый житель жил свою жизнь, отводил детей в школу, ходил на работу, ужинал, ездил на машине, ходил к доктору”. А когда играл в старые фантастические космические стратегии c исследованиями и производством, типа Master of Orion или X-COM UFO Defense, восхищался проработанностью для тех лет, и мечтал “вот бы переписать их с новой графикой”.

Но все это заканчивалось на осознании того, какой объем усилий нужно вложить дизайн, а затем и программирование, по вечерам, после основной работы (где я по сути делал все тоже самое только для бизнес-приложений).

И вот прошло 15 или даже может уже 20 лет перекладывания мыслей из головы в код с клавиатурой, кажется даже незаметно подкрался кризис среднего возраста.

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

Идея

Так, ну если со вторичной идеей (с технической стороны) все понятно даже ежику – я типа product owner, у меня аджаил, ставлю ЫЫ-шечке задачи, на код вообще по барабану, что там. Фокус на “продукте”. Ну… попрошу только “ты там это давай ООП, слабую связанность, высокую связность, чистую архитектуру, и все такое”). Но яжпрограммист, думаю буду все же иногда заглядывать в код и комиты, чтобы оценить насколько такой подход можно использовать в реальной работе.

То вот с первичной идеей – а чего я собственно хочу – уже сложнее. Симуляцию города и жителей? Космос с деревом исследований и экономической моделью? Может детектив? А!… может быть симуляцию города и жителей с детективными историями? А что дальше? Какой геймплей? А точно интересно будет в это играть? Книжек про гейм-дизайн я не читал (пока), так что начнем с метода проб и ошибок.

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

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

Сначала был вакуум.

А во всех космических стратегиях, в которые мне довелось играть, основным экраном была карта космоса. Гейм-дизайнер из меня так себе, поэтому я решил не отступать от этого стандарта. Но уже тут мне захотелось реалистичности. Что если найти в интернете в открытых источниках список звезд, их координаты, параметры, планеты (тут я вспомнил про телескоп Кеплер) и… разместить их в 2.5д / 3д пространстве? Ясень Джарвис подсказал где искать, но не постеснялся уточнить, что миссия Кеплера заключается в поиске экзопланет у далеких звезд (тынц – Mission name: Kepler [1]). В итоге в интернете были найдены два каталога – каталог звезд на расстоянии 100 парсек от Солнца (тынц – Gaia Catalogue of Nearby Stars [2]) и каталог планет (тынц – Catalogue of Exoplanets [3] или тынц – NASA Exoplanet Archive [4]).

Отлично, для начала пойдет. Астра там еще предложила на первое время ограничиться 30-ю световыми годами от Солнца. Я не стал спорить. Потом буду выпускать DLC с расширениями. У нас же 100500 световых лет в Млечном Пути. Миллионером стану… долларовым (…мечтает…) 😌

Попутно я размышлял об игровом процессе.

Решил начать с того, что это будет браузерная игра (проще запускать), 2.5д, чтобы камеру можно было крутить. Единица пространства 1 световой год, расстояния до звезд пропорциональные. Астра порекомендовала TypeScript + React + Vite + Three.js. Не вопрос, хозяин барин.

Так что у нас там дальше? Вспомнилась статья вроде бы из Игромании 2005-2010-ых (может эта? [5]), про Питера Молиньё – 3д движок он сделал, а чем внутри него заниматься игроку – предстояло еще придумать, в итоге получилась Fable.

Так вот – геймплей. Наверняка в умных книжках гейм-дизайну учат этому. А пока, все что я знаю на собственном опыте [6] – в каждой игре есть некая глобальная цель или условия победы (победить главного босса, захватить мир, обыграть конкурентов), натянутая по возможности на сюжет и палки в колеса игроку, чтобы было по мере сложно и нескучно.

Чем меня увлекали игры?

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

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

Пока суть да дело, я в блокнотик записывал мысли и как эти штуки могли бы повлиять на геймплей:

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

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

  • Можно и другие ветки исследований. Например сверхсветовая связь. Как тот момент в фильме Пассажиры “ответ придет через 55 лет”. Тут я конечно придумываю все менее научную фантастику. Но я и так делаю фантастическую игру, так что сойдет.

Джарвис, среди всего нейрослопа, выдал мне на это, что дескать при условной скорости 0.01с путь на 10 световых лет занимает около 1000 лет, а при 0.1с около 100. Ну, логично… и мой мозг [7] зацепился за эти цифры. Так стоп, человек еле как доживает до 100 лет. И я решил погрузиться этот вопрос.

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

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

Чтобы разогнаться до очень больших скоростей и не сплющиться от перегрузок, разгоняться надо очень медленно. Например 1g. Ну типа 10 лет разгона, 10 лет торможения и вот мы на Проксиме Центавра. И как приятный бонус – весь полет сила тяжести как на Земле.

(Можно ли 2g или 4g? Можно, но не долго. Мне довелось полетать пассажиром на учебно-тренировочном ЯК-52 с перегрузками примерно 4g. Я не думаю, что даже тренированный человек сможет выдерживать такую нагрузку в течение 1-2 лет подряд 24 часа в сутки. В самолете во время перегрузок дышать тяжело было, а они длились типа 5-10-15 секунд за раз в зависимости от виража, и то с перерывами. Плюс неприятное ощущение в голове, типа она болит, но не как обычно. А после этого, уже на земле, я отходил еще 3-5 дней, во всем теле что-то где-то болело)

Чтобы разгоняться и тормозить 20 лет – нужно топлива на 20 лет. Чем больше топлива, тем больше масса корабля, чем больше масса корабля, тем больше нужно топлива.

Так это же у нас формула Циолковского (вариант через эффективную скорость истечения газа)

Δv=v_e cdot lnleft(frac{m_0}{m_f}right)

Для околосветовых скоростей формулы другие – уравнение Аккерета

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

Некоторые ученые (? назову их так, возможно студенты) в некоторых теоретических статьях (источники и/или авторы могут не внушать доверия, но для игры, думаю, сойдет) предполагают следующую скорость истечения гипотетических “газов”:

(Кстати отличные цифры, мне подойдут, когда я буду строить игровое дерево исследований и параметры двигателей. И еще интересный момент – во второй статье идет речь о Proton/Lithium-7 в качестве “топлива” – можно было бы предусмотреть в дереве исследований различные топливные элементы для термоядерного двигателя, что влияло бы на его характеристики).

В итоге, если прикинуть расчет – сколько будет длиться разгон / торможение и сколько понадобится топлива, исходя из скорости истечения “газов” 10 000 км/с, ускорения 1g и полета до Сириуса (~ 8,6 световых лет), и сведем в одну таблицу для разных скоростей, то получим:

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

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

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

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

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

Топливо на 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 световых лет от Солнца – 20 минут ожидания и…

Скриншот
Астра пилит космос - 4

Я впечатлился. Потыкал, покрутил, баги не попались, код не смотрел.

И оказалось, что далеко не всем звездам ученые дали читаемые названия. Часто встречаются буквенно-цифровые коды.

Вечер #2: Астра кодит MVP теории относительности Эйнштейна

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

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

Еще немного теоретической физики и расчетов

Аннигиляция электрона и позитрона – это давняя тема в квантовой электродинамике, где процесс порождает два фотона (тынц – Electron-positron annihilation into two photons in an intense plane-wave field [10])

e^+ + e^- to 2gamma

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

И если пересчитать ту табличку (до Сириуса, ускорение 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.80с крейсерской скорости выглядят довольно оптимально. Для прибавки предыдущих 0.10с скорости требуется на 71% топлива больше от предыдущего уровня, а вот следующие 0.10с уже на 125%. Сделаю заметку в блокноте про геймплей.

Итак, в каком формате я бы хотел видеть это в игре?

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

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

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

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

Да, и хотелось бы выпадающий список двигателей с заданными параметрами (для формул, например скорость истечения “газов”). Пока что там будет только фотонный, но эпохи и научные исследования будут влиять на этот список. И все же условимся, что каждый модуль весит 1 у.е. массы. Бак содержит 1 у.е. расходуемого топлива, а сама конструкция бака невесома (для упрощения мат модели). Так же для упрощения – любой двигатель выдает бесконечную мощность, имеет КПД 100% и одного двигателя всегда хватает на корабль любой массы. Позже можно будет ввести дополнительные бизнес-правила и ограничения, чтобы повысить интерес [11] игрока.

И по завершению – нужна кнопка “Отправить в полет” и показывать информацию о текущем состоянии экспедиции.

Ну что ж, инструкции Астре даны, 18 минут автономной работы и результат:

Скриншоты
Конструктор - 1

Конструктор – 1
Конструктор - 2

Конструктор – 2
Конструктор - 3

Конструктор – 3
Летим, 30 световых лет, на борту пройдет 14 лет, макс скорость 0,89 скорости света

Летим, 30 световых лет, на борту пройдет 14 лет, макс скорость 0,89 скорости света

Абалдеть…!!! И графики примерно как я себе представлял в мыслях.

Шатап энд тейк май мани

Шатап энд тейк май мани

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

  • Разница в земном и бортовом времени

  • Торможение, инерция, разгон присутствуют, скорость по инерции – прямая линия

  • На разгон топлива уходит больше, на торможение меньше (масса корабля меньше к концу путешествия)

Какие еще были мысли насчет геймплея?

  • Не у каждой звезды могли бы быть подходящие планеты в зоне обитаемости. Возможно стоить использовать рандомизатор, вместо реального каталога планет? Да и вообще добавить такой параметр как “зона обитаемости” к объекту “Звезда”. В зависимости от ее размера, светимости, температуры (тынц – HABITABLE ZONES AROUND MAIN-SEQUENCE STARS: NEW ESTIMATES [12])

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

  • Можно было бы определить типы планет (каменистая, пояс астероидов, газовый гигант), рандомно назначать их планетам в звездных системах. И каждый тип обладал бы уникальными характеристиками. Каменистая – для колонии (если в зоне обитаемости), астероиды – как более мощный / объемный / ассортиментный (по сравнению с каменистыми планетами) источник различных ресурсов (химических элементов), а газовые гиганты – как источник водорода и гелия – топлива для двигателей. В том числе, имея фабрику по созданию антиматерии – заполнять баки фотонных двигателей.

  • Да и в целом различные постройки в пределах звездной системы по аналогии с другими играми – научные центры (ускоряют изучение дерева исследований), фабрики для производства кораблей, “шахты” для добычи ресурсов.

  • Возможно какая-то демографическая модель? На данный момент шкала времени у нас реалистичная (один ход = один земной год). И если основная задача игры будет расселение человечества – можно было бы ввести бизнес-правила (с некоторыми допущениями и упрощениями) – типа удвоение населения каждые 25 лет. А выбытие населения только по естественным причинам, и максимальный возраст как параметр игры. Прикину расчеты, без учета выбытия. Сейчас в каталоге 270 звезд, если брать за начало отсчета 8 млрд человек, то через 150 лет будет 512 млрд, или в среднем примерно по 1,89 млрд на звездную систему. И, пожалуй, было бы интересно совместить это с релятивистским замедлением времени во время полетов в космосе. А если еще поставить условие – игрок слишком долго тупит – человечество вымирает, потому что ресурсы Земли исчерпались? Что-то во всем этом такое есть…

  • И тогда уж еще одну ветку био-исследований? Туда кстати можно было бы поместить анабиоз.

  • Галактическая эпоха с абсолютно выдуманной фантастикой:

    • Антигравитация? Корабль без массы, топливо без массы, и на бесконечно малом объеме топлива можно улететь бесконечно далеко

    • Или какой-нибудь супер-струнный двигатель, которому не нужно никакое топливо – цепляется за струну пространства-времени и двигает корабль любой массы больше нуля со скоростью 0,999999999с на любое расстояние

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

Заключение

Все же немного нейро-кода, а то подумаете, что я попросил нейросетку сгенерировать только картинки

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

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

Тут используется asinh, вместо atanh. Оба вычисляют одну и ту же величину, но из разных исходных данных:

  • 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;
  }
}

Возможно будет продолжение, если не наскучит…

Автор: onets

Источник [13]


Сайт-источник BrainTools: https://www.braintools.ru

Путь до страницы источника: https://www.braintools.ru/article/36478

URLs in this post:

[1] тынц – Mission name: Kepler: https://astrobiology.nasa.gov/missions/kepler/

[2] тынц – Gaia Catalogue of Nearby Stars: https://www.cosmos.esa.int/web/gaia/edr3-gcns

[3] тынц – Catalogue of Exoplanets: https://exoplanet.eu/home

[4] тынц – NASA Exoplanet Archive: https://exoplanetarchive.ipac.caltech.edu

[5] может эта?: https://www.igromania.ru/article/20698/Igra_v_boga._Neveroyatnye_priklyucheniya_Pitera_Molinyo_v_mire_videoigr.html

[6] опыте: http://www.braintools.ru/article/6952

[7] мозг: http://www.braintools.ru/parts-of-the-brain

[8] тынц – PLASMA CORE ROCKET ENGINES: https://arxiv.org/pdf/2510.07339

[9] тынц – Fusion Propulsion Constraints Required by Robust Exoplanet Exploration: https://nstopenresearch.org/articles/3-9

[10] тынц – Electron-positron annihilation into two photons in an intense plane-wave field: https://arxiv.org/abs/2003.02231

[11] интерес: http://www.braintools.ru/article/4220

[12] тынц – HABITABLE ZONES AROUND MAIN-SEQUENCE STARS: NEW ESTIMATES: https://iopscience.iop.org/article/10.1088/0004-637X/765/2/131

[13] Источник: https://habr.com/ru/articles/1089396/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1089396

www.BrainTools.ru

Rambler's Top100