В последнее время я стал замечать, что знакомые программисты в моем круге общения и незнакомые в интернетах вайбкодят что-то для души.
Недавно друг скинул ссылку на сайт, где нарисована 3д модель Земли и спутники вокруг нее. Фильтры там, орбиты всякие. Данные из открытых источников в интернетах. Сказал что завайбкодил за пару-тройку недель. А еще через пару дней попросил потестить игру. Сделал раздел с орбитальными миссиями, где нужно управлять спутником и его орбитой, на базе некоторой физической модели с формулами. Геймплей, ачивки (уложится в минимальный импульс), звездочки. И тут я вспомнил, что у него была мечта кодить игры.
И тут я подумал - а ведь у меня тоже были какие-то подобные мечты. Когда я играл и восхищался первой Мафией, думал "вот бы закодить симуляцию города, чтобы каждый житель жил свою жизнь, отводил детей в школу, ходил на работу, ужинал, ездил на машине, ходил к доктору". А когда играл в старые фантастические космические стратегии c исследованиями и производством, типа Master of Orion или X-COM UFO Defense, восхищался проработанностью для тех лет, и мечтал "вот бы переписать их с новой графикой".
Но все это заканчивалось на осознании того, какой объем усилий нужно вложить дизайн, а затем и программирование, по вечерам, после основной работы (где я по сути делал все тоже самое только для бизнес-приложений).
И вот прошло 15 или даже может уже 20 лет перекладывания мыслей из головы в код с клавиатурой, кажется даже незаметно подкрался кризис среднего возраста.
И что теперь...? А теперь я Тони Старк, а кодекс мой Джарвис.
Идея
Так, ну если со вторичной идеей (с технической стороны) все понятно даже ежику - я типа product owner, у меня аджаил, ставлю ЫЫ-шечке задачи, на код вообще по барабану, что там. Фокус на "продукте". Ну... попрошу только "ты там это давай ООП, слабую связанность, высокую связность, чистую архитектуру, и все такое"). Но яжпрограммист, думаю буду все же иногда заглядывать в код и комиты, чтобы оценить насколько такой подход можно использовать в реальной работе.
То вот с первичной идеей - а чего я собственно хочу - уже сложнее. Симуляцию города и жителей? Космос с деревом исследований и экономической моделью? Может детектив? А!... может быть симуляцию города и жителей с детективными историями? А что дальше? Какой геймплей? А точно интересно будет в это играть? Книжек про гейм-дизайн я не читал (пока), так что начнем с метода проб и ошибок.
И как видно из заголовка - я выбрал космос. И, так как я, в свое время, вырос на фантастике Артура Кларка и Станислава Лема (Непобедимый был одним из первых романов, и в котором мне зашел уровень научности) - мне захотелось привнести элементы реалистичности и научной фантастики в то, что я собираюсь сделать. Ну штош, поехали.
Вечер #1: Астра кодит космос
Сначала был вакуум.
А во всех космических стратегиях, в которые мне довелось играть, основным экраном была карта космоса. Гейм-дизайнер из меня так себе, поэтому я решил не отступать от этого стандарта. Но уже тут мне захотелось реалистичности. Что если найти в интернете в открытых источниках список звезд, их координаты, параметры, планеты (тут я вспомнил про телескоп Кеплер) и... разместить их в 2.5д / 3д пространстве? Ясень Джарвис подсказал где искать, но не постеснялся уточнить, что миссия Кеплера заключается в поиске экзопланет у далеких звезд (тынц - Mission name: Kepler). В итоге в интернете были найдены два каталога - каталог звезд на расстоянии 100 парсек от Солнца (тынц - Gaia Catalogue of Nearby Stars) и каталог планет (тынц - Catalogue of Exoplanets или тынц - NASA Exoplanet Archive).
Отлично, для начала пойдет. Астра там еще предложила на первое время ограничиться 30-ю световыми годами от Солнца. Я не стал спорить. Потом буду выпускать DLC с расширениями. У нас же 100500 световых лет в Млечном Пути. Миллионером стану... долларовым (...мечтает...) 😌
Попутно я размышлял об игровом процессе.
Решил начать с того, что это будет браузерная игра (проще запускать), 2.5д, чтобы камеру можно было крутить. Единица пространства 1 световой год, расстояния до звезд пропорциональные. Астра порекомендовала TypeScript + React + Vite + Three.js. Не вопрос, хозяин барин.
Так что у нас там дальше? Вспомнилась статья вроде бы из Игромании 2005-2010-ых (может эта?), про Питера Молиньё - 3д движок он сделал, а чем внутри него заниматься игроку - предстояло еще придумать, в итоге получилась Fable.
Так вот - геймплей. Наверняка в умных книжках гейм-дизайну учат этому. А пока, все что я знаю на собственном опыте - в каждой игре есть некая глобальная цель или условия победы (победить главного босса, захватить мир, обыграть конкурентов), натянутая по возможности на сюжет и палки в колеса игроку, чтобы было по мере сложно и нескучно.
Чем меня увлекали игры?
Реиграбельностью - в конце-то концов, в шахматах одна и та же доска и фигуры вот уже на протяжении полторы тысячи лет, но в них до сих пор играют. Рандомизатор и комбинаторика в Master of Orion тоже выдает невероятное количество вариантов партий, хотя условия победы, дерево исследований и конструктор кораблей там всегда один и тот же.
Атмосферой - в Мафию (первые две части и затем Definitive Edition) я переигрывал несколько раз, хотя там сюжет и последовательность событий прибита гвоздями. Но мне нравилось как будто ощущать себя в той эпохе.
Пока суть да дело, я в блокнотик записывал мысли и как эти штуки могли бы повлиять на геймплей:
Будем снаряжать экспедиции к другим звездам. Пока не знаю, что будет с ними дальше. Может быть какую-нибудь экономическую модель или модель популяции. И цель типа расселить человечество по всему космосу, с ачивками
Дерево исследований? Например двигатели - начнем с ядерных, потом термоядерные, там не знаю, фотонные и в конце варп. Это можно было бы учитывать в экономической модели или модели расселения, потому что скорость быстрее.
Можно и другие ветки исследований. Например сверхсветовая связь. Как тот момент в фильме Пассажиры "ответ придет через 55 лет". Тут я конечно придумываю все менее научную фантастику. Но я и так делаю фантастическую игру, так что сойдет.
Джарвис, среди всего нейрослопа, выдал мне на это, что дескать при условной скорости 0.01с путь на 10 световых лет занимает около 1000 лет, а при 0.1с около 100. Ну, логично... и мой мозг зацепился за эти цифры. Так стоп, человек еле как доживает до 100 лет. И я решил погрузиться этот вопрос.
Совсем чуть-чуть здравого смысла, физики, матана и личного опыта
Если мы летим к другой звезде и хотим прилететь до того как сыграем в ящик + немного времени погулять там, то нужно разогнаться до очень больших скоростей.
Чтобы разогнаться до очень больших скоростей и не сплющиться от перегрузок, разгоняться надо очень медленно. Например 1g. Ну типа 10 лет разгона, 10 лет торможения и вот мы на Проксиме Центавра. И как приятный бонус - весь полет сила тяжести как на Земле.
(Можно ли 2g или 4g? Можно, но не долго. Мне довелось полетать пассажиром на учебно-тренировочном ЯК-52 с перегрузками примерно 4g. Я не думаю, что даже тренированный человек сможет выдерживать такую нагрузку в течение 1-2 лет подряд 24 часа в сутки. В самолете во время перегрузок дышать тяжело было, а они длились типа 5-10-15 секунд за раз в зависимости от виража, и то с перерывами. Плюс неприятное ощущение в голове, типа она болит, но не как обычно. А после этого, уже на земле, я отходил еще 3-5 дней, во всем теле что-то где-то болело)
Чтобы разгоняться и тормозить 20 лет - нужно топлива на 20 лет. Чем больше топлива, тем больше масса корабля, чем больше масса корабля, тем больше нужно топлива.
Так это же у нас формула Циолковского (вариант через эффективную скорость истечения газа)
Для околосветовых скоростей формулы другие - уравнение Аккерета
Некоторые ученые (? назову их так, возможно студенты) в некоторых теоретических статьях (источники и/или авторы могут не внушать доверия, но для игры, думаю, сойдет) предполагают следующую скорость истечения гипотетических "газов":
Газофазные и ядерно-плазменные двигатели (GCNR / PPR) - до 50 000 м/с (50 км/с) (тынц - PLASMA CORE ROCKET ENGINES)
Термоядерные ракетные двигатели - до 0.071c (21 285 км/с) (тынц - Fusion Propulsion Constraints Required by Robust Exoplanet Exploration)
(Кстати отличные цифры, мне подойдут, когда я буду строить игровое дерево исследований и параметры двигателей. И еще интересный момент - во второй статье идет речь о 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 минут ожидания и...
Скриншот

Я впечатлился. Потыкал, покрутил, баги не попались, код не смотрел.
И оказалось, что далеко не всем звездам ученые дали читаемые названия. Часто встречаются буквенно-цифровые коды.
Вечер #2: Астра кодит MVP теории относительности Эйнштейна
Итак, звездная карта у меня теперь есть, и настало время поработать над первым элементом геймплея. Решил начать с космических перелетов. Все же, думаю, это будет одна из основных вещей в игре. И тут мне понадобится математическая модель полетов. Кое что у меня уже есть.
В предыдущий вечер выяснилось, что термояд не очень-то подходит для межзвездных перелетов (x400 топлива и 86 лет до Сириуса). И были мысли насчет эпох. Ну хорошо, начну сразу с межзвездной, в основе которой лежал бы гипотетический (я же все еще делаю фантастическую игру) фотонный двигатель на антиматерии с некоторыми допущениями. А аджаил поможет добавить другие эпохи, но позже.
Еще немного теоретической физики и расчетов
Аннигиляция электрона и позитрона - это давняя тема в квантовой электродинамике, где процесс порождает два фотона (тынц - Electron-positron annihilation into two photons in an intense plane-wave field)
А фотон, по неведомому стечению обстоятельств 😅, разлетается со скоростью света. То есть, для идеального гипотетического фотонного двигателя в вакууме скорость истечения "газов" могла бы равняться скорости света.
И если пересчитать ту табличку (до Сириуса, ускорение 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% и одного двигателя всегда хватает на корабль любой массы. Позже можно будет ввести дополнительные бизнес-правила и ограничения, чтобы повысить интерес игрока.
И по завершению - нужна кнопка "Отправить в полет" и показывать информацию о текущем состоянии экспедиции.
Ну что ж, инструкции Астре даны, 18 минут автономной работы и результат:
Скриншоты




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

Потыкал немного, вроде бы попался один баг (космолет создался, но не смог вылететь) - не удалось повторить. И по-хорошему проверить бы расчеты и графики. Вдруг Джарвис там что-то выдумал. На первый взгляд выглядит норм:
Разница в земном и бортовом времени
Торможение, инерция, разгон присутствуют, скорость по инерции - прямая линия
На разгон топлива уходит больше, на торможение меньше (масса корабля меньше к концу путешествия)
Какие еще были мысли насчет геймплея?
Не у каждой звезды могли бы быть подходящие планеты в зоне обитаемости. Возможно стоить использовать рандомизатор, вместо реального каталога планет? Да и вообще добавить такой параметр как "зона обитаемости" к объекту "Звезда". В зависимости от ее размера, светимости, температуры (тынц - HABITABLE ZONES AROUND MAIN-SEQUENCE STARS: NEW ESTIMATES)
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; } }
Возможно будет продолжение, если не наскучит...

