ScribblaZ Opcodes
The shared OP table is the wire contract; the Nakama handler validates and authoritatively turns client intent into game state.
| Field | Value |
|---|---|
| Shared table | src/lib/gameState.ts:6-35 |
| Server router | server/match_handler.ts:961-1086 |
| Client transport | src/ScribblaZGame.svelte:126-153 |
| Client projection | src/lib/ScribblaZController.svelte.ts:102-269 |
| Settings side channel | SETTINGS_OPCODE = 50 in src/lib/protocol.ts:3-5 |
Docs: Hub · Architecture · Game design · Runbook · Verification · Nakama Multiplayer · Games Catalog
Shared opcode table
Payload shapes below describe the current source contract. “Client location” names the call site for outbound traffic or the receive handler for inbound traffic; “server location” names the routing/emission location.
| # | Opcode | Direction | Payload | Client location | Server location |
|---|---|---|---|---|---|
| 1 | DRAW_STROKE | C→S | { points, color, width, groupId? } | send ScribblaZGame.svelte:248-251 | route/validate match_handler.ts:985-994, :459-509 |
| 2 | DRAW_FILL | C→S | { x, y, color } | send ScribblaZGame.svelte:254-257 | route/validate match_handler.ts:985-994, :459-509 |
| 3 | DRAW_SHAPE | C→S | `{ shape: ‘line' | 'box' | 'circle’, points, color, width, filled }` |
| 4 | DRAW_CLEAR | C→S | {} | send ScribblaZGame.svelte:260-263 | route/validate match_handler.ts:985-994, :459-509 |
| 5 | DRAW_UNDO | C→S | {} | send ScribblaZGame.svelte:265-268 | route/validate match_handler.ts:985-994, :459-509 |
| 22 | DRAW_REDO | C→S | {} | send ScribblaZGame.svelte:271-273 | route/validate match_handler.ts:985-994, :459-509 |
| 6 | CHOOSE_WORD | C→S | { hashId }, legacy { index: 1..3 } | send ScribblaZGame.svelte:282-291 | resolve/start match_handler.ts:997-1016 |
| 7 | SEND_GUESS | C→S | { text } | send ScribblaZGame.svelte:294-296 | route/score/chat match_handler.ts:1019-1023, :511-618 |
| 8 | TOGGLE_READY | C→S | {} | send ScribblaZGame.svelte:331-333 | route match_handler.ts:1026-1032 |
| 9 | REQUEST_START | C→S | {} | send ScribblaZGame.svelte:335-338 | start/reset route match_handler.ts:1034-1041 |
| 10 | GAME_STATE | S→C | { phase, round, totalRounds, drawerId, drawerName, creatorId, creatorDisplayName, players, strokes, hint, msRemaining, totalMs, scores, settings, isDelta? } | receive ScribblaZController.svelte.ts:104-140 | emit match_handler.ts:790, :897-910, :1029, :1070 |
| 11 | ROUND_START | S→C | { round, totalRounds, drawerId, drawerName, timeLimitMs, phase } | receive ScribblaZController.svelte.ts:148-168; root UI ScribblaZGame.svelte:163-171 | emit match_handler.ts:655-662, :677-684 |
| 12 | WORD_OPTIONS | S→C | { words: hashIds[], plaintexts: string[] } | receive ScribblaZGame.svelte:172-177 | emit match_handler.ts:650-653; resync :1072-1077 |
| 13 | HINT_UPDATE | S→C | { hint, revealed, word? } | receive ScribblaZController.svelte.ts:176-182 | emit match_handler.ts:686-695, :899-905, :1078-1084, :1125-1133 |
| 14 | DRAW_RELAY | S→C | { op, data } | receive ScribblaZController.svelte.ts:184-200 | emit match_handler.ts:508 |
| 15 | GUESS_RESULT | S→C | { playerId, correct, displayName } | receive ScribblaZController.svelte.ts:203-217; bridge mirror ScribblaZGame.svelte:198-206 | emit match_handler.ts:564-568 |
| 16 | PLAYER_SCORED | S→C | { playerId, delta, total } | receive ScribblaZController.svelte.ts:256-265 | emit match_handler.ts:569-585 |
| 17 | ROUND_END | S→C | { word, scores } | receive ScribblaZController.svelte.ts:219-225; private-save call ScribblaZGame.svelte:178-187 | emit match_handler.ts:718-721 |
| 18 | GAME_END | S→C | { winnerId, finalScores, top3 } | receive ScribblaZController.svelte.ts:228-232 | emit match_handler.ts:742-746 |
| 19 | CHAT_MSG | S→C | { playerId, displayName, text, nearMiss } | receive ScribblaZController.svelte.ts:235-245; bridge mirror ScribblaZGame.svelte:188-197 | emit match_handler.ts:541-546, :613-618, :937-942, :1049-1054 |
| 20 | TIMER_SYNC | S→C | { msRemaining } | receive ScribblaZController.svelte.ts:248-253 | emit match_handler.ts:1137-1141 |
| 21 | REQUEST_SYNC | C→S, S→C state | { lastStrokeIndex } request; response is GAME_STATE plus settings and role secret | send ScribblaZGame.svelte:431-464; receives resulting state in controller :104-140 | route/respond match_handler.ts:1060-1085 |
| 23 | VOTE_KICK | C→S | { targetId } | No root sender is present in the inspected current source; shared declaration gameState.ts:34 | host route/kick match_handler.ts:1043-1057 |
Settings side channel
SETTINGS_OPCODE is 50, not an OP member. src/lib/protocol.ts:3-5 records why: its former value 5 collided with OP.DRAW_UNDO = 5. The root sends host custom-word settings through this side channel (ScribblaZGame.svelte:310-328); the server accepts it only from the creator during waiting and broadcasts normalized shared settings plus GAME_STATE (match_handler.ts:970-982). The controller receives it at ScribblaZController.svelte.ts:143-145.
Synchronization notes
GAME_STATE is a full projection unless isDelta is set. For a valid lastStrokeIndex, REQUEST_SYNC sends only later strokes and marks the response isDelta; the controller appends those events (match_handler.ts:1060-1068; ScribblaZController.svelte.ts:116-120). Word-pick resync and its drawer-only option re-send are visible in the current handler (match_handler.ts:1072-1077) but require end-to-end verification.