@thegeem/protocol 0.1.41 → 0.1.42
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 +55 -7
- package/dist/index.d.cts +73 -19
- package/dist/index.d.ts +73 -19
- package/dist/index.js +47 -6
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -24,6 +24,7 @@ __export(index_exports, {
|
|
|
24
24
|
COMEBACK_BONUS_MIN_PV: () => COMEBACK_BONUS_MIN_PV,
|
|
25
25
|
COMEBACK_EFFECTS: () => COMEBACK_EFFECTS,
|
|
26
26
|
ClientMsg: () => ClientMsg,
|
|
27
|
+
EFFECT_MIN_PV: () => EFFECT_MIN_PV,
|
|
27
28
|
EMOTE_IDS: () => EMOTE_IDS,
|
|
28
29
|
GAMES: () => GAMES,
|
|
29
30
|
GAME_IDS: () => GAME_IDS,
|
|
@@ -34,15 +35,21 @@ __export(index_exports, {
|
|
|
34
35
|
PROTOCOL_VERSION: () => PROTOCOL_VERSION,
|
|
35
36
|
REJECT_REASONS: () => REJECT_REASONS,
|
|
36
37
|
ROOM_ALPHABET: () => ROOM_ALPHABET,
|
|
38
|
+
SEEDHA_MODE_IDS: () => SEEDHA_MODE_IDS,
|
|
39
|
+
SNAG_MODES_MIN_PV: () => SNAG_MODES_MIN_PV,
|
|
40
|
+
SNAG_MODE_IDS: () => SNAG_MODE_IDS,
|
|
37
41
|
SUBMISSION_KINDS: () => SUBMISSION_KINDS,
|
|
38
42
|
SUBMISSION_STATUSES: () => SUBMISSION_STATUSES,
|
|
39
43
|
effectsForPv: () => effectsForPv,
|
|
40
44
|
gameIdForPv: () => gameIdForPv,
|
|
41
45
|
gamesForPv: () => gamesForPv,
|
|
42
46
|
hostActivityForPv: () => hostActivityForPv,
|
|
47
|
+
internalGameOf: () => internalGameOf,
|
|
43
48
|
modesForGame: () => modesForGame,
|
|
49
|
+
modesOf: () => modesOf,
|
|
44
50
|
normalizeWesternDigits: () => normalizeWesternDigits,
|
|
45
|
-
sanitizeName: () => sanitizeName
|
|
51
|
+
sanitizeName: () => sanitizeName,
|
|
52
|
+
wireShapeOf: () => wireShapeOf
|
|
46
53
|
});
|
|
47
54
|
module.exports = __toCommonJS(index_exports);
|
|
48
55
|
var import_zod = require("zod");
|
|
@@ -55,7 +62,7 @@ var LIMITS = {
|
|
|
55
62
|
* Clients batch pen points to stay under this — ~150ms batching lands at ~7/sec. */
|
|
56
63
|
STROKE_BATCH_PER_SEC: 20
|
|
57
64
|
};
|
|
58
|
-
var PROTOCOL_VERSION =
|
|
65
|
+
var PROTOCOL_VERSION = 8;
|
|
59
66
|
var MIN_SUPPORTED_PV = 1;
|
|
60
67
|
var GAME_IDS = ["seedha", "snag", "trivia", "dama", "ersimha", "sebaq"];
|
|
61
68
|
var GAMES = [
|
|
@@ -87,21 +94,55 @@ var GAMES = [
|
|
|
87
94
|
];
|
|
88
95
|
var hostActivityForPv = (activity, pv) => activity === "options" && (pv ?? MIN_SUPPORTED_PV) < 6 ? "configuring" : activity;
|
|
89
96
|
var COMEBACK_BONUS_MIN_PV = 7;
|
|
97
|
+
var EFFECT_MIN_PV = {
|
|
98
|
+
comebackOffer: COMEBACK_BONUS_MIN_PV,
|
|
99
|
+
comebackBonus: COMEBACK_BONUS_MIN_PV,
|
|
100
|
+
comebackDeclined: COMEBACK_BONUS_MIN_PV
|
|
101
|
+
};
|
|
90
102
|
var COMEBACK_EFFECTS = ["comebackOffer", "comebackBonus", "comebackDeclined"];
|
|
91
|
-
var
|
|
92
|
-
|
|
103
|
+
var effectsForPv = (effects, pv) => {
|
|
104
|
+
const have = pv ?? MIN_SUPPORTED_PV;
|
|
105
|
+
return effects.filter((e) => (EFFECT_MIN_PV[e.t] ?? MIN_SUPPORTED_PV) <= have);
|
|
106
|
+
};
|
|
93
107
|
var gamesForPv = (pv) => GAMES.filter((g) => !g.deprecated && g.minPv <= pv);
|
|
94
108
|
var gameIdForPv = (gameId, pv) => {
|
|
95
109
|
const g = GAMES.find((x) => x.id === gameId);
|
|
96
110
|
return g && !g.deprecated && (pv ?? MIN_SUPPORTED_PV) >= g.minPv ? gameId : "trivia";
|
|
97
111
|
};
|
|
98
|
-
var
|
|
112
|
+
var SEEDHA_MODE_IDS = ["solo", "oneDevice", "multiplayer"];
|
|
113
|
+
var SNAG_MODE_IDS = ["normal", "sebaq"];
|
|
114
|
+
var MODE_IDS = [...SEEDHA_MODE_IDS, ...SNAG_MODE_IDS, "snag"];
|
|
115
|
+
var SNAG_MODES_MIN_PV = 8;
|
|
116
|
+
function modesOf(gameId) {
|
|
117
|
+
return MODES.filter((m) => m.gameId === gameId && !m.legacyAlias);
|
|
118
|
+
}
|
|
99
119
|
var MODES = [
|
|
100
120
|
{ id: "solo", gameId: "seedha", label: "Training", onlineEligible: false },
|
|
101
121
|
{ id: "oneDevice", gameId: "seedha", label: "One Device", onlineEligible: false },
|
|
102
122
|
{ id: "multiplayer", gameId: "seedha", label: "Multiplayer", onlineEligible: true },
|
|
103
|
-
|
|
123
|
+
// D-102: today's buzz Snag, by its real name. OFF the boards (owner 2026-08-29): the host awards 1-answer
|
|
124
|
+
// questions and the Comeback +100, so its score is not the relay's alone.
|
|
125
|
+
{ id: "normal", gameId: "snag", label: "Normal", onlineEligible: false },
|
|
126
|
+
{ id: "sebaq", gameId: "snag", label: "Sebaq", onlineEligible: true },
|
|
127
|
+
// D-102: the race — relay-scored end to end; THE Snag board
|
|
128
|
+
// NOT a mode — the legacy spelling of «snag / normal» (see MODE_IDS). Kept so old clients' `MODES.find` still maps it.
|
|
129
|
+
{ id: "snag", gameId: "snag", label: "Snag", onlineEligible: false, legacyAlias: true }
|
|
104
130
|
];
|
|
131
|
+
function internalGameOf(game, modeId) {
|
|
132
|
+
if (modeId === "sebaq" || game === "sebaq") return { gameId: "sebaq", modeId: void 0 };
|
|
133
|
+
if (modeId === "snag") return { gameId: "snag", modeId: "normal" };
|
|
134
|
+
if (modeId === "normal") return { gameId: "snag", modeId: "normal" };
|
|
135
|
+
return { gameId: game, modeId };
|
|
136
|
+
}
|
|
137
|
+
function wireShapeOf(gameId, modeId, pv) {
|
|
138
|
+
const v = pv ?? MIN_SUPPORTED_PV;
|
|
139
|
+
if (gameId === "sebaq") return v >= SNAG_MODES_MIN_PV ? { gameId: "snag", modeId: "sebaq" } : { gameId: gameIdForPv("sebaq", pv), modeId: null };
|
|
140
|
+
if (gameId === "snag") {
|
|
141
|
+
const m = modeId === "normal" || modeId === "snag" ? v >= SNAG_MODES_MIN_PV ? "normal" : "snag" : modeId;
|
|
142
|
+
return { gameId: gameIdForPv("snag", pv), modeId: m };
|
|
143
|
+
}
|
|
144
|
+
return { gameId: gameIdForPv(gameId, pv), modeId };
|
|
145
|
+
}
|
|
105
146
|
var modesForGame = (gameId) => MODES.filter((m) => m.gameId === gameId);
|
|
106
147
|
var EMOTE_IDS = ["laugh", "wow", "think", "gg"];
|
|
107
148
|
var CHARACTER_IDS = ["m", "f"];
|
|
@@ -366,6 +407,7 @@ var REJECT_REASONS = ["duplicate", "unclear_answer", "not_family", "inaccurate",
|
|
|
366
407
|
COMEBACK_BONUS_MIN_PV,
|
|
367
408
|
COMEBACK_EFFECTS,
|
|
368
409
|
ClientMsg,
|
|
410
|
+
EFFECT_MIN_PV,
|
|
369
411
|
EMOTE_IDS,
|
|
370
412
|
GAMES,
|
|
371
413
|
GAME_IDS,
|
|
@@ -376,13 +418,19 @@ var REJECT_REASONS = ["duplicate", "unclear_answer", "not_family", "inaccurate",
|
|
|
376
418
|
PROTOCOL_VERSION,
|
|
377
419
|
REJECT_REASONS,
|
|
378
420
|
ROOM_ALPHABET,
|
|
421
|
+
SEEDHA_MODE_IDS,
|
|
422
|
+
SNAG_MODES_MIN_PV,
|
|
423
|
+
SNAG_MODE_IDS,
|
|
379
424
|
SUBMISSION_KINDS,
|
|
380
425
|
SUBMISSION_STATUSES,
|
|
381
426
|
effectsForPv,
|
|
382
427
|
gameIdForPv,
|
|
383
428
|
gamesForPv,
|
|
384
429
|
hostActivityForPv,
|
|
430
|
+
internalGameOf,
|
|
385
431
|
modesForGame,
|
|
432
|
+
modesOf,
|
|
386
433
|
normalizeWesternDigits,
|
|
387
|
-
sanitizeName
|
|
434
|
+
sanitizeName,
|
|
435
|
+
wireShapeOf
|
|
388
436
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -36,7 +36,7 @@ declare const LIMITS: {
|
|
|
36
36
|
* `seedha`/`snag`). Raise the floor SLOWLY to retire ancient builds. Unversioned clients (no `pv`,
|
|
37
37
|
* e.g. the legacy web harness) are treated as the floor and never rejected.
|
|
38
38
|
*/
|
|
39
|
-
declare const PROTOCOL_VERSION =
|
|
39
|
+
declare const PROTOCOL_VERSION = 8;
|
|
40
40
|
declare const MIN_SUPPORTED_PV = 1;
|
|
41
41
|
/**
|
|
42
42
|
* Geem is a PLATFORM of GAMES (jackbox-style). The trivia board split (D-023) into TWO player-facing
|
|
@@ -105,10 +105,24 @@ declare const COMEBACK_BONUS_MIN_PV = 7;
|
|
|
105
105
|
* inventing one (sending `scorePop` instead) would make the client animate a normal score change for
|
|
106
106
|
* an event that is not one.
|
|
107
107
|
*/
|
|
108
|
-
/**
|
|
109
|
-
*
|
|
110
|
-
*
|
|
108
|
+
/**
|
|
109
|
+
* The pv each VERSION-GATED effect first became decodable at. Absent = decodable since the floor.
|
|
110
|
+
*
|
|
111
|
+
* ⚠️ ONE TABLE, THE SHAPE `GAMES` ALREADY USES (`minPv` per entry) — not one constant, one set and one
|
|
112
|
+
* `||` per feature. The comeback bonus was the first gated effect and arrived as three of each; the
|
|
113
|
+
* next would have added three more. ⚠️ The relay's `CLIENT_FX` spreads EVERY key of this table, whatever
|
|
114
|
+
* its pv — the gate withholds, the allow-list must never — so a gated effect cannot be forgotten in one
|
|
115
|
+
* of the two places. I re-typed that list once, listed one of three, and shipped a feature whose refusal
|
|
116
|
+
* rendered as silence on prod. A hand-list fails by OMISSION.
|
|
117
|
+
*/
|
|
118
|
+
declare const EFFECT_MIN_PV: Partial<Record<Effect['t'], number>>;
|
|
119
|
+
/** The comeback FAMILY, explicit. ⚠️ Not «every effect whose minPv is 7» — that derivation would silently
|
|
120
|
+
* drop a fourth comeback effect gated at pv8, and the test that checks this list would stay green because
|
|
121
|
+
* it derives from the same list (code-review C7). Family membership is a fact about the feature; the pv is
|
|
122
|
+
* a fact about the wire. Two tables, on purpose. */
|
|
111
123
|
declare const COMEBACK_EFFECTS: readonly ["comebackOffer", "comebackBonus", "comebackDeclined"];
|
|
124
|
+
/** The effects a client of version `pv` can decode. Withholding is the only option for an effect — an
|
|
125
|
+
* unknown variant has no older equivalent, and a strict Swift enum drops the WHOLE frame on one. */
|
|
112
126
|
declare const effectsForPv: (effects: Effect[], pv: number | undefined) => Effect[];
|
|
113
127
|
declare const gamesForPv: (pv: number) => GameInfo[];
|
|
114
128
|
/** The gameId to SEND a client of version `pv`: new games need pv >= their minPv; older clients see 'trivia'. */
|
|
@@ -122,15 +136,53 @@ declare const gameIdForPv: (gameId: GameId, pv: number | undefined) => GameId;
|
|
|
122
136
|
* (only server-arbitrated, multi-device modes; single-device/solo are local-only — the host controls
|
|
123
137
|
* input, so the server can't trust the score).
|
|
124
138
|
*/
|
|
125
|
-
|
|
139
|
+
/** صيدها's modes — one name each, the ONLY name anything outside the engine uses (guide §9 item 1, owner «go» 2026-08-29). */
|
|
140
|
+
declare const SEEDHA_MODE_IDS: readonly ["solo", "oneDevice", "multiplayer"];
|
|
141
|
+
/** سناق (من سبق لبق)'s modes — D-102, owner 2026-08-29: `normal` = buzz-to-claim on ONE shared question (what
|
|
142
|
+
* every shipped build calls «Snag»), `sebaq` = everyone races the SAME set on their own phone (D-099's rules).
|
|
143
|
+
* ⚠️ The word ديوانية is RESERVED for a future «Group Diwaniya» — it names nothing today. */
|
|
144
|
+
declare const SNAG_MODE_IDS: readonly ["normal", "sebaq"];
|
|
145
|
+
/** The wire union. `'snag'` is NOT a mode: it is the LEGACY SPELLING of «game snag, mode normal» that every
|
|
146
|
+
* shipped iOS and web build sends, and a frozen build must keep working — see internalGameOf(). The new ids
|
|
147
|
+
* reach only pv ≥ SNAG_MODES_MIN_PV clients (`lobby.modeId` is a STRICT Swift enum: an unknown value drops
|
|
148
|
+
* the whole frame) — wireShapeOf() down-maps for everyone else. */
|
|
149
|
+
declare const MODE_IDS: readonly ["solo", "oneDevice", "multiplayer", "normal", "sebaq", "snag"];
|
|
150
|
+
declare const SNAG_MODES_MIN_PV = 8;
|
|
151
|
+
type SnagModeId = (typeof SNAG_MODE_IDS)[number];
|
|
126
152
|
type ModeId = (typeof MODE_IDS)[number];
|
|
153
|
+
type SeedhaModeId = (typeof SEEDHA_MODE_IDS)[number];
|
|
154
|
+
/** The modes a game offers, from the registry. Games with no modes (دامة, ارسمها, سباق) → []. */
|
|
155
|
+
declare function modesOf(gameId: GameId): ModeInfo[];
|
|
127
156
|
interface ModeInfo {
|
|
128
157
|
id: ModeId;
|
|
129
158
|
gameId: GameId;
|
|
130
159
|
label: string;
|
|
131
160
|
onlineEligible: boolean;
|
|
161
|
+
/** A row kept only so `MODES.find(id)` keeps resolving for builds that send a game as a modeId. Not a mode:
|
|
162
|
+
* never render it in a mode picker — `modesOf()` filters it out. */
|
|
163
|
+
legacyAlias?: true;
|
|
132
164
|
}
|
|
133
165
|
declare const MODES: readonly ModeInfo[];
|
|
166
|
+
/**
|
|
167
|
+
* D-102 — the two directions between the PRODUCT shape (game Snag → mode normal | sebaq) and the ENGINE
|
|
168
|
+
* shape the relay runs (`gameId 'snag'` = the trivia engine in race flow; `gameId 'sebaq'` = its own
|
|
169
|
+
* engine/driver, D-026 tile-vs-engine). Both spellings stay on the wire forever: `game: 'sebaq'` and
|
|
170
|
+
* `modeId: 'snag'` are what every shipped build sends.
|
|
171
|
+
*
|
|
172
|
+
* INBOUND — `internalGameOf(game, modeId)`: what the client SAID → what the relay RUNS.
|
|
173
|
+
*/
|
|
174
|
+
declare function internalGameOf(game: GameId | undefined, modeId: ModeId | undefined): {
|
|
175
|
+
gameId: GameId | undefined;
|
|
176
|
+
modeId: ModeId | undefined;
|
|
177
|
+
};
|
|
178
|
+
/**
|
|
179
|
+
* OUTBOUND — `wireShapeOf(internalGameId, modeId, pv)`: what the relay RUNS → what THIS client can DECODE.
|
|
180
|
+
* pv ≥ 8 sees the product shape; below it, the pv7 shape it shipped with (`sebaq` as a game, `snag` as a mode).
|
|
181
|
+
*/
|
|
182
|
+
declare function wireShapeOf(gameId: GameId, modeId: ModeId | null, pv: number | undefined): {
|
|
183
|
+
gameId: GameId;
|
|
184
|
+
modeId: ModeId | null;
|
|
185
|
+
};
|
|
134
186
|
/** The modes available for a given game (for a client's mode picker). */
|
|
135
187
|
declare const modesForGame: (gameId: GameId) => ModeInfo[];
|
|
136
188
|
/**
|
|
@@ -279,8 +331,8 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
279
331
|
games: z.ZodOptional<z.ZodArray<z.ZodEnum<["seedha", "snag", "trivia", "dama", "ersimha", "sebaq"]>, "many">>;
|
|
280
332
|
appVersion: z.ZodOptional<z.ZodString>;
|
|
281
333
|
}, "strip", z.ZodTypeAny, {
|
|
282
|
-
code: string;
|
|
283
334
|
t: "joinRoom";
|
|
335
|
+
code: string;
|
|
284
336
|
name: string;
|
|
285
337
|
avatar?: {
|
|
286
338
|
emoji: string;
|
|
@@ -293,8 +345,8 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
293
345
|
games?: ("seedha" | "snag" | "trivia" | "dama" | "ersimha" | "sebaq")[] | undefined;
|
|
294
346
|
appVersion?: string | undefined;
|
|
295
347
|
}, {
|
|
296
|
-
code: string;
|
|
297
348
|
t: "joinRoom";
|
|
349
|
+
code: string;
|
|
298
350
|
name: string;
|
|
299
351
|
avatar?: {
|
|
300
352
|
emoji: string;
|
|
@@ -327,8 +379,8 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
327
379
|
games: z.ZodOptional<z.ZodArray<z.ZodEnum<["seedha", "snag", "trivia", "dama", "ersimha", "sebaq"]>, "many">>;
|
|
328
380
|
appVersion: z.ZodOptional<z.ZodString>;
|
|
329
381
|
}, "strip", z.ZodTypeAny, {
|
|
330
|
-
code: string;
|
|
331
382
|
t: "resume";
|
|
383
|
+
code: string;
|
|
332
384
|
token: string;
|
|
333
385
|
avatar?: {
|
|
334
386
|
emoji: string;
|
|
@@ -341,8 +393,8 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
341
393
|
games?: ("seedha" | "snag" | "trivia" | "dama" | "ersimha" | "sebaq")[] | undefined;
|
|
342
394
|
appVersion?: string | undefined;
|
|
343
395
|
}, {
|
|
344
|
-
code: string;
|
|
345
396
|
t: "resume";
|
|
397
|
+
code: string;
|
|
346
398
|
token: string;
|
|
347
399
|
avatar?: {
|
|
348
400
|
emoji: string;
|
|
@@ -459,15 +511,15 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
459
511
|
}>, z.ZodObject<{
|
|
460
512
|
t: z.ZodLiteral<"setMode">;
|
|
461
513
|
game: z.ZodOptional<z.ZodEnum<["seedha", "snag", "trivia", "dama", "ersimha", "sebaq"]>>;
|
|
462
|
-
modeId: z.ZodOptional<z.ZodEnum<["solo", "oneDevice", "
|
|
514
|
+
modeId: z.ZodOptional<z.ZodEnum<["solo", "oneDevice", "multiplayer", "normal", "sebaq", "snag"]>>;
|
|
463
515
|
}, "strip", z.ZodTypeAny, {
|
|
464
516
|
t: "setMode";
|
|
465
517
|
game?: "seedha" | "snag" | "trivia" | "dama" | "ersimha" | "sebaq" | undefined;
|
|
466
|
-
modeId?: "snag" | "solo" | "oneDevice" | "multiplayer" | undefined;
|
|
518
|
+
modeId?: "snag" | "sebaq" | "solo" | "oneDevice" | "multiplayer" | "normal" | undefined;
|
|
467
519
|
}, {
|
|
468
520
|
t: "setMode";
|
|
469
521
|
game?: "seedha" | "snag" | "trivia" | "dama" | "ersimha" | "sebaq" | undefined;
|
|
470
|
-
modeId?: "snag" | "solo" | "oneDevice" | "multiplayer" | undefined;
|
|
522
|
+
modeId?: "snag" | "sebaq" | "solo" | "oneDevice" | "multiplayer" | "normal" | undefined;
|
|
471
523
|
}>, z.ZodObject<{
|
|
472
524
|
t: z.ZodLiteral<"hostActivity">;
|
|
473
525
|
activity: z.ZodEnum<["options", "configuring", "idle"]>;
|
|
@@ -491,7 +543,7 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
491
543
|
}, {
|
|
492
544
|
difficulty?: "easy" | "medium" | "hard" | undefined;
|
|
493
545
|
}>>;
|
|
494
|
-
modeId: z.ZodOptional<z.ZodEnum<["solo", "oneDevice", "
|
|
546
|
+
modeId: z.ZodOptional<z.ZodEnum<["solo", "oneDevice", "multiplayer", "normal", "sebaq", "snag"]>>;
|
|
495
547
|
buzzerMode: z.ZodOptional<z.ZodEnum<["off", "solo", "race", "controller"]>>;
|
|
496
548
|
noHost: z.ZodOptional<z.ZodBoolean>;
|
|
497
549
|
}, "strip", z.ZodTypeAny, {
|
|
@@ -499,7 +551,7 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
499
551
|
categoryIds: string[];
|
|
500
552
|
totalRounds: number;
|
|
501
553
|
game?: "seedha" | "snag" | "trivia" | "dama" | "ersimha" | "sebaq" | undefined;
|
|
502
|
-
modeId?: "snag" | "solo" | "oneDevice" | "multiplayer" | undefined;
|
|
554
|
+
modeId?: "snag" | "sebaq" | "solo" | "oneDevice" | "multiplayer" | "normal" | undefined;
|
|
503
555
|
hostPlays?: boolean | undefined;
|
|
504
556
|
players?: string[] | undefined;
|
|
505
557
|
bot?: {
|
|
@@ -512,7 +564,7 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
512
564
|
categoryIds: string[];
|
|
513
565
|
totalRounds: number;
|
|
514
566
|
game?: "seedha" | "snag" | "trivia" | "dama" | "ersimha" | "sebaq" | undefined;
|
|
515
|
-
modeId?: "snag" | "solo" | "oneDevice" | "multiplayer" | undefined;
|
|
567
|
+
modeId?: "snag" | "sebaq" | "solo" | "oneDevice" | "multiplayer" | "normal" | undefined;
|
|
516
568
|
hostPlays?: boolean | undefined;
|
|
517
569
|
players?: string[] | undefined;
|
|
518
570
|
bot?: {
|
|
@@ -726,8 +778,8 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
726
778
|
color: z.ZodString;
|
|
727
779
|
width: z.ZodNumber;
|
|
728
780
|
}, "strip", z.ZodTypeAny, {
|
|
729
|
-
color: string;
|
|
730
781
|
t: "strokeBatch";
|
|
782
|
+
color: string;
|
|
731
783
|
strokeId: string;
|
|
732
784
|
seq: number;
|
|
733
785
|
points: {
|
|
@@ -736,8 +788,8 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
736
788
|
}[];
|
|
737
789
|
width: number;
|
|
738
790
|
}, {
|
|
739
|
-
color: string;
|
|
740
791
|
t: "strokeBatch";
|
|
792
|
+
color: string;
|
|
741
793
|
strokeId: string;
|
|
742
794
|
seq: number;
|
|
743
795
|
points: {
|
|
@@ -896,7 +948,7 @@ interface PackView {
|
|
|
896
948
|
hasImage?: boolean;
|
|
897
949
|
}
|
|
898
950
|
type GameStatus = 'setup' | 'categorySelection' | 'inProgress' | 'completed';
|
|
899
|
-
type GameplayFlow = 'classic' | '
|
|
951
|
+
type GameplayFlow = 'classic' | 'noHost' | 'buzzer';
|
|
900
952
|
type BuzzerGameMode = 'off' | 'solo' | 'race' | 'controller';
|
|
901
953
|
/** The help/lifeline ids (same set as the inbound `HelpType` zod enum above). */
|
|
902
954
|
type HelpName = z.infer<typeof HelpType>;
|
|
@@ -912,6 +964,7 @@ interface TeamView {
|
|
|
912
964
|
helps: {
|
|
913
965
|
type: HelpName;
|
|
914
966
|
isUsed: boolean;
|
|
967
|
+
unusable?: true;
|
|
915
968
|
}[];
|
|
916
969
|
/** The team's players in rotation order (`connected` = that player's socket is online right now).
|
|
917
970
|
* Empty for host-run single-device teams. */
|
|
@@ -1251,6 +1304,7 @@ type ServerMsg = {
|
|
|
1251
1304
|
hostFrame?: string;
|
|
1252
1305
|
hostPlaying?: boolean;
|
|
1253
1306
|
hostPinned?: boolean;
|
|
1307
|
+
hostTeamIndex?: number | null;
|
|
1254
1308
|
players: LobbyPlayer[];
|
|
1255
1309
|
categories: CategoryView[];
|
|
1256
1310
|
packs?: PackView[];
|
|
@@ -1362,4 +1416,4 @@ interface MyQuestionRow {
|
|
|
1362
1416
|
};
|
|
1363
1417
|
}
|
|
1364
1418
|
|
|
1365
|
-
export { type AvatarView, type BuzzerGameMode, CHARACTER_IDS, COMEBACK_BONUS_MIN_PV, COMEBACK_EFFECTS, type CategoryView, type CharacterId, ClientMsg, type DamaMoveView, type DamaPieceView, type DamaStateView, EMOTE_IDS, type Effect, type EmoteId, type ErsimhaStateView, 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, type MyQuestionRow, PROTOCOL_VERSION, type PackView, type QuestionView, REJECT_REASONS, ROOM_ALPHABET, type RejectReason, type RosterEntry, SUBMISSION_KINDS, SUBMISSION_STATUSES, type SebaqStandingRow, type SebaqStateView, type ServerMsg, type SoundName, type StrokePointView, type SubmissionKind, type SubmissionStatus, type TeamSlotView, type TeamView, type YouContext, effectsForPv, gameIdForPv, gamesForPv, hostActivityForPv, modesForGame, normalizeWesternDigits, sanitizeName };
|
|
1419
|
+
export { type AvatarView, type BuzzerGameMode, CHARACTER_IDS, COMEBACK_BONUS_MIN_PV, COMEBACK_EFFECTS, type CategoryView, type CharacterId, ClientMsg, type DamaMoveView, type DamaPieceView, type DamaStateView, EFFECT_MIN_PV, EMOTE_IDS, type Effect, type EmoteId, type ErsimhaStateView, 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, type MyQuestionRow, PROTOCOL_VERSION, type PackView, type QuestionView, REJECT_REASONS, ROOM_ALPHABET, type RejectReason, type RosterEntry, SEEDHA_MODE_IDS, SNAG_MODES_MIN_PV, SNAG_MODE_IDS, SUBMISSION_KINDS, SUBMISSION_STATUSES, type SebaqStandingRow, type SebaqStateView, type SeedhaModeId, type ServerMsg, type SnagModeId, type SoundName, type StrokePointView, type SubmissionKind, type SubmissionStatus, type TeamSlotView, type TeamView, type YouContext, effectsForPv, gameIdForPv, gamesForPv, hostActivityForPv, internalGameOf, modesForGame, modesOf, normalizeWesternDigits, sanitizeName, wireShapeOf };
|
package/dist/index.d.ts
CHANGED
|
@@ -36,7 +36,7 @@ declare const LIMITS: {
|
|
|
36
36
|
* `seedha`/`snag`). Raise the floor SLOWLY to retire ancient builds. Unversioned clients (no `pv`,
|
|
37
37
|
* e.g. the legacy web harness) are treated as the floor and never rejected.
|
|
38
38
|
*/
|
|
39
|
-
declare const PROTOCOL_VERSION =
|
|
39
|
+
declare const PROTOCOL_VERSION = 8;
|
|
40
40
|
declare const MIN_SUPPORTED_PV = 1;
|
|
41
41
|
/**
|
|
42
42
|
* Geem is a PLATFORM of GAMES (jackbox-style). The trivia board split (D-023) into TWO player-facing
|
|
@@ -105,10 +105,24 @@ declare const COMEBACK_BONUS_MIN_PV = 7;
|
|
|
105
105
|
* inventing one (sending `scorePop` instead) would make the client animate a normal score change for
|
|
106
106
|
* an event that is not one.
|
|
107
107
|
*/
|
|
108
|
-
/**
|
|
109
|
-
*
|
|
110
|
-
*
|
|
108
|
+
/**
|
|
109
|
+
* The pv each VERSION-GATED effect first became decodable at. Absent = decodable since the floor.
|
|
110
|
+
*
|
|
111
|
+
* ⚠️ ONE TABLE, THE SHAPE `GAMES` ALREADY USES (`minPv` per entry) — not one constant, one set and one
|
|
112
|
+
* `||` per feature. The comeback bonus was the first gated effect and arrived as three of each; the
|
|
113
|
+
* next would have added three more. ⚠️ The relay's `CLIENT_FX` spreads EVERY key of this table, whatever
|
|
114
|
+
* its pv — the gate withholds, the allow-list must never — so a gated effect cannot be forgotten in one
|
|
115
|
+
* of the two places. I re-typed that list once, listed one of three, and shipped a feature whose refusal
|
|
116
|
+
* rendered as silence on prod. A hand-list fails by OMISSION.
|
|
117
|
+
*/
|
|
118
|
+
declare const EFFECT_MIN_PV: Partial<Record<Effect['t'], number>>;
|
|
119
|
+
/** The comeback FAMILY, explicit. ⚠️ Not «every effect whose minPv is 7» — that derivation would silently
|
|
120
|
+
* drop a fourth comeback effect gated at pv8, and the test that checks this list would stay green because
|
|
121
|
+
* it derives from the same list (code-review C7). Family membership is a fact about the feature; the pv is
|
|
122
|
+
* a fact about the wire. Two tables, on purpose. */
|
|
111
123
|
declare const COMEBACK_EFFECTS: readonly ["comebackOffer", "comebackBonus", "comebackDeclined"];
|
|
124
|
+
/** The effects a client of version `pv` can decode. Withholding is the only option for an effect — an
|
|
125
|
+
* unknown variant has no older equivalent, and a strict Swift enum drops the WHOLE frame on one. */
|
|
112
126
|
declare const effectsForPv: (effects: Effect[], pv: number | undefined) => Effect[];
|
|
113
127
|
declare const gamesForPv: (pv: number) => GameInfo[];
|
|
114
128
|
/** The gameId to SEND a client of version `pv`: new games need pv >= their minPv; older clients see 'trivia'. */
|
|
@@ -122,15 +136,53 @@ declare const gameIdForPv: (gameId: GameId, pv: number | undefined) => GameId;
|
|
|
122
136
|
* (only server-arbitrated, multi-device modes; single-device/solo are local-only — the host controls
|
|
123
137
|
* input, so the server can't trust the score).
|
|
124
138
|
*/
|
|
125
|
-
|
|
139
|
+
/** صيدها's modes — one name each, the ONLY name anything outside the engine uses (guide §9 item 1, owner «go» 2026-08-29). */
|
|
140
|
+
declare const SEEDHA_MODE_IDS: readonly ["solo", "oneDevice", "multiplayer"];
|
|
141
|
+
/** سناق (من سبق لبق)'s modes — D-102, owner 2026-08-29: `normal` = buzz-to-claim on ONE shared question (what
|
|
142
|
+
* every shipped build calls «Snag»), `sebaq` = everyone races the SAME set on their own phone (D-099's rules).
|
|
143
|
+
* ⚠️ The word ديوانية is RESERVED for a future «Group Diwaniya» — it names nothing today. */
|
|
144
|
+
declare const SNAG_MODE_IDS: readonly ["normal", "sebaq"];
|
|
145
|
+
/** The wire union. `'snag'` is NOT a mode: it is the LEGACY SPELLING of «game snag, mode normal» that every
|
|
146
|
+
* shipped iOS and web build sends, and a frozen build must keep working — see internalGameOf(). The new ids
|
|
147
|
+
* reach only pv ≥ SNAG_MODES_MIN_PV clients (`lobby.modeId` is a STRICT Swift enum: an unknown value drops
|
|
148
|
+
* the whole frame) — wireShapeOf() down-maps for everyone else. */
|
|
149
|
+
declare const MODE_IDS: readonly ["solo", "oneDevice", "multiplayer", "normal", "sebaq", "snag"];
|
|
150
|
+
declare const SNAG_MODES_MIN_PV = 8;
|
|
151
|
+
type SnagModeId = (typeof SNAG_MODE_IDS)[number];
|
|
126
152
|
type ModeId = (typeof MODE_IDS)[number];
|
|
153
|
+
type SeedhaModeId = (typeof SEEDHA_MODE_IDS)[number];
|
|
154
|
+
/** The modes a game offers, from the registry. Games with no modes (دامة, ارسمها, سباق) → []. */
|
|
155
|
+
declare function modesOf(gameId: GameId): ModeInfo[];
|
|
127
156
|
interface ModeInfo {
|
|
128
157
|
id: ModeId;
|
|
129
158
|
gameId: GameId;
|
|
130
159
|
label: string;
|
|
131
160
|
onlineEligible: boolean;
|
|
161
|
+
/** A row kept only so `MODES.find(id)` keeps resolving for builds that send a game as a modeId. Not a mode:
|
|
162
|
+
* never render it in a mode picker — `modesOf()` filters it out. */
|
|
163
|
+
legacyAlias?: true;
|
|
132
164
|
}
|
|
133
165
|
declare const MODES: readonly ModeInfo[];
|
|
166
|
+
/**
|
|
167
|
+
* D-102 — the two directions between the PRODUCT shape (game Snag → mode normal | sebaq) and the ENGINE
|
|
168
|
+
* shape the relay runs (`gameId 'snag'` = the trivia engine in race flow; `gameId 'sebaq'` = its own
|
|
169
|
+
* engine/driver, D-026 tile-vs-engine). Both spellings stay on the wire forever: `game: 'sebaq'` and
|
|
170
|
+
* `modeId: 'snag'` are what every shipped build sends.
|
|
171
|
+
*
|
|
172
|
+
* INBOUND — `internalGameOf(game, modeId)`: what the client SAID → what the relay RUNS.
|
|
173
|
+
*/
|
|
174
|
+
declare function internalGameOf(game: GameId | undefined, modeId: ModeId | undefined): {
|
|
175
|
+
gameId: GameId | undefined;
|
|
176
|
+
modeId: ModeId | undefined;
|
|
177
|
+
};
|
|
178
|
+
/**
|
|
179
|
+
* OUTBOUND — `wireShapeOf(internalGameId, modeId, pv)`: what the relay RUNS → what THIS client can DECODE.
|
|
180
|
+
* pv ≥ 8 sees the product shape; below it, the pv7 shape it shipped with (`sebaq` as a game, `snag` as a mode).
|
|
181
|
+
*/
|
|
182
|
+
declare function wireShapeOf(gameId: GameId, modeId: ModeId | null, pv: number | undefined): {
|
|
183
|
+
gameId: GameId;
|
|
184
|
+
modeId: ModeId | null;
|
|
185
|
+
};
|
|
134
186
|
/** The modes available for a given game (for a client's mode picker). */
|
|
135
187
|
declare const modesForGame: (gameId: GameId) => ModeInfo[];
|
|
136
188
|
/**
|
|
@@ -279,8 +331,8 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
279
331
|
games: z.ZodOptional<z.ZodArray<z.ZodEnum<["seedha", "snag", "trivia", "dama", "ersimha", "sebaq"]>, "many">>;
|
|
280
332
|
appVersion: z.ZodOptional<z.ZodString>;
|
|
281
333
|
}, "strip", z.ZodTypeAny, {
|
|
282
|
-
code: string;
|
|
283
334
|
t: "joinRoom";
|
|
335
|
+
code: string;
|
|
284
336
|
name: string;
|
|
285
337
|
avatar?: {
|
|
286
338
|
emoji: string;
|
|
@@ -293,8 +345,8 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
293
345
|
games?: ("seedha" | "snag" | "trivia" | "dama" | "ersimha" | "sebaq")[] | undefined;
|
|
294
346
|
appVersion?: string | undefined;
|
|
295
347
|
}, {
|
|
296
|
-
code: string;
|
|
297
348
|
t: "joinRoom";
|
|
349
|
+
code: string;
|
|
298
350
|
name: string;
|
|
299
351
|
avatar?: {
|
|
300
352
|
emoji: string;
|
|
@@ -327,8 +379,8 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
327
379
|
games: z.ZodOptional<z.ZodArray<z.ZodEnum<["seedha", "snag", "trivia", "dama", "ersimha", "sebaq"]>, "many">>;
|
|
328
380
|
appVersion: z.ZodOptional<z.ZodString>;
|
|
329
381
|
}, "strip", z.ZodTypeAny, {
|
|
330
|
-
code: string;
|
|
331
382
|
t: "resume";
|
|
383
|
+
code: string;
|
|
332
384
|
token: string;
|
|
333
385
|
avatar?: {
|
|
334
386
|
emoji: string;
|
|
@@ -341,8 +393,8 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
341
393
|
games?: ("seedha" | "snag" | "trivia" | "dama" | "ersimha" | "sebaq")[] | undefined;
|
|
342
394
|
appVersion?: string | undefined;
|
|
343
395
|
}, {
|
|
344
|
-
code: string;
|
|
345
396
|
t: "resume";
|
|
397
|
+
code: string;
|
|
346
398
|
token: string;
|
|
347
399
|
avatar?: {
|
|
348
400
|
emoji: string;
|
|
@@ -459,15 +511,15 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
459
511
|
}>, z.ZodObject<{
|
|
460
512
|
t: z.ZodLiteral<"setMode">;
|
|
461
513
|
game: z.ZodOptional<z.ZodEnum<["seedha", "snag", "trivia", "dama", "ersimha", "sebaq"]>>;
|
|
462
|
-
modeId: z.ZodOptional<z.ZodEnum<["solo", "oneDevice", "
|
|
514
|
+
modeId: z.ZodOptional<z.ZodEnum<["solo", "oneDevice", "multiplayer", "normal", "sebaq", "snag"]>>;
|
|
463
515
|
}, "strip", z.ZodTypeAny, {
|
|
464
516
|
t: "setMode";
|
|
465
517
|
game?: "seedha" | "snag" | "trivia" | "dama" | "ersimha" | "sebaq" | undefined;
|
|
466
|
-
modeId?: "snag" | "solo" | "oneDevice" | "multiplayer" | undefined;
|
|
518
|
+
modeId?: "snag" | "sebaq" | "solo" | "oneDevice" | "multiplayer" | "normal" | undefined;
|
|
467
519
|
}, {
|
|
468
520
|
t: "setMode";
|
|
469
521
|
game?: "seedha" | "snag" | "trivia" | "dama" | "ersimha" | "sebaq" | undefined;
|
|
470
|
-
modeId?: "snag" | "solo" | "oneDevice" | "multiplayer" | undefined;
|
|
522
|
+
modeId?: "snag" | "sebaq" | "solo" | "oneDevice" | "multiplayer" | "normal" | undefined;
|
|
471
523
|
}>, z.ZodObject<{
|
|
472
524
|
t: z.ZodLiteral<"hostActivity">;
|
|
473
525
|
activity: z.ZodEnum<["options", "configuring", "idle"]>;
|
|
@@ -491,7 +543,7 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
491
543
|
}, {
|
|
492
544
|
difficulty?: "easy" | "medium" | "hard" | undefined;
|
|
493
545
|
}>>;
|
|
494
|
-
modeId: z.ZodOptional<z.ZodEnum<["solo", "oneDevice", "
|
|
546
|
+
modeId: z.ZodOptional<z.ZodEnum<["solo", "oneDevice", "multiplayer", "normal", "sebaq", "snag"]>>;
|
|
495
547
|
buzzerMode: z.ZodOptional<z.ZodEnum<["off", "solo", "race", "controller"]>>;
|
|
496
548
|
noHost: z.ZodOptional<z.ZodBoolean>;
|
|
497
549
|
}, "strip", z.ZodTypeAny, {
|
|
@@ -499,7 +551,7 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
499
551
|
categoryIds: string[];
|
|
500
552
|
totalRounds: number;
|
|
501
553
|
game?: "seedha" | "snag" | "trivia" | "dama" | "ersimha" | "sebaq" | undefined;
|
|
502
|
-
modeId?: "snag" | "solo" | "oneDevice" | "multiplayer" | undefined;
|
|
554
|
+
modeId?: "snag" | "sebaq" | "solo" | "oneDevice" | "multiplayer" | "normal" | undefined;
|
|
503
555
|
hostPlays?: boolean | undefined;
|
|
504
556
|
players?: string[] | undefined;
|
|
505
557
|
bot?: {
|
|
@@ -512,7 +564,7 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
512
564
|
categoryIds: string[];
|
|
513
565
|
totalRounds: number;
|
|
514
566
|
game?: "seedha" | "snag" | "trivia" | "dama" | "ersimha" | "sebaq" | undefined;
|
|
515
|
-
modeId?: "snag" | "solo" | "oneDevice" | "multiplayer" | undefined;
|
|
567
|
+
modeId?: "snag" | "sebaq" | "solo" | "oneDevice" | "multiplayer" | "normal" | undefined;
|
|
516
568
|
hostPlays?: boolean | undefined;
|
|
517
569
|
players?: string[] | undefined;
|
|
518
570
|
bot?: {
|
|
@@ -726,8 +778,8 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
726
778
|
color: z.ZodString;
|
|
727
779
|
width: z.ZodNumber;
|
|
728
780
|
}, "strip", z.ZodTypeAny, {
|
|
729
|
-
color: string;
|
|
730
781
|
t: "strokeBatch";
|
|
782
|
+
color: string;
|
|
731
783
|
strokeId: string;
|
|
732
784
|
seq: number;
|
|
733
785
|
points: {
|
|
@@ -736,8 +788,8 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
736
788
|
}[];
|
|
737
789
|
width: number;
|
|
738
790
|
}, {
|
|
739
|
-
color: string;
|
|
740
791
|
t: "strokeBatch";
|
|
792
|
+
color: string;
|
|
741
793
|
strokeId: string;
|
|
742
794
|
seq: number;
|
|
743
795
|
points: {
|
|
@@ -896,7 +948,7 @@ interface PackView {
|
|
|
896
948
|
hasImage?: boolean;
|
|
897
949
|
}
|
|
898
950
|
type GameStatus = 'setup' | 'categorySelection' | 'inProgress' | 'completed';
|
|
899
|
-
type GameplayFlow = 'classic' | '
|
|
951
|
+
type GameplayFlow = 'classic' | 'noHost' | 'buzzer';
|
|
900
952
|
type BuzzerGameMode = 'off' | 'solo' | 'race' | 'controller';
|
|
901
953
|
/** The help/lifeline ids (same set as the inbound `HelpType` zod enum above). */
|
|
902
954
|
type HelpName = z.infer<typeof HelpType>;
|
|
@@ -912,6 +964,7 @@ interface TeamView {
|
|
|
912
964
|
helps: {
|
|
913
965
|
type: HelpName;
|
|
914
966
|
isUsed: boolean;
|
|
967
|
+
unusable?: true;
|
|
915
968
|
}[];
|
|
916
969
|
/** The team's players in rotation order (`connected` = that player's socket is online right now).
|
|
917
970
|
* Empty for host-run single-device teams. */
|
|
@@ -1251,6 +1304,7 @@ type ServerMsg = {
|
|
|
1251
1304
|
hostFrame?: string;
|
|
1252
1305
|
hostPlaying?: boolean;
|
|
1253
1306
|
hostPinned?: boolean;
|
|
1307
|
+
hostTeamIndex?: number | null;
|
|
1254
1308
|
players: LobbyPlayer[];
|
|
1255
1309
|
categories: CategoryView[];
|
|
1256
1310
|
packs?: PackView[];
|
|
@@ -1362,4 +1416,4 @@ interface MyQuestionRow {
|
|
|
1362
1416
|
};
|
|
1363
1417
|
}
|
|
1364
1418
|
|
|
1365
|
-
export { type AvatarView, type BuzzerGameMode, CHARACTER_IDS, COMEBACK_BONUS_MIN_PV, COMEBACK_EFFECTS, type CategoryView, type CharacterId, ClientMsg, type DamaMoveView, type DamaPieceView, type DamaStateView, EMOTE_IDS, type Effect, type EmoteId, type ErsimhaStateView, 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, type MyQuestionRow, PROTOCOL_VERSION, type PackView, type QuestionView, REJECT_REASONS, ROOM_ALPHABET, type RejectReason, type RosterEntry, SUBMISSION_KINDS, SUBMISSION_STATUSES, type SebaqStandingRow, type SebaqStateView, type ServerMsg, type SoundName, type StrokePointView, type SubmissionKind, type SubmissionStatus, type TeamSlotView, type TeamView, type YouContext, effectsForPv, gameIdForPv, gamesForPv, hostActivityForPv, modesForGame, normalizeWesternDigits, sanitizeName };
|
|
1419
|
+
export { type AvatarView, type BuzzerGameMode, CHARACTER_IDS, COMEBACK_BONUS_MIN_PV, COMEBACK_EFFECTS, type CategoryView, type CharacterId, ClientMsg, type DamaMoveView, type DamaPieceView, type DamaStateView, EFFECT_MIN_PV, EMOTE_IDS, type Effect, type EmoteId, type ErsimhaStateView, 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, type MyQuestionRow, PROTOCOL_VERSION, type PackView, type QuestionView, REJECT_REASONS, ROOM_ALPHABET, type RejectReason, type RosterEntry, SEEDHA_MODE_IDS, SNAG_MODES_MIN_PV, SNAG_MODE_IDS, SUBMISSION_KINDS, SUBMISSION_STATUSES, type SebaqStandingRow, type SebaqStateView, type SeedhaModeId, type ServerMsg, type SnagModeId, type SoundName, type StrokePointView, type SubmissionKind, type SubmissionStatus, type TeamSlotView, type TeamView, type YouContext, effectsForPv, gameIdForPv, gamesForPv, hostActivityForPv, internalGameOf, modesForGame, modesOf, normalizeWesternDigits, sanitizeName, wireShapeOf };
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ var LIMITS = {
|
|
|
9
9
|
* Clients batch pen points to stay under this — ~150ms batching lands at ~7/sec. */
|
|
10
10
|
STROKE_BATCH_PER_SEC: 20
|
|
11
11
|
};
|
|
12
|
-
var PROTOCOL_VERSION =
|
|
12
|
+
var PROTOCOL_VERSION = 8;
|
|
13
13
|
var MIN_SUPPORTED_PV = 1;
|
|
14
14
|
var GAME_IDS = ["seedha", "snag", "trivia", "dama", "ersimha", "sebaq"];
|
|
15
15
|
var GAMES = [
|
|
@@ -41,21 +41,55 @@ var GAMES = [
|
|
|
41
41
|
];
|
|
42
42
|
var hostActivityForPv = (activity, pv) => activity === "options" && (pv ?? MIN_SUPPORTED_PV) < 6 ? "configuring" : activity;
|
|
43
43
|
var COMEBACK_BONUS_MIN_PV = 7;
|
|
44
|
+
var EFFECT_MIN_PV = {
|
|
45
|
+
comebackOffer: COMEBACK_BONUS_MIN_PV,
|
|
46
|
+
comebackBonus: COMEBACK_BONUS_MIN_PV,
|
|
47
|
+
comebackDeclined: COMEBACK_BONUS_MIN_PV
|
|
48
|
+
};
|
|
44
49
|
var COMEBACK_EFFECTS = ["comebackOffer", "comebackBonus", "comebackDeclined"];
|
|
45
|
-
var
|
|
46
|
-
|
|
50
|
+
var effectsForPv = (effects, pv) => {
|
|
51
|
+
const have = pv ?? MIN_SUPPORTED_PV;
|
|
52
|
+
return effects.filter((e) => (EFFECT_MIN_PV[e.t] ?? MIN_SUPPORTED_PV) <= have);
|
|
53
|
+
};
|
|
47
54
|
var gamesForPv = (pv) => GAMES.filter((g) => !g.deprecated && g.minPv <= pv);
|
|
48
55
|
var gameIdForPv = (gameId, pv) => {
|
|
49
56
|
const g = GAMES.find((x) => x.id === gameId);
|
|
50
57
|
return g && !g.deprecated && (pv ?? MIN_SUPPORTED_PV) >= g.minPv ? gameId : "trivia";
|
|
51
58
|
};
|
|
52
|
-
var
|
|
59
|
+
var SEEDHA_MODE_IDS = ["solo", "oneDevice", "multiplayer"];
|
|
60
|
+
var SNAG_MODE_IDS = ["normal", "sebaq"];
|
|
61
|
+
var MODE_IDS = [...SEEDHA_MODE_IDS, ...SNAG_MODE_IDS, "snag"];
|
|
62
|
+
var SNAG_MODES_MIN_PV = 8;
|
|
63
|
+
function modesOf(gameId) {
|
|
64
|
+
return MODES.filter((m) => m.gameId === gameId && !m.legacyAlias);
|
|
65
|
+
}
|
|
53
66
|
var MODES = [
|
|
54
67
|
{ id: "solo", gameId: "seedha", label: "Training", onlineEligible: false },
|
|
55
68
|
{ id: "oneDevice", gameId: "seedha", label: "One Device", onlineEligible: false },
|
|
56
69
|
{ id: "multiplayer", gameId: "seedha", label: "Multiplayer", onlineEligible: true },
|
|
57
|
-
|
|
70
|
+
// D-102: today's buzz Snag, by its real name. OFF the boards (owner 2026-08-29): the host awards 1-answer
|
|
71
|
+
// questions and the Comeback +100, so its score is not the relay's alone.
|
|
72
|
+
{ id: "normal", gameId: "snag", label: "Normal", onlineEligible: false },
|
|
73
|
+
{ id: "sebaq", gameId: "snag", label: "Sebaq", onlineEligible: true },
|
|
74
|
+
// D-102: the race — relay-scored end to end; THE Snag board
|
|
75
|
+
// NOT a mode — the legacy spelling of «snag / normal» (see MODE_IDS). Kept so old clients' `MODES.find` still maps it.
|
|
76
|
+
{ id: "snag", gameId: "snag", label: "Snag", onlineEligible: false, legacyAlias: true }
|
|
58
77
|
];
|
|
78
|
+
function internalGameOf(game, modeId) {
|
|
79
|
+
if (modeId === "sebaq" || game === "sebaq") return { gameId: "sebaq", modeId: void 0 };
|
|
80
|
+
if (modeId === "snag") return { gameId: "snag", modeId: "normal" };
|
|
81
|
+
if (modeId === "normal") return { gameId: "snag", modeId: "normal" };
|
|
82
|
+
return { gameId: game, modeId };
|
|
83
|
+
}
|
|
84
|
+
function wireShapeOf(gameId, modeId, pv) {
|
|
85
|
+
const v = pv ?? MIN_SUPPORTED_PV;
|
|
86
|
+
if (gameId === "sebaq") return v >= SNAG_MODES_MIN_PV ? { gameId: "snag", modeId: "sebaq" } : { gameId: gameIdForPv("sebaq", pv), modeId: null };
|
|
87
|
+
if (gameId === "snag") {
|
|
88
|
+
const m = modeId === "normal" || modeId === "snag" ? v >= SNAG_MODES_MIN_PV ? "normal" : "snag" : modeId;
|
|
89
|
+
return { gameId: gameIdForPv("snag", pv), modeId: m };
|
|
90
|
+
}
|
|
91
|
+
return { gameId: gameIdForPv(gameId, pv), modeId };
|
|
92
|
+
}
|
|
59
93
|
var modesForGame = (gameId) => MODES.filter((m) => m.gameId === gameId);
|
|
60
94
|
var EMOTE_IDS = ["laugh", "wow", "think", "gg"];
|
|
61
95
|
var CHARACTER_IDS = ["m", "f"];
|
|
@@ -319,6 +353,7 @@ export {
|
|
|
319
353
|
COMEBACK_BONUS_MIN_PV,
|
|
320
354
|
COMEBACK_EFFECTS,
|
|
321
355
|
ClientMsg,
|
|
356
|
+
EFFECT_MIN_PV,
|
|
322
357
|
EMOTE_IDS,
|
|
323
358
|
GAMES,
|
|
324
359
|
GAME_IDS,
|
|
@@ -329,13 +364,19 @@ export {
|
|
|
329
364
|
PROTOCOL_VERSION,
|
|
330
365
|
REJECT_REASONS,
|
|
331
366
|
ROOM_ALPHABET,
|
|
367
|
+
SEEDHA_MODE_IDS,
|
|
368
|
+
SNAG_MODES_MIN_PV,
|
|
369
|
+
SNAG_MODE_IDS,
|
|
332
370
|
SUBMISSION_KINDS,
|
|
333
371
|
SUBMISSION_STATUSES,
|
|
334
372
|
effectsForPv,
|
|
335
373
|
gameIdForPv,
|
|
336
374
|
gamesForPv,
|
|
337
375
|
hostActivityForPv,
|
|
376
|
+
internalGameOf,
|
|
338
377
|
modesForGame,
|
|
378
|
+
modesOf,
|
|
339
379
|
normalizeWesternDigits,
|
|
340
|
-
sanitizeName
|
|
380
|
+
sanitizeName,
|
|
381
|
+
wireShapeOf
|
|
341
382
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thegeem/protocol",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.42",
|
|
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",
|