Files
x0gp/ARCHITECTURE.md
T
x0gp 978d36c505 feat(server): initial implementation
This commit captures the full server code accumulated across
several development sessions. It includes the following layers,
applied in roughly this order during development:

1. Base infrastructure
   - cmd/poc-server HTTP+WebSocket server, /health, /stats
   - internal/transport wire types (Envelope + all message types)
   - internal/catalog, internal/realtime, internal/stats
   - internal/storage/postgres with migrations 001-004
   - .env, .env.example, docker-compose, Dockerfile, scripts/

2. Swagger documentation
   - go get github.com/swaggo/http-swagger
   - swag init produces docs/docs.go
   - /swagger/index.html UI, /swagger/doc.json spec
   - annotations on health/stats/ws and catalog handlers

3. Races API
   - internal/races/{store,service,keyset,types}.go
   - migration 005_races.sql: finished_races, race_plans, race_queue
   - GET /api/races with keyset pagination, status filter
   - GET /api/races/upcoming
   - POST /api/races/queue/join, GET, DELETE
   - POST/GET /api/races/plans, DELETE /{id}
   - lobby.RaceMeta simplification (no host_id/host_name)

4. Races seeder
   - internal/races/seed with deterministic generator
   - --seed-races / --reset CLI flags in main.go
   - 30 finished + 5 live + 5 plans + 4 queue entries

5. Drivers and clans
   - internal/drivers, internal/clans (CRUD, validation)
   - migration 008_drivers_clans.sql
   - /api/clans and /api/drivers endpoints
   - 3-letter uppercase nickname/tag validation
   - lobby.DriverMeta: Nickname, AvatarURL, ClanID, ClanTag

6. Podium
   - internal/transport.RacePodiumEntry
   - lobby.SetDriverProfile for in-memory metadata sync
   - migration 007_podium.sql (podium JSONB column)
   - seeder populates top-3 per finished race

7. Live races persistence
   - migration 009_live_persistence.sql: live_races,
     live_race_drivers, lobby_drivers
   - internal/races/live_store.go: LiveStore with write-side
     mirror for lobby.Service mutations
   - Service.collectLive and Upcoming read from Postgres
   - main.go RestoreFromDB rehydrates lobby on startup

8. Unify live + finished into one races table
   - migration 010_unify_races.sql: rename finished_races to
     races, expand status CHECK, merge live rows
   - PgStore now hosts both write paths; LiveStore is a
     thin facade implementing lobby.Persistence
   - seeder resetAll drops only finished/cancelled rows and
     race_plans / race_queue / lobby_drivers

Each layer is consistent on its own; cross-layer changes are
visible in the file history. A future refactor may split this
commit into the per-stage boundaries listed above.
2026-06-22 22:01:09 +04:00

224 lines
11 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.
# x0gp — Architecture Overview
Корень проекта. Содержит общую картину системы, glossary и связи между
подсистемами.
Дата фиксации архитектурных решений: 2026-06-18.
Дата обновления (Bambu A1, своё шасси, домашний сервер): 2026-06-18.
Дата обновления v2 (Orange Pi 5 Pro + Coolify, Compact 1/27): 2026-06-18.
---
## 1. Что такое x0gp
x0gp — это платформа для проведения **гонок на реальных радиоуправляемых
машинках** через интернет. Игроки подключаются через Web (позже — мобильное
приложение), платят за заезд/турнир и управляют физической машинкой в
реальном времени.
Целевой пользователь: гонщик-любитель 16-45 лет, желающий участвовать в
реальных гонках без необходимости ехать в хобби-клуб.
---
## 2. Целевые метрики
| Метрика | Цель |
|---|---|
| Round-trip команды (экран -> колёса) p50 | < 100 мс |
| Round-trip команды p99 | < 200 мс |
| Tick rate гонки | 60 Hz |
| Одновременных активных заездов на Edge-инстанс | 50-100 |
| Доступность | 99.5% (PoC) -> 99.9% (prod) |
| Конверсия "регистрация -> первый заезд" | > 30% |
| Стоимость инфраструктуры на 1 заезд/час | < 5 руб. (цель) |
---
## 3. Компоненты системы
### 3.1. Слой клиента
- **Web client** — React, Vite, TypeScript. WebSocket-клиент с
client-side prediction. Canvas/WebGL-рендер трассы и машинок.
- **Mobile client** (фаза 5) — React Native, общий с Web протокол.
- **Admin panel** — React + TanStack Table, управление трассами, турнирами,
пользователями.
### 3.2. Edge-сервер (Go)
- **api-gateway** — stateless, REST + WS-терминация, auth, rate-limit.
- **race-engine** — stateful, authoritative tick 60 Hz, fan-out.
- **agent-linker** — long-lived WS к бортовым агентам в помещении.
- **indexer** — пайплайн телеметрии в TimescaleDB.
- **replay-recorder** — пишет replay-чанки в S3.
### 3.3. Локальная инфраструктура в помещении
- **Gateway-агент (мини-ПК)** — Go-бинарь, держит связь с Edge-сервером
и машинками, выполняет CV-инференс, буферизует команды на случай обрыва WAN.
- **Wi-Fi роутер** — выделенный SSID "x0gp-track", WPA3.
- **Overhead-камеры** — 1-2 шт, 1080p 60 fps global shutter.
- **IR-магнитные петли** — на секторах трассы.
### 3.4. Борт машинки
- **ESP32-S3 агент** — Wi-Fi, WebSocket к gateway, генерация команд ESC/серво.
- **Brushless ESC + мотор** — 1/18 scale.
- **Mini digital servo** — рулевое.
- **IMU, IR/магнитные сенсоры** — телеметрия.
### 3.5. Хранение
- **PostgreSQL** — пользователи, платежи, гонки, турниры.
- **Redis** — сессии, pub/sub, лобби, rate-limit.
- **TimescaleDB** — телеметрия, replay-индекс.
- **S3-совместимое** — replay-чанки (видео, JSON-логи).
### 3.6. Observability
- **Loki** — логи.
- **Prometheus + Grafana** — метрики.
- **Tempo / Jaeger** — трейсы.
- **Sentry** — ошибки на клиенте и сервере.
---
## 4. Поток данных (упрощённый)
```
1. Игрок нажимает газ в браузере
|
2. Web-client отправляет InputState{seq, tsMs, throttle: 1.0} по WS
|
3. api-gateway принимает, валидирует, кладёт в Redis Stream "race:{id}:inputs"
|
4. race-engine на ближайшем тике читает stream, применяет к state
|
5. race-engine публикует RaceSnapshot в Redis pub/sub
|
6. race-engine шлёт AgentCommand в agent-linker (WebSocket в помещение)
|
7. agent-linker шлёт AgentCommand по WS на ESP32-S3 машинки
|
8. ESP32-S3 выдаёт PWM на ESC и серво
|
9. ESP32-S3 шлёт AgentTelemetry обратно (10-30 Hz)
|
10. gateway-агент делает CV-инференс и шлёт CarFrame на сервер
|
11. race-engine обновляет authoritative state, шлёт следующий Snapshot
|
12. Web-client получает Snapshot, корректирует prediction, рисует кадр
```
Типичный round-trip: 50-120 мс (зависит от расстояния до Edge-сервера).
---
## 5. Модель доверия
**Authoritative server**: истиной владеет сервер. Клиент может рендерить
раньше (через client-side prediction), но любое решение о кругах, победах,
позициях принимается сервером на основе данных от agent-linker (телеметрия
ESP32 + CV).
**Anti-cheat контур**: вход -> sanity check -> race-engine (state) ->
agent-linker (CV) -> indexer (telemetry) -> admin (manual review).
---
## 6. Структура репозитория
```
x0gp/
ARCHITECTURE.md # этот файл
README.md # quickstart, how to run
server/ # Go-код (см. server/PLAN.md)
PLAN.md
cmd/
internal/
proto/
migrations/
deploy/
hardware/ # CAD, спецификации, BOM (см. hardware/PLAN.md)
PLAN.md
cad/
bom/
photos/
web/ # React-клиент (отдельный пакет)
mobile/ # React Native (фаза 5)
ops/ # общие devops-скрипты, конфиги
docs/ # ADR, RFC, диаграммы
adr/
diagrams/
```
---
## 7. Glossary
| Термин | Значение |
|---|---|
| **Race** | Одна гонка: 2-12 игроков на трассе, общий старт, финиш по N кругов или по времени |
| **Lobby** | Пре-гонка: игроки подключаются, выбирают машину, ждут старта |
| **Tick** | 1 шаг симуляции (16.67 мс, 60 Hz) |
| **Snapshot** | Полное состояние гонки на момент тика (позиции, скорости, круги) |
| **Prediction** | Локальная симуляция у клиента между Snapshot'ами |
| **Correction** | Сообщение сервера о расхождении клиентского prediction |
| **Agent** | Софт на борту машинки (ESP32-S3) или в помещении (мини-ПК) |
| **Edge** | Сервер в регионе, ближайшем к игрокам и помещению с трассой |
| **Gateway-агент** | Локальный мини-ПК, "мост" между сервером и машинками в помещении |
| **Track** | Физическая трасса + логическая (layout_id) |
| **Class** | Класс машинки (1/18 Formula, 1/20 Mini и т.д.) |
| **Sector** | Часть круга (1/3, 2/3) — IR-магнитная метка |
| **DNF** | Did Not Finish — игрок не закончил гонку |
| **DQF** | Disqualified — дисквалификация за нарушение |
| **Replay** | Запись гонки для просмотра и разбора |
---
## 8. Ключевые ADR (Architecture Decision Records)
Подробности — в `docs/adr/`. Здесь — сводка:
- **ADR-001**: WebSocket + protobuf как основной транспорт.
- **ADR-002**: Authoritative server + client-side prediction.
- **ADR-003**: ESP32-S3 как бортовая MCU, MicroPython в PoC -> ESP-IDF в проде.
- **ADR-004**: Гибрид IR-магнитные секторы + overhead-CV для позиционирования.
- **ADR-005**: Масштаб машинки 1/18 (длина шасси 18-22 см), ширина полосы 30-40 см.
- **ADR-006**: Edge + Gateway-агент: сервер не знает о Wi-Fi трассе, агент буферизует обрыв WAN.
- **ADR-007**: Биллинг через ЮKassa (РФ) + Stripe (международный).
- **ADR-008**: 3D-принтер **Bambu A1** как основной для производства шасси,
барьеров, маунтов, кузовов. Альтернатива — Bambu P1S при необходимости
enclosure для инженерных пластиков.
- **ADR-009**: Шасси машинки **полностью своё**, спроектированное и
напечатанное на 3D-принтере. Не используем покупные донорские деки.
- **ADR-010**: Edge-сервер в PoC и MVP работает на **домашнем сервере** с
доступом из интернета через Cloudflare Tunnel (default) или WireGuard.
Перенос на облако/VPS — только при появлении коммерческой нагрузки и
SLA обязательств.
- **ADR-011**: Домашний сервер — **Orange Pi 5 Pro** (RK3588S, ARM64, 8 cores,
6 TOPS NPU, 2×2.5 GbE) с развёрнутым **Coolify** (self-hosted PaaS) для
деплоя и управления сервисами. NPU Orange Pi используется для CV-инференса
(YOLO/ByteTrack через RKNN Toolkit 2). LAN-порт Orange Pi изолирует сеть
трассы от домашней сети. Доступ из интернета — Caddy + Let's Encrypt
напрямую (статический IP от провайдера).
- **ADR-012**: Масштаб машинки — **Compact 1/27** как primary класс
(длина шасси 13-15 см, wheelbase 100-115 мм, вес 150-220 г). Ширина полосы
трассы 20-25 см, рабочая зона трассы 200×130 см (помещается в комнату
446×319 см с запасом). Альтернативный класс — Standard 1/24 (16-18 см)
для тех, кому нужна чуть большая стабильность. Цель — минимизация площади
трассы и стоимости при сохранении управляемости 3D-печати.
---
## 9. Что дальше
После фиксации плана — переход к **PoC**:
1. Минимальный набор: 1 машинка, 1 трасса 100x60 см, 1 gateway-агент, 1 сервер.
2. Цель: end-to-end "команда -> колёса крутятся, телеметрия возвращается".
3. Срок: 1-2 недели.
Детали — в `server/PLAN.md` (фаза S0) и `hardware/PLAN.md` (фаза H0).