@thegeem/protocol 0.1.14 → 0.1.16

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
@@ -21,12 +21,16 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  ClientMsg: () => ClientMsg,
24
+ GAMES: () => GAMES,
24
25
  GAME_IDS: () => GAME_IDS,
25
26
  LIMITS: () => LIMITS,
27
+ MIN_SUPPORTED_PV: () => MIN_SUPPORTED_PV,
26
28
  MODES: () => MODES,
27
29
  MODE_IDS: () => MODE_IDS,
28
30
  PROTOCOL_VERSION: () => PROTOCOL_VERSION,
29
31
  ROOM_ALPHABET: () => ROOM_ALPHABET,
32
+ gameIdForPv: () => gameIdForPv,
33
+ gamesForPv: () => gamesForPv,
30
34
  modesForGame: () => modesForGame
31
35
  });
32
36
  module.exports = __toCommonJS(index_exports);
@@ -37,14 +41,25 @@ var LIMITS = {
37
41
  MAX_PLAYERS: 12,
38
42
  MAX_MSG_PER_SEC: 8
39
43
  };
40
- var PROTOCOL_VERSION = 1;
41
- var GAME_IDS = ["trivia"];
44
+ var PROTOCOL_VERSION = 2;
45
+ var MIN_SUPPORTED_PV = 1;
46
+ var GAME_IDS = ["seedha", "snag", "trivia"];
47
+ var GAMES = [
48
+ { id: "seedha", label: "Seedha", minPv: 2 },
49
+ { id: "snag", label: "Snag", minPv: 2 },
50
+ { id: "trivia", label: "Trivia", minPv: 1, deprecated: true }
51
+ ];
52
+ var gamesForPv = (pv) => GAMES.filter((g) => !g.deprecated && g.minPv <= pv);
53
+ var gameIdForPv = (gameId, pv) => {
54
+ const g = GAMES.find((x) => x.id === gameId);
55
+ return g && !g.deprecated && (pv ?? MIN_SUPPORTED_PV) >= g.minPv ? gameId : "trivia";
56
+ };
42
57
  var MODE_IDS = ["solo", "oneDevice", "snag", "multiplayer"];
43
58
  var MODES = [
44
- { id: "solo", gameId: "trivia", label: "Solo", onlineEligible: false },
45
- { id: "oneDevice", gameId: "trivia", label: "One Device", onlineEligible: false },
46
- { id: "snag", gameId: "trivia", label: "Snag", onlineEligible: true },
47
- { id: "multiplayer", gameId: "trivia", label: "Multiplayer", onlineEligible: true }
59
+ { id: "solo", gameId: "seedha", label: "Training", onlineEligible: false },
60
+ { id: "oneDevice", gameId: "seedha", label: "One Device", onlineEligible: false },
61
+ { id: "multiplayer", gameId: "seedha", label: "Multiplayer", onlineEligible: true },
62
+ { id: "snag", gameId: "snag", label: "Snag", onlineEligible: true }
48
63
  ];
49
64
  var modesForGame = (gameId) => MODES.filter((m) => m.gameId === gameId);
50
65
  var ROOM_ALPHABET = "ACDEFGHJKMNPQRTUVWXY2346789";
@@ -134,16 +149,24 @@ var ClientMsg = import_zod.z.discriminatedUnion("t", [
134
149
  // moderation
135
150
  // Report another player in the room (App Store 1.2 UGC — display names). Available in lobby or
136
151
  // game. The server records reporter/reported/room context; `reason` is an optional free-text note.
137
- import_zod.z.object({ t: import_zod.z.literal("report"), playerId: PlayerId, reason: import_zod.z.string().max(500).optional() })
152
+ import_zod.z.object({ t: import_zod.z.literal("report"), playerId: PlayerId, reason: import_zod.z.string().max(500).optional() }),
153
+ // Report the CURRENT question as bad (wrong answer / typo / offensive). The relay resolves WHICH
154
+ // question from its authoritative room state — the client never sees the question id (anti-cheat),
155
+ // so it only sends an optional `reason`. Only meaningful while a question is on screen.
156
+ import_zod.z.object({ t: import_zod.z.literal("reportQuestion"), reason: import_zod.z.string().max(500).optional() })
138
157
  ]);
139
158
  // Annotate the CommonJS export names for ESM import in node:
140
159
  0 && (module.exports = {
141
160
  ClientMsg,
161
+ GAMES,
142
162
  GAME_IDS,
143
163
  LIMITS,
164
+ MIN_SUPPORTED_PV,
144
165
  MODES,
145
166
  MODE_IDS,
146
167
  PROTOCOL_VERSION,
147
168
  ROOM_ALPHABET,
169
+ gameIdForPv,
170
+ gamesForPv,
148
171
  modesForGame
149
172
  });
package/dist/index.d.cts CHANGED
@@ -23,20 +23,43 @@ declare const LIMITS: {
23
23
  *
24
24
  * Why this exists: web auto-updates, but an App-Store / Play build is FROZEN on
25
25
  * the user's device. A client sends the version it was built against (`pv`) on
26
- * its first message; the server rejects `pv < PROTOCOL_VERSION` with
27
- * `{ t:'error', code:'client_outdated' }` so old native builds get a clean
28
- * "please update" instead of silently mis-rendering. Unversioned clients (no
29
- * `pv`, e.g. the legacy web harness) are never rejected.
26
+ * its first message.
27
+ *
28
+ * Versioning model (D-025) ADAPT within a support window, don't hard-gate:
29
+ * - `PROTOCOL_VERSION` = the CURRENT capability level (bump when new wire features ship).
30
+ * - `MIN_SUPPORTED_PV` = the REJECT FLOOR: the server rejects only `pv < MIN_SUPPORTED_PV`
31
+ * (`{ t:'error', code:'client_outdated' }`); builds between the floor and current keep working —
32
+ * the server tailors its output to their `pv` (e.g. sends the legacy `gameId:'trivia'` instead of
33
+ * `seedha`/`snag`). Raise the floor SLOWLY to retire ancient builds. Unversioned clients (no `pv`,
34
+ * e.g. the legacy web harness) are treated as the floor and never rejected.
30
35
  */
31
- declare const PROTOCOL_VERSION = 1;
36
+ declare const PROTOCOL_VERSION = 2;
37
+ declare const MIN_SUPPORTED_PV = 1;
32
38
  /**
33
- * Geem is a PLATFORM of GAMES (jackbox-style); the trivia board is Game #1.
34
- * Every room runs one game, identified by `gameId`. Add ids here as games ship.
35
- * (NOTE: "modes" controller/race/no-host are variants WITHIN the trivia
36
- * game, not separate games. See the platform-of-games decision.)
39
+ * Geem is a PLATFORM of GAMES (jackbox-style). The trivia board split (D-023) into TWO player-facing
40
+ * games: `seedha` (turn-based: Training/One Device/Multiplayer) and `snag` (buzzer race one mode,
41
+ * needs ≥2 devices). `trivia` is kept as a DEPRECATED legacy alias: it's what the relay sends to
42
+ * pre-pv2 clients (they never saw seedha/snag), and what old clients send on startGame. Every room
43
+ * runs one game, identified by `gameId`.
37
44
  */
38
- declare const GAME_IDS: readonly ["trivia"];
45
+ declare const GAME_IDS: readonly ["seedha", "snag", "trivia"];
39
46
  type GameId = (typeof GAME_IDS)[number];
47
+ /**
48
+ * The GAME REGISTRY — player-facing games + the version gating (D-024/D-025). A client renders a tile
49
+ * per game whose `minPv <= its own pv` (so old builds are never offered a game they can't run); the
50
+ * client maps its `pv` → an "update to play X" hint for games above it. Adding Game #N = one row here.
51
+ */
52
+ interface GameInfo {
53
+ id: GameId;
54
+ label: string;
55
+ minPv: number;
56
+ deprecated?: boolean;
57
+ }
58
+ declare const GAMES: readonly GameInfo[];
59
+ /** Games to OFFER a client of version `pv` (tiles). Excludes the deprecated alias + anything too new. */
60
+ declare const gamesForPv: (pv: number) => GameInfo[];
61
+ /** The gameId to SEND a client of version `pv`: new games need pv >= their minPv; older clients see 'trivia'. */
62
+ declare const gameIdForPv: (gameId: GameId, pv: number | undefined) => GameId;
40
63
  /**
41
64
  * The MODE REGISTRY — the list of ways to play each game. A client renders its mode picker from this
42
65
  * and sends the chosen `modeId` on `startGame`; the SERVER owns the modeId → engine-rules mapping (the
@@ -248,10 +271,10 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
248
271
  modeId: z.ZodEnum<["solo", "oneDevice", "snag", "multiplayer"]>;
249
272
  }, "strip", z.ZodTypeAny, {
250
273
  t: "setMode";
251
- modeId: "solo" | "oneDevice" | "snag" | "multiplayer";
274
+ modeId: "snag" | "solo" | "oneDevice" | "multiplayer";
252
275
  }, {
253
276
  t: "setMode";
254
- modeId: "solo" | "oneDevice" | "snag" | "multiplayer";
277
+ modeId: "snag" | "solo" | "oneDevice" | "multiplayer";
255
278
  }>, z.ZodObject<{
256
279
  t: z.ZodLiteral<"hostActivity">;
257
280
  activity: z.ZodEnum<["configuring", "idle"]>;
@@ -263,7 +286,7 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
263
286
  activity: "configuring" | "idle";
264
287
  }>, z.ZodObject<{
265
288
  t: z.ZodLiteral<"startGame">;
266
- game: z.ZodOptional<z.ZodEnum<["trivia"]>>;
289
+ game: z.ZodOptional<z.ZodEnum<["seedha", "snag", "trivia"]>>;
267
290
  totalRounds: z.ZodNumber;
268
291
  categoryIds: z.ZodArray<z.ZodString, "many">;
269
292
  modeId: z.ZodOptional<z.ZodEnum<["solo", "oneDevice", "snag", "multiplayer"]>>;
@@ -273,16 +296,16 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
273
296
  t: "startGame";
274
297
  categoryIds: string[];
275
298
  totalRounds: number;
276
- modeId?: "solo" | "oneDevice" | "snag" | "multiplayer" | undefined;
277
- game?: "trivia" | undefined;
299
+ modeId?: "snag" | "solo" | "oneDevice" | "multiplayer" | undefined;
300
+ game?: "seedha" | "snag" | "trivia" | undefined;
278
301
  buzzerMode?: "solo" | "off" | "race" | "controller" | undefined;
279
302
  noHost?: boolean | undefined;
280
303
  }, {
281
304
  t: "startGame";
282
305
  categoryIds: string[];
283
306
  totalRounds: number;
284
- modeId?: "solo" | "oneDevice" | "snag" | "multiplayer" | undefined;
285
- game?: "trivia" | undefined;
307
+ modeId?: "snag" | "solo" | "oneDevice" | "multiplayer" | undefined;
308
+ game?: "seedha" | "snag" | "trivia" | undefined;
286
309
  buzzerMode?: "solo" | "off" | "race" | "controller" | undefined;
287
310
  noHost?: boolean | undefined;
288
311
  }>, z.ZodObject<{
@@ -387,6 +410,15 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
387
410
  t: "report";
388
411
  playerId: string;
389
412
  reason?: string | undefined;
413
+ }>, z.ZodObject<{
414
+ t: z.ZodLiteral<"reportQuestion">;
415
+ reason: z.ZodOptional<z.ZodString>;
416
+ }, "strip", z.ZodTypeAny, {
417
+ t: "reportQuestion";
418
+ reason?: string | undefined;
419
+ }, {
420
+ t: "reportQuestion";
421
+ reason?: string | undefined;
390
422
  }>]>;
391
423
  type ClientMsg = z.infer<typeof ClientMsg>;
392
424
  /** A player's privacy-safe avatar (emoji + circle color) for in-room display. */
@@ -600,4 +632,4 @@ type ServerMsg = {
600
632
  message: string;
601
633
  };
602
634
 
603
- 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, MODES, MODE_IDS, type ModeId, type ModeInfo, PROTOCOL_VERSION, type QuestionView, ROOM_ALPHABET, type ServerMsg, type SoundName, type TeamSlotView, type TeamView, type YouContext, modesForGame };
635
+ export { type AvatarView, type BuzzerGameMode, type CategoryView, ClientMsg, type Effect, GAMES, GAME_IDS, type GameId, type GameInfo, type GameStateView, type GameStatus, type GameplayFlow, type HelpName, LIMITS, type LobbyPlayer, MIN_SUPPORTED_PV, MODES, MODE_IDS, type ModeId, type ModeInfo, PROTOCOL_VERSION, type QuestionView, ROOM_ALPHABET, type ServerMsg, type SoundName, type TeamSlotView, type TeamView, type YouContext, gameIdForPv, gamesForPv, modesForGame };
package/dist/index.d.ts CHANGED
@@ -23,20 +23,43 @@ declare const LIMITS: {
23
23
  *
24
24
  * Why this exists: web auto-updates, but an App-Store / Play build is FROZEN on
25
25
  * the user's device. A client sends the version it was built against (`pv`) on
26
- * its first message; the server rejects `pv < PROTOCOL_VERSION` with
27
- * `{ t:'error', code:'client_outdated' }` so old native builds get a clean
28
- * "please update" instead of silently mis-rendering. Unversioned clients (no
29
- * `pv`, e.g. the legacy web harness) are never rejected.
26
+ * its first message.
27
+ *
28
+ * Versioning model (D-025) ADAPT within a support window, don't hard-gate:
29
+ * - `PROTOCOL_VERSION` = the CURRENT capability level (bump when new wire features ship).
30
+ * - `MIN_SUPPORTED_PV` = the REJECT FLOOR: the server rejects only `pv < MIN_SUPPORTED_PV`
31
+ * (`{ t:'error', code:'client_outdated' }`); builds between the floor and current keep working —
32
+ * the server tailors its output to their `pv` (e.g. sends the legacy `gameId:'trivia'` instead of
33
+ * `seedha`/`snag`). Raise the floor SLOWLY to retire ancient builds. Unversioned clients (no `pv`,
34
+ * e.g. the legacy web harness) are treated as the floor and never rejected.
30
35
  */
31
- declare const PROTOCOL_VERSION = 1;
36
+ declare const PROTOCOL_VERSION = 2;
37
+ declare const MIN_SUPPORTED_PV = 1;
32
38
  /**
33
- * Geem is a PLATFORM of GAMES (jackbox-style); the trivia board is Game #1.
34
- * Every room runs one game, identified by `gameId`. Add ids here as games ship.
35
- * (NOTE: "modes" controller/race/no-host are variants WITHIN the trivia
36
- * game, not separate games. See the platform-of-games decision.)
39
+ * Geem is a PLATFORM of GAMES (jackbox-style). The trivia board split (D-023) into TWO player-facing
40
+ * games: `seedha` (turn-based: Training/One Device/Multiplayer) and `snag` (buzzer race one mode,
41
+ * needs ≥2 devices). `trivia` is kept as a DEPRECATED legacy alias: it's what the relay sends to
42
+ * pre-pv2 clients (they never saw seedha/snag), and what old clients send on startGame. Every room
43
+ * runs one game, identified by `gameId`.
37
44
  */
38
- declare const GAME_IDS: readonly ["trivia"];
45
+ declare const GAME_IDS: readonly ["seedha", "snag", "trivia"];
39
46
  type GameId = (typeof GAME_IDS)[number];
47
+ /**
48
+ * The GAME REGISTRY — player-facing games + the version gating (D-024/D-025). A client renders a tile
49
+ * per game whose `minPv <= its own pv` (so old builds are never offered a game they can't run); the
50
+ * client maps its `pv` → an "update to play X" hint for games above it. Adding Game #N = one row here.
51
+ */
52
+ interface GameInfo {
53
+ id: GameId;
54
+ label: string;
55
+ minPv: number;
56
+ deprecated?: boolean;
57
+ }
58
+ declare const GAMES: readonly GameInfo[];
59
+ /** Games to OFFER a client of version `pv` (tiles). Excludes the deprecated alias + anything too new. */
60
+ declare const gamesForPv: (pv: number) => GameInfo[];
61
+ /** The gameId to SEND a client of version `pv`: new games need pv >= their minPv; older clients see 'trivia'. */
62
+ declare const gameIdForPv: (gameId: GameId, pv: number | undefined) => GameId;
40
63
  /**
41
64
  * The MODE REGISTRY — the list of ways to play each game. A client renders its mode picker from this
42
65
  * and sends the chosen `modeId` on `startGame`; the SERVER owns the modeId → engine-rules mapping (the
@@ -248,10 +271,10 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
248
271
  modeId: z.ZodEnum<["solo", "oneDevice", "snag", "multiplayer"]>;
249
272
  }, "strip", z.ZodTypeAny, {
250
273
  t: "setMode";
251
- modeId: "solo" | "oneDevice" | "snag" | "multiplayer";
274
+ modeId: "snag" | "solo" | "oneDevice" | "multiplayer";
252
275
  }, {
253
276
  t: "setMode";
254
- modeId: "solo" | "oneDevice" | "snag" | "multiplayer";
277
+ modeId: "snag" | "solo" | "oneDevice" | "multiplayer";
255
278
  }>, z.ZodObject<{
256
279
  t: z.ZodLiteral<"hostActivity">;
257
280
  activity: z.ZodEnum<["configuring", "idle"]>;
@@ -263,7 +286,7 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
263
286
  activity: "configuring" | "idle";
264
287
  }>, z.ZodObject<{
265
288
  t: z.ZodLiteral<"startGame">;
266
- game: z.ZodOptional<z.ZodEnum<["trivia"]>>;
289
+ game: z.ZodOptional<z.ZodEnum<["seedha", "snag", "trivia"]>>;
267
290
  totalRounds: z.ZodNumber;
268
291
  categoryIds: z.ZodArray<z.ZodString, "many">;
269
292
  modeId: z.ZodOptional<z.ZodEnum<["solo", "oneDevice", "snag", "multiplayer"]>>;
@@ -273,16 +296,16 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
273
296
  t: "startGame";
274
297
  categoryIds: string[];
275
298
  totalRounds: number;
276
- modeId?: "solo" | "oneDevice" | "snag" | "multiplayer" | undefined;
277
- game?: "trivia" | undefined;
299
+ modeId?: "snag" | "solo" | "oneDevice" | "multiplayer" | undefined;
300
+ game?: "seedha" | "snag" | "trivia" | undefined;
278
301
  buzzerMode?: "solo" | "off" | "race" | "controller" | undefined;
279
302
  noHost?: boolean | undefined;
280
303
  }, {
281
304
  t: "startGame";
282
305
  categoryIds: string[];
283
306
  totalRounds: number;
284
- modeId?: "solo" | "oneDevice" | "snag" | "multiplayer" | undefined;
285
- game?: "trivia" | undefined;
307
+ modeId?: "snag" | "solo" | "oneDevice" | "multiplayer" | undefined;
308
+ game?: "seedha" | "snag" | "trivia" | undefined;
286
309
  buzzerMode?: "solo" | "off" | "race" | "controller" | undefined;
287
310
  noHost?: boolean | undefined;
288
311
  }>, z.ZodObject<{
@@ -387,6 +410,15 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
387
410
  t: "report";
388
411
  playerId: string;
389
412
  reason?: string | undefined;
413
+ }>, z.ZodObject<{
414
+ t: z.ZodLiteral<"reportQuestion">;
415
+ reason: z.ZodOptional<z.ZodString>;
416
+ }, "strip", z.ZodTypeAny, {
417
+ t: "reportQuestion";
418
+ reason?: string | undefined;
419
+ }, {
420
+ t: "reportQuestion";
421
+ reason?: string | undefined;
390
422
  }>]>;
391
423
  type ClientMsg = z.infer<typeof ClientMsg>;
392
424
  /** A player's privacy-safe avatar (emoji + circle color) for in-room display. */
@@ -600,4 +632,4 @@ type ServerMsg = {
600
632
  message: string;
601
633
  };
602
634
 
603
- 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, MODES, MODE_IDS, type ModeId, type ModeInfo, PROTOCOL_VERSION, type QuestionView, ROOM_ALPHABET, type ServerMsg, type SoundName, type TeamSlotView, type TeamView, type YouContext, modesForGame };
635
+ export { type AvatarView, type BuzzerGameMode, type CategoryView, ClientMsg, type Effect, GAMES, GAME_IDS, type GameId, type GameInfo, type GameStateView, type GameStatus, type GameplayFlow, type HelpName, LIMITS, type LobbyPlayer, MIN_SUPPORTED_PV, MODES, MODE_IDS, type ModeId, type ModeInfo, PROTOCOL_VERSION, type QuestionView, ROOM_ALPHABET, type ServerMsg, type SoundName, type TeamSlotView, type TeamView, type YouContext, gameIdForPv, gamesForPv, modesForGame };
package/dist/index.js CHANGED
@@ -6,14 +6,25 @@ var LIMITS = {
6
6
  MAX_PLAYERS: 12,
7
7
  MAX_MSG_PER_SEC: 8
8
8
  };
9
- var PROTOCOL_VERSION = 1;
10
- var GAME_IDS = ["trivia"];
9
+ var PROTOCOL_VERSION = 2;
10
+ var MIN_SUPPORTED_PV = 1;
11
+ var GAME_IDS = ["seedha", "snag", "trivia"];
12
+ var GAMES = [
13
+ { id: "seedha", label: "Seedha", minPv: 2 },
14
+ { id: "snag", label: "Snag", minPv: 2 },
15
+ { id: "trivia", label: "Trivia", minPv: 1, deprecated: true }
16
+ ];
17
+ var gamesForPv = (pv) => GAMES.filter((g) => !g.deprecated && g.minPv <= pv);
18
+ var gameIdForPv = (gameId, pv) => {
19
+ const g = GAMES.find((x) => x.id === gameId);
20
+ return g && !g.deprecated && (pv ?? MIN_SUPPORTED_PV) >= g.minPv ? gameId : "trivia";
21
+ };
11
22
  var MODE_IDS = ["solo", "oneDevice", "snag", "multiplayer"];
12
23
  var MODES = [
13
- { id: "solo", gameId: "trivia", label: "Solo", onlineEligible: false },
14
- { id: "oneDevice", gameId: "trivia", label: "One Device", onlineEligible: false },
15
- { id: "snag", gameId: "trivia", label: "Snag", onlineEligible: true },
16
- { id: "multiplayer", gameId: "trivia", label: "Multiplayer", onlineEligible: true }
24
+ { id: "solo", gameId: "seedha", label: "Training", onlineEligible: false },
25
+ { id: "oneDevice", gameId: "seedha", label: "One Device", onlineEligible: false },
26
+ { id: "multiplayer", gameId: "seedha", label: "Multiplayer", onlineEligible: true },
27
+ { id: "snag", gameId: "snag", label: "Snag", onlineEligible: true }
17
28
  ];
18
29
  var modesForGame = (gameId) => MODES.filter((m) => m.gameId === gameId);
19
30
  var ROOM_ALPHABET = "ACDEFGHJKMNPQRTUVWXY2346789";
@@ -103,15 +114,23 @@ var ClientMsg = z.discriminatedUnion("t", [
103
114
  // moderation
104
115
  // Report another player in the room (App Store 1.2 UGC — display names). Available in lobby or
105
116
  // game. The server records reporter/reported/room context; `reason` is an optional free-text note.
106
- z.object({ t: z.literal("report"), playerId: PlayerId, reason: z.string().max(500).optional() })
117
+ z.object({ t: z.literal("report"), playerId: PlayerId, reason: z.string().max(500).optional() }),
118
+ // Report the CURRENT question as bad (wrong answer / typo / offensive). The relay resolves WHICH
119
+ // question from its authoritative room state — the client never sees the question id (anti-cheat),
120
+ // so it only sends an optional `reason`. Only meaningful while a question is on screen.
121
+ z.object({ t: z.literal("reportQuestion"), reason: z.string().max(500).optional() })
107
122
  ]);
108
123
  export {
109
124
  ClientMsg,
125
+ GAMES,
110
126
  GAME_IDS,
111
127
  LIMITS,
128
+ MIN_SUPPORTED_PV,
112
129
  MODES,
113
130
  MODE_IDS,
114
131
  PROTOCOL_VERSION,
115
132
  ROOM_ALPHABET,
133
+ gameIdForPv,
134
+ gamesForPv,
116
135
  modesForGame
117
136
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thegeem/protocol",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
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",