@linxin666/dsh-pet 0.1.20 → 0.2.1

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 (85) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +18 -11
  3. package/README.zh.md +18 -11
  4. package/assets/whale/pet.json +9 -0
  5. package/lib/client.js +394 -58
  6. package/lib/client.js.map +1 -1
  7. package/lib/index.js +1007 -43
  8. package/lib/invariant.js +1 -1
  9. package/lib/{state-Diiq8vjk.js → state-DrMX22GL.js} +86 -28
  10. package/lib/types/affinity.d.ts +4 -0
  11. package/lib/types/affinity.d.ts.map +1 -1
  12. package/lib/types/affinity.js +32 -6
  13. package/lib/types/chatter.d.ts +111 -0
  14. package/lib/types/chatter.d.ts.map +1 -0
  15. package/lib/types/chatter.js +756 -0
  16. package/lib/types/client/PetDockEntry.d.ts.map +1 -1
  17. package/lib/types/client/PetDockEntry.js +1 -1
  18. package/lib/types/client/PetSprite.d.ts.map +1 -1
  19. package/lib/types/client/PetSprite.js +133 -16
  20. package/lib/types/client/PluginSettingsCard.d.ts +28 -0
  21. package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
  22. package/lib/types/client/PluginSettingsCard.js +147 -6
  23. package/lib/types/client/index.d.ts.map +1 -1
  24. package/lib/types/client/index.js +1 -0
  25. package/lib/types/client/locales.d.ts +4 -0
  26. package/lib/types/client/locales.d.ts.map +1 -1
  27. package/lib/types/client/locales.js +4 -0
  28. package/lib/types/client/sequences.d.ts +10 -0
  29. package/lib/types/client/sequences.d.ts.map +1 -0
  30. package/lib/types/client/sequences.js +20 -0
  31. package/lib/types/event-projection.d.ts +14 -1
  32. package/lib/types/event-projection.d.ts.map +1 -1
  33. package/lib/types/event-projection.js +38 -17
  34. package/lib/types/ledger.d.ts.map +1 -1
  35. package/lib/types/ledger.js +15 -7
  36. package/lib/types/loopback.d.ts +25 -0
  37. package/lib/types/loopback.d.ts.map +1 -0
  38. package/lib/types/loopback.js +68 -0
  39. package/lib/types/persist.d.ts.map +1 -1
  40. package/lib/types/persist.js +3 -1
  41. package/lib/types/registry.d.ts +7 -1
  42. package/lib/types/registry.d.ts.map +1 -1
  43. package/lib/types/registry.js +47 -3
  44. package/lib/types/remarks.d.ts +2 -0
  45. package/lib/types/remarks.d.ts.map +1 -1
  46. package/lib/types/remarks.js +35 -0
  47. package/lib/types/routes.d.ts.map +1 -1
  48. package/lib/types/routes.js +14 -0
  49. package/lib/types/service.d.ts +6 -0
  50. package/lib/types/service.d.ts.map +1 -1
  51. package/lib/types/service.js +15 -2
  52. package/lib/types/state.d.ts +7 -4
  53. package/lib/types/state.d.ts.map +1 -1
  54. package/lib/types/state.js +20 -20
  55. package/package.json +9 -9
  56. package/src/affinity.test.ts +4 -2
  57. package/src/affinity.ts +37 -6
  58. package/src/chatter.test.ts +154 -0
  59. package/src/chatter.ts +787 -0
  60. package/src/client/PetDockEntry.test.tsx +93 -0
  61. package/src/client/PetDockEntry.tsx +1 -0
  62. package/src/client/PetSprite.test.tsx +253 -3
  63. package/src/client/PetSprite.tsx +192 -37
  64. package/src/client/PluginSettingsCard.tsx +229 -18
  65. package/src/client/index.test.tsx +95 -0
  66. package/src/client/index.ts +1 -0
  67. package/src/client/locales.ts +4 -0
  68. package/src/client/pet-css.test.ts +54 -0
  69. package/src/client/pet.module.css +215 -57
  70. package/src/client/sequences.test.ts +33 -0
  71. package/src/client/sequences.ts +33 -0
  72. package/src/client/settings-card.module.css +90 -1
  73. package/src/event-projection.ts +49 -16
  74. package/src/ledger.test.ts +42 -1
  75. package/src/ledger.ts +15 -7
  76. package/src/loopback.ts +63 -0
  77. package/src/persist.test.ts +18 -1
  78. package/src/persist.ts +3 -1
  79. package/src/registry.test.ts +79 -4
  80. package/src/registry.ts +58 -6
  81. package/src/remarks.ts +36 -0
  82. package/src/routes.ts +11 -0
  83. package/src/service.ts +27 -2
  84. package/src/state.test.ts +13 -0
  85. package/src/state.ts +24 -18
@@ -0,0 +1,10 @@
1
+ /** Pure timing helpers for manifest-defined scene animation sequences. */
2
+ import type { PetTrackDef } from '../registry.ts';
3
+ import type { PetAnimation } from '../state.ts';
4
+ export interface SequenceFrame {
5
+ animation: PetAnimation;
6
+ frameIndex: number;
7
+ }
8
+ /** Resolve the active track and frame after elapsed milliseconds of a looping sequence. */
9
+ export declare function sequenceFrameAt(sequence: readonly PetAnimation[], tracks: Record<PetAnimation, PetTrackDef>, elapsedMs: number): SequenceFrame;
10
+ //# sourceMappingURL=sequences.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sequences.d.ts","sourceRoot":"","sources":["../../../src/client/sequences.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAE1E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AACjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE/C,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,YAAY,CAAA;IACvB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,2FAA2F;AAC3F,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,SAAS,YAAY,EAAE,EACjC,MAAM,EAAE,MAAM,CAAC,YAAY,EAAE,WAAW,CAAC,EACzC,SAAS,EAAE,MAAM,GAChB,aAAa,CAiBf"}
@@ -0,0 +1,20 @@
1
+ /** Pure timing helpers for manifest-defined scene animation sequences. */
2
+ /** Resolve the active track and frame after elapsed milliseconds of a looping sequence. */
3
+ export function sequenceFrameAt(sequence, tracks, elapsedMs) {
4
+ const itemDurations = sequence.map(animation => tracks[animation].durations.reduce((sum, value) => sum + value, 0));
5
+ const sequenceDuration = itemDurations.reduce((sum, value) => sum + value, 0);
6
+ let offset = Math.max(0, elapsedMs) % sequenceDuration;
7
+ let itemIndex = 0;
8
+ while (itemIndex < sequence.length - 1 && offset >= itemDurations[itemIndex]) {
9
+ offset -= itemDurations[itemIndex];
10
+ itemIndex += 1;
11
+ }
12
+ const animation = sequence[itemIndex];
13
+ const track = tracks[animation];
14
+ let frameIndex = 0;
15
+ while (frameIndex < track.frames.length - 1 && offset >= track.durations[frameIndex]) {
16
+ offset -= track.durations[frameIndex];
17
+ frameIndex += 1;
18
+ }
19
+ return { animation, frameIndex };
20
+ }
@@ -3,10 +3,16 @@
3
3
  * vocabulary onto the pet's visual phases and carries an optional completed-
4
4
  * turn reward for the ledger. Holds no state of its own; callers keep a
5
5
  * {@link ProjectionRuntime} per session and feed events in arrival order.
6
+ *
7
+ * Status copy comes from the chatter voice (big rotating pools, per-tool
8
+ * families, real-argument hints), and streamed model output feeds the murmur
9
+ * engine so the pet can whisper its inner voice (碎碎念). The wall clock is
10
+ * injected by the caller, keeping every projection reproducible.
6
11
  * @module @linxin666/dsh-pet/event-projection
7
12
  */
8
13
  import type { SessionEvent } from '@deepseek-ai/dsh-session';
9
14
  import type { PetStateInput } from './state.ts';
15
+ import { StatusVoice, WhisperEngine } from './chatter.ts';
10
16
  /** Runtime shape of the optional legacy activity event. */
11
17
  export interface ActivityStatusEventLike {
12
18
  phase?: string;
@@ -18,11 +24,17 @@ export interface ProjectionRuntime {
18
24
  activeTools: Set<string>;
19
25
  officialEventsSeen: boolean;
20
26
  stepHadFailure: boolean;
27
+ /** Round-robin status copy voice (scene-stable, cadence-rotated). */
28
+ voice: StatusVoice;
29
+ /** Inner-whisper engine fed by the model's own streamed output. */
30
+ whispers: WhisperEngine;
21
31
  }
22
32
  /** One official event projection, optionally carrying a completed turn reward. */
23
33
  export interface PetActivityTransition {
24
34
  input: PetStateInput;
25
35
  completedTurn?: number;
36
+ /** A fresh inner whisper woken by this event's model output, when any. */
37
+ whisper?: string;
26
38
  }
27
39
  /** Fresh projection runtime for a newly seen session. */
28
40
  export declare function emptyProjectionRuntime(): ProjectionRuntime;
@@ -31,6 +43,7 @@ export declare function isActivityPhase(phase: string): phase is PetStateInput['
31
43
  /**
32
44
  * Project the durable DSH session vocabulary into the pet's visual phases.
33
45
  * Unknown and log-only events do not disturb the last meaningful activity.
46
+ * @param nowMs - injected wall clock for copy rotation and whisper pacing.
34
47
  */
35
- export declare function projectOfficialEvent(event: SessionEvent, runtime: ProjectionRuntime): PetActivityTransition | undefined;
48
+ export declare function projectOfficialEvent(event: SessionEvent, runtime: ProjectionRuntime, nowMs?: number): PetActivityTransition | undefined;
36
49
  //# sourceMappingURL=event-projection.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"event-projection.d.ts","sourceRoot":"","sources":["../../src/event-projection.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE/C,2DAA2D;AAC3D,MAAM,WAAW,uBAAuB;IACtC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,qEAAqE;AACrE,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACxB,kBAAkB,EAAE,OAAO,CAAA;IAC3B,cAAc,EAAE,OAAO,CAAA;CACxB;AAED,kFAAkF;AAClF,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,aAAa,CAAA;IACpB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,yDAAyD;AACzD,wBAAgB,sBAAsB,IAAI,iBAAiB,CAE1D;AAQD,wEAAwE;AACxE,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,aAAa,CAAC,OAAO,CAAC,CAE9E;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,iBAAiB,GACzB,qBAAqB,GAAG,SAAS,CA2EnC"}
1
+ {"version":3,"file":"event-projection.d.ts","sourceRoot":"","sources":["../../src/event-projection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC/C,OAAO,EAAE,WAAW,EAAe,aAAa,EAAE,MAAM,cAAc,CAAA;AAEtE,2DAA2D;AAC3D,MAAM,WAAW,uBAAuB;IACtC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,qEAAqE;AACrE,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACxB,kBAAkB,EAAE,OAAO,CAAA;IAC3B,cAAc,EAAE,OAAO,CAAA;IACvB,qEAAqE;IACrE,KAAK,EAAE,WAAW,CAAA;IAClB,mEAAmE;IACnE,QAAQ,EAAE,aAAa,CAAA;CACxB;AAED,kFAAkF;AAClF,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,aAAa,CAAA;IACpB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,yDAAyD;AACzD,wBAAgB,sBAAsB,IAAI,iBAAiB,CAQ1D;AAQD,wEAAwE;AACxE,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,aAAa,CAAC,OAAO,CAAC,CAE9E;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,iBAAiB,EAC1B,KAAK,GAAE,MAAmB,GACzB,qBAAqB,GAAG,SAAS,CAwFnC"}
@@ -3,16 +3,28 @@
3
3
  * vocabulary onto the pet's visual phases and carries an optional completed-
4
4
  * turn reward for the ledger. Holds no state of its own; callers keep a
5
5
  * {@link ProjectionRuntime} per session and feed events in arrival order.
6
+ *
7
+ * Status copy comes from the chatter voice (big rotating pools, per-tool
8
+ * families, real-argument hints), and streamed model output feeds the murmur
9
+ * engine so the pet can whisper its inner voice (碎碎念). The wall clock is
10
+ * injected by the caller, keeping every projection reproducible.
6
11
  * @module @linxin666/dsh-pet/event-projection
7
12
  */
13
+ import { StatusVoice, toolArgHint, WhisperEngine } from './chatter.js';
8
14
  /** Fresh projection runtime for a newly seen session. */
9
15
  export function emptyProjectionRuntime() {
10
- return { activeTools: new Set(), officialEventsSeen: false, stepHadFailure: false };
16
+ return {
17
+ activeTools: new Set(),
18
+ officialEventsSeen: false,
19
+ stepHadFailure: false,
20
+ voice: new StatusVoice(),
21
+ whispers: new WhisperEngine(),
22
+ };
11
23
  }
12
24
  /** Keep tool names readable inside the compact status bubble. */
13
25
  function displayToolName(name) {
14
26
  const compact = name.replace(/\s+/g, ' ').trim() || '工具';
15
- return compact.length <= 24 ? compact : `${compact.slice(0, 21)}...`;
27
+ return compact.length <= 24 ? compact : compact.slice(0, 21) + '...';
16
28
  }
17
29
  /** Whether a legacy phase is part of the pet's supported vocabulary. */
18
30
  export function isActivityPhase(phase) {
@@ -21,35 +33,44 @@ export function isActivityPhase(phase) {
21
33
  /**
22
34
  * Project the durable DSH session vocabulary into the pet's visual phases.
23
35
  * Unknown and log-only events do not disturb the last meaningful activity.
36
+ * @param nowMs - injected wall clock for copy rotation and whisper pacing.
24
37
  */
25
- export function projectOfficialEvent(event, runtime) {
38
+ export function projectOfficialEvent(event, runtime, nowMs = Date.now()) {
26
39
  switch (event.type) {
27
40
  case 'turn/start':
28
41
  runtime.activeTools.clear();
29
42
  runtime.stepHadFailure = false;
30
- return { input: { phase: 'waiting', line: '准备开始' } };
43
+ return { input: { phase: 'waiting', line: runtime.voice.scene('prepare', nowMs) } };
31
44
  case 'step/start':
32
45
  runtime.activeTools.clear();
33
46
  runtime.stepHadFailure = false;
34
- return { input: { phase: 'waiting', line: '等待模型响应' } };
47
+ return { input: { phase: 'waiting', line: runtime.voice.scene('waiting', nowMs) } };
35
48
  case 'assistant/chunk': {
36
49
  const { chunk } = event.data;
37
50
  if (chunk.type === 'reasoning-delta' && chunk.text.length > 0) {
38
- return { input: { phase: 'thinking', line: '正在思考' } };
51
+ const whisper = runtime.whispers.feed(chunk.text, nowMs);
52
+ return {
53
+ input: { phase: 'thinking', line: runtime.voice.scene('thinking', nowMs) },
54
+ ...(whisper === undefined ? {} : { whisper }),
55
+ };
39
56
  }
40
57
  if (chunk.type === 'text-delta' && chunk.text.length > 0) {
41
- return { input: { phase: 'review', line: '整理回复中' } };
58
+ const whisper = runtime.whispers.feed(chunk.text, nowMs);
59
+ return {
60
+ input: { phase: 'review', line: runtime.voice.scene('review', nowMs) },
61
+ ...(whisper === undefined ? {} : { whisper }),
62
+ };
42
63
  }
43
64
  return undefined;
44
65
  }
45
66
  case 'assistant/message':
46
- return { input: { phase: 'review', line: '整理回复中' } };
67
+ return { input: { phase: 'review', line: runtime.voice.scene('review', nowMs) } };
47
68
  case 'tool/call':
48
69
  runtime.activeTools.add(String(event.data.callId));
49
70
  return {
50
71
  input: {
51
72
  phase: 'tool',
52
- line: `正在使用 ${displayToolName(event.data.name)}`,
73
+ line: runtime.voice.tool(event.data.name, displayToolName(event.data.name), toolArgHint(event.data.name, event.data.arguments), nowMs),
53
74
  },
54
75
  };
55
76
  case 'tool/result': {
@@ -60,30 +81,30 @@ export function projectOfficialEvent(event, runtime) {
60
81
  return {
61
82
  input: {
62
83
  phase: 'tool',
63
- line: `还有 ${runtime.activeTools.size} 个工具运行中`,
84
+ line: runtime.voice.toolRemaining(runtime.activeTools.size, nowMs),
64
85
  },
65
86
  };
66
87
  }
67
88
  return runtime.stepHadFailure
68
- ? { input: { phase: 'failed', line: '工具执行失败' } }
69
- : { input: { phase: 'thinking', line: '处理工具结果' } };
89
+ ? { input: { phase: 'failed', line: runtime.voice.scene('toolFailed', nowMs) } }
90
+ : { input: { phase: 'thinking', line: runtime.voice.scene('toolResult', nowMs) } };
70
91
  }
71
92
  case 'turn/end': {
72
93
  runtime.activeTools.clear();
73
94
  switch (event.data.reason.kind) {
74
95
  case 'completed':
75
96
  return {
76
- input: { phase: 'done', line: '完成啦' },
97
+ input: { phase: 'done', line: runtime.voice.scene('done', nowMs) },
77
98
  completedTurn: event.data.turn,
78
99
  };
79
100
  case 'error':
80
- return { input: { phase: 'failed', line: '执行失败' } };
101
+ return { input: { phase: 'failed', line: runtime.voice.scene('failed', nowMs) } };
81
102
  case 'max-tokens':
82
- return { input: { phase: 'failed', line: '达到输出上限' } };
103
+ return { input: { phase: 'failed', line: runtime.voice.scene('maxTokens', nowMs) } };
83
104
  case 'interrupted':
84
- return { input: { phase: 'failed', line: '执行意外中断' } };
105
+ return { input: { phase: 'failed', line: runtime.voice.scene('interrupted', nowMs) } };
85
106
  case 'blocked':
86
- return { input: { phase: 'waiting', line: '等待继续' } };
107
+ return { input: { phase: 'waiting', line: runtime.voice.scene('blocked', nowMs) } };
87
108
  case 'aborted':
88
109
  // A stopped session settles to idle without a bubble: the pet
89
110
  // visibly calms down and the session drops out of the bubble stack.
@@ -1 +1 @@
1
- {"version":3,"file":"ledger.d.ts","sourceRoot":"","sources":["../../src/ledger.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAKL,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,cAAc,EACpB,MAAM,eAAe,CAAA;AACtB,OAAO,EAIL,KAAK,WAAW,EACjB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,cAAc,CAAA;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAEhE,iDAAiD;AACjD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,CAAA;IAClC,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAA;IAC7B,mFAAmF;IACnF,OAAO,CAAC,EAAE,UAAU,CAAA;CACrB;AAED,wEAAwE;AACxE,MAAM,WAAW,uBAAuB;IACtC,4BAA4B;IAC5B,QAAQ,EAAE,MAAM,CAAA;IAChB,kDAAkD;IAClD,KAAK,EAAE,MAAM,CAAA;IACb,6DAA6D;IAC7D,QAAQ,EAAE,eAAe,CAAA;CAC1B;AAED;;;;GAIG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAgB;IAC/C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAa;IACzC,0EAA0E;IAC1E,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,OAAO,CAAY;IAC3B,oFAAoF;IACpF,OAAO,CAAC,aAAa,CAA4B;IACjD,OAAO,CAAC,sBAAsB,CAAI;IAClC,OAAO,CAAC,KAAK,CAAQ;IAErB,YAAY,OAAO,EAAE,UAAU,EAAE,MAAM,GAAE,YAAiB,EAKzD;IAED,iDAAiD;IACjD,IAAI,QAAQ,IAAI,cAAc,CAE7B;IAED,qEAAqE;IACrE,IAAI,QAAQ,IAAI,UAAU,CAEzB;IAED,qCAAqC;IACrC,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,+DAA+D;IAC/D,SAAS,IAAI,OAAO,CAInB;IAED;;;OAGG;IACH,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAErC;IAED,mEAAmE;IACnE,UAAU,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAG1C;IAED,uEAAuE;IACvE,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAI5B;IAED,0EAA0E;IAC1E,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAG5C;IAED;;;OAGG;IACH,UAAU,CAAC,OAAO,CAAC,EAAE,UAAU,GAAG,IAAI,CAErC;IAED;;;;;OAKG;IACH,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAWnC;IAED;;;;OAIG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAOlE;IAED,8EAA8E;IAC9E,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAOvC;IAED,OAAO,CAAC,eAAe;IASvB;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,GAAG,uBAAuB,CA8BrE;IAED,kDAAkD;IAClD,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,eAAe,CAE3C;CACF"}
1
+ {"version":3,"file":"ledger.d.ts","sourceRoot":"","sources":["../../src/ledger.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAKL,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,cAAc,EACpB,MAAM,eAAe,CAAA;AACtB,OAAO,EAIL,KAAK,WAAW,EACjB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,cAAc,CAAA;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAEhE,iDAAiD;AACjD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,CAAA;IAClC,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAA;IAC7B,mFAAmF;IACnF,OAAO,CAAC,EAAE,UAAU,CAAA;CACrB;AAED,wEAAwE;AACxE,MAAM,WAAW,uBAAuB;IACtC,4BAA4B;IAC5B,QAAQ,EAAE,MAAM,CAAA;IAChB,kDAAkD;IAClD,KAAK,EAAE,MAAM,CAAA;IACb,6DAA6D;IAC7D,QAAQ,EAAE,eAAe,CAAA;CAC1B;AAED;;;;GAIG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAgB;IAC/C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAa;IACzC,0EAA0E;IAC1E,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,OAAO,CAAY;IAC3B,oFAAoF;IACpF,OAAO,CAAC,aAAa,CAA4B;IACjD,OAAO,CAAC,sBAAsB,CAAI;IAClC,OAAO,CAAC,KAAK,CAAQ;IAErB,YAAY,OAAO,EAAE,UAAU,EAAE,MAAM,GAAE,YAAiB,EAKzD;IAED,iDAAiD;IACjD,IAAI,QAAQ,IAAI,cAAc,CAE7B;IAED,qEAAqE;IACrE,IAAI,QAAQ,IAAI,UAAU,CAEzB;IAED,qCAAqC;IACrC,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,+DAA+D;IAC/D,SAAS,IAAI,OAAO,CAInB;IAED;;;OAGG;IACH,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAErC;IAED,mEAAmE;IACnE,UAAU,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAG1C;IAED,uEAAuE;IACvE,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAI5B;IAED,0EAA0E;IAC1E,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAG5C;IAED;;;OAGG;IACH,UAAU,CAAC,OAAO,CAAC,EAAE,UAAU,GAAG,IAAI,CAErC;IAED;;;;;OAKG;IACH,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAWnC;IAED;;;;OAIG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAOlE;IAED,8EAA8E;IAC9E,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAOvC;IAED,OAAO,CAAC,eAAe;IASvB;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,GAAG,uBAAuB,CAsCrE;IAED,kDAAkD;IAClD,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,eAAe,CAE3C;CACF"}
@@ -135,12 +135,19 @@ export class PetLedger {
135
135
  interact(kind, nowMs) {
136
136
  if (kind === 'feed')
137
137
  this.settleTreats(nowMs);
138
- const outcome = applyInteraction(this.current.affinity, kind, nowMs, this.affinityConfig);
138
+ const before = this.current.affinity;
139
+ const outcome = applyInteraction(before, kind, nowMs, this.affinityConfig);
139
140
  // Reactions come from the picker pools (custom per-pet slots override
140
141
  // the built-in library per slot); outcome.reaction stays the fallback
141
142
  // copy for direct applyInteraction callers.
142
143
  if (kind === 'feed' && !outcome.accepted) {
143
- return { reaction: this.picker.pick('feedCooldown'), delta: 0, affinity: this.affinityView(nowMs) };
144
+ this.current = { ...this.current, affinity: outcome.affinity };
145
+ this.dirty = true;
146
+ return {
147
+ reaction: this.picker.pickAt('feedCooldown', before.feedRejects),
148
+ delta: 0,
149
+ affinity: this.affinityView(nowMs),
150
+ };
144
151
  }
145
152
  if (kind === 'feed') {
146
153
  const consume = consumeTreat(this.current.treats);
@@ -154,12 +161,13 @@ export class PetLedger {
154
161
  this.current = { ...this.current, treats: consume.ledger };
155
162
  this.dirty = true;
156
163
  }
157
- if (outcome.accepted) {
158
- this.current = { ...this.current, affinity: outcome.affinity };
159
- this.dirty = true;
160
- }
164
+ this.current = { ...this.current, affinity: outcome.affinity };
165
+ this.dirty = true;
166
+ const count = kind === 'pet'
167
+ ? outcome.accepted ? before.pets : before.petRejects
168
+ : before.feeds;
161
169
  return {
162
- reaction: this.picker.pick(outcome.accepted ? kind : 'petCooldown'),
170
+ reaction: this.picker.pickAt(outcome.accepted ? kind : 'petCooldown', count),
163
171
  delta: outcome.delta,
164
172
  affinity: this.affinityView(nowMs),
165
173
  };
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Loopback trust fence shared by the host route families: socket address,
3
+ * Host header, and browser same-origin markers. Packages receive this file as
4
+ * a generated copy via scripts/sync-shared.mjs; edit the shared source and
5
+ * re-run the sync instead of editing a copy.
6
+ *
7
+ * Semantics: RFC 5735 IPv4 127/8, ::1, IPv4-mapped ::ffff:127/8 (matching the
8
+ * remote-web-ui gate), localhost hostnames, plus the browser same-origin
9
+ * markers (sec-fetch-site and Origin) for the request-level fence.
10
+ * @module dsh-web-ui-shared/host/loopback
11
+ */
12
+ import type { IncomingMessage } from 'node:http';
13
+ /** IPv4 127/8 predicate (four decimal octets, first == 127). */
14
+ export declare function isIPv4Loopback(v4: string): boolean;
15
+ /** Whether a socket remote address names the loopback range (127/8, ::1, IPv4-mapped). */
16
+ export declare function isLoopbackAddress(address: string | undefined): boolean;
17
+ /** Whether a normalized URL hostname names the loopback authority (localhost, [::1], 127/8). */
18
+ export declare function isLoopbackHostname(hostname: string): boolean;
19
+ /**
20
+ * Request-level trust fence: a loopback socket address AND a loopback Host
21
+ * header, plus browser same-origin markers. The socket address is
22
+ * authoritative; X-Forwarded-For is never trusted.
23
+ */
24
+ export declare function isLoopbackRequest(request: IncomingMessage): boolean;
25
+ //# sourceMappingURL=loopback.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loopback.d.ts","sourceRoot":"","sources":["../../src/loopback.ts"],"names":[],"mappings":"AACA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAEhD,gEAAgE;AAChE,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAKlD;AAED,0FAA0F;AAC1F,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAMtE;AAED,gGAAgG;AAChG,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAG5D;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAmBnE"}
@@ -0,0 +1,68 @@
1
+ // Generated by scripts/sync-shared.mjs from shared/host/loopback.ts. Do not edit this copy; edit the shared source and run "node scripts/sync-shared.mjs".
2
+ /**
3
+ * Loopback trust fence shared by the host route families: socket address,
4
+ * Host header, and browser same-origin markers. Packages receive this file as
5
+ * a generated copy via scripts/sync-shared.mjs; edit the shared source and
6
+ * re-run the sync instead of editing a copy.
7
+ *
8
+ * Semantics: RFC 5735 IPv4 127/8, ::1, IPv4-mapped ::ffff:127/8 (matching the
9
+ * remote-web-ui gate), localhost hostnames, plus the browser same-origin
10
+ * markers (sec-fetch-site and Origin) for the request-level fence.
11
+ * @module dsh-web-ui-shared/host/loopback
12
+ */
13
+ /** IPv4 127/8 predicate (four decimal octets, first == 127). */
14
+ export function isIPv4Loopback(v4) {
15
+ const parts = v4.split('.');
16
+ return parts.length === 4
17
+ && parts[0] === '127'
18
+ && parts.every(part => /^\d{1,3}$/.test(part) && Number(part) <= 255);
19
+ }
20
+ /** Whether a socket remote address names the loopback range (127/8, ::1, IPv4-mapped). */
21
+ export function isLoopbackAddress(address) {
22
+ if (address === undefined)
23
+ return false;
24
+ const normalized = address.toLowerCase();
25
+ if (normalized === '::1')
26
+ return true;
27
+ if (normalized.startsWith('::ffff:'))
28
+ return isIPv4Loopback(normalized.slice('::ffff:'.length));
29
+ return isIPv4Loopback(normalized);
30
+ }
31
+ /** Whether a normalized URL hostname names the loopback authority (localhost, [::1], 127/8). */
32
+ export function isLoopbackHostname(hostname) {
33
+ if (hostname === 'localhost' || hostname === '[::1]')
34
+ return true;
35
+ return isIPv4Loopback(hostname);
36
+ }
37
+ /**
38
+ * Request-level trust fence: a loopback socket address AND a loopback Host
39
+ * header, plus browser same-origin markers. The socket address is
40
+ * authoritative; X-Forwarded-For is never trusted.
41
+ */
42
+ export function isLoopbackRequest(request) {
43
+ if (!isLoopbackAddress(request.socket.remoteAddress))
44
+ return false;
45
+ const host = request.headers.host;
46
+ if (typeof host !== 'string')
47
+ return false;
48
+ let hostUrl;
49
+ try {
50
+ hostUrl = new URL('http://' + host);
51
+ }
52
+ catch {
53
+ return false;
54
+ }
55
+ if (!isLoopbackHostname(hostUrl.hostname))
56
+ return false;
57
+ if (request.headers['sec-fetch-site'] === 'cross-site')
58
+ return false;
59
+ const origin = request.headers.origin;
60
+ if (origin === undefined)
61
+ return true;
62
+ try {
63
+ return new URL(origin).host === hostUrl.host;
64
+ }
65
+ catch {
66
+ return false;
67
+ }
68
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"persist.d.ts","sourceRoot":"","sources":["../../src/persist.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,EAA+B,KAAK,aAAa,EAAE,MAAM,eAAe,CAAA;AAC/E,OAAO,EAAwC,KAAK,WAAW,EAAE,MAAM,aAAa,CAAA;AAEpF,gDAAgD;AAChD,MAAM,WAAW,gBAAgB;IAC/B,qBAAqB;IACrB,OAAO,EAAE,OAAO,CAAA;IAChB,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAA;IACZ,yDAAyD;IACzD,KAAK,EAAE,MAAM,CAAA;IACb,wDAAwD;IACxD,MAAM,EAAE,MAAM,CAAA;CACf;AAED,eAAO,MAAM,oBAAoB,EAAE,gBAKlC,CAAA;AAED,2EAA2E;AAC3E,eAAO,MAAM,gBAAgB,KAAK,CAAA;AAClC,eAAO,MAAM,gBAAgB,MAAM,CAAA;AACnC,eAAO,MAAM,iBAAiB,QAAS,CAAA;AAEvC,wCAAwC;AACxC,MAAM,WAAW,UAAU;IACzB,sEAAsE;IACtE,KAAK,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC7B,QAAQ,EAAE,aAAa,CAAA;IACvB,gCAAgC;IAChC,MAAM,EAAE,WAAW,CAAA;IACnB,OAAO,EAAE,gBAAgB,CAAA;CAC1B;AAED,qEAAqE;AACrE,eAAO,MAAM,cAAc,eAAe,CAAA;AAE1C,2EAA2E;AAC3E,eAAO,MAAM,gBAAgB,uBAAQ,CAAA;AAErC,wBAAwB;AACxB,eAAO,MAAM,mBAAmB,KAAK,CAAA;AAErC,wBAAgB,YAAY,IAAI,UAAU,CAQzC;AAED;;;;GAIG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AA4BD,4EAA4E;AAC5E,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAqB,GAAG,UAAU,CAiDrE;AAED,sDAAsD;AACtD,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,GAAE,MAAqB,GAAG,IAAI,CAMjF"}
1
+ {"version":3,"file":"persist.d.ts","sourceRoot":"","sources":["../../src/persist.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,EAA+B,KAAK,aAAa,EAAE,MAAM,eAAe,CAAA;AAC/E,OAAO,EAAwC,KAAK,WAAW,EAAE,MAAM,aAAa,CAAA;AAEpF,gDAAgD;AAChD,MAAM,WAAW,gBAAgB;IAC/B,qBAAqB;IACrB,OAAO,EAAE,OAAO,CAAA;IAChB,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAA;IACZ,yDAAyD;IACzD,KAAK,EAAE,MAAM,CAAA;IACb,wDAAwD;IACxD,MAAM,EAAE,MAAM,CAAA;CACf;AAED,eAAO,MAAM,oBAAoB,EAAE,gBAKlC,CAAA;AAED,2EAA2E;AAC3E,eAAO,MAAM,gBAAgB,KAAK,CAAA;AAClC,eAAO,MAAM,gBAAgB,MAAM,CAAA;AACnC,eAAO,MAAM,iBAAiB,QAAS,CAAA;AAEvC,wCAAwC;AACxC,MAAM,WAAW,UAAU;IACzB,sEAAsE;IACtE,KAAK,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC7B,QAAQ,EAAE,aAAa,CAAA;IACvB,gCAAgC;IAChC,MAAM,EAAE,WAAW,CAAA;IACnB,OAAO,EAAE,gBAAgB,CAAA;CAC1B;AAED,qEAAqE;AACrE,eAAO,MAAM,cAAc,eAAe,CAAA;AAE1C,2EAA2E;AAC3E,eAAO,MAAM,gBAAgB,uBAAQ,CAAA;AAErC,wBAAwB;AACxB,eAAO,MAAM,mBAAmB,KAAK,CAAA;AAErC,wBAAgB,YAAY,IAAI,UAAU,CAQzC;AAED;;;;GAIG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AA4BD,4EAA4E;AAC5E,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAqB,GAAG,UAAU,CAmDrE;AAED,sDAAsD;AACtD,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,GAAE,MAAqB,GAAG,IAAI,CAMjF"}
@@ -13,7 +13,7 @@ export const defaultDisplayConfig = {
13
13
  visible: true,
14
14
  size: 160,
15
15
  right: 24,
16
- bottom: 20,
16
+ bottom: 120,
17
17
  };
18
18
  /** Display value bounds (shared by load-time validation and setConfig). */
19
19
  export const DISPLAY_SIZE_MIN = 32;
@@ -78,6 +78,8 @@ export function loadPetPersist(dir = petHomeDir()) {
78
78
  lastFeedAt: clamp(finiteNum(rawAffinity.lastFeedAt, 0), Number.MAX_SAFE_INTEGER),
79
79
  pets: clamp(finiteNum(rawAffinity.pets, 0), Number.MAX_SAFE_INTEGER),
80
80
  feeds: clamp(finiteNum(rawAffinity.feeds, 0), Number.MAX_SAFE_INTEGER),
81
+ petRejects: clamp(finiteNum(rawAffinity.petRejects, 0), Number.MAX_SAFE_INTEGER),
82
+ feedRejects: clamp(finiteNum(rawAffinity.feedRejects, 0), Number.MAX_SAFE_INTEGER),
81
83
  turns: clamp(finiteNum(rawAffinity.turns, 0), Number.MAX_SAFE_INTEGER),
82
84
  };
83
85
  const rawTreats = (parsed.treats ?? {});
@@ -16,7 +16,7 @@
16
16
  * whale-girl manifest overrides its own durations.
17
17
  * @module @linxin666/dsh-pet/registry
18
18
  */
19
- import type { PetAnimation } from './state.ts';
19
+ import type { ActivityPhase, PetAnimation } from './state.ts';
20
20
  import { type PetRemarks, type PetRemarksManifest } from './remarks.ts';
21
21
  /** Fixed row order of the 9-state animation contract. */
22
22
  export declare const PET_ROW_ORDER: readonly PetAnimation[];
@@ -81,6 +81,8 @@ export interface PetManifest {
81
81
  frames?: number[];
82
82
  /** Optional per-track rhythm overrides; omitted tracks use the defaults. */
83
83
  tracks?: Partial<Record<PetAnimation, PetTrackOverride>>;
84
+ /** Optional per-scene track sequences; every declared sequence has at least 5 items. */
85
+ sequences?: Partial<Record<ActivityPhase, PetAnimation[]>>;
84
86
  /**
85
87
  * Optional witty-remark overrides the pet speaks on interactions
86
88
  * (community contributions use this to give their pet its own voice).
@@ -109,8 +111,12 @@ export interface PetDefinition {
109
111
  columns: number;
110
112
  /** Per-row frame counts (length 9, row order above). */
111
113
  rows: number[];
114
+ /** Total atlas rows (9 for v1, 11 for v2 look-row atlases). */
115
+ atlasRows: number;
112
116
  /** Fully resolved animation tracks (frames + durations + loop/fallback). */
113
117
  tracks: Record<PetAnimation, PetTrackDef>;
118
+ /** Validated per-scene track sequences; omitted scenes keep single-track playback. */
119
+ sequences?: Partial<Record<ActivityPhase, PetAnimation[]>>;
114
120
  /** Browser URL of the atlas (served by the host asset route). */
115
121
  atlasUrl: string;
116
122
  /** Browser URL of the manifest (served by the host asset route). */
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/registry.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/registry.ts"],"names":[],"mappings":"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"}
@@ -86,6 +86,35 @@ const PET_ID_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
86
86
  /** Safe path-segment charset for atlas files. */
87
87
  const PATH_SEGMENT_PATTERN = /^[A-Za-z0-9._-]+$/;
88
88
  const PET_NAME_MAX_LENGTH = 80;
89
+ const PET_PHASES = ['idle', 'waiting', 'thinking', 'tool', 'review', 'done', 'failed'];
90
+ /** Validate optional scene sequences without rejecting an otherwise usable pet. */
91
+ function normalizeSequences(raw, id, warn) {
92
+ if (raw === undefined)
93
+ return undefined;
94
+ if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
95
+ warn('manifest ' + id + ': sequences must be an object keyed by activity phase');
96
+ return undefined;
97
+ }
98
+ const sequences = {};
99
+ for (const [phase, value] of Object.entries(raw)) {
100
+ if (!PET_PHASES.includes(phase)) {
101
+ warn('manifest ' + id + ': unknown sequence phase ' + JSON.stringify(phase));
102
+ continue;
103
+ }
104
+ if (!Array.isArray(value) || value.length < 5) {
105
+ warn('manifest ' + id + ': sequence ' + phase + ' must contain at least 5 animations');
106
+ continue;
107
+ }
108
+ const unknownIndex = value.findIndex(animation => typeof animation !== 'string' || !PET_ROW_ORDER.includes(animation));
109
+ if (unknownIndex !== -1) {
110
+ const unknown = value[unknownIndex];
111
+ warn('manifest ' + id + ': sequence ' + phase + ' contains unknown animation ' + JSON.stringify(unknown));
112
+ continue;
113
+ }
114
+ sequences[phase] = value;
115
+ }
116
+ return Object.keys(sequences).length === 0 ? undefined : sequences;
117
+ }
89
118
  /**
90
119
  * Normalize one parsed manifest into a renderable pet entry, or undefined
91
120
  * (with a warning recorded) when the manifest violates the contract.
@@ -126,11 +155,14 @@ export function resolvePetManifest(raw, dir, options = {}) {
126
155
  height: finiteInt(rawCell.height, DEFAULT_PET_CELL.height, 2048),
127
156
  };
128
157
  const columns = finiteInt(source.columns, DEFAULT_PET_COLUMNS, 32);
158
+ // v2 atlases (spriteVersionNumber 2) hold 11 rows: 9 animation rows + 2 look rows.
159
+ const atlasRowCount = source.spriteVersionNumber === 2 ? 11 : DEFAULT_PET_ROW_COUNT;
129
160
  const rows = DEFAULT_FRAME_COUNTS.map((fallback, index) => {
130
161
  const value = Array.isArray(source.frames) ? source.frames[index] : undefined;
131
162
  return finiteInt(value, fallback, columns);
132
163
  });
133
164
  const remarks = normalizePetRemarks(source.remarks, message => warn('manifest ' + id + ': ' + message));
165
+ const sequences = normalizeSequences(source.sequences, id, warn);
134
166
  const trackOverrides = (typeof source.tracks === 'object' && source.tracks !== null ? source.tracks : {});
135
167
  const tracks = {};
136
168
  for (const [row, animation] of PET_ROW_ORDER.entries()) {
@@ -165,7 +197,9 @@ export function resolvePetManifest(raw, dir, options = {}) {
165
197
  cell,
166
198
  columns,
167
199
  rows,
200
+ atlasRows: atlasRowCount,
168
201
  tracks,
202
+ ...(sequences === undefined ? {} : { sequences }),
169
203
  atlasUrl: assetUrl(assetPrefix, id, spritesheet),
170
204
  manifestUrl: assetUrl(assetPrefix, id, 'pet.json'),
171
205
  dir,
@@ -237,10 +271,18 @@ export function loadPetRegistry(options) {
237
271
  }
238
272
  }
239
273
  for (const manifest of options.extra ?? []) {
240
- const dir = manifest.spritesheetPath === undefined || isAbsolute(manifest.spritesheetPath)
274
+ const raw = manifest.spritesheetPath;
275
+ const dir = raw === undefined || isAbsolute(raw)
241
276
  ? join(packageRoot, 'assets', 'extra')
242
- : dirname(resolve(packageRoot, manifest.spritesheetPath));
243
- const entry = resolvePetManifest(manifest, dir, { assetPrefix, warnings });
277
+ : dirname(resolve(packageRoot, raw));
278
+ // petAtlasFile joins entry.dir (already the spritesheet's parent when the
279
+ // path is package-relative) with entry.spritesheetPath, so the stored path
280
+ // must be the basename only — otherwise the directory segment is applied
281
+ // twice and the atlas 404s.
282
+ const source = raw === undefined || isAbsolute(raw)
283
+ ? manifest
284
+ : { ...manifest, spritesheetPath: basename(raw) };
285
+ const entry = resolvePetManifest(source, dir, { assetPrefix, warnings });
244
286
  if (entry === undefined)
245
287
  continue;
246
288
  if (byId.has(entry.id))
@@ -264,7 +306,9 @@ export function petEntryView(entry) {
264
306
  cell: entry.cell,
265
307
  columns: entry.columns,
266
308
  rows: entry.rows,
309
+ atlasRows: entry.atlasRows,
267
310
  tracks: entry.tracks,
311
+ ...(entry.sequences === undefined ? {} : { sequences: entry.sequences }),
268
312
  atlasUrl: entry.atlasUrl,
269
313
  manifestUrl: entry.manifestUrl,
270
314
  };
@@ -52,5 +52,7 @@ export declare class RemarkPicker {
52
52
  pool(kind: RemarkKind): readonly string[];
53
53
  /** The next line for one slot (round-robin within its pool). */
54
54
  pick(kind: RemarkKind): string;
55
+ /** Select a line from a stable external counter without changing local picker state. */
56
+ pickAt(kind: RemarkKind, count: number): string;
55
57
  }
56
58
  //# sourceMappingURL=remarks.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"remarks.d.ts","sourceRoot":"","sources":["../../src/remarks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,wDAAwD;AACxD,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,aAAa,GAAG,MAAM,GAAG,cAAc,GAAG,UAAU,CAAA;AAErF,4CAA4C;AAC5C,eAAO,MAAM,YAAY,EAAE,SAAS,UAAU,EAM7C,CAAA;AAED,6EAA6E;AAC7E,MAAM,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC,CAAC,CAAA;AAE9D,yEAAyE;AACzE,MAAM,MAAM,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC,CAAA;AAE/E,2EAA2E;AAC3E,eAAO,MAAM,eAAe,MAAM,CAAA;AAClC,sCAAsC;AACtC,eAAO,MAAM,gBAAgB,KAAK,CAAA;AAElC;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,EAAE,QAAQ,CAAC,MAAM,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC,CA6D3E,CAAA;AAED,yEAAyE;AACzE,wBAAgB,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAEtD;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,OAAO,EACZ,SAAS,GAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAe,GAC9C,UAAU,GAAG,SAAS,CAyBxB;AAED;;;;GAIG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgC;IACzD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAuC;IAE7D,YAAY,SAAS,CAAC,EAAE,UAAU,EAMjC;IAED,qEAAqE;IACrE,IAAI,CAAC,IAAI,EAAE,UAAU,GAAG,SAAS,MAAM,EAAE,CAExC;IAED,gEAAgE;IAChE,IAAI,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAK7B;CACF"}
1
+ {"version":3,"file":"remarks.d.ts","sourceRoot":"","sources":["../../src/remarks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,wDAAwD;AACxD,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,aAAa,GAAG,MAAM,GAAG,cAAc,GAAG,UAAU,CAAA;AAErF,4CAA4C;AAC5C,eAAO,MAAM,YAAY,EAAE,SAAS,UAAU,EAM7C,CAAA;AAED,6EAA6E;AAC7E,MAAM,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC,CAAC,CAAA;AAE9D,yEAAyE;AACzE,MAAM,MAAM,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC,CAAA;AAE/E,2EAA2E;AAC3E,eAAO,MAAM,eAAe,MAAM,CAAA;AAClC,sCAAsC;AACtC,eAAO,MAAM,gBAAgB,KAAK,CAAA;AAElC;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,EAAE,QAAQ,CAAC,MAAM,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC,CA2F3E,CAAA;AAED,yEAAyE;AACzE,wBAAgB,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAEtD;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,OAAO,EACZ,SAAS,GAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAe,GAC9C,UAAU,GAAG,SAAS,CAyBxB;AAED;;;;GAIG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgC;IACzD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAuC;IAE7D,YAAY,SAAS,CAAC,EAAE,UAAU,EAMjC;IAED,qEAAqE;IACrE,IAAI,CAAC,IAAI,EAAE,UAAU,GAAG,SAAS,MAAM,EAAE,CAExC;IAED,gEAAgE;IAChE,IAAI,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAK7B;IAED,wFAAwF;IACxF,MAAM,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAG9C;CACF"}
@@ -41,6 +41,12 @@ export const BUILTIN_REMARKS = {
41
41
  '蹭蹭你的手心,这是回礼~',
42
42
  '多摸摸我,亲密度会涨哦!',
43
43
  '闭眼享受中,请勿打扰~',
44
+ '头再低一点,够不着了~',
45
+ '呼噜呼噜,声音都冒出来了',
46
+ '这手感,比小鱼干还上瘾',
47
+ '摸到第三下,满意',
48
+ '耳朵后面,别漏了……啊,舒服',
49
+ '被摸得尾巴都卷起来了',
44
50
  ],
45
51
  petCooldown: [
46
52
  '摸过头啦,让鲸鱼娘歇口气~',
@@ -53,6 +59,12 @@ export const BUILTIN_REMARKS = {
53
59
  '歇一歇,摸摸的手感会更好哦~',
54
60
  '咕……等我回个蓝~',
55
61
  '让我先消化一下刚才的爱!',
62
+ '痒痒的,先让我缓一下……',
63
+ '再摸就掉线了,真的',
64
+ '库存的呼噜声用完了',
65
+ '手歇会儿,我也要补个蓝',
66
+ '舒服归舒服,得缓缓呀',
67
+ '再摸下去,我就要融化了',
56
68
  ],
57
69
  feed: [
58
70
  '呜哇!小鱼干好好吃!',
@@ -65,6 +77,12 @@ export const BUILTIN_REMARKS = {
65
77
  '饱餐一顿,马上满血复活~',
66
78
  '这个味道,是幸福的味道!',
67
79
  '吃完了还不忘舔舔爪子~',
80
+ '这小鱼干,是今天的顶配',
81
+ '一口下去,精神头全回来了',
82
+ '脆!香!就是这个味儿',
83
+ '边吃边摇尾巴,形象不要了',
84
+ '好吃到眼睛都眯起来了',
85
+ '这块鱼干我记住了,懂我的',
68
86
  ],
69
87
  feedCooldown: [
70
88
  '吃饱啦,晚点再喂~',
@@ -77,6 +95,12 @@ export const BUILTIN_REMARKS = {
77
95
  '肚皮已经鼓鼓的啦~',
78
96
  '好吃是好吃,可也得节制呀~',
79
97
  '等我饿了会告诉你哦~',
98
+ '胃说它满了,脑子说还能吃',
99
+ '这条得留着慢慢品',
100
+ '先消消食,待会儿再战',
101
+ '塞不下了,真塞不下了',
102
+ '闻着香,可惜没地方放了',
103
+ '嗝……这顿值了',
80
104
  ],
81
105
  noTreats: [
82
106
  '没有小鱼干了,多陪我工作一会儿吧~',
@@ -89,6 +113,12 @@ export const BUILTIN_REMARKS = {
89
113
  '饿着肚子等你完成下一轮任务~',
90
114
  '小鱼干藏在你的工作里,去找找看!',
91
115
  '先工作后干饭,我们的约定哦~',
116
+ '粮仓见底,全靠感情撑着了',
117
+ '饿是真饿,活也是真得干',
118
+ '画饼充饥……不对,画鱼干充饥',
119
+ '没鱼干的日子,靠意志力过',
120
+ '下一轮任务,我闻到了鱼干味',
121
+ '先记账上,欠我两条,记住啦',
92
122
  ],
93
123
  };
94
124
  /** The legacy first line of one kind (direct callers' fallback copy). */
@@ -154,4 +184,9 @@ export class RemarkPicker {
154
184
  this.counters.set(kind, index + 1);
155
185
  return pool[index];
156
186
  }
187
+ /** Select a line from a stable external counter without changing local picker state. */
188
+ pickAt(kind, count) {
189
+ const pool = this.pools[kind];
190
+ return pool[Math.max(0, Math.floor(count)) % pool.length];
191
+ }
157
192
  }
@@ -1 +1 @@
1
- {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAMH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAA;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAI9C,+CAA+C;AAC/C,eAAO,MAAM,cAAc,aAAa,CAAA;AAExC,0EAA0E;AAC1E,eAAO,MAAM,gBAAgB,SAAS,CAAA;AAuNtC,kEAAkE;AAClE,wBAAgB,aAAa,CAAC,IAAI,EAAE;IAAE,OAAO,EAAE,UAAU,CAAA;CAAE,GAAG,QAAQ,EAAE,CAwCvE;AAGD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA"}
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAMH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAA;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAK9C,+CAA+C;AAC/C,eAAO,MAAM,cAAc,aAAa,CAAA;AAExC,0EAA0E;AAC1E,eAAO,MAAM,gBAAgB,SAAS,CAAA;AAiOtC,kEAAkE;AAClE,wBAAgB,aAAa,CAAC,IAAI,EAAE;IAAE,OAAO,EAAE,UAAU,CAAA;CAAE,GAAG,QAAQ,EAAE,CAwCvE;AAGD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA"}