diff --git a/README.md b/README.md index 508ee21..879eb18 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ | Мета-API: JWT, коллекция, колоды, магазин, история, реплеи | готово | | CI (Forgejo Actions) | workflow добавлен, runner нужно привязать | | EF-миграции | готово (`InitialCreate`) | -| Orleans | не начато | +| Orleans | готово: silo + `MatchActorGrain`, включается `Orleans:Enabled` | | Клиент и лаунчер | не начато | ## Требования diff --git a/docs/HANDOFF.md b/docs/HANDOFF.md index 0cd9a15..1fe5015 100644 --- a/docs/HANDOFF.md +++ b/docs/HANDOFF.md @@ -12,7 +12,7 @@ MVP: бустер **BT01** + триал-деки **TD01/TD02** — 94 карты Стек: -- сервер — **.NET 10**, планируются **Orleans**, PostgreSQL, Redis; +- сервер — **.NET 10**, **Orleans** (опционально), PostgreSQL, Redis; - клиент — **Unity 6** (ещё не начат); - лаунчер — **WPF** (ещё не начат); - контент — YAML + C#-скрипты карт; @@ -38,7 +38,7 @@ MVP: бустер **BT01** + триал-деки **TD01/TD02** — 94 карты | Проверка | Результат | |---|---| -| Тесты | **173/173** (131 правила/контракты + 42 сервер) | +| Тесты | **174/174** (131 правила/контракты + 43 сервер) | | `validate` | 0 ошибок, 0 предупреждений | | `translations check` | 94/94 | | `stress 1000` | 1000/1000 партий, 0 ошибок | @@ -71,7 +71,7 @@ dotnet run --project server\src\Vanguard.Cli -- stress 1000 ``` Пройден! : не пройдено 0, пройдено 131 (Vanguard.Rules.Tests) -Пройден! : не пройдено 0, пройдено 42 (Vanguard.Server.Tests) +Пройден! : не пройдено 0, пройдено 43 (Vanguard.Server.Tests) OK: ошибок 0, предупреждений 0 Переведено: 94/94 Партий: 1000, завершено: 1000, ошибок: 0 @@ -231,7 +231,9 @@ Solution — **`server/Vanguard.slnx`**, не `.sln`. инстансами), иначе — из памяти. - **`IMatchGrain`**: 1 матч = 1 экземпляр, все входы (интенты, подключения, тики) проходят через один семафор; внутри нет ни ASP.NET Core, ни WebSocket — - только `IMatchConnection`. Orleans-хостинг подключается поверх контракта. + только `IMatchConnection`. Orleans-хост (`MatchActorGrain`) подключается + поверх того же контракта; соединения живут в `IMatchConnectionRegistry`, + чтобы состояние грана оставалось сериализуемым. - **WebSocket** `/game`: handshake, MessagePack, `intentId`, idempotency, `eventSeq`, `resync`, heartbeat (20 с простоя → ping, ещё 20 с без ответа → `4408`), реконнект, захват сессии (`4409`), rate limit 16 интентов/с. @@ -254,6 +256,12 @@ Solution — **`server/Vanguard.slnx`**, не `.sln`. - **Конфигурация**: `Storage:Provider` = `None` / `Memory` / `Postgres`, `Storage:ConnectionString`; `Redis:Configuration`. `Memory` — EF InMemory для тестов, `Postgres` — постоянное хранилище. +- **Orleans**: `Orleans:Enabled` (`false` по умолчанию, `true` в compose). + Включённый режим поднимает silo с localhost-clustering в том же процессе; + `MatchActorGrain` держит доменный `MatchGrain`, `IMatchConnectionRegistry` + остаётся в процессе. Граны `[KeepAlive]`: WebSocket нельзя потерять при + выгрузке. Один silo на инстанс; для нескольких реплик нужен настоящий + кластер и внешний реестр соединений. Тест кластера — `OrleansMatchTests`. - **Docker**: `Dockerfile` + `docker-compose.yml` поднимают postgres, redis (AOF) и сервер; `/health` на `http://localhost:8080` (порт меняется через `VANGUARD_PORT`, адрес WebSocket — `VANGUARD_WS_URL`). @@ -261,7 +269,7 @@ Solution — **`server/Vanguard.slnx`**, не `.sln`. `translations check` на push/PR. Workflow добавлен, но home-runner зарегистрирован на другой репозиторий: для этого репозитория нужно включить Actions и привязать runner. -- **Тесты серверного ядра** (33 из 42 в `Vanguard.Server.Tests`): +- **Тесты серверного ядра** (34 из 43 в `Vanguard.Server.Tests`): - тикеты: одноразовость и TTL; - heartbeat: ping после 20 с простоя, закрытие через 20 с, сброс любым кадром; - grain: attach, чужое место, duplicate intent, чужой ход, сдача, @@ -276,9 +284,11 @@ Solution — **`server/Vanguard.slnx`**, не `.sln`. `VANGUARD_TEST_POSTGRES` / `VANGUARD_TEST_REDIS`): реальные PostgreSQL и Redis, изъятие тикета, очередь; - E2E: два клиента играют полный матч по WebSocket и сходятся в победителе; - обрыв и возврат по новому тикету с догоном состояния. + обрыв и возврат по новому тикету с догоном состояния; + - Orleans: грана инициализируется, принимает attach и интент, граница + Orleans сериализуется (тестовый `TestCluster`). -Проверено на `ssh home`: 173/173 тестов, включая реальные PostgreSQL и Redis; +Проверено на `ssh home`: 131 + 43 теста, включая реальные PostgreSQL и Redis; `docker compose up` поднимает стенд, матч создаётся через HTTP-очередь, а после `docker compose restart server` восстанавливается (`Recovery: 1 of 1`). @@ -322,7 +332,7 @@ Solution — **`server/Vanguard.slnx`**, не `.sln`. 11 стартовых событий и кадр, рука противника пуста. После рестарта сервера матч восстановлен (`matches: 1`). -Счётчик `Vanguard.Server.Tests` вырос до 42: добавлены auth, мета-профиль, +Счётчик `Vanguard.Server.Tests` вырос до 43: добавлены auth, мета-профиль, мета-E2E (онбординг → бустер → колода → матч → история → реплей), проверки реплея, явного выхода и истории/реплея после выхода. @@ -364,7 +374,7 @@ Solution — **`server/Vanguard.slnx`**, не `.sln`. /Profiles/ интерфейс MMR /Dockerfile /server/tests/Vanguard.Rules.Tests 131 тест -/server/tests/Vanguard.Server.Tests 42 теста, включая E2E и внешние хранилища +/server/tests/Vanguard.Server.Tests 43 теста, включая E2E и внешние хранилища /content/sets карты /content/trial-decks.yaml две триал-деки /content/pack_odds.yaml веса бустера @@ -402,7 +412,7 @@ Solution — **`server/Vanguard.slnx`**, не `.sln`. в таком виде нельзя. - Состояние сервера: PostgreSQL — снапшот/журнал матчей, Redis — тикеты и очередь; без конфигурации работает in-memory. `IMatchGrain` отделён - от транспорта, Orleans подключается поверх. + от транспорта, Orleans подключается поверх и включается конфигом. - Rate limit — 16 интентов в секунду на соединение. - Скрытый MMR: старт 1000, Эло K = 32, casual рейтинг не меняет. - Аккаунты: BCrypt, access JWT 15 минут, refresh 30 дней с ротацией; @@ -445,7 +455,7 @@ Solution — **`server/Vanguard.slnx`**, не `.sln`. своя рука и свой выбор полностью, у противника только количества и открытые зоны, seed и RNG не уходят. 5. **[x] `Docker Compose`** — postgres, redis (AOF) и сервер; `/health`, - порт через `VANGUARD_PORT`. + порт через `VANGUARD_PORT`. Сервер запускается с `Orleans:Enabled=true`. 6. **[x] Персистентность**: - PostgreSQL — заголовок матча + журнал интентов, восстановление повтором команд после рестарта, `MatchRecoveryService`; @@ -456,14 +466,17 @@ Solution — **`server/Vanguard.slnx`**, не `.sln`. 7. **Мета-API** — сделано: JWT, аккаунты, коллекция, колоды, магазин, экономика, онбординг, матчмейкинг по `deckId`, история и реплеи. `playerId` остался только как dev-режим при `Game:AllowDevQueue=true`. -8. **Orleans** — заменить in-process регистр на настоящий silo и граны. - `IMatchGrain` уже отделён от транспорта и ASP.NET Core; персистентность - и проекция от хостинга не зависят. +8. **[x] Orleans** — `MatchActorGrain` + `OrleansMatchAdapter`; silo + включается `Orleans:Enabled=true`, в compose включён. `MatchGrain` остался + доменным ядром, соединения вынесены в `IMatchConnectionRegistry`, + `MatchActorGrain` восстанавливается из снапшота на активации. Покрыто + `OrleansMatchTests` (тестовый кластер). Критерий готовности серверного блока выполнен: два клиента играют полный матч по сети; kill клиента восстанавливается через новый тикет и `resumeFromSeq`; рестарт сервера восстанавливает матчи из PostgreSQL. -Остались Orleans и привязка CI-runner. +Остался только CI-runner: workflow есть, но home-runner привязан к другому +репозиторию. ### 7.2. Мета-API @@ -520,7 +533,7 @@ Solution — **`server/Vanguard.slnx`**, не `.sln`. `GameConnection`, логин, коллекция, декбилдер, магазин, очередь, история и реплей. Сервер и мета-API готовы: `docs/protocol.md`, `docs/server-api.md`, `Vanguard.Shared`. -4. Orleans и CI-runner — отдельные серверные долги, не блокируют клиент. +4. CI-runner — отдельный серверный долг, не блокирует клиент; Orleans уже готов. 5. После каждого блока: тесты, `validate`, `translations check`, коммит, push. 6. Не ломать `Vanguard.Rules`: сервер должен зависеть от него, а не наоборот. diff --git a/docs/server-api.md b/docs/server-api.md index f250520..21b334c 100644 --- a/docs/server-api.md +++ b/docs/server-api.md @@ -424,7 +424,8 @@ Redis: тикеты (`GETDEL`, TTL 60 с) и очередь (JSON + sorted set). История матча пишется, но реплей при `None` недоступен: строки матча нет. Для истории и реплеев нужен `Memory` или `Postgres`. - `EnsureCreated` используется только для InMemory; PostgreSQL — миграции. -- Один процесс: Orleans-хостинг ещё впереди; `IMatchGrain` уже отделён. +- Один процесс: Orleans включается `Orleans:Enabled`; по умолчанию матчи + идут через in-process `MatchGrain`, в compose — через silo. - Нет магазина косметики, квестов, баттлпасса, Draft, друзей и чата. - Rate limit: 60/мин на auth, 300/мин на остальные мутирующие запросы. - Dev-очередь включается только `Game:AllowDevQueue=true` и не для production.