@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 +1 -1
- package/sdk/BUILD_SDK_INDEX.md +97 -26
package/package.json
CHANGED
package/sdk/BUILD_SDK_INDEX.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Build SDK Index
|
|
2
2
|
|
|
3
|
-
Version: 1.
|
|
4
|
-
Updated: 2026-
|
|
5
|
-
Generated: 2026-
|
|
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;
|
|
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
|
|
222
|
-
- Example: const { stories } = await Twinkle.aiStories.list({
|
|
223
|
-
- async
|
|
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
|
|
226
|
-
- Example: const { stories } = await Twinkle.aiStories.search({ query: searchText,
|
|
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,
|
|
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
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
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
|
-
|
|
545
|
-
|
|
546
|
-
if (
|
|
547
|
-
|
|
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
|
-
|
|
552
|
-
await
|
|
553
|
-
await world.send('emote', { emote: 'wave' });
|
|
623
|
+
await connectWorld();
|
|
624
|
+
await syncPresence();
|
|
554
625
|
```
|
|
555
626
|
|
|
556
627
|
### Play chess against the computer
|