@linxin666/dsh-pet 0.3.3 → 0.3.5

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 (123) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +30 -13
  3. package/README.zh.md +30 -13
  4. package/assets/ouo-neko/pet.json +31 -0
  5. package/assets/ouo-neko/previews/failed.gif +0 -0
  6. package/assets/ouo-neko/previews/idle.gif +0 -0
  7. package/assets/ouo-neko/previews/jumping.gif +0 -0
  8. package/assets/ouo-neko/previews/review.gif +0 -0
  9. package/assets/ouo-neko/previews/running-left.gif +0 -0
  10. package/assets/ouo-neko/previews/running-right.gif +0 -0
  11. package/assets/ouo-neko/previews/running.gif +0 -0
  12. package/assets/ouo-neko/previews/waiting.gif +0 -0
  13. package/assets/ouo-neko/previews/waving.gif +0 -0
  14. package/assets/ouo-neko/spritesheet.webp +0 -0
  15. package/contracts/pet-manifest-v2.schema.json +102 -1
  16. package/contracts/voice-pack-v1.schema.json +23 -28
  17. package/lib/client.js +781 -40
  18. package/lib/client.js.map +1 -1
  19. package/lib/index.js +1567 -482
  20. package/lib/types/chatter.d.ts +68 -46
  21. package/lib/types/chatter.d.ts.map +1 -1
  22. package/lib/types/chatter.js +243 -301
  23. package/lib/types/client/PetDockEntry.d.ts +3 -0
  24. package/lib/types/client/PetDockEntry.d.ts.map +1 -1
  25. package/lib/types/client/PetDockEntry.js +18 -2
  26. package/lib/types/client/PetSprite.d.ts +23 -0
  27. package/lib/types/client/PetSprite.d.ts.map +1 -1
  28. package/lib/types/client/PetSprite.js +49 -45
  29. package/lib/types/client/PluginSettingsCard.d.ts +2 -0
  30. package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
  31. package/lib/types/client/PluginSettingsCard.js +2 -2
  32. package/lib/types/client/drag-stream.d.ts +19 -0
  33. package/lib/types/client/drag-stream.d.ts.map +1 -0
  34. package/lib/types/client/drag-stream.js +26 -0
  35. package/lib/types/client/gameplay-hud.d.ts +50 -0
  36. package/lib/types/client/gameplay-hud.d.ts.map +1 -0
  37. package/lib/types/client/gameplay-hud.js +274 -0
  38. package/lib/types/client/index.d.ts.map +1 -1
  39. package/lib/types/client/index.js +38 -9
  40. package/lib/types/client/locales.d.ts +34 -0
  41. package/lib/types/client/locales.d.ts.map +1 -1
  42. package/lib/types/client/locales.js +36 -0
  43. package/lib/types/client/pet-store.d.ts +7 -1
  44. package/lib/types/client/pet-store.d.ts.map +1 -1
  45. package/lib/types/client/pet-store.js +4 -0
  46. package/lib/types/client/renderers/Frames2dVisualMount.d.ts +29 -0
  47. package/lib/types/client/renderers/Frames2dVisualMount.d.ts.map +1 -0
  48. package/lib/types/client/renderers/Frames2dVisualMount.js +83 -0
  49. package/lib/types/client/renderers/PetRendererSwitch.d.ts +6 -0
  50. package/lib/types/client/renderers/PetRendererSwitch.d.ts.map +1 -1
  51. package/lib/types/client/renderers/PetRendererSwitch.js +14 -1
  52. package/lib/types/client/renderers/frames2d.d.ts +36 -0
  53. package/lib/types/client/renderers/frames2d.d.ts.map +1 -0
  54. package/lib/types/client/renderers/frames2d.js +196 -0
  55. package/lib/types/defaults.d.ts +6 -0
  56. package/lib/types/defaults.d.ts.map +1 -0
  57. package/lib/types/defaults.js +5 -0
  58. package/lib/types/dsh-home.d.ts.map +1 -1
  59. package/lib/types/dsh-home.js +9 -3
  60. package/lib/types/event-projection.d.ts +9 -4
  61. package/lib/types/event-projection.d.ts.map +1 -1
  62. package/lib/types/event-projection.js +46 -14
  63. package/lib/types/gameplay.d.ts +182 -0
  64. package/lib/types/gameplay.d.ts.map +1 -0
  65. package/lib/types/gameplay.js +618 -0
  66. package/lib/types/ledger.d.ts +14 -0
  67. package/lib/types/ledger.d.ts.map +1 -1
  68. package/lib/types/ledger.js +30 -0
  69. package/lib/types/manifest-v2.d.ts +38 -1
  70. package/lib/types/manifest-v2.d.ts.map +1 -1
  71. package/lib/types/manifest-v2.js +182 -2
  72. package/lib/types/persist.d.ts +4 -4
  73. package/lib/types/persist.d.ts.map +1 -1
  74. package/lib/types/persist.js +41 -4
  75. package/lib/types/registry.d.ts +29 -0
  76. package/lib/types/registry.d.ts.map +1 -1
  77. package/lib/types/registry.js +170 -1
  78. package/lib/types/routes.d.ts.map +1 -1
  79. package/lib/types/routes.js +30 -3
  80. package/lib/types/service.d.ts +63 -11
  81. package/lib/types/service.d.ts.map +1 -1
  82. package/lib/types/service.js +208 -19
  83. package/lib/types/voice-pack.d.ts +6 -7
  84. package/lib/types/voice-pack.d.ts.map +1 -1
  85. package/lib/types/voice-pack.js +44 -47
  86. package/package.json +5 -2
  87. package/src/chatter.test.ts +68 -38
  88. package/src/chatter.ts +269 -316
  89. package/src/client/PetDockEntry.test.tsx +6 -0
  90. package/src/client/PetDockEntry.tsx +32 -1
  91. package/src/client/PetSprite.test.tsx +68 -21
  92. package/src/client/PetSprite.tsx +63 -45
  93. package/src/client/PluginSettingsCard.tsx +4 -2
  94. package/src/client/drag-stream.ts +35 -0
  95. package/src/client/gameplay-hud.test.tsx +338 -0
  96. package/src/client/gameplay-hud.tsx +403 -0
  97. package/src/client/index.test.tsx +10 -1
  98. package/src/client/index.ts +46 -11
  99. package/src/client/locales.ts +36 -0
  100. package/src/client/pet-store.ts +10 -1
  101. package/src/client/pet.module.css +192 -29
  102. package/src/client/renderers/Frames2dVisualMount.tsx +111 -0
  103. package/src/client/renderers/PetRendererSwitch.tsx +28 -1
  104. package/src/client/renderers/frames2d.test.ts +137 -0
  105. package/src/client/renderers/frames2d.ts +216 -0
  106. package/src/client/settings-card.module.css +5 -0
  107. package/src/defaults.ts +7 -0
  108. package/src/dsh-home.ts +9 -3
  109. package/src/event-projection.ts +57 -16
  110. package/src/gameplay.test.ts +239 -0
  111. package/src/gameplay.ts +692 -0
  112. package/src/ledger.test.ts +16 -0
  113. package/src/ledger.ts +31 -0
  114. package/src/manifest-v2.test.ts +130 -1
  115. package/src/manifest-v2.ts +181 -2
  116. package/src/persist.test.ts +3 -0
  117. package/src/persist.ts +43 -6
  118. package/src/registry.test.ts +168 -1
  119. package/src/registry.ts +199 -1
  120. package/src/routes.ts +28 -4
  121. package/src/service.ts +240 -29
  122. package/src/voice-pack.test.ts +48 -25
  123. package/src/voice-pack.ts +50 -47
@@ -11,9 +11,11 @@ import { jsx as _jsx } from "react/jsx-runtime";
11
11
  * the registry list — no per-pet component exists.
12
12
  * @module @linxin666/dsh-pet/client/PetDockEntry
13
13
  */
14
- import { useEffect, useSyncExternalStore } from 'react';
14
+ import { useEffect, useRef, useSyncExternalStore } from 'react';
15
15
  import { PetSprite } from "./PetSprite.js";
16
16
  import { PetRendererSwitch } from "./renderers/PetRendererSwitch.js";
17
+ import { createDragStream } from "./drag-stream.js";
18
+ import { GameplayHud } from "./gameplay-hud.js";
17
19
  import styles from './pet.module.css';
18
20
  const DEFAULT_DISPLAY = { visible: true, size: 160, right: 24, bottom: 20 };
19
21
  /**
@@ -33,10 +35,24 @@ export function PetDockEntry(props) {
33
35
  useEffect(() => {
34
36
  ensure();
35
37
  }, [ensure]);
38
+ // Per-pet gameplay wiring: the coordination bus (HUD taps <-> chrome,
39
+ // HUD track overrides <-> frames2d mount) and the shared drag stream.
40
+ const auxRef = useRef(null);
41
+ if (definition !== null && (auxRef.current === null || auxRef.current.id !== definition.id)) {
42
+ auxRef.current = { id: definition.id, bus: {}, drag: createDragStream() };
43
+ }
44
+ const aux = auxRef.current;
45
+ const gameplay = definition?.gameplay;
36
46
  if (visible) {
37
47
  return (_jsx("span", { "data-pet-dock": true, "data-testid": "pet-dock", children: snapshot === null || definition === null
38
48
  ? null
39
- : (_jsx(PetRendererSwitch, { definition: definition, phase: snapshot?.phase ?? 'idle', onPet: props.pet, t: props.t, children: _jsx(PetSprite, { snapshot: snapshot, definition: definition, display: snapshot.display, feedback: feedback, onPet: props.pet, onFeed: props.feed, onHide: props.hide, onDragEnd: props.dragEnd, onRename: props.rename, onOpenSession: props.openSession, onFeedbackDone: props.feedbackDone, t: props.t }) })) }));
49
+ : (_jsx(PetRendererSwitch, { definition: definition, phase: snapshot?.phase ?? 'idle', onPet: props.pet, ...(aux === null ? {} : { drag: aux.drag, bus: aux.bus }), t: props.t, children: _jsx(PetSprite, { snapshot: snapshot, definition: definition, display: snapshot.display, feedback: feedback, onPet: props.pet, onFeed: props.feed, onHide: props.hide, onDragEnd: props.dragEnd, onRename: props.rename, onOpenSession: props.openSession, onFeedbackDone: props.feedbackDone, dragDisabled: snapshot.gameplay?.mode === 'work', ...(gameplay === undefined || aux === null
50
+ ? {}
51
+ : {
52
+ onGameplayTap: (fx, fy) => aux.bus.tap?.(fx, fy),
53
+ onGameplayMenu: () => aux.bus.openCard?.(),
54
+ hud: (_jsx(GameplayHud, { definition: definition, store: store, api: props.gameplay, bus: aux.bus, drag: aux.drag, t: props.t })),
55
+ }), t: props.t }) })) }));
40
56
  }
41
57
  const display = snapshot?.display ?? DEFAULT_DISPLAY;
42
58
  return (_jsx("button", { type: "button", className: styles.summon, style: {
@@ -33,6 +33,8 @@ export interface PetSpriteProps {
33
33
  onHide: () => void;
34
34
  /** Persist a drag position. */
35
35
  onDragEnd: (right: number, bottom: number) => void;
36
+ /** Drag gesture notifications for renderers with a drag track (frames2d). */
37
+ onDraggingChange?: (dragging: boolean) => void;
36
38
  /** Rename the selected pet (persisted by the host). */
37
39
  onRename: (name: string) => void;
38
40
  /** Navigate to the session one status bubble reports on. */
@@ -45,6 +47,27 @@ export interface PetSpriteProps {
45
47
  * renders inside the sprite box, and the atlas load + frame loop skip.
46
48
  */
47
49
  visual?: ReactNode;
50
+ /**
51
+ * Gameplay overlay (miku-pet generalization): rendered inside the float
52
+ * container so hover containment and stacking work unchanged. Absent for
53
+ * pets without a gameplay block.
54
+ */
55
+ hud?: ReactNode;
56
+ /**
57
+ * Gameplay tap sink: receives the tap point as sprite-box fractions
58
+ * (0..1). When present the chrome reports the tap IN ADDITION to the
59
+ * affinity pet; the HUD decides zones/no-ops.
60
+ */
61
+ onGameplayTap?: (fractionX: number, fractionY: number) => void;
62
+ /**
63
+ * Gameplay entry (miku-pet generalization): when present the hover panel
64
+ * renders a 玩法 action that opens/closes the gameplay card. The chrome
65
+ * wires it to the HUD through the per-pet bus (openCard), mirroring the
66
+ * onGameplayTap sink. Absent for pets without a gameplay block.
67
+ */
68
+ onGameplayMenu?: () => void;
69
+ /** Disable the drag gesture (gameplay work mode blocks dragging). */
70
+ dragDisabled?: boolean;
48
71
  /** Locale translate seat (namespace-bound). */
49
72
  t: TranslateNS<typeof NS>;
50
73
  }
@@ -1 +1 @@
1
- {"version":3,"file":"PetSprite.d.ts","sourceRoot":"","sources":["../../../src/client/PetSprite.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAkE,SAAS,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AAGnH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AACnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAIjD,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAA;AAGjC,wEAAwE;AACxE,MAAM,WAAW,cAAc;IAC7B,gDAAgD;IAChD,QAAQ,EAAE,YAAY,GAAG,IAAI,CAAA;IAC7B,8EAA8E;IAC9E,UAAU,EAAE,aAAa,CAAA;IACzB,qDAAqD;IACrD,OAAO,EAAE,gBAAgB,CAAA;IACzB,sCAAsC;IACtC,QAAQ,EAAE,WAAW,GAAG,IAAI,CAAA;IAC5B,8BAA8B;IAC9B,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,sCAAsC;IACtC,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,sCAAsC;IACtC,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,+BAA+B;IAC/B,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IAClD,uDAAuD;IACvD,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,4DAA4D;IAC5D,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IAC1C,2DAA2D;IAC3D,cAAc,EAAE,MAAM,IAAI,CAAA;IAC1B;;;;OAIG;IACH,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,+CAA+C;IAC/C,CAAC,EAAE,WAAW,CAAC,OAAO,EAAE,CAAC,CAAA;CAC1B;AAgGD;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,WAAW,CA0iB5D"}
1
+ {"version":3,"file":"PetSprite.d.ts","sourceRoot":"","sources":["../../../src/client/PetSprite.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAkE,SAAS,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AAGnH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AACnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAIjD,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAA;AAGjC,wEAAwE;AACxE,MAAM,WAAW,cAAc;IAC7B,gDAAgD;IAChD,QAAQ,EAAE,YAAY,GAAG,IAAI,CAAA;IAC7B,8EAA8E;IAC9E,UAAU,EAAE,aAAa,CAAA;IACzB,qDAAqD;IACrD,OAAO,EAAE,gBAAgB,CAAA;IACzB,sCAAsC;IACtC,QAAQ,EAAE,WAAW,GAAG,IAAI,CAAA;IAC5B,8BAA8B;IAC9B,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,sCAAsC;IACtC,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,sCAAsC;IACtC,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,+BAA+B;IAC/B,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IAClD,6EAA6E;IAC7E,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAA;IAC9C,uDAAuD;IACvD,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,4DAA4D;IAC5D,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IAC1C,2DAA2D;IAC3D,cAAc,EAAE,MAAM,IAAI,CAAA;IAC1B;;;;OAIG;IACH,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB;;;;OAIG;IACH,GAAG,CAAC,EAAE,SAAS,CAAA;IACf;;;;OAIG;IACH,aAAa,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IAC9D;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,IAAI,CAAA;IAC3B,qEAAqE;IACrE,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,+CAA+C;IAC/C,CAAC,EAAE,WAAW,CAAC,OAAO,EAAE,CAAC,CAAA;CAC1B;AAgGD;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,WAAW,CAqiB5D"}
@@ -312,6 +312,8 @@ export function PetSprite(props) {
312
312
  // "window is not defined" (slow-runner timing, PetSprite.test.tsx).
313
313
  useEffect(() => () => clearHideTimer(), []);
314
314
  const onPointerDown = (e) => {
315
+ if (props.dragDisabled === true)
316
+ return;
315
317
  e.preventDefault();
316
318
  e.target.setPointerCapture?.(e.pointerId);
317
319
  const current = dragPos ?? { right: display.right, bottom: display.bottom };
@@ -325,8 +327,11 @@ export function PetSprite(props) {
325
327
  return;
326
328
  const dx = e.clientX - drag.startX;
327
329
  const dy = e.clientY - drag.startY;
328
- if (Math.abs(dx) > 4 || Math.abs(dy) > 4)
330
+ if (Math.abs(dx) > 4 || Math.abs(dy) > 4) {
331
+ if (!draggedRef.current)
332
+ props.onDraggingChange?.(true);
329
333
  draggedRef.current = true;
334
+ }
330
335
  const right = clampOffset(drag.right - dx, window.innerWidth - 40);
331
336
  const bottom = clampOffset(drag.bottom - dy, window.innerHeight - 40);
332
337
  setDragPos({ right, bottom });
@@ -335,6 +340,8 @@ export function PetSprite(props) {
335
340
  if (dragRef.current === null)
336
341
  return;
337
342
  dragRef.current = null;
343
+ if (draggedRef.current)
344
+ props.onDraggingChange?.(false);
338
345
  if (dragPos !== null)
339
346
  props.onDragEnd(dragPos.right, dragPos.bottom);
340
347
  };
@@ -353,14 +360,14 @@ export function PetSprite(props) {
353
360
  const statusBubble = feedback === null && sessionBubbles.length === 0
354
361
  ? snapshot?.bubble
355
362
  : undefined;
356
- // The display session's inner whisper (碎碎念) — short inner-voice copy
357
- // woken by the model's output. Instead of a second bubble of its own, a
358
- // fresh whisper takes over the display session's bubble (the stack top, or
359
- // the single status bubble) and re-tints it, so the pet never wears two
360
- // voices at once. Interaction feedback takes over the whole bubble area
361
- // while it plays, so whispers yield to it like status copy.
362
- const whisper = feedback === null ? snapshot?.whisper : undefined;
363
- const bubblePresent = feedback !== null || sessionBubbles.length > 0 || statusBubble !== undefined || whisper !== undefined;
363
+ // Each session's inner whisper (碎碎念) rides its own bubble — short
364
+ // inner-voice copy woken by that session's activity, never the model's or
365
+ // another session's. Instead of a second bubble of its own, a fresh
366
+ // whisper takes over its session's bubble and re-tints it, so the pet
367
+ // never wears two voices at once. Interaction feedback takes over the
368
+ // whole bubble area while it plays, so whispers yield to it like status
369
+ // copy.
370
+ const bubblePresent = feedback !== null || sessionBubbles.length > 0 || statusBubble !== undefined;
364
371
  const displayName = snapshot?.name ?? definition.displayName;
365
372
  // The host-served status decoration (M5, #567); absent = text-only bubbles.
366
373
  const decoration = snapshot?.decoration;
@@ -416,40 +423,37 @@ export function PetSprite(props) {
416
423
  return;
417
424
  clearHideTimer();
418
425
  hideTimerRef.current = window.setTimeout(() => setHovered(false), 300);
419
- }, children: [_jsxs("div", { className: styles.spriteWrap, style: { width: spriteWidth, height: spriteHeight }, children: [_jsx("div", { ref: spriteRef, className: styles.sprite, style: {
420
- width: spriteWidth,
421
- height: spriteHeight,
422
- ...(props.visual === undefined
423
- ? {
424
- backgroundImage: imageReady ? 'url(' + definition.atlasUrl + ')' : undefined,
425
- backgroundSize: (cell.width * columns * spriteScale) + 'px ' + (cell.height * (definition.atlasRows ?? rows.length) * spriteScale) + 'px',
426
- backgroundRepeat: 'no-repeat',
427
- backgroundPosition: '0 0',
428
- }
429
- : {}),
430
- cursor: dragRef.current === null ? 'grab' : 'grabbing',
431
- }, onPointerDown: onPointerDown, onPointerMove: onPointerMove, onPointerUp: onPointerUp, onClick: () => {
432
- // A pointer sequence that moved (dragged) still fires a trailing
433
- // click; skip the pet when that happened.
434
- if (draggedRef.current)
435
- return;
436
- props.onPet();
437
- }, role: "button", "aria-label": definition.displayName, children: props.visual }), _jsx("button", { type: "button", className: styles.closeButton, "aria-label": panelLabel('hide', props.t('pet.hide')), title: panelLabel('hide', props.t('pet.hide')), "data-testid": "pet-close", onPointerDown: (e) => {
438
- // Keep the close control from starting a drag on the sprite.
439
- e.stopPropagation();
440
- }, onClick: (e) => {
441
- // The close control sits beside the pet button; do not pet as a
442
- // side effect of closing the overlay.
443
- e.stopPropagation();
444
- props.onHide();
445
- }, children: "\u00D7" })] }), feedback !== null && (_jsx("div", { ref: bubbleRef, className: clsx(styles.bubble, feedback.kind === 'feed' ? styles.bubbleFeed : styles.bubblePet), children: feedback.text }, feedback.at)), feedback === null && (sessionBubbles.length > 0 || statusBubble !== undefined || whisper !== undefined) && (_jsxs("div", { ref: bubbleRef, className: styles.bubbleStack, onPointerEnter: () => setStackPeek(true), onPointerLeave: () => setStackPeek(false), children: [visibleSessions.map((session, index) => {
446
- // The whisper rides the display session's bubble the stack's
447
- // primary entry (DOM-first, rendered bottom-most by the reversed
448
- // column so it stays glued to the sprite when extras open above).
449
- // The key swap restarts the entrance animation so the mood change
450
- // reads as the bubble re-speaking.
451
- const speaksWhisper = index === 0 && whisper !== undefined;
452
- const bubble = (_jsxs("button", { type: "button", className: clsx(styles.bubble, styles.bubbleStatus, styles.bubbleClickable, speaksWhisper && styles.bubbleWhisper), title: props.t('pet.openSessionHint'), onClick: () => { props.onOpenSession(session.sessionId); }, children: [index === 0 && !speaksWhisper && decoration !== undefined && (_jsx(StatusOrnament, { decoration: decoration, phase: phase })), speaksWhisper ? whisper : session.bubble] }, speaksWhisper ? 'whisper:' + whisper : session.sessionId));
426
+ }, children: [_jsx("div", { className: styles.spriteWrap, style: { width: spriteWidth, height: spriteHeight }, children: _jsx("div", { ref: spriteRef, className: styles.sprite, style: {
427
+ width: spriteWidth,
428
+ height: spriteHeight,
429
+ ...(props.visual === undefined
430
+ ? {
431
+ backgroundImage: imageReady ? 'url(' + definition.atlasUrl + ')' : undefined,
432
+ backgroundSize: (cell.width * columns * spriteScale) + 'px ' + (cell.height * (definition.atlasRows ?? rows.length) * spriteScale) + 'px',
433
+ backgroundRepeat: 'no-repeat',
434
+ backgroundPosition: '0 0',
435
+ }
436
+ : {}),
437
+ cursor: dragRef.current === null ? 'grab' : 'grabbing',
438
+ }, onPointerDown: onPointerDown, onPointerMove: onPointerMove, onPointerUp: onPointerUp, onClick: (e) => {
439
+ // A pointer sequence that moved (dragged) still fires a trailing
440
+ // click; skip the pet when that happened.
441
+ if (draggedRef.current)
442
+ return;
443
+ if (props.onGameplayTap !== undefined && spriteRef.current !== null) {
444
+ const rect = spriteRef.current.getBoundingClientRect();
445
+ if (rect.width > 0 && rect.height > 0) {
446
+ props.onGameplayTap((e.clientX - rect.left) / rect.width, (e.clientY - rect.top) / rect.height);
447
+ }
448
+ }
449
+ props.onPet();
450
+ }, role: "button", "aria-label": definition.displayName, children: props.visual }) }), props.hud, feedback !== null && (_jsx("div", { ref: bubbleRef, className: clsx(styles.bubble, feedback.kind === 'feed' ? styles.bubbleFeed : styles.bubblePet), children: feedback.text }, feedback.at)), feedback === null && (sessionBubbles.length > 0 || statusBubble !== undefined) && (_jsxs("div", { ref: bubbleRef, className: styles.bubbleStack, onPointerEnter: () => setStackPeek(true), onPointerLeave: () => setStackPeek(false), children: [visibleSessions.map((session, index) => {
451
+ // A session's whisper rides ITS OWN bubble (the stack lead when
452
+ // the current session has one, any bubble when the stack is
453
+ // expanded). The key swap restarts the entrance animation so the
454
+ // mood change reads as the bubble re-speaking.
455
+ const speaksWhisper = session.whisper !== undefined;
456
+ const bubble = (_jsxs("button", { type: "button", className: clsx(styles.bubble, styles.bubbleStatus, styles.bubbleClickable, speaksWhisper && styles.bubbleWhisper), title: props.t('pet.openSessionHint'), onClick: () => { props.onOpenSession(session.sessionId); }, children: [index === 0 && !speaksWhisper && decoration !== undefined && (_jsx(StatusOrnament, { decoration: decoration, phase: phase })), session.whisper ?? session.bubble] }, speaksWhisper ? 'whisper:' + session.whisper : session.sessionId));
453
457
  // The primary bubble carries the '+N' badge while other sessions
454
458
  // hide behind it; the badge toggles the pinned (touch) expansion.
455
459
  if (index !== 0 || sessionBubbles.length <= 1)
@@ -462,7 +466,7 @@ export function PetSprite(props) {
462
466
  e.stopPropagation();
463
467
  setStackPinned(open => !open);
464
468
  }, children: stackOpen ? '×' : '+' + String(sessionBubbles.length - 1) })] }, "primary"));
465
- }), sessionBubbles.length === 0 && (statusBubble !== undefined || whisper !== undefined) && (_jsxs("div", { className: clsx(styles.bubble, styles.bubbleStatus, whisper !== undefined && styles.bubbleWhisper), role: "status", "aria-live": "polite", children: [whisper === undefined && decoration !== undefined && (_jsx(StatusOrnament, { decoration: decoration, phase: phase })), whisper ?? statusBubble] }, whisper === undefined ? 'status' : 'whisper:' + whisper))] })), hovered && dragRef.current === null && (_jsx("div", { ref: panelRef, className: clsx(styles.panel, panelAbove && styles.panelAbove), "data-placement": panelAbove ? 'above' : 'below', style: panelAbove && panelLift > 0
469
+ }), sessionBubbles.length === 0 && statusBubble !== undefined && (_jsxs("div", { className: clsx(styles.bubble, styles.bubbleStatus), role: "status", "aria-live": "polite", children: [decoration !== undefined && (_jsx(StatusOrnament, { decoration: decoration, phase: phase })), statusBubble] }, "status"))] })), hovered && dragRef.current === null && (_jsx("div", { ref: panelRef, className: clsx(styles.panel, panelAbove && styles.panelAbove), "data-placement": panelAbove ? 'above' : 'below', style: panelAbove && panelLift > 0
466
470
  ? { marginBottom: panelLift }
467
471
  : undefined, onPointerEnter: () => {
468
472
  // Reaching the panel (or its bridge) must cancel any hide timer
@@ -500,6 +504,6 @@ export function PetSprite(props) {
500
504
  clearHideTimer();
501
505
  setNameDraft(displayName);
502
506
  setRenaming(true);
503
- }, children: panelLabel('rename', props.t('pet.rename')) })), panelShows('hide') && (_jsx("button", { type: "button", className: styles.action, onClick: props.onHide, children: panelLabel('hide', props.t('pet.hide')) }))] })] })) }))] }));
507
+ }, children: panelLabel('rename', props.t('pet.rename')) })), panelShows('hide') && (_jsx("button", { type: "button", className: styles.action, onClick: props.onHide, children: panelLabel('hide', props.t('pet.hide')) })), props.onGameplayMenu !== undefined && (_jsx("button", { type: "button", className: styles.action, onClick: props.onGameplayMenu, children: props.t('pet.gameplay.menu') }))] })] })) }))] }));
504
508
  return createPortal(float, document.body);
505
509
  }
@@ -22,6 +22,8 @@ export interface PluginSettingsCardProps<TKey extends string = string> {
22
22
  titleKey: TKey;
23
23
  /** Locale key of the line describing what this plugin's settings govern. */
24
24
  descriptionKey: TKey;
25
+ /** Optional rich header description rendered instead of the plain t(descriptionKey) text (the plain text stays as the tooltip). */
26
+ descriptionNode?: ReactNode;
25
27
  /** The card's form state: availability, writability, and what a save would do. */
26
28
  state: CardShell;
27
29
  /** Write every staged edit. */
@@ -1 +1 @@
1
- {"version":3,"file":"PluginSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/client/PluginSettingsCard.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"PluginSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/client/PluginSettingsCard.tsx"],"names":[],"mappings":"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"}
@@ -40,8 +40,8 @@ export function PluginSettingsCard(props) {
40
40
  // With alwaysOpen the nav entry already provides the selection, so the
41
41
  // header is a static title row instead of a disclosure button.
42
42
  const header = alwaysOpen === true
43
- ? (_jsxs("div", { className: css.headerStatic, children: [_jsxs("span", { className: css.headText, children: [_jsx("span", { className: css.name, title: title, children: title }), _jsx("span", { className: css.description, title: description, children: description })] }), state.dirty ? _jsx("span", { className: css.pending, title: props.t('settings.unsaved'), children: props.t('settings.unsaved') }) : null] }))
44
- : (_jsxs("button", { type: "button", className: css.header, "aria-expanded": open, "aria-label": `${props.t(open ? 'settings.collapse' : 'settings.expand')}: ${title}`, onClick: () => { setOpen(!open); }, children: [_jsxs("span", { className: css.headText, children: [_jsx("span", { className: css.name, title: title, children: title }), _jsx("span", { className: css.description, title: description, children: description })] }), state.dirty ? _jsx("span", { className: css.pending, title: props.t('settings.unsaved'), children: props.t('settings.unsaved') }) : null, _jsx("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", xmlns: "http://www.w3.org/2000/svg", className: open ? `${css.chevron} ${css.chevronOpen}` : css.chevron, children: _jsx("path", { d: "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z", fill: "currentColor" }) })] }));
43
+ ? (_jsxs("div", { className: css.headerStatic, children: [_jsxs("span", { className: css.headText, children: [_jsx("span", { className: css.name, title: title, children: title }), _jsx("span", { className: css.description, title: description, children: props.descriptionNode ?? description })] }), state.dirty ? _jsx("span", { className: css.pending, title: props.t('settings.unsaved'), children: props.t('settings.unsaved') }) : null] }))
44
+ : (_jsxs("button", { type: "button", className: css.header, "aria-expanded": open, "aria-label": `${props.t(open ? 'settings.collapse' : 'settings.expand')}: ${title}`, onClick: () => { setOpen(!open); }, children: [_jsxs("span", { className: css.headText, children: [_jsx("span", { className: css.name, title: title, children: title }), _jsx("span", { className: css.description, title: description, children: props.descriptionNode ?? description })] }), state.dirty ? _jsx("span", { className: css.pending, title: props.t('settings.unsaved'), children: props.t('settings.unsaved') }) : null, _jsx("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", xmlns: "http://www.w3.org/2000/svg", className: open ? `${css.chevron} ${css.chevronOpen}` : css.chevron, children: _jsx("path", { d: "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z", fill: "currentColor" }) })] }));
45
45
  // The namespace exists but the Host does not serve it to this client (the
46
46
  // official settings allowlist omits third-party namespaces): show a card
47
47
  // that explains the gap instead of vanishing, so a missing card never
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Drag stream — bridges the chrome's pointer drag state onto the renderer
3
+ * contract's { get, subscribe } shape, so renderers with a dedicated drag
4
+ * track (frames2d) can follow the chrome-owned drag gesture. Dispatch is
5
+ * change-only, mirroring the phase stream.
6
+ * @module @linxin666/dsh-pet/client/drag-stream
7
+ */
8
+ /** The renderer-facing drag stream. */
9
+ export interface DragStream {
10
+ /** Whether the chrome reports an active drag gesture. */
11
+ get(): boolean;
12
+ /** Subscribe to drag state changes; returns the unsubscribe. */
13
+ subscribe(listener: (dragging: boolean) => void): () => void;
14
+ /** Feed a fresh drag state; no-op when unchanged. */
15
+ push(dragging: boolean): void;
16
+ }
17
+ /** Create the stream (one per pet activation, owned by the renderer switch). */
18
+ export declare function createDragStream(): DragStream;
19
+ //# sourceMappingURL=drag-stream.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drag-stream.d.ts","sourceRoot":"","sources":["../../../src/client/drag-stream.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,uCAAuC;AACvC,MAAM,WAAW,UAAU;IACzB,yDAAyD;IACzD,GAAG,IAAI,OAAO,CAAA;IACd,gEAAgE;IAChE,SAAS,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,GAAG,MAAM,IAAI,CAAA;IAC5D,qDAAqD;IACrD,IAAI,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAAA;CAC9B;AAED,gFAAgF;AAChF,wBAAgB,gBAAgB,IAAI,UAAU,CAe7C"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Drag stream — bridges the chrome's pointer drag state onto the renderer
3
+ * contract's { get, subscribe } shape, so renderers with a dedicated drag
4
+ * track (frames2d) can follow the chrome-owned drag gesture. Dispatch is
5
+ * change-only, mirroring the phase stream.
6
+ * @module @linxin666/dsh-pet/client/drag-stream
7
+ */
8
+ /** Create the stream (one per pet activation, owned by the renderer switch). */
9
+ export function createDragStream() {
10
+ let current = false;
11
+ const listeners = new Set();
12
+ return {
13
+ get: () => current,
14
+ subscribe(listener) {
15
+ listeners.add(listener);
16
+ return () => { listeners.delete(listener); };
17
+ },
18
+ push(dragging) {
19
+ if (dragging === current)
20
+ return;
21
+ current = dragging;
22
+ for (const listener of [...listeners])
23
+ listener(dragging);
24
+ },
25
+ };
26
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Gameplay HUD — the client half of the manifest 'gameplay' block (miku-pet
3
+ * generalization). One component owns everything the block needs: the stat
4
+ * bars and shop card (menu / shop pages), the touch-zone tap
5
+ * handling, the idle director rolls, the work and sleep loops, and the
6
+ * float-text toasts. It talks to the host through the injected verb API,
7
+ * writes results straight back into the store (the 2 s poll stays the
8
+ * backstop), and steers the frames2d renderer through the per-pet bus.
9
+ * @module @linxin666/dsh-pet/client/GameplayHud
10
+ */
11
+ import { type ReactElement } from 'react';
12
+ import type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots';
13
+ import type { PetDefinition } from '../registry.ts';
14
+ import type { PetGameplayVerbResult } from '../service.ts';
15
+ import type { PetStoreInstance } from './pet-store.ts';
16
+ import type { DragStream } from './drag-stream.ts';
17
+ import type { NS } from './locales.ts';
18
+ /** The gameplay verb API the plugin apply body injects (host-authoritative). */
19
+ export interface GameplayApi {
20
+ touch: (zone?: string) => Promise<PetGameplayVerbResult>;
21
+ setMode: (mode: 'work' | 'sleep' | null) => Promise<PetGameplayVerbResult>;
22
+ workTick: () => Promise<PetGameplayVerbResult>;
23
+ buy: (item: string) => Promise<PetGameplayVerbResult>;
24
+ }
25
+ /**
26
+ * The per-pet coordination bus. The frames2d visual mount registers the
27
+ * track override (setTrack); the HUD registers the sprite tap handler; the
28
+ * chrome calls into whatever is registered. Optional chaining everywhere —
29
+ * either side may be mid-remount during a hot reload.
30
+ */
31
+ export interface GameplayBus {
32
+ setTrack?: (track?: string) => void;
33
+ tap?: (fx: number, fy: number) => void;
34
+ /**
35
+ * Card open/close request from the chrome (the hover panel's 玩法 action):
36
+ * the HUD registers this, and calling it with no argument toggles the card
37
+ * while a boolean pins the state (same chrome -> HUD direction as tap).
38
+ */
39
+ openCard?: (open?: boolean) => void;
40
+ }
41
+ /** The gameplay overlay for one frames2d pet that declares 'gameplay'. */
42
+ export declare function GameplayHud(props: {
43
+ definition: PetDefinition;
44
+ store: PetStoreInstance;
45
+ api: GameplayApi;
46
+ bus: GameplayBus;
47
+ drag: DragStream;
48
+ t: PropsLocale<typeof NS>['t'];
49
+ }): ReactElement | null;
50
+ //# sourceMappingURL=gameplay-hud.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gameplay-hud.d.ts","sourceRoot":"","sources":["../../../src/client/gameplay-hud.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAsE,KAAK,YAAY,EAAE,MAAM,OAAO,CAAA;AAC7G,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AACnD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAE1D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,cAAc,CAAA;AAGtC,gFAAgF;AAChF,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,CAAC,IAAI,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAA;IACxD,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAA;IAC1E,QAAQ,EAAE,MAAM,OAAO,CAAC,qBAAqB,CAAC,CAAA;IAC9C,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAA;CACtD;AAED;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;IACnC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IACtC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;CACpC;AAYD,0EAA0E;AAC1E,wBAAgB,WAAW,CAAC,KAAK,EAAE;IACjC,UAAU,EAAE,aAAa,CAAA;IACzB,KAAK,EAAE,gBAAgB,CAAA;IACvB,GAAG,EAAE,WAAW,CAAA;IAChB,GAAG,EAAE,WAAW,CAAA;IAChB,IAAI,EAAE,UAAU,CAAA;IAChB,CAAC,EAAE,WAAW,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,CAAA;CAC/B,GAAG,YAAY,GAAG,IAAI,CAkVtB"}