@linxin666/dsh-pet 0.3.4 → 0.3.6

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 +13 -10
  3. package/README.zh.md +13 -10
  4. package/contracts/voice-pack-v1.schema.json +53 -28
  5. package/cordis.patch.yml +1 -1
  6. package/lib/client.js +166 -30
  7. package/lib/client.js.map +1 -1
  8. package/lib/index.js +400 -478
  9. package/lib/invariant.js +1 -1
  10. package/lib/{state-DrMX22GL.js → state-NvDEoCln.js} +5 -2
  11. package/lib/types/access.d.ts +5 -4
  12. package/lib/types/access.d.ts.map +1 -1
  13. package/lib/types/access.js +2 -12
  14. package/lib/types/affinity.d.ts +2 -0
  15. package/lib/types/affinity.d.ts.map +1 -1
  16. package/lib/types/affinity.js +12 -0
  17. package/lib/types/chatter.d.ts +68 -46
  18. package/lib/types/chatter.d.ts.map +1 -1
  19. package/lib/types/chatter.js +243 -301
  20. package/lib/types/client/PetSprite.d.ts.map +1 -1
  21. package/lib/types/client/PetSprite.js +23 -18
  22. package/lib/types/client/index.d.ts.map +1 -1
  23. package/lib/types/client/index.js +26 -9
  24. package/lib/types/client/locales.d.ts +18 -0
  25. package/lib/types/client/locales.d.ts.map +1 -1
  26. package/lib/types/client/locales.js +18 -0
  27. package/lib/types/client/renderers/frames2d.d.ts +12 -0
  28. package/lib/types/client/renderers/frames2d.d.ts.map +1 -1
  29. package/lib/types/client/renderers/frames2d.js +143 -18
  30. package/lib/types/event-projection.d.ts +9 -4
  31. package/lib/types/event-projection.d.ts.map +1 -1
  32. package/lib/types/event-projection.js +46 -14
  33. package/lib/types/ledger.d.ts +4 -2
  34. package/lib/types/ledger.d.ts.map +1 -1
  35. package/lib/types/ledger.js +4 -4
  36. package/lib/types/pair-access.d.ts +35 -0
  37. package/lib/types/pair-access.d.ts.map +1 -0
  38. package/lib/types/pair-access.js +19 -0
  39. package/lib/types/remarks.d.ts +1 -1
  40. package/lib/types/remarks.d.ts.map +1 -1
  41. package/lib/types/remarks.js +11 -2
  42. package/lib/types/routes.d.ts.map +1 -1
  43. package/lib/types/routes.js +8 -3
  44. package/lib/types/service.d.ts +7 -11
  45. package/lib/types/service.d.ts.map +1 -1
  46. package/lib/types/service.js +31 -20
  47. package/lib/types/voice-pack.d.ts +11 -7
  48. package/lib/types/voice-pack.d.ts.map +1 -1
  49. package/lib/types/voice-pack.js +80 -49
  50. package/package.json +1 -1
  51. package/src/access.ts +7 -27
  52. package/src/affinity.ts +13 -0
  53. package/src/chatter.test.ts +68 -38
  54. package/src/chatter.ts +269 -316
  55. package/src/client/PetSprite.test.tsx +50 -15
  56. package/src/client/PetSprite.tsx +30 -25
  57. package/src/client/index.test.tsx +10 -1
  58. package/src/client/index.ts +29 -10
  59. package/src/client/locales.ts +18 -0
  60. package/src/client/renderers/frames2d.test.ts +101 -0
  61. package/src/client/renderers/frames2d.ts +138 -18
  62. package/src/event-projection.ts +57 -16
  63. package/src/ledger.ts +6 -4
  64. package/src/pair-access.ts +49 -0
  65. package/src/remarks.test.ts +10 -0
  66. package/src/remarks.ts +9 -2
  67. package/src/routes.ts +9 -4
  68. package/src/service.ts +36 -30
  69. package/src/voice-pack.test.ts +87 -25
  70. package/src/voice-pack.ts +87 -49
package/src/voice-pack.ts CHANGED
@@ -31,9 +31,13 @@
31
31
  import {
32
32
  STATUS_SCENES,
33
33
  TOOL_CATEGORIES,
34
+ WHISPER_CATEGORIES,
35
+ WHISPER_RESULTS,
34
36
  type VoicePackOverrides,
35
- type WhisperRule,
37
+ type WhisperCategory,
38
+ type WhisperResult,
36
39
  } from './chatter.ts'
40
+ import { normalizePetRemarks, type PetRemarks } from './remarks.ts'
37
41
 
38
42
  /** Schema version this module normalizes (optional field; missing = 1). */
39
43
  export const VOICE_PACK_V1 = 1 as const
@@ -66,14 +70,15 @@ export interface VoicePack {
66
70
  overrides: VoicePackOverrides
67
71
  /** Hover-panel chrome, when the pack declares any. */
68
72
  panel?: PetPanelView
73
+ /** Pat/feed interaction remark pools (issue #1226). */
74
+ remarks?: PetRemarks
75
+ /** Affinity rank name overrides (issue #1226). */
76
+ ranks?: Record<string, string>
69
77
  }
70
78
 
71
79
  /** Hard caps shared by every pool slot (mirrors the remarks discipline). */
72
80
  export const VOICE_POOL_LINES_MAX = 64
73
81
  export const VOICE_LINE_MAX = 160
74
- export const VOICE_KEYWORDS_PER_RULE_MAX = 16
75
- export const VOICE_KEYWORD_MAX = 40
76
- export const VOICE_RULES_MAX = 32
77
82
  export const VOICE_LABEL_MAX = 40
78
83
  export const VOICE_STAT_MAX = 80
79
84
 
@@ -87,7 +92,7 @@ const PLACEHOLDER_WHITELIST: Partial<Record<PoolKind, readonly string[]>> = {
87
92
  stat: ['{rank}', '{n}', '{points}'],
88
93
  }
89
94
 
90
- type PoolKind = 'status' | 'tools' | 'toolRemaining' | 'whisperGeneric' | 'whisperRule' | 'label' | 'stat'
95
+ type PoolKind = 'status' | 'tools' | 'toolRemaining' | 'whisperCategory' | 'whisperResult' | 'label' | 'stat'
91
96
 
92
97
  function isRecord(value: unknown): value is Record<string, unknown> {
93
98
  return typeof value === 'object' && value !== null && !Array.isArray(value)
@@ -151,51 +156,48 @@ export function normalizePool(
151
156
  return pool
152
157
  }
153
158
 
154
- /** Normalize the ordered keyword-rule list ([] disables the keyword channel). */
155
- export function normalizeWhisperRules(
159
+ /** Normalize whisper category pools; a key replaces that category's built-in pool (an explicit empty pool mutes it). */
160
+ export function normalizeWhisperCategories(
156
161
  raw: unknown,
157
162
  onWarning: (message: string) => void = () => {},
158
- ): WhisperRule[] | undefined {
163
+ ): Partial<Record<WhisperCategory, readonly string[]>> | undefined {
159
164
  if (raw === undefined) return undefined
160
- if (!Array.isArray(raw)) {
161
- onWarning('whispers.rules must be an array')
165
+ if (!isRecord(raw)) {
166
+ onWarning('whispers.categories must be an object')
162
167
  return undefined
163
168
  }
164
- if (raw.length > VOICE_RULES_MAX) {
165
- onWarning('whispers.rules has more than ' + VOICE_RULES_MAX + ' rules; extra rules are ignored')
166
- }
167
- const rules: WhisperRule[] = []
168
- for (const item of raw.slice(0, VOICE_RULES_MAX)) {
169
- if (!isRecord(item)) {
170
- onWarning('whisper rule must be an object')
169
+ const pools: Partial<Record<WhisperCategory, readonly string[]>> = {}
170
+ for (const key of Object.keys(raw)) {
171
+ if (!(WHISPER_CATEGORIES as readonly string[]).includes(key)) {
172
+ onWarning('unknown whisper category ' + key + ' ignored')
171
173
  continue
172
174
  }
173
- for (const key of Object.keys(item)) {
174
- if (key !== 'keywords' && key !== 'pool') onWarning('unknown whisper rule field ' + key + ' ignored')
175
- }
176
- const keywordsRaw: unknown = item.keywords
177
- const keywords: string[] = []
178
- if (Array.isArray(keywordsRaw)) {
179
- for (const entry of keywordsRaw.slice(0, VOICE_KEYWORDS_PER_RULE_MAX)) {
180
- if (typeof entry !== 'string') {
181
- onWarning('non-string keyword dropped')
182
- continue
183
- }
184
- const trimmed = entry.trim().toLowerCase().slice(0, VOICE_KEYWORD_MAX)
185
- if (trimmed !== '') keywords.push(trimmed)
186
- }
187
- if (keywordsRaw.length > VOICE_KEYWORDS_PER_RULE_MAX) {
188
- onWarning('rule has more than ' + VOICE_KEYWORDS_PER_RULE_MAX + ' keywords; extra keywords are ignored')
189
- }
190
- }
191
- const pool = normalizePool(item.pool, 'whisperRule', onWarning)
192
- if (keywords.length === 0 || pool === undefined || pool.length === 0) {
193
- onWarning('whisper rule dropped (needs keywords and a non-empty pool)')
175
+ const pool = normalizePool(raw[key], 'whisperCategory', onWarning)
176
+ if (pool !== undefined) pools[key as WhisperCategory] = pool
177
+ }
178
+ return Object.keys(pools).length > 0 ? pools : undefined
179
+ }
180
+
181
+ /** Normalize whisper outcome pools; a key replaces that outcome's built-in pool (an explicit empty pool mutes it). */
182
+ export function normalizeWhisperResults(
183
+ raw: unknown,
184
+ onWarning: (message: string) => void = () => {},
185
+ ): Partial<Record<WhisperResult, readonly string[]>> | undefined {
186
+ if (raw === undefined) return undefined
187
+ if (!isRecord(raw)) {
188
+ onWarning('whispers.results must be an object')
189
+ return undefined
190
+ }
191
+ const pools: Partial<Record<WhisperResult, readonly string[]>> = {}
192
+ for (const key of Object.keys(raw)) {
193
+ if (!(WHISPER_RESULTS as readonly string[]).includes(key)) {
194
+ onWarning('unknown whisper result ' + key + ' ignored')
194
195
  continue
195
196
  }
196
- rules.push({ keywords, pool })
197
+ const pool = normalizePool(raw[key], 'whisperResult', onWarning)
198
+ if (pool !== undefined) pools[key as WhisperResult] = pool
197
199
  }
198
- return rules
200
+ return Object.keys(pools).length > 0 ? pools : undefined
199
201
  }
200
202
 
201
203
  /** Normalize the panel block (labels / stats / actions; warn-and-drop). */
@@ -268,10 +270,10 @@ export function normalizePanel(
268
270
  }
269
271
 
270
272
  /** Voice-pack top-level fields ('$schema' mirrors the schema twin; drift-locked in tests). */
271
- export const VOICE_PACK_KEYS = new Set(['$schema', 'voicePackVersion', 'status', 'tools', 'toolRemaining', 'whispers', 'panel'])
273
+ export const VOICE_PACK_KEYS = new Set(['$schema', 'voicePackVersion', 'status', 'tools', 'toolRemaining', 'whispers', 'panel', 'remarks', 'ranks'])
272
274
 
273
275
  /** Allowed whisper-section fields (drift-locked in tests). */
274
- export const WHISPER_KEYS = new Set(['generic', 'rules'])
276
+ export const WHISPER_KEYS = new Set(['categories', 'results'])
275
277
 
276
278
  /**
277
279
  * Normalize one raw voice.json document into a VoicePack, or undefined when
@@ -340,29 +342,55 @@ export function normalizeVoicePack(
340
342
  onWarning('whispers must be an object')
341
343
  } else {
342
344
  for (const key of Object.keys(whispersRaw)) {
343
- if (!WHISPER_KEYS.has(key)) onWarning('unknown whispers field ' + key + ' ignored')
345
+ if (key === 'generic' || key === 'rules') {
346
+ // pet M6: keyword and ambient whisper channels are gone; legacy
347
+ // fields are ignored with a warning, never mapped to the new shape.
348
+ onWarning('whispers.' + key + ' is no longer supported and was ignored')
349
+ } else if (!WHISPER_KEYS.has(key)) {
350
+ onWarning('unknown whispers field ' + key + ' ignored')
351
+ }
344
352
  }
345
- const generic = normalizePool(whispersRaw.generic, 'whisperGeneric', onWarning)
346
- const rules = normalizeWhisperRules(whispersRaw.rules, onWarning)
347
- if (generic !== undefined || rules !== undefined) {
353
+ const categories = normalizeWhisperCategories(whispersRaw.categories, onWarning)
354
+ const results = normalizeWhisperResults(whispersRaw.results, onWarning)
355
+ if (categories !== undefined || results !== undefined) {
348
356
  overrides.whispers = {
349
- ...(generic === undefined ? {} : { generic }),
350
- ...(rules === undefined ? {} : { rules }),
357
+ ...(categories === undefined ? {} : { categories }),
358
+ ...(results === undefined ? {} : { results }),
351
359
  }
352
360
  }
353
361
  }
354
362
  }
355
363
  const panel = raw.panel === undefined ? undefined : normalizePanel(raw.panel, onWarning)
364
+ const remarks = raw.remarks === undefined ? undefined : normalizePetRemarks(raw.remarks, onWarning)
365
+ const ranksRaw = raw.ranks
366
+ let ranks: Record<string, string> | undefined
367
+ if (ranksRaw !== undefined) {
368
+ if (!isRecord(ranksRaw)) {
369
+ onWarning('ranks must be an object')
370
+ } else {
371
+ const out: Record<string, string> = {}
372
+ for (const [key, value] of Object.entries(ranksRaw)) {
373
+ if (typeof value === 'string' && value.trim() !== '') {
374
+ out[key] = value.trim().slice(0, VOICE_STAT_MAX)
375
+ } else {
376
+ onWarning('invalid rank name for ' + key)
377
+ }
378
+ }
379
+ if (Object.keys(out).length > 0) ranks = out
380
+ }
381
+ }
356
382
  if (
357
383
  overrides.status === undefined && overrides.tools === undefined
358
384
  && overrides.toolRemaining === undefined && overrides.whispers === undefined
359
- && panel === undefined
385
+ && panel === undefined && remarks === undefined && ranks === undefined
360
386
  ) {
361
387
  return undefined
362
388
  }
363
389
  return {
364
390
  overrides,
365
391
  ...(panel === undefined ? {} : { panel }),
392
+ ...(remarks === undefined ? {} : { remarks }),
393
+ ...(ranks === undefined ? {} : { ranks }),
366
394
  }
367
395
  }
368
396
 
@@ -376,6 +404,8 @@ export function mergeVoicePacks(...layers: (VoicePack | undefined)[]): VoicePack
376
404
  const overrides: VoicePackOverrides = {}
377
405
  const labels: NonNullable<PetPanelView['labels']> = {}
378
406
  const stats: NonNullable<PetPanelView['stats']> = {}
407
+ let remarks: PetRemarks | undefined
408
+ let ranks: Record<string, string> | undefined
379
409
  let actions: PanelAction[] | undefined
380
410
  let panelSeen = false
381
411
  let any = false
@@ -398,6 +428,12 @@ export function mergeVoicePacks(...layers: (VoicePack | undefined)[]): VoicePack
398
428
  if (layer.panel.stats !== undefined) Object.assign(stats, layer.panel.stats)
399
429
  if (layer.panel.actions !== undefined) actions = layer.panel.actions
400
430
  }
431
+ if (layer.remarks !== undefined) {
432
+ remarks = { ...(remarks ?? {}), ...layer.remarks }
433
+ }
434
+ if (layer.ranks !== undefined) {
435
+ ranks = { ...(ranks ?? {}), ...layer.ranks }
436
+ }
401
437
  }
402
438
  if (!any) return undefined
403
439
  const panel: PetPanelView = {
@@ -409,5 +445,7 @@ export function mergeVoicePacks(...layers: (VoicePack | undefined)[]): VoicePack
409
445
  return {
410
446
  overrides,
411
447
  ...(panelSeen && !panelEmpty ? { panel } : {}),
448
+ ...(remarks !== undefined ? { remarks } : {}),
449
+ ...(ranks !== undefined ? { ranks } : {}),
412
450
  }
413
451
  }