LoVisual/TODO.md
loki5512344 9d08fa910a chore(history): squash 59 commit(s) from 2026-09-23
- refactor(hud/weather/render): Scoreboard 931→240 + Rain 903→159 + UiMeshGeometry 956→99 (8.5.2)
- refactor(chams): Chams 985 → 129 + 4×≤195 (8.5.2)
- refactor(module): Module 1031 → 139 + 4×≤134 (8.5.2)
- refactor(tooltips): BetterTooltips 960 → 181 + 4×≤199 (8.5.2)
- refactor(clickgui): ClickGuiPickerState 970→166 + ModulesMenuScreen 966→145 (8.5.2)
- refactor(clickgui): CombatProtocolHeuristicsEditorState 932→79 + 4×≤192 (8.5.2)
- refactor(hud): CompactHudStatModel 894 → 125 + 4×≤175 (8.5.2)
- refactor(iris): ShaderPatchCompiler 843→66 + 4×≤174 (8.5.2)
- refactor(tab): CustomTabList 834→136 + 4×≤166 (8.5.2)
- refactor(relations): OnlineRelationPlayerPickerComponent 837→74 + 4×≤192 (8.5.2)
- refactor(hits): HitEffect 770→120 + 4×≤176 (8.5.2)
- refactor(predict): Predictions 815→150 + 4×≤195 (8.5.2)
- refactor(config): ConfigProfilesComponent 799→125 + 4×≤195 (8.5.2)
- refactor(sim): PlayerMovementSimulation 757→157 + 4×≤194 (8.5.2)
- refactor(predict): ProjectilePuncher 747→104 + 3×≤134 (8.5.2)
- refactor(crosshair): Crosshair 743→143 + 4×≤167 (8.5.2)
- refactor(mediaplayer): MediaPlayer 743→174 + 4×≤196 (8.5.2)
- refactor(hud): Itemizer 699→193 + Cooldowns 707→151 (8.5.2)
- refactor(hud): Potions 617→176 + 4×≤166 (8.5.2)
- refactor(hud): Admins 702→118 + 4×≤138 (8.5.2)
- refactor(hud): Keybinds 611→157 + 4×≤160 (8.5.2)
- refactor(hud): ModuleList 499→181 + 3×≤150 (8.5.2)
- refactor(hud): Radar 392→161 + 2×≤124 (8.5.2)
- refactor(hud): Fps 309→168 + 3×≤107 (8.5.2)
- refactor(hud): Memory 304→151 + 3×≤144 (8.5.2)
- refactor(hud): Tps 339→154 + 3×≤143 (8.5.2)
- refactor(hud): Ping 309→145 + 3×≤125 (8.5.2)
- refactor(hud): SpeedBps 334→156 + 3×≤142 (8.5.2)
- refactor(hud): GameTime 314→150 + 3×≤127 (8.5.2)
- refactor(hud): SystemTime 300→136 + 3×≤130 (8.5.2)
- refactor(hud): Coordinates 337→174 + 2×≤172 (8.5.2)
- refactor(hud): Armor 324→162 + 2×≤148 (8.5.2)
- refactor(hud): Inventory 476→153 + 3×≤181 (8.5.2)
- docs(todo): обновлён 8.5.2 — отмечены готовые гиганты (BetterButtons/InventorySwap/Svg/Темы/WorldParticles/Module/Chams и хвост)
- docs(api): папка combatant-client-26.2/docs + addons.md (API можно ломать при рефакторе)
- refactor(visuals): BlockHighlight 692→271 + 4×≤200 (8.5.2)
- refactor(mixins): GameRendererMixin 830→344 (хуки) + 4 хелпера (8.5.2)
- refactor(visuals): WorldParticles 821→260 + 6×≤200, режимы добиты (8.5.2)
- docs(todo): 8.5.2 — WorldParticles/GameRendererMixin/BlockHighlight отмечены ГОТОВО
- refactor(render): MeshBuilder 738→447 (фасад write-API) + 3 хелпера, FrameStats снесён (8.5.2)
- docs(todo): Фаза 10 — дизайн платформы (аккаунты/конфиги/аватарки), backlog RPC-чата
- docs(todo): Подсистема 3 — Аддоны 2.0 (скрипты/версии) + витрина аддонов + админка
- docs(todo): Подсистема 2 — RPC-чат/друзья/виджеты-телеметрия + рейт-лимиты, доп. правки Подсистемы 3
- chore: переименование combatant-client-26.2 -> mod, добавлены backend/ и frontend/
- docs(backend): implementation plan for accounts-service (Подсистема 1, часть 1)
- chore: пересоздать backend/frontend через cargo new и bun create vite + tailwind; убрать таблицу компонентов из TODO.md
- docs(backend): edition 2024 в плане вместо 2021
- chore: обновить версии до реально актуальных (проверено компиляцией)
- docs: правила платформы (лимит 250 строк, антипаттерны backend/frontend по итогам ресёрча) + frontend/ARCHITECTURE.md
- docs: добавить правило ≤4 файла на папку в правила платформы (backend/frontend)
- docs: commit messages in English from now on
- chore: gitignore .superpowers/ scratch directory
- docs: commit messages in English from now on (mod)
- feat(backend): scaffold accounts-service with health check
- chore(backend): remove target/ build artifacts from git, add gitignore
- chore: track docs/ in git (was accidentally gitignored, never committed)
- chore(backend): convert to Cargo workspace, plan full microservice layout
- feat(backend): accounts/avatars/device_links schema + migration
- feat(backend): argon2id password hashing
2026-09-23 22:38:08 +02:00

450 lines
38 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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](https://rustify.rs/articles/rust-backend-development-axum-2026), [Rust FAQ — Common Anti-Patterns](https://www.rustfaq.org/en/common-rust-anti-patterns-and-how-to-avoid-them/), [Rust Design Patterns — Anti-patterns](https://rust-unofficial.github.io/patterns/anti_patterns/), [Robin Wieruch — React Folder Structure 2026](https://www.robinwieruch.de/react-folder-structure/).
---
## Статус
- [x] Фаза 1: Ребрендинг `combatant` → `lovisual`
- [x] Фаза 2: Выкинуть Runtime/JarReplacement + Panic
- [x] Фаза 3: Разбивка 12 файлов-гигантов
- [x] Фаза 3.5: Удаление читерских модулей (легит-направление)
- [x] Фаза 4: Security (BackdoorProtection, StaffTracker)
- [x] Фаза 5: Нормализация модулей (ленивый Minecraft, GameClock)
- [x] Фаза 6: Тесты (EventBus, конфиг, утилиты) — см. `mod/TODO.md`
- [x] Фаза 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),
план в `mod/TODO.md`
- [ ] Фаза 9: Оптимизация FPS — 9.1 (мёртвые грузы) ЗАВЕРШЕНА 2026-09-11 (137121e…4d60d3d);
9.2 Optimize гейтится Фазой 8.5, 9.3 A/B-мерка needs запуск на Windows
- [ ] Фаза 10: Платформа (сайт + бэкенд) — Подсистемы 1/2/3 спроектированы 2026-09-23
(см. ниже). Реализация: план backend Подсистемы 1 в `backend/PLAN.md`
(написан 2026-09-23), frontend/Подсистема 2/3 — планы позже.
---
## Фаза 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/`.
- [x] **Hitboxes** (visuals) — цветные хитбоксы сущностей, как у Soup: ТОЛЬКО рендер
контейнера с настройкой цвета/видимости, БЕЗ увеличения хитбокса (увеличение —
чит, удалён в Фазе 3.5). Референс: `visuals/Hitboxes.java`
- [x] **Watermark** (HUD, draggable) — обещан в README, в коде отсутствует
- [x] **Gps** (world) — ЗАВЕРШЕНО 2026-09-15: компас-стрелка + маркер к точке
(`visuals/world/Gps.java`, `util/world/GpsBearing.java` чистая математика + тесты,
команда `%gps <x> <y> <z> | clear`). Референс: `world/Gps.java` — идея взята
(стрелка+маркер+дистанция+авто-удаление), код не копировался. Xaero-связка НЕ
сделана (была опциональной идеей, не в скоупе запроса). Коммит fb0b288.
- [x] **Light** (world) — кастомный цвет освещения (у Soup 41 стр.). Референс: `world/Light.java`
- [x] **HitBubbles** (particles) — ЗАВЕРШЕНО 2026-09-15: отдельный модуль
`visuals/effects/HitBubbles.java` (не режим HitEffect.BUBBLES — независимое включение,
трёхфазная анимация появление/жизнь/исчезновение через новый `ThreeStageProgress`).
Заодно вынесены общие хелперы из HitEffect (DRY): `AttackHitPoint`,
`OrientedQuadRenderer`, `AnimatedColorModeOptions`. Референс: `particles/HitBubbles.java`
(идея взята, код не копировался; интерполяции/множественные текстуры Soup не портированы).
Коммит 5f2fc70.
- [x] **AmbientParticles + FireFlyParticle** (particles) — ЗАВЕРШЕНО 2026-09-15: модуль
`visuals/particles/AmbientParticles.java`, два режима — Ambient (плывущие мошки) и
Firefly (блуждание + кольцевой буфер шлейфа вместо Trail-объектов референса). 19
визуальных стилей референса (Stars/Hearts/Bloom/.../Pyramid, asset-тяжёлые) НЕ
портированы — фича-крип чит-клиента вне направления проекта; цвет через уже
существующий AnimatedRenderColors вместо своей Sync/Custom-системы. Коммит 28ec166.
- [x] **NameProtect** (misc) — подмена своего ника и ников друзей в тексте чата/скорборда
(у Soup 42 стр.). Референс: `other/NameProtect.java`
- [x] **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.
- [x] **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.
- [x] **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),
предпросмотр до эквипа.
- [x] **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.
- [x] **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).
- [x] **Фикс ChinaHat** — текущая геометрия кривая (смахивает на самбреро/зонт);
переделать модель в нормальную конусную шляпу по референсу Soup
(`visuals/ChinaHat.java` + `ChinaHatModel.java`).
---
## Фаза 10: Платформа — сайт + бэкенд (дизайн от 2026-09-23)
> Прим.: лаунчер и старый backend (Rust+axum) были удалены ранее (см. шапку файла) —
> это НЕ восстановление старого кода, а новый дизайн с нуля под текущие требования.
> Статус: дизайн готов, реализация backend начинается — план в `backend/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_at
- `avatars`: account_id, s3_key, uploaded_at
- `device_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_at
- `share_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),
пароль в мод никогда не вводится.
1. Мод: `POST /device/code` → `{device_code, user_code, expires_in}`
(`user_code` короткий, напр. `ABCD-1234`)
2. Мод показывает `user_code` в настройках, игрок вводит его на
`site.<domain>/link` (уже залогинен на сайте) → `POST /device/confirm`
3. Мод параллельно поллит `POST /device/token` {device_code} каждые 2-3с
4. После подтверждения — долгоживущий `device_token` (хранится у мода
локально), используется как Bearer-заголовок ко всем запросам gateway
5. Личный кабинет → список привязанных устройств с `last_seen` и кнопкой
revoke (единичный отзыв, не ломает остальные устройства)
### API конфигов и аватарок
- `GET /configs` → 4 слота аккаунта (name, updated_at, share_code)
- `PUT /configs/{slot}` {name, data} → сохранить, last-write-wins
- `GET /configs/{slot}` → своё содержимое
- `POST /configs/{slot}/regenerate-code`
- `GET /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` — argon2id
- `device_token` хранится хэшированным в БД, как пароль
- Share-код — криптографически случайный, не инкрементный ID (не перебираем)
## Подсистема 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` (default `true`) — отдельный флаг,
отзываемый админом независимо от общего бана аккаунта: юзер продолжает
пользоваться сайтом/конфигами/чатом, но не может публиковать новые аддоны
(существующие остаются видны, если явно не удалены отдельно)
- Модерация витрины конфигов (Подсистема 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 сообщения с уведомлением клиенту, без разрыва соединения.