@thegeem/protocol 0.1.40 → 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 +62 -10
- package/dist/index.d.cts +92 -16
- package/dist/index.d.ts +92 -16
- package/dist/index.js +53 -9
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -22,7 +22,9 @@ var index_exports = {};
|
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
CHARACTER_IDS: () => CHARACTER_IDS,
|
|
24
24
|
COMEBACK_BONUS_MIN_PV: () => COMEBACK_BONUS_MIN_PV,
|
|
25
|
+
COMEBACK_EFFECTS: () => COMEBACK_EFFECTS,
|
|
25
26
|
ClientMsg: () => ClientMsg,
|
|
27
|
+
EFFECT_MIN_PV: () => EFFECT_MIN_PV,
|
|
26
28
|
EMOTE_IDS: () => EMOTE_IDS,
|
|
27
29
|
GAMES: () => GAMES,
|
|
28
30
|
GAME_IDS: () => GAME_IDS,
|
|
@@ -33,15 +35,21 @@ __export(index_exports, {
|
|
|
33
35
|
PROTOCOL_VERSION: () => PROTOCOL_VERSION,
|
|
34
36
|
REJECT_REASONS: () => REJECT_REASONS,
|
|
35
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,
|
|
36
41
|
SUBMISSION_KINDS: () => SUBMISSION_KINDS,
|
|
37
42
|
SUBMISSION_STATUSES: () => SUBMISSION_STATUSES,
|
|
38
43
|
effectsForPv: () => effectsForPv,
|
|
39
44
|
gameIdForPv: () => gameIdForPv,
|
|
40
45
|
gamesForPv: () => gamesForPv,
|
|
41
46
|
hostActivityForPv: () => hostActivityForPv,
|
|
47
|
+
internalGameOf: () => internalGameOf,
|
|
42
48
|
modesForGame: () => modesForGame,
|
|
49
|
+
modesOf: () => modesOf,
|
|
43
50
|
normalizeWesternDigits: () => normalizeWesternDigits,
|
|
44
|
-
sanitizeName: () => sanitizeName
|
|
51
|
+
sanitizeName: () => sanitizeName,
|
|
52
|
+
wireShapeOf: () => wireShapeOf
|
|
45
53
|
});
|
|
46
54
|
module.exports = __toCommonJS(index_exports);
|
|
47
55
|
var import_zod = require("zod");
|
|
@@ -54,7 +62,7 @@ var LIMITS = {
|
|
|
54
62
|
* Clients batch pen points to stay under this — ~150ms batching lands at ~7/sec. */
|
|
55
63
|
STROKE_BATCH_PER_SEC: 20
|
|
56
64
|
};
|
|
57
|
-
var PROTOCOL_VERSION =
|
|
65
|
+
var PROTOCOL_VERSION = 8;
|
|
58
66
|
var MIN_SUPPORTED_PV = 1;
|
|
59
67
|
var GAME_IDS = ["seedha", "snag", "trivia", "dama", "ersimha", "sebaq"];
|
|
60
68
|
var GAMES = [
|
|
@@ -86,20 +94,55 @@ var GAMES = [
|
|
|
86
94
|
];
|
|
87
95
|
var hostActivityForPv = (activity, pv) => activity === "options" && (pv ?? MIN_SUPPORTED_PV) < 6 ? "configuring" : activity;
|
|
88
96
|
var COMEBACK_BONUS_MIN_PV = 7;
|
|
89
|
-
var
|
|
90
|
-
|
|
97
|
+
var EFFECT_MIN_PV = {
|
|
98
|
+
comebackOffer: COMEBACK_BONUS_MIN_PV,
|
|
99
|
+
comebackBonus: COMEBACK_BONUS_MIN_PV,
|
|
100
|
+
comebackDeclined: COMEBACK_BONUS_MIN_PV
|
|
101
|
+
};
|
|
102
|
+
var COMEBACK_EFFECTS = ["comebackOffer", "comebackBonus", "comebackDeclined"];
|
|
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
|
+
};
|
|
91
107
|
var gamesForPv = (pv) => GAMES.filter((g) => !g.deprecated && g.minPv <= pv);
|
|
92
108
|
var gameIdForPv = (gameId, pv) => {
|
|
93
109
|
const g = GAMES.find((x) => x.id === gameId);
|
|
94
110
|
return g && !g.deprecated && (pv ?? MIN_SUPPORTED_PV) >= g.minPv ? gameId : "trivia";
|
|
95
111
|
};
|
|
96
|
-
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
|
+
}
|
|
97
119
|
var MODES = [
|
|
98
120
|
{ id: "solo", gameId: "seedha", label: "Training", onlineEligible: false },
|
|
99
121
|
{ id: "oneDevice", gameId: "seedha", label: "One Device", onlineEligible: false },
|
|
100
122
|
{ id: "multiplayer", gameId: "seedha", label: "Multiplayer", onlineEligible: true },
|
|
101
|
-
|
|
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 }
|
|
102
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
|
+
}
|
|
103
146
|
var modesForGame = (gameId) => MODES.filter((m) => m.gameId === gameId);
|
|
104
147
|
var EMOTE_IDS = ["laugh", "wow", "think", "gg"];
|
|
105
148
|
var CHARACTER_IDS = ["m", "f"];
|
|
@@ -122,6 +165,7 @@ var HelpType = import_zod.z.enum([
|
|
|
122
165
|
var Pv = import_zod.z.number().int().min(1).max(1e6).optional();
|
|
123
166
|
var IdToken = import_zod.z.string().max(4096).optional();
|
|
124
167
|
var Platform = import_zod.z.enum(["web", "ios", "android", "tv"]).optional();
|
|
168
|
+
var Games = import_zod.z.array(import_zod.z.enum(GAME_IDS)).max(GAME_IDS.length).optional();
|
|
125
169
|
var AppVersion = import_zod.z.string().trim().max(24).regex(/^[0-9]+(\.[0-9]+){0,3}([-+][0-9A-Za-z.-]+)?$/, "bad app version").optional();
|
|
126
170
|
var Avatar = import_zod.z.object({
|
|
127
171
|
emoji: import_zod.z.string().min(1).max(16).transform((s) => s.replace(UNSAFE_CHARS, "")).refine((s) => s.length >= 1, "empty emoji"),
|
|
@@ -130,7 +174,7 @@ var Avatar = import_zod.z.object({
|
|
|
130
174
|
var Character = import_zod.z.enum(CHARACTER_IDS);
|
|
131
175
|
var ClientMsg = import_zod.z.discriminatedUnion("t", [
|
|
132
176
|
// room / lobby
|
|
133
|
-
import_zod.z.object({ t: import_zod.z.literal("createRoom"), name: DisplayName.optional(), avatar: Avatar.optional(), game: import_zod.z.enum(GAME_IDS).optional(), character: Character.optional(), pv: Pv, idToken: IdToken, platform: Platform, appVersion: AppVersion }),
|
|
177
|
+
import_zod.z.object({ t: import_zod.z.literal("createRoom"), name: DisplayName.optional(), avatar: Avatar.optional(), game: import_zod.z.enum(GAME_IDS).optional(), character: Character.optional(), pv: Pv, idToken: IdToken, platform: Platform, games: Games, appVersion: AppVersion }),
|
|
134
178
|
// Host sets/changes their display name AFTER createRoom (for anonymous one-tap
|
|
135
179
|
// hosts who started nameless). Host-only; the relay re-broadcasts the lobby.
|
|
136
180
|
// SUPERSEDED by setProfile (kept working for shipped builds).
|
|
@@ -144,8 +188,8 @@ var ClientMsg = import_zod.z.discriminatedUnion("t", [
|
|
|
144
188
|
// view carries names+avatars so it updates live; dama/ersimha views are id-keyed (clients
|
|
145
189
|
// map ids→names from the lobby roster), so a change there lands on the next lobby.
|
|
146
190
|
import_zod.z.object({ t: import_zod.z.literal("setProfile"), name: DisplayName.optional(), avatar: Avatar.optional(), character: Character.optional() }),
|
|
147
|
-
import_zod.z.object({ t: import_zod.z.literal("joinRoom"), code: RoomCode, name: DisplayName, avatar: Avatar.optional(), character: Character.optional(), pv: Pv, idToken: IdToken, platform: Platform, appVersion: AppVersion }),
|
|
148
|
-
import_zod.z.object({ t: import_zod.z.literal("resume"), code: RoomCode, token: import_zod.z.string().max(120), avatar: Avatar.optional(), character: Character.optional(), pv: Pv, idToken: IdToken, platform: Platform, appVersion: AppVersion }),
|
|
191
|
+
import_zod.z.object({ t: import_zod.z.literal("joinRoom"), code: RoomCode, name: DisplayName, avatar: Avatar.optional(), character: Character.optional(), pv: Pv, idToken: IdToken, platform: Platform, games: Games, appVersion: AppVersion }),
|
|
192
|
+
import_zod.z.object({ t: import_zod.z.literal("resume"), code: RoomCode, token: import_zod.z.string().max(120), avatar: Avatar.optional(), character: Character.optional(), pv: Pv, idToken: IdToken, platform: Platform, games: Games, appVersion: AppVersion }),
|
|
149
193
|
// `tally` (gs#46) = wins per PLAYER across every game this room has played tonight — «نتيجة الليلة».
|
|
150
194
|
// Keyed by playerId, NOT by team: team names are recycled each game, so a per-team count means nothing
|
|
151
195
|
// across a night. Survives playAgain. A One Device game credits nobody (its team slots hold no players).
|
|
@@ -361,7 +405,9 @@ var REJECT_REASONS = ["duplicate", "unclear_answer", "not_family", "inaccurate",
|
|
|
361
405
|
0 && (module.exports = {
|
|
362
406
|
CHARACTER_IDS,
|
|
363
407
|
COMEBACK_BONUS_MIN_PV,
|
|
408
|
+
COMEBACK_EFFECTS,
|
|
364
409
|
ClientMsg,
|
|
410
|
+
EFFECT_MIN_PV,
|
|
365
411
|
EMOTE_IDS,
|
|
366
412
|
GAMES,
|
|
367
413
|
GAME_IDS,
|
|
@@ -372,13 +418,19 @@ var REJECT_REASONS = ["duplicate", "unclear_answer", "not_family", "inaccurate",
|
|
|
372
418
|
PROTOCOL_VERSION,
|
|
373
419
|
REJECT_REASONS,
|
|
374
420
|
ROOM_ALPHABET,
|
|
421
|
+
SEEDHA_MODE_IDS,
|
|
422
|
+
SNAG_MODES_MIN_PV,
|
|
423
|
+
SNAG_MODE_IDS,
|
|
375
424
|
SUBMISSION_KINDS,
|
|
376
425
|
SUBMISSION_STATUSES,
|
|
377
426
|
effectsForPv,
|
|
378
427
|
gameIdForPv,
|
|
379
428
|
gamesForPv,
|
|
380
429
|
hostActivityForPv,
|
|
430
|
+
internalGameOf,
|
|
381
431
|
modesForGame,
|
|
432
|
+
modesOf,
|
|
382
433
|
normalizeWesternDigits,
|
|
383
|
-
sanitizeName
|
|
434
|
+
sanitizeName,
|
|
435
|
+
wireShapeOf
|
|
384
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
|
|
@@ -96,6 +96,33 @@ declare const hostActivityForPv: (activity: "options" | "configuring" | "idle",
|
|
|
96
96
|
* the honest degradation: the game stays correct, the animation is missing.
|
|
97
97
|
*/
|
|
98
98
|
declare const COMEBACK_BONUS_MIN_PV = 7;
|
|
99
|
+
/**
|
|
100
|
+
* The effects safe to send a client of version `pv`.
|
|
101
|
+
*
|
|
102
|
+
* ⚠️ WITHHOLDING IS THE ONLY OPTION FOR AN EFFECT, and it is why this is a filter rather than a
|
|
103
|
+
* mapper like `hostActivityForPv`. An unknown `hostActivity` string could be down-mapped to an older
|
|
104
|
+
* word that means nearly the same thing; an unknown effect VARIANT has no older equivalent, and
|
|
105
|
+
* inventing one (sending `scorePop` instead) would make the client animate a normal score change for
|
|
106
|
+
* an event that is not one.
|
|
107
|
+
*/
|
|
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. */
|
|
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. */
|
|
99
126
|
declare const effectsForPv: (effects: Effect[], pv: number | undefined) => Effect[];
|
|
100
127
|
declare const gamesForPv: (pv: number) => GameInfo[];
|
|
101
128
|
/** The gameId to SEND a client of version `pv`: new games need pv >= their minPv; older clients see 'trivia'. */
|
|
@@ -109,15 +136,53 @@ declare const gameIdForPv: (gameId: GameId, pv: number | undefined) => GameId;
|
|
|
109
136
|
* (only server-arbitrated, multi-device modes; single-device/solo are local-only — the host controls
|
|
110
137
|
* input, so the server can't trust the score).
|
|
111
138
|
*/
|
|
112
|
-
|
|
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];
|
|
113
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[];
|
|
114
156
|
interface ModeInfo {
|
|
115
157
|
id: ModeId;
|
|
116
158
|
gameId: GameId;
|
|
117
159
|
label: string;
|
|
118
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;
|
|
119
164
|
}
|
|
120
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
|
+
};
|
|
121
186
|
/** The modes available for a given game (for a client's mode picker). */
|
|
122
187
|
declare const modesForGame: (gameId: GameId) => ModeInfo[];
|
|
123
188
|
/**
|
|
@@ -176,6 +241,7 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
176
241
|
pv: z.ZodOptional<z.ZodNumber>;
|
|
177
242
|
idToken: z.ZodOptional<z.ZodString>;
|
|
178
243
|
platform: z.ZodOptional<z.ZodEnum<["web", "ios", "android", "tv"]>>;
|
|
244
|
+
games: z.ZodOptional<z.ZodArray<z.ZodEnum<["seedha", "snag", "trivia", "dama", "ersimha", "sebaq"]>, "many">>;
|
|
179
245
|
appVersion: z.ZodOptional<z.ZodString>;
|
|
180
246
|
}, "strip", z.ZodTypeAny, {
|
|
181
247
|
t: "createRoom";
|
|
@@ -189,6 +255,7 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
189
255
|
pv?: number | undefined;
|
|
190
256
|
idToken?: string | undefined;
|
|
191
257
|
platform?: "web" | "ios" | "android" | "tv" | undefined;
|
|
258
|
+
games?: ("seedha" | "snag" | "trivia" | "dama" | "ersimha" | "sebaq")[] | undefined;
|
|
192
259
|
appVersion?: string | undefined;
|
|
193
260
|
}, {
|
|
194
261
|
t: "createRoom";
|
|
@@ -202,6 +269,7 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
202
269
|
pv?: number | undefined;
|
|
203
270
|
idToken?: string | undefined;
|
|
204
271
|
platform?: "web" | "ios" | "android" | "tv" | undefined;
|
|
272
|
+
games?: ("seedha" | "snag" | "trivia" | "dama" | "ersimha" | "sebaq")[] | undefined;
|
|
205
273
|
appVersion?: string | undefined;
|
|
206
274
|
}>, z.ZodObject<{
|
|
207
275
|
t: z.ZodLiteral<"renameHost">;
|
|
@@ -260,10 +328,11 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
260
328
|
pv: z.ZodOptional<z.ZodNumber>;
|
|
261
329
|
idToken: z.ZodOptional<z.ZodString>;
|
|
262
330
|
platform: z.ZodOptional<z.ZodEnum<["web", "ios", "android", "tv"]>>;
|
|
331
|
+
games: z.ZodOptional<z.ZodArray<z.ZodEnum<["seedha", "snag", "trivia", "dama", "ersimha", "sebaq"]>, "many">>;
|
|
263
332
|
appVersion: z.ZodOptional<z.ZodString>;
|
|
264
333
|
}, "strip", z.ZodTypeAny, {
|
|
265
|
-
code: string;
|
|
266
334
|
t: "joinRoom";
|
|
335
|
+
code: string;
|
|
267
336
|
name: string;
|
|
268
337
|
avatar?: {
|
|
269
338
|
emoji: string;
|
|
@@ -273,10 +342,11 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
273
342
|
pv?: number | undefined;
|
|
274
343
|
idToken?: string | undefined;
|
|
275
344
|
platform?: "web" | "ios" | "android" | "tv" | undefined;
|
|
345
|
+
games?: ("seedha" | "snag" | "trivia" | "dama" | "ersimha" | "sebaq")[] | undefined;
|
|
276
346
|
appVersion?: string | undefined;
|
|
277
347
|
}, {
|
|
278
|
-
code: string;
|
|
279
348
|
t: "joinRoom";
|
|
349
|
+
code: string;
|
|
280
350
|
name: string;
|
|
281
351
|
avatar?: {
|
|
282
352
|
emoji: string;
|
|
@@ -286,6 +356,7 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
286
356
|
pv?: number | undefined;
|
|
287
357
|
idToken?: string | undefined;
|
|
288
358
|
platform?: "web" | "ios" | "android" | "tv" | undefined;
|
|
359
|
+
games?: ("seedha" | "snag" | "trivia" | "dama" | "ersimha" | "sebaq")[] | undefined;
|
|
289
360
|
appVersion?: string | undefined;
|
|
290
361
|
}>, z.ZodObject<{
|
|
291
362
|
t: z.ZodLiteral<"resume">;
|
|
@@ -305,10 +376,11 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
305
376
|
pv: z.ZodOptional<z.ZodNumber>;
|
|
306
377
|
idToken: z.ZodOptional<z.ZodString>;
|
|
307
378
|
platform: z.ZodOptional<z.ZodEnum<["web", "ios", "android", "tv"]>>;
|
|
379
|
+
games: z.ZodOptional<z.ZodArray<z.ZodEnum<["seedha", "snag", "trivia", "dama", "ersimha", "sebaq"]>, "many">>;
|
|
308
380
|
appVersion: z.ZodOptional<z.ZodString>;
|
|
309
381
|
}, "strip", z.ZodTypeAny, {
|
|
310
|
-
code: string;
|
|
311
382
|
t: "resume";
|
|
383
|
+
code: string;
|
|
312
384
|
token: string;
|
|
313
385
|
avatar?: {
|
|
314
386
|
emoji: string;
|
|
@@ -318,10 +390,11 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
318
390
|
pv?: number | undefined;
|
|
319
391
|
idToken?: string | undefined;
|
|
320
392
|
platform?: "web" | "ios" | "android" | "tv" | undefined;
|
|
393
|
+
games?: ("seedha" | "snag" | "trivia" | "dama" | "ersimha" | "sebaq")[] | undefined;
|
|
321
394
|
appVersion?: string | undefined;
|
|
322
395
|
}, {
|
|
323
|
-
code: string;
|
|
324
396
|
t: "resume";
|
|
397
|
+
code: string;
|
|
325
398
|
token: string;
|
|
326
399
|
avatar?: {
|
|
327
400
|
emoji: string;
|
|
@@ -331,6 +404,7 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
331
404
|
pv?: number | undefined;
|
|
332
405
|
idToken?: string | undefined;
|
|
333
406
|
platform?: "web" | "ios" | "android" | "tv" | undefined;
|
|
407
|
+
games?: ("seedha" | "snag" | "trivia" | "dama" | "ersimha" | "sebaq")[] | undefined;
|
|
334
408
|
appVersion?: string | undefined;
|
|
335
409
|
}>, z.ZodObject<{
|
|
336
410
|
t: z.ZodLiteral<"setPlaying">;
|
|
@@ -437,15 +511,15 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
437
511
|
}>, z.ZodObject<{
|
|
438
512
|
t: z.ZodLiteral<"setMode">;
|
|
439
513
|
game: z.ZodOptional<z.ZodEnum<["seedha", "snag", "trivia", "dama", "ersimha", "sebaq"]>>;
|
|
440
|
-
modeId: z.ZodOptional<z.ZodEnum<["solo", "oneDevice", "
|
|
514
|
+
modeId: z.ZodOptional<z.ZodEnum<["solo", "oneDevice", "multiplayer", "normal", "sebaq", "snag"]>>;
|
|
441
515
|
}, "strip", z.ZodTypeAny, {
|
|
442
516
|
t: "setMode";
|
|
443
517
|
game?: "seedha" | "snag" | "trivia" | "dama" | "ersimha" | "sebaq" | undefined;
|
|
444
|
-
modeId?: "snag" | "solo" | "oneDevice" | "multiplayer" | undefined;
|
|
518
|
+
modeId?: "snag" | "sebaq" | "solo" | "oneDevice" | "multiplayer" | "normal" | undefined;
|
|
445
519
|
}, {
|
|
446
520
|
t: "setMode";
|
|
447
521
|
game?: "seedha" | "snag" | "trivia" | "dama" | "ersimha" | "sebaq" | undefined;
|
|
448
|
-
modeId?: "snag" | "solo" | "oneDevice" | "multiplayer" | undefined;
|
|
522
|
+
modeId?: "snag" | "sebaq" | "solo" | "oneDevice" | "multiplayer" | "normal" | undefined;
|
|
449
523
|
}>, z.ZodObject<{
|
|
450
524
|
t: z.ZodLiteral<"hostActivity">;
|
|
451
525
|
activity: z.ZodEnum<["options", "configuring", "idle"]>;
|
|
@@ -469,7 +543,7 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
469
543
|
}, {
|
|
470
544
|
difficulty?: "easy" | "medium" | "hard" | undefined;
|
|
471
545
|
}>>;
|
|
472
|
-
modeId: z.ZodOptional<z.ZodEnum<["solo", "oneDevice", "
|
|
546
|
+
modeId: z.ZodOptional<z.ZodEnum<["solo", "oneDevice", "multiplayer", "normal", "sebaq", "snag"]>>;
|
|
473
547
|
buzzerMode: z.ZodOptional<z.ZodEnum<["off", "solo", "race", "controller"]>>;
|
|
474
548
|
noHost: z.ZodOptional<z.ZodBoolean>;
|
|
475
549
|
}, "strip", z.ZodTypeAny, {
|
|
@@ -477,7 +551,7 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
477
551
|
categoryIds: string[];
|
|
478
552
|
totalRounds: number;
|
|
479
553
|
game?: "seedha" | "snag" | "trivia" | "dama" | "ersimha" | "sebaq" | undefined;
|
|
480
|
-
modeId?: "snag" | "solo" | "oneDevice" | "multiplayer" | undefined;
|
|
554
|
+
modeId?: "snag" | "sebaq" | "solo" | "oneDevice" | "multiplayer" | "normal" | undefined;
|
|
481
555
|
hostPlays?: boolean | undefined;
|
|
482
556
|
players?: string[] | undefined;
|
|
483
557
|
bot?: {
|
|
@@ -490,7 +564,7 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
490
564
|
categoryIds: string[];
|
|
491
565
|
totalRounds: number;
|
|
492
566
|
game?: "seedha" | "snag" | "trivia" | "dama" | "ersimha" | "sebaq" | undefined;
|
|
493
|
-
modeId?: "snag" | "solo" | "oneDevice" | "multiplayer" | undefined;
|
|
567
|
+
modeId?: "snag" | "sebaq" | "solo" | "oneDevice" | "multiplayer" | "normal" | undefined;
|
|
494
568
|
hostPlays?: boolean | undefined;
|
|
495
569
|
players?: string[] | undefined;
|
|
496
570
|
bot?: {
|
|
@@ -704,8 +778,8 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
704
778
|
color: z.ZodString;
|
|
705
779
|
width: z.ZodNumber;
|
|
706
780
|
}, "strip", z.ZodTypeAny, {
|
|
707
|
-
color: string;
|
|
708
781
|
t: "strokeBatch";
|
|
782
|
+
color: string;
|
|
709
783
|
strokeId: string;
|
|
710
784
|
seq: number;
|
|
711
785
|
points: {
|
|
@@ -714,8 +788,8 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
714
788
|
}[];
|
|
715
789
|
width: number;
|
|
716
790
|
}, {
|
|
717
|
-
color: string;
|
|
718
791
|
t: "strokeBatch";
|
|
792
|
+
color: string;
|
|
719
793
|
strokeId: string;
|
|
720
794
|
seq: number;
|
|
721
795
|
points: {
|
|
@@ -874,7 +948,7 @@ interface PackView {
|
|
|
874
948
|
hasImage?: boolean;
|
|
875
949
|
}
|
|
876
950
|
type GameStatus = 'setup' | 'categorySelection' | 'inProgress' | 'completed';
|
|
877
|
-
type GameplayFlow = 'classic' | '
|
|
951
|
+
type GameplayFlow = 'classic' | 'noHost' | 'buzzer';
|
|
878
952
|
type BuzzerGameMode = 'off' | 'solo' | 'race' | 'controller';
|
|
879
953
|
/** The help/lifeline ids (same set as the inbound `HelpType` zod enum above). */
|
|
880
954
|
type HelpName = z.infer<typeof HelpType>;
|
|
@@ -890,6 +964,7 @@ interface TeamView {
|
|
|
890
964
|
helps: {
|
|
891
965
|
type: HelpName;
|
|
892
966
|
isUsed: boolean;
|
|
967
|
+
unusable?: true;
|
|
893
968
|
}[];
|
|
894
969
|
/** The team's players in rotation order (`connected` = that player's socket is online right now).
|
|
895
970
|
* Empty for host-run single-device teams. */
|
|
@@ -1229,6 +1304,7 @@ type ServerMsg = {
|
|
|
1229
1304
|
hostFrame?: string;
|
|
1230
1305
|
hostPlaying?: boolean;
|
|
1231
1306
|
hostPinned?: boolean;
|
|
1307
|
+
hostTeamIndex?: number | null;
|
|
1232
1308
|
players: LobbyPlayer[];
|
|
1233
1309
|
categories: CategoryView[];
|
|
1234
1310
|
packs?: PackView[];
|
|
@@ -1340,4 +1416,4 @@ interface MyQuestionRow {
|
|
|
1340
1416
|
};
|
|
1341
1417
|
}
|
|
1342
1418
|
|
|
1343
|
-
export { type AvatarView, type BuzzerGameMode, CHARACTER_IDS, COMEBACK_BONUS_MIN_PV, 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
|
|
@@ -96,6 +96,33 @@ declare const hostActivityForPv: (activity: "options" | "configuring" | "idle",
|
|
|
96
96
|
* the honest degradation: the game stays correct, the animation is missing.
|
|
97
97
|
*/
|
|
98
98
|
declare const COMEBACK_BONUS_MIN_PV = 7;
|
|
99
|
+
/**
|
|
100
|
+
* The effects safe to send a client of version `pv`.
|
|
101
|
+
*
|
|
102
|
+
* ⚠️ WITHHOLDING IS THE ONLY OPTION FOR AN EFFECT, and it is why this is a filter rather than a
|
|
103
|
+
* mapper like `hostActivityForPv`. An unknown `hostActivity` string could be down-mapped to an older
|
|
104
|
+
* word that means nearly the same thing; an unknown effect VARIANT has no older equivalent, and
|
|
105
|
+
* inventing one (sending `scorePop` instead) would make the client animate a normal score change for
|
|
106
|
+
* an event that is not one.
|
|
107
|
+
*/
|
|
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. */
|
|
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. */
|
|
99
126
|
declare const effectsForPv: (effects: Effect[], pv: number | undefined) => Effect[];
|
|
100
127
|
declare const gamesForPv: (pv: number) => GameInfo[];
|
|
101
128
|
/** The gameId to SEND a client of version `pv`: new games need pv >= their minPv; older clients see 'trivia'. */
|
|
@@ -109,15 +136,53 @@ declare const gameIdForPv: (gameId: GameId, pv: number | undefined) => GameId;
|
|
|
109
136
|
* (only server-arbitrated, multi-device modes; single-device/solo are local-only — the host controls
|
|
110
137
|
* input, so the server can't trust the score).
|
|
111
138
|
*/
|
|
112
|
-
|
|
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];
|
|
113
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[];
|
|
114
156
|
interface ModeInfo {
|
|
115
157
|
id: ModeId;
|
|
116
158
|
gameId: GameId;
|
|
117
159
|
label: string;
|
|
118
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;
|
|
119
164
|
}
|
|
120
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
|
+
};
|
|
121
186
|
/** The modes available for a given game (for a client's mode picker). */
|
|
122
187
|
declare const modesForGame: (gameId: GameId) => ModeInfo[];
|
|
123
188
|
/**
|
|
@@ -176,6 +241,7 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
176
241
|
pv: z.ZodOptional<z.ZodNumber>;
|
|
177
242
|
idToken: z.ZodOptional<z.ZodString>;
|
|
178
243
|
platform: z.ZodOptional<z.ZodEnum<["web", "ios", "android", "tv"]>>;
|
|
244
|
+
games: z.ZodOptional<z.ZodArray<z.ZodEnum<["seedha", "snag", "trivia", "dama", "ersimha", "sebaq"]>, "many">>;
|
|
179
245
|
appVersion: z.ZodOptional<z.ZodString>;
|
|
180
246
|
}, "strip", z.ZodTypeAny, {
|
|
181
247
|
t: "createRoom";
|
|
@@ -189,6 +255,7 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
189
255
|
pv?: number | undefined;
|
|
190
256
|
idToken?: string | undefined;
|
|
191
257
|
platform?: "web" | "ios" | "android" | "tv" | undefined;
|
|
258
|
+
games?: ("seedha" | "snag" | "trivia" | "dama" | "ersimha" | "sebaq")[] | undefined;
|
|
192
259
|
appVersion?: string | undefined;
|
|
193
260
|
}, {
|
|
194
261
|
t: "createRoom";
|
|
@@ -202,6 +269,7 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
202
269
|
pv?: number | undefined;
|
|
203
270
|
idToken?: string | undefined;
|
|
204
271
|
platform?: "web" | "ios" | "android" | "tv" | undefined;
|
|
272
|
+
games?: ("seedha" | "snag" | "trivia" | "dama" | "ersimha" | "sebaq")[] | undefined;
|
|
205
273
|
appVersion?: string | undefined;
|
|
206
274
|
}>, z.ZodObject<{
|
|
207
275
|
t: z.ZodLiteral<"renameHost">;
|
|
@@ -260,10 +328,11 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
260
328
|
pv: z.ZodOptional<z.ZodNumber>;
|
|
261
329
|
idToken: z.ZodOptional<z.ZodString>;
|
|
262
330
|
platform: z.ZodOptional<z.ZodEnum<["web", "ios", "android", "tv"]>>;
|
|
331
|
+
games: z.ZodOptional<z.ZodArray<z.ZodEnum<["seedha", "snag", "trivia", "dama", "ersimha", "sebaq"]>, "many">>;
|
|
263
332
|
appVersion: z.ZodOptional<z.ZodString>;
|
|
264
333
|
}, "strip", z.ZodTypeAny, {
|
|
265
|
-
code: string;
|
|
266
334
|
t: "joinRoom";
|
|
335
|
+
code: string;
|
|
267
336
|
name: string;
|
|
268
337
|
avatar?: {
|
|
269
338
|
emoji: string;
|
|
@@ -273,10 +342,11 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
273
342
|
pv?: number | undefined;
|
|
274
343
|
idToken?: string | undefined;
|
|
275
344
|
platform?: "web" | "ios" | "android" | "tv" | undefined;
|
|
345
|
+
games?: ("seedha" | "snag" | "trivia" | "dama" | "ersimha" | "sebaq")[] | undefined;
|
|
276
346
|
appVersion?: string | undefined;
|
|
277
347
|
}, {
|
|
278
|
-
code: string;
|
|
279
348
|
t: "joinRoom";
|
|
349
|
+
code: string;
|
|
280
350
|
name: string;
|
|
281
351
|
avatar?: {
|
|
282
352
|
emoji: string;
|
|
@@ -286,6 +356,7 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
286
356
|
pv?: number | undefined;
|
|
287
357
|
idToken?: string | undefined;
|
|
288
358
|
platform?: "web" | "ios" | "android" | "tv" | undefined;
|
|
359
|
+
games?: ("seedha" | "snag" | "trivia" | "dama" | "ersimha" | "sebaq")[] | undefined;
|
|
289
360
|
appVersion?: string | undefined;
|
|
290
361
|
}>, z.ZodObject<{
|
|
291
362
|
t: z.ZodLiteral<"resume">;
|
|
@@ -305,10 +376,11 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
305
376
|
pv: z.ZodOptional<z.ZodNumber>;
|
|
306
377
|
idToken: z.ZodOptional<z.ZodString>;
|
|
307
378
|
platform: z.ZodOptional<z.ZodEnum<["web", "ios", "android", "tv"]>>;
|
|
379
|
+
games: z.ZodOptional<z.ZodArray<z.ZodEnum<["seedha", "snag", "trivia", "dama", "ersimha", "sebaq"]>, "many">>;
|
|
308
380
|
appVersion: z.ZodOptional<z.ZodString>;
|
|
309
381
|
}, "strip", z.ZodTypeAny, {
|
|
310
|
-
code: string;
|
|
311
382
|
t: "resume";
|
|
383
|
+
code: string;
|
|
312
384
|
token: string;
|
|
313
385
|
avatar?: {
|
|
314
386
|
emoji: string;
|
|
@@ -318,10 +390,11 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
318
390
|
pv?: number | undefined;
|
|
319
391
|
idToken?: string | undefined;
|
|
320
392
|
platform?: "web" | "ios" | "android" | "tv" | undefined;
|
|
393
|
+
games?: ("seedha" | "snag" | "trivia" | "dama" | "ersimha" | "sebaq")[] | undefined;
|
|
321
394
|
appVersion?: string | undefined;
|
|
322
395
|
}, {
|
|
323
|
-
code: string;
|
|
324
396
|
t: "resume";
|
|
397
|
+
code: string;
|
|
325
398
|
token: string;
|
|
326
399
|
avatar?: {
|
|
327
400
|
emoji: string;
|
|
@@ -331,6 +404,7 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
331
404
|
pv?: number | undefined;
|
|
332
405
|
idToken?: string | undefined;
|
|
333
406
|
platform?: "web" | "ios" | "android" | "tv" | undefined;
|
|
407
|
+
games?: ("seedha" | "snag" | "trivia" | "dama" | "ersimha" | "sebaq")[] | undefined;
|
|
334
408
|
appVersion?: string | undefined;
|
|
335
409
|
}>, z.ZodObject<{
|
|
336
410
|
t: z.ZodLiteral<"setPlaying">;
|
|
@@ -437,15 +511,15 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
437
511
|
}>, z.ZodObject<{
|
|
438
512
|
t: z.ZodLiteral<"setMode">;
|
|
439
513
|
game: z.ZodOptional<z.ZodEnum<["seedha", "snag", "trivia", "dama", "ersimha", "sebaq"]>>;
|
|
440
|
-
modeId: z.ZodOptional<z.ZodEnum<["solo", "oneDevice", "
|
|
514
|
+
modeId: z.ZodOptional<z.ZodEnum<["solo", "oneDevice", "multiplayer", "normal", "sebaq", "snag"]>>;
|
|
441
515
|
}, "strip", z.ZodTypeAny, {
|
|
442
516
|
t: "setMode";
|
|
443
517
|
game?: "seedha" | "snag" | "trivia" | "dama" | "ersimha" | "sebaq" | undefined;
|
|
444
|
-
modeId?: "snag" | "solo" | "oneDevice" | "multiplayer" | undefined;
|
|
518
|
+
modeId?: "snag" | "sebaq" | "solo" | "oneDevice" | "multiplayer" | "normal" | undefined;
|
|
445
519
|
}, {
|
|
446
520
|
t: "setMode";
|
|
447
521
|
game?: "seedha" | "snag" | "trivia" | "dama" | "ersimha" | "sebaq" | undefined;
|
|
448
|
-
modeId?: "snag" | "solo" | "oneDevice" | "multiplayer" | undefined;
|
|
522
|
+
modeId?: "snag" | "sebaq" | "solo" | "oneDevice" | "multiplayer" | "normal" | undefined;
|
|
449
523
|
}>, z.ZodObject<{
|
|
450
524
|
t: z.ZodLiteral<"hostActivity">;
|
|
451
525
|
activity: z.ZodEnum<["options", "configuring", "idle"]>;
|
|
@@ -469,7 +543,7 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
469
543
|
}, {
|
|
470
544
|
difficulty?: "easy" | "medium" | "hard" | undefined;
|
|
471
545
|
}>>;
|
|
472
|
-
modeId: z.ZodOptional<z.ZodEnum<["solo", "oneDevice", "
|
|
546
|
+
modeId: z.ZodOptional<z.ZodEnum<["solo", "oneDevice", "multiplayer", "normal", "sebaq", "snag"]>>;
|
|
473
547
|
buzzerMode: z.ZodOptional<z.ZodEnum<["off", "solo", "race", "controller"]>>;
|
|
474
548
|
noHost: z.ZodOptional<z.ZodBoolean>;
|
|
475
549
|
}, "strip", z.ZodTypeAny, {
|
|
@@ -477,7 +551,7 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
477
551
|
categoryIds: string[];
|
|
478
552
|
totalRounds: number;
|
|
479
553
|
game?: "seedha" | "snag" | "trivia" | "dama" | "ersimha" | "sebaq" | undefined;
|
|
480
|
-
modeId?: "snag" | "solo" | "oneDevice" | "multiplayer" | undefined;
|
|
554
|
+
modeId?: "snag" | "sebaq" | "solo" | "oneDevice" | "multiplayer" | "normal" | undefined;
|
|
481
555
|
hostPlays?: boolean | undefined;
|
|
482
556
|
players?: string[] | undefined;
|
|
483
557
|
bot?: {
|
|
@@ -490,7 +564,7 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
490
564
|
categoryIds: string[];
|
|
491
565
|
totalRounds: number;
|
|
492
566
|
game?: "seedha" | "snag" | "trivia" | "dama" | "ersimha" | "sebaq" | undefined;
|
|
493
|
-
modeId?: "snag" | "solo" | "oneDevice" | "multiplayer" | undefined;
|
|
567
|
+
modeId?: "snag" | "sebaq" | "solo" | "oneDevice" | "multiplayer" | "normal" | undefined;
|
|
494
568
|
hostPlays?: boolean | undefined;
|
|
495
569
|
players?: string[] | undefined;
|
|
496
570
|
bot?: {
|
|
@@ -704,8 +778,8 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
704
778
|
color: z.ZodString;
|
|
705
779
|
width: z.ZodNumber;
|
|
706
780
|
}, "strip", z.ZodTypeAny, {
|
|
707
|
-
color: string;
|
|
708
781
|
t: "strokeBatch";
|
|
782
|
+
color: string;
|
|
709
783
|
strokeId: string;
|
|
710
784
|
seq: number;
|
|
711
785
|
points: {
|
|
@@ -714,8 +788,8 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
714
788
|
}[];
|
|
715
789
|
width: number;
|
|
716
790
|
}, {
|
|
717
|
-
color: string;
|
|
718
791
|
t: "strokeBatch";
|
|
792
|
+
color: string;
|
|
719
793
|
strokeId: string;
|
|
720
794
|
seq: number;
|
|
721
795
|
points: {
|
|
@@ -874,7 +948,7 @@ interface PackView {
|
|
|
874
948
|
hasImage?: boolean;
|
|
875
949
|
}
|
|
876
950
|
type GameStatus = 'setup' | 'categorySelection' | 'inProgress' | 'completed';
|
|
877
|
-
type GameplayFlow = 'classic' | '
|
|
951
|
+
type GameplayFlow = 'classic' | 'noHost' | 'buzzer';
|
|
878
952
|
type BuzzerGameMode = 'off' | 'solo' | 'race' | 'controller';
|
|
879
953
|
/** The help/lifeline ids (same set as the inbound `HelpType` zod enum above). */
|
|
880
954
|
type HelpName = z.infer<typeof HelpType>;
|
|
@@ -890,6 +964,7 @@ interface TeamView {
|
|
|
890
964
|
helps: {
|
|
891
965
|
type: HelpName;
|
|
892
966
|
isUsed: boolean;
|
|
967
|
+
unusable?: true;
|
|
893
968
|
}[];
|
|
894
969
|
/** The team's players in rotation order (`connected` = that player's socket is online right now).
|
|
895
970
|
* Empty for host-run single-device teams. */
|
|
@@ -1229,6 +1304,7 @@ type ServerMsg = {
|
|
|
1229
1304
|
hostFrame?: string;
|
|
1230
1305
|
hostPlaying?: boolean;
|
|
1231
1306
|
hostPinned?: boolean;
|
|
1307
|
+
hostTeamIndex?: number | null;
|
|
1232
1308
|
players: LobbyPlayer[];
|
|
1233
1309
|
categories: CategoryView[];
|
|
1234
1310
|
packs?: PackView[];
|
|
@@ -1340,4 +1416,4 @@ interface MyQuestionRow {
|
|
|
1340
1416
|
};
|
|
1341
1417
|
}
|
|
1342
1418
|
|
|
1343
|
-
export { type AvatarView, type BuzzerGameMode, CHARACTER_IDS, COMEBACK_BONUS_MIN_PV, 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,20 +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
|
|
45
|
-
|
|
44
|
+
var EFFECT_MIN_PV = {
|
|
45
|
+
comebackOffer: COMEBACK_BONUS_MIN_PV,
|
|
46
|
+
comebackBonus: COMEBACK_BONUS_MIN_PV,
|
|
47
|
+
comebackDeclined: COMEBACK_BONUS_MIN_PV
|
|
48
|
+
};
|
|
49
|
+
var COMEBACK_EFFECTS = ["comebackOffer", "comebackBonus", "comebackDeclined"];
|
|
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
|
+
};
|
|
46
54
|
var gamesForPv = (pv) => GAMES.filter((g) => !g.deprecated && g.minPv <= pv);
|
|
47
55
|
var gameIdForPv = (gameId, pv) => {
|
|
48
56
|
const g = GAMES.find((x) => x.id === gameId);
|
|
49
57
|
return g && !g.deprecated && (pv ?? MIN_SUPPORTED_PV) >= g.minPv ? gameId : "trivia";
|
|
50
58
|
};
|
|
51
|
-
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
|
+
}
|
|
52
66
|
var MODES = [
|
|
53
67
|
{ id: "solo", gameId: "seedha", label: "Training", onlineEligible: false },
|
|
54
68
|
{ id: "oneDevice", gameId: "seedha", label: "One Device", onlineEligible: false },
|
|
55
69
|
{ id: "multiplayer", gameId: "seedha", label: "Multiplayer", onlineEligible: true },
|
|
56
|
-
|
|
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 }
|
|
57
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
|
+
}
|
|
58
93
|
var modesForGame = (gameId) => MODES.filter((m) => m.gameId === gameId);
|
|
59
94
|
var EMOTE_IDS = ["laugh", "wow", "think", "gg"];
|
|
60
95
|
var CHARACTER_IDS = ["m", "f"];
|
|
@@ -77,6 +112,7 @@ var HelpType = z.enum([
|
|
|
77
112
|
var Pv = z.number().int().min(1).max(1e6).optional();
|
|
78
113
|
var IdToken = z.string().max(4096).optional();
|
|
79
114
|
var Platform = z.enum(["web", "ios", "android", "tv"]).optional();
|
|
115
|
+
var Games = z.array(z.enum(GAME_IDS)).max(GAME_IDS.length).optional();
|
|
80
116
|
var AppVersion = z.string().trim().max(24).regex(/^[0-9]+(\.[0-9]+){0,3}([-+][0-9A-Za-z.-]+)?$/, "bad app version").optional();
|
|
81
117
|
var Avatar = z.object({
|
|
82
118
|
emoji: z.string().min(1).max(16).transform((s) => s.replace(UNSAFE_CHARS, "")).refine((s) => s.length >= 1, "empty emoji"),
|
|
@@ -85,7 +121,7 @@ var Avatar = z.object({
|
|
|
85
121
|
var Character = z.enum(CHARACTER_IDS);
|
|
86
122
|
var ClientMsg = z.discriminatedUnion("t", [
|
|
87
123
|
// room / lobby
|
|
88
|
-
z.object({ t: z.literal("createRoom"), name: DisplayName.optional(), avatar: Avatar.optional(), game: z.enum(GAME_IDS).optional(), character: Character.optional(), pv: Pv, idToken: IdToken, platform: Platform, appVersion: AppVersion }),
|
|
124
|
+
z.object({ t: z.literal("createRoom"), name: DisplayName.optional(), avatar: Avatar.optional(), game: z.enum(GAME_IDS).optional(), character: Character.optional(), pv: Pv, idToken: IdToken, platform: Platform, games: Games, appVersion: AppVersion }),
|
|
89
125
|
// Host sets/changes their display name AFTER createRoom (for anonymous one-tap
|
|
90
126
|
// hosts who started nameless). Host-only; the relay re-broadcasts the lobby.
|
|
91
127
|
// SUPERSEDED by setProfile (kept working for shipped builds).
|
|
@@ -99,8 +135,8 @@ var ClientMsg = z.discriminatedUnion("t", [
|
|
|
99
135
|
// view carries names+avatars so it updates live; dama/ersimha views are id-keyed (clients
|
|
100
136
|
// map ids→names from the lobby roster), so a change there lands on the next lobby.
|
|
101
137
|
z.object({ t: z.literal("setProfile"), name: DisplayName.optional(), avatar: Avatar.optional(), character: Character.optional() }),
|
|
102
|
-
z.object({ t: z.literal("joinRoom"), code: RoomCode, name: DisplayName, avatar: Avatar.optional(), character: Character.optional(), pv: Pv, idToken: IdToken, platform: Platform, appVersion: AppVersion }),
|
|
103
|
-
z.object({ t: z.literal("resume"), code: RoomCode, token: z.string().max(120), avatar: Avatar.optional(), character: Character.optional(), pv: Pv, idToken: IdToken, platform: Platform, appVersion: AppVersion }),
|
|
138
|
+
z.object({ t: z.literal("joinRoom"), code: RoomCode, name: DisplayName, avatar: Avatar.optional(), character: Character.optional(), pv: Pv, idToken: IdToken, platform: Platform, games: Games, appVersion: AppVersion }),
|
|
139
|
+
z.object({ t: z.literal("resume"), code: RoomCode, token: z.string().max(120), avatar: Avatar.optional(), character: Character.optional(), pv: Pv, idToken: IdToken, platform: Platform, games: Games, appVersion: AppVersion }),
|
|
104
140
|
// `tally` (gs#46) = wins per PLAYER across every game this room has played tonight — «نتيجة الليلة».
|
|
105
141
|
// Keyed by playerId, NOT by team: team names are recycled each game, so a per-team count means nothing
|
|
106
142
|
// across a night. Survives playAgain. A One Device game credits nobody (its team slots hold no players).
|
|
@@ -315,7 +351,9 @@ var REJECT_REASONS = ["duplicate", "unclear_answer", "not_family", "inaccurate",
|
|
|
315
351
|
export {
|
|
316
352
|
CHARACTER_IDS,
|
|
317
353
|
COMEBACK_BONUS_MIN_PV,
|
|
354
|
+
COMEBACK_EFFECTS,
|
|
318
355
|
ClientMsg,
|
|
356
|
+
EFFECT_MIN_PV,
|
|
319
357
|
EMOTE_IDS,
|
|
320
358
|
GAMES,
|
|
321
359
|
GAME_IDS,
|
|
@@ -326,13 +364,19 @@ export {
|
|
|
326
364
|
PROTOCOL_VERSION,
|
|
327
365
|
REJECT_REASONS,
|
|
328
366
|
ROOM_ALPHABET,
|
|
367
|
+
SEEDHA_MODE_IDS,
|
|
368
|
+
SNAG_MODES_MIN_PV,
|
|
369
|
+
SNAG_MODE_IDS,
|
|
329
370
|
SUBMISSION_KINDS,
|
|
330
371
|
SUBMISSION_STATUSES,
|
|
331
372
|
effectsForPv,
|
|
332
373
|
gameIdForPv,
|
|
333
374
|
gamesForPv,
|
|
334
375
|
hostActivityForPv,
|
|
376
|
+
internalGameOf,
|
|
335
377
|
modesForGame,
|
|
378
|
+
modesOf,
|
|
336
379
|
normalizeWesternDigits,
|
|
337
|
-
sanitizeName
|
|
380
|
+
sanitizeName,
|
|
381
|
+
wireShapeOf
|
|
338
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",
|