Модель сессий и игр
Ключевая идея: Session ≠ Game
Session (долгоживущая)
├── invite_code: "ABCD12"
├── participants: [P1, P2, P3]
├── status: Lobby | GameSelection | InGame | Closed
└── game_instances: [
{ id: g1, plugin: dice_board, status: finished },
{ id: g2, plugin: dice_board, status: active } ← только один active
]
Session lifecycle
stateDiagram-v2
[*] --> Lobby: createSession
Lobby --> GameSelection: hostOpensCatalog
GameSelection --> InGame: startGame
InGame --> GameSelection: gameEnds
GameSelection --> InGame: switchGame
InGame --> Paused: pause
Paused --> InGame: resume
Lobby --> Closed: close
GameSelection --> Closed: close
InGame --> Closed: close
| Статус |
Описание |
Lobby |
Участники присоединяются, host настраивает |
GameSelection |
Выбор игры, ожидание старта |
InGame |
Активная GameInstance |
Paused |
Пауза (опционально Phase 2+) |
Closed |
Сессия архивирована |
GameInstance
| Поле |
Тип |
Описание |
id |
UUID |
|
session_id |
UUID |
FK |
plugin_id |
string |
e.g. dice_board |
status |
enum |
active, finished, aborted |
state |
JSONB |
GameState от плагина |
current_player_id |
UUID |
Participant |
winner_id |
UUID? |
После finished |
started_at |
timestamp |
|
finished_at |
timestamp? |
|
Participant
| Поле |
Описание |
id |
UUID |
session_id |
FK |
user_id |
nullable — для registered |
guest_id |
nullable — для guest |
telegram_id |
nullable |
role |
host, player, spectator |
display_name |
string |
presence_web |
bool + last_seen_at |
presence_telegram |
bool + last_seen_at |
control_channel |
web | telegram | null |
Смена игры (switch)
- Host вызывает
POST /sessions/{id}/games с новым plugin_id
- Предыдущая GameInstance →
finished или aborted
- Новая GameInstance →
active, initial_state() плагина
- Session остаётся
InGame; invite_code не меняется
- Событие
game.started → все клиенты
Инварианты
- INV-001: не более одного
active GameInstance на сессию
- Участники сохраняются между играми
- История
game_instances append-only
Связанные FR
FR-004, FR-005, FR-033, FR-034