@thegeem/protocol 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -52,18 +52,25 @@ var HelpType = import_zod.z.enum([
52
52
  ]);
53
53
  var Pv = import_zod.z.number().int().min(1).max(1e6).optional();
54
54
  var IdToken = import_zod.z.string().max(4096).optional();
55
+ var Avatar = import_zod.z.object({
56
+ emoji: import_zod.z.string().min(1).max(16).transform((s) => s.replace(UNSAFE_CHARS, "")).refine((s) => s.length >= 1, "empty emoji"),
57
+ color: import_zod.z.string().regex(/^#[0-9a-fA-F]{6}$/, "color must be #rrggbb")
58
+ });
55
59
  var ClientMsg = import_zod.z.discriminatedUnion("t", [
56
60
  // room / lobby
57
- import_zod.z.object({ t: import_zod.z.literal("createRoom"), name: DisplayName.optional(), pv: Pv, idToken: IdToken }),
61
+ import_zod.z.object({ t: import_zod.z.literal("createRoom"), name: DisplayName.optional(), avatar: Avatar.optional(), pv: Pv, idToken: IdToken }),
58
62
  // Host sets/changes their display name AFTER createRoom (for anonymous one-tap
59
63
  // hosts who started nameless). Host-only; the relay re-broadcasts the lobby.
60
64
  import_zod.z.object({ t: import_zod.z.literal("renameHost"), name: DisplayName }),
61
- import_zod.z.object({ t: import_zod.z.literal("joinRoom"), code: RoomCode, name: DisplayName, pv: Pv, idToken: IdToken }),
62
- import_zod.z.object({ t: import_zod.z.literal("resume"), code: RoomCode, token: import_zod.z.string().max(120), pv: Pv, idToken: IdToken }),
65
+ import_zod.z.object({ t: import_zod.z.literal("joinRoom"), code: RoomCode, name: DisplayName, avatar: Avatar.optional(), pv: Pv, idToken: IdToken }),
66
+ import_zod.z.object({ t: import_zod.z.literal("resume"), code: RoomCode, token: import_zod.z.string().max(120), avatar: Avatar.optional(), pv: Pv, idToken: IdToken }),
63
67
  import_zod.z.object({ t: import_zod.z.literal("leaveRoom") }),
64
68
  import_zod.z.object({ t: import_zod.z.literal("approve"), playerId: PlayerId }),
65
69
  import_zod.z.object({ t: import_zod.z.literal("reject"), playerId: PlayerId }),
66
70
  import_zod.z.object({ t: import_zod.z.literal("kick"), playerId: PlayerId }),
71
+ // Host's IN-PROGRESS category selection in the lobby — a live preview for waiting
72
+ // players. Distinct from startGame.categoryIds, which COMMITS the board. Empty = cleared.
73
+ import_zod.z.object({ t: import_zod.z.literal("setCategories"), categoryIds: import_zod.z.array(import_zod.z.string().max(60)).max(12) }),
67
74
  // host game actions
68
75
  import_zod.z.object({
69
76
  t: import_zod.z.literal("startGame"),
@@ -78,14 +85,17 @@ var ClientMsg = import_zod.z.discriminatedUnion("t", [
78
85
  // host: after a game ends, return the SAME players to the lobby
79
86
  import_zod.z.object({ t: import_zod.z.literal("markOpen"), correct: import_zod.z.boolean() }),
80
87
  import_zod.z.object({ t: import_zod.z.literal("revealNoHost") }),
81
- import_zod.z.object({ t: import_zod.z.literal("awardNoHost"), teamIndex: import_zod.z.number().int().min(0).max(LIMITS.MAX_PLAYERS) }),
88
+ import_zod.z.object({ t: import_zod.z.literal("awardNoHost"), teamIndex: import_zod.z.number().int().min(0).max(LIMITS.MAX_PLAYERS - 1) }),
89
+ // 0-based team index
82
90
  import_zod.z.object({ t: import_zod.z.literal("noHostNobody") }),
83
91
  // player actions
84
92
  import_zod.z.object({ t: import_zod.z.literal("buzz") }),
85
- import_zod.z.object({ t: import_zod.z.literal("answer"), index: import_zod.z.number().int().min(0).max(10) }),
93
+ import_zod.z.object({ t: import_zod.z.literal("answer"), index: import_zod.z.number().int().min(0).max(3) }),
94
+ // trivia is 4-option (0–3)
86
95
  import_zod.z.object({ t: import_zod.z.literal("useHelp"), helpType: HelpType }),
87
96
  // sabotage a rival (active team only): spend a sabotage help to debuff targetTeam's next question
88
- import_zod.z.object({ t: import_zod.z.literal("sabotage"), helpType: HelpType, targetTeam: import_zod.z.number().int().min(0).max(LIMITS.MAX_PLAYERS) })
97
+ import_zod.z.object({ t: import_zod.z.literal("sabotage"), helpType: HelpType, targetTeam: import_zod.z.number().int().min(0).max(LIMITS.MAX_PLAYERS - 1) })
98
+ // 0-based team index
89
99
  ]);
90
100
  // Annotate the CommonJS export names for ESM import in node:
91
101
  0 && (module.exports = {
package/dist/index.d.cts CHANGED
@@ -43,16 +43,34 @@ declare const HelpType: z.ZodEnum<["removeTwoAnswers", "changeQuestion", "extraT
43
43
  declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
44
44
  t: z.ZodLiteral<"createRoom">;
45
45
  name: z.ZodOptional<z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>>;
46
+ avatar: z.ZodOptional<z.ZodObject<{
47
+ emoji: z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>;
48
+ color: z.ZodString;
49
+ }, "strip", z.ZodTypeAny, {
50
+ emoji: string;
51
+ color: string;
52
+ }, {
53
+ emoji: string;
54
+ color: string;
55
+ }>>;
46
56
  pv: z.ZodOptional<z.ZodNumber>;
47
57
  idToken: z.ZodOptional<z.ZodString>;
48
58
  }, "strip", z.ZodTypeAny, {
49
59
  t: "createRoom";
50
60
  name?: string | undefined;
61
+ avatar?: {
62
+ emoji: string;
63
+ color: string;
64
+ } | undefined;
51
65
  pv?: number | undefined;
52
66
  idToken?: string | undefined;
53
67
  }, {
54
68
  t: "createRoom";
55
69
  name?: string | undefined;
70
+ avatar?: {
71
+ emoji: string;
72
+ color: string;
73
+ } | undefined;
56
74
  pv?: number | undefined;
57
75
  idToken?: string | undefined;
58
76
  }>, z.ZodObject<{
@@ -68,36 +86,72 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
68
86
  t: z.ZodLiteral<"joinRoom">;
69
87
  code: z.ZodString;
70
88
  name: z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>;
89
+ avatar: z.ZodOptional<z.ZodObject<{
90
+ emoji: z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>;
91
+ color: z.ZodString;
92
+ }, "strip", z.ZodTypeAny, {
93
+ emoji: string;
94
+ color: string;
95
+ }, {
96
+ emoji: string;
97
+ color: string;
98
+ }>>;
71
99
  pv: z.ZodOptional<z.ZodNumber>;
72
100
  idToken: z.ZodOptional<z.ZodString>;
73
101
  }, "strip", z.ZodTypeAny, {
74
102
  code: string;
75
103
  t: "joinRoom";
76
104
  name: string;
105
+ avatar?: {
106
+ emoji: string;
107
+ color: string;
108
+ } | undefined;
77
109
  pv?: number | undefined;
78
110
  idToken?: string | undefined;
79
111
  }, {
80
112
  code: string;
81
113
  t: "joinRoom";
82
114
  name: string;
115
+ avatar?: {
116
+ emoji: string;
117
+ color: string;
118
+ } | undefined;
83
119
  pv?: number | undefined;
84
120
  idToken?: string | undefined;
85
121
  }>, z.ZodObject<{
86
122
  t: z.ZodLiteral<"resume">;
87
123
  code: z.ZodString;
88
124
  token: z.ZodString;
125
+ avatar: z.ZodOptional<z.ZodObject<{
126
+ emoji: z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>;
127
+ color: z.ZodString;
128
+ }, "strip", z.ZodTypeAny, {
129
+ emoji: string;
130
+ color: string;
131
+ }, {
132
+ emoji: string;
133
+ color: string;
134
+ }>>;
89
135
  pv: z.ZodOptional<z.ZodNumber>;
90
136
  idToken: z.ZodOptional<z.ZodString>;
91
137
  }, "strip", z.ZodTypeAny, {
92
138
  code: string;
93
139
  t: "resume";
94
140
  token: string;
141
+ avatar?: {
142
+ emoji: string;
143
+ color: string;
144
+ } | undefined;
95
145
  pv?: number | undefined;
96
146
  idToken?: string | undefined;
97
147
  }, {
98
148
  code: string;
99
149
  t: "resume";
100
150
  token: string;
151
+ avatar?: {
152
+ emoji: string;
153
+ color: string;
154
+ } | undefined;
101
155
  pv?: number | undefined;
102
156
  idToken?: string | undefined;
103
157
  }>, z.ZodObject<{
@@ -133,6 +187,15 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
133
187
  }, {
134
188
  t: "kick";
135
189
  playerId: string;
190
+ }>, z.ZodObject<{
191
+ t: z.ZodLiteral<"setCategories">;
192
+ categoryIds: z.ZodArray<z.ZodString, "many">;
193
+ }, "strip", z.ZodTypeAny, {
194
+ t: "setCategories";
195
+ categoryIds: string[];
196
+ }, {
197
+ t: "setCategories";
198
+ categoryIds: string[];
136
199
  }>, z.ZodObject<{
137
200
  t: z.ZodLiteral<"startGame">;
138
201
  game: z.ZodOptional<z.ZodEnum<["trivia"]>>;
@@ -140,13 +203,13 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
140
203
  categoryIds: z.ZodArray<z.ZodString, "many">;
141
204
  }, "strip", z.ZodTypeAny, {
142
205
  t: "startGame";
143
- totalRounds: number;
144
206
  categoryIds: string[];
207
+ totalRounds: number;
145
208
  game?: "trivia" | undefined;
146
209
  }, {
147
210
  t: "startGame";
148
- totalRounds: number;
149
211
  categoryIds: string[];
212
+ totalRounds: number;
150
213
  game?: "trivia" | undefined;
151
214
  }>, z.ZodObject<{
152
215
  t: z.ZodLiteral<"pickLevel">;
@@ -240,11 +303,20 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
240
303
  targetTeam: number;
241
304
  }>]>;
242
305
  type ClientMsg = z.infer<typeof ClientMsg>;
306
+ /** A player's privacy-safe avatar (emoji + circle color) for in-room display. */
307
+ interface AvatarView {
308
+ emoji: string;
309
+ color: string;
310
+ }
243
311
  interface LobbyPlayer {
244
312
  id: string;
245
313
  name: string;
246
314
  approved: boolean;
247
315
  connected: boolean;
316
+ /** Privacy-safe avatar (emoji + color); absent if the player didn't set one. */
317
+ avatar?: AvatarView;
318
+ /** Team index once the game has started; null/absent in the lobby. */
319
+ teamIndex?: number | null;
248
320
  }
249
321
  /** Per-recipient context attached to lobby/state pushes. */
250
322
  interface YouContext {
@@ -280,6 +352,8 @@ interface TeamView {
280
352
  type: HelpName;
281
353
  isUsed: boolean;
282
354
  }[];
355
+ /** The seated player's privacy-safe avatar (relay-enriched; engine view omits it). */
356
+ avatar?: AvatarView;
283
357
  }
284
358
  interface QuestionView {
285
359
  text: string;
@@ -313,6 +387,11 @@ interface GameStateView {
313
387
  usedLevels: {
314
388
  [categoryId: string]: number[];
315
389
  };
390
+ /** Live question countdown: ms remaining while the timer is **ticking** (relay-
391
+ * enriched from the server-authoritative timer). Absent between questions, while
392
+ * paused (e.g. steal intro), or after time-up. Present ⇒ animate a countdown from
393
+ * it; the server remains the authority on actual time-up. */
394
+ timerRemainingMs?: number;
316
395
  }
317
396
  /** Transient presentation effects the relay emits; clients play them once. */
318
397
  type Effect = {
@@ -390,6 +469,7 @@ type ServerMsg = {
390
469
  hostName: string;
391
470
  players: LobbyPlayer[];
392
471
  categories: CategoryView[];
472
+ selectedCategoryIds: string[];
393
473
  you: YouContext;
394
474
  } | {
395
475
  t: 'state';
@@ -406,4 +486,4 @@ type ServerMsg = {
406
486
  message: string;
407
487
  };
408
488
 
409
- export { type BuzzerGameMode, type CategoryView, ClientMsg, type Effect, GAME_IDS, type GameId, type GameStateView, type GameStatus, type GameplayFlow, type HelpName, LIMITS, type LobbyPlayer, PROTOCOL_VERSION, type QuestionView, ROOM_ALPHABET, type ServerMsg, type SoundName, type TeamView, type YouContext };
489
+ export { type AvatarView, type BuzzerGameMode, type CategoryView, ClientMsg, type Effect, GAME_IDS, type GameId, type GameStateView, type GameStatus, type GameplayFlow, type HelpName, LIMITS, type LobbyPlayer, PROTOCOL_VERSION, type QuestionView, ROOM_ALPHABET, type ServerMsg, type SoundName, type TeamView, type YouContext };
package/dist/index.d.ts CHANGED
@@ -43,16 +43,34 @@ declare const HelpType: z.ZodEnum<["removeTwoAnswers", "changeQuestion", "extraT
43
43
  declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
44
44
  t: z.ZodLiteral<"createRoom">;
45
45
  name: z.ZodOptional<z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>>;
46
+ avatar: z.ZodOptional<z.ZodObject<{
47
+ emoji: z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>;
48
+ color: z.ZodString;
49
+ }, "strip", z.ZodTypeAny, {
50
+ emoji: string;
51
+ color: string;
52
+ }, {
53
+ emoji: string;
54
+ color: string;
55
+ }>>;
46
56
  pv: z.ZodOptional<z.ZodNumber>;
47
57
  idToken: z.ZodOptional<z.ZodString>;
48
58
  }, "strip", z.ZodTypeAny, {
49
59
  t: "createRoom";
50
60
  name?: string | undefined;
61
+ avatar?: {
62
+ emoji: string;
63
+ color: string;
64
+ } | undefined;
51
65
  pv?: number | undefined;
52
66
  idToken?: string | undefined;
53
67
  }, {
54
68
  t: "createRoom";
55
69
  name?: string | undefined;
70
+ avatar?: {
71
+ emoji: string;
72
+ color: string;
73
+ } | undefined;
56
74
  pv?: number | undefined;
57
75
  idToken?: string | undefined;
58
76
  }>, z.ZodObject<{
@@ -68,36 +86,72 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
68
86
  t: z.ZodLiteral<"joinRoom">;
69
87
  code: z.ZodString;
70
88
  name: z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>;
89
+ avatar: z.ZodOptional<z.ZodObject<{
90
+ emoji: z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>;
91
+ color: z.ZodString;
92
+ }, "strip", z.ZodTypeAny, {
93
+ emoji: string;
94
+ color: string;
95
+ }, {
96
+ emoji: string;
97
+ color: string;
98
+ }>>;
71
99
  pv: z.ZodOptional<z.ZodNumber>;
72
100
  idToken: z.ZodOptional<z.ZodString>;
73
101
  }, "strip", z.ZodTypeAny, {
74
102
  code: string;
75
103
  t: "joinRoom";
76
104
  name: string;
105
+ avatar?: {
106
+ emoji: string;
107
+ color: string;
108
+ } | undefined;
77
109
  pv?: number | undefined;
78
110
  idToken?: string | undefined;
79
111
  }, {
80
112
  code: string;
81
113
  t: "joinRoom";
82
114
  name: string;
115
+ avatar?: {
116
+ emoji: string;
117
+ color: string;
118
+ } | undefined;
83
119
  pv?: number | undefined;
84
120
  idToken?: string | undefined;
85
121
  }>, z.ZodObject<{
86
122
  t: z.ZodLiteral<"resume">;
87
123
  code: z.ZodString;
88
124
  token: z.ZodString;
125
+ avatar: z.ZodOptional<z.ZodObject<{
126
+ emoji: z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>;
127
+ color: z.ZodString;
128
+ }, "strip", z.ZodTypeAny, {
129
+ emoji: string;
130
+ color: string;
131
+ }, {
132
+ emoji: string;
133
+ color: string;
134
+ }>>;
89
135
  pv: z.ZodOptional<z.ZodNumber>;
90
136
  idToken: z.ZodOptional<z.ZodString>;
91
137
  }, "strip", z.ZodTypeAny, {
92
138
  code: string;
93
139
  t: "resume";
94
140
  token: string;
141
+ avatar?: {
142
+ emoji: string;
143
+ color: string;
144
+ } | undefined;
95
145
  pv?: number | undefined;
96
146
  idToken?: string | undefined;
97
147
  }, {
98
148
  code: string;
99
149
  t: "resume";
100
150
  token: string;
151
+ avatar?: {
152
+ emoji: string;
153
+ color: string;
154
+ } | undefined;
101
155
  pv?: number | undefined;
102
156
  idToken?: string | undefined;
103
157
  }>, z.ZodObject<{
@@ -133,6 +187,15 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
133
187
  }, {
134
188
  t: "kick";
135
189
  playerId: string;
190
+ }>, z.ZodObject<{
191
+ t: z.ZodLiteral<"setCategories">;
192
+ categoryIds: z.ZodArray<z.ZodString, "many">;
193
+ }, "strip", z.ZodTypeAny, {
194
+ t: "setCategories";
195
+ categoryIds: string[];
196
+ }, {
197
+ t: "setCategories";
198
+ categoryIds: string[];
136
199
  }>, z.ZodObject<{
137
200
  t: z.ZodLiteral<"startGame">;
138
201
  game: z.ZodOptional<z.ZodEnum<["trivia"]>>;
@@ -140,13 +203,13 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
140
203
  categoryIds: z.ZodArray<z.ZodString, "many">;
141
204
  }, "strip", z.ZodTypeAny, {
142
205
  t: "startGame";
143
- totalRounds: number;
144
206
  categoryIds: string[];
207
+ totalRounds: number;
145
208
  game?: "trivia" | undefined;
146
209
  }, {
147
210
  t: "startGame";
148
- totalRounds: number;
149
211
  categoryIds: string[];
212
+ totalRounds: number;
150
213
  game?: "trivia" | undefined;
151
214
  }>, z.ZodObject<{
152
215
  t: z.ZodLiteral<"pickLevel">;
@@ -240,11 +303,20 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
240
303
  targetTeam: number;
241
304
  }>]>;
242
305
  type ClientMsg = z.infer<typeof ClientMsg>;
306
+ /** A player's privacy-safe avatar (emoji + circle color) for in-room display. */
307
+ interface AvatarView {
308
+ emoji: string;
309
+ color: string;
310
+ }
243
311
  interface LobbyPlayer {
244
312
  id: string;
245
313
  name: string;
246
314
  approved: boolean;
247
315
  connected: boolean;
316
+ /** Privacy-safe avatar (emoji + color); absent if the player didn't set one. */
317
+ avatar?: AvatarView;
318
+ /** Team index once the game has started; null/absent in the lobby. */
319
+ teamIndex?: number | null;
248
320
  }
249
321
  /** Per-recipient context attached to lobby/state pushes. */
250
322
  interface YouContext {
@@ -280,6 +352,8 @@ interface TeamView {
280
352
  type: HelpName;
281
353
  isUsed: boolean;
282
354
  }[];
355
+ /** The seated player's privacy-safe avatar (relay-enriched; engine view omits it). */
356
+ avatar?: AvatarView;
283
357
  }
284
358
  interface QuestionView {
285
359
  text: string;
@@ -313,6 +387,11 @@ interface GameStateView {
313
387
  usedLevels: {
314
388
  [categoryId: string]: number[];
315
389
  };
390
+ /** Live question countdown: ms remaining while the timer is **ticking** (relay-
391
+ * enriched from the server-authoritative timer). Absent between questions, while
392
+ * paused (e.g. steal intro), or after time-up. Present ⇒ animate a countdown from
393
+ * it; the server remains the authority on actual time-up. */
394
+ timerRemainingMs?: number;
316
395
  }
317
396
  /** Transient presentation effects the relay emits; clients play them once. */
318
397
  type Effect = {
@@ -390,6 +469,7 @@ type ServerMsg = {
390
469
  hostName: string;
391
470
  players: LobbyPlayer[];
392
471
  categories: CategoryView[];
472
+ selectedCategoryIds: string[];
393
473
  you: YouContext;
394
474
  } | {
395
475
  t: 'state';
@@ -406,4 +486,4 @@ type ServerMsg = {
406
486
  message: string;
407
487
  };
408
488
 
409
- export { type BuzzerGameMode, type CategoryView, ClientMsg, type Effect, GAME_IDS, type GameId, type GameStateView, type GameStatus, type GameplayFlow, type HelpName, LIMITS, type LobbyPlayer, PROTOCOL_VERSION, type QuestionView, ROOM_ALPHABET, type ServerMsg, type SoundName, type TeamView, type YouContext };
489
+ export { type AvatarView, type BuzzerGameMode, type CategoryView, ClientMsg, type Effect, GAME_IDS, type GameId, type GameStateView, type GameStatus, type GameplayFlow, type HelpName, LIMITS, type LobbyPlayer, PROTOCOL_VERSION, type QuestionView, ROOM_ALPHABET, type ServerMsg, type SoundName, type TeamView, type YouContext };
package/dist/index.js CHANGED
@@ -24,18 +24,25 @@ var HelpType = z.enum([
24
24
  ]);
25
25
  var Pv = z.number().int().min(1).max(1e6).optional();
26
26
  var IdToken = z.string().max(4096).optional();
27
+ var Avatar = z.object({
28
+ emoji: z.string().min(1).max(16).transform((s) => s.replace(UNSAFE_CHARS, "")).refine((s) => s.length >= 1, "empty emoji"),
29
+ color: z.string().regex(/^#[0-9a-fA-F]{6}$/, "color must be #rrggbb")
30
+ });
27
31
  var ClientMsg = z.discriminatedUnion("t", [
28
32
  // room / lobby
29
- z.object({ t: z.literal("createRoom"), name: DisplayName.optional(), pv: Pv, idToken: IdToken }),
33
+ z.object({ t: z.literal("createRoom"), name: DisplayName.optional(), avatar: Avatar.optional(), pv: Pv, idToken: IdToken }),
30
34
  // Host sets/changes their display name AFTER createRoom (for anonymous one-tap
31
35
  // hosts who started nameless). Host-only; the relay re-broadcasts the lobby.
32
36
  z.object({ t: z.literal("renameHost"), name: DisplayName }),
33
- z.object({ t: z.literal("joinRoom"), code: RoomCode, name: DisplayName, pv: Pv, idToken: IdToken }),
34
- z.object({ t: z.literal("resume"), code: RoomCode, token: z.string().max(120), pv: Pv, idToken: IdToken }),
37
+ z.object({ t: z.literal("joinRoom"), code: RoomCode, name: DisplayName, avatar: Avatar.optional(), pv: Pv, idToken: IdToken }),
38
+ z.object({ t: z.literal("resume"), code: RoomCode, token: z.string().max(120), avatar: Avatar.optional(), pv: Pv, idToken: IdToken }),
35
39
  z.object({ t: z.literal("leaveRoom") }),
36
40
  z.object({ t: z.literal("approve"), playerId: PlayerId }),
37
41
  z.object({ t: z.literal("reject"), playerId: PlayerId }),
38
42
  z.object({ t: z.literal("kick"), playerId: PlayerId }),
43
+ // Host's IN-PROGRESS category selection in the lobby — a live preview for waiting
44
+ // players. Distinct from startGame.categoryIds, which COMMITS the board. Empty = cleared.
45
+ z.object({ t: z.literal("setCategories"), categoryIds: z.array(z.string().max(60)).max(12) }),
39
46
  // host game actions
40
47
  z.object({
41
48
  t: z.literal("startGame"),
@@ -50,14 +57,17 @@ var ClientMsg = z.discriminatedUnion("t", [
50
57
  // host: after a game ends, return the SAME players to the lobby
51
58
  z.object({ t: z.literal("markOpen"), correct: z.boolean() }),
52
59
  z.object({ t: z.literal("revealNoHost") }),
53
- z.object({ t: z.literal("awardNoHost"), teamIndex: z.number().int().min(0).max(LIMITS.MAX_PLAYERS) }),
60
+ z.object({ t: z.literal("awardNoHost"), teamIndex: z.number().int().min(0).max(LIMITS.MAX_PLAYERS - 1) }),
61
+ // 0-based team index
54
62
  z.object({ t: z.literal("noHostNobody") }),
55
63
  // player actions
56
64
  z.object({ t: z.literal("buzz") }),
57
- z.object({ t: z.literal("answer"), index: z.number().int().min(0).max(10) }),
65
+ z.object({ t: z.literal("answer"), index: z.number().int().min(0).max(3) }),
66
+ // trivia is 4-option (0–3)
58
67
  z.object({ t: z.literal("useHelp"), helpType: HelpType }),
59
68
  // sabotage a rival (active team only): spend a sabotage help to debuff targetTeam's next question
60
- z.object({ t: z.literal("sabotage"), helpType: HelpType, targetTeam: z.number().int().min(0).max(LIMITS.MAX_PLAYERS) })
69
+ z.object({ t: z.literal("sabotage"), helpType: HelpType, targetTeam: z.number().int().min(0).max(LIMITS.MAX_PLAYERS - 1) })
70
+ // 0-based team index
61
71
  ]);
62
72
  export {
63
73
  ClientMsg,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thegeem/protocol",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Geem's wire protocol — shared zod schemas + TypeScript types for talking to the Geem game server over Socket.IO. The source of truth all clients implement against.",
5
5
  "license": "UNLICENSED",
6
6
  "homepage": "https://geem.tv",