48 KiB
LoVisual
Проект: клиентский мод (mod/, бывш. combatant-client-26.2/) + платформа
(backend/, frontend/ — сайт+бэкенд), см. Фазу 10/Подсистемы 1-3 ниже.
Старый лаунчер (Rust + Slint) и старый backend (Rust + axum, другой дизайн) были удалены раньше (решение 2026-09-05) —
backend/иfrontend/сейчас строятся заново по дизайну из Фазы 10 / Подсистем 1-3, это не восстановление старого кода.
Коммиты — на английском (решение 2026-09-23, действует с этого момента вперёд; более старые коммиты на русском не переписываем).
TEMP-NOTE (удалить этот блок, когда сделано): после окончания текущего рефакторинга владелец squash'ит историю git с 100+ коммитов до ~20, затем пушит и открывает репозиторий из приватного в публичный (сейчас приват — ранний код был спижен). Это план владельца, не техническая задача агента — просто держать коммиты чистыми/по делу до этого момента.
Правила (для мода, продублированы в его TODO)
- KISS / DRY / SOLID
- Макс. 200 строк на файл (фасады-делегаты исключение)
- Макс. 4 файла на папку
- Никакого статика, никаких this-escape, никаких мёртвых миксинов
- Без пустышек; каждый этап компилируется (
./gradlew build) - Тесты обязательны для новых модулей (JUnit)
Правила платформы (backend/, frontend/) — исследовано 2026-09-23
KISS/DRY/SOLID и "без пустышек, тесты обязательны" из правил мода действуют и здесь без изменений. Ниже — то, что отличается или уточняется под Rust/Axum и React/Vite, по итогам ресёрча актуальных практик и антипаттернов.
Лимит на файл: ≤250 строк (не 200, как у мода) — сознательно шире:
у мода лимит калибровался под Java (многословный синтаксис, много
boilerplate на класс), а в Rust/TypeScript тот же объём логики обычно
компактнее на строку, но модуль (структуры + impl + тесты в одном файле,
как в backend/PLAN.md) естественно тянет чуть больше. Фасады-делегаты —
то же исключение, что и у мода.
Макс. 4 файла на папку — то же правило, что у мода, без изменений:
превышение — сигнал выносить смысловую подпапку. Считаются только файлы,
подпапки в лимит не входят (проверено на живом коде мода: addon/ — 0
файлов + 4 подпапки).
Backend (Rust/Axum) — паттерны:
- Модуль на домен (
auth/,accounts/,device/,avatars/), а не по техническому слою (handlers/,models/,services/вперемешку) — так одну фичу не размазывает по всему дереву папок - Repository-паттерн: SQL живёт только в
*/repo.rs, хендлеры его не видят напрямую (уже так вbackend/PLAN.md) - Собственный
enum AppErrorвместоString/anyhowкак типа ошибки на границе HTTP —Stringтеряет контекст и заставляет аллоцировать на каждой ошибке - Axum-экстракторы (
Json<T>,State<T>) — валидация на границе запроса компилятором, а не руками в теле хендлера
Антипаттерны — сознательно избегаем:
.unwrap()/.expect()на данных из запроса — только?с конвертацией черезFrom<...> for AppError(уже вGlobal Constraintsплана)- Чрезмерный
.clone()вместо ссылок — первый признак борьбы с borrow checker'ом не по делу - Блокирующий I/O внутри
async fn(напр. синхронный файловый API) — душит рантайм tokio целиком, а не только один запрос - "Бог-структуры" (
AppStateсо всем на свете) — состояние делится по доменам (AuthState,DeviceState,AvatarState— уже так в плане)
Frontend (React/Vite) — структура фичами, не по типу файла:
Один из самых частых промахов — раскладывать по /components, /hooks,
/services вперемешку: фича размазывается по всему дереву, и удалить её
целиком нельзя, не зацепив другие фичи. Вместо этого — src/features/<имя>/
хранит свои компоненты+хуки+API-вызовы+типы вместе; общее (src/shared/)
только для того, что реально используют 2+ фичи. Детали — в
frontend/ARCHITECTURE.md.
Источники: Rustify — Axum Guide 2026, Rust FAQ — Common Anti-Patterns, Rust Design Patterns — Anti-patterns, Robin Wieruch — React Folder Structure 2026.
Статус
- Фаза 1: Ребрендинг
combatant→lovisual - Фаза 2: Выкинуть Runtime/JarReplacement + Panic
- Фаза 3: Разбивка 12 файлов-гигантов
- Фаза 3.5: Удаление читерских модулей (легит-направление)
- Фаза 4: Security (BackdoorProtection, StaffTracker)
- Фаза 5: Нормализация модулей (ленивый Minecraft, GameClock)
- Фаза 6: Тесты (EventBus, конфиг, утилиты) — см.
mod/TODO.md - Фаза 7: Новые модули по мотивам SoupVisuals — ЗАВЕРШЕНА 2026-09-16 (Cosmetics Overhaul 2026-09-11; Gps/HitBubbles/AmbientParticles/EndCrystal/Balancer/Trinket/ HandsShader — 2026-09-15…16, см. ниже)
- Фаза 8.5: Аудит структуры ≤200/≤4 — 8.5.1 (папки >4) ЗАВЕРШЕНА 2026-09-10
(0 папок >4, коммиты 8846d53…303b17e); 8.5.2 (гиганты >600) ЗАВЕРШЕНА
2026-09-28 (0 файлов >600, крупнейший — Renderer2D 474, фасад-исключение);
план в
mod/TODO.md - Задача «Скачивание» (2026-09-29, В РАБОТЕ, детали в конце файла) — страница
/downloadс двумя кнопками: прямая скачка jar с нашего бэкенда + кнопка GitHub под лентой «временно не работает»; jar ≥55 МБ живёт на VDS и отдаётся gateway (GET /downloads/lovisual.jar); кнопки лендинга/футера ведут на страницу, а не на файл - Фаза 11: Серверный плагин — сервер может отключать функции мода (идея владельца 2026-09-29, скетч в конце файла) — дизайн не написан, реализация не начата
- Фаза 8.5.3: остаток разбивки гигантов мода (файлы >200 → ≤200;
крупнейшие сейчас: ConfigDiffPanelComponent 393, RotationManager 391,
ScriptedTriangulatorHudPanel 386, BetterButtons 381) — не марафон,
по мере касания, план в
mod/TODO.md - Фаза 9: Оптимизация FPS — 9.1 (мёртвые грузы) ЗАВЕРШЕНА 2026-09-11 (137121e…4d60d3d); 9.2 Optimize ЗАВЕРШЕНА 2026-09-25 (ade82f06…8746074d, 4 тумблера + OptimizeState); 9.3 A/B-мерка убрана из плана (решение владельца 2026-09-25: нужен запуск Minecraft на Windows, задачей не является) — фаза закрыта
- Фаза 10: Платформа (сайт + бэкенд) — Подсистемы 1/2/3 спроектированы 2026-09-23
(см. ниже). Реализация: план backend Подсистемы 1 в
backend/PLAN.md(написан 2026-09-23), frontend/Подсистема 2/3 — планы позже. Бэкенд 2026-09-25: Подсистема 1 backend ЗАВЕРШЕНА —accounts-service,common,gateway(identity, рейт-лимиты, CORS, refresh-ротация, device links) иconfigs-service(4 слота, share-коды, витрина publish/browse/detail/copy, публичный/users/{id}с бейджемearly); планы:backend/PLAN.md,backend/gateway/PLAN.md,backend/configs-service/PLAN.md; e2e через gateway проверен вручную. Следующий шаг — фронтенд (frontend/PLAN.md) и интеграция мода. Решения 2026-09-25: мод бесплатный (никаких цен/подписок/ключей на сайте); сайт двуязычный ru/en (i18n); кнопка «Скачать» — прямая ссылка наreleases/latest/download/lovisual.jarв GitHub; в план сайта добавлены редактор тем в браузере, страница «Скачать» + ченджлог, публичные профили; лендинг — насыщенный (живой ClickGui/HUD, частицы), см.frontend/PLAN.md. Интеграция мода 2026-09-27: первый шаг — URL бэкенда стал runtime-настройкой, а не константой сборки. НовыйPlatformConfig(mod/src/main/java/dev/loki/lovisual/features/platform/, +BackendEndpointнормализатор) хранитbackendBaseUrlчерез тот жеConfigSerializer, редактируется в ClickGui (категория UTILITY главных настроек, рядом соStyleConfig). Значение по умолчанию — плейсхолдерhttps://api.lovisual.example(реальный прод-домен ещё нигде в репо не зафиксирован). HTTP-клиент и сама интеграция (auth/device-link/витрина) — отдельная будущая задача. Решение 2026-09-29 (см. задачу «Скачивание» ниже): прямая ссылка на GitHub из кнопок убрана — jar отдаёт наш бэкенд, GitHub остаётся второй кнопкой под лентой «временно не работает».
Фаза 7: Новые модули — референс SoupVisuals
Референс:
ref/soupvisuals-src/— SoupVisuals 3.2.0 (MC 1.21.11, лицензия SOUP-1.0). Код референса НЕ копируем (чужая лицензия + декомпил) — только пере-реализация идей под наши правила (≤200 строк/файл, ≤4 файла/папку, без статики, тесты). Модули референса лежат вref/soupvisuals-src/padej/soup/implement/features/modules/.
- Hitboxes (visuals) — цветные хитбоксы сущностей, как у Soup: ТОЛЬКО рендер
контейнера с настройкой цвета/видимости, БЕЗ увеличения хитбокса (увеличение —
чит, удалён в Фазе 3.5). Референс:
visuals/Hitboxes.java - Watermark (HUD, draggable) — обещан в README, в коде отсутствует
- Gps (world) — ЗАВЕРШЕНО 2026-09-15: компас-стрелка + маркер к точке
(
visuals/world/Gps.java,util/world/GpsBearing.javaчистая математика + тесты, команда%gps <x> <y> <z> | clear). Референс:world/Gps.java— идея взята (стрелка+маркер+дистанция+авто-удаление), код не копировался. Xaero-связка НЕ сделана (была опциональной идеей, не в скоупе запроса). Коммит fb0b288. - Light (world) — кастомный цвет освещения (у Soup 41 стр.). Референс:
world/Light.java - HitBubbles (particles) — ЗАВЕРШЕНО 2026-09-15: отдельный модуль
visuals/effects/HitBubbles.java(не режим HitEffect.BUBBLES — независимое включение, трёхфазная анимация появление/жизнь/исчезновение через новыйThreeStageProgress). Заодно вынесены общие хелперы из HitEffect (DRY):AttackHitPoint,OrientedQuadRenderer,AnimatedColorModeOptions. Референс:particles/HitBubbles.java(идея взята, код не копировался; интерполяции/множественные текстуры Soup не портированы). Коммит 5f2fc70. - AmbientParticles + FireFlyParticle (particles) — ЗАВЕРШЕНО 2026-09-15: модуль
visuals/particles/AmbientParticles.java, два режима — Ambient (плывущие мошки) и Firefly (блуждание + кольцевой буфер шлейфа вместо Trail-объектов референса). 19 визуальных стилей референса (Stars/Hearts/Bloom/.../Pyramid, asset-тяжёлые) НЕ портированы — фича-крип чит-клиента вне направления проекта; цвет через уже существующий AnimatedRenderColors вместо своей Sync/Custom-системы. Коммит 28ec166. - NameProtect (misc) — подмена своего ника и ников друзей в тексте чата/скорборда
(у Soup 42 стр.). Референс:
other/NameProtect.java - EndCrystal (visuals) — ЗАВЕРШЕНО 2026-09-15:
visuals/scene/EndCrystal.java+mixins/render/entity/EndCrystalRendererMixin.java(WrapOperation на ModelPart.render, различает 2 вызова glass + 1 cube счётчиком-ordinal — тонировка/скрытие частей) + inject вClientLevelMixin.removeEntity(осколки-вспышка на месте разрушения, т.к. ваниль убирает сущность мгновенно). Референс:other/EndCrystal.java— идея взята (кастомный цвет, "доиграть" разрушение), Sync/Custom×4-цвета+Wave/Vertex упрощены до общего AnimatedRenderColors. Коммит 50db8e3. - Trinket (не "2.0" — CosmeticDisplay удалён откатом 2026-09-11, апгрейдить нечего) —
ЗАВЕРШЕНО 2026-09-16: отдельный модуль
visuals/items/Trinket.javaв стиле текущих ChinaHat/TazikHat.util/physics/TrinketPhysics(чистая, тесты) — шарик на пружине у ног игрока (гравитация/отскок/тетер). Рендер — тонированный billboard (наш AnimatedRenderColors) + затухающий шлейф (кольцевой буфер) + звук на отскок (ванильный SoundEvents.SLIME_JUMP). Библиотека 10 иконок-спутников референса и 8 кастомных звуковых тем НЕ портированы — нужны новые ассеты. Референс:other/Trinket.java(там тоже почти нет реализации — только настройки). Коммит 840a716. - Cosmetics Overhaul (Unified Cosmetics) — ЗАВЕРШЁН 2026-09-11:
слоты
CosmeticSlot(MODEL/PET/HEAD/SHOULDERS/BACK/WAIST/LEGS/HELD), парсингslotиз avatar.json + эвристика по имени папки,CosmeticManagerна EnumMap (аксессуары — несколько одновременно),CosmeticsScreen(3D-превью drag/zoom + хотспоты + правая панель live-apply с "None" первым), per-slot offset/rot/scale через NumberValue-поля, built-in ChinaHat/TazikHat/BackSword вcosmetics/builtin/(старые модули chinahat/tazikhat/backsword удалены), i18n en+ru, тесты CosmeticSlotTest/CosmeticsHotspotTest. -CosmeticDisplay 232 / TazikHatRenderer 242 / BackSwordRenderer 231 >200— разбиты: CosmeticDisplay 196 + CosmeticBuiltins (значения+диспетчер built-in), TazikHat 186 (палитра-математика переиспользует ChinaHatPalette), BackSword 200 (edge/glow слиты в параметризованный drawKatana). - Не сделано из плана (следующий заход): иконки-превью built-in в списке слота, правка слотов SHOULDERS/LEGS/PET из экрана (сейчас только HEAD/BACK built-in), предпросмотр до эквипа. - Balancer (mctiers) — ЗАВЕРШЕНО 2026-09-16:
features/module/modules/misc/Balancer.java(fixType Vanilla/UHC/Pot/NethOP/SMP/Sword/Axe/Mace) +util/mctiers/MCTiersCache(асинхронный кэш, паттерн SkinManager) +util/mctiers/MCTiersTier(форматирование, тесты) + команда%tier <ник> [режим]. Референсmctiers/Balancer.javaпочти пуст — логика была в недоступной McTiersModule-базе; реальный публичный API mctiers.com найден и проверен через сторонний open-source плагин (sinnayuh/MCTier):GET https://mctiers.com/api/search_profile/<username>. Коммит cf81ee4. - HandsShader — ЗАВЕРШЕНО 2026-09-16: сверка показала, что Chams уже реализует
9/10 режимов Soup (Solid/Chroma/Balatro/Smoke/Stripes/Glow/Glass/Snow + свои
Metallic/Noise) и полный dual/blend-набор (Mix/Add/Multiply/Screen/Overlay/
Difference,
HandBlendUniforms) — сверх ожиданий. Не хватало только "Invert": добавленhand_chams_invert.frag(RGB-инверт под маской руки через уже существующий UBO HandChams/u_Mix.x, без HSV-тумблеров и edge-glow референса — это дублировало бы наш Glow) + регистрация пайплайна + вписан в оба mode-списка Chams (основной и dual shader1/shader2). Референс:visuals/HandsShader.java. Коммит 76ffdec. Это был последний пункт Фазы 7 — SoupVisuals-раздел закрыт полностью (Gps/HitBubbles/AmbientParticles/EndCrystal/Balancer/Trinket/HandsShader). - Фикс ChinaHat — текущая геометрия кривая (смахивает на самбреро/зонт);
переделать модель в нормальную конусную шляпу по референсу Soup
(
visuals/ChinaHat.java+ChinaHatModel.java).
Фаза 10: Платформа — сайт + бэкенд (дизайн от 2026-09-23)
Прим.: лаунчер и старый backend (Rust+axum) были удалены ранее (см. шапку файла) — это НЕ восстановление старого кода, а новый дизайн с нуля под текущие требования. Статус: дизайн готов, backend Подсистемы 1 реализован 2026-09-25 (планы в
backend/PLAN.md,backend/gateway/PLAN.md,backend/configs-service/PLAN.md); дальше — фронтенд (frontend/PLAN.md). Код — в выделенныхbackend/иfrontend/(созданы 2026-09-23), отдельно отmod/.
Границы (декомпозиция)
- Подсистема 1 (этот дизайн): аккаунты, облачные конфиги, аватарки, публичная витрина конфигов.
- Подсистема 2 (backlog, отдельный дизайн): RPC-чат (переименован из рабочего названия "RVS" — отдельная чат-группа для владельцев клиента, не привязана к конкретному minecraft-серверу), друзья/статус онлайн, виджеты-телеметрия друга (HP/координаты/сервер/в бою или нет поверх HUD). Требует realtime-слой (WebSocket/presence) — не влезает в REST-модель Подсистемы 1.
Архитектура
Микросервисы с самого старта (осознанный выбор владельца проекта, не
default-рекомендация): accounts-service, configs-service, chat-service
(зарезервирован под Фазу 11), каждый — отдельный Rust/Axum процесс, общение
между сервисами по gRPC (tonic). Перед ними — отдельный API Gateway
(тоже Axum): единственная публичная точка (api.<domain>), терминирует TLS,
проверяет JWT один раз, роутит по gRPC во внутренние сервисы; сайт и мод не
знают о микросервисах и стучатся только в gateway. Для будущего chat-service
gateway дополнительно проксирует WebSocket (wss://api.<domain>/chat/ws).
Один физический Postgres-инстанс/порт, у каждого сервиса — своя отдельная БД
внутри этого инстанса (accounts_db, configs_db, chat_db) — логическая
изоляция без отдельных серверов на старте. Аватарки и файлы конфигов — в
S3-совместимом хранилище (MinIO), не в Postgres.
Фронтенд сайта: React + Vite + Tailwind CSS. Дизайн/палитра — плагин
design-assets (jezweb/claude-skills маркетплейс, skills color-palette,
favicon-gen, icon-set-generator, image-processing, ai-image-generator)
- встроенный skill
frontend-design.
Модель данных
accounts_db
accounts: id, email (unique), password_hash (argon2id), display_nick (произвольный, НЕ привязан к minecraft-нику), created_atavatars: account_id, s3_key, uploaded_atdevice_links: id, account_id, device_token_hash, linked_at, last_seen — привязанные устройства (моды), видны/отзываемы в личном кабинете
configs_db
config_slots: id, account_id, slot_index (1..4 — фиксированный лимит на аккаунт), name, data (jsonb), updated_atshare_codes: config_slot_id, code (unique, криптографически случайный, постоянный, владелец может регенерировать — старый код инвалидируется)showcase_listings: config_slot_id, title, description, published_at — публичная витрина (фича из brainstorm-опроса)
Свой конфиг грузится по имени/индексу напрямую. Чужой — только по коду через
%config load <код> в игре (см. API ниже); одноразовость кода осознанно
отклонена — код обязан быть постоянным (ссылка-приглашение), пока владелец
сам не перевыпустит.
Auth — два независимых пути
Сайт: обычный email + пароль.
POST /auth/register{email, password, nick}POST /auth/login→ access JWT (~15 мин) + refresh-токен (httpOnly cookie)POST /auth/refresh,POST /auth/logout
Мод → аккаунт: OAuth 2.0 Device Authorization Grant (как Twitch/PlayStation), пароль в мод никогда не вводится.
- Мод:
POST /device/code→{device_code, user_code, expires_in}(user_codeкороткий, напр.ABCD-1234) - Мод показывает
user_codeв настройках, игрок вводит его наsite.<domain>/link(уже залогинен на сайте) →POST /device/confirm - Мод параллельно поллит
POST /device/token{device_code} каждые 2-3с - После подтверждения — долгоживущий
device_token(хранится у мода локально), используется как Bearer-заголовок ко всем запросам gateway - Личный кабинет → список привязанных устройств с
last_seenи кнопкой revoke (единичный отзыв, не ломает остальные устройства)
API конфигов и аватарок
GET /configs→ 4 слота аккаунта (name, updated_at, share_code)PUT /configs/{slot}{name, data} → сохранить, last-write-winsGET /configs/{slot}→ своё содержимоеPOST /configs/{slot}/regenerate-codeGET /configs/shared/{code}→ чужой конфиг по коду (код = секрет, отдельной авторизации получателя не требует) — этот путь дёргает%config load <код>POST|DELETE /configs/{slot}/publish→ витринаGET /showcase?sort=new|popular&page=,GET /showcase/{listing_id}— публично, без авторизации; "скопировать к себе" → копия в свободный слотPOST /avatars(multipart, ≤5MB, png/jpg/webp, валидация по magic bytes, не по расширению) → ресайз до 256×256, S3 + CDN-URL вaccounts.avatar_url
Безопасность
- Rate-limit на
/auth/loginи/device/*(брутфорс / спам кодов) password_hash— argon2iddevice_tokenхранится хэшированным в БД, как пароль- Share-код — криптографически случайный, не инкрементный ID (не перебираем)
Пасхалки (решение владельца 2026-09-25)
.env-ловушка (gateway): запросы на.env(включая через../) → 200 + фейковый шуточный.env(«nice_try_skiddie…»), в апстрим не уходят, IP в лог. Делается вместе с фиксом обхода лимитов через dot-segments.- Konami code на сайте (↑↑↓↓←→←→BA): ClickGui на главной включает TrollfaceMask, всё уходит в радужную тему.
- Сообщение в консоли DevTools: ASCII-логотип LoVisual + «Шаришь? Исходники аддонов — на витрине».
IDDQD:%config load IDDQDв моде и/configs/shared/IDDQDна сайте отдают шуточный конфиг «God mode» — всё выключено, кроме ChinaHat. Код не пересекается с настоящими (в алфавите share-кодов нетI, длина 5 ≠ 8).- 404 = пустой мир: падает блок, по клику «ломается» с частицами, как в игре.
/coffee→ 418 I'm a teapot (gateway) со ссылкой на мод.
Планы: frontend/PLAN.md Task 11, backend/configs-service/PLAN.md Task 7,
backend/gateway/PLAN.md Task 12; мод-часть %config load IDDQD — в плане интеграции мода.
Подсистема 2: RPC-чат + друзья + виджеты-телеметрия (дизайн от 2026-09-23)
Статус: спроектировано, реализация не начата. Требует realtime-слой (WebSocket + presence), поэтому — отдельный
chat-service(см. Фазу 10, раздел "Архитектура"), а не REST как у Подсистем 1/3.Важно не путать с уже существующей в моде системой "друзей" (
NameProtectи т.д.,features/module/modules/misc/protect/NameProtect.java) — это локальный список ников на клиенте для защиты от пинга/подсветки, не связан с аккаунтами. Ниже — отдельная, новая система друзей на аккаунтах.
Структура чата
Один глобальный канал ("все онлайн-владельцы клиента") + личные сообщения 1-на-1 только между взаимными друзьями. Без тематических каналов/групп — не усложняем на старте.
- Только live-доставка, без хранения истории на сервере (как IRC) —
ушёл из чата/оффлайн — старое не восстановить. Упрощает
chat_dbдо почти пустой схемы (не нужна retention-политика, GDPR-очистка и т.п.) chat_dbхранит только: список текущих online-подключений (для presence) и friend-графа (см. ниже) — сами сообщения в БД не пишутся, гоняются напрямую через WebSocket-хаб в памяти chat-service
Друзья (новая, account-based система)
- Добавление — запрос/принятие по нику аккаунта (не по minecraft-нику):
поиск на сайте или в клиенте →
POST /friends/request{target_nick} → получатель видит входящий запрос (на сайте и/или в клиенте) →POST /friends/accept|decline chat_db.friendships: account_id_a, account_id_b, status (pending/accepted), created_at (симметричная связь после accepted)- Личные сообщения в чате доступны только между accepted-друзьями
Виджеты-телеметрия друга (HUD)
- Данные: HP, координаты, minecraft-сервер (адрес/имя), в бою (да/нет) — каждое поле передаётся отдельно, не пакетом "всё или ничего"
- Настраиваемый шаринг по полям (выбор пользователя, приватность):
chat_db.telemetry_sharing_prefs: account_id, share_hp (bool), share_coords (bool), share_server (bool), share_combat_state (bool) — дефолт консервативный (напр. только HP+combat включены, координаты/сервер выключены by default, т.к. чувствительны) - Транспорт: тот же WebSocket-канал chat-service, отдельный тип сообщения
telemetry_update(broadcast только подписанным взаимным друзьям, с фильтром поsharing_prefsполучателя-источника на сервере — клиент никогда не получает поле, которое источник не разрешил шарить) - Отображение — новые draggable HUD-элементы по образцу существующих
(
registerDraggableHudElement), один виджет на друга или агрегированный список — решить на этапе UI-дизайна перед реализацией
Presence (онлайн-статус)
- Мод коннектится к
wss://api.<domain>/chat/wsсdevice_tokenпри старте клиента (не требует отдельного логина — токен уже есть после device-link из Подсистемы 1) online= есть открытый WebSocket;last_seenобновляется на disconnect- Список друзей на сайте/в клиенте показывает online/offline живым статусом через тот же сокет (site тоже может подключиться, если залогинен — видеть друзей онлайн из браузера)
Профили/статусы (лёгкая версия, backlog внутри backlog)
Идея из brainstorm-опроса ("уровень/бейджи/тайтлы", "давно с нами", "шарил N
конфигов") — не проектируется в деталях сейчас, т.к. это чисто косметическая
надстройка без технических рисков — легко добавить в accounts_db (поля
created_at уже есть, badges — просто jsonb-массив) на любом этапе позже,
не блокирует остальную Подсистему 2.
Рейт-лимиты Подсистемы 2 (дополнение к общей таблице в Фазе 10)
| Действие | Лимит |
|---|---|
| Отправка сообщения (глобальный канал) | 1 / сек / аккаунт, burst 5 |
| Отправка личного сообщения | 2 / сек / аккаунт |
POST /friends/request |
20 / час / аккаунт (защита от спама заявками) |
telemetry_update (от клиента к серверу) |
не чаще 1 раз / 2 сек / аккаунт — сервер троттлит и досылает клиентам с той же частотой, не завязан на тик-рейт игры |
Подсистема 3: Аддоны 2.0 + витрина аддонов (дизайн от 2026-09-23)
Статус: спроектировано, реализация не начата. Расширяет существующий Java addon-API (
docs/api/addons.md,addon/core/AddonManager.java) — тот API и дальше можно ломать (решение владельца 2026-09-05, см. верх файла), новый скриптовый слой — надстройка сбоку, не замена.
1. Скриптовые аддоны (новый тип, для витрины)
- JavaScript через GraalVM, отдельный от Java-
AddonManagerзагрузчик рядом сaddon/core - Точка входа —
manifest.json(неfabric.mod.json: скрипт-аддону не нужен Fabric-loader):id, name, version (semver), author, tags, entry.js - Ограниченный whitelisted API — обёртки над тем же, что доступно
Java-аддонам сейчас:
registerModule(JS-класс с полями-настройками),registerCommand,addRenderCallback(только 2D HUD-отрисовка). Никакого прямого доступа к рефлексии/файловой системе/сети — весь I/O только через явно предоставленные хелперы контекста - Java-аддоны (текущие Fabric-моды) остаются для продвинутых случаев (мировой рендер, миксины), устанавливаются вручную; в витрине идут отдельным помеченным разделом от скриптовых
2. Больше точек расширения в существующем Java API
Добавить в LoVisualAddonContext:
- Доступ к событиям (input, world tick, packet)
- Кастомные конфиг-виджеты в ClickGui
- Регистрация собственных рендер-пайплайнов (не только callback)
- API для чтения/записи через облачную конфиг-систему Подсистемы 1
3. Метаданные / версии / автообновления
manifest.jsonполучаетversion(semver) — и у скриптовых, и у Java-аддонов (вcustom.lovisual:addonдобавляется поле)AddonManagerпри старте сверяет установленную версию с версией на сайте (если аддон пришёл оттуда — манифест хранитsource_url/addon_id) → бейдж "обновление доступно" вLoVisualAddonManagerScreen; апдейт — по клику пользователя, никогда не втихую
4. Витрина аддонов на сайте
- Публикация обязательно требует исходники (архив или ссылка на репозиторий) — открытость как единственный механизм доверия. Читерские аддоны не блокируются (решение владельца) — пользователь сам выбирает, что качать
- Теги: обязательный
type: cheat | legit; необязательные категории (мультивыбор, для фильтров, не для модерации):visuals | hud | new-module | utility | qol | other - Данные:
addons_db(отдельная БД в том же Postgres-инстансе, какaccounts_db/configs_db) — таблицыaddons(id, owner_account_id, latest_version, tags[], type, source_url, downloads_count) иaddon_versions(addon_id, version, changelog, file_s3_key, published_at) - Скачивание — прямая ссылка на файл/архив в S3; установка не автоматическая
и не тихая — юзер жмёт "добавить" в клиенте, клиент качает и кладёт в
addons-папку, но не запускает без явного включения в
LoVisualAddonManagerScreen
5. Админка на сайте (/admin)
- Новая роль
accounts.role: admin(по умолчаниюuser); доступ к admin-эндпоинтам проверяется на gateway, не на фронте — обычные пользователи даже не видят/adminв UI - Модерация аддонов: снять с публикации / пометить проблемным (не за cheat/legit — только за реальные нарушения: сигналы о malware от юзеров, нерабочие/поддельные ссылки на исходники, спам) / удалить аддон целиком (со всеми версиями, если, например, DMCA или явный malware)
accounts.can_publish_addons: bool(defaulttrue) — отдельный флаг, отзываемый админом независимо от общего бана аккаунта: юзер продолжает пользоваться сайтом/конфигами/чатом, но не может публиковать новые аддоны (существующие остаются видны, если явно не удалены отдельно)- Модерация витрины конфигов (Подсистема 1): аналогично, скрыть листинг
- Управление аккаунтами: бан/разбан (полный), отзыв
can_publish_addons, просмотр жалоб - Статистика: кол-во аккаунтов/аддонов/конфигов, загрузок за период; график
регистраций по дням/неделям/месяцам; активные аккаунты DAU/MAU (по
device_links.last_seen+ активности на сайте)
6. Рейт-лимиты (сквозной механизм, все Подсистемы)
Реализуются на API Gateway (единая точка — не дублируются в каждом
сервисе), token bucket на Redis (или in-memory при однонодовом деплое на
старте), ключ — account_id для авторизованных запросов, IP для
неавторизованных (регистрация, логин).
| Эндпоинт(ы) | Лимит | Почему |
|---|---|---|
POST /auth/login |
5 / мин / IP | брутфорс пароля |
POST /auth/register |
3 / час / IP | спам-аккаунты |
POST /device/code, /device/token |
10 / мин / IP | спам device-кодов |
PUT /configs/{slot} |
20 / мин / аккаунт | защита БД от спама сохранений (мод может дёргать часто) |
GET /configs/shared/{code} |
30 / мин / IP | перебор share-кодов |
POST /avatars |
5 / час / аккаунт | не файлопомойка |
POST /addons (публикация версии) |
10 / день / аккаунт | не спамить витрину |
GET /showcase, GET /addons (листинги) |
60 / мин / IP | защита от скрейпинга/DoS чтения |
| Chat WebSocket: отправка сообщения | 1 / сек / аккаунт (burst 5) | защита от флуда в RPC-чате |
Превышение → 429 Too Many Requests с Retry-After; для WebSocket —
локальный drop сообщения с уведомлением клиенту, без разрыва соединения.
Задача «Скачивание»: две кнопки + jar с нашего бэкенда (2026-09-29, В РАБОТЕ)
Проблема: кнопка «Скачать» вела на /downloads/lovisual.jar, но на проде такого
файла нет — nginx по try_files отдаёт index.html, то есть вместо мода
скачивалась HTML-страница (проверено 2026-09-29: content-length: 2458).
Решение владельца 2026-09-29:
- Все кнопки «Скачать» (hero лендинга, FaqDownload, футер) ведут на страницу
/download, а не на файл напрямую. - На
/downloadдве кнопки:- «Скачать напрямую» — рабочая, тянет jar с нашего бэкенда
(
/api/downloads/lovisual.jar→ nginx/api/→ gateway → файл на диске); - «Скачать с GitHub» — закрыта лентой «временно не работает» (релизы в GitHub сейчас не публикуются), пока её не уберут вручную.
- «Скачать напрямую» — рабочая, тянет jar с нашего бэкенда
(
- Gateway: новый маршрут
GET /downloads/{file}— отдаёт файл из каталогаDOWNLOADS_DIR(env) сContent-Type: application/java-archive,Content-Length,Cache-Control: public, max-age=.... Только известные имена (allowlist), без обхода каталога. - Артефакт:
./gradlew buildвmod/, jar ≥55 МБ, кладётся на VDS вDOWNLOADS_DIR/lovisual.jar, деплой gateway, проверка curl'ом. - Релизный процесс: пересобрать мод → положить jar на VDS вручную
(мод остаётся отдельным Gradle-проектом, в
deploy.shне встраивается).
Проверка: curl -I https://visual.loki-code.dev/api/downloads/lovisual.jar →
200, content-type: application/java-archive, content-length ≥ 55 МБ;
страница /download показывает две кнопки; bun run test зелёный.
Фаза 11: Серверный плагин — сервер может отключать функции мода (скетч 2026-09-29)
Идея владельца 2026-09-29. Статус: обсуждение, дизайн не написан, кода нет.
Что это. Отдельный серверный плагин (Paper/Spigot или Fabric-сервер), который публикует подключившимся игрокам «политику»: какие возможности LoVisual на этом сервере запрещены. Мод применяет её автоматически — честный игрок не может включить запрещённое, пока находится на этом сервере.
Как связываемся. Plugin message channel (custom payload) lovisual:policy:
сервер шлёт политику при входе и по запросу. Надёжнее HTTP к нашему бэкенду —
работает и без интернета, и без бэкенда.
Формат (черновик):
{ "deny": ["reach", "esp", "freecam"], "mode": "lock",
"reason": "правила сервера" }
mode: lock— модули выключены и не включаются, ClickGui показывает замок с подписью «отключено сервером»;mode: suggest— только предупреждение, игрок сам решает.
Где код.
server-plugin/— новый Gradle-проект в корне репо (серверная Java, не Fabric-клиент, поэтому отдельно отmod/);- в моде —
features/platform/serverpolicy/рядом с существующей платформенной папкой (PlatformHttpClient,BackendEndpointпереиспользуем, если политика однажды будет тянуться с бэкенда); - конфиг политики сервера — YAML в папке плагина.
Честные ограничения (записать заранее): применение политики — на клиенте, читер может её игнорировать. Это не античит: цель — правила сервера и удобство честных игроков, а не защита от нарушителей. Настоящая защита — серверный античит, он отдельно.
Открытые вопросы: целевой серверный софт (Paper vs Fabric-server vs Velocity);
публикация политики через наш бэкенд (/policy/{server}) или только локальным
конфигом плагина; команды %policy в чате; не конфликтует ли это с Подсистемой 2
(там realtime-слой и друзья, здесь достаточно одного payload при входе).