@stage5/lumine 0.1.9 → 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.9",
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.26.1
4
- Updated: 2026-06-08
5
- Generated: 2026-06-08T05:37:13.752Z
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.
@@ -17,7 +17,7 @@ Generated: 2026-06-08T05:37:13.752Z
17
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
 
@@ -316,6 +316,28 @@ const result = await Twinkle.characters.chat({ character: 'zero', thinkingMode:
316
316
  - Example: const world = await Twinkle.world.join({ roomKey: 'town-square', presence: { x: 0, y: 0, z: 0, facing: 'south' }, player: { name: avatarName } });
317
317
  world.subscribe((event) => updateRemotePlayers(event.players));
318
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
+ }
319
341
  - leaveAll() | scopes: none
320
342
  - Returns: void
321
343
  - Leave every active world session in the current iframe.
@@ -542,27 +564,64 @@ await Twinkle.chat.sendMessage('lobby', 'hello');
542
564
  ```
543
565
 
544
566
  ### Realtime MMO town room
545
- 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.
546
568
  Keywords: multiplayer, mmo, town, presence, avatars, movement, three.js, realtime
547
569
 
548
570
  ```js
549
- const world = await Twinkle.world.join({
550
- worldKey: 'town',
551
- roomKey: 'square',
552
- presence: { x: 0, y: 0, z: 0, facing: 'south', animation: 'idle' },
553
- player: { name: avatarName }
554
- });
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
+ }
555
594
 
556
- world.subscribe((event) => {
557
- renderPlayers(event.players);
558
- if (event.type === 'action.received' && event.action?.type === 'emote') {
559
- 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);
560
602
  }
561
- });
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
+ }
562
622
 
563
- // Throttle this in the game loop, for example 5-15 times per second.
564
- await world.updatePresence({ x: player.x, y: player.y, z: player.z, facing });
565
- await world.send('emote', { emote: 'wave' });
623
+ await connectWorld();
624
+ await syncPresence();
566
625
  ```
567
626
 
568
627
  ### Play chess against the computer