@stage5/lumine 0.1.8 → 0.1.10

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stage5/lumine",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "Command line tools for launching Lumine builds on Twinkle.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,8 +1,8 @@
1
1
  # Build SDK Index
2
2
 
3
- Version: 1.25.1
4
- Updated: 2026-05-28
5
- Generated: 2026-05-28T10:24:18.784Z
3
+ Version: 1.26.2
4
+ Updated: 2026-06-09
5
+ Generated: 2026-06-09T01:00:18.637Z
6
6
 
7
7
  ## Notes
8
8
  - This SDK is injected into Build iframes via the Build preview/runtime.
@@ -14,10 +14,10 @@ Generated: 2026-05-28T10:24:18.784Z
14
14
  - Use Twinkle.sharedDb for custom shared multi-user structured data, guestbooks, votes, and append-only run history.
15
15
  - Use Twinkle.subjects.search for in-app subject pickers. Twinkle.mount remains an optional host-provided preselection/context shortcut, not a data API.
16
16
  - Use Twinkle.aiCards for read-only existing public AI Card words and example texts, including word levels for typing games.
17
- - Use Twinkle.aiStories for read-only existing AI Story galleries, readers, quizzes, and remix tools.
17
+ - Use Twinkle.aiStories for read-only existing AI Story galleries, readers, quizzes, topic chapter indexes, and remix tools.
18
18
  - Use Twinkle.grammarbles for public Grammarbles question-bank trainer apps and optional signed-in viewer attempt-history filtering.
19
19
  - Use Twinkle.chess for chess engine play and analysis; app code still owns chess rules, legal moves, board state, and UI.
20
- - Use Twinkle.world for realtime multiplayer rooms, avatar presence, movement, emotes, and lightweight actions; keep durable MMO state in sharedDb/privateDb.
20
+ - Use Twinkle.world for realtime multiplayer rooms, avatar presence, movement, emotes, and lightweight actions; world sessions are disposable and durable MMO state belongs in sharedDb/privateDb.
21
21
  - Use Twinkle.characters.chat for real Zero/Ciel NPC dialogue with shared room context and AI Energy-aware thinking modes.
22
22
  - Twinkle.ai.chat history entries must use { role, content }; map local message.text fields to content before passing history.
23
23
 
@@ -93,10 +93,18 @@ files:read, user:read, users:read, dailyReflections:read, content:read, sharedDb
93
93
  - Returns: { subscription }
94
94
  - Subscribe the current viewer to an app-defined notification channel target.
95
95
  - Example: await Twinkle.notifications.subscribe('room.message', { targetKey: 'room:lobby', launchTarget: { view: 'room', roomId: 'lobby' } });
96
+ - async subscribeMany([{ channelKey, targetKey, launchTarget }]) | scopes: notifications:write
97
+ - Returns: { subscriptions }
98
+ - Subscribe the current viewer to multiple app-defined notification channel targets in one request.
99
+ - Example: await Twinkle.notifications.subscribeMany([{ channelKey: 'room.message', targetKey: 'room:lobby', launchTarget: { view: 'room', roomId: 'lobby' } }]);
96
100
  - async unsubscribe(channelKey, { targetKey }) | scopes: notifications:write
97
101
  - Returns: { subscription: null }
98
102
  - Unsubscribe the current viewer from an app-defined notification channel target.
99
103
  - Example: await Twinkle.notifications.unsubscribe('room.message', { targetKey: 'room:lobby' });
104
+ - async unsubscribeMany([{ channelKey, targetKey }]) | scopes: notifications:write
105
+ - Returns: { subscriptions: [], removed }
106
+ - Unsubscribe the current viewer from multiple app-defined notification channel targets in one request.
107
+ - Example: await Twinkle.notifications.unsubscribeMany([{ channelKey: 'room.message', targetKey: 'room:lobby' }]);
100
108
  - async notifySubscribers(channelKey, { targetKey, eventKey, label, summary, launchTarget, payload }) | scopes: notifications:emit
101
109
  - Returns: { sent }
102
110
  - Notify viewers who opted into an app-defined channel target, without requiring a sharedDb write.
@@ -216,14 +224,18 @@ const result = await Twinkle.characters.chat({ character: 'zero', thinkingMode:
216
224
  - Example: const { card } = await Twinkle.aiCards.get(cardId);
217
225
 
218
226
  ### Twinkle.aiStories
219
- - async list({ limit, cursor, difficulty, type, isListening, userId, hasImage, hasQuestions } = {}) | scopes: content:read
227
+ - async list({ limit, cursor, order, difficulty, type, topicKey, isListening, userId, hasImage, hasQuestions } = {}) | scopes: content:read
220
228
  - Returns: { stories: [{ id, contentType, contentId, topic, topicKey, type, story, explanation, difficulty, isListening, imagePath, imageUrl, audioPath, audioUrl, questions, questionsBy, hasImage, hasQuestions, userId, username, profilePicUrl, timeStamp }], cursor?, pagination: { limit, hasMore, nextCursor }, filters }
221
- - List completed existing user-generated AI Stories newest first for galleries, quiz apps, readers, passage typing, and image/story collections.
222
- - Example: const { stories } = await Twinkle.aiStories.list({ hasImage: true, hasQuestions: true, limit: 12 });
223
- - async search({ query, limit, cursor, difficulty, type, isListening, userId, hasImage, hasQuestions } = {}) | scopes: content:read
229
+ - List completed existing user-generated AI Stories, optionally filtered by exact level/type/topicKey book and ordered newest or oldest first.
230
+ - Example: const { stories } = await Twinkle.aiStories.list({ difficulty: 1, type: 'science', topicKey: 'Astronomy', order: 'oldest', limit: 20 });
231
+ - async chapters({ limit, cursor, difficulty, type, topicKey, isListening, userId, hasImage, hasQuestions } = {}) | scopes: content:read
232
+ - Returns: { chapters: [{ difficulty, type, topicKey, title, sampleTopic, storyCount, readingCount, listeningCount, imageCount, questionCount, latestStoryId, latestTimeStamp }], cursor?, pagination: { limit, hasMore, nextCursor }, filters }
233
+ - List server-built AI Story books grouped by level, type, and topic, with counts and navigation metadata but no story bodies.
234
+ - Example: const page = await Twinkle.aiStories.chapters({ limit: 200 });
235
+ - async search({ query, limit, cursor, order, difficulty, type, topicKey, isListening, userId, hasImage, hasQuestions } = {}) | scopes: content:read
224
236
  - Returns: { stories: [{ id, contentType, contentId, topic, topicKey, type, story, explanation, difficulty, isListening, imagePath, imageUrl, audioPath, audioUrl, questions, questionsBy, hasImage, hasQuestions, userId, username, profilePicUrl, timeStamp }], cursor?, pagination: { limit, hasMore, nextCursor }, filters }
225
- - Search completed existing user-generated AI Stories by topic or story text for galleries, quizzes, readers, and passage typing games.
226
- - Example: const { stories } = await Twinkle.aiStories.search({ query: searchText, hasQuestions: true, limit: 12 });
237
+ - Search completed existing user-generated AI Stories by topic or story text, optionally within an exact level/type/topicKey book.
238
+ - Example: const { stories } = await Twinkle.aiStories.search({ query: searchText, difficulty: 2, type: 'history', topicKey: 'Ancient Rome', order: 'oldest', limit: 12 });
227
239
  - async get(storyId) | scopes: content:read
228
240
  - Returns: { story: { id, contentType, contentId, topic, topicKey, type, story, explanation, difficulty, isListening, imagePath, imageUrl, audioPath, audioUrl, questions, questionsBy, hasImage, hasQuestions, userId, username, profilePicUrl, timeStamp } }
229
241
  - Fetch one completed existing AI Story by id, including story text for passage typing, media URLs, and normalized questions when available.
@@ -304,6 +316,28 @@ const result = await Twinkle.characters.chat({ character: 'zero', thinkingMode:
304
316
  - Example: const world = await Twinkle.world.join({ roomKey: 'town-square', presence: { x: 0, y: 0, z: 0, facing: 'south' }, player: { name: avatarName } });
305
317
  world.subscribe((event) => updateRemotePlayers(event.players));
306
318
  world.updatePresence({ x, y, z, facing });
319
+ - isRecoverableSessionError(error) | scopes: none
320
+ - Returns: boolean
321
+ - Return true when a world request error is expected to be handled by app code instead of crashing.
322
+ - Example: try {
323
+ await world.updatePresence({ x, y, z, facing });
324
+ } catch (error) {
325
+ if (Twinkle.world.isSessionEndedError(error)) {
326
+ world = null;
327
+ scheduleReconnect();
328
+ } else if (Twinkle.world.isRecoverableSessionError(error)) {
329
+ // Drop this transient presence update and keep the current handle.
330
+ } else {
331
+ throw error;
332
+ }
333
+ }
334
+ - isSessionEndedError(error) | scopes: none
335
+ - Returns: boolean
336
+ - Return true when a world request error means the current session handle is stale and app code should reconnect with a fresh Twinkle.world.join call.
337
+ - Example: if (Twinkle.world.isSessionEndedError(error)) {
338
+ world = null;
339
+ scheduleReconnect();
340
+ }
307
341
  - leaveAll() | scopes: none
308
342
  - Returns: void
309
343
  - Leave every active world session in the current iframe.
@@ -530,27 +564,64 @@ await Twinkle.chat.sendMessage('lobby', 'hello');
530
564
  ```
531
565
 
532
566
  ### Realtime MMO town room
533
- Use Twinkle.world for live avatar presence and lightweight room actions, while durable state like inventory and quests stays in sharedDb/privateDb.
567
+ Use Twinkle.world for live avatar presence and lightweight room actions, recover stale session handles, and keep durable state like inventory and quests in sharedDb/privateDb.
534
568
  Keywords: multiplayer, mmo, town, presence, avatars, movement, three.js, realtime
535
569
 
536
570
  ```js
537
- const world = await Twinkle.world.join({
538
- worldKey: 'town',
539
- roomKey: 'square',
540
- presence: { x: 0, y: 0, z: 0, facing: 'south', animation: 'idle' },
541
- player: { name: avatarName }
542
- });
571
+ let world = null;
572
+ let reconnectTimer = 0;
573
+
574
+ async function connectWorld() {
575
+ if (world) return world;
576
+ world = await Twinkle.world.join({
577
+ worldKey: 'town',
578
+ roomKey: 'square',
579
+ presence: { x: 0, y: 0, z: 0, facing: 'south', animation: 'idle' },
580
+ player: { name: avatarName }
581
+ });
582
+
583
+ world.subscribe((event) => {
584
+ renderPlayers(event.players);
585
+ if (event.type === 'session.ended') {
586
+ handleWorldDrop();
587
+ }
588
+ if (event.type === 'action.received' && event.action?.type === 'emote') {
589
+ showEmote(event.sessionId, event.action.data.emote);
590
+ }
591
+ });
592
+ return world;
593
+ }
543
594
 
544
- world.subscribe((event) => {
545
- renderPlayers(event.players);
546
- if (event.type === 'action.received' && event.action?.type === 'emote') {
547
- showEmote(event.sessionId, event.action.data.emote);
595
+ function handleWorldDrop() {
596
+ world = null;
597
+ if (!reconnectTimer) {
598
+ reconnectTimer = setTimeout(() => {
599
+ reconnectTimer = 0;
600
+ connectWorld().catch(handleWorldDrop);
601
+ }, 1000);
548
602
  }
549
- });
603
+ }
604
+
605
+ async function syncPresence() {
606
+ try {
607
+ const session = await connectWorld();
608
+ // Throttle this in the game loop, for example 5-15 times per second.
609
+ await session.updatePresence({ x: player.x, y: player.y, z: player.z, facing });
610
+ } catch (error) {
611
+ if (Twinkle.world.isSessionEndedError(error)) {
612
+ handleWorldDrop();
613
+ return;
614
+ }
615
+ if (Twinkle.world.isRecoverableSessionError(error)) {
616
+ // Drop this transient presence update and keep the current handle.
617
+ return;
618
+ }
619
+ throw error;
620
+ }
621
+ }
550
622
 
551
- // Throttle this in the game loop, for example 5-15 times per second.
552
- await world.updatePresence({ x: player.x, y: player.y, z: player.z, facing });
553
- await world.send('emote', { emote: 'wave' });
623
+ await connectWorld();
624
+ await syncPresence();
554
625
  ```
555
626
 
556
627
  ### Play chess against the computer