Underground 2 Revived
Play Need for Speed: Underground 2 (2004) in Chrome and Firefox with WebGPU graphics, Web Audio, up to 120 fps widescreen, controller support, free roam and Online races with friends. Fan project, not
翻譯準備中
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證正在載入完整 README。 以下為已儲存的摘錄。
Браузерная мультиплеерная экономическая стратегия в сеттинге космоса: колонии на планетах, добыча и энергобаланс, дерево технологий, верфь, логистика флота между системами, биржа за криптогривну без посредников, синдикаты со своим Кошем, модулями, технологиями, Брамами и пактами, PvE-экспедиции, колонизация свободных планет, шпионаж с лестницей раскрытия, боты-командиры под управлением языковой модели, рейтинг по вложенным ресурсам и раундовые бои со скорострелом и боевыми технологиями.檢視完整 README ↗
Браузерная мультиплеерная экономическая стратегия в сеттинге космоса: колонии на планетах, добыча и энергобаланс, дерево технологий, верфь, логистика флота между системами, биржа за криптогривну без посредников, синдикаты со своим Кошем, модулями, технологиями, Брамами и пактами, PvE-экспедиции, колонизация свободных планет, шпионаж с лестницей раскрытия, боты-командиры под управлением языковой модели, рейтинг по вложенным ресурсам и раундовые бои со скорострелом и боевыми технологиями.
Играть: spacemmo.fly.dev — регистрация по email без подтверждения, вход через Google или из мини-приложения Telegram. С телефона игра сама предложит поставить себя на домашний экран.
Все расчеты — на сервере. Клиент только рисует состояние и отправляет намерения.
Стек: Node.js + TypeScript (strict, ESM) · Express 5 · Socket.IO ·
PostgreSQL (Prisma 7 + @prisma/adapter-pg) · jose для проверки id_token
Google · HTML/JS/CSS без сборщика · Docker.
Нужны Node.js 24 (как в Dockerfile) и доступная база PostgreSQL (подойдет Neon или локальный Postgres).
npm install
Скопируй пример окружения и заполни его:
cp .env.example .env
| Переменная | Обязательна | Назначение |
|---|---|---|
DATABASE_URL | да | строка подключения к PostgreSQL |
JWT_SECRET | в production | секрет подписи токенов сессии |
PORT | нет | порт сервера, по умолчанию 3000 |
GOOGLE_CLIENT_ID | нет | client id приложения Google. Пустой — кнопки входа через Google просто нет. Секрет не нужен: для проверки id_token хватает client id |
AUTH_EXPOSE_RESET_TOKEN | нет | отдавать код смены пароля прямо в ответе API. Только для стенда: роут открытый, и с этим флагом любой, кто знает чужой email, заберет аккаунт. В production игнорируется |
GEMINI_API_KEY | нет | ключ языковой модели для ботов. Пустой — боты играют по статичному характеру и не отвечают на письма; это штатный режим, а не авария |
GEMINI_MODEL | нет | какую модель звать, по умолчанию gemini-3.5-flash-lite |
Примени миграции, сгенерируй галактику и подними dev-сервер:
npx prisma migrate dev
npm run generate
npm run dev
Клиент откроется на http://localhost:3000 — там же регистрация, создание командира и выдача стартовой колонии.
docker build -t space-mmo . && docker run --env-file .env -p 3000:3000 space-mmo
Сервер живет на Fly.io в регионе fra, конфигурация — в fly.toml.
fly deploy --ha=false
Флаг --ha=false обязателен каждый раз. Без него Fly поднимает вторую машину
для отказоустойчивости, а этой игре она противопоказана: игровой цикл держит
состояние командиров в памяти процесса, и два экземпляра заведут два тика
со своими копиями баз — они начнут затирать друг друга сбросами.
По той же причине выключено автомасштабирование и запрещено засыпание: мир живет тиком, а не запросами игроков. На остановленной машине не идет добыча, не летят флоты и не ходят боты.
База стоит рядом с приложением. Сначала был Neon, но тик ходит в базу раз в секунду, и бесплатные 5 ГБ трафика в месяц выбирались за трое суток — база отказывала, мир вставал целиком. Платный тариф при таком профиле стоил бы около $19 в месяц (засыпание при простое нам не помогает, простоя не бывает), своя база на соседней машине — $3, и сеть уходит из горячего пути.
Памяти базе нужно минимум 512 МБ: умолчательные 256 МБ postgres-flex
не хватает, он держит Postgres, repmgr и монитор в одной машине.
Локальная разработка — на отдельной базе. Следствие того же правила про
один цикл: npm run dev на боевой базе дает второй тик поверх живого мира.
Нужна вторая база на том же кластере; до нее с ноутбука ходят через
fly proxy 15432:5432 -a spacemmo-db.
Миграции гоняются с ноутбука (npm run migrate:deploy), а не из образа:
prisma лежит в devDependencies, а рантайм собирается через npm ci --omit=dev —
CLI внутри просто нет. База доступна отовсюду, так что с ноутбука работает не хуже.
Игра ставится на домашний экран. manifest.webmanifest и иконки в public/icons/
дают свой значок и запуск без адресной строки; iOS читает манифест не полностью,
поэтому имя, значок и режим продублированы мета-тегами. Service worker есть, но
не кеширует ничего: он нужен затем, что без зарегистрированного обработчика fetch
Chrome не считает страницу устанавливаемой и не выдает beforeinstallprompt.
Кеша нет намеренно — на приложении без сборщика он дает несвежий app.js.
| Команда | Назначение |
|---|---|
npm run dev | dev-сервер с автоперезапуском (tsx watch) |
npm run build / npm start | сборка и запуск production-версии |
npm run typecheck | строгая проверка типов |
npm run audit | поиск мертвого кода: неиспользуемые переменные и параметры |
npm run audit:client | проверка public/app.js на необъявленные имена: файл не попадает в typecheck, а node --check вызов удаленной функции пропускает |
npm run forecast -- 7 | прогон недели на семи ботах настоящими формулами: чем мерить правку экономики |
npm run migrate:copy | перенос базы целиком между серверами: схему на приемнике создают миграции, порядок таблиц не важен (проверки ключей выключаются), в конце сверка по строкам |
npm run migrate:deploy | применить миграции к боевой базе (гоняется с ноутбука: prisma в devDependencies и в рантайм-образ не попадает) |
npm run generate | генерация галактики: 10-15 систем, планеты и торговые хабы. Размер задается аргументом — npm run generate -- 25 расширяет уже существующую галактику |
npm run check:rules | sanity-check игровых формул (добыча, энергия, бой, экспедиции) |
npm run prisma:migrate | миграции БД |
npm run prisma:generate | перегенерация Prisma-клиента |
npm run legacy:password | задать пароль аккаунту, мигрировавшему до появления авторизации |
npm run test:stand | подготовить стенд для тестов и напечатать их конфигурацию |
npm run test:cleanup | удалить одноразовые аккаунты тестов и вернуть их колонии в галактику |
npm run test:storage | тесты лимита склада, офлайн-добычи и формулы грабежа |
npm run test:commander | тесты симулятора, шаблонов флотов и старения разведданных |
npm run test:mail |
Аккаунт и командир. Регистрация и вход по email с паролем (scrypt с солью,
сравнение постоянным по времени), сессия — JWT: один и тот же токен для REST
и для рукопожатия WebSocket. Смена пароля одноразовым кодом с истечением; ответ
одинаков для существующего и несуществующего email. Код смены пароля наружу
не отдается: роут открытый, и вернуть код в ответе значит раздать любой аккаунт
тому, кто знает адрес владельца. На стенде это включается флагом
AUTH_EXPOSE_RESET_TOKEN=true, в production он игнорируется. Из внешних провайдеров
оставлен один — Google. Кнопку рисует Google Identity Services, id_token
проверяется на сервере по JWKS: подпись, издатель, срок, aud и обязательный
email_verified. Без GOOGLE_CLIENT_ID кнопки нет вовсе.
Второй провайдер — Telegram, и устроен он наоборот. Google опознается публичным
client_id, который уезжает в браузер; Telegram — секретом, токеном бота,
которым подписан initData мини-приложения. Поэтому токен лежит в отдельном
поле secret, а не в clientId: поля с этим именем отдаются клиенту.
Проверка целиком локальная, на node:crypto: из строки убирается hash,
пары сортируются и склеиваются переводом строки, ключ считается как HMAC
от имени «WebAppData» с токеном бота в роли ключа, сравнение хешей постоянное
по времени. Подпись живет сутки. Почты Telegram не дает вовсе, поэтому адрес
собирается служебный — tg-<id>@telegram.local. Клиент внутри Telegram входит
сам, без экрана логина. Apple и Facebook убраны намеренно — первый требует
платной подписки и ротации секрета раз в полгода, второй ревью приложения
с непредсказуемым сроком.
Аккаунты по совпавшему email не связываются: email при регистрации не проверяется,
поэтому кто угодно мог бы заранее занять чужой адрес и остаться в аккаунте после
того, как владелец войдет через Google. Аккаунт ищется по паре «провайдер + sub»,
а на занятый email вход через Google отвечает отказом с объяснением. Учетная запись отделена от игровой
личности: User отвечает за вход, Commander — за позывной, аватар, криптогривну
и счетчики боев. Достижения («Первый шаг», «Архитектор», «Гроза пиратов») выдаются
идемпотентно после сохранения состояния.
Ресурсы. Руда (основной строительный материал), Полимеры (электроника и высокие технологии), Плазма (топливо и реагент), Антиматерия (топливо гиперпрыжков) и Криптогривна как валюта. У каждого ресурса своя SVG-иконка: в интерфейсе рядом с цифрой стоит иконка, а название остается в подсказке — так строки стоимости и балансов читаются быстрее.
Иллюстрации. Карточка начинается с обложки: картинка объекта, под ней название,
а еще ниже метка — уровень, «В ангаре: 3» или «На позиции: 4». Метка стоит отдельной
строкой, а не рядом с названием: в строку они не помещаются, и «Колониальный транспорт»
рвался по слогам на три строки. Остальное содержимое идет ниже на всю ширину, поэтому
сетка карточек не зависит от размера картинки и сворачивается в одну колонку на телефоне.
Растровые иллюстрации лежат в public/assets/ — buildings/, ships/, defense/,
tech/ для карточек и planets/, systems/, bg/ для карт — и называются по типу
объекта в нижнем регистре (ore_mine.webp). Обложка карточки —
строгий квадрат, под ним название, уровень и стоимость; карточки в ряду держат
одну высоту, кнопка прижата к низу.
Пока файла нет, на месте баннера стоит заглушка с инициалами — верстка от этого
не ломается, и карточки не прыгают, когда картинка появляется.
Список ожидаемых файлов — в public/assets/README.md.
Центр управления. Вкладка по умолчанию и все о колониях в одном месте: паспорт активной базы, энергобаланс развернуто (выработка, потребление, остаток, мощность шахт), сводка четырех очередей разом, флоты в полете и уровни всех построек с технологиями. Паспорт раньше стоял над каждой панелью сразу: на широком экране это занимало полосу сверху, а на телефоне разворачивалось на весь экран, и до карточек шахт приходилось листать мимо него каждый раз.
Склад показан тремя ярусами на ресурс: название с текущей добычей, полоса, разбор под ней. Полоса разбита на два отрезка — несгораемый запас, который прячет само хранилище, и то, что вывезет победитель. Общей суммы по трем складам нет намеренно: лимиты поресурсные, и «занято 38 030 из 43 311» не отвечает ни на один вопрос — руда может стоять на полном складе с остановленной добычей, пока итог показывает свободное место.
Раскладка интерфейса. Экран разложен на три постоянные зоны и рабочую середину.
Сверху закрепленная шапка: командир с тегом синдиката и переключатель колоний
с координатами X:Y:орбита, по центру ресурсы: у каждого своя скорость
добычи, а на полном складе вместо нее краснеет «склад полон» — метка адресная,
потому что и склады раздельные. Справа центр связи с бейджем непрочитанного
и выход. Слева меню разделов по группам: развитие, армия, командование,
операции, инструменты и — только администратору — сервер. Справа сводка, которая
появляется на картах: карточка планеты, отправка флота и очередь полетов с таймерами.
В центре управления паспорт активной базы в три колонки: портрет планеты тем же артом, что и на карте, адрес и склад слева, богатство недр посередине, гарнизон справа. Богатство показано не голым множителем, а полосой со шкалой и словесной оценкой от «скудно» до «изобилия»: у множителя нет ни нуля, ни потолка, и сравнить его не с чем. Ожидание везде одно и то же: стройка, исследование, верфь и оборона показывают название, время до конца, полосу и строку деталей.
На узком экране раскладка складывается ступенями: правая сводка уходит под контент, строка ресурсов переезжает на вторую строку шапки, а на телефоне меню превращается в выезжающую панель с бургером и карточки встают в одну колонку.
Панель биржи. Три блока сверху вниз, и порядок в них — порядок решений. Сверху состояние рынка: цена каждого ресурса с изменением за сутки, обороты, число сделок, перекос спроса полосой и бартер отдельной карточкой; клик по цене разворачивает дневную динамику за 30 суток. В середине стакан: форма заявки с кнопкой «макс» (она считает по-разному для сторон — продажа упирается в остаток на складе, покупка в кассу и свободное место разом), фильтры по стороне, ресурсу и позывному, сортировка по любой колонке. Внизу журнал сделок с подкачкой по пятьдесят строк.
Стакан отсортирован по цене, а не по времени: он затем и нужен, чтобы лучшее предложение было первым. Новая заявка встает по своей цене и там же подсвечивается. Сторона сделки закодирована трижды — цветом, стрелкой направления и формой метки, — потому что одной заливкой в столбце одинаковых меток их не развести. Ресурс опознается значком, а не цветной точкой.
Темп игры. Экономика рассчитана на месяц до полного открытия контента: первая сессия 30–60 минут, дальше несколько коротких заходов в день. Добыча растет медленнее цены уровней, время постройки идет по степенному закону — первые уровни ставятся за минуту-две, поздние занимают часы и сутки. Главные ворота месяца — наука: поздние уровни технологий изучаются днями. Флот при этом основная инвестиция: эскадра из двадцати линкоров стоит суток добычи, поэтому терять ее больно, а базу хочется проверять.
Опорные точки: истребитель — первый час, крейсер — вторые сутки, фрегат — четвертые, колонизация и бомбардировщик — вторая неделя, линкор — третья, авианосец и «Перун» — конец месяца.
Колонии и экономика. Планеты с индивидуальными коэффициентами руды, полимеров,
плазмы и инсоляции. Одиннадцать типов построек:
три шахты, энергостанция, научный центр, верфь, фабрика антиматерии,
крипто-ферма и три раздельных хранилища. Здания тратят энергию: при дефиците
добыча всех шахт падает пропорционально (efficiency = output / usage).
Крипто-ферма — единственная постройка, производящая деньги, и конкурирует
с шахтой за ту же энергию. Дерево из пятнадцати технологий
с иерархией требований: энергетика, вычислительная техника, оружейная, щитовая
и бронебойная, горное дело, реактивный двигатель, гиперпространственная физика,
гипердвигатель, астрофизика, робототехника, криптоинженерия, бункерование,
шпионаж и сжатие времени. Верфь строит корабли и стационарную оборону
собственными очередями.
Тиковый движок. Стройка, исследования и очереди считаются от абсолютных меток времени: процессы идут, пока игрок офлайн, и переживают перезапуск сервера. Добыча за офлайн (до 24 ч) начисляется по отрезкам между завершениями таймеров. Тик раз в секунду, пакетная запись в БД, push состояния клиенту по WebSocket.
Карта системы. Круговая микро-карта: звезда или черная дыра в центре, планеты
на концентрических орбитах, торговый хаб на своем кольце и точка выхода в глубокий
космос снаружи. Тела берут картинки из assets/planets/ по биому, пока файла нет —
цветной круг. Размер планеты зависит от ее типа: газовый гигант крупнее всех,
скалистый огрызок мельче всех, и по карте видно, где добыча богаче, еще до разведки.
Орбиты начинаются у самой звезды, а их шаг зависит от числа планет: система из трех
выглядит компактной, из пяти — просторной, и тела никогда не наезжают друг на друга.
Название стоит под планетой, а своя колония отмечена не кольцом, а зеленой точкой
под подписью. Звезда, черная дыра, туманность и станция хаба не обрезаются кругом
и растворяются в фоне через mix-blend-mode, планеты остаются непрозрачными шарами
с круглой обрезкой. Фон карты — тайл звездного поля под сильным затемнением: яркая
подложка съела бы свечение тел.
Карта галактики. Системы нарисованы миниатюрами галактик и туманностей
из assets/systems/ — четыре варианта, и вариант выбирается по координатам, чтобы
система не меняла вид при каждой перерисовке. Шаг сетки одинаков по обеим осям,
а размер холста считается от разброса координат: расстояние между системами на карте
соответствует расстоянию между ними в игре, а не форме панели. Клик по системе
открывает ее микро-карту — с тем же туманом войны, что и своя.
Информация при наведении. Рядом с телом разворачивается HUD-карточка: сначала от него протягивается линия, затем на ее конце проявляется панель — около 0.4 секунды на все. Цифры разложены по сетке, а не в столбец, поэтому карточка не растет вниз на пол-карты; постройки в ней опущены — полный разбор открывается кликом в панели справа. Наведение на звезду показывает ее класс, температуру и число планет; у черной дыры температуры нет. На сенсорном экране карточка не показывается: наведения там нет, а панель закрыла бы саму карту.
Туман войны и логистика. Своя планета видна целиком и в реальном времени, чужая — только название, орбита, тип и размер; после прилета зонда сохраняется стареющий снимок разведки. Скорость флота задает самый медленный корабль, грузоподъемность и топливо считаются по составу и времени в пути. Руда, полимеры и плазма делят один трюм: плазма и топливо, и товар, поэтому колонии перебрасывают его между собой обычным транспортом. Внутри системы флот идет на плазме, между системами — гиперпрыжком на антиматерии, и прыжок требует «Гипердвигателя». Топливо берется за дорогу туда и обратно; рейс без возврата платит только за один конец. Прилеты обрабатываются глобальным сканом: груз доходит и корабли возвращаются, даже если обе стороны офлайн.
Отправка флота. Цель выбирается кликом по круговой карте или вводом координат «система X:Y и орбита» — по ним цель находится и в чужой системе, которую карта еще не открывала. Дальше панель ведет по трем шагам.
Первый — действие: не выпадающий список, а меню кнопок. Действий у цели три-четыре, они разные по смыслу, и от выбора зависит остальная форма; в свернутом списке игрок не видит, что вообще можно сделать с планетой. Набор решает не наличие владельца, а состояние планеты, и состояний четыре:
| Планета | Действия |
|---|---|
| своя колония | транспортировка, дислокация |
| чужая колония | атака, отправить груз или помощь, шпионить зондом |
| свободная | разведка, основать колонию (с основателем в составе) |
| не разведана | разведка, атака, транспорт, колонизация |
Последний случай отдельный: у неразведанной планеты владелец скрыт туманом войны, и пустой владелец там не значит «свободна». Пункты в ней не мертвые, а именно неизвестные — игрок может знать о планете от союзника, и сервер откажет, только если догадка не подтвердится. По умолчанию подставляется безопасное действие, а не первое в списке: у чужой колонии первая — атака, и она объявляет войну.
Второй шаг — состав: поля по классам кораблей с остатком на базе, кнопки «Выбрать все» и «Очистить», плюс сохраненные шаблоны. Третий — груз и расчет маршрута: дистанция, время, топливо с остатком на складе и занятость трюмов; нехватка подсвечивается до вылета. Справа же очередь активных полетов с таймерами до прибытия.
Миссии: транспортировка, разведка зондом, атака, дислокация, доставка и вывоз на хабе, экспедиция, переработка обломков и колонизация. Переработка появляется, только когда над планетой висит поле и в составе есть переработчик.
Война объявляется атакой. Отдельный поход в раздел дипломатии больше не нужен: если войны с целью нет, вылет объявляет ее сам. Членство в синдикате личную войну не запрещает — запрет приводил к тому, что кнопка «Атака» у половины игроков давала отказ вместо боя. Вместо запрета предупреждение: предпросмотр маршрута говорит, что войны нет и что синдикат цели может ответить всем составом. Считает его сервер — только он знает дипломатию, а отдавать ее клиенту ради строки текста незачем.
Помощь союзнику. Транспорт на чужую колонию можно отправить с флажком «без возврата»: тогда у получателя остается не только груз, но и сами корабли — так передают флот. Обычный рейс, как и раньше, выгружает груз и возвращается. Атака и разведка возвращаются всегда: флот, брошенный у чужой колонии, достался бы противнику даром.
Колонизация. Расширяться можно одноразовым «Колониальным транспортом»: он садится на свободную планету и разбирается на первую инфраструктуру новой базы. Рейс односторонний — конвой и груз остаются колонии, обратно летит только тот, кто не понадобился. Сколько колоний можно держать, задает «Астрофизика»: одна на старте и еще одна за каждые два уровня. Свобода планеты и наличие слота проверяются дважды — при вылете и на прилете, потому что за время полета планету могли занять. Сорвавшийся рейс возвращает флот целиком и даром: обратный путь при вылете не оплачивался. Причина всегда приходит письмом.
Арсенал. Двенадцать классов кораблей и пять классов обороны с украинскими позывными. Легкая линия — «Око», «Чайка», «Чумак», «Сокіл»; средняя — «Гайдамака», «Отаман», «Характерник»; лейтгейм — «Булава», «Гетьман» и «Січ». Оборона идет от массового «Града» через «Промінь» и «Скіф» к батарее «Сварог» и ультимативному излучателю «Перун».
Роли разведены матрицей скорострела: тяжелый истребитель косит мелочь, крейсер — истребителей, линкор — крейсера и бомбардировщики, авианосец — весь легкий и средний класс, а бомбардировщик разбирает планетарную оборону очередями. По «Перуну» очереди нет ни у кого: ультимативную установку берут только массой.
Сила «Перуна» — не корпус, а щит: 3000 восстанавливаются к каждому раунду, и эскадра с суммарным залпом ниже этого числа не наносит ему вообще ничего. Восемь линкоров бьют на 8000, но теряют по кораблю в раунд и к шестому уже не пробивают щит: бой кончается ничьей, а она остается за защитником. Порог пробоя — двенадцать линкоров на один излучатель.
Лаборатории колоний. Технологии общие для всех колоний, и изучается одновременно одна. Лаборатория любой другой колонии может присоединиться к идущему исследованию и взять на себя его долю: лаборатория того же уровня, что ведущая, или выше — половину, слабее — долю от половины по отношению уровней (шестая при ведущей двенадцатого — четверть). Взятая доля срезается со всего срока исследования и доплачивается той же долей всей его цены со склада помогающей колонии. Опоздавшая берет только остаток и платит только за него. При отмене каждая база получает назад свое.
Технологии скорости. «Робототехника» ускоряет постройку зданий и сборку кораблей с обороной — на исследования она не влияет, там работает уровень лаборатории. «Сжатие времени» открывается в самом конце дерева и каждым уровнем сокращает вдвое вообще все сроки: стройку, верфь, оборону и науку. Плата — энергия: каждый следующий уровень потребляет вдвое больше предыдущего, а дефицит энергии режет добычу всей базы. На средней энергетике включенное сжатие второго уровня роняет доход втрое, на развитой не отнимает ничего — поэтому ускорение окупается только тому, кто заранее вложился в станции.
Антиматерия и аномалии. «Фабрика антиматерии» — самая дорогая постройка с огромным расходом энергии, синтез идет на порядки медленнее обычной добычи. Аномалия «Черная дыра» в 1-2 системах дает «Искажение времени»: синтез антиматерии +50%, стройка (включая верфь) и исследования на 30% дольше.
Биржа и криптогривна. Торговый хаб — нейтральная станция на орбите у звезды. Товар попадает на склад только транспортным флотом, что не влезло — возвращается домой в трюмах; обратный рейс забирает купленное домой, где оно и превращается в постройки. Расширение склада на хабе платится криптогривной.
Посредников нет: станция не покупает и не продает ничего, торгуют между собой игроки. Встречные заявки сводятся автоматически и по средней цене: продажа по 10 против покупки по 12 исполняется по 11 на меньший из объемов, остаток большей заявки висит по своей прежней цене. Середина делит выигрыш поровну — отдать сделку по цене одной из сторон значило бы подарить всю разницу тому, кто выставился вторым. Комиссия 0.5% с продавца и 0.6% с покупателя.
Цену назначает рынок: она средневзвешенная по объему за последние двадцать сделок. Затравочные 10 за руду и 14 за полимеры участвуют ровно один раз — пока сделок нет вовсе. Рядом со стоимостью показан перекос стакана: спрос выше предложения — цена пойдет вверх, и вкладываться выгоднее в добычу именно этого ресурса.
Кроме ордеров есть бартер — обмен ресурса на ресурс без денег. Предложение берется целиком: дробить обмен незачем, а частичное исполнение усложнило бы расчет вдвое ради удобства, которое достигается вторым предложением.
Криптогривна берется единственным способом — с крипто-фермы, а уходит пятью стоками (см. ниже). Все сделки между игроками это переводы, поэтому массу держат кран и стоки, а не эмиссия. Ферма конкурирует с шахтой за одну и ту же энергию и дает на единицу энергии меньше, чем шахта, если добытое удается продать, — это выбор между деньгами сразу и сырьем с логистикой. Равновесие держится само: отдача фермы задана в гривне и от цен не зависит, поэтому дешевеющий товар делает ферму выгоднее, а дорожающий — шахту.
Денежная масса держится на одном кране и пяти стоках. Кран — крипто-ферма, единственная постройка, производящая деньги. Стоки: комиссия биржи, разовое расширение склада на хабе, постоянная плата за место на нем, спешка и траты казны синдиката на Кіш — основание, модули, технологии, Брамы и оборону.
Плата за хаб берется за вместимость, а не за занятое: место арендуется целиком, пустая полка стоит столько же, сколько полная. Первый уровень бесплатен. Не хватило на всю плату — не списывается ничего: долгов в игре нет. Собирается раз в минуту по всем командирам сразу, включая офлайновых.
Спешка — единственное, на что криптогривну тратят по своей воле: стройку можно доделать немедленно за деньги. Цена привязана к рыночной стоимости самой работы и к доле оставшегося времени, поэтому дорожает вместе с инфляцией сама — любая постоянная цена устарела бы за сутки.
Отдача фермы и ставка аренды подобраны вместе: это один рычаг на две константы.
Мерить такую правку положено прогоном (npm run forecast -- 7), а не на глаз.
Биржа общая на весь сервер. Хаб есть в каждой системе, но стакан, цены, бартер и журнал сделок одни на всех, и боты торгуют со всеми. Привезенное флотом лежит на складе того хаба, куда его привезли, и забирается только там; купленное и полученное обменом ложится на общий склад, который забирается с любого хаба. Размер склада и аренда — одни на все хабы. Продажа берет товар сначала со склада хаба своей системы, потом с общего, а снятая заявка возвращает его туда, откуда он ушел, — бесплатно переслать ресурсы между системами нельзя.
Синдикаты. Название, тег, описание, главарь и состав; основание стоит криптогривну. Вместо трех ролей — ранги: главарь создает до восьми, у каждого место в иерархии и набор прав (заявки, исключение, назначение рангов, рассылка, выдача из казны с дневным лимитом, налог, кодекс, правила набора, дипломатия, развитие Коша, Академия). Действовать можно только на тех, чей ранг ниже своего. Кодекс кандидат читает и принимает до вступления; набор бывает открытым, по заявке и закрытым, с минимальным рейтингом и вступительным взносом. Налог с крипто-фермы (до 30%) уходит в казну; повышение вступает в силу через сутки и приходит письмом. Атаковать участников своего синдиката нельзя, после выхода вступить в другой можно через сутки, а при роспуске казна сгорает. Раздел «Синдикат» — про людей и деньги: шапка с описанием, казна со сводкой поступлений и расходов за неделю, вклад каждого участника, журнал с фильтром, устав, состав и ранги.
Кіш — хаб синдиката, стоящий в системе главаря. Он выбирается в переключателе колоний, после счетчика колоний, и разделы игры показывают его сторону: «Мостик» (тревоги, казна, состояние, перенос), «Отсеки» (модули), «Академия» (технологии синдиката), «Флотилия» (караул и налеты в пути) и «Бастион» (оборона), а в шапке видна казна. На первом уровне Кіш дает три места в составе, дальше по одному за уровень. Ресурсная казна хранит руду, полимеры, плазму и антиматерию; привозят их рейсом «Доставка в Кіш» (любой участник, в заслуги), вывозят рейсом «Вывоз из казны» (ранг с правом выдачи, в общий с гривной дневной лимит). Флот к Кошу можно отправить прямо с его карты, выбрав колонию вылета.
Модули Коша строятся из казны по времени, одна стройка на синдикат: цена списывается при запуске, отмена возвращает ее целиком, уровень меняется по завершении. Карточки модулей и технологий раскрываются таблицей уровней вперед с ценой и сроком. Скарбниця бережет при налете 20% ресурсной казны и по 5% за уровень, не больше 80%. Академия открывает технологии синдиката: добыча, скорость стройки, трюмы, комиссия биржи — по 3% за уровень; несгораемая доля склада; контрразведка +1 к «Шпионажу» в обороне за каждые три уровня; «Инженерный корпус» — стройка в Коше и изучение на 10% быстрее за уровень. Каждый уровень технологии втрое дороже предыдущего, уровень не выше уровня Академии, изучение одно за раз, бонусы — участникам, пробывшим в синдикате двое суток. Дозор показывает участникам вражеские атаки на них в радиусе от Коша; разведку он не видит и состав флота не раскрывает. Брама — врата в системах с колониями участников: между системами со своими Брамами флот прыгает без «Гипердвигателя» и за треть антиматерии, не больше 200 кораблей в час за уровень врат. Кіш переносится к своей Браме за антиматерию казны (500 за единицу расстояния), не чаще раза в сутки.
Война синдикатов. Удержание — флот встает на орбиту колонии участника или у своего Коша на 1, 4, 8 или 24 часа и защищает ее в бою; уцелевшие делятся по вкладу, отозвать флот можно раньше срока. Налет на Кіш — атака чужого Коша от лица синдиката, только в войне синдикатов (если ее нет, вылет объявляет войну, нужно право дипломатии). Кіш защищают оборона из казны и караул на удержании; победитель уносит до 90% незащищенной руды, полимеров и плазмы в пределах трюмов, гривну не грабят. Разбитые корабли оставляют у Коша осколки, их собирает переработчик. Пока к Кошу летит налет, переносить его нельзя. Совместная атака — флоты синдиката и союзников на одну колонию прилетают одновременно и дерутся одним боем (до 8 флотов); уцелевшие и добыча делятся по вкладу и трюмам.
Пакты — ненападение, союз и торговое соглашение. Предлагает и принимает ранг с правом дипломатии, расторгнуть может любая сторона, но пакт действует еще сутки, и об этом узнают все участники. Под ненападением и союзом нельзя атаковать другую сторону, налетать на ее Кіш и объявлять ей войну; мириться пактом во время войны нельзя. Союз вдобавок открывает Брамы союзника, показывает его колонии в Дозоре и позволяет присоединяться к его атакам. Торговое соглашение вдвое снижает комиссию биржи между участниками двух синдикатов и возможно даже во время войны.
Рейтинг. Счет командира — это ресурсы на руках плюс стоимость всего, что построено и не потеряно: флот (в том числе тот, что сейчас в полете), оборона, здания и технологии. Здания и наука считаются суммой по всем уровням с первого: десятый уровень стоил владельцу и всех девяти предыдущих. Считается наличие, а не история трат — потерянный в бою флот из счета уходит, иначе таблица превратилась бы в список тех, кто дольше играет. Все ресурсы идут один к одному по единицам. Рядом с таблицей своя строка с разбивкой по слагаемым: по ней видно, чем игрок силен. Синдикаты ранжируются четырьмя таблицами: общий счет (состав плюс вложенное из казны в Кіш, модули и технологии), военный (уничтоженный флот противника), экономический (взносы и налог в казну) и научный (сумма уровней технологий синдиката). Военный и экономический счета копятся в момент события и не переходят с участником в другой синдикат.
Экспедиции и войны. Раздел со сводкой командира сверху — победы с долей, поражения, войны, занятые слоты экспедиций, достижения — и вкладками «Бои», «Экспедиции», «Дипломатия», «Достижения». Бои идут короткими строками с фильтром и раскрываются полным отчетом, экспедицию можно отправить одной кнопкой — карта откроется уже с глубоким космосом целью, дипломатия синдиката разложена карточками войн, пактов и синдикатов галактики.
Экспедиции (PvE). Вылет в «глубокий космос» — абстрактную 16-ю позицию, которая есть у каждой системы. «Астрофизика» задает число одновременных экспедиций, повышает находки и дает шанс уйти от засады, а заодно открывает колониальный транспорт и слоты под колонии. Событийный движок бросает кубик по весам: мертвая тишина, заброшенный груз, уклонение или бой с пиратами. Генератор случайных чисел передается снаружи, поэтому любой исход воспроизводим в тестах.
Три хранилища, по одному на ресурс. «Рудный склад», «Склад полимеров» и «Плазмохранилище» строятся и качаются независимо. Уровень 0 — колониальный резерв на 2 500, уровень 1 дает 3 500, дальше вместимость растет в 1.5 раза за уровень. Полный склад останавливает добычу только своего ресурса — и в тике, и в догоне офлайна, так что неделя отсутствия не приносит больше, чем влезает. Антиматерия хранится в магнитных ловушках и под лимиты не попадает вовсе.
Общий лимит на три ресурса был здесь раньше, и он создавал состояние без выхода: обильный ресурс занимал место, дефицитный переставал добываться вместе с ним, а все постройки требовали именно дефицитного. Выбраться можно было только вывозом излишка в хаб — то есть при уже построенных транспортниках, которых у новичка нет. Раздельные лимиты убирают саму возможность такого состояния.
Переполнить склад сверх лимита все же можно — возвратным рейсом, трофеями экспедиции или отменой биржевого ордера; такие ресурсы не пропадают, но и не защищены.
Поля обломков. Бой оставляет экономический след: 30% стоимости уничтоженных кораблей — и нападавшего, и защитника — оседает обломками на орбите планеты. Разбитая оборона обломков не дает: она восстанавливается на месте, а не улетает в поле. Поле видно всем игрокам и не скрывается туманом войны, поэтому за крупным полем начинается гонка. Собрать его может только «Переработчик» — безоружный корабль с трюмом на 20 000, который ползет медленнее всех и жжет плазма как десять транспортов. Миссия «Переработка» доступна над любой планетой, даже чужой и необитаемой. Обломки достаются тому, чей флот долетит первым: списание атомарно, и опоздавший либо добирает остаток, либо возвращается пустым — о чем прямо пишет отчет, иначе исчезнувшее поле выглядело бы багом.
Боты-командиры. Бот — обычный командир в БД: своя учетная запись с ролью
BOT, стартовая колония, место в рейтинге. Войти в его учетную запись нельзя
ни паролем, ни через Google. Ходит он теми же методами игрового цикла
и сервисов, что и живой игрок, поэтому платит те же цены и ждет те же таймеры.
Характеров два: торговец качает экономику и торгует, агрессор растит флот
и расширяется — но война для него средство, а не занятие.
Решения идут двумя контурами. Рутину ведет код: раз в 45 секунд чистая функция «снимок → намерения» выбирает, что достроить, дозаказать и куда отправить флот. Языковая модель зовется редко — раз в два часа по расписанию или когда случилось нестандартное: бой, объявленная война, потеря колонии, резкий сдвиг рынка. Вызов на каждый заход стоил бы почти два миллиона токенов в сутки против четырех тысяч и отдал бы модели арифметику, которую код считает точнее.
Модель задает курс — доли бюджета, порядок науки, приоритеты застройки, состав эскадры — и отдает разовые поручения: набег, мир, заявка на бирже, колонизация, сбор обломков, письмо игроку. Поручение это просьба, а не исполнение: выполняет его тот же игровой цикл, поэтому правила остаются в силе. Проверяются поручения по снимку — модель может распорядиться только тем, что ей показали.
Границы модель не отменяет. Щит новичка: бот не трогает живых командиров младше трех суток ни при какой формулировке (на других ботов щит не распространяется — иначе мир замирает). Пол безнадежности: лететь на цель втрое сильнее нельзя. Доля развития не опускается ниже трети — иначе флот съедает базу, которая его кормит.
Против серийного агрессора соседи скидываются сами: три набега за сутки по одной жертве делают из соседа угрозу, и любой бот рядом сдвигает бюджет во флот с обороной и летит на него. Договора между союзниками нет — согласие это когда сосед действительно поднял флот, а не строка в базе. Жертва рассылает призыв письмом, и живым игрокам оно тоже приходит: это единственный способ узнать, что рядом кто-то зарвался.
Норма обращений к модели общая на всех ботов и считается по попыткам: отказ сервиса стоит у провайдера столько же, сколько удачный ответ. Потолок переписки стоит в коде, а не в просьбе к модели, — шесть писем в сутки на бота и два одному адресату. Без ключа все работает: бот играет по статичному характеру и не отвечает на письма, и это штатный режим, а не авария.
Панель администратора. Отдельная роль ADMIN на учетной записи и защищенный
раздел /api/admin/*: список командиров с поиском по позывному, полный стейт игрока
и принудительная правка ресурсов, криптогривны, зданий, технологий, флота и обороны.
Значения выставляются целиком, а не прибавляются — в форме уже стоят текущие числа,
и повторная отправка не удваивает выданное. Каждое изменение пишется в лог сервера.
Вкладка появляется только у администратора, но доступ решает сервер: запрос обычного
игрока получает 403 независимо от интерфейса. Роль выдается скриптом
npm run admin:grant -- <email>, игрового пути к повышению прав нет.
Сверху сводка по серверу: сколько игроков сейчас в сети (по живым сокетам, а не по времени последнего входа), сколько заходило сегодня и за неделю, сколько новых, колоний, флотов в полете и синдикатов. У каждого игрока — карточка учетной записи: почта, способ входа, даты регистрации и последнего входа, состояние доступа.
Действия над аккаунтом отделены от правки игровых чисел: у них разные последствия,
и складывать их в одну кнопку «Сохранить» значит однажды заблокировать игрока,
поправляя ему руду. Пароль пульт не задает и не показывает — админ выдает
одноразовый код, а новый пароль игрок вводит себе сам. Блокировка закрывает вход
и игровой канал, но не трогает колонии: разблокированный находит мир таким, каким
оставил. Удаление необратимо и требует ввести позывной — каскад уносит колонии
(планеты освобождаются), флоты и синдикат, если игрок им руководил.
Администратора нельзя ни заблокировать, ни удалить. Письмо от гейм-мастера
приходит игроку отдельным типом ADMIN, чтобы не теряться среди автоотчетов.
Центр связи. Внутриигровая почта с фильтрами по типам писем. Личные сообщения отправляются по позывному, ранги синдиката с правом рассылки рассылают объявление всему составу разом. Все системные отчеты приходят сюда же: бой, разведка, экспедиция и логистика доставляются письмами от «Центра связи». Доставка груза уведомляет обе стороны — отправитель видит, что выгрузился, владелец колонии узнает, кто прислал ресурсы, иначе они появлялись бы на складе молча. Флот, вернувшийся с добычей, тоже пишет; пустой возврат молчит, о таком рейсе уже был свой отчет. Один бой дает два зеркальных письма — атакующий читает про свои трофеи, защитник про свои потери. В шапке висит иконка почты с бейджем непрочитанного, который обновляется через Socket.IO в момент доставки, а не по опросу. Ящик строго адресный: чужое письмо нельзя ни прочитать, ни удалить даже по прямому идентификатору.
Боевой отчет. Письмо о бое разворачивается не текстом, а полноценным отчетом: исход (победа, поражение или ничья) глазами того, кто его открыл, место боя с системой и орбитой, дата. Дальше итоги крупно — сколько обломков осело на орбите и сколько ресурсов вывезено, — и потери обеих сторон таблицей: было, потеряно, осталось, с иконкой каждого класса. Сторона, от которой ничего не осталось, подписывается «Флот уничтожен». Тем же компонентом рисуется предпросмотр в симуляторе, поэтому прогноз и настоящий отчет читаются одинаково. Пораундового лога пока нет: сводки «было / стало» хватает, чтобы понять итог.
Инструменты командира. Боевой симулятор прогоняет любой состав атакующих против любого флота и обороны защитника через тот же боевой движок, что и настоящий бой, и ничего не пишет в базу: видно потери, пробитие слоев и предполагаемую добычу с учетом хранилища и трюмов уцелевших. Состав защитника подставляется из отчета разведки одним выбором. Шаблоны флотов — сохраненные составы («Фарм-отряд»: 10 крейсеров, 5 транспортов), которыми заполняется форма отправки; шаблон только подставляет числа, наличие кораблей проверяется при вылете.
Старение разведданных. Снимок зонда не обновляется сам, поэтому у него есть срок годности: до часа данные свежие, от часа до суток помечаются как неточные, а после суток сервер вообще не отдает флот, оборону и склад — остаются только уровни построек, владелец и богатство планеты. Возраст снимка виден рядом с цифрами, чтобы атаку не планировали по вчерашней разведке.
Шпионаж решает разница уровней, а не свой уровень. Технология сразу наступательная и оборонительная: вложившийся видит больше и показывает меньше, поэтому нужна она и торговцу — для него это контрразведка. Зонд без нее не собрать вовсе.
Лестница раскрытия по разнице d между уровнем шпиона и уровнем цели:
d | дрон | что привозит |
|---|---|---|
| −2 и ниже | сбит (99%) | ничего |
| −1 | 50% сбит | только постройки и недра |
| 0 | цел | флот числом; склад числом с шансом 4/5 |
| +1 | цел | и оборона числом |
| +2 | цел | и оборона по типам |
| +3…+4 | цел | флот и склад по составу |
| +5 и выше | цел | и уровни технологий |
Заметность — та же разница с другой стороны: при перевесе +1 цель замечает пролет в 70% случаев, +2 — 40%, +3 — 10%, +4 и выше — 1%. Содержание тревоги тоже растет: от «кто-то пролетал» до «кто, откуда, когда и что успел прочесть». Абсолютов нет ни в одну сторону — джокер в один процент оставляет возможным и провал непобедимого, и удачу обреченного.
Броски делаются в момент пролета, и их итог ложится в снимок навсегда: уничтоженный дрон не может задним числом привезти данные, а выученная позже наука не улучшает старый снимок. В письмо кладется только то, до чего дотянулся зонд, — где положено видеть одно число, приходит число, а не состав.
Отчет разведки. Не текст, а разбор: портрет планеты тем же артом, что на карте, недра шкалой, склад значками ресурсов, флот и оборона плитками с картинками кораблей и установок, инфраструктура по уровням, технологии ярлычками. Ступень, до которой дотянулся зонд, стоит меткой в шапке — по ней видно, почему отчет неполон, без чтения строк. Пустое место всегда объяснено причиной, и причины разные: «зонд не дотянулся» чинится уровнем технологии, «планета не укреплена» приглашает напасть. Тем же компонентом рисуется тревога о чужом зонде над своей колонией.
Бой. Раундовая симуляция: до шести раундов, каждый живой юнит раз в раунд бьет по случайной цели. У юнита три характеристики — атака, щит и корпус. Щит поглощает урон первым и восстанавливается к следующему раунду, корпус не лечится; юнит гибнет, когда корпус уходит в ноль. Отсюда главное правило боя: одиночный слабый залп вязнет в щите, но десять подряд продавливают его в том же раунде. Ионный фрегат снимает щиты с двойным множителем — его роль вскрывать защиту тяжелых кораблей, а не убивать их.
Скорострел решает обратную задачу: после выстрела юнит с шансом (N-1)/N стреляет
снова и снова, пока очередь не оборвется, — так тяжелый корабль успевает выкосить
рой мелочи за один раунд, а не разменивать себя на нее по одной цели. Матрица
односторонняя: крейсер бьет очередью по истребителям (N=10), зондам и транспортам
(N=5), ионный фрегат — по лазерным турелям (N=8) и той же мелочи, истребитель —
по пушечным турелям (N=3). У обороны скорострела нет. Отсюда состав флота начинает
значить больше числа: крейсер уверенно берет равный по стоимости рой истребителей,
но вязнет в лазерных турелях, где тот же бюджет фрегатов проходит насквозь.
Три технологии масштабируют характеристики: оружейная поднимает атаку, щитовая — щиты, бронебойная — корпус, каждая на +10% за уровень, и все три действуют и на флот, и на оборону. «Энергетика» на бой больше не влияет — она осталась экономической веткой и пререквизитом для фрегата и реактора.
Побеждает тот, у кого противник кончился; если после шестого раунда живы обе стороны — ничья, и поле остается за защитником. Разбитая оборона восстанавливается с шансом 70%. Победивший атакующий грабит склад по механике «сейфа»: хранилище прячет пятую часть своей вместимости, а с уязвимого излишка сверх этого порога агрессор забирает 90% пропорционально каждому типу ресурса — руда, полимеры и плазма одинаково. Защита поресурсная: пустой рудный склад не одалживает свою защиту полимерам. Итог все так же режется трюмами уцелевших кораблей.
Порог поднимает «Бункерование» — по два процента за уровень. До половины
вместимости (пятнадцатый уровень) это обычная ветка развития, за ней каждый
уровень обходится втрое дороже обычного роста: на шестнадцатом это уже почти
семь миллионов руды. Абсолютная броня покупаться не должна — полная
неуязвимость убивает грабеж, а с ним и повод держать флот. Отчеты показывают, куда ушел урон с обеих сторон и сколько
ресурсов спрятало хранилище защитника. Бой случаен — цели выбираются броском кубика,
и один и тот же состав дает разброс исходов; случайность вынесена в параметр rng,
поэтому предпросмотр в симуляторе и тесты подставляют зерно и получают повторяемый бой.
src/
index.ts точка входа: Express, Socket.IO, монтирование роутов
config/env.ts загрузка и валидация окружения
config/auth.ts секрет JWT, сроки жизни токенов, провайдеры
config/version.ts номер версии игры, который видят игроки
db/prisma.ts PrismaClient с pg-адаптером
game/rules.ts чистые формулы: добыча, стоимость, энергия, вместимость складов
game/techTree.ts дерево технологий: стоимость, время, требования, бонусы
game/ships.ts классы кораблей: стоимость, время, боевые профили
game/defenses.ts стационарная оборона: стоимость, время, энергия
game/combat.ts бой: раунды, щиты и корпус, скорострел, обломки, грабеж
game/fleets.ts логистика: орбиты, гиперпрыжки, трюмы, топливо
game/fogOfWar.ts видимость планет, снимки разведки и их старение
game/market.ts цена рынка, комиссии, залоги и вместимость складов хаба
game/espionage.ts лестница шпионажа: что видит зонд и что замечает цель
game/expeditions.ts событийный движок экспедиций и лимит слотов
game/score.ts формулы рейтинга: во что вложены ресурсы (чистый модуль)
game/syndicate.ts правила синдиката: ранги, налог, Кіш, модули и их стройка, технологии, налет, пакты
game/baseState.ts состояние игрока и баз в памяти + снимки для клиента
game/gameLoop.ts изолированный тик: таймеры, очереди, догон офлайна
game/bot/decide.ts решения бота как чистая функция «снимок → намерения»
game/bot/director.ts исполнение намерений и сборка снимка для бота
game/bot/personality.ts характеры: доли бюджета, порядок науки, состав эскадры
game/bot/mind.ts две роли языковой модели: стратег и дипломат
game/bot/plan.ts разбор и проверка плана от модели
game/bot/directives.ts разовые поручения модели и их сверка со снимком
game/bot/budget.ts учет обращений к модели и потолки переписки
game/bot/llm.ts вызов провайдера; любая неудача возвращает null
routes/ REST + middleware авторизации и разбор ввода
services/authService.ts пароли, JWT, смена пароля, вход через Google
services/commanderService.ts профиль командира и онбординг
services/achievementService.ts каталог достижений и их выдача
services/mapService.ts сборка карты системы под конкретного игрока
services/marketService.ts биржа: ордера, сведение сделок, бартер (в транзакциях)
services/botService.ts создание, пауза и удаление ботов
services/buildingService.ts карточки построек и проекция уровней вперед
services/technologyService.ts карточки технологий и проекция уровней вперед
services/syndicateService.ts синдикаты: ранги, заявки, казна, Кіш, модули и стройка, налог, кодекс
services/syndicateAccess.ts членство, права и пакты — отдельно, чтобы не замкнуть импорты
services/pactService.ts пакты синдикатов: предложение, ответ, расторжение, письма
services/hubStock.ts склады биржи: склад хаба и общий склад под блокировкой
services/adminService.ts пульт гейм-мастера: чтение и правка состояния
services/mailService.ts центр связи: ящик, доставка, приватность
services/reportMail.ts сборка текста системных отчетов (чистый модуль)
services/simulationService.ts боевой симулятор: расчет без записи в БД
services/fleetTemplateService.ts шаблоны флотов
services/warService.ts дипломатия, отчеты о боях, предупреждение перед атакой
services/scoreService.ts рейтинг командиров и синдикатов с кешем
types/api.ts типы ответов REST
types/socket.ts типизированные интерфейсы Socket.IO
scripts/ генерация галактики, проверка формул, стенд для тестов
public/ клиент: вход, командный центр, карты, биржа, синдикаты
(index.html хранит спрайты иконок и иллюстраций)
prisma/ схема и миграции
tests/ тесты формул на игровых модулях и стресс-тесты по живому API
| Метод | Путь | Назначение |
|---|---|---|
| POST | /api/auth/register | регистрация по email и паролю |
| POST | /api/auth/login | вход, выдача JWT |
| GET | /api/auth/config | публичные настройки входа и версия игры |
| POST | /api/auth/oauth/:provider | вход через Google (id_token) или Telegram (initData) |
| POST | /api/auth/password/reset-request | запрос кода смены пароля |
| POST | /api/auth/password/reset | установка нового пароля |
| GET | /api/auth/me | текущая сессия: аккаунт, командир, аватары |
| POST | /api/auth/commander | создание командира (онбординг) |
| GET | /api/state | состояние игрока: базы, очереди, технологии, флот |
| GET | /api/bases/:id/buildings/:type | проекция постройки на десять уровней вперед |
| GET | /api/bases/:id/technologies/:tech | проекция технологии на десять уровней вперед |
| POST | /api/bases/:id/build | поставить здание в стройку |
| POST | /api/bases/:id/rush | доделать стройку за криптогривну |
| POST | /api/bases/:id/build/cancel | отменить стройку с полным возвратом |
| POST | /api/bases/:id/research | запустить исследование |
| POST | /api/bases/:id/research/cancel | отменить исследование |
| POST | /api/bases/:id/research/join | присоединить лабораторию колонии к идущему исследованию |
| POST | /api/bases/:id/ships | заказать корабли на верфи |
| POST | /api/bases/:id/ships/cancel | отменить заказ верфи |
Экспедиция отправляется тем же POST /api/bases/:id/fleets с миссией EXPEDITION:
без цели — в глубокий космос родной системы, с targetSystemId — в чужую.
Тесты в tests/ делятся надвое. Большая часть считает формулы
напрямую на игровых модулях и сервера не требует: бой, полеты, хранилища,
рейтинг, обломки, шпионаж, решения ботов, учет обращений к модели, проверка
id_token и соответствие растровых файлов типам объектов. Остальные —
стресс-тесты, права в синдикатах, гонки на бирже и доступ к пульту
гейм-мастера — бьют по живому API работающего dev-сервера.
Тесты не проходят проверку типов: tsconfig.json включает только src/**,
поэтому расхождение с изменившейся сигнатурой видно исключительно на запуске.
Меняя экспортируемый тип, наборы надо прогнать, а не понадеяться
на npm run typecheck.
npm run --silent test:stand > stand.json
node tests/auth.mjs
node tests/stress-market-fleet.mjs stand.json
node tests/stress-races.mjs stand.json
node tests/syndicates.mjs stand.json
node tests/economy-build.mjs stand.json
Тесты хранилищ проверяют формулы напрямую на игровых модулях, поэтому им нужен только запущенный сервер для последней секции:
npm run test:storage
npm run test:commander
npm run test:mail
npm run test:admin
npm run test:debris
npm run test:assets
npm run test:combat
npm run test:fleets
npm run test:score
npm run test:speed
npm run test:syndicate
npm run test:oauth
npm run test:bot
npm run test:budget
npm run test:espionage
strict, noUncheckedIndexedAccess, exactOptionalPropertyTypes.
Отдельный прогон npm run audit добавляет noUnusedLocals и noUnusedParameters.public/app.js в tsconfig.json не входит, поэтому у него свой узкий гейт —
npm run audit:client. Он отбирает из checkJs только «имя не найдено»:
полная проверка дает полторы сотни замечаний о типах DOM и как гейт непригодна,
а вот вызов удаленной функции node --check пропускает — и падает уже
в браузере, унося все, что рисуется после.Response<...> (types/api.ts),
события Socket.IO — через типовые контракты (types/socket.ts):
расхождение между сервером и клиентом ловит компилятор.npm run check:rules).UPDATE,
жесткий перезапуск между шагами не задваивает начисления и не теряет корабли.Правила разработки и текущая архитектура — в CLAUDE.md.
| тесты приватности почты и формулировок системных отчетов |
npm run test:admin | тесты доступа к пульту гейм-мастера и синхронизации с тиком |
npm run test:score | формулы рейтинга: постройки по уровням, флот, ресурсы |
npm run test:speed | технологии скорости: ускорение стройки и плата энергией |
npm run test:syndicate | правила синдикатов: ранги, налог, Кіш и его модули, стройка, налет, пакты, журнал казны |
npm run test:oauth | проверка id_token Google: подпись, aud, срок, email_verified |
npm run test:debris | тесты формулы обломков и атомарности их сборки |
npm run test:assets | сверка имен растровых файлов с типами объектов |
npm run test:combat | тесты боевого движка: раунды, щиты, скорострел, технологии, обломки, баланс |
npm run test:fleets | тесты полетов: скорость, топливо, прыжки, типы миссий, правила вылета |
npm run test:bot | решения бота: порядок застройки и науки, состав флота, щит новичка, торговля, коалиция |
npm run test:budget | учет обращений к языковой модели: дневная норма, паузы, потолки переписки |
npm run test:espionage | лестница шпионажа: раскрытие по разнице уровней, заметность, нагрузка письма |
npm run admin:grant | выдать (или снять --revoke) роль администратора аккаунту |
| POST | /api/bases/:id/defenses | заказать стационарную оборону |
| POST | /api/bases/:id/defenses/cancel | отменить заказ обороны |
| GET | /api/galaxy | макро-карта: все системы с координатами |
| GET | /api/map?systemId= | карта системы (по умолчанию — родной) с туманом войны |
| GET | /api/planets/at?x=&y=&position= | планета по координатам: система X:Y и орбита |
| GET | /api/planets/:planetId/joint-attacks | атаки на планету, к которым можно присоединиться |
| GET | /api/leaderboard | рейтинг командиров и синдикатов |
| POST | /api/bases/:id/fleets/preview | предрасчет маршрута: время, топливо, трюмы, Брама |
| POST | /api/bases/:id/fleets | отправить флот: любая миссия, в том числе удержание, рейсы в Кіш, налет и совместная атака |
| POST | /api/fleets/:fleetId/recall | отозвать флот с удержания |
| GET | /api/market | склады биржи, стакан, сводка рынка, бартер, первая страница журнала |
| GET | /api/market/history/:resource | средняя цена по дням за 30 суток |
| GET | /api/market/trades | страница журнала сделок, курсор ?before=, фильтры ?resource= и ?mine=1 |
| POST | /api/market/storage/upgrade | расширить склад биржи за криптогривну |
| POST | /api/market/orders | выставить ордер на покупку или продажу |
| POST | /api/market/orders/:id/fill | исполнить чужой ордер |
| DELETE | /api/market/orders/:id | снять свой ордер |
| POST | /api/market/barter | предложить обмен ресурса на ресурс |
| POST | /api/market/barter/:id/accept | принять обмен целиком |
| DELETE | /api/market/barter/:id | снять свое предложение обмена |
| GET | /api/war | дипломатия, пакты, отчеты о боях и экспедициях |
| POST | /api/war/declare | объявить личную войну |
| POST | /api/war/peace | заключить личный мир |
| POST | /api/war/syndicate/declare | объявить войну синдикату (право дипломатии) |
| POST | /api/war/syndicate/peace | заключить мир между синдикатами (право дипломатии) |
| POST | /api/war/syndicate/pact/propose | предложить пакт другому синдикату |
| POST | /api/war/syndicate/pact/respond | принять или отклонить предложение пакта |
| POST | /api/war/syndicate/pact/cancel | отозвать предложение или расторгнуть пакт |
| GET | /api/syndicates | список синдикатов и свой синдикат целиком: казна, Кіш, модули, стройка, состав |
| POST | /api/syndicates | основать синдикат |
| GET | /api/syndicates/:id/codex | кодекс синдиката для кандидата |
| POST | /api/syndicates/:id/apply | вступить или подать заявку с принятием кодекса |
| POST | /api/syndicates/applications/:id/approve | одобрить заявку |
| POST | /api/syndicates/applications/:id/reject | отклонить заявку |
| POST | /api/syndicates/members/:id/kick | исключить участника |
| POST | /api/syndicates/members/:id/rank | назначить ранг |
| POST | /api/syndicates/members/:id/leader | передать лидерство |
| POST | /api/syndicates/members/:id/payout | выдать гривну из казны |
| POST | /api/syndicates/ranks | создать ранг |
| POST | /api/syndicates/ranks/:id | изменить ранг |
| POST | /api/syndicates/ranks/:id/delete | удалить ранг |
| POST | /api/syndicates/rules | правила набора: режим, взнос, минимальный рейтинг |
| POST | /api/syndicates/description | описание синдиката |
| POST | /api/syndicates/tax | ставка налога с крипто-фермы |
| POST | /api/syndicates/codex | новая редакция кодекса |
| POST | /api/syndicates/kish/upgrade | начать стройку уровня Коша |
| POST | /api/syndicates/treasury/upgrade | начать стройку Скарбниці |
| POST | /api/syndicates/academy/upgrade | начать стройку Академии |
| POST | /api/syndicates/watch/upgrade | начать стройку Дозора |
| POST | /api/syndicates/gates | начать стройку или повышение Брамы в системе |
| POST | /api/syndicates/construction/cancel | отменить стройку в Коше с полным возвратом |
| GET | /api/syndicates/projection/module/:module | уровни вперед у модуля Коша (?systemId= для Брамы) |
| GET | /api/syndicates/projection/tech/:tech | уровни вперед у технологии синдиката |
| POST | /api/syndicates/research | начать изучение технологии синдиката |
| POST | /api/syndicates/kish/defenses | поставить оборону Коша из казны |
| POST | /api/syndicates/kish/move | перенести Кіш к своей Браме |
| POST | /api/syndicates/donate | взнос в казну с личного счета |
| POST | /api/syndicates/broadcast | рассылка по составу |
| POST | /api/syndicates/leave | покинуть синдикат |
| POST | /api/syndicates/disband | распустить синдикат (главарь) |
| POST | /api/commander/simulate | прогон боя без записи в БД |
| GET | /api/commander/espionage | разведанные цели для симулятора |
| GET | /api/commander/fleet-templates | список шаблонов флотов |
| POST | /api/commander/fleet-templates | создать шаблон |
| PUT | /api/commander/fleet-templates/:id | изменить шаблон |
| DELETE | /api/commander/fleet-templates/:id | удалить шаблон |
| GET | /api/mail | ящик командира, фильтр ?type= |
| POST | /api/mail | личное письмо по позывному |
| POST | /api/mail/:id/read | пометить письмо прочитанным |
| POST | /api/mail/read-all | пометить прочитанным весь ящик |
| DELETE | /api/mail/:id | удалить письмо |
| GET | /api/admin/schema | справочник ключей для формы админки (admin) |
| GET | /api/admin/dashboard | сводка по серверу: онлайн, активность, колонии (admin) |
| GET | /api/admin/commanders | список командиров, поиск ?search= (admin) |
| GET | /api/admin/commanders/:id | полный стейт игрока (admin) |
| PATCH | /api/admin/commanders/:id | принудительная правка состояния (admin) |
| POST | /api/admin/commanders/:id/password-reset | выдать игроку код смены пароля (admin) |
| POST | /api/admin/commanders/:id/block | закрыть или вернуть доступ (admin) |
| POST | /api/admin/commanders/:id/message | письмо игроку от гейм-мастера (admin) |
| DELETE | /api/admin/commanders/:id | удалить учетную запись, подтверждение позывным (admin) |
| GET | /api/admin/bots | список ботов и каталог характеров (admin) |
| POST | /api/admin/bots | создать бота: позывной, характер, система (admin) |
| POST | /api/admin/bots/:id/active | пауза и возобновление решений (admin) |
| POST | /api/admin/bots/:id/nudge | заставить бота решать прямо сейчас (admin) |
| DELETE | /api/admin/bots/:id | удалить бота вместе с колониями (admin) |