Кросс-клиентная синхронизация
Проблема
Один человек может открыть веб и Telegram одновременно. Несколько людей играют с разных каналов. Состояние должно быть единым и согласованным.
Модель Participant
- Один Participant на человека в сессии
- Два presence-канала:
presence_web, presence_telegram
- Один голос в игре — не два хода
Control channel
| Правило |
Описание |
| Default |
Последний канал, с которого пришло действие, становится control_channel |
| Mirror |
Второй канал показывает state read-only до следующего действия с него |
| Active turn |
Только current_player may submit actions |
Поток события
sequenceDiagram
participant TG as Telegram
participant Bot as telegram-bot
participant Game as game-runtime
participant Redis as Redis PubSub
participant RT as realtime-service
participant Web as web-app
TG->>Bot: callback choose_cell
Bot->>Game: POST /games/{id}/actions
Game->>Game: validate, apply, audit
Game->>Redis: publish game.state_changed
Redis->>RT: event
RT->>Web: WebSocket push
Redis->>Bot: event
Bot->>Bot: fetch media URL, edit_message
Идемпотентность
POST /api/v1/games/{game_id}/actions
{
"action_id": "550e8400-e29b-41d4-a716-446655440000",
"action_type": "choose_cell",
"payload": { "row": 3, "col": 5 }
}
- Повтор с тем же
action_id → 200 с тем же результатом, без дубля
- Конфликт фазы →
409 CONFLICT
Optimistic UI (веб)
- Клиент подсвечивает клетку локально
- Отправляет action
- При ошибке — rollback из последнего WS state
- При успехе — подтверждение от WS (authoritative)
Telegram рендер
| Шаг |
Действие |
| 1 |
game.state_changed → bot handler |
| 2 |
GET /media/render?game_id=... → PNG URL |
| 3 |
edit_message_media с фото + inline keyboard |
| Fallback |
Текстовая сетка ASCII + кнопки если media fail |
Reconnect (веб)
- WS disconnect → exponential backoff reconnect
- On connect:
GET /games/{id}/state + subscribe WS topic
- Merge с локальным буфером pending actions
Latency budget
| Этап |
Target |
| API apply |
< 300 ms p95 |
| Pub/Sub → WS |
< 200 ms |
| Bot edit_message |
< 500 ms |
| End-to-end |
< 1 s p95 |
Связанные FR/NFR
FR-003, FR-025–FR-029, NFR-002