В последнее время я стал замечать, что знакомые программисты в моем круге общения и незнакомые в интернетах вайбкодят что-то для души.
Недавно друг скинул ссылку на сайт, где нарисована 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;
}
}
Возможно будет продолжение, если не наскучит…
Автор: onets


