@thegeem/protocol 0.1.18 → 0.1.20
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 +31 -5
- package/dist/index.d.cts +128 -6
- package/dist/index.d.ts +128 -6
- package/dist/index.js +31 -5
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -39,16 +39,21 @@ var LIMITS = {
|
|
|
39
39
|
MAX_NAME_LEN: 20,
|
|
40
40
|
ROOM_CODE_LEN: 4,
|
|
41
41
|
MAX_PLAYERS: 12,
|
|
42
|
-
MAX_MSG_PER_SEC: 8
|
|
42
|
+
MAX_MSG_PER_SEC: 8,
|
|
43
|
+
/** Ersimha strokes ride a SEPARATE per-socket lane (general msgs keep MAX_MSG_PER_SEC).
|
|
44
|
+
* Clients batch pen points to stay under this — ~150ms batching lands at ~7/sec. */
|
|
45
|
+
STROKE_BATCH_PER_SEC: 20
|
|
43
46
|
};
|
|
44
|
-
var PROTOCOL_VERSION =
|
|
47
|
+
var PROTOCOL_VERSION = 4;
|
|
45
48
|
var MIN_SUPPORTED_PV = 1;
|
|
46
|
-
var GAME_IDS = ["seedha", "snag", "trivia", "dama"];
|
|
49
|
+
var GAME_IDS = ["seedha", "snag", "trivia", "dama", "ersimha"];
|
|
47
50
|
var GAMES = [
|
|
48
51
|
{ id: "seedha", label: "Seedha", minPv: 2 },
|
|
49
52
|
{ id: "snag", label: "Snag", minPv: 2 },
|
|
50
53
|
{ id: "dama", label: "Dama", minPv: 3 },
|
|
51
54
|
// engine #2 (D-027) — offered only to pv≥3 clients
|
|
55
|
+
{ id: "ersimha", label: "Ersimha", minPv: 4 },
|
|
56
|
+
// engine #3 (D-030) draw-and-guess — offered only to pv≥4 clients
|
|
52
57
|
{ id: "trivia", label: "Trivia", minPv: 1, deprecated: true }
|
|
53
58
|
];
|
|
54
59
|
var gamesForPv = (pv) => GAMES.filter((g) => !g.deprecated && g.minPv <= pv);
|
|
@@ -87,7 +92,7 @@ var Avatar = import_zod.z.object({
|
|
|
87
92
|
});
|
|
88
93
|
var ClientMsg = import_zod.z.discriminatedUnion("t", [
|
|
89
94
|
// room / lobby
|
|
90
|
-
import_zod.z.object({ t: import_zod.z.literal("createRoom"), name: DisplayName.optional(), avatar: Avatar.optional(), pv: Pv, idToken: IdToken, platform: Platform }),
|
|
95
|
+
import_zod.z.object({ t: import_zod.z.literal("createRoom"), name: DisplayName.optional(), avatar: Avatar.optional(), game: import_zod.z.enum(GAME_IDS).optional(), pv: Pv, idToken: IdToken, platform: Platform }),
|
|
91
96
|
// Host sets/changes their display name AFTER createRoom (for anonymous one-tap
|
|
92
97
|
// hosts who started nameless). Host-only; the relay re-broadcasts the lobby.
|
|
93
98
|
import_zod.z.object({ t: import_zod.z.literal("renameHost"), name: DisplayName }),
|
|
@@ -168,7 +173,28 @@ var ClientMsg = import_zod.z.discriminatedUnion("t", [
|
|
|
168
173
|
path: import_zod.z.array(import_zod.z.object({ r: import_zod.z.number().int().min(0).max(7), c: import_zod.z.number().int().min(0).max(7) })).max(12).optional()
|
|
169
174
|
}),
|
|
170
175
|
// Resign the current Dama game (concede) → opponent wins.
|
|
171
|
-
import_zod.z.object({ t: import_zod.z.literal("damaResign") })
|
|
176
|
+
import_zod.z.object({ t: import_zod.z.literal("damaResign") }),
|
|
177
|
+
// ── Ersimha slice (gameId 'ersimha', engine #3 — D-030) ──
|
|
178
|
+
// Drawer streams stroke points as batches. Coordinates are NORMALIZED [0,1] floats (device-
|
|
179
|
+
// independent — every client and the TV scale to their own canvas). The relay is a pure
|
|
180
|
+
// PASSTHROUGH: strokes are validated, rate-limited, fanned out, and NEVER stored (ephemeral
|
|
181
|
+
// drawings, D-030). `seq` orders batches within one stroke; a new strokeId = pen-down.
|
|
182
|
+
import_zod.z.object({
|
|
183
|
+
t: import_zod.z.literal("strokeBatch"),
|
|
184
|
+
strokeId: import_zod.z.string().min(1).max(40),
|
|
185
|
+
seq: import_zod.z.number().int().min(0).max(1e4),
|
|
186
|
+
points: import_zod.z.array(import_zod.z.object({ x: import_zod.z.number().min(0).max(1), y: import_zod.z.number().min(0).max(1) })).min(1).max(128),
|
|
187
|
+
color: import_zod.z.string().regex(/^#[0-9a-fA-F]{6}$/),
|
|
188
|
+
width: import_zod.z.number().min(1).max(64)
|
|
189
|
+
}),
|
|
190
|
+
// Drawer wipes the canvas (relayed to everyone).
|
|
191
|
+
import_zod.z.object({ t: import_zod.z.literal("clearCanvas") }),
|
|
192
|
+
// Drawer removes one stroke — the given id, or their latest when omitted (relayed).
|
|
193
|
+
import_zod.z.object({ t: import_zod.z.literal("undoStroke"), strokeId: import_zod.z.string().min(1).max(40).optional() }),
|
|
194
|
+
// Drawer rules the CURRENT buzzer's spoken guess صح/خطأ (guessing is out loud — no text on the wire).
|
|
195
|
+
import_zod.z.object({ t: import_zod.z.literal("judgeGuess"), correct: import_zod.z.boolean() }),
|
|
196
|
+
// Drawer passes on the served word (capped per draw-turn; relay serves a fresh one).
|
|
197
|
+
import_zod.z.object({ t: import_zod.z.literal("skipWord") })
|
|
172
198
|
]);
|
|
173
199
|
// Annotate the CommonJS export names for ESM import in node:
|
|
174
200
|
0 && (module.exports = {
|
package/dist/index.d.cts
CHANGED
|
@@ -14,6 +14,9 @@ declare const LIMITS: {
|
|
|
14
14
|
readonly ROOM_CODE_LEN: 4;
|
|
15
15
|
readonly MAX_PLAYERS: 12;
|
|
16
16
|
readonly MAX_MSG_PER_SEC: 8;
|
|
17
|
+
/** Ersimha strokes ride a SEPARATE per-socket lane (general msgs keep MAX_MSG_PER_SEC).
|
|
18
|
+
* Clients batch pen points to stay under this — ~150ms batching lands at ~7/sec. */
|
|
19
|
+
readonly STROKE_BATCH_PER_SEC: 20;
|
|
17
20
|
};
|
|
18
21
|
/**
|
|
19
22
|
* Wire-format generation. **Bump ONLY on a breaking change** to ClientMsg /
|
|
@@ -33,7 +36,7 @@ declare const LIMITS: {
|
|
|
33
36
|
* `seedha`/`snag`). Raise the floor SLOWLY to retire ancient builds. Unversioned clients (no `pv`,
|
|
34
37
|
* e.g. the legacy web harness) are treated as the floor and never rejected.
|
|
35
38
|
*/
|
|
36
|
-
declare const PROTOCOL_VERSION =
|
|
39
|
+
declare const PROTOCOL_VERSION = 4;
|
|
37
40
|
declare const MIN_SUPPORTED_PV = 1;
|
|
38
41
|
/**
|
|
39
42
|
* Geem is a PLATFORM of GAMES (jackbox-style). The trivia board split (D-023) into TWO player-facing
|
|
@@ -42,7 +45,7 @@ declare const MIN_SUPPORTED_PV = 1;
|
|
|
42
45
|
* pre-pv2 clients (they never saw seedha/snag), and what old clients send on startGame. Every room
|
|
43
46
|
* runs one game, identified by `gameId`.
|
|
44
47
|
*/
|
|
45
|
-
declare const GAME_IDS: readonly ["seedha", "snag", "trivia", "dama"];
|
|
48
|
+
declare const GAME_IDS: readonly ["seedha", "snag", "trivia", "dama", "ersimha"];
|
|
46
49
|
type GameId = (typeof GAME_IDS)[number];
|
|
47
50
|
/**
|
|
48
51
|
* The GAME REGISTRY — player-facing games + the version gating (D-024/D-025). A client renders a tile
|
|
@@ -96,6 +99,7 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
96
99
|
emoji: string;
|
|
97
100
|
color: string;
|
|
98
101
|
}>>;
|
|
102
|
+
game: z.ZodOptional<z.ZodEnum<["seedha", "snag", "trivia", "dama", "ersimha"]>>;
|
|
99
103
|
pv: z.ZodOptional<z.ZodNumber>;
|
|
100
104
|
idToken: z.ZodOptional<z.ZodString>;
|
|
101
105
|
platform: z.ZodOptional<z.ZodEnum<["web", "ios", "android"]>>;
|
|
@@ -106,6 +110,7 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
106
110
|
emoji: string;
|
|
107
111
|
color: string;
|
|
108
112
|
} | undefined;
|
|
113
|
+
game?: "seedha" | "snag" | "trivia" | "dama" | "ersimha" | undefined;
|
|
109
114
|
pv?: number | undefined;
|
|
110
115
|
idToken?: string | undefined;
|
|
111
116
|
platform?: "web" | "ios" | "android" | undefined;
|
|
@@ -116,6 +121,7 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
116
121
|
emoji: string;
|
|
117
122
|
color: string;
|
|
118
123
|
} | undefined;
|
|
124
|
+
game?: "seedha" | "snag" | "trivia" | "dama" | "ersimha" | undefined;
|
|
119
125
|
pv?: number | undefined;
|
|
120
126
|
idToken?: string | undefined;
|
|
121
127
|
platform?: "web" | "ios" | "android" | undefined;
|
|
@@ -286,7 +292,7 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
286
292
|
activity: "configuring" | "idle";
|
|
287
293
|
}>, z.ZodObject<{
|
|
288
294
|
t: z.ZodLiteral<"startGame">;
|
|
289
|
-
game: z.ZodOptional<z.ZodEnum<["seedha", "snag", "trivia", "dama"]>>;
|
|
295
|
+
game: z.ZodOptional<z.ZodEnum<["seedha", "snag", "trivia", "dama", "ersimha"]>>;
|
|
290
296
|
totalRounds: z.ZodNumber;
|
|
291
297
|
categoryIds: z.ZodArray<z.ZodString, "many">;
|
|
292
298
|
modeId: z.ZodOptional<z.ZodEnum<["solo", "oneDevice", "snag", "multiplayer"]>>;
|
|
@@ -296,16 +302,16 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
296
302
|
t: "startGame";
|
|
297
303
|
categoryIds: string[];
|
|
298
304
|
totalRounds: number;
|
|
305
|
+
game?: "seedha" | "snag" | "trivia" | "dama" | "ersimha" | undefined;
|
|
299
306
|
modeId?: "snag" | "solo" | "oneDevice" | "multiplayer" | undefined;
|
|
300
|
-
game?: "seedha" | "snag" | "trivia" | "dama" | undefined;
|
|
301
307
|
buzzerMode?: "solo" | "off" | "race" | "controller" | undefined;
|
|
302
308
|
noHost?: boolean | undefined;
|
|
303
309
|
}, {
|
|
304
310
|
t: "startGame";
|
|
305
311
|
categoryIds: string[];
|
|
306
312
|
totalRounds: number;
|
|
313
|
+
game?: "seedha" | "snag" | "trivia" | "dama" | "ersimha" | undefined;
|
|
307
314
|
modeId?: "snag" | "solo" | "oneDevice" | "multiplayer" | undefined;
|
|
308
|
-
game?: "seedha" | "snag" | "trivia" | "dama" | undefined;
|
|
309
315
|
buzzerMode?: "solo" | "off" | "race" | "controller" | undefined;
|
|
310
316
|
noHost?: boolean | undefined;
|
|
311
317
|
}>, z.ZodObject<{
|
|
@@ -485,6 +491,72 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
485
491
|
t: "damaResign";
|
|
486
492
|
}, {
|
|
487
493
|
t: "damaResign";
|
|
494
|
+
}>, z.ZodObject<{
|
|
495
|
+
t: z.ZodLiteral<"strokeBatch">;
|
|
496
|
+
strokeId: z.ZodString;
|
|
497
|
+
seq: z.ZodNumber;
|
|
498
|
+
points: z.ZodArray<z.ZodObject<{
|
|
499
|
+
x: z.ZodNumber;
|
|
500
|
+
y: z.ZodNumber;
|
|
501
|
+
}, "strip", z.ZodTypeAny, {
|
|
502
|
+
x: number;
|
|
503
|
+
y: number;
|
|
504
|
+
}, {
|
|
505
|
+
x: number;
|
|
506
|
+
y: number;
|
|
507
|
+
}>, "many">;
|
|
508
|
+
color: z.ZodString;
|
|
509
|
+
width: z.ZodNumber;
|
|
510
|
+
}, "strip", z.ZodTypeAny, {
|
|
511
|
+
color: string;
|
|
512
|
+
t: "strokeBatch";
|
|
513
|
+
strokeId: string;
|
|
514
|
+
seq: number;
|
|
515
|
+
points: {
|
|
516
|
+
x: number;
|
|
517
|
+
y: number;
|
|
518
|
+
}[];
|
|
519
|
+
width: number;
|
|
520
|
+
}, {
|
|
521
|
+
color: string;
|
|
522
|
+
t: "strokeBatch";
|
|
523
|
+
strokeId: string;
|
|
524
|
+
seq: number;
|
|
525
|
+
points: {
|
|
526
|
+
x: number;
|
|
527
|
+
y: number;
|
|
528
|
+
}[];
|
|
529
|
+
width: number;
|
|
530
|
+
}>, z.ZodObject<{
|
|
531
|
+
t: z.ZodLiteral<"clearCanvas">;
|
|
532
|
+
}, "strip", z.ZodTypeAny, {
|
|
533
|
+
t: "clearCanvas";
|
|
534
|
+
}, {
|
|
535
|
+
t: "clearCanvas";
|
|
536
|
+
}>, z.ZodObject<{
|
|
537
|
+
t: z.ZodLiteral<"undoStroke">;
|
|
538
|
+
strokeId: z.ZodOptional<z.ZodString>;
|
|
539
|
+
}, "strip", z.ZodTypeAny, {
|
|
540
|
+
t: "undoStroke";
|
|
541
|
+
strokeId?: string | undefined;
|
|
542
|
+
}, {
|
|
543
|
+
t: "undoStroke";
|
|
544
|
+
strokeId?: string | undefined;
|
|
545
|
+
}>, z.ZodObject<{
|
|
546
|
+
t: z.ZodLiteral<"judgeGuess">;
|
|
547
|
+
correct: z.ZodBoolean;
|
|
548
|
+
}, "strip", z.ZodTypeAny, {
|
|
549
|
+
t: "judgeGuess";
|
|
550
|
+
correct: boolean;
|
|
551
|
+
}, {
|
|
552
|
+
t: "judgeGuess";
|
|
553
|
+
correct: boolean;
|
|
554
|
+
}>, z.ZodObject<{
|
|
555
|
+
t: z.ZodLiteral<"skipWord">;
|
|
556
|
+
}, "strip", z.ZodTypeAny, {
|
|
557
|
+
t: "skipWord";
|
|
558
|
+
}, {
|
|
559
|
+
t: "skipWord";
|
|
488
560
|
}>]>;
|
|
489
561
|
type ClientMsg = z.infer<typeof ClientMsg>;
|
|
490
562
|
/** A player's privacy-safe avatar (emoji + circle color) for in-room display. */
|
|
@@ -670,6 +742,33 @@ interface DamaStateView {
|
|
|
670
742
|
legalMoves: DamaMoveView[];
|
|
671
743
|
moveCount: number;
|
|
672
744
|
}
|
|
745
|
+
/**
|
|
746
|
+
* The authoritative Ersimha round state. Anti-cheat boundary (D-030): `word` is set ONLY in the
|
|
747
|
+
* payload sent to the DRAWER's connection — guessers and the TV get `maskedWord` (per-segment
|
|
748
|
+
* letter counts, «سيارة إسعاف» → [5,5]). Names/avatars come from the lobby roster (map by id),
|
|
749
|
+
* same as Dama. Strokes are NOT in the state — they stream as `strokeBatch` passthroughs and a
|
|
750
|
+
* late joiner simply starts from a blank canvas (ephemeral by design).
|
|
751
|
+
*/
|
|
752
|
+
interface ErsimhaStateView {
|
|
753
|
+
phase: 'choosing' | 'drawing' | 'judging' | 'roundEnd' | 'completed';
|
|
754
|
+
drawerId: string;
|
|
755
|
+
judgedId: string | null;
|
|
756
|
+
turn: number;
|
|
757
|
+
totalTurns: number;
|
|
758
|
+
scores: {
|
|
759
|
+
id: string;
|
|
760
|
+
score: number;
|
|
761
|
+
}[];
|
|
762
|
+
maskedWord: number[];
|
|
763
|
+
word: string | null;
|
|
764
|
+
buzzQueue: string[];
|
|
765
|
+
lockedOut: string[];
|
|
766
|
+
canBuzz: boolean;
|
|
767
|
+
skipsLeft: number;
|
|
768
|
+
lastWord: string | null;
|
|
769
|
+
lastWinnerId: string | null;
|
|
770
|
+
timerRemainingMs: number | null;
|
|
771
|
+
}
|
|
673
772
|
type ServerMsg = {
|
|
674
773
|
t: 'roomCreated';
|
|
675
774
|
code: string;
|
|
@@ -719,6 +818,29 @@ type ServerMsg = {
|
|
|
719
818
|
gameId: 'dama';
|
|
720
819
|
view: DamaStateView;
|
|
721
820
|
you: YouContext;
|
|
821
|
+
} | {
|
|
822
|
+
t: 'ersimhaState';
|
|
823
|
+
gameId: 'ersimha';
|
|
824
|
+
view: ErsimhaStateView;
|
|
825
|
+
you: YouContext;
|
|
826
|
+
} | {
|
|
827
|
+
t: 'strokeBatch';
|
|
828
|
+
from: string;
|
|
829
|
+
strokeId: string;
|
|
830
|
+
seq: number;
|
|
831
|
+
points: {
|
|
832
|
+
x: number;
|
|
833
|
+
y: number;
|
|
834
|
+
}[];
|
|
835
|
+
color: string;
|
|
836
|
+
width: number;
|
|
837
|
+
} | {
|
|
838
|
+
t: 'clearCanvas';
|
|
839
|
+
from: string;
|
|
840
|
+
} | {
|
|
841
|
+
t: 'undoStroke';
|
|
842
|
+
from: string;
|
|
843
|
+
strokeId: string | null;
|
|
722
844
|
} | {
|
|
723
845
|
t: 'fx';
|
|
724
846
|
effects: Effect[];
|
|
@@ -735,4 +857,4 @@ type ServerMsg = {
|
|
|
735
857
|
message: string;
|
|
736
858
|
};
|
|
737
859
|
|
|
738
|
-
export { type AvatarView, type BuzzerGameMode, type CategoryView, ClientMsg, type DamaMoveView, type DamaPieceView, type DamaStateView, type Effect, GAMES, GAME_IDS, type GameId, type GameInfo, type GameStateView, type GameStatus, type GameplayFlow, type HelpName, LIMITS, type LobbyPlayer, MIN_SUPPORTED_PV, MODES, MODE_IDS, type ModeId, type ModeInfo, PROTOCOL_VERSION, type QuestionView, ROOM_ALPHABET, type ServerMsg, type SoundName, type TeamSlotView, type TeamView, type YouContext, gameIdForPv, gamesForPv, modesForGame };
|
|
860
|
+
export { type AvatarView, type BuzzerGameMode, type CategoryView, ClientMsg, type DamaMoveView, type DamaPieceView, type DamaStateView, type Effect, 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, PROTOCOL_VERSION, type QuestionView, ROOM_ALPHABET, type ServerMsg, type SoundName, type TeamSlotView, type TeamView, type YouContext, gameIdForPv, gamesForPv, modesForGame };
|
package/dist/index.d.ts
CHANGED
|
@@ -14,6 +14,9 @@ declare const LIMITS: {
|
|
|
14
14
|
readonly ROOM_CODE_LEN: 4;
|
|
15
15
|
readonly MAX_PLAYERS: 12;
|
|
16
16
|
readonly MAX_MSG_PER_SEC: 8;
|
|
17
|
+
/** Ersimha strokes ride a SEPARATE per-socket lane (general msgs keep MAX_MSG_PER_SEC).
|
|
18
|
+
* Clients batch pen points to stay under this — ~150ms batching lands at ~7/sec. */
|
|
19
|
+
readonly STROKE_BATCH_PER_SEC: 20;
|
|
17
20
|
};
|
|
18
21
|
/**
|
|
19
22
|
* Wire-format generation. **Bump ONLY on a breaking change** to ClientMsg /
|
|
@@ -33,7 +36,7 @@ declare const LIMITS: {
|
|
|
33
36
|
* `seedha`/`snag`). Raise the floor SLOWLY to retire ancient builds. Unversioned clients (no `pv`,
|
|
34
37
|
* e.g. the legacy web harness) are treated as the floor and never rejected.
|
|
35
38
|
*/
|
|
36
|
-
declare const PROTOCOL_VERSION =
|
|
39
|
+
declare const PROTOCOL_VERSION = 4;
|
|
37
40
|
declare const MIN_SUPPORTED_PV = 1;
|
|
38
41
|
/**
|
|
39
42
|
* Geem is a PLATFORM of GAMES (jackbox-style). The trivia board split (D-023) into TWO player-facing
|
|
@@ -42,7 +45,7 @@ declare const MIN_SUPPORTED_PV = 1;
|
|
|
42
45
|
* pre-pv2 clients (they never saw seedha/snag), and what old clients send on startGame. Every room
|
|
43
46
|
* runs one game, identified by `gameId`.
|
|
44
47
|
*/
|
|
45
|
-
declare const GAME_IDS: readonly ["seedha", "snag", "trivia", "dama"];
|
|
48
|
+
declare const GAME_IDS: readonly ["seedha", "snag", "trivia", "dama", "ersimha"];
|
|
46
49
|
type GameId = (typeof GAME_IDS)[number];
|
|
47
50
|
/**
|
|
48
51
|
* The GAME REGISTRY — player-facing games + the version gating (D-024/D-025). A client renders a tile
|
|
@@ -96,6 +99,7 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
96
99
|
emoji: string;
|
|
97
100
|
color: string;
|
|
98
101
|
}>>;
|
|
102
|
+
game: z.ZodOptional<z.ZodEnum<["seedha", "snag", "trivia", "dama", "ersimha"]>>;
|
|
99
103
|
pv: z.ZodOptional<z.ZodNumber>;
|
|
100
104
|
idToken: z.ZodOptional<z.ZodString>;
|
|
101
105
|
platform: z.ZodOptional<z.ZodEnum<["web", "ios", "android"]>>;
|
|
@@ -106,6 +110,7 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
106
110
|
emoji: string;
|
|
107
111
|
color: string;
|
|
108
112
|
} | undefined;
|
|
113
|
+
game?: "seedha" | "snag" | "trivia" | "dama" | "ersimha" | undefined;
|
|
109
114
|
pv?: number | undefined;
|
|
110
115
|
idToken?: string | undefined;
|
|
111
116
|
platform?: "web" | "ios" | "android" | undefined;
|
|
@@ -116,6 +121,7 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
116
121
|
emoji: string;
|
|
117
122
|
color: string;
|
|
118
123
|
} | undefined;
|
|
124
|
+
game?: "seedha" | "snag" | "trivia" | "dama" | "ersimha" | undefined;
|
|
119
125
|
pv?: number | undefined;
|
|
120
126
|
idToken?: string | undefined;
|
|
121
127
|
platform?: "web" | "ios" | "android" | undefined;
|
|
@@ -286,7 +292,7 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
286
292
|
activity: "configuring" | "idle";
|
|
287
293
|
}>, z.ZodObject<{
|
|
288
294
|
t: z.ZodLiteral<"startGame">;
|
|
289
|
-
game: z.ZodOptional<z.ZodEnum<["seedha", "snag", "trivia", "dama"]>>;
|
|
295
|
+
game: z.ZodOptional<z.ZodEnum<["seedha", "snag", "trivia", "dama", "ersimha"]>>;
|
|
290
296
|
totalRounds: z.ZodNumber;
|
|
291
297
|
categoryIds: z.ZodArray<z.ZodString, "many">;
|
|
292
298
|
modeId: z.ZodOptional<z.ZodEnum<["solo", "oneDevice", "snag", "multiplayer"]>>;
|
|
@@ -296,16 +302,16 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
296
302
|
t: "startGame";
|
|
297
303
|
categoryIds: string[];
|
|
298
304
|
totalRounds: number;
|
|
305
|
+
game?: "seedha" | "snag" | "trivia" | "dama" | "ersimha" | undefined;
|
|
299
306
|
modeId?: "snag" | "solo" | "oneDevice" | "multiplayer" | undefined;
|
|
300
|
-
game?: "seedha" | "snag" | "trivia" | "dama" | undefined;
|
|
301
307
|
buzzerMode?: "solo" | "off" | "race" | "controller" | undefined;
|
|
302
308
|
noHost?: boolean | undefined;
|
|
303
309
|
}, {
|
|
304
310
|
t: "startGame";
|
|
305
311
|
categoryIds: string[];
|
|
306
312
|
totalRounds: number;
|
|
313
|
+
game?: "seedha" | "snag" | "trivia" | "dama" | "ersimha" | undefined;
|
|
307
314
|
modeId?: "snag" | "solo" | "oneDevice" | "multiplayer" | undefined;
|
|
308
|
-
game?: "seedha" | "snag" | "trivia" | "dama" | undefined;
|
|
309
315
|
buzzerMode?: "solo" | "off" | "race" | "controller" | undefined;
|
|
310
316
|
noHost?: boolean | undefined;
|
|
311
317
|
}>, z.ZodObject<{
|
|
@@ -485,6 +491,72 @@ declare const ClientMsg: z.ZodDiscriminatedUnion<"t", [z.ZodObject<{
|
|
|
485
491
|
t: "damaResign";
|
|
486
492
|
}, {
|
|
487
493
|
t: "damaResign";
|
|
494
|
+
}>, z.ZodObject<{
|
|
495
|
+
t: z.ZodLiteral<"strokeBatch">;
|
|
496
|
+
strokeId: z.ZodString;
|
|
497
|
+
seq: z.ZodNumber;
|
|
498
|
+
points: z.ZodArray<z.ZodObject<{
|
|
499
|
+
x: z.ZodNumber;
|
|
500
|
+
y: z.ZodNumber;
|
|
501
|
+
}, "strip", z.ZodTypeAny, {
|
|
502
|
+
x: number;
|
|
503
|
+
y: number;
|
|
504
|
+
}, {
|
|
505
|
+
x: number;
|
|
506
|
+
y: number;
|
|
507
|
+
}>, "many">;
|
|
508
|
+
color: z.ZodString;
|
|
509
|
+
width: z.ZodNumber;
|
|
510
|
+
}, "strip", z.ZodTypeAny, {
|
|
511
|
+
color: string;
|
|
512
|
+
t: "strokeBatch";
|
|
513
|
+
strokeId: string;
|
|
514
|
+
seq: number;
|
|
515
|
+
points: {
|
|
516
|
+
x: number;
|
|
517
|
+
y: number;
|
|
518
|
+
}[];
|
|
519
|
+
width: number;
|
|
520
|
+
}, {
|
|
521
|
+
color: string;
|
|
522
|
+
t: "strokeBatch";
|
|
523
|
+
strokeId: string;
|
|
524
|
+
seq: number;
|
|
525
|
+
points: {
|
|
526
|
+
x: number;
|
|
527
|
+
y: number;
|
|
528
|
+
}[];
|
|
529
|
+
width: number;
|
|
530
|
+
}>, z.ZodObject<{
|
|
531
|
+
t: z.ZodLiteral<"clearCanvas">;
|
|
532
|
+
}, "strip", z.ZodTypeAny, {
|
|
533
|
+
t: "clearCanvas";
|
|
534
|
+
}, {
|
|
535
|
+
t: "clearCanvas";
|
|
536
|
+
}>, z.ZodObject<{
|
|
537
|
+
t: z.ZodLiteral<"undoStroke">;
|
|
538
|
+
strokeId: z.ZodOptional<z.ZodString>;
|
|
539
|
+
}, "strip", z.ZodTypeAny, {
|
|
540
|
+
t: "undoStroke";
|
|
541
|
+
strokeId?: string | undefined;
|
|
542
|
+
}, {
|
|
543
|
+
t: "undoStroke";
|
|
544
|
+
strokeId?: string | undefined;
|
|
545
|
+
}>, z.ZodObject<{
|
|
546
|
+
t: z.ZodLiteral<"judgeGuess">;
|
|
547
|
+
correct: z.ZodBoolean;
|
|
548
|
+
}, "strip", z.ZodTypeAny, {
|
|
549
|
+
t: "judgeGuess";
|
|
550
|
+
correct: boolean;
|
|
551
|
+
}, {
|
|
552
|
+
t: "judgeGuess";
|
|
553
|
+
correct: boolean;
|
|
554
|
+
}>, z.ZodObject<{
|
|
555
|
+
t: z.ZodLiteral<"skipWord">;
|
|
556
|
+
}, "strip", z.ZodTypeAny, {
|
|
557
|
+
t: "skipWord";
|
|
558
|
+
}, {
|
|
559
|
+
t: "skipWord";
|
|
488
560
|
}>]>;
|
|
489
561
|
type ClientMsg = z.infer<typeof ClientMsg>;
|
|
490
562
|
/** A player's privacy-safe avatar (emoji + circle color) for in-room display. */
|
|
@@ -670,6 +742,33 @@ interface DamaStateView {
|
|
|
670
742
|
legalMoves: DamaMoveView[];
|
|
671
743
|
moveCount: number;
|
|
672
744
|
}
|
|
745
|
+
/**
|
|
746
|
+
* The authoritative Ersimha round state. Anti-cheat boundary (D-030): `word` is set ONLY in the
|
|
747
|
+
* payload sent to the DRAWER's connection — guessers and the TV get `maskedWord` (per-segment
|
|
748
|
+
* letter counts, «سيارة إسعاف» → [5,5]). Names/avatars come from the lobby roster (map by id),
|
|
749
|
+
* same as Dama. Strokes are NOT in the state — they stream as `strokeBatch` passthroughs and a
|
|
750
|
+
* late joiner simply starts from a blank canvas (ephemeral by design).
|
|
751
|
+
*/
|
|
752
|
+
interface ErsimhaStateView {
|
|
753
|
+
phase: 'choosing' | 'drawing' | 'judging' | 'roundEnd' | 'completed';
|
|
754
|
+
drawerId: string;
|
|
755
|
+
judgedId: string | null;
|
|
756
|
+
turn: number;
|
|
757
|
+
totalTurns: number;
|
|
758
|
+
scores: {
|
|
759
|
+
id: string;
|
|
760
|
+
score: number;
|
|
761
|
+
}[];
|
|
762
|
+
maskedWord: number[];
|
|
763
|
+
word: string | null;
|
|
764
|
+
buzzQueue: string[];
|
|
765
|
+
lockedOut: string[];
|
|
766
|
+
canBuzz: boolean;
|
|
767
|
+
skipsLeft: number;
|
|
768
|
+
lastWord: string | null;
|
|
769
|
+
lastWinnerId: string | null;
|
|
770
|
+
timerRemainingMs: number | null;
|
|
771
|
+
}
|
|
673
772
|
type ServerMsg = {
|
|
674
773
|
t: 'roomCreated';
|
|
675
774
|
code: string;
|
|
@@ -719,6 +818,29 @@ type ServerMsg = {
|
|
|
719
818
|
gameId: 'dama';
|
|
720
819
|
view: DamaStateView;
|
|
721
820
|
you: YouContext;
|
|
821
|
+
} | {
|
|
822
|
+
t: 'ersimhaState';
|
|
823
|
+
gameId: 'ersimha';
|
|
824
|
+
view: ErsimhaStateView;
|
|
825
|
+
you: YouContext;
|
|
826
|
+
} | {
|
|
827
|
+
t: 'strokeBatch';
|
|
828
|
+
from: string;
|
|
829
|
+
strokeId: string;
|
|
830
|
+
seq: number;
|
|
831
|
+
points: {
|
|
832
|
+
x: number;
|
|
833
|
+
y: number;
|
|
834
|
+
}[];
|
|
835
|
+
color: string;
|
|
836
|
+
width: number;
|
|
837
|
+
} | {
|
|
838
|
+
t: 'clearCanvas';
|
|
839
|
+
from: string;
|
|
840
|
+
} | {
|
|
841
|
+
t: 'undoStroke';
|
|
842
|
+
from: string;
|
|
843
|
+
strokeId: string | null;
|
|
722
844
|
} | {
|
|
723
845
|
t: 'fx';
|
|
724
846
|
effects: Effect[];
|
|
@@ -735,4 +857,4 @@ type ServerMsg = {
|
|
|
735
857
|
message: string;
|
|
736
858
|
};
|
|
737
859
|
|
|
738
|
-
export { type AvatarView, type BuzzerGameMode, type CategoryView, ClientMsg, type DamaMoveView, type DamaPieceView, type DamaStateView, type Effect, GAMES, GAME_IDS, type GameId, type GameInfo, type GameStateView, type GameStatus, type GameplayFlow, type HelpName, LIMITS, type LobbyPlayer, MIN_SUPPORTED_PV, MODES, MODE_IDS, type ModeId, type ModeInfo, PROTOCOL_VERSION, type QuestionView, ROOM_ALPHABET, type ServerMsg, type SoundName, type TeamSlotView, type TeamView, type YouContext, gameIdForPv, gamesForPv, modesForGame };
|
|
860
|
+
export { type AvatarView, type BuzzerGameMode, type CategoryView, ClientMsg, type DamaMoveView, type DamaPieceView, type DamaStateView, type Effect, 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, PROTOCOL_VERSION, type QuestionView, ROOM_ALPHABET, type ServerMsg, type SoundName, type TeamSlotView, type TeamView, type YouContext, gameIdForPv, gamesForPv, modesForGame };
|
package/dist/index.js
CHANGED
|
@@ -4,16 +4,21 @@ var LIMITS = {
|
|
|
4
4
|
MAX_NAME_LEN: 20,
|
|
5
5
|
ROOM_CODE_LEN: 4,
|
|
6
6
|
MAX_PLAYERS: 12,
|
|
7
|
-
MAX_MSG_PER_SEC: 8
|
|
7
|
+
MAX_MSG_PER_SEC: 8,
|
|
8
|
+
/** Ersimha strokes ride a SEPARATE per-socket lane (general msgs keep MAX_MSG_PER_SEC).
|
|
9
|
+
* Clients batch pen points to stay under this — ~150ms batching lands at ~7/sec. */
|
|
10
|
+
STROKE_BATCH_PER_SEC: 20
|
|
8
11
|
};
|
|
9
|
-
var PROTOCOL_VERSION =
|
|
12
|
+
var PROTOCOL_VERSION = 4;
|
|
10
13
|
var MIN_SUPPORTED_PV = 1;
|
|
11
|
-
var GAME_IDS = ["seedha", "snag", "trivia", "dama"];
|
|
14
|
+
var GAME_IDS = ["seedha", "snag", "trivia", "dama", "ersimha"];
|
|
12
15
|
var GAMES = [
|
|
13
16
|
{ id: "seedha", label: "Seedha", minPv: 2 },
|
|
14
17
|
{ id: "snag", label: "Snag", minPv: 2 },
|
|
15
18
|
{ id: "dama", label: "Dama", minPv: 3 },
|
|
16
19
|
// engine #2 (D-027) — offered only to pv≥3 clients
|
|
20
|
+
{ id: "ersimha", label: "Ersimha", minPv: 4 },
|
|
21
|
+
// engine #3 (D-030) draw-and-guess — offered only to pv≥4 clients
|
|
17
22
|
{ id: "trivia", label: "Trivia", minPv: 1, deprecated: true }
|
|
18
23
|
];
|
|
19
24
|
var gamesForPv = (pv) => GAMES.filter((g) => !g.deprecated && g.minPv <= pv);
|
|
@@ -52,7 +57,7 @@ var Avatar = z.object({
|
|
|
52
57
|
});
|
|
53
58
|
var ClientMsg = z.discriminatedUnion("t", [
|
|
54
59
|
// room / lobby
|
|
55
|
-
z.object({ t: z.literal("createRoom"), name: DisplayName.optional(), avatar: Avatar.optional(), pv: Pv, idToken: IdToken, platform: Platform }),
|
|
60
|
+
z.object({ t: z.literal("createRoom"), name: DisplayName.optional(), avatar: Avatar.optional(), game: z.enum(GAME_IDS).optional(), pv: Pv, idToken: IdToken, platform: Platform }),
|
|
56
61
|
// Host sets/changes their display name AFTER createRoom (for anonymous one-tap
|
|
57
62
|
// hosts who started nameless). Host-only; the relay re-broadcasts the lobby.
|
|
58
63
|
z.object({ t: z.literal("renameHost"), name: DisplayName }),
|
|
@@ -133,7 +138,28 @@ var ClientMsg = z.discriminatedUnion("t", [
|
|
|
133
138
|
path: z.array(z.object({ r: z.number().int().min(0).max(7), c: z.number().int().min(0).max(7) })).max(12).optional()
|
|
134
139
|
}),
|
|
135
140
|
// Resign the current Dama game (concede) → opponent wins.
|
|
136
|
-
z.object({ t: z.literal("damaResign") })
|
|
141
|
+
z.object({ t: z.literal("damaResign") }),
|
|
142
|
+
// ── Ersimha slice (gameId 'ersimha', engine #3 — D-030) ──
|
|
143
|
+
// Drawer streams stroke points as batches. Coordinates are NORMALIZED [0,1] floats (device-
|
|
144
|
+
// independent — every client and the TV scale to their own canvas). The relay is a pure
|
|
145
|
+
// PASSTHROUGH: strokes are validated, rate-limited, fanned out, and NEVER stored (ephemeral
|
|
146
|
+
// drawings, D-030). `seq` orders batches within one stroke; a new strokeId = pen-down.
|
|
147
|
+
z.object({
|
|
148
|
+
t: z.literal("strokeBatch"),
|
|
149
|
+
strokeId: z.string().min(1).max(40),
|
|
150
|
+
seq: z.number().int().min(0).max(1e4),
|
|
151
|
+
points: z.array(z.object({ x: z.number().min(0).max(1), y: z.number().min(0).max(1) })).min(1).max(128),
|
|
152
|
+
color: z.string().regex(/^#[0-9a-fA-F]{6}$/),
|
|
153
|
+
width: z.number().min(1).max(64)
|
|
154
|
+
}),
|
|
155
|
+
// Drawer wipes the canvas (relayed to everyone).
|
|
156
|
+
z.object({ t: z.literal("clearCanvas") }),
|
|
157
|
+
// Drawer removes one stroke — the given id, or their latest when omitted (relayed).
|
|
158
|
+
z.object({ t: z.literal("undoStroke"), strokeId: z.string().min(1).max(40).optional() }),
|
|
159
|
+
// Drawer rules the CURRENT buzzer's spoken guess صح/خطأ (guessing is out loud — no text on the wire).
|
|
160
|
+
z.object({ t: z.literal("judgeGuess"), correct: z.boolean() }),
|
|
161
|
+
// Drawer passes on the served word (capped per draw-turn; relay serves a fresh one).
|
|
162
|
+
z.object({ t: z.literal("skipWord") })
|
|
137
163
|
]);
|
|
138
164
|
export {
|
|
139
165
|
ClientMsg,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thegeem/protocol",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.20",
|
|
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",
|