star-sdk-cli 0.1.4 → 0.1.6

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 (2) hide show
  1. package/dist/cli.mjs +109 -472
  2. package/package.json +1 -1
package/dist/cli.mjs CHANGED
@@ -61,6 +61,17 @@ Star.game(ctx => {
61
61
  });
62
62
  \`\`\`
63
63
 
64
+ ## Initialization (Required for Leaderboards)
65
+
66
+ If using leaderboards outside the Star platform (local dev, self-hosted), initialize with your game ID:
67
+
68
+ \`\`\`javascript
69
+ import Star from 'star-sdk';
70
+
71
+ // Get your gameId from .starrc (created by: npx star-sdk init "Game Name")
72
+ Star.init({ gameId: 'your-game-id-here' });
73
+ \`\`\`
74
+
64
75
  ## Common Patterns
65
76
 
66
77
  ### Game Over -> Submit Score -> Show Leaderboard
@@ -110,6 +121,9 @@ Only these 17 presets exist:
110
121
  \`\`\`javascript
111
122
  import Star from 'star-sdk';
112
123
 
124
+ // Initialize for leaderboard support (get gameId from .starrc)
125
+ Star.init({ gameId: 'your-game-id' });
126
+
113
127
  Star.game(ctx => {
114
128
  const { canvas, width, height, ctx: c } = ctx;
115
129
  let score = 0;
@@ -314,7 +328,7 @@ var CANVAS_DOCS = `**Installation**
314
328
  First, add the package to your project:
315
329
 
316
330
  \`\`\`bash
317
- yarn add star-dom
331
+ yarn add star-canvas
318
332
  \`\`\`
319
333
 
320
334
  ### Star DOM SDK
@@ -662,7 +676,7 @@ game(({ ctx, width, height, loop, toStagePoint, canvas }) => {
662
676
 
663
677
  \`\`\`ts
664
678
  import { game } from 'star-canvas';
665
- import { createLeaderboard } from '/star-sdk/v1/leaderboard.js';
679
+ import { createLeaderboard } from 'star-leaderboard';
666
680
 
667
681
  const leaderboard = createLeaderboard();
668
682
 
@@ -919,7 +933,86 @@ game(({ ctx, width, height, loop, canvas, toStagePoint }) => {
919
933
  4. \u274C Not clearing state on pointerup \u2192 \`createDrag()\` fixes this
920
934
  5. \u274C Missing \`setPointerCapture()\` (drags break outside canvas) \u2192 **You must add this!**
921
935
 
922
- **Recommendation:** Use \`createDrag()\` + \`setPointerCapture()\` for bulletproof drag-and-drop.`;
936
+ **Recommendation:** Use \`createDrag()\` + \`setPointerCapture()\` for bulletproof drag-and-drop.
937
+
938
+ ### Recipe 9: Image Backgrounds
939
+
940
+ Two patterns for backgrounds: **full-canvas** (unique scenes) or **tileable patterns** (repeating textures).
941
+
942
+ **Full-canvas background (scaled to fit):**
943
+
944
+ \`\`\`ts
945
+ import { game } from 'star-canvas';
946
+
947
+ game(({ ctx, width, height, loop }) => {
948
+ const bg = new Image();
949
+ bg.src = 'https://example.com/background.png'; // Use generated asset URL
950
+
951
+ const player = { x: 320, y: 300 };
952
+
953
+ loop((dt) => {
954
+ // Draw background scaled to canvas (no tiling)
955
+ if (bg.complete) {
956
+ ctx.drawImage(bg, 0, 0, width, height);
957
+ } else {
958
+ ctx.fillStyle = '#1e293b'; // Fallback color while loading
959
+ ctx.fillRect(0, 0, width, height);
960
+ }
961
+
962
+ // Draw game objects on top
963
+ ctx.fillStyle = '#22d3ee';
964
+ ctx.fillRect(player.x - 16, player.y - 16, 32, 32);
965
+ });
966
+ });
967
+ \`\`\`
968
+
969
+ **Tileable pattern background (repeating texture):**
970
+
971
+ \`\`\`ts
972
+ import { game } from 'star-canvas';
973
+
974
+ game(({ ctx, width, height, loop }) => {
975
+ const tile = new Image();
976
+ tile.src = 'https://example.com/grass_tile.png'; // Use generated asset URL (seamlessTile: true)
977
+
978
+ let pattern = null;
979
+ tile.onload = () => {
980
+ pattern = ctx.createPattern(tile, 'repeat');
981
+ };
982
+
983
+ const player = { x: 320, y: 300 };
984
+
985
+ loop((dt) => {
986
+ // Draw tiled background
987
+ if (pattern) {
988
+ ctx.fillStyle = pattern;
989
+ ctx.fillRect(0, 0, width, height);
990
+ } else {
991
+ ctx.fillStyle = '#22c55e'; // Fallback color while loading
992
+ ctx.fillRect(0, 0, width, height);
993
+ }
994
+
995
+ // Draw game objects on top
996
+ ctx.fillStyle = '#3b82f6';
997
+ ctx.fillRect(player.x - 16, player.y - 16, 32, 32);
998
+ });
999
+ });
1000
+ \`\`\`
1001
+
1002
+ **When to use which:**
1003
+
1004
+ | Type | Size | Use Case | Generation Settings |
1005
+ |------|------|----------|---------------------|
1006
+ | Full-canvas | 1024\xD71024 | Unique scenes, landscapes, detailed environments | \`model: "gemini"\`, no \`seamlessTile\` |
1007
+ | Tileable (all directions) | 256\xD7256 or 512\xD7512 | Grass, water, brick, abstract patterns | \`model: "gemini"\`, \`seamlessTile: "both"\` |
1008
+ | Horizontal tiling | 256\xD7512 or similar | Side-scroller parallax layers, horizon lines | \`model: "gemini"\`, \`seamlessTile: "horizontal"\` |
1009
+ | Vertical tiling | 512\xD7256 or similar | Vertical scroller backgrounds | \`model: "gemini"\`, \`seamlessTile: "vertical"\` |
1010
+
1011
+ **Common mistakes:**
1012
+ - \u274C Generating a detailed scene and expecting it to tile \u2192 Use \`seamlessTile\` only for patterns
1013
+ - \u274C Using \`drawImage()\` without size params \u2192 Image won't scale to canvas
1014
+ - \u274C Not handling image load state \u2192 Blank canvas until loaded
1015
+ - \u274C Using \`seamlessTile: "both"\` when you only need one direction \u2192 AI has better success with single-axis tiling`;
923
1016
  var LEADERBOARD_DOCS = `**Installation**
924
1017
 
925
1018
  \`\`\`bash
@@ -944,7 +1037,7 @@ const leaderboard = createLeaderboard();
944
1037
 
945
1038
  \`\`\`javascript
946
1039
  import { createLeaderboard } from 'star-leaderboard';
947
- import { game } from '/star-sdk/v1/dom.js';
1040
+ import { game } from 'star-canvas';
948
1041
 
949
1042
  const leaderboard = createLeaderboard();
950
1043
 
@@ -1033,7 +1126,7 @@ async function gameOver(finalScore) {
1033
1126
 
1034
1127
  \`\`\`javascript
1035
1128
  import { createLeaderboard } from 'star-leaderboard';
1036
- import { game } from '/star-sdk/v1/dom.js';
1129
+ import { game } from 'star-canvas';
1037
1130
 
1038
1131
  const leaderboard = createLeaderboard();
1039
1132
 
@@ -1079,7 +1172,7 @@ async function showCustomLeaderboard() {
1079
1172
 
1080
1173
  \`\`\`javascript
1081
1174
  import { createLeaderboard } from 'star-leaderboard';
1082
- import { game } from '/star-sdk/v1/dom.js';
1175
+ import { game } from 'star-canvas';
1083
1176
 
1084
1177
  const leaderboard = createLeaderboard();
1085
1178
 
@@ -1178,456 +1271,6 @@ const data = await leaderboard.getScores({
1178
1271
  4. **Don't store leaderboard state** - Just call the SDK methods when needed. The platform handles caching.
1179
1272
 
1180
1273
  5. **Works for guests** - Guests get a generated name like "Guest1234". They can sign in later to claim scores.`;
1181
- var MULTIPLAYER_DOCS = `**Installation**
1182
-
1183
- \`\`\`bash
1184
- yarn add star-multiplayer
1185
- \`\`\`
1186
-
1187
- ### Star Multiplayer SDK
1188
-
1189
- **Real-time multiplayer for Star games.** Host-authoritative state sync with automatic room management.
1190
-
1191
- **Import:**
1192
- \`\`\`javascript
1193
- import { createMultiplayer } from 'star-multiplayer';
1194
- const mp = createMultiplayer();
1195
- \`\`\`
1196
-
1197
- **CRITICAL:** Import in JavaScript - don't add \`<script>\` tags.
1198
-
1199
- **Features:**
1200
- - **Host-authoritative** - One player runs game logic, others receive state
1201
- - **Room-based** - Create/join rooms with 6-character invite codes
1202
- - **Auto host migration** - If host leaves, another player becomes host
1203
- - **Built-in lobby UI** - No need to build your own
1204
- - **Works in iframes** - Uses postMessage relay to parent window
1205
-
1206
- ---
1207
-
1208
- **Quick Start:**
1209
-
1210
- \`\`\`javascript
1211
- import { createMultiplayer } from 'star-multiplayer';
1212
- import { game } from '/star-sdk/v1/dom.js';
1213
-
1214
- const mp = createMultiplayer();
1215
-
1216
- game(async ({ ctx, width, height, loop, canvas }) => {
1217
- // 1. Start - auto-finds or creates room, starts immediately
1218
- await mp.start();
1219
-
1220
- // 2. Game state
1221
- let state = { players: {}, ball: { x: width/2, y: height/2, vx: 200, vy: 150 } };
1222
-
1223
- // 3. Receive state updates - update shared objects, keep local player prediction
1224
- mp.onState((s) => {
1225
- state.ball = s.ball;
1226
- for (const [id, p] of Object.entries(s.players)) {
1227
- if (id !== mp.localPlayerId) {
1228
- state.players[id] = p;
1229
- }
1230
- }
1231
- });
1232
-
1233
- // 4. Handle inputs (runs locally for prediction, on host for authoritative state)
1234
- mp.onInput((playerId, input) => {
1235
- if (!state.players[playerId]) {
1236
- state.players[playerId] = { y: height/2 };
1237
- }
1238
- state.players[playerId].y = input.y;
1239
- });
1240
-
1241
- // 5. Game loop
1242
- loop((dt) => {
1243
- // Host: run physics + broadcast state (auto-throttled to 20Hz)
1244
- mp.hostTick(dt, () => {
1245
- state.ball.x += state.ball.vx * dt;
1246
- state.ball.y += state.ball.vy * dt;
1247
- if (state.ball.y < 0 || state.ball.y > height) state.ball.vy *= -1;
1248
- return state;
1249
- });
1250
-
1251
- // Everyone: render with interpolation for smooth movement
1252
- const renderState = mp.getInterpolatedState(dt, state);
1253
- ctx.fillStyle = '#0f172a';
1254
- ctx.fillRect(0, 0, width, height);
1255
- for (const p of Object.values(renderState.players)) {
1256
- ctx.fillStyle = '#22d3ee';
1257
- ctx.fillRect(20, p.y - 40, 10, 80);
1258
- }
1259
- ctx.beginPath();
1260
- ctx.arc(renderState.ball.x, renderState.ball.y, 10, 0, Math.PI * 2);
1261
- ctx.fill();
1262
- });
1263
-
1264
- // 6. Input - works for EVERYONE (applied locally for instant feedback)
1265
- canvas.onpointermove = (e) => {
1266
- const rect = canvas.getBoundingClientRect();
1267
- mp.input({ y: ((e.clientY - rect.top) / rect.height) * height });
1268
- };
1269
- });
1270
- \`\`\`
1271
-
1272
- **That's it!** The SDK handles:
1273
- - Auto-matchmaking (finds open room or creates one)
1274
- - URL detection (\`?room=CODE\` \u2192 joins specific room)
1275
- - Throttling state broadcasts to 20Hz
1276
- - Routing host's inputs to their own handler
1277
-
1278
- ---
1279
-
1280
- **Architecture:**
1281
-
1282
- \`\`\`
1283
- Host (Player 1) Server Client (Player 2)
1284
- \u2502 \u2502 \u2502
1285
- \u2502 \u2500\u2500 state \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2502\u2500\u2500 state \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25B6\u2502
1286
- \u2502 \u2502 \u2502
1287
- \u2502\u25C0\u2500\u2500 input \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2502\u2500\u2500 input \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2502
1288
- \u2502 \u2502 \u2502
1289
- \u2502 (runs game logic) \u2502 (dumb relay) \u2502 (renders state)
1290
- \`\`\`
1291
-
1292
- ---
1293
-
1294
- **API Reference:**
1295
-
1296
- **State (read-only):**
1297
- \`\`\`javascript
1298
- mp.isHost // true if you're the host
1299
- mp.isReady // true if game has started
1300
- mp.players // Array of { id: string }
1301
- mp.localPlayerId // Your player ID
1302
- mp.roomCode // Current room code
1303
- \`\`\`
1304
-
1305
- **Setup:**
1306
- \`\`\`javascript
1307
- await mp.start(); // Uses all defaults - auto-matchmaking, 8 players
1308
-
1309
- // Or customize:
1310
- await mp.start({
1311
- maxPlayers: 8, // default: 8 (max players per room)
1312
- mode: 'auto', // default: 'auto' - finds/creates rooms automatically
1313
- // 'private' - shows lobby with shareable code
1314
- showLobby: true, // default: true (only used in 'private' mode)
1315
- tickRate: 50, // default: 50ms (20Hz state broadcasts)
1316
- prediction: true // default: true (client-side prediction for instant input)
1317
- });
1318
-
1319
- // Examples:
1320
- await mp.start({ maxPlayers: 2 }); // 1v1 game
1321
- await mp.start({ maxPlayers: 100 }); // Battle royale
1322
- await mp.start({ mode: 'private' }); // Friends-only with invite code
1323
- await mp.start({ mode: 'private', maxPlayers: 4 }); // Small private game
1324
- \`\`\`
1325
-
1326
- **Sync:**
1327
- \`\`\`javascript
1328
- mp.onState((state) => { ... }) // Receive state (returns unsubscribe)
1329
- mp.onInput((playerId, input) => { ... }) // Handle continuous input (position, aim)
1330
- mp.hostTick(dt, () => state) // Run on host, broadcast returned state
1331
- mp.input(data) // Send continuous input (position, aim)
1332
- mp.getInterpolatedState(dt, state) // Get smoothly interpolated state for rendering
1333
- \`\`\`
1334
-
1335
- **Discrete Events (shooting, jumping, using items):**
1336
- \`\`\`javascript
1337
- mp.event(type, data) // Send event - guaranteed delivery
1338
- mp.onEvent((playerId, type, data) => { }) // Handle events (HOST ONLY, authoritative)
1339
- \`\`\`
1340
-
1341
- **Player Updates:**
1342
- \`\`\`javascript
1343
- mp.onLocalPlayerUpdate((player, prevPlayer) => { ... }) // Local player changed
1344
- mp.onPlayerUpdate(playerId, (player, prevPlayer) => { ... }) // Specific player changed
1345
- \`\`\`
1346
-
1347
- **Lifecycle Events:**
1348
- \`\`\`javascript
1349
- mp.onPlayers((players) => { ... }) // Player list changed
1350
- mp.onPlayerJoin((player) => { ... }) // Another player joined (use to init their state)
1351
- mp.onPlayerLeave((player) => { ... }) // A player left
1352
- mp.onHost((isHost) => { ... }) // Host status changed (for host migration)
1353
- mp.onError((message) => { ... }) // Error occurred
1354
- \`\`\`
1355
-
1356
- **Cleanup:**
1357
- \`\`\`javascript
1358
- mp.leave() // Leave room
1359
- mp.destroy() // Full cleanup
1360
- \`\`\`
1361
-
1362
- ---
1363
-
1364
- **Tips:**
1365
-
1366
- 1. **Use \`hostTick()\`** - It handles host detection and throttling. Don't use \`setInterval\`.
1367
-
1368
- 2. **\`input()\` works on host** - Host's \`input()\` calls their \`onInput\` handler directly. Same code for everyone.
1369
-
1370
- 3. **Handle host migration** - If host leaves, \`onHost(true)\` fires on new host. They should start running physics.
1371
-
1372
- 4. **State design** - Only sync what changes: positions, scores, game objects. Not constants or textures.
1373
-
1374
- 5. **Auto mode is default** - Players join instantly without friction. Use \`mode: 'private'\` only if you need invite codes.
1375
-
1376
- 6. **Scale with maxPlayers** - Set \`maxPlayers: 2\` for 1v1, \`maxPlayers: 50\` for larger games. Default is 8.
1377
-
1378
- 7. **Built-in prediction** - Your \`onInput\` handler runs immediately for local player input, eliminating input lag. Use \`mp.getInterpolatedState(dt, state)\` for smooth rendering of all players.
1379
-
1380
- 8. **State pattern** - In \`onState\`, update shared objects (ball, score) but don't overwrite local player (keep your prediction). The SDK handles interpolation for other players.
1381
-
1382
- 9. **Player count** - Use \`mp.players.length\` for player count in UI. Don't use \`Object.keys(state.players).length\` - that only counts players who've sent input.
1383
-
1384
- 10. **Use \`mp.event()\` for discrete actions** - \`mp.input()\` is for continuous state (position, aim). Use \`mp.event('shoot', data)\` for discrete actions (shooting, jumping) - SDK guarantees delivery.
1385
-
1386
- ---
1387
-
1388
- **Where Code Runs (Important!):**
1389
-
1390
- | Code Type | Where | Why |
1391
- |-----------|-------|-----|
1392
- | Physics, collision | Host (\`hostTick\`) | Single source of truth |
1393
- | Hit detection, damage | Host (\`onInput\`) | Prevents cheating |
1394
- | State changes (health, score) | Host | Authoritative |
1395
- | UI updates, messages | **Everyone** (\`onState\` or \`loop\`) | Each player sees their own |
1396
- | Visual/sound effects | **Everyone** | Local feedback |
1397
- | Input sending | **Everyone** (\`mp.input()\`) | All players send inputs |
1398
-
1399
- **Golden rule:**
1400
- - **CHANGES state** \u2192 host only
1401
- - **REACTS to state** \u2192 everyone locally
1402
-
1403
- \`\`\`javascript
1404
- // \u274C WRONG - UI in host-only code
1405
- mp.hostTick(dt, () => {
1406
- if (player.health <= 0) showDeathMessage(); // Only host sees!
1407
- return state;
1408
- });
1409
-
1410
- // \u2705 RIGHT - React to state on every client
1411
- mp.onState((state) => {
1412
- const wasAlive = localPlayer.health > 0;
1413
- localPlayer.health = state.players[mp.localPlayerId].health;
1414
- if (wasAlive && localPlayer.health <= 0) {
1415
- showDeathMessage(); // Everyone sees their own death!
1416
- }
1417
- });
1418
- \`\`\`
1419
-
1420
- ---
1421
-
1422
- **Common Patterns:**
1423
-
1424
- ### Player Initialization (Important!)
1425
-
1426
- Initialize player state when they join, not when they first send input:
1427
-
1428
- \`\`\`javascript
1429
- // \u274C WRONG - Players invisible until they move
1430
- mp.onInput((playerId, input) => {
1431
- if (!state.players[playerId]) {
1432
- state.players[playerId] = { x: 0, y: 0, health: 100 }; // Too late!
1433
- }
1434
- });
1435
-
1436
- // \u2705 RIGHT - Initialize on join
1437
- mp.onPlayerJoin((player) => {
1438
- state.players[player.id] = {
1439
- x: Math.random() * width,
1440
- y: Math.random() * height,
1441
- health: 100
1442
- };
1443
- });
1444
-
1445
- mp.onPlayerLeave((player) => {
1446
- delete state.players[player.id];
1447
- });
1448
- \`\`\`
1449
-
1450
- **Why this matters:** Without \`onPlayerJoin\`, a spectating player (not sending input) would be invisible to everyone.
1451
-
1452
- ### Position Sync (3D/Complex Games)
1453
-
1454
- Clients send position, host tracks all players:
1455
-
1456
- \`\`\`javascript
1457
- // Everyone: Send position every frame
1458
- mp.input({
1459
- pos: [localPlayer.x, localPlayer.y, localPlayer.z],
1460
- yaw: localPlayer.yaw
1461
- });
1462
-
1463
- // Host: Track in onInput
1464
- const remotePlayers = {};
1465
- mp.onInput((playerId, input) => {
1466
- if (!remotePlayers[playerId]) remotePlayers[playerId] = { health: 100 };
1467
- if (input.pos) {
1468
- remotePlayers[playerId].pos = input.pos;
1469
- remotePlayers[playerId].yaw = input.yaw;
1470
- }
1471
- });
1472
- \`\`\`
1473
-
1474
- ### Actions (Shooting, Jumping, Items) - Use \`mp.event()\`
1475
-
1476
- **For discrete actions, use \`mp.event()\` instead of \`mp.input()\`.** The SDK guarantees delivery, deduplication, and preserves each event's data.
1477
-
1478
- **The Pattern:**
1479
-
1480
- \`\`\`javascript
1481
- // \u274C WRONG - mp.input() loses rapid discrete actions (30Hz throttling)
1482
- mp.input({ shoot: true }); // 3 rapid clicks = 1 shot!
1483
-
1484
- // \u2705 RIGHT - Two parts: cosmetic (instant) + authoritative (host)
1485
-
1486
- // PART 1: At the trigger point (EVERYONE runs this)
1487
- if (shooting && canShoot(localPlayer)) {
1488
- // Cosmetic feedback - instant, local only
1489
- audio.play('shoot');
1490
- particles.muzzleFlash(localPlayer.pos);
1491
-
1492
- // Send event to host for authoritative processing
1493
- mp.event('shoot', {
1494
- origin: [x, y, z],
1495
- dir: [dx, dy, dz]
1496
- });
1497
- }
1498
-
1499
- // PART 2: Authoritative handler (HOST ONLY processes this)
1500
- mp.onEvent((playerId, type, data) => {
1501
- if (type === 'shoot') {
1502
- // This creates the REAL bullet in game state
1503
- state.bullets.push({
1504
- owner: playerId,
1505
- pos: data.origin,
1506
- dir: data.dir
1507
- });
1508
- }
1509
- });
1510
- \`\`\`
1511
-
1512
- **Why two parts?**
1513
-
1514
- | Part | Who runs it | What it does |
1515
- |------|-------------|--------------|
1516
- | Cosmetic (at call site) | Everyone | Instant feedback: sound, particles, animations |
1517
- | Authoritative (\`onEvent\`) | Host only | State changes: spawn bullets, apply damage |
1518
-
1519
- **This separation is intentional:**
1520
- - **Cosmetic:** Your own shots feel instant (no network latency)
1521
- - **Authoritative:** Game state is consistent (host is source of truth)
1522
- - **Other players' bullets:** Appear in state broadcast, ~50ms later (acceptable for casual games)
1523
-
1524
- **Hearing other players' shots (optional):**
1525
- \`\`\`javascript
1526
- const seenBullets = new Set();
1527
- mp.onState((state) => {
1528
- for (const bullet of state.bullets || []) {
1529
- if (!seenBullets.has(bullet.id) && bullet.owner !== mp.localPlayerId) {
1530
- audio.play('shoot'); // Hear remote player's shot
1531
- seenBullets.add(bullet.id);
1532
- }
1533
- }
1534
- });
1535
- \`\`\`
1536
-
1537
- **When to use which:**
1538
- | Action Type | Method | Example |
1539
- |-------------|--------|---------|
1540
- | Continuous state | \`mp.input()\` | Position, aim direction, movement |
1541
- | Discrete action | \`mp.event()\` | Shooting, jumping, using items, emotes |
1542
-
1543
- ### Health (Host-Authoritative)
1544
-
1545
- Host owns health. Clients accept and react:
1546
-
1547
- \`\`\`javascript
1548
- // \u274C WRONG - Client sends health (cheatable!)
1549
- mp.input({ health: localPlayer.health });
1550
-
1551
- // \u2705 RIGHT - Host applies damage
1552
- mp.onInput((playerId, input) => {
1553
- if (hit.playerId) remotePlayers[hit.playerId].health -= 25;
1554
- });
1555
-
1556
- // \u2705 RIGHT - Client accepts AND reacts
1557
- mp.onState((state) => {
1558
- const myHealth = state.players[mp.localPlayerId]?.health;
1559
- if (myHealth !== undefined) {
1560
- if (localPlayer.health > 0 && myHealth <= 0) showDeathMessage();
1561
- localPlayer.health = myHealth;
1562
- }
1563
- });
1564
- \`\`\`
1565
-
1566
- ### Reacting to Player State Changes (Death, Score, etc.)
1567
-
1568
- Use \`onLocalPlayerUpdate\` to react when your player's state changes - the SDK tracks previous state for you:
1569
-
1570
- \`\`\`javascript
1571
- mp.onLocalPlayerUpdate((player, prevPlayer) => {
1572
- // Detect death
1573
- if (prevPlayer.health > 0 && player.health <= 0) {
1574
- showDeathScreen();
1575
- }
1576
-
1577
- // Detect respawn
1578
- if (prevPlayer.health <= 0 && player.health > 0) {
1579
- hideDeathScreen();
1580
- }
1581
-
1582
- // Detect score increase
1583
- if (player.score > (prevPlayer.score || 0)) {
1584
- showScorePopup(player.score - prevPlayer.score);
1585
- }
1586
- });
1587
- \`\`\`
1588
-
1589
- For remote player effects (death animations, etc.):
1590
-
1591
- \`\`\`javascript
1592
- // Subscribe to each remote player's updates
1593
- for (const p of mp.players) {
1594
- if (p.id !== mp.localPlayerId) {
1595
- mp.onPlayerUpdate(p.id, (player, prev) => {
1596
- if (prev.health > 0 && player.health <= 0) {
1597
- playDeathAnimation(p.id);
1598
- }
1599
- });
1600
- }
1601
- }
1602
- \`\`\`
1603
-
1604
- ---
1605
-
1606
- **Anti-Patterns:**
1607
-
1608
- ### Using \`mp.input()\` for Discrete Actions
1609
-
1610
- **Problem**: Using \`mp.input({ shoot: true })\` for shooting, jumping, or any discrete action.
1611
-
1612
- **Why it breaks**: \`mp.input()\` is throttled to 30Hz. Multiple rapid clicks collapse into a single value, losing inputs.
1613
-
1614
- \`\`\`javascript
1615
- // \u274C BAD - mp.input() loses rapid discrete actions
1616
- mp.input({ shoot: true }); // 3 rapid clicks = 1 shot!
1617
-
1618
- // \u274C ALSO BAD - counters in mp.input() still have issues
1619
- mp.input({ shootCount: count }); // Bursts all shots at once, loses timing
1620
-
1621
- // \u2705 GOOD - Use mp.event() for discrete actions
1622
- mp.event('shoot', { origin, dir }); // Each event preserved with its data
1623
- \`\`\`
1624
-
1625
- **The rule is simple:**
1626
-
1627
- | Action Type | Method | Why |
1628
- |-------------|--------|-----|
1629
- | **Continuous** (position, aim) | \`mp.input()\` | Last value is correct, throttling is fine |
1630
- | **Discrete** (shoot, jump, use item) | \`mp.event()\` | Each action must be delivered with its data |`;
1631
1274
 
1632
1275
  // src/cli.ts
1633
1276
  var VERSION = "0.1.0";
@@ -1767,11 +1410,14 @@ async function initCommand(name, email) {
1767
1410
  log(`${colors.dim}Config saved to${colors.reset} ${colors.bright}.starrc${colors.reset}`);
1768
1411
  log("");
1769
1412
  log(`${colors.dim}Next steps:${colors.reset}`);
1770
- log(` 1. Import Star SDK in your game`);
1771
- log(` 2. Use Star.leaderboard.submit(score) to submit scores`);
1413
+ log(` 1. Install: ${colors.cyan}npm install star-sdk${colors.reset}`);
1414
+ log(` 2. Import Star SDK in your game`);
1415
+ log(` 3. Initialize with your game ID`);
1416
+ log(` 4. Use Star.leaderboard.submit(score) to submit scores`);
1772
1417
  log("");
1773
1418
  log(`${colors.dim}Example:${colors.reset}`);
1774
1419
  log(` ${colors.cyan}import Star from 'star-sdk';${colors.reset}`);
1420
+ log(` ${colors.cyan}Star.init({ gameId: '${data.gameId}' });${colors.reset}`);
1775
1421
  log(` ${colors.cyan}Star.leaderboard.submit(1500);${colors.reset}`);
1776
1422
  log("");
1777
1423
  log(`${colors.dim}Using an AI coding agent?${colors.reset}`);
@@ -1813,7 +1459,7 @@ function whoamiCommand() {
1813
1459
  function transformForStdout(content) {
1814
1460
  return content.replace(/\[(\w+)\.md\]\(\.\/\1\.md\)/g, "$1 (`npx star-sdk docs $1`)").replace(/see (\w+)\.md/g, "run `npx star-sdk docs $1`").replace(
1815
1461
  /For detailed API documentation, see the linked files above\./,
1816
- "For detailed API docs: `npx star-sdk docs <topic>` where topic is audio, canvas, leaderboard, or multiplayer."
1462
+ "For detailed API docs: `npx star-sdk docs <topic>` where topic is audio, canvas, or leaderboard."
1817
1463
  );
1818
1464
  }
1819
1465
  function getAllDocsContent() {
@@ -1835,13 +1481,7 @@ ${CANVAS_DOCS}
1835
1481
 
1836
1482
  # Leaderboard API
1837
1483
 
1838
- ${LEADERBOARD_DOCS}
1839
-
1840
- ---
1841
-
1842
- # Multiplayer API
1843
-
1844
- ${MULTIPLAYER_DOCS}`;
1484
+ ${LEADERBOARD_DOCS}`;
1845
1485
  }
1846
1486
  function installClaudeCode(scope) {
1847
1487
  const skillDir = scope === "global" ? path.join(os.homedir(), ".claude", "skills", "star-sdk") : path.join(process.cwd(), ".claude", "skills", "star-sdk");
@@ -1850,7 +1490,6 @@ function installClaudeCode(scope) {
1850
1490
  fs.writeFileSync(path.join(skillDir, "audio.md"), AUDIO_DOCS);
1851
1491
  fs.writeFileSync(path.join(skillDir, "canvas.md"), CANVAS_DOCS);
1852
1492
  fs.writeFileSync(path.join(skillDir, "leaderboard.md"), LEADERBOARD_DOCS);
1853
- fs.writeFileSync(path.join(skillDir, "multiplayer.md"), MULTIPLAYER_DOCS);
1854
1493
  log("");
1855
1494
  success(`Star SDK skill installed to ${skillDir}`);
1856
1495
  log("");
@@ -1861,7 +1500,6 @@ function installClaudeCode(scope) {
1861
1500
  log(` audio.md ${colors.dim}Star.audio API docs${colors.reset}`);
1862
1501
  log(` canvas.md ${colors.dim}Star.game() API docs${colors.reset}`);
1863
1502
  log(` leaderboard.md ${colors.dim}Star.leaderboard API docs${colors.reset}`);
1864
- log(` multiplayer.md ${colors.dim}Star.multiplayer API docs${colors.reset}`);
1865
1503
  log("");
1866
1504
  }
1867
1505
  function installCodex(scope) {
@@ -1892,7 +1530,7 @@ function installCursor() {
1892
1530
  const rulesDir = path.join(process.cwd(), ".cursor", "rules");
1893
1531
  const filePath = path.join(rulesDir, "star-sdk.mdc");
1894
1532
  const content = `---
1895
- description: Star SDK for browser game development with audio, canvas, leaderboards, and multiplayer
1533
+ description: Star SDK for browser game development with audio, canvas, and leaderboards
1896
1534
  globs: ["**/*.js", "**/*.ts", "**/*.jsx", "**/*.tsx", "**/*.html"]
1897
1535
  alwaysApply: false
1898
1536
  ---
@@ -1976,8 +1614,7 @@ function docsCommand(topic) {
1976
1614
  skill: SKILL_CONTENT,
1977
1615
  audio: AUDIO_DOCS,
1978
1616
  canvas: CANVAS_DOCS,
1979
- leaderboard: LEADERBOARD_DOCS,
1980
- multiplayer: MULTIPLAYER_DOCS
1617
+ leaderboard: LEADERBOARD_DOCS
1981
1618
  };
1982
1619
  if (!topic) {
1983
1620
  console.log(transformForStdout(SKILL_CONTENT));
@@ -1986,7 +1623,7 @@ function docsCommand(topic) {
1986
1623
  } else {
1987
1624
  error(`Unknown topic: ${topic}`);
1988
1625
  log("");
1989
- log("Available topics: skill, audio, canvas, leaderboard, multiplayer");
1626
+ log("Available topics: skill, audio, canvas, leaderboard");
1990
1627
  process.exit(1);
1991
1628
  }
1992
1629
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "star-sdk-cli",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "CLI tool for Star SDK - register games and manage leaderboards",
5
5
  "type": "module",
6
6
  "bin": {