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
|
|
@ -60,3 +60,16 @@ export {
|
|||
type LayoutValidationResult,
|
||||
} from "./layouts/index.js";
|
||||
export { validateLayout } from "./layouts/validate.js";
|
||||
|
||||
// Piece modifier profiles — orthogonal to layouts and presets. Exposed
|
||||
// so the server can type-check wire payloads against the authoritative
|
||||
// chess-side shape. The zod schema itself is NOT re-exported here
|
||||
// because the server package pins a different zod major; the server
|
||||
// mirrors the schema locally (same shape) for wire validation.
|
||||
export type {
|
||||
ModifierProfile,
|
||||
TypeModifier,
|
||||
InstanceModifier,
|
||||
ModifierKindId,
|
||||
Direction,
|
||||
} from "./modifiers/types.js";
|
||||
|
|
|
|||
|
|
@ -28,7 +28,12 @@ export type ErrorCode =
|
|||
| "MSG_TOO_LARGE"
|
||||
| "BAD_TOKEN"
|
||||
| "INVALID_MESSAGE"
|
||||
| "LAYOUT_INVALID";
|
||||
| "LAYOUT_INVALID"
|
||||
// Modifier-profile rejections — mirrored from server/protocol.ts.
|
||||
| "MODIFIER_PROFILE_INVALID"
|
||||
| "MODIFIER_PROFILE_NO_KING"
|
||||
| "MODIFIER_PROFILE_INVULN_KING"
|
||||
| "MODIFIER_PROFILE_DEADLOCK";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Starting layout wire shapes (mirrors server/src/protocol.ts)
|
||||
|
|
@ -66,6 +71,50 @@ export interface Fact {
|
|||
value: unknown;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Modifier profile wire shapes (mirrors server/src/protocol.ts)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export type ModifierKindIdWire =
|
||||
| "hp-bonus"
|
||||
| "range-bonus"
|
||||
| "direction-additions"
|
||||
| "capture-flags"
|
||||
| "promotion-override"
|
||||
| "damage-resistance";
|
||||
|
||||
export interface TypeModifierWire {
|
||||
kind: ModifierKindIdWire;
|
||||
pieceType: PieceType;
|
||||
color: Color | "both";
|
||||
value: unknown;
|
||||
}
|
||||
|
||||
export interface InstanceModifierWire {
|
||||
kind: ModifierKindIdWire;
|
||||
/** Algebraic notation, e.g. "b1". */
|
||||
square: string;
|
||||
value: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON-serializable shape of a ModifierProfile as it travels on the
|
||||
* wire. Mirrors the chess-package `ModifierProfile` type structurally;
|
||||
* kept as an independent interface here so the net-types layer doesn't
|
||||
* need to import from the modifiers subsystem (and accidentally pull
|
||||
* in the descriptor registry side-effects).
|
||||
*/
|
||||
export interface ModifierProfileWire {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
layoutId?: string;
|
||||
perType: TypeModifierWire[];
|
||||
perInstance: InstanceModifierWire[];
|
||||
version: 1;
|
||||
source: "premade" | "custom";
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Server → Client payloads
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -124,6 +173,9 @@ export interface RoomCreatedPayload {
|
|||
/** Resolved starting layout. Optional on the wire for backward
|
||||
* compat with pre-layouts servers. */
|
||||
layout?: ResolvedLayoutWire;
|
||||
/** Modifier profile active at room creation. Optional for backward
|
||||
* compat with pre-modifiers servers. */
|
||||
profile?: ModifierProfileWire;
|
||||
}
|
||||
|
||||
export interface RoomJoinedPayload {
|
||||
|
|
@ -133,6 +185,21 @@ export interface RoomJoinedPayload {
|
|||
activeRules: string[];
|
||||
/** Resolved starting layout (see RoomCreatedPayload.layout). */
|
||||
layout?: ResolvedLayoutWire;
|
||||
/** Active modifier profile when this client joined (see
|
||||
* RoomCreatedPayload.profile). */
|
||||
profile?: ModifierProfileWire;
|
||||
}
|
||||
|
||||
/**
|
||||
* Server → client broadcast: the room's active modifier profile was
|
||||
* replaced. Clients apply this to their local engine at the next turn
|
||||
* boundary — the server guarantees the broadcast is ordered
|
||||
* immediately before the ensuing `game.state` / `game.delta`.
|
||||
*/
|
||||
export interface ModifierProfileUpdatedPayload {
|
||||
profile: ModifierProfileWire;
|
||||
version: number;
|
||||
appliedAt: "turn-boundary";
|
||||
}
|
||||
|
||||
export interface ErrorPayload {
|
||||
|
|
@ -150,6 +217,21 @@ export interface RoomCreatePayload {
|
|||
/** Optional starting-layout selector. When omitted the server
|
||||
* opens the room with the FIDE classic layout. */
|
||||
layout?: LayoutRequest;
|
||||
/** Optional inline modifier profile applied at room creation. */
|
||||
profile?: ModifierProfileWire;
|
||||
}
|
||||
|
||||
/**
|
||||
* Client → server intent: swap the active modifier profile for the
|
||||
* room. Server validates and applies at the next turn boundary, then
|
||||
* broadcasts `modifier-profile.updated` to both players. `version` is
|
||||
* the profile version the client last observed; stale requests are
|
||||
* rejected with `MODIFIER_PROFILE_INVALID`.
|
||||
*/
|
||||
export interface ModifierProfileUpdatePayload {
|
||||
roomCode: string;
|
||||
newProfile: ModifierProfileWire;
|
||||
version: number;
|
||||
}
|
||||
|
||||
export interface RoomJoinPayload {
|
||||
|
|
@ -182,6 +264,7 @@ export type ServerMessage =
|
|||
| MessageEnvelope<"game.presets", GamePresetsPayload>
|
||||
| MessageEnvelope<"room.created", RoomCreatedPayload>
|
||||
| MessageEnvelope<"room.joined", RoomJoinedPayload>
|
||||
| MessageEnvelope<"modifier-profile.updated", ModifierProfileUpdatedPayload>
|
||||
| MessageEnvelope<"error", ErrorPayload>;
|
||||
|
||||
export type ClientMessage =
|
||||
|
|
@ -189,4 +272,5 @@ export type ClientMessage =
|
|||
| MessageEnvelope<"room.join", RoomJoinPayload>
|
||||
| MessageEnvelope<"room.leave", Record<string, never>>
|
||||
| MessageEnvelope<"game.move", GameMovePayload>
|
||||
| MessageEnvelope<"room.setPresets", RoomSetPresetsPayload>;
|
||||
| MessageEnvelope<"room.setPresets", RoomSetPresetsPayload>
|
||||
| MessageEnvelope<"modifier-profile.update", ModifierProfileUpdatePayload>;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue