feat(server): modifier profile protocol schemas + error codes
Adds wire schemas and error codes for the modifier-profile feature:
- ModifierProfileSchema mirrored in server/protocol.ts (server pins a different zod major, so the chess-side schema cannot be re-exported directly). A keyof parity check guards against drift.
- RoomCreatePayloadSchema gains an optional 'profile' field — additive, existing callers unaffected.
- modifier-profile.update (client->server) payload schema with roomCode, newProfile, and version (for optimistic-concurrency checks).
- modifier-profile.updated (server->client) broadcast payload interface, typed for future promotion to the discriminated union when the broadcast is wired through rooms.
- Four new error codes: MODIFIER_PROFILE_INVALID / NO_KING / INVULN_KING / DEADLOCK, exported as const literals alongside the enum.
- Client wire types (packages/chess/src/net/types.ts) mirror all of the above: ModifierProfileWire shape, optional 'profile' on Room{Create,Created,Joined} payloads, and the new modifier-profile.* client/server message envelopes.
- PROTOCOL.md documents the new request/response flow and extends the error-code table.
Tests: 20 new cases across ModifierProfileSchema, RoomCreate profile integration, ModifierProfileUpdatePayloadSchema, and error-code acceptance.
This commit is contained in:
parent
0e9809007d
commit
4b7d943edc
5 changed files with 603 additions and 2 deletions
|
|
@ -94,6 +94,9 @@ Response (Server → Client, type `room.created`):
|
|||
- `layout`: resolved starting layout echoed back. Present on all
|
||||
`room.created` and `room.joined` responses from new servers; may be
|
||||
absent on legacy (pre-layouts) servers.
|
||||
- `profile`: resolved modifier profile echoed back. Same shape as the
|
||||
request-side `profile` field. Omitted when the room was created
|
||||
without a profile, or when the server is pre-modifiers.
|
||||
|
||||
Error cases:
|
||||
|
||||
|
|
@ -101,6 +104,14 @@ Error cases:
|
|||
- Layout fails validation (bad king count, duplicate squares,
|
||||
unknown premade id, malformed FEN): `error` code `LAYOUT_INVALID`.
|
||||
Message field carries the human-readable reason.
|
||||
- Modifier profile fails validation: `error` code
|
||||
`MODIFIER_PROFILE_INVALID` (schema / descriptor value-schema
|
||||
failure) or one of the three carved-out invariants:
|
||||
`MODIFIER_PROFILE_NO_KING`, `MODIFIER_PROFILE_INVULN_KING`,
|
||||
`MODIFIER_PROFILE_DEADLOCK`. The `message` field carries a
|
||||
human-readable reason; clients may use the code for targeted UI
|
||||
hints (e.g. "Your profile leaves white with no king — please
|
||||
adjust the setup").
|
||||
|
||||
### Message: room.join
|
||||
|
||||
|
|
@ -248,6 +259,95 @@ Payload:
|
|||
}
|
||||
```
|
||||
|
||||
### Message: modifier-profile.update
|
||||
|
||||
Direction: Client → Server
|
||||
Purpose: Replace the room's active modifier profile. Applied by the
|
||||
server at the NEXT TURN BOUNDARY — never mid-move.
|
||||
|
||||
Request payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "modifier-profile.update",
|
||||
"roomCode": "ABC123",
|
||||
"newProfile": {
|
||||
"id": "buff-rooks",
|
||||
"name": "Rook Reach",
|
||||
"description": "All rooks get +1 range.",
|
||||
"layoutId": "classic",
|
||||
"perType": [
|
||||
{ "kind": "range-bonus", "pieceType": "rook", "color": "both", "value": 1 }
|
||||
],
|
||||
"perInstance": [],
|
||||
"version": 1,
|
||||
"source": "custom"
|
||||
},
|
||||
"version": 3
|
||||
}
|
||||
```
|
||||
|
||||
- `roomCode`: the code of the room whose profile is being swapped.
|
||||
- `newProfile`: the full replacement `ModifierProfile` (same shape as
|
||||
the `profile` field on `room.create`). Partial updates are not
|
||||
supported — the server treats the incoming profile as authoritative.
|
||||
- `version`: the PROFILE version the client last observed (starts at
|
||||
`0` before any update and is incremented by the server on each
|
||||
successful apply). Submitting a stale version triggers an `error`
|
||||
with code `MODIFIER_PROFILE_INVALID` so concurrent edits from both
|
||||
players never silently overwrite.
|
||||
|
||||
On successful apply, the server broadcasts `modifier-profile.updated`
|
||||
to both players (see below), followed by the usual `game.state` /
|
||||
`game.delta` reflecting any piece-attribute changes.
|
||||
|
||||
Error cases:
|
||||
|
||||
- Profile fails schema / value-schema validation:
|
||||
`MODIFIER_PROFILE_INVALID`
|
||||
- Profile would leave a side with no king: `MODIFIER_PROFILE_NO_KING`
|
||||
- Profile would make a king invulnerable (damage-resistance ≥ 1.0):
|
||||
`MODIFIER_PROFILE_INVULN_KING`
|
||||
- Profile's combined effects make the current position a legal-move
|
||||
deadlock (neither side has any move): `MODIFIER_PROFILE_DEADLOCK`
|
||||
- Room not found / client not a member: `ROOM_NOT_FOUND`
|
||||
|
||||
### Message: modifier-profile.updated
|
||||
|
||||
Direction: Server → Client
|
||||
Purpose: Broadcast to both players after a successful
|
||||
`modifier-profile.update`. Clients apply the new profile to their
|
||||
local engine at the next turn boundary.
|
||||
|
||||
Payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "modifier-profile.updated",
|
||||
"profile": {
|
||||
"id": "buff-rooks",
|
||||
"name": "Rook Reach",
|
||||
"description": "All rooks get +1 range.",
|
||||
"layoutId": "classic",
|
||||
"perType": [
|
||||
{ "kind": "range-bonus", "pieceType": "rook", "color": "both", "value": 1 }
|
||||
],
|
||||
"perInstance": [],
|
||||
"version": 1,
|
||||
"source": "custom"
|
||||
},
|
||||
"version": 4,
|
||||
"appliedAt": "turn-boundary"
|
||||
}
|
||||
```
|
||||
|
||||
- `profile`: the newly-active modifier profile (same shape as
|
||||
`room.create.profile`).
|
||||
- `version`: the monotonically-incremented profile version. Clients
|
||||
echo this on subsequent `modifier-profile.update` requests.
|
||||
- `appliedAt`: always `"turn-boundary"` in v1. Reserved for future
|
||||
policies (e.g. `"immediate"` for settings that apply mid-move).
|
||||
|
||||
### Message: error
|
||||
|
||||
Direction: Server → Client
|
||||
|
|
@ -321,3 +421,8 @@ Messages counted: all messages from client including heartbeats.
|
|||
| `MSG_TOO_LARGE` | Yes | Message exceeds 64KB |
|
||||
| `BAD_TOKEN` | Yes | Token missing or invalid for room |
|
||||
| `INVALID_MESSAGE` | Yes | JSON parse failure or schema validation failure |
|
||||
| `LAYOUT_INVALID` | No | Starting layout failed validation |
|
||||
| `MODIFIER_PROFILE_INVALID` | No | Modifier profile failed schema / descriptor validation, or stale `version` on an update |
|
||||
| `MODIFIER_PROFILE_NO_KING` | No | Profile would leave a side without a king |
|
||||
| `MODIFIER_PROFILE_INVULN_KING` | No | Profile would make a king invulnerable |
|
||||
| `MODIFIER_PROFILE_DEADLOCK` | No | Profile would make the current position an unplayable deadlock |
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue