@linxin666/dsh-pet 0.2.3 → 0.2.4

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 (70) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +64 -1
  3. package/README.zh.md +64 -1
  4. package/assets/decorations/whale/decoration.json +20 -0
  5. package/assets/decorations/whale/whale-frames.png +0 -0
  6. package/contracts/pet-manifest-v2.schema.json +281 -0
  7. package/contracts/status-decoration-v1.schema.json +144 -0
  8. package/contracts/voice-pack-v1.schema.json +234 -0
  9. package/lib/client.js +144 -18
  10. package/lib/client.js.map +1 -1
  11. package/lib/index.js +979 -70
  12. package/lib/types/chatter.d.ts +61 -3
  13. package/lib/types/chatter.d.ts.map +1 -1
  14. package/lib/types/chatter.js +71 -12
  15. package/lib/types/client/PetSettingsCard.d.ts +4 -0
  16. package/lib/types/client/PetSettingsCard.d.ts.map +1 -1
  17. package/lib/types/client/PetSettingsCard.js +3 -1
  18. package/lib/types/client/PetSprite.d.ts.map +1 -1
  19. package/lib/types/client/PetSprite.js +103 -4
  20. package/lib/types/client/locales.d.ts +4 -0
  21. package/lib/types/client/locales.d.ts.map +1 -1
  22. package/lib/types/client/locales.js +4 -0
  23. package/lib/types/client/renderers/live2d.d.ts.map +1 -1
  24. package/lib/types/client/renderers/live2d.js +13 -1
  25. package/lib/types/client/settings-form.d.ts.map +1 -1
  26. package/lib/types/client/settings-form.js +9 -6
  27. package/lib/types/contracts/status-decoration.d.ts +85 -0
  28. package/lib/types/contracts/status-decoration.d.ts.map +1 -0
  29. package/lib/types/contracts/status-decoration.js +21 -0
  30. package/lib/types/decoration.d.ts +39 -0
  31. package/lib/types/decoration.d.ts.map +1 -0
  32. package/lib/types/decoration.js +210 -0
  33. package/lib/types/event-projection.d.ts +8 -3
  34. package/lib/types/event-projection.d.ts.map +1 -1
  35. package/lib/types/event-projection.js +9 -4
  36. package/lib/types/index.d.ts +2 -0
  37. package/lib/types/index.d.ts.map +1 -1
  38. package/lib/types/index.js +2 -0
  39. package/lib/types/registry.d.ts +55 -2
  40. package/lib/types/registry.d.ts.map +1 -1
  41. package/lib/types/registry.js +198 -16
  42. package/lib/types/routes.d.ts.map +1 -1
  43. package/lib/types/routes.js +131 -3
  44. package/lib/types/service.d.ts +33 -0
  45. package/lib/types/service.d.ts.map +1 -1
  46. package/lib/types/service.js +42 -3
  47. package/lib/types/voice-pack.d.ts +98 -0
  48. package/lib/types/voice-pack.d.ts.map +1 -0
  49. package/lib/types/voice-pack.js +384 -0
  50. package/package.json +11 -10
  51. package/src/chatter.test.ts +89 -2
  52. package/src/chatter.ts +120 -14
  53. package/src/client/PetSettingsCard.tsx +18 -0
  54. package/src/client/PetSprite.test.tsx +254 -12
  55. package/src/client/PetSprite.tsx +142 -25
  56. package/src/client/locales.ts +4 -0
  57. package/src/client/renderers/live2d.test.ts +23 -2
  58. package/src/client/renderers/live2d.ts +14 -1
  59. package/src/client/settings-form.ts +8 -6
  60. package/src/contracts/status-decoration.ts +78 -0
  61. package/src/decoration.test.ts +178 -0
  62. package/src/decoration.ts +220 -0
  63. package/src/event-projection.ts +10 -5
  64. package/src/index.ts +2 -0
  65. package/src/registry.test.ts +223 -0
  66. package/src/registry.ts +235 -15
  67. package/src/routes.ts +128 -3
  68. package/src/service.ts +59 -3
  69. package/src/voice-pack.test.ts +216 -0
  70. package/src/voice-pack.ts +413 -0
@@ -17,6 +17,11 @@
17
17
  * plugin has always shown, so existing installs keep their wording until the
18
18
  * scene cycles. No emoji anywhere (repository rule); ~ is the whale-girl's
19
19
  * signature.
20
+ *
21
+ * Since pet-center M4 (issue #677) every pool is overridable through a
22
+ * {@link VoicePoolsProvider}: the built-in pools are the fallback layer, and
23
+ * voice packs (per-pet voice.json / the global .voice.json) layer their
24
+ * pools on top at draw time.
20
25
  * @module @linxin666/dsh-pet/chatter
21
26
  */
22
27
  /** Status copy scenes — the situations a session bubble can report. */
@@ -27,6 +32,10 @@ export declare const STATUS_ROTATE_MS = 4000;
27
32
  export declare const STATUS_POOLS: Readonly<Record<StatusScene, readonly string[]>>;
28
33
  /** Tool families for friendlier per-tool status copy. */
29
34
  export type ToolCategory = 'read' | 'write' | 'edit' | 'shell' | 'grep' | 'find' | 'ls' | 'webSearch' | 'webFetch' | 'mcp' | 'memory' | 'subagent' | 'todo' | 'browser' | 'git' | 'ask' | 'generic';
35
+ /** Every status scene key, in declaration order (voice-pack key allow-list). */
36
+ export declare const STATUS_SCENES: readonly StatusScene[];
37
+ /** Every tool-family key, in declaration order (voice-pack key allow-list). */
38
+ export declare const TOOL_CATEGORIES: readonly ToolCategory[];
30
39
  /** Map a raw tool name onto its copy family (working-activity style regexes). */
31
40
  export declare function toolCategory(toolName: string): ToolCategory;
32
41
  /**
@@ -52,16 +61,23 @@ export declare function toolArgHint(toolName: string, argumentsJson: string): st
52
61
  * stretch keeps changing its wording.
53
62
  */
54
63
  export declare class StatusVoice {
64
+ private readonly pools;
55
65
  private readonly rotateMs;
56
66
  private readonly counters;
57
67
  private lastScene;
58
68
  private lastLine;
59
69
  private lastLineAt;
60
- constructor(rotateMs?: number);
70
+ constructor(pools?: VoicePoolsProvider, rotateMs?: number);
61
71
  /** Draw the next line of one pool, advancing its round-robin cursor. */
62
72
  private draw;
63
73
  /** Reuse the stable line or advance when the cadence elapsed. */
64
74
  private voice;
75
+ /**
76
+ * A scene's effective pool: the voice-pack override when it carries lines,
77
+ * else the built-in pool. Empty overrides fall back rather than blank the
78
+ * bubble — a scene line always renders.
79
+ */
80
+ private scenePool;
65
81
  /** Status line for a phase scene. */
66
82
  scene(scene: StatusScene, nowMs: number): string;
67
83
  /** Status line for a tool call, with the real-argument hint when known. */
@@ -85,22 +101,64 @@ export declare const WHISPER_TTL_MS = 8000;
85
101
  export declare const WHISPER_GENERIC_POOL: readonly string[];
86
102
  /** Keyword-triggered whisper rules, most specific moods first. */
87
103
  export declare const WHISPER_RULES: readonly WhisperRule[];
104
+ /**
105
+ * Voice-pack overrides (pet-center M4, issue #677): the content a voice
106
+ * pack can replace, one pool at a time. Every field is optional — missing
107
+ * keys inherit the built-in pools. Resolution happens at draw time through
108
+ * a provider function, so swapping pets (or editing the global file) re-
109
+ * voices live engines without rebuilding them.
110
+ *
111
+ * Override semantics:
112
+ * - status/tools/toolRemaining: a non-empty override replaces the built-in
113
+ * pool for that key; an empty override falls back to the built-in pool
114
+ * (a scene line always renders, so it can never be blanked).
115
+ * - whispers.generic / whispers.rules: the override REPLACES the built-in
116
+ * section; an empty array mutes that channel (ambient or keyword).
117
+ */
118
+ export interface VoicePackOverrides {
119
+ /** Status copy pools by scene; each key replaces that scene's pool. */
120
+ status?: Partial<Record<StatusScene, readonly string[]>>;
121
+ /** Tool copy pools by family; each key replaces that family's pool. */
122
+ tools?: Partial<Record<ToolCategory, readonly string[]>>;
123
+ /** The parallel-tools count line pool ({n} interpolates the count). */
124
+ toolRemaining?: readonly string[];
125
+ /** Murmur pools; each section replaces the built-in one as a whole. */
126
+ whispers?: {
127
+ /** Ambient inner-whisper pool (empty mutes ambient whispers). */
128
+ generic?: readonly string[];
129
+ /** Ordered keyword rules (empty disables keyword-triggered whispers). */
130
+ rules?: readonly WhisperRule[];
131
+ };
132
+ }
133
+ /** Read the current effective voice-pack overrides (draw-time resolution). */
134
+ export type VoicePoolsProvider = () => VoicePackOverrides;
135
+ /** The built-in voice pack: the plugin's default copy, unchanged since v1. */
136
+ export declare const BUILTIN_VOICE_PACK: VoicePackOverrides;
88
137
  /**
89
138
  * The murmur engine (碎碎念): watches the model's own output and lets the pet
90
139
  * whisper its inner voice. Two ways to earn a whisper:
91
140
  * - a keyword rule matches the fresh chunk text (themed whisper);
92
141
  * - enough output volume flowed by without one (ambient whisper).
93
142
  * A cooldown keeps whispers occasional; all picks are round-robin so tests
94
- * reproduce exact lines.
143
+ * reproduce exact lines. The voice-pack provider (pet-center M4) swaps the
144
+ * pools at draw time, so a pet switch re-voices live engines in place.
95
145
  */
96
146
  export declare class WhisperEngine {
147
+ private readonly pools;
97
148
  private readonly cooldownMs;
98
149
  private readonly charBudget;
99
150
  private readonly counters;
100
151
  private genericCursor;
101
152
  private lastWhisperAt;
102
153
  private charsSinceWhisper;
103
- constructor(cooldownMs?: number, charBudget?: number);
154
+ constructor(pools?: VoicePoolsProvider, cooldownMs?: number, charBudget?: number);
155
+ /**
156
+ * Effective keyword rules: an override replaces the built-in rules as a
157
+ * whole; an explicit empty array disables keyword-triggered whispers.
158
+ */
159
+ private rules;
160
+ /** Effective ambient pool (an explicit empty array mutes ambient whispers). */
161
+ private generic;
104
162
  /**
105
163
  * Feed one model-output chunk (reasoning or text). Returns the whisper to
106
164
  * show, or undefined when the moment stays quiet.
@@ -1 +1 @@
1
- {"version":3,"file":"chatter.d.ts","sourceRoot":"","sources":["../../src/chatter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"chatter.d.ts","sourceRoot":"","sources":["../../src/chatter.ts"],"names":[],"mappings":"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"}
@@ -17,6 +17,11 @@
17
17
  * plugin has always shown, so existing installs keep their wording until the
18
18
  * scene cycles. No emoji anywhere (repository rule); ~ is the whale-girl's
19
19
  * signature.
20
+ *
21
+ * Since pet-center M4 (issue #677) every pool is overridable through a
22
+ * {@link VoicePoolsProvider}: the built-in pools are the fallback layer, and
23
+ * voice packs (per-pet voice.json / the global .voice.json) layer their
24
+ * pools on top at draw time.
20
25
  * @module @linxin666/dsh-pet/chatter
21
26
  */
22
27
  /** While a scene persists, its copy advances on this cadence (ms). */
@@ -156,6 +161,16 @@ export const STATUS_POOLS = {
156
161
  '蹲一个继续的指令',
157
162
  ],
158
163
  };
164
+ /** Every status scene key, in declaration order (voice-pack key allow-list). */
165
+ export const STATUS_SCENES = [
166
+ 'prepare', 'waiting', 'thinking', 'review', 'toolResult', 'done',
167
+ 'failed', 'toolFailed', 'maxTokens', 'interrupted', 'blocked',
168
+ ];
169
+ /** Every tool-family key, in declaration order (voice-pack key allow-list). */
170
+ export const TOOL_CATEGORIES = [
171
+ 'read', 'write', 'edit', 'shell', 'grep', 'find', 'ls', 'webSearch',
172
+ 'webFetch', 'mcp', 'memory', 'subagent', 'todo', 'browser', 'git', 'ask', 'generic',
173
+ ];
159
174
  /** Map a raw tool name onto its copy family (working-activity style regexes). */
160
175
  export function toolCategory(toolName) {
161
176
  const name = toolName.toLowerCase();
@@ -414,12 +429,16 @@ export function toolArgHint(toolName, argumentsJson) {
414
429
  * stretch keeps changing its wording.
415
430
  */
416
431
  export class StatusVoice {
432
+ pools;
417
433
  rotateMs;
418
434
  counters = new Map();
419
435
  lastScene = '';
420
436
  lastLine = '';
421
437
  lastLineAt = Number.NEGATIVE_INFINITY;
422
- constructor(rotateMs = STATUS_ROTATE_MS) {
438
+ constructor(pools = () => BUILTIN_VOICE_PACK, rotateMs = STATUS_ROTATE_MS) {
439
+ // Plain property assignment, not parameter properties: this module is
440
+ // imported by scripts/ under node's strip-only mode (pet-center M4).
441
+ this.pools = pools;
423
442
  this.rotateMs = rotateMs;
424
443
  }
425
444
  /** Draw the next line of one pool, advancing its round-robin cursor. */
@@ -437,22 +456,35 @@ export class StatusVoice {
437
456
  this.lastLineAt = nowMs;
438
457
  return this.lastLine;
439
458
  }
459
+ /**
460
+ * A scene's effective pool: the voice-pack override when it carries lines,
461
+ * else the built-in pool. Empty overrides fall back rather than blank the
462
+ * bubble — a scene line always renders.
463
+ */
464
+ scenePool(scene) {
465
+ const override = this.pools().status?.[scene];
466
+ return override !== undefined && override.length > 0 ? override : STATUS_POOLS[scene];
467
+ }
440
468
  /** Status line for a phase scene. */
441
469
  scene(scene, nowMs) {
442
- return this.voice('scene:' + scene, 'pool:' + scene, STATUS_POOLS[scene], nowMs);
470
+ return this.voice('scene:' + scene, 'pool:' + scene, this.scenePool(scene), nowMs);
443
471
  }
444
472
  /** Status line for a tool call, with the real-argument hint when known. */
445
473
  tool(toolName, displayName, hint, nowMs) {
446
474
  const category = toolCategory(toolName);
447
- const line = this.voice('tool:' + category, 'tool:' + category, TOOL_POOLS[category], nowMs);
475
+ const override = this.pools().tools?.[category];
476
+ const pool = override !== undefined && override.length > 0 ? override : TOOL_POOLS[category];
477
+ const line = this.voice('tool:' + category, 'tool:' + category, pool, nowMs);
448
478
  return line
449
- .replace('{tool}', displayName)
450
- .replace('{hint}', hint ?? displayName);
479
+ .replaceAll('{tool}', displayName)
480
+ .replaceAll('{hint}', hint ?? displayName);
451
481
  }
452
482
  /** Status line while sibling tools still run (always reflects the count). */
453
483
  toolRemaining(count, nowMs) {
454
- return this.voice('toolRemaining', 'toolRemaining', TOOL_REMAINING_POOL, nowMs)
455
- .replace('{n}', String(count));
484
+ const override = this.pools().toolRemaining;
485
+ const pool = override !== undefined && override.length > 0 ? override : TOOL_REMAINING_POOL;
486
+ return this.voice('toolRemaining', 'toolRemaining', pool, nowMs)
487
+ .replaceAll('{n}', String(count));
456
488
  }
457
489
  }
458
490
  /** Murmur pacing: cooldown between whispers and output volume that earns one. */
@@ -701,25 +733,48 @@ export const WHISPER_RULES = [
701
733
  ],
702
734
  },
703
735
  ];
736
+ /** The built-in voice pack: the plugin's default copy, unchanged since v1. */
737
+ export const BUILTIN_VOICE_PACK = {
738
+ status: STATUS_POOLS,
739
+ tools: TOOL_POOLS,
740
+ toolRemaining: TOOL_REMAINING_POOL,
741
+ whispers: { generic: WHISPER_GENERIC_POOL, rules: WHISPER_RULES },
742
+ };
704
743
  /**
705
744
  * The murmur engine (碎碎念): watches the model's own output and lets the pet
706
745
  * whisper its inner voice. Two ways to earn a whisper:
707
746
  * - a keyword rule matches the fresh chunk text (themed whisper);
708
747
  * - enough output volume flowed by without one (ambient whisper).
709
748
  * A cooldown keeps whispers occasional; all picks are round-robin so tests
710
- * reproduce exact lines.
749
+ * reproduce exact lines. The voice-pack provider (pet-center M4) swaps the
750
+ * pools at draw time, so a pet switch re-voices live engines in place.
711
751
  */
712
752
  export class WhisperEngine {
753
+ pools;
713
754
  cooldownMs;
714
755
  charBudget;
715
756
  counters = new Map();
716
757
  genericCursor = 0;
717
758
  lastWhisperAt = Number.NEGATIVE_INFINITY;
718
759
  charsSinceWhisper = 0;
719
- constructor(cooldownMs = WHISPER_COOLDOWN_MS, charBudget = WHISPER_CHAR_BUDGET) {
760
+ constructor(pools = () => BUILTIN_VOICE_PACK, cooldownMs = WHISPER_COOLDOWN_MS, charBudget = WHISPER_CHAR_BUDGET) {
761
+ this.pools = pools;
720
762
  this.cooldownMs = cooldownMs;
721
763
  this.charBudget = charBudget;
722
764
  }
765
+ /**
766
+ * Effective keyword rules: an override replaces the built-in rules as a
767
+ * whole; an explicit empty array disables keyword-triggered whispers.
768
+ */
769
+ rules() {
770
+ const override = this.pools().whispers?.rules;
771
+ return override === undefined ? WHISPER_RULES : override;
772
+ }
773
+ /** Effective ambient pool (an explicit empty array mutes ambient whispers). */
774
+ generic() {
775
+ const override = this.pools().whispers?.generic;
776
+ return override === undefined ? WHISPER_GENERIC_POOL : override;
777
+ }
723
778
  /**
724
779
  * Feed one model-output chunk (reasoning or text). Returns the whisper to
725
780
  * show, or undefined when the moment stays quiet.
@@ -733,8 +788,9 @@ export class WhisperEngine {
733
788
  return undefined;
734
789
  }
735
790
  const haystack = text.toLowerCase();
736
- for (let ruleIndex = 0; ruleIndex < WHISPER_RULES.length; ruleIndex += 1) {
737
- const rule = WHISPER_RULES[ruleIndex];
791
+ const rules = this.rules();
792
+ for (let ruleIndex = 0; ruleIndex < rules.length; ruleIndex += 1) {
793
+ const rule = rules[ruleIndex];
738
794
  if (!rule.keywords.some(keyword => haystack.includes(keyword)))
739
795
  continue;
740
796
  const index = (this.counters.get(ruleIndex) ?? 0) % rule.pool.length;
@@ -744,7 +800,10 @@ export class WhisperEngine {
744
800
  this.charsSinceWhisper += text.length;
745
801
  if (this.charsSinceWhisper < this.charBudget)
746
802
  return undefined;
747
- const line = WHISPER_GENERIC_POOL[this.genericCursor % WHISPER_GENERIC_POOL.length];
803
+ const generic = this.generic();
804
+ if (generic.length === 0)
805
+ return undefined;
806
+ const line = generic[this.genericCursor % generic.length];
748
807
  this.genericCursor += 1;
749
808
  return this.speak(line, nowMs);
750
809
  }
@@ -24,6 +24,8 @@ export interface PetSettings {
24
24
  bottom?: number;
25
25
  /** Selected pet id (a registry entry). */
26
26
  petId?: string;
27
+ /** Status-decoration master switch (pet-center M5, #567). */
28
+ decorationEnabled?: boolean;
27
29
  }
28
30
  /** What the pet settings card renders. */
29
31
  export interface PetSettingsCardState extends CardShell {
@@ -39,6 +41,8 @@ export interface PetSettingsCardState extends CardShell {
39
41
  bottom: CardFieldState;
40
42
  /** Selected pet. */
41
43
  petId: CardFieldState;
44
+ /** Status-decoration master switch. */
45
+ decorationEnabled: CardFieldState;
42
46
  /** Pet choices (registry ids + display names), loaded from the host. */
43
47
  petChoices: readonly {
44
48
  value: string;
@@ -1 +1 @@
1
- {"version":3,"file":"PetSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/client/PetSettingsCard.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"PetSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/client/PetSettingsCard.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AAC7F,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,wCAAwC,CAAA;AAI1F,OAAO,EAAoD,KAAK,WAAW,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,IAAI,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAG1J,+EAA+E;AAC/E,MAAM,WAAW,WAAW;IAC1B,oCAAoC;IACpC,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,qBAAqB;IACrB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,4DAA4D;IAC5D,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,yDAAyD;IACzD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,wDAAwD;IACxD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,0CAA0C;IAC1C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,6DAA6D;IAC7D,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAC5B;AAED,0CAA0C;AAC1C,MAAM,WAAW,oBAAqB,SAAQ,SAAS;IACrD,4BAA4B;IAC5B,OAAO,EAAE,cAAc,CAAA;IACvB,qBAAqB;IACrB,OAAO,EAAE,cAAc,CAAA;IACvB,iBAAiB;IACjB,IAAI,EAAE,cAAc,CAAA;IACpB,mBAAmB;IACnB,KAAK,EAAE,cAAc,CAAA;IACrB,oBAAoB;IACpB,MAAM,EAAE,cAAc,CAAA;IACtB,oBAAoB;IACpB,KAAK,EAAE,cAAc,CAAA;IACrB,uCAAuC;IACvC,iBAAiB,EAAE,cAAc,CAAA;IACjC,wEAAwE;IACxE,UAAU,EAAE,SAAS;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IACvD,+EAA+E;IAC/E,cAAc,EAAE,SAAS,iBAAiB,EAAE,CAAA;CAC7C;AAED,gEAAgE;AAChE,MAAM,WAAW,mBAAoB,SAAQ,WAAW;IACtD,KAAK,EAAE;QACL,iEAAiE;QACjE,eAAe,EAAE,aAAa,CAAC,oBAAoB,CAAC,CAAA;KACrD,CAAA;CACF;AAQD,0EAA0E;AAC1E,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,OAAO,GAAG,SAAS,CAAA;IAC1B,OAAO,EAAE,MAAM,CAAA;CAChB;AAiBD,2DAA2D;AAC3D,qBAAa,yBAAyB;IACpC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAuB;IAC5C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqC;IAI3D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAe;IAC1C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA4B;IACtD,OAAO,CAAC,WAAW,CAA0B;IAC7C,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,QAAQ,CAAI;IAEpB,uEAAuE;IACvE,YAAY,KAAK,EAAE,aAAa,CAAC,WAAW,CAAC,EAa5C;IAED,2EAA2E;YAC7D,eAAe;IAS7B,0EAA0E;YAC5D,QAAQ;IAgBtB,OAAO,CAAC,UAAU;IAelB;;;OAGG;IACH,MAAM,IAAI,mBAAmB,CAE5B;IAED;;;OAGG;IACH,OAAO,IAAI,IAAI,CAEd;CACF;AAED,0DAA0D;AAC1D,MAAM,MAAM,oBAAoB,GAC9B,WAAW,CAAC,KAAK,CAAC,GAChB,UAAU,CAAC,mBAAmB,CAAC,CAAA;AAEnC;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,+BA6G1D;AAED,8DAA8D;AAC9D,MAAM,MAAM,uBAAuB,GACjC,YAAY,CAAC,kBAAkB,CAAC,GAC9B,WAAW,CAAC,KAAK,CAAC,GAClB,UAAU,CAAC,mBAAmB,CAAC,CAAA;AAEnC,mEAAmE;AACnE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,GAAG,SAAS,CAO5E"}
@@ -33,6 +33,7 @@ export class PetSettingsCardController {
33
33
  constructor(scope) {
34
34
  this.form = new CardForm(scope, [
35
35
  booleanField('enabled'),
36
+ booleanField('decorationEnabled'),
36
37
  booleanField('visible'),
37
38
  numberField('size'),
38
39
  numberField('right'),
@@ -76,6 +77,7 @@ export class PetSettingsCardController {
76
77
  return {
77
78
  ...this.form.shell(),
78
79
  enabled: this.form.field('enabled'),
80
+ decorationEnabled: this.form.field('decorationEnabled'),
79
81
  visible: this.form.field('visible'),
80
82
  size: this.form.field('size'),
81
83
  right: this.form.field('right'),
@@ -115,7 +117,7 @@ export function PetSettingsCard(props) {
115
117
  invalidLabel: t('settings.invalidNumber'),
116
118
  disabled,
117
119
  };
118
- return (_jsxs(PluginSettingsCard, { t: t, titleKey: "settings.title", descriptionKey: "settings.description", state: state, onSave: props.save, onDiscard: props.discard, alwaysOpen: true, children: [_jsx(BooleanField, { id: "settings-pet-enabled", label: t('settings.enabled'), hint: t('settings.enabledHint'), inheritLabel: t('settings.inherit'), onLabel: t('settings.on'), offLabel: t('settings.off'), ...fieldProps, ...state.enabled, onEdit: (text) => { props.edit('enabled', text); }, onReset: () => { props.resetField('enabled'); } }), _jsx(ChoiceField, { id: "settings-pet-pet", label: t('settings.pet'), hint: t('settings.petHint'), inheritLabel: t('settings.inherit'), ...fieldProps, ...state.petId, choices: state.petChoices, onEdit: (text) => { props.edit('petId', text); }, onReset: () => { props.resetField('petId'); } }), state.petDiagnostics.length === 0 ? null : (_jsxs("li", { className: sectionCss.diagnostics, "data-dsh-part": "diagnostics", children: [_jsx("span", { className: sectionCss.diagnosticsTitle, children: t('settings.diagnosticsTitle') }), _jsx("ul", { children: state.petDiagnostics.map((diagnostic, index) => (_jsx("li", { "data-level": diagnostic.level, children: diagnostic.message }, index))) })] })), _jsx(BooleanField, { id: "settings-pet-visible", label: t('settings.visible'), hint: t('settings.visibleHint'), inheritLabel: t('settings.inherit'), onLabel: t('settings.on'), offLabel: t('settings.off'), ...fieldProps, ...state.visible, onEdit: (text) => { props.edit('visible', text); }, onReset: () => { props.resetField('visible'); } }), _jsx(ValueField, { id: "settings-pet-size", label: t('settings.size'), hint: t('settings.sizeHint'), numeric: true, ...fieldProps, ...state.size, onEdit: (text) => { props.edit('size', text); }, onReset: () => { props.resetField('size'); } }), _jsx(ValueField, { id: "settings-pet-right", label: t('settings.right'), hint: t('settings.rightHint'), numeric: true, ...fieldProps, ...state.right, onEdit: (text) => { props.edit('right', text); }, onReset: () => { props.resetField('right'); } }), _jsx(ValueField, { id: "settings-pet-bottom", label: t('settings.bottom'), hint: t('settings.bottomHint'), numeric: true, ...fieldProps, ...state.bottom, onEdit: (text) => { props.edit('bottom', text); }, onReset: () => { props.resetField('bottom'); } })] }));
120
+ return (_jsxs(PluginSettingsCard, { t: t, titleKey: "settings.title", descriptionKey: "settings.description", state: state, onSave: props.save, onDiscard: props.discard, alwaysOpen: true, children: [_jsx(BooleanField, { id: "settings-pet-enabled", label: t('settings.enabled'), hint: t('settings.enabledHint'), inheritLabel: t('settings.inherit'), onLabel: t('settings.on'), offLabel: t('settings.off'), ...fieldProps, ...state.enabled, onEdit: (text) => { props.edit('enabled', text); }, onReset: () => { props.resetField('enabled'); } }), _jsx(BooleanField, { id: "settings-pet-decoration", label: t('settings.decoration'), hint: t('settings.decorationHint'), inheritLabel: t('settings.inherit'), onLabel: t('settings.on'), offLabel: t('settings.off'), ...fieldProps, ...state.decorationEnabled, onEdit: (text) => { props.edit('decorationEnabled', text); }, onReset: () => { props.resetField('decorationEnabled'); } }), _jsx(ChoiceField, { id: "settings-pet-pet", label: t('settings.pet'), hint: t('settings.petHint'), inheritLabel: t('settings.inherit'), ...fieldProps, ...state.petId, choices: state.petChoices, onEdit: (text) => { props.edit('petId', text); }, onReset: () => { props.resetField('petId'); } }), state.petDiagnostics.length === 0 ? null : (_jsxs("li", { className: sectionCss.diagnostics, "data-dsh-part": "diagnostics", children: [_jsx("span", { className: sectionCss.diagnosticsTitle, children: t('settings.diagnosticsTitle') }), _jsx("ul", { children: state.petDiagnostics.map((diagnostic, index) => (_jsx("li", { "data-level": diagnostic.level, children: diagnostic.message }, index))) })] })), _jsx(BooleanField, { id: "settings-pet-visible", label: t('settings.visible'), hint: t('settings.visibleHint'), inheritLabel: t('settings.inherit'), onLabel: t('settings.on'), offLabel: t('settings.off'), ...fieldProps, ...state.visible, onEdit: (text) => { props.edit('visible', text); }, onReset: () => { props.resetField('visible'); } }), _jsx(ValueField, { id: "settings-pet-size", label: t('settings.size'), hint: t('settings.sizeHint'), numeric: true, ...fieldProps, ...state.size, onEdit: (text) => { props.edit('size', text); }, onReset: () => { props.resetField('size'); } }), _jsx(ValueField, { id: "settings-pet-right", label: t('settings.right'), hint: t('settings.rightHint'), numeric: true, ...fieldProps, ...state.right, onEdit: (text) => { props.edit('right', text); }, onReset: () => { props.resetField('right'); } }), _jsx(ValueField, { id: "settings-pet-bottom", label: t('settings.bottom'), hint: t('settings.bottomHint'), numeric: true, ...fieldProps, ...state.bottom, onEdit: (text) => { props.edit('bottom', text); }, onReset: () => { props.resetField('bottom'); } })] }));
119
121
  }
120
122
  /** Render the pet settings card as a first-level settings page. */
121
123
  export function PetSettingsSection(props) {
@@ -1 +1 @@
1
- {"version":3,"file":"PetSprite.d.ts","sourceRoot":"","sources":["../../../src/client/PetSprite.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAoD,SAAS,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AAGrG,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;AACnD,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;AAOD;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,WAAW,CAge5D"}
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;AAkFD;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,WAAW,CAygB5D"}
@@ -20,6 +20,80 @@ import styles from './pet.module.css';
20
20
  function clampOffset(value, max) {
21
21
  return Math.max(0, Math.min(max, value));
22
22
  }
23
+ /**
24
+ * The status decoration ornament (pet-center M5, #567). Renders the active
25
+ * phase's frame segment as a CSS-background strip at a compact bubble
26
+ * height; prefers-reduced-motion holds the segment's first frame, and a
27
+ * missing or undecodable asset simply paints nothing (CSS background
28
+ * failure) — the bubble text is never disturbed. The span is aria-hidden;
29
+ * the bubble keeps its own semantics untouched.
30
+ */
31
+ function StatusOrnament(props) {
32
+ const { decoration, phase } = props;
33
+ const segment = decoration.phases[phase];
34
+ const shown = segment !== undefined && segment !== 'hide';
35
+ const segmentKey = segment !== undefined && segment !== 'hide' ? segment.from + ':' + segment.to : 'none';
36
+ const spanRef = useRef(null);
37
+ const scale = 18 / decoration.cell.height;
38
+ const frameWidth = Math.round(decoration.cell.width * scale);
39
+ const stripWidth = decoration.columns * frameWidth;
40
+ // Value-stable dependency key: the host serves a fresh DecorationView
41
+ // object on every state poll (2 s), so the effect must not depend on the
42
+ // object identity — otherwise each poll would cancel and restart the
43
+ // frame loop and the animation would jump back to its first frame.
44
+ const durationsKey = decoration.durations.join(',');
45
+ useEffect(() => {
46
+ if (segment === undefined || segment === 'hide')
47
+ return;
48
+ const el = spanRef.current;
49
+ if (el === null)
50
+ return;
51
+ const position = (index) => (-index * frameWidth) + 'px 0px';
52
+ const reduceMotion = window.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches === true;
53
+ el.style.backgroundPosition = position(segment.from);
54
+ if (reduceMotion)
55
+ return;
56
+ let raf = 0;
57
+ let index = segment.from;
58
+ let elapsed = 0;
59
+ let last = performance.now();
60
+ const tick = (ts) => {
61
+ const delta = ts - last;
62
+ last = ts;
63
+ elapsed += delta;
64
+ const duration = decoration.durations[index] ?? 160;
65
+ if (elapsed >= duration) {
66
+ elapsed = 0;
67
+ if (index < segment.to)
68
+ index += 1;
69
+ else if (decoration.loop)
70
+ index = segment.from;
71
+ }
72
+ el.style.backgroundPosition = position(index);
73
+ // A non-looping segment settles on its last frame; stop scheduling
74
+ // instead of repainting the same position every frame.
75
+ if (!decoration.loop && index === segment.to)
76
+ return;
77
+ raf = requestAnimationFrame(tick);
78
+ };
79
+ raf = requestAnimationFrame(tick);
80
+ return () => cancelAnimationFrame(raf);
81
+ }, [shown, segmentKey, frameWidth, decoration.loop, durationsKey]);
82
+ if (!shown)
83
+ return null;
84
+ return (_jsx("span", { ref: spanRef, "aria-hidden": "true", "data-dsh-pet-decoration": decoration.id, style: {
85
+ display: 'inline-block',
86
+ width: frameWidth,
87
+ height: 18,
88
+ marginRight: 6,
89
+ verticalAlign: 'middle',
90
+ flexShrink: 0,
91
+ backgroundImage: 'url(' + decoration.entryUrl + ')',
92
+ backgroundSize: stripWidth + 'px 18px',
93
+ backgroundRepeat: 'no-repeat',
94
+ backgroundPosition: '0px 0px',
95
+ } }));
96
+ }
23
97
  /**
24
98
  * The floating pet. The spritesheet frame advances on requestAnimationFrame
25
99
  * with per-frame durations from the definition's tracks; the atlas image is
@@ -66,6 +140,29 @@ export function PetSprite(props) {
66
140
  const rows = definition.rows;
67
141
  const tracks = definition.tracks;
68
142
  const sequences = definition.sequences;
143
+ // Hover-panel chrome from the pet's voice pack (pet-center M4, issue
144
+ // #677): every slot falls back to the i18n dictionary when unset. Stat
145
+ // formats carry {rank}/{n}/{points} placeholders the host validated.
146
+ const panel = definition.panel;
147
+ const panelLabel = (slot, i18n) => panel?.labels?.[slot] ?? i18n;
148
+ const panelStat = (slot, i18nKey, values) => {
149
+ const format = panel?.stats?.[slot] ?? props.t(i18nKey, values);
150
+ if (panel?.stats?.[slot] === undefined)
151
+ return format;
152
+ // The host whitelists {rank}/{n}/{points} in every stat slot, so a pack
153
+ // format may reference any of them; substitute all three live values
154
+ // (the slot's own value plus the siblings) instead of only the slot's.
155
+ const all = {
156
+ rank: snapshot?.affinity.rank ?? '?',
157
+ n: snapshot?.treats.stocked ?? 0,
158
+ points: snapshot?.affinity.points ?? 0,
159
+ };
160
+ let text = format;
161
+ for (const [name, value] of Object.entries(all))
162
+ text = text.replaceAll('{' + name + '}', String(value));
163
+ return text;
164
+ };
165
+ const panelShows = (action) => panel?.actions === undefined || panel.actions.includes(action);
69
166
  // Load the atlas once; the definition carries the authoritative per-row
70
167
  // frame counts and per-track durations, so nothing else is fetched. A
71
168
  // custom visual (pet-center M3) replaces the atlas entirely.
@@ -242,6 +339,8 @@ export function PetSprite(props) {
242
339
  const whisper = feedback === null ? snapshot?.whisper : undefined;
243
340
  const bubblePresent = feedback !== null || sessionBubbles.length > 0 || statusBubble !== undefined || whisper !== undefined;
244
341
  const displayName = snapshot?.name ?? definition.displayName;
342
+ // The host-served status decoration (M5, #567); absent = text-only bubbles.
343
+ const decoration = snapshot?.decoration;
245
344
  // A settled session list can no longer stay pinned open.
246
345
  useEffect(() => {
247
346
  if (sessionBubbles.length <= 1)
@@ -319,7 +418,7 @@ export function PetSprite(props) {
319
418
  // The key swap restarts the entrance animation so the mood change
320
419
  // reads as the bubble re-speaking.
321
420
  const speaksWhisper = index === 0 && whisper !== undefined;
322
- const bubble = (_jsx("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: speaksWhisper ? whisper : session.bubble }, speaksWhisper ? 'whisper:' + whisper : session.sessionId));
421
+ 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));
323
422
  // The primary bubble carries the '+N' badge while other sessions
324
423
  // hide behind it; the badge toggles the pinned (touch) expansion.
325
424
  if (index !== 0 || sessionBubbles.length <= 1)
@@ -332,7 +431,7 @@ export function PetSprite(props) {
332
431
  e.stopPropagation();
333
432
  setStackPinned(open => !open);
334
433
  }, children: stackOpen ? '×' : '+' + String(sessionBubbles.length - 1) })] }, "primary"));
335
- }), sessionBubbles.length === 0 && (statusBubble !== undefined || whisper !== undefined) && (_jsx("div", { className: clsx(styles.bubble, styles.bubbleStatus, whisper !== undefined && styles.bubbleWhisper), role: "status", "aria-live": "polite", children: 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
434
+ }), 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
336
435
  ? { marginBottom: panelLift }
337
436
  : undefined, onPointerEnter: () => {
338
437
  // Reaching the panel (or its bridge) must cancel any hide timer
@@ -364,12 +463,12 @@ export function PetSprite(props) {
364
463
  props.onRename(trimmed);
365
464
  setRenaming(false);
366
465
  }
367
- }, children: props.t('pet.confirm') })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: styles.rankRow, children: [_jsx("span", { className: styles.nameCell, children: displayName }), _jsx("span", { className: styles.statRank, children: props.t('pet.rank', { rank: snapshot?.affinity.rank ?? '?' }) })] }), _jsxs("div", { className: styles.rankRow, children: [_jsx("span", { className: styles.statTreats, children: props.t('pet.treats', { n: snapshot?.treats.stocked ?? 0 }) }), _jsx("span", { className: styles.statPoints, children: props.t('pet.points', { points: snapshot?.affinity.points ?? 0 }) })] }), _jsxs("div", { className: styles.actions, children: [_jsx("button", { type: "button", className: styles.action, onClick: props.onFeed, children: props.t('pet.feed') }), _jsx("button", { type: "button", className: styles.action, onClick: () => {
466
+ }, children: panelLabel('confirm', props.t('pet.confirm')) })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: styles.rankRow, children: [_jsx("span", { className: styles.nameCell, children: displayName }), _jsx("span", { className: styles.statRank, children: panelStat('rank', 'pet.rank', { rank: snapshot?.affinity.rank ?? '?' }) })] }), _jsxs("div", { className: styles.rankRow, children: [_jsx("span", { className: styles.statTreats, children: panelStat('treats', 'pet.treats', { n: snapshot?.treats.stocked ?? 0 }) }), _jsx("span", { className: styles.statPoints, children: panelStat('points', 'pet.points', { points: snapshot?.affinity.points ?? 0 }) })] }), _jsxs("div", { className: styles.actions, children: [panelShows('feed') && (_jsx("button", { type: "button", className: styles.action, onClick: props.onFeed, children: panelLabel('feed', props.t('pet.feed')) })), panelShows('rename') && (_jsx("button", { type: "button", className: styles.action, onClick: () => {
368
467
  // Cancel any pending hide so the rename box cannot
369
468
  // unmount right as the user starts typing (#303).
370
469
  clearHideTimer();
371
470
  setNameDraft(displayName);
372
471
  setRenaming(true);
373
- }, children: props.t('pet.rename') }), _jsx("button", { type: "button", className: styles.action, onClick: props.onHide, children: props.t('pet.hide') })] })] })) }))] }));
472
+ }, 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')) }))] })] })) }))] }));
374
473
  return createPortal(float, document.body);
375
474
  }
@@ -31,6 +31,8 @@ export declare const zh: {
31
31
  readonly 'settings.petHint': '选择显示哪只宠物;每只宠物独立命名,可在宠物悬浮面板改名。';
32
32
  readonly 'settings.enabled': '启用宠物';
33
33
  readonly 'settings.enabledHint': '关闭后隐藏宠物并停止轮询,可在设置里重新启用。';
34
+ readonly 'settings.decoration': '状态装饰';
35
+ readonly 'settings.decorationHint': '在宠物状态气泡里显示喷水鲸鱼等状态装饰;关闭后气泡只剩文字。';
34
36
  readonly 'settings.visible': '显示宠物';
35
37
  readonly 'settings.visibleHint': '关闭后宠物隐藏,可从聊天输入区重新召唤。';
36
38
  readonly 'settings.size': '大小(px)';
@@ -82,6 +84,8 @@ export declare const en: {
82
84
  readonly 'settings.petHint': 'Choose which pet shows. Names are stored per pet; rename from the pet hover panel.';
83
85
  readonly 'settings.enabled': 'Enable the pet';
84
86
  readonly 'settings.enabledHint': 'When off, the pet hides and polling stops; re-enable it here.';
87
+ readonly 'settings.decoration': 'Status decoration';
88
+ readonly 'settings.decorationHint': 'Show ornaments like the spouting whale inside the pet status bubbles; when off, bubbles stay text-only.';
85
89
  readonly 'settings.visible': 'Show the pet';
86
90
  readonly 'settings.visibleHint': 'When off, the pet hides; summon it again from the input row.';
87
91
  readonly 'settings.size': 'Size (px)';
@@ -1 +1 @@
1
- {"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"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"}
@@ -32,6 +32,8 @@ export const zh = {
32
32
  'settings.petHint': '选择显示哪只宠物;每只宠物独立命名,可在宠物悬浮面板改名。',
33
33
  'settings.enabled': '启用宠物',
34
34
  'settings.enabledHint': '关闭后隐藏宠物并停止轮询,可在设置里重新启用。',
35
+ 'settings.decoration': '状态装饰',
36
+ 'settings.decorationHint': '在宠物状态气泡里显示喷水鲸鱼等状态装饰;关闭后气泡只剩文字。',
35
37
  'settings.visible': '显示宠物',
36
38
  'settings.visibleHint': '关闭后宠物隐藏,可从聊天输入区重新召唤。',
37
39
  'settings.size': '大小(px)',
@@ -84,6 +86,8 @@ export const en = {
84
86
  'settings.petHint': 'Choose which pet shows. Names are stored per pet; rename from the pet hover panel.',
85
87
  'settings.enabled': 'Enable the pet',
86
88
  'settings.enabledHint': 'When off, the pet hides and polling stops; re-enable it here.',
89
+ 'settings.decoration': 'Status decoration',
90
+ 'settings.decorationHint': 'Show ornaments like the spouting whale inside the pet status bubbles; when off, bubbles stay text-only.',
87
91
  'settings.visible': 'Show the pet',
88
92
  'settings.visibleHint': 'When off, the pet hides; summon it again from the input row.',
89
93
  'settings.size': 'Size (px)',
@@ -1 +1 @@
1
- {"version":3,"file":"live2d.d.ts","sourceRoot":"","sources":["../../../../src/client/renderers/live2d.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AACnD,OAAO,EAEL,KAAK,WAAW,EAEhB,KAAK,iBAAiB,EACvB,MAAM,6BAA6B,CAAA;AASpC,gFAAgF;AAChF,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE;QAAE,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACtC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;IAClE,WAAW,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,CAAA;IACpD,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;CACpB;AAED,sDAAsD;AACtD,MAAM,MAAM,eAAe,GAAG,cAAc,GAAG,gBAAgB,GAAG,aAAa,CAAA;AAE/E,4EAA4E;AAC5E,MAAM,WAAW,oBAAqB,SAAQ,iBAAiB;IAC7D,2EAA2E;IAC3E,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,0EAA0E;IAC1E,OAAO,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,IAAI,GAAG,IAAI,CAAA;CACzD;AAeD,kCAAkC;AAClC,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C;AAcD,0CAA0C;AAC1C,eAAO,MAAM,cAAc,EAAE,WAAW,CAAC,eAAe,CA+HvD,CAAA"}
1
+ {"version":3,"file":"live2d.d.ts","sourceRoot":"","sources":["../../../../src/client/renderers/live2d.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AACnD,OAAO,EAEL,KAAK,WAAW,EAEhB,KAAK,iBAAiB,EACvB,MAAM,6BAA6B,CAAA;AASpC,gFAAgF;AAChF,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE;QAAE,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACtC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;IAClE,WAAW,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,CAAA;IACpD,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;CACpB;AAED,sDAAsD;AACtD,MAAM,MAAM,eAAe,GAAG,cAAc,GAAG,gBAAgB,GAAG,aAAa,CAAA;AAE/E,4EAA4E;AAC5E,MAAM,WAAW,oBAAqB,SAAQ,iBAAiB;IAC7D,2EAA2E;IAC3E,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,0EAA0E;IAC1E,OAAO,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,IAAI,GAAG,IAAI,CAAA;CACzD;AAwBD,kCAAkC;AAClC,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C;AAcD,0CAA0C;AAC1C,eAAO,MAAM,cAAc,EAAE,WAAW,CAAC,eAAe,CAmIvD,CAAA"}
@@ -24,6 +24,14 @@ import { PET_RENDERER_API_VERSION, } from '../../contracts/renderer.js';
24
24
  import { ensureCubismCore, ensureLive2dVendor, } from './live2d/runtime.js';
25
25
  /** The de-facto tap-motion group of Cubism sample models. */
26
26
  const TAP_GROUP = 'TapBody';
27
+ /**
28
+ * Keep one screen-appropriate atlas LOD instead of asking Pixi for the
29
+ * engine's default full mip chain. A user model can legitimately carry an
30
+ * 8192px texture while the pet itself is only a few hundred pixels tall;
31
+ * `single-auto` preserves the source for larger renders and generates one
32
+ * downsampled atlas only when the effective on-screen scale warrants it.
33
+ */
34
+ const TEXTURE_OPTIONS = { lod: 'single-auto' };
27
35
  let vendorConfigured = false;
28
36
  /** Configure pixi extensions + the Cubism SDK once per page. */
29
37
  function configureOnce(vendor) {
@@ -115,7 +123,11 @@ export const live2dRenderer = {
115
123
  pixiApp.canvas.style.width = '100%';
116
124
  pixiApp.canvas.style.height = '100%';
117
125
  ctx.container.appendChild(pixiApp.canvas);
118
- const loaded = await vendor.Live2DModel.from(config.modelUrl, { autoHitTest: false, autoFocus: false });
126
+ const loaded = await vendor.Live2DModel.from(config.modelUrl, {
127
+ autoHitTest: false,
128
+ autoFocus: false,
129
+ textureOptions: TEXTURE_OPTIONS,
130
+ });
119
131
  if (disposed) {
120
132
  pixiApp.destroy(true, { children: true });
121
133
  return;