feat: implement safe leave, profile reset, and full game reset semantics

This commit is contained in:
2026-08-30 23:18:07 +03:00
parent c0c3c73c7a
commit a68023e684
16 changed files with 281 additions and 9 deletions
+13 -1
View File
@@ -38,7 +38,7 @@ Failure (maximum 160 encoded bytes):
```
`code` is one of `MALFORMED_JSON`, `PAYLOAD_TOO_LARGE`, `INVALID_NAME`,
`INVALID_ROLE`, `INVALID_MODE`, `INVALID_COORDINATE`, `UNAUTHORIZED`,
`INVALID_ROLE`, `INVALID_MODE`, `INVALID_COORDINATE`, `UNAUTHORIZED`, `SESSION_INVALIDATED`,
`NO_PLAYER_SLOT`, `NO_SPECTATOR_SLOT`, `FORBIDDEN_ROLE`, `WRONG_PHASE`,
`NOT_YOUR_TURN`, `CELL_ALREADY_SHOT`, `STALE_GAME`, or `SERVER_BUSY`.
`message` is Russian and at most 80 UTF-8 bytes.
@@ -51,11 +51,14 @@ Failure (maximum 160 encoded bytes):
| `GET /api/health` | 128 B target | diagnostics without secrets, 320 B; includes reset reason |
| `POST /api/session/join` | 192 B body | `{name,requestedRole}`; token and role, 192 B |
| `POST /api/session/resume` | 96 B body | `{token}`; role and state metadata, 192 B |
| `POST /api/session/leave` | 80 B body | `{token,gameId}`; releases only that session |
| `POST /api/session/profile-reset` | 80 B body | `{token,gameId}`; server release for local profile reset |
| `POST /api/game/config` | 96 B body | `{token,gameId,mode}`; common envelope |
| `POST /api/game/start` | 80 B body | `{token,gameId}`; common envelope |
| `POST /api/game/shot` | 96 B body | `{token,gameId,x,y}`; common envelope |
| `POST /api/game/rematch` | 80 B body | `{token,gameId}`; common envelope |
| `POST /api/game/abort` | 80 B body | `{token,gameId}`; common envelope |
| `POST /api/game/reset` | 80 B body | `{token,gameId}`; player-only full RAM reset |
| `GET /api/state?version=N` | 128 B target | one role-safe state, 768 B |
| `GET /api/statistics` | 128 B target | role and bounded match/cumulative counters |
@@ -63,6 +66,15 @@ The session token is in each POST body. For `GET /api/state`, it is supplied
in `X-Session-Token`; absence creates a spectator-safe view. It is never a URL
parameter.
`leave` and `profile-reset` release only the requesting session; profile data is
cleared by the browser in Milestone 022. A player leaving an active match aborts
that match and returns remaining valid players to the lobby. `game/reset` is
available only to an authenticated player and atomically invalidates every
session, clears match and cumulative statistics, and starts a fresh game
generation. Successful recovery responses contain `resetReason` (`session_left`,
`profile_reset`, or `game_reset`) and `generation`. Invalidated-token state
polling returns `SESSION_INVALIDATED` with the same bounded recovery metadata.
## Role-safe state event
The HTTP state response and WebSocket `state` event use this single 768-byte
+3
View File
@@ -274,10 +274,13 @@ struct Board {
| `GET` | `/api/info` | Состояние устройства и доступность мест без скрытых данных |
| `POST` | `/api/session/join` | Вход по имени и желаемой роли |
| `POST` | `/api/session/resume` | Восстановление роли по токену |
| `POST` | `/api/session/leave` | Освобождение только текущей сессии |
| `POST` | `/api/session/profile-reset` | Освобождение сессии перед локальной очисткой профиля |
| `POST` | `/api/game/config` | Выбор режима игроком 1 |
| `POST` | `/api/game/start` | Запуск готовой партии |
| `POST` | `/api/game/shot` | Выстрел по координатам |
| `POST` | `/api/game/rematch` | Подтверждение повторной игры |
| `POST` | `/api/game/reset` | Аварийный полный сброс игровой памяти игроком |
| `GET` | `/api/state?version=N` | Снимок разрешённого состояния и резервный опрос |
| `GET` | `/api/health` | Проверка доступности сервера |
+42
View File
@@ -73,6 +73,24 @@ paths:
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
'413': { $ref: '#/components/responses/PayloadTooLarge' }
/api/session/leave:
post:
summary: Release only the authenticated session
requestBody:
required: true
content: { application/json: { schema: { $ref: '#/components/schemas/GameRequest' } } }
responses: &recoveryResponses
'200': { $ref: '#/components/responses/RecoveryAccepted' }
'400': { $ref: '#/components/responses/BadRequest' }
'409': { $ref: '#/components/responses/Conflict' }
'413': { $ref: '#/components/responses/PayloadTooLarge' }
/api/session/profile-reset:
post:
summary: Release the authenticated session for a browser-local profile reset
requestBody:
required: true
content: { application/json: { schema: { $ref: '#/components/schemas/GameRequest' } } }
responses: *recoveryResponses
/api/game/config:
post:
summary: Set the game mode (Player 1, lobby only)
@@ -131,6 +149,18 @@ paths:
schema:
$ref: '#/components/schemas/GameRequest'
responses: *commandResponses
/api/game/reset:
post:
summary: Player-only full in-memory game reset
requestBody:
required: true
content: { application/json: { schema: { $ref: '#/components/schemas/GameRequest' } } }
responses:
'200': { $ref: '#/components/responses/RecoveryAccepted' }
'400': { $ref: '#/components/responses/BadRequest' }
'403': { $ref: '#/components/responses/Forbidden' }
'409': { $ref: '#/components/responses/Conflict' }
'413': { $ref: '#/components/responses/PayloadTooLarge' }
/api/state:
get:
summary: Get a complete role-safe state snapshot
@@ -175,6 +205,11 @@ components:
schema: { $ref: '#/components/schemas/Token' }
description: Omit only when a spectator-safe public view is intended.
responses:
RecoveryAccepted:
description: Recovery completed or an idempotent retry observed the same completed recovery.
content:
application/json:
schema: { $ref: '#/components/schemas/RecoveryAccepted' }
CommandAccepted:
description: Command accepted by the authoritative application layer.
content:
@@ -269,6 +304,13 @@ components:
- type: object
required: [role]
properties: { role: { $ref: '#/components/schemas/Role' } }
RecoveryAccepted:
type: object
required: [ok, resetReason, generation]
properties:
ok: { type: boolean, enum: [true] }
resetReason: { type: string, enum: [session_left, profile_reset, game_reset] }
generation: { $ref: '#/components/schemas/GameId' }
Info:
type: object
required: [ok, phase, gameId, version, player1Available, player2Available, player1Name, player2Name, spectatorsAvailable]