@remix-gg/mcp 0.4.3

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.
Files changed (139) hide show
  1. package/README.md +81 -0
  2. package/dist/client-helpers/index.d.ts +2 -0
  3. package/dist/client-helpers/index.d.ts.map +1 -0
  4. package/dist/client-helpers/index.js +2 -0
  5. package/dist/client-helpers/index.js.map +1 -0
  6. package/dist/config.d.ts +20 -0
  7. package/dist/config.d.ts.map +1 -0
  8. package/dist/config.js +58 -0
  9. package/dist/config.js.map +1 -0
  10. package/dist/core/api-client.d.ts +4 -0
  11. package/dist/core/api-client.d.ts.map +1 -0
  12. package/dist/core/api-client.js +12 -0
  13. package/dist/core/api-client.js.map +1 -0
  14. package/dist/core/config.d.ts +6 -0
  15. package/dist/core/config.d.ts.map +1 -0
  16. package/dist/core/config.js +19 -0
  17. package/dist/core/config.js.map +1 -0
  18. package/dist/core/index.d.ts +5 -0
  19. package/dist/core/index.d.ts.map +1 -0
  20. package/dist/core/index.js +4 -0
  21. package/dist/core/index.js.map +1 -0
  22. package/dist/core/skills.d.ts +22 -0
  23. package/dist/core/skills.d.ts.map +1 -0
  24. package/dist/core/skills.js +49 -0
  25. package/dist/core/skills.js.map +1 -0
  26. package/dist/core/tool-defs.d.ts +12 -0
  27. package/dist/core/tool-defs.d.ts.map +1 -0
  28. package/dist/core/tool-defs.js +356 -0
  29. package/dist/core/tool-defs.js.map +1 -0
  30. package/dist/core/tools/create-game.d.ts +9 -0
  31. package/dist/core/tools/create-game.d.ts.map +1 -0
  32. package/dist/core/tools/create-game.js +21 -0
  33. package/dist/core/tools/create-game.js.map +1 -0
  34. package/dist/core/tools/create-shop-item.d.ts +14 -0
  35. package/dist/core/tools/create-shop-item.d.ts.map +1 -0
  36. package/dist/core/tools/create-shop-item.js +78 -0
  37. package/dist/core/tools/create-shop-item.js.map +1 -0
  38. package/dist/core/tools/delete-shop-item.d.ts +9 -0
  39. package/dist/core/tools/delete-shop-item.d.ts.map +1 -0
  40. package/dist/core/tools/delete-shop-item.js +19 -0
  41. package/dist/core/tools/delete-shop-item.js.map +1 -0
  42. package/dist/core/tools/generate-image.d.ts +8 -0
  43. package/dist/core/tools/generate-image.d.ts.map +1 -0
  44. package/dist/core/tools/generate-image.js +32 -0
  45. package/dist/core/tools/generate-image.js.map +1 -0
  46. package/dist/core/tools/generate-sprite-sheet.d.ts +14 -0
  47. package/dist/core/tools/generate-sprite-sheet.d.ts.map +1 -0
  48. package/dist/core/tools/generate-sprite-sheet.js +29 -0
  49. package/dist/core/tools/generate-sprite-sheet.js.map +1 -0
  50. package/dist/core/tools/helpers.d.ts +60 -0
  51. package/dist/core/tools/helpers.d.ts.map +1 -0
  52. package/dist/core/tools/helpers.js +68 -0
  53. package/dist/core/tools/helpers.js.map +1 -0
  54. package/dist/core/tools/index.d.ts +13 -0
  55. package/dist/core/tools/index.d.ts.map +1 -0
  56. package/dist/core/tools/index.js +13 -0
  57. package/dist/core/tools/index.js.map +1 -0
  58. package/dist/core/tools/list-shop-items.d.ts +8 -0
  59. package/dist/core/tools/list-shop-items.d.ts.map +1 -0
  60. package/dist/core/tools/list-shop-items.js +17 -0
  61. package/dist/core/tools/list-shop-items.js.map +1 -0
  62. package/dist/core/tools/update-game.d.ts +11 -0
  63. package/dist/core/tools/update-game.d.ts.map +1 -0
  64. package/dist/core/tools/update-game.js +22 -0
  65. package/dist/core/tools/update-game.js.map +1 -0
  66. package/dist/core/tools/update-shop-item.d.ts +15 -0
  67. package/dist/core/tools/update-shop-item.d.ts.map +1 -0
  68. package/dist/core/tools/update-shop-item.js +24 -0
  69. package/dist/core/tools/update-shop-item.js.map +1 -0
  70. package/dist/core/tools/upload-game-asset.d.ts +8 -0
  71. package/dist/core/tools/upload-game-asset.d.ts.map +1 -0
  72. package/dist/core/tools/upload-game-asset.js +43 -0
  73. package/dist/core/tools/upload-game-asset.js.map +1 -0
  74. package/dist/core/tools/upload-version.d.ts +8 -0
  75. package/dist/core/tools/upload-version.d.ts.map +1 -0
  76. package/dist/core/tools/upload-version.js +20 -0
  77. package/dist/core/tools/upload-version.js.map +1 -0
  78. package/dist/core/tools/validate-game.d.ts +6 -0
  79. package/dist/core/tools/validate-game.d.ts.map +1 -0
  80. package/dist/core/tools/validate-game.js +41 -0
  81. package/dist/core/tools/validate-game.js.map +1 -0
  82. package/dist/core/tools.test.d.ts +2 -0
  83. package/dist/core/tools.test.d.ts.map +1 -0
  84. package/dist/core/tools.test.js +825 -0
  85. package/dist/core/tools.test.js.map +1 -0
  86. package/dist/generated/server-api.d.ts +3673 -0
  87. package/dist/generated/server-api.d.ts.map +1 -0
  88. package/dist/generated/server-api.js +2 -0
  89. package/dist/generated/server-api.js.map +1 -0
  90. package/dist/index.d.ts +3 -0
  91. package/dist/index.d.ts.map +1 -0
  92. package/dist/index.js +365 -0
  93. package/dist/index.js.map +1 -0
  94. package/dist/server/create-server.d.ts +12 -0
  95. package/dist/server/create-server.d.ts.map +1 -0
  96. package/dist/server/create-server.js +29 -0
  97. package/dist/server/create-server.js.map +1 -0
  98. package/dist/server/create-server.test.d.ts +2 -0
  99. package/dist/server/create-server.test.d.ts.map +1 -0
  100. package/dist/server/create-server.test.js +37 -0
  101. package/dist/server/create-server.test.js.map +1 -0
  102. package/dist/server/index.d.ts +3 -0
  103. package/dist/server/index.d.ts.map +1 -0
  104. package/dist/server/index.js +8 -0
  105. package/dist/server/index.js.map +1 -0
  106. package/dist/server/lib/config.d.ts +2 -0
  107. package/dist/server/lib/config.d.ts.map +1 -0
  108. package/dist/server/lib/config.js +2 -0
  109. package/dist/server/lib/config.js.map +1 -0
  110. package/dist/server/resources/skills.d.ts +3 -0
  111. package/dist/server/resources/skills.d.ts.map +1 -0
  112. package/dist/server/resources/skills.js +60 -0
  113. package/dist/server/resources/skills.js.map +1 -0
  114. package/dist/server/resources/skills.test.d.ts +2 -0
  115. package/dist/server/resources/skills.test.d.ts.map +1 -0
  116. package/dist/server/resources/skills.test.js +44 -0
  117. package/dist/server/resources/skills.test.js.map +1 -0
  118. package/dist/server/tools/register.d.ts +3 -0
  119. package/dist/server/tools/register.d.ts.map +1 -0
  120. package/dist/server/tools/register.js +26 -0
  121. package/dist/server/tools/register.js.map +1 -0
  122. package/dist/server/tools/register.test.d.ts +2 -0
  123. package/dist/server/tools/register.test.d.ts.map +1 -0
  124. package/dist/server/tools/register.test.js +85 -0
  125. package/dist/server/tools/register.test.js.map +1 -0
  126. package/dist/types.d.ts +73 -0
  127. package/dist/types.d.ts.map +1 -0
  128. package/dist/types.js +31 -0
  129. package/dist/types.js.map +1 -0
  130. package/package.json +38 -0
  131. package/skills/SKILL.md +82 -0
  132. package/skills/actions/open-game.md +18 -0
  133. package/skills/workflows/add-image-to-game.md +121 -0
  134. package/skills/workflows/add-sprite-to-game.md +127 -0
  135. package/skills/workflows/game-creation.md +124 -0
  136. package/skills/workflows/implement-multiplayer.md +355 -0
  137. package/skills/workflows/integrate-save-game.md +135 -0
  138. package/skills/workflows/manage-shop-items.md +246 -0
  139. package/skills/workflows/upload-game.md +74 -0
@@ -0,0 +1,355 @@
1
+ # Implement Multiplayer Workflow
2
+
3
+ ## Overview
4
+
5
+ This skill guides you through integrating turn-based multiplayer into an HTML
6
+ game on the Remix platform. Multiplayer games use the RemixSDK's `multiplayer`
7
+ namespace instead of `singlePlayer`. Games are two-player and fully turn-based.
8
+
9
+ ## Prerequisites
10
+
11
+ - The game must include the RemixSDK script tag:
12
+ ```html
13
+ <script src="https://cdn.jsdelivr.net/npm/@remix-gg/sdk@latest/dist/index.min.js"></script>
14
+ ```
15
+ - The game must already be playable (follow the **game-creation** workflow first
16
+ if starting from scratch).
17
+ - The `REMIX_API_KEY` environment variable must be set.
18
+ - `.remix-settings.json` must contain a `gameId`.
19
+
20
+ ## Constraints
21
+
22
+ - Multiplayer is **2-player only**. Do not design for more than two players.
23
+ - All games must be **fully turn-based**. Real-time multiplayer is not supported.
24
+
25
+ ## Steps
26
+
27
+ ### 1. Check `.remix-settings.json`
28
+
29
+ Read the file and extract `gameId`. If it is missing, follow the
30
+ **game-creation** workflow first to create and register the game.
31
+
32
+ ### 2. Mark the Game as Multiplayer
33
+
34
+ Call the `updateGame` tool with the `gameId` and `isMultiplayer: true`. This
35
+ registers the game as multiplayer on the platform.
36
+
37
+ ```
38
+ updateGame({ gameId: "<gameId>", isMultiplayer: true })
39
+ ```
40
+
41
+ ### 3. Initialize the SDK and Check for Existing Game State
42
+
43
+ Call `await window.RemixSDK.ready()` before the game loop starts. Then check
44
+ `window.RemixSDK.gameState` to restore in-progress matches. If state exists,
45
+ resume the game from that state.
46
+
47
+ ```js
48
+ await window.RemixSDK.ready();
49
+
50
+ const savedState = window.RemixSDK.gameState;
51
+ if (savedState) {
52
+ // Resume in-progress match from savedState
53
+ }
54
+ ```
55
+
56
+ ### 4. Identify the Current Player and Opponent
57
+
58
+ Use `window.RemixSDK.player` for the current player and
59
+ `window.RemixSDK.players` for all players. Derive the opponent:
60
+
61
+ ```js
62
+ const player = window.RemixSDK.player;
63
+ const players = window.RemixSDK.players;
64
+ const opponent = players.find(p => p.id !== player.id);
65
+ ```
66
+
67
+ **Important:** The first player in the `players` array (`players[0]`) is always
68
+ the player who should act first in a new game. Use this ordering to determine
69
+ who takes the opening turn rather than sorting or randomizing.
70
+
71
+ ### 5. Display Player Names and Avatars
72
+
73
+ Each `Player` object has a `name` (string) and an optional `imageUrl` (string)
74
+ for the player's avatar. Use these to personalize the game UI.
75
+
76
+ ```js
77
+ const player = window.RemixSDK.player;
78
+ const opponent = players.find(p => p.id !== player.id);
79
+
80
+ // Access name and avatar
81
+ player.name; // e.g. "Alice"
82
+ player.imageUrl; // e.g. "https://…/avatar.png" or undefined
83
+ opponent.name;
84
+ opponent.imageUrl;
85
+ ```
86
+
87
+ Display names in status messages so players know who they are playing against:
88
+
89
+ ```js
90
+ statusEl.textContent = isMyTurn
91
+ ? `Your turn, ${player.name}`
92
+ : `Waiting for ${opponent.name}…`;
93
+ ```
94
+
95
+ Show avatars with a fallback for when `imageUrl` is missing:
96
+
97
+ ```html
98
+ <div class="player-info">
99
+ <img src="${player.imageUrl}" alt="${player.name}"
100
+ onerror="this.style.display='none'; this.nextElementSibling.style.display='flex'">
101
+ <div class="avatar-fallback" style="display:none">${player.name[0]}</div>
102
+ <span>${player.name}</span>
103
+ </div>
104
+ ```
105
+
106
+ ### 6. Save Game State After Each Turn
107
+
108
+ Use `multiplayer.actions.saveGameState` with `alertUserIds` containing the
109
+ opponent's ID so they are notified it is their turn:
110
+
111
+ ```js
112
+ window.RemixSDK.multiplayer.actions.saveGameState({
113
+ gameState: { /* current board/game state */ },
114
+ alertUserIds: [opponent.id],
115
+ });
116
+ ```
117
+
118
+ ### 7. Listen for Opponent's Moves
119
+
120
+ Register `onGameStateUpdated` to handle incoming state changes from the
121
+ opponent:
122
+
123
+ ```js
124
+ window.RemixSDK.onGameStateUpdated((data) => {
125
+ if (!data) return;
126
+ const { id, gameState } = data;
127
+ // Validate and apply the new state
128
+
129
+ // If the move is invalid, refute it:
130
+ // window.RemixSDK.multiplayer.actions.refuteGameState({ gameStateId: id });
131
+ });
132
+ ```
133
+
134
+ ### 8. End the Game with Multiplayer gameOver
135
+
136
+ Report scores for both players using `multiplayer.actions.gameOver`:
137
+
138
+ ```js
139
+ window.RemixSDK.multiplayer.actions.gameOver({
140
+ scores: [
141
+ { playerId: player.id, score: playerScore },
142
+ { playerId: opponent.id, score: opponentScore },
143
+ ],
144
+ });
145
+ ```
146
+
147
+ ### 9. Handle Play Again and Mute
148
+
149
+ Register `onPlayAgain` and `onToggleMute` callbacks — same as single player:
150
+
151
+ ```js
152
+ window.RemixSDK.onPlayAgain(() => {
153
+ // Reset game state and start a new match
154
+ });
155
+
156
+ window.RemixSDK.onToggleMute(({ isMuted }) => {
157
+ // Mute or unmute audio
158
+ });
159
+ ```
160
+
161
+ ## Examples
162
+
163
+ ### Tic-Tac-Toe
164
+
165
+ ```html
166
+ <!DOCTYPE html>
167
+ <html lang="en">
168
+ <head>
169
+ <meta charset="UTF-8">
170
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
171
+ <title>Tic-Tac-Toe</title>
172
+ <script src="https://cdn.jsdelivr.net/npm/@remix-gg/sdk@latest/dist/index.min.js"></script>
173
+ <style>
174
+ * { margin: 0; padding: 0; box-sizing: border-box; }
175
+ body { width: 100vw; height: 100vh; display: flex; flex-direction: column;
176
+ align-items: center; justify-content: center; font-family: sans-serif;
177
+ background: #1a1a2e; color: #eee; }
178
+ #status { margin-bottom: 16px; font-size: 1.2rem; }
179
+ #board { display: grid; grid-template-columns: repeat(3, 100px);
180
+ grid-template-rows: repeat(3, 100px); gap: 4px; }
181
+ .cell { background: #16213e; display: flex; align-items: center;
182
+ justify-content: center; font-size: 2.5rem; cursor: pointer;
183
+ border-radius: 4px; }
184
+ .cell:hover { background: #0f3460; }
185
+ #players { display: flex; gap: 24px; margin-bottom: 12px; }
186
+ .player-info { display: flex; align-items: center; gap: 8px; }
187
+ .player-info img { width: 32px; height: 32px; border-radius: 50%; }
188
+ .avatar-fallback { width: 32px; height: 32px; border-radius: 50%;
189
+ background: #0f3460; display: flex; align-items: center;
190
+ justify-content: center; font-size: 0.9rem; font-weight: bold; }
191
+ </style>
192
+ </head>
193
+ <body>
194
+ <div id="players"></div>
195
+ <div id="status">Loading…</div>
196
+ <div id="board"></div>
197
+ <script>
198
+ let board = Array(9).fill(null);
199
+ let player, opponent, mySymbol, isMyTurn;
200
+ const playersEl = document.getElementById("players");
201
+ const statusEl = document.getElementById("status");
202
+ const boardEl = document.getElementById("board");
203
+
204
+ function avatarHTML(p) {
205
+ if (p.imageUrl) {
206
+ return `<img src="${p.imageUrl}" alt="${p.name}"
207
+ onerror="this.style.display='none';this.nextElementSibling.style.display='flex'">
208
+ <div class="avatar-fallback" style="display:none">${p.name[0]}</div>`;
209
+ }
210
+ return `<div class="avatar-fallback">${p.name[0]}</div>`;
211
+ }
212
+
213
+ function renderPlayers() {
214
+ if (!player || !opponent) return;
215
+ playersEl.innerHTML = [player, opponent].map(p =>
216
+ `<div class="player-info">${avatarHTML(p)}<span>${p.name}</span></div>`
217
+ ).join("");
218
+ }
219
+
220
+ function render() {
221
+ boardEl.innerHTML = "";
222
+ board.forEach((val, i) => {
223
+ const cell = document.createElement("div");
224
+ cell.className = "cell";
225
+ cell.textContent = val ?? "";
226
+ cell.addEventListener("click", () => handleClick(i));
227
+ boardEl.appendChild(cell);
228
+ });
229
+ statusEl.textContent = isMyTurn
230
+ ? `Your turn, ${player.name}`
231
+ : `Waiting for ${opponent.name}…`;
232
+ }
233
+
234
+ function checkWinner() {
235
+ const lines = [
236
+ [0,1,2],[3,4,5],[6,7,8],
237
+ [0,3,6],[1,4,7],[2,5,8],
238
+ [0,4,8],[2,4,6],
239
+ ];
240
+ for (const [a,b,c] of lines) {
241
+ if (board[a] && board[a] === board[b] && board[a] === board[c]) {
242
+ return board[a];
243
+ }
244
+ }
245
+ return board.every(Boolean) ? "draw" : null;
246
+ }
247
+
248
+ function handleClick(i) {
249
+ if (!isMyTurn || board[i]) return;
250
+ board[i] = mySymbol;
251
+ isMyTurn = false;
252
+ render();
253
+
254
+ const winner = checkWinner();
255
+ if (winner) {
256
+ endGame(winner);
257
+ return;
258
+ }
259
+
260
+ window.RemixSDK.multiplayer.actions.saveGameState({
261
+ gameState: { board, lastPlayer: player.id },
262
+ alertUserIds: [opponent.id],
263
+ });
264
+ }
265
+
266
+ function endGame(winner) {
267
+ const iWin = winner === mySymbol;
268
+ const isDraw = winner === "draw";
269
+ statusEl.textContent = isDraw ? "Draw!" : iWin ? "You win!" : "You lose!";
270
+ window.RemixSDK.multiplayer.actions.gameOver({
271
+ scores: [
272
+ { playerId: player.id, score: iWin ? 1 : 0 },
273
+ { playerId: opponent.id, score: !iWin && !isDraw ? 1 : 0 },
274
+ ],
275
+ });
276
+ }
277
+
278
+ async function init() {
279
+ await window.RemixSDK.ready();
280
+
281
+ player = window.RemixSDK.player;
282
+ const players = window.RemixSDK.players;
283
+ opponent = players.find(p => p.id !== player.id);
284
+
285
+ // First player in the array takes the first turn (X)
286
+ mySymbol = players[0].id === player.id ? "X" : "O";
287
+ renderPlayers();
288
+
289
+ // Restore in-progress match
290
+ const saved = window.RemixSDK.gameState;
291
+ if (saved && saved.board) {
292
+ board = saved.board;
293
+ isMyTurn = saved.lastPlayer !== player.id;
294
+ } else {
295
+ board = Array(9).fill(null);
296
+ isMyTurn = mySymbol === "X";
297
+ }
298
+
299
+ render();
300
+
301
+ window.RemixSDK.onGameStateUpdated((data) => {
302
+ if (!data) return;
303
+ const { gameState } = data;
304
+ board = gameState.board;
305
+ isMyTurn = gameState.lastPlayer !== player.id;
306
+ render();
307
+
308
+ const winner = checkWinner();
309
+ if (winner) endGame(winner);
310
+ });
311
+
312
+ window.RemixSDK.onPlayAgain(() => {
313
+ board = Array(9).fill(null);
314
+ isMyTurn = mySymbol === "X";
315
+ render();
316
+ });
317
+
318
+ window.RemixSDK.onToggleMute(({ isMuted }) => {
319
+ // No audio in this example
320
+ });
321
+ }
322
+
323
+ init();
324
+ </script>
325
+ </body>
326
+ </html>
327
+ ```
328
+
329
+ ## Wrong Patterns
330
+
331
+ - **Using `singlePlayer.actions.gameOver`** instead of
332
+ `multiplayer.actions.gameOver` — scores will not be recorded for both players.
333
+ - **Using `singlePlayer.actions.saveGameState`** instead of
334
+ `multiplayer.actions.saveGameState` — the opponent will not receive state
335
+ updates.
336
+ - **Forgetting `alertUserIds`** when saving state — the opponent will not be
337
+ notified that it is their turn.
338
+ - **Not checking `gameState` on startup** — breaks resuming in-progress matches.
339
+ Always check `window.RemixSDK.gameState` after `ready()`.
340
+ - **Hardcoding player IDs** — always read from `window.RemixSDK.player` and
341
+ `window.RemixSDK.players`.
342
+ - **Not showing player identity** — multiplayer games should display names and
343
+ avatars so players know who they are playing against.
344
+
345
+ ## Tips
346
+
347
+ - **Keep state JSON-serializable.** No functions, class instances, or circular
348
+ references.
349
+ - **Validate opponent moves.** Check that the incoming state is legal before
350
+ applying it. Use `refuteGameState` if a move is invalid.
351
+ - **Save at turn boundaries**, not every frame.
352
+ - **Assign symbols deterministically** — use `players[0]` as the first actor
353
+ (e.g. X) so both clients agree on who goes first.
354
+ - **Handle missing `imageUrl`** — the field is optional; use a placeholder or
355
+ initials fallback when it is `undefined`.
@@ -0,0 +1,135 @@
1
+ # Integrate Save Game State Workflow
2
+
3
+ ## Overview
4
+
5
+ This skill guides you through integrating save game state into an HTML game on
6
+ the Remix platform. Save game state lets players persist progress across
7
+ sessions — scores, unlocked levels, inventory, and more — using the RemixSDK.
8
+
9
+ ## Prerequisites
10
+
11
+ - The game must include the RemixSDK script tag:
12
+ ```html
13
+ <script src="https://cdn.jsdelivr.net/npm/@remix-gg/sdk@latest/dist/index.min.js"></script>
14
+ ```
15
+ - The game must already be playable (follow the **game-creation** workflow first
16
+ if starting from scratch).
17
+
18
+ ## Steps
19
+
20
+ ### 1. Initialize the SDK
21
+
22
+ Call `await window.RemixSDK.ready()` **before** the game loop starts. This
23
+ ensures the SDK is loaded and any existing saved state is available.
24
+
25
+ ```js
26
+ await window.RemixSDK.ready();
27
+ ```
28
+
29
+ ### 2. Load Existing State
30
+
31
+ Read `window.RemixSDK.gameState` to get previously saved data. It returns
32
+ `Record<string, unknown> | null | undefined` — always check for null before
33
+ using it.
34
+
35
+ ```js
36
+ const savedState = window.RemixSDK.gameState;
37
+ ```
38
+
39
+ Use the saved state to restore game progress, or fall back to defaults if the
40
+ player is starting fresh:
41
+
42
+ ```js
43
+ const state = savedState ?? { score: 0, level: 1 };
44
+ ```
45
+
46
+ ### 3. Save State During Gameplay
47
+
48
+ Call `saveGameState` whenever the player reaches a meaningful checkpoint. The
49
+ `gameState` value must be a JSON-serializable object.
50
+
51
+ ```js
52
+ window.RemixSDK.singlePlayer.actions.saveGameState({
53
+ gameState: { score: player.score, level: player.level },
54
+ });
55
+ ```
56
+
57
+ ## Examples
58
+
59
+ ### Simple Clicker Game
60
+
61
+ ```html
62
+ <script>
63
+ let clicks = 0;
64
+
65
+ async function init() {
66
+ await window.RemixSDK.ready();
67
+
68
+ // Load
69
+ const gameState = window.RemixSDK.gameState;
70
+ if (gameState && typeof gameState.clicks === "number") {
71
+ clicks = gameState.clicks;
72
+ }
73
+
74
+ document.getElementById("count").textContent = clicks;
75
+ document.getElementById("btn").addEventListener("click", () => {
76
+ clicks++;
77
+ document.getElementById("count").textContent = clicks;
78
+
79
+ // Save after every click
80
+ window.RemixSDK.singlePlayer.actions.saveGameState({
81
+ gameState: { clicks },
82
+ });
83
+ });
84
+ }
85
+
86
+ init();
87
+ </script>
88
+ ```
89
+
90
+ ### Platformer with Level Progression
91
+
92
+ ```html
93
+ <script>
94
+ let level = 1;
95
+ let coins = 0;
96
+
97
+ async function init() {
98
+ await window.RemixSDK.ready();
99
+
100
+ // Load
101
+ const saved = window.RemixSDK.gameState;
102
+ if (saved) {
103
+ level = saved.level ?? 1;
104
+ coins = saved.coins ?? 0;
105
+ }
106
+
107
+ startLevel(level);
108
+ }
109
+
110
+ function onLevelComplete() {
111
+ level++;
112
+ coins += 10;
113
+
114
+ // Save at level transitions
115
+ window.RemixSDK.singlePlayer.actions.saveGameState({
116
+ gameState: { level, coins },
117
+ });
118
+
119
+ startLevel(level);
120
+ }
121
+
122
+ init();
123
+ </script>
124
+ ```
125
+
126
+ ## Tips
127
+
128
+ - **Keep state small.** Only save what's needed to restore the session — avoid
129
+ storing large arrays or transient UI state.
130
+ - **Save at meaningful moments** — level completion, checkpoints, purchases —
131
+ not every frame.
132
+ - **State must be JSON-serializable.** No functions, class instances, or
133
+ circular references.
134
+ - **Always guard against null.** The first time a player loads the game there is
135
+ no saved state.