@linxin666/dsh-pet 0.3.3 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +30 -13
  3. package/README.zh.md +30 -13
  4. package/assets/ouo-neko/pet.json +31 -0
  5. package/assets/ouo-neko/previews/failed.gif +0 -0
  6. package/assets/ouo-neko/previews/idle.gif +0 -0
  7. package/assets/ouo-neko/previews/jumping.gif +0 -0
  8. package/assets/ouo-neko/previews/review.gif +0 -0
  9. package/assets/ouo-neko/previews/running-left.gif +0 -0
  10. package/assets/ouo-neko/previews/running-right.gif +0 -0
  11. package/assets/ouo-neko/previews/running.gif +0 -0
  12. package/assets/ouo-neko/previews/waiting.gif +0 -0
  13. package/assets/ouo-neko/previews/waving.gif +0 -0
  14. package/assets/ouo-neko/spritesheet.webp +0 -0
  15. package/contracts/pet-manifest-v2.schema.json +102 -1
  16. package/contracts/voice-pack-v1.schema.json +23 -28
  17. package/lib/client.js +781 -40
  18. package/lib/client.js.map +1 -1
  19. package/lib/index.js +1567 -482
  20. package/lib/types/chatter.d.ts +68 -46
  21. package/lib/types/chatter.d.ts.map +1 -1
  22. package/lib/types/chatter.js +243 -301
  23. package/lib/types/client/PetDockEntry.d.ts +3 -0
  24. package/lib/types/client/PetDockEntry.d.ts.map +1 -1
  25. package/lib/types/client/PetDockEntry.js +18 -2
  26. package/lib/types/client/PetSprite.d.ts +23 -0
  27. package/lib/types/client/PetSprite.d.ts.map +1 -1
  28. package/lib/types/client/PetSprite.js +49 -45
  29. package/lib/types/client/PluginSettingsCard.d.ts +2 -0
  30. package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
  31. package/lib/types/client/PluginSettingsCard.js +2 -2
  32. package/lib/types/client/drag-stream.d.ts +19 -0
  33. package/lib/types/client/drag-stream.d.ts.map +1 -0
  34. package/lib/types/client/drag-stream.js +26 -0
  35. package/lib/types/client/gameplay-hud.d.ts +50 -0
  36. package/lib/types/client/gameplay-hud.d.ts.map +1 -0
  37. package/lib/types/client/gameplay-hud.js +274 -0
  38. package/lib/types/client/index.d.ts.map +1 -1
  39. package/lib/types/client/index.js +38 -9
  40. package/lib/types/client/locales.d.ts +34 -0
  41. package/lib/types/client/locales.d.ts.map +1 -1
  42. package/lib/types/client/locales.js +36 -0
  43. package/lib/types/client/pet-store.d.ts +7 -1
  44. package/lib/types/client/pet-store.d.ts.map +1 -1
  45. package/lib/types/client/pet-store.js +4 -0
  46. package/lib/types/client/renderers/Frames2dVisualMount.d.ts +29 -0
  47. package/lib/types/client/renderers/Frames2dVisualMount.d.ts.map +1 -0
  48. package/lib/types/client/renderers/Frames2dVisualMount.js +83 -0
  49. package/lib/types/client/renderers/PetRendererSwitch.d.ts +6 -0
  50. package/lib/types/client/renderers/PetRendererSwitch.d.ts.map +1 -1
  51. package/lib/types/client/renderers/PetRendererSwitch.js +14 -1
  52. package/lib/types/client/renderers/frames2d.d.ts +36 -0
  53. package/lib/types/client/renderers/frames2d.d.ts.map +1 -0
  54. package/lib/types/client/renderers/frames2d.js +196 -0
  55. package/lib/types/defaults.d.ts +6 -0
  56. package/lib/types/defaults.d.ts.map +1 -0
  57. package/lib/types/defaults.js +5 -0
  58. package/lib/types/dsh-home.d.ts.map +1 -1
  59. package/lib/types/dsh-home.js +9 -3
  60. package/lib/types/event-projection.d.ts +9 -4
  61. package/lib/types/event-projection.d.ts.map +1 -1
  62. package/lib/types/event-projection.js +46 -14
  63. package/lib/types/gameplay.d.ts +182 -0
  64. package/lib/types/gameplay.d.ts.map +1 -0
  65. package/lib/types/gameplay.js +618 -0
  66. package/lib/types/ledger.d.ts +14 -0
  67. package/lib/types/ledger.d.ts.map +1 -1
  68. package/lib/types/ledger.js +30 -0
  69. package/lib/types/manifest-v2.d.ts +38 -1
  70. package/lib/types/manifest-v2.d.ts.map +1 -1
  71. package/lib/types/manifest-v2.js +182 -2
  72. package/lib/types/persist.d.ts +4 -4
  73. package/lib/types/persist.d.ts.map +1 -1
  74. package/lib/types/persist.js +41 -4
  75. package/lib/types/registry.d.ts +29 -0
  76. package/lib/types/registry.d.ts.map +1 -1
  77. package/lib/types/registry.js +170 -1
  78. package/lib/types/routes.d.ts.map +1 -1
  79. package/lib/types/routes.js +30 -3
  80. package/lib/types/service.d.ts +63 -11
  81. package/lib/types/service.d.ts.map +1 -1
  82. package/lib/types/service.js +208 -19
  83. package/lib/types/voice-pack.d.ts +6 -7
  84. package/lib/types/voice-pack.d.ts.map +1 -1
  85. package/lib/types/voice-pack.js +44 -47
  86. package/package.json +5 -2
  87. package/src/chatter.test.ts +68 -38
  88. package/src/chatter.ts +269 -316
  89. package/src/client/PetDockEntry.test.tsx +6 -0
  90. package/src/client/PetDockEntry.tsx +32 -1
  91. package/src/client/PetSprite.test.tsx +68 -21
  92. package/src/client/PetSprite.tsx +63 -45
  93. package/src/client/PluginSettingsCard.tsx +4 -2
  94. package/src/client/drag-stream.ts +35 -0
  95. package/src/client/gameplay-hud.test.tsx +338 -0
  96. package/src/client/gameplay-hud.tsx +403 -0
  97. package/src/client/index.test.tsx +10 -1
  98. package/src/client/index.ts +46 -11
  99. package/src/client/locales.ts +36 -0
  100. package/src/client/pet-store.ts +10 -1
  101. package/src/client/pet.module.css +192 -29
  102. package/src/client/renderers/Frames2dVisualMount.tsx +111 -0
  103. package/src/client/renderers/PetRendererSwitch.tsx +28 -1
  104. package/src/client/renderers/frames2d.test.ts +137 -0
  105. package/src/client/renderers/frames2d.ts +216 -0
  106. package/src/client/settings-card.module.css +5 -0
  107. package/src/defaults.ts +7 -0
  108. package/src/dsh-home.ts +9 -3
  109. package/src/event-projection.ts +57 -16
  110. package/src/gameplay.test.ts +239 -0
  111. package/src/gameplay.ts +692 -0
  112. package/src/ledger.test.ts +16 -0
  113. package/src/ledger.ts +31 -0
  114. package/src/manifest-v2.test.ts +130 -1
  115. package/src/manifest-v2.ts +181 -2
  116. package/src/persist.test.ts +3 -0
  117. package/src/persist.ts +43 -6
  118. package/src/registry.test.ts +168 -1
  119. package/src/registry.ts +199 -1
  120. package/src/routes.ts +28 -4
  121. package/src/service.ts +240 -29
  122. package/src/voice-pack.test.ts +48 -25
  123. package/src/voice-pack.ts +50 -47
package/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # node scripts/verify-docs.mjs --write <dir>
5
- README.md: ffeff4808b44845783cb85423a6bf8a1cacdcd84
6
- README.zh.md: 8cf46c60798a6711696bde433abcecae2dbe2b76
5
+ README.md: 2501ae7db35246c4348ed08fd169ee58a7569a95
6
+ README.zh.md: a490c37210773c3da3eec1d9c61807864f56b6aa
package/README.md CHANGED
@@ -23,8 +23,8 @@ Re-implemented from the pet feature of the Codex desktop app, as an official DSH
23
23
  | Dragging | Hold and drag the pet to reposition; position persisted |
24
24
  | Hide/Summon | The hover panel sits below the pet (lifted above the status bubbles when there is no room below) and provides 隐藏 (Hide); after hiding, a 召唤{name} (Summon {name}) button appears |
25
25
  | Witty remarks | Built-in remark library (10 lines per event) plus per-pet custom lines; success lines rotate by persisted success counts and cooldown lines by persisted rejection counts |
26
- | Status bubbles | Only the most recently active top-level session speaks by default — when several sessions run at once, the rest collapse behind a +N badge on the main bubble instead of stacking a tall column; hover the bubble (or tap the badge, for touch) to fan every session's bubble out above it and click one to jump to its session; subagent sessions report through their spawning conversation and never occupy a bubble of their own; transient interaction feedback temporarily takes priority. Bubble copy comes from generous rotating pools per scene (waiting / thinking / writing / done / failed...), tool calls map onto per-family witty lines carrying the real argument hint (e.g. 跑跑 npm test), and a long-lived scene re-phrases itself every few seconds |
27
- | Inner whispers | 碎碎念: while the model streams, the pet occasionally speaks its inner voice through its own bubble — a fresh whisper takes over the display session's bubble and marks it with 「」 quotes — sharing the same DeepSeek-blue glass as every status bubble, so stacked bubbles never clash — instead of stacking a second bubble — keyword moods woken by the model output (errors, test greens, plans, victories...) plus ambient whispers earned by output volume; paced by a cooldown, the status copy returns after a few seconds |
26
+ | Status bubbles | Only one top-level session speaks by default — the session the GUI is currently on when it is reported, otherwise the most recently active one — and the rest collapse behind a +N badge on the main bubble instead of stacking a tall column; hover the bubble (or tap the badge, for touch) to fan every session's bubble out above it and click one to jump to its session; subagent sessions report through their spawning conversation and never occupy a bubble of their own; transient interaction feedback temporarily takes priority. Bubble copy comes from generous rotating pools per scene (waiting / thinking / writing / done / failed...), tool calls map onto per-family witty lines carrying the real argument hint (e.g. 跑跑 npm test), and a long-lived scene re-phrases itself every few seconds |
27
+ | Inner whispers | 碎碎念: while a session streams, the pet occasionally speaks its inner voice through that session's own bubble — a fresh whisper takes over the bubble and marks it with 「」 quotes — sharing the same DeepSeek-blue glass as every status bubble, so stacked bubbles never clash — instead of stacking a second bubble — category lines woken by the SITUATION (thinking / writing / the running tool family) plus outcome lines woken only by structured results (test green from a passed test tool, error from a failed tool result, completion from a completed turn) never from the model's words, so a discussion that merely mentions a keyword cannot wake a mood, and a whisper never quotes real content (no tool names, paths or model text); paced by cooldowns (9 s category, 5 s outcome) and an 8 s display TTL, the status copy returns after a few seconds |
28
28
  | Multi-session activity | The pet is host-global: the most recent meaningful event drives the sprite animation while every active top-level session reports its own state in a separate bubble; completed turns from every session (subagents included) contribute affinity and treats |
29
29
  | Voice packs and panel DIY | A per-pet voice.json plus the global $DSH_HOME/pets/.voice.json override replace every bubble word and the hover panel (button labels, stat formats, button visibility); merge precedence per-pet > global > built-in, broken packs warn and never reject a pet |
30
30
 
@@ -65,9 +65,9 @@ A pet is a directory holding one `pet.json` manifest and one atlas image. Nothin
65
65
  A pet directory's `pet.json` declares its renderer explicitly in v2:
66
66
 
67
67
  - `petManifestVersion: 2` (absent = v1, compat-read as `sprite2d` with a migration hint);
68
- - `renderer`: `"sprite2d"` (the atlas contract above) or `"live2d"`;
68
+ - `renderer`: `"sprite2d"` (the atlas contract above), `"live2d"`, or `"frames2d"`;
69
69
  - `license` (required in v2): asset license identifier — community pets carry provenance;
70
- - a renderer block: `sprite2d` (spritesheetPath/cell/columns/atlasRows/frames/tracks) or `live2d` (model/motions/expressions/hitAreas/scale/translate).
70
+ - a renderer block: `sprite2d` (spritesheetPath/cell/columns/atlasRows/frames/tracks), `live2d` (model/motions/expressions/hitAreas/scale/translate), or `frames2d` (dir/defaultFrameMs/tracks/phases — directory-style frame sequences).
71
71
 
72
72
  Validation is fail-closed on structure (unknown fields or renderer kinds reject the entry with a diagnostic) and warn-and-drop on sequence/remark content. The machine-readable schema lives at `contracts/pet-manifest-v2.schema.json`; the authoritative validator is `src/manifest-v2.ts`. Migrate v1 manifests with `node scripts/dsh-pet-migrate-v2.mjs <dir> --write` (dry-run by default; keeps `pet.json.v1.bak`).
73
73
 
@@ -102,11 +102,14 @@ Every word in the thought bubble (status / tool / whisper copy) and the hover pa
102
102
  "shell": ["Running {hint}", "Hit enter: {hint}"]
103
103
  },
104
104
  "toolRemaining": ["{n} helpers still at work"], // {n} allowed
105
- "whispers": { // murmur pools; each section replaces the built-in one
106
- "generic": ["On it", "Almost there"], // ambient pool; an explicit empty array mutes it
107
- "rules": [ // ordered keyword rules; given rules replace the built-ins
108
- { "keywords": ["all tests pass"], "pool": ["All green!"] }
109
- ]
105
+ "whispers": { // murmur pools; keys replace the built-in pools
106
+ "categories": { // situation pools; an explicit empty array mutes that category
107
+ "thinking": ["Let me think..."],
108
+ "running": ["It's running now"]
109
+ },
110
+ "results": { // outcome pools (test green / error / completion)
111
+ "pass": ["All green!"]
112
+ }
110
113
  },
111
114
  "panel": { // hover panel; unset slots keep the plugin i18n copy
112
115
  "labels": { "feed": "Treat", "hide": "Dive", "rename": "Rename me", "confirm": "Sure" },
@@ -116,11 +119,11 @@ Every word in the thought bubble (status / tool / whisper copy) and the hover pa
116
119
  }
117
120
  ```
118
121
 
119
- - Merge precedence (per slot): the pet voice.json > the global .voice.json > built-in copy. status/tools merge per key, whispers replace per section, panel merges per slot; any slot a layer misses falls through.
122
+ - Merge precedence (per slot): the pet voice.json > the global .voice.json > built-in copy. status/tools/whispers merge per key, panel merges per slot; any slot a layer misses falls through.
120
123
  - Placeholder whitelist: tools accept {tool} / {hint}; toolRemaining accepts {n}; panel.stats accept {rank} / {n} / {points}; status, whisper and panel-label lines accept no placeholders (lines carrying one are dropped with a warning).
121
- - Caps (warn-and-drop): at most 64 lines per pool and 160 characters per line; at most 32 rules and 16 keywords (40 characters) per rule; panel labels 40 and stats 80 characters.
124
+ - Caps (warn-and-drop): at most 64 lines per pool and 160 characters per line; panel labels 40 and stats 80 characters.
122
125
  - A broken pack never breaks the pet: voice.json that is not valid JSON or whose root is not an object is ignored with a warning; every other issue drops its slot only. Diagnostics appear under Settings > Pet directory diagnostics. node scripts/dsh-pet validate <dir> fails installs on structure errors and lists content issues as warnings.
123
- - Semantics: an empty status/tools pool falls back to the built-in copy (a scene line always renders); an explicit empty whisper pool mutes that channel; an empty panel actions array hides all three buttons; uncovered buttons and stats keep the plugin bilingual dictionary.
126
+ - Semantics: an empty status/tools pool falls back to the built-in copy (a scene line always renders); an explicit empty whisper pool mutes that channel; an empty panel actions array hides all three buttons; uncovered buttons and stats keep the plugin bilingual dictionary. Legacy whispers.generic / whispers.rules fields are no longer supported and are ignored with a warning.
124
127
 
125
128
  ## Live2D pets (renderer: live2d)
126
129
 
@@ -157,6 +160,14 @@ A Live2D manifest maps the seven activity phases onto the model's motion groups:
157
160
 
158
161
  Model licensing: the official Live2D sample models (Hiyori, Haru, and friends) are evaluation-only and must not be redistributed — ship only models you have rights to (original creations or permissively licensed ones).
159
162
 
163
+ ## Frames2d pets and gameplay (renderer: frames2d)
164
+
165
+ Frames2d pets ship directory-style frame sequences instead of an atlas: `thumb/<track>/<frame>.webp`, with per-frame durations from an optional `_<ms>` filename tail or the track's `frameMs` list (default 200 ms, bounds 16–5000). The manifest maps the activity phases onto tracks; a `drag` track follows the chrome's drag gesture, and non-looping tracks settle into their `fallback` (default the idle track), so intro/loop splits (sleep-intro → sleep) are plain manifest data.
166
+
167
+ A frames2d pet may declare a `gameplay` block — an opt-in mini-game layer generalized from the miku desktop pet: decaying stat bars (`stats` with per-minute decay, a working variant and an idle variant), the unified treat (小鱼干) currency — gameplay income and shop spending ride the same panel treat stock (capped at 20): `work` success, `passiveIncome` and lottery prizes grant treats, and shop items are priced and paid in treats, with no separate wallet page — a weighted `idleDirector` (rolls an act every `intervalMs`, `maxMiss` forces one after consecutive idle rolls), `touch` zones inside a `hitBox` (roll branches with effects, a track hold and phrase bubbles), a `work` loop (host-adjudicated ticks with success/fail result tracks), a `sleep` loop (lazy stat restore), `passiveIncome`, and a `shop` whose items carry effects or tiered lotteries. All rolls and bookkeeping are host-authoritative (`POST /api/pet/gameplay/*`); state persists per pet in `pet.json` and settles lazily on the treats-economy discipline. The browser half renders the menu card (stat bars, work/sleep toggles, shop grid) automatically for any pet that declares the block.
168
+
169
+ The **Miku pet** (contributed by stushansusu under MIT; Hatsune Miku character rights belong to Crypton Future Media under the Piapro Character License — see THIRD_PARTY_NOTICES.md) is the reference frames2d gameplay pet. It ships through the **Workshop** only (not the npm bundle): install it from the Workshop's pet list and it lands in `$DSH_HOME/pets/miku/`.
170
+
160
171
  ## Status decorations (decoration.json, pet-center M5, #567)
161
172
 
162
173
  A status bubble can carry a small ornament ahead of its text (built-in: the spouting whale), driven by the ActivityPhase stream. Decorations are independent of pets: own descriptor, own id, own directory — switching pets never switches decorations. Entry assets are PNG/WebP single-row sprite strips only (no SVG/CSS); the bubble always keeps its role=status/aria-live (or session-bubble button semantics), the ornament is aria-hidden; prefers-reduced-motion holds the segment first frame, and a broken asset only removes the ornament — the text stays.
@@ -189,14 +200,17 @@ A status bubble can carry a small ornament ahead of its text (built-in: the spou
189
200
 
190
201
  | Registry id | Selector label | Source |
191
202
  |---|---|---|
203
+ | `ouo-neko` | OUO Neko | Pink-sakura cat-eared companion contributed by `Pessimist0906` under MIT |
192
204
  | `whale-girl` | 鲸鱼娘(原版) | The repository's original whale-girl atlas |
193
205
  | `whale-girl-refined` | 鲸鱼娘(精致版) | An AI-assisted derivative with repaired and refined details, based on the whale-girl design direction |
194
206
 
207
+ The Miku pet is deliberately not bundled: it is a frames2d gameplay pet installed on demand from the Workshop (see the frames2d section above).
208
+
195
209
  The refined variant references DreamSkin's “DeepSeek-Whale” theme. The historical source record identifies `powerdog996` as the original theme author and marks the theme as MIT: [DreamSkin](https://dreamskin.cc), [repository source record](https://github.com/zhu1090093659/dsh-web/commit/87edd7ff4800dffd40bc93fb76e4ae450390facd). This attribution records the source and derivative relationship; it does not present the refined variant as an official work of the original author or redefine the original artwork's licensing scope.
196
210
 
197
211
  ## Animation preview
198
212
 
199
- The sprites are an 8-column × 9-row atlas (192×208 cells) generated by the [hatch-pet](https://github.com/dsh2026) pipeline; below are previews of each state:
213
+ Sprite pets use 8-column atlases with 192×208 cells generated by the [hatch-pet](https://github.com/dsh2026) pipeline. Classic atlases carry 9 animation rows; v2 atlases add 2 rows for 16 look directions. Below are previews of the standard animation states:
200
214
 
201
215
  | idle | waiting | running | jumping |
202
216
  |---|---|---|---|
@@ -230,6 +244,7 @@ dsh-pet/
230
244
  | `-- pet.module.css
231
245
  |-- assets/whale/ # built-in original whale-girl (manifest + atlas + previews)
232
246
  |-- assets/whale-refined/ # built-in refined whale-girl registry variant
247
+ |-- assets/ouo-neko/ # built-in OUO Neko v2 pet (11-row atlas + previews)
233
248
  `-- cordis.patch.yml # bundle patch: inserts the pet plugin row
234
249
  ```
235
250
 
@@ -286,6 +301,8 @@ The browser bundle rides the `window.__ModuleLoader__.load` contract; React/cord
286
301
 
287
302
  The two built-in whale-girl atlases use the same 9-state × 8-column contract: `assets/whale/` is the original and `assets/whale-refined/` is the refined variant. Each atlas is 1536×1872 (8 columns × 9 rows of 192×208 cells). Frame counts, rhythm, and scene rotation live in each directory's `pet.json`; pets without overrides follow the hatch-pet contract rhythm and canonical single-track scene mapping (row order: 0 idle / 1 running-right / 2 running-left / 3 waving / 4 jumping / 5 failed / 6 waiting / 7 running / 8 review).
288
303
 
304
+ OUO Neko uses the extended v2 contract: a 1536×2288 atlas with the same 9 animation rows plus 2 eight-frame look-direction rows. Its manifest declares `sprite2d.atlasRows: 11`, and the renderer treats the last 16 cells as the clockwise look loop.
305
+
289
306
  ## Security model
290
307
 
291
308
  - Every `/api/pet/*` and `/pet/<id>/*` route is loopback-only by default (the shared plugin-family fence: loopback socket + Host header + browser same-origin markers): unpaired LAN clients get `403 forbidden: loopback-only` before any pet state or atlas is served. When `dsh-remote-web-ui` is also loaded, a live paired-device cookie is an additional allow path (the same cookie `api/gate` already checks); unpaired and revoked devices stay 403. The pet does not depend on the remote plugin.
package/README.zh.md CHANGED
@@ -23,8 +23,8 @@
23
23
  | 拖动 | 按住拖动宠物换位置;位置持久化 |
24
24
  | 隐藏/召唤 | 悬浮面板位于宠物下方(下方空间不足时上移到状态气泡之上)并提供 隐藏;隐藏后出现 召唤{name} 按钮 |
25
25
  | 妙语库 | 内置默认妙语库(每类事件 10 句)+ 宠物自定义台词;成功文案按持久化成功次数轮换,冷却文案按持久化拒绝次数轮换 |
26
- | 状态气泡 | 默认只有最近活动的顶层会话说话——多会话并行时,其余会话收进主气泡右上角的 +N 角标,不再叠出一长列;悬停气泡(触屏点按角标)即可向上展开所有会话的气泡,点击任一气泡跳转到对应会话;子代理会话借由其发起会话体现,不占用独立气泡;瞬时互动反馈临时优先。气泡文案按场景准备了大量轮换词库(等待 / 思考 / 整理 / 完成 / 失败……),工具调用按工具族映射俏皮文案并带上真实参数(如 跑跑 npm test),同一场景持续数秒会自动换一种说法 |
27
- | 碎碎念 | 模型流式输出期间,宠物会偶尔借自己的气泡说出内心独白——新鲜的碎碎念会接管展示会话的气泡并以「」引号标记——与状态气泡共用同一片 DeepSeek 蓝黑玻璃,气泡栈内颜色统一不再色差——不再叠出第二只气泡——由模型输出里的关键词触发对应心境(报错、测试全绿、做计划、打胜仗……),输出量累积也会换来日常碎碎念;有冷却节制,数秒后气泡恢复状态文案 |
26
+ | 状态气泡 | 默认只有一个顶层会话说话——上报 GUI 当前会话时优先它,否则是最近活动的一个——其余会话收进主气泡右上角的 +N 角标,不再叠出一长列;悬停气泡(触屏点按角标)即可向上展开所有会话的气泡,点击任一气泡跳转到对应会话;子代理会话借由其发起会话体现,不占用独立气泡;瞬时互动反馈临时优先。气泡文案按场景准备了大量轮换词库(等待 / 思考 / 整理 / 完成 / 失败……),工具调用按工具族映射俏皮文案并带上真实参数(如 跑跑 npm test),同一场景持续数秒会自动换一种说法 |
27
+ | 碎碎念 | 会话流式输出期间,宠物会偶尔借该会话自己的气泡说出内心独白——新鲜的碎碎念接管所属会话的气泡并以「」引号标记——与状态气泡共用同一片 DeepSeek 蓝黑玻璃,气泡栈内颜色统一不再色差——不再叠出第二只气泡——分类台词由「正在干什么」触发(思考 / 写回复 / 正在跑的工具族),结果台词只由结构化结果触发(测试工具通过 → 全绿、工具失败 → 报错、回合完成 → 收工)——决不读模型说的话,讨论里提到关键词也不会误触发心境;碎碎念也从不引用真实内容(无工具名、路径、模型原文);分类冷却 9 秒、结果冷却 5 秒,展示 8 秒后气泡恢复状态文案 |
28
28
  | 多会话活动 | 宠物是宿主全局的:最近一次有意义事件驱动精灵动画,同时每个活动的顶层会话用自己的气泡报告各自状态;每个会话(含子代理)完成的轮次都计入亲密度与小鱼干 |
29
29
  | 语音包与面板 DIY | 宠物目录 voice.json + 全局 $DSH_HOME/pets/.voice.json 覆盖气泡全部文案与悬浮面板(按钮标签/统计格式/按钮显隐);合并优先级 宠物自带 > 全局 > 内置,坏包警告不拒载 |
30
30
 
@@ -65,9 +65,9 @@
65
65
  宠物目录的 `pet.json` 在 v2 中显式声明渲染器:
66
66
 
67
67
  - `petManifestVersion: 2`(缺省 = v1,按 `sprite2d` 兼容读并给出迁移提示);
68
- - `renderer`:`"sprite2d"`(上文图集契约)或 `"live2d"`;
68
+ - `renderer`:`"sprite2d"`(上文图集契约)、`"live2d"` `"frames2d"`;
69
69
  - `license`(v2 必填):资产授权标识——社区宠物必须携带来源声明;
70
- - 渲染器专属块:`sprite2d`(spritesheetPath/cell/columns/atlasRows/frames/tracks)或 `live2d`(model/motions/expressions/hitAreas/scale/translate)。
70
+ - 渲染器专属块:`sprite2d`(spritesheetPath/cell/columns/atlasRows/frames/tracks)、`live2d`(model/motions/expressions/hitAreas/scale/translate)或 `frames2d`(dir/defaultFrameMs/tracks/phases——目录式帧序列)。
71
71
 
72
72
  校验纪律:结构 fail-closed(未知字段或未知渲染器直接拒载并给出诊断),sequences/remarks 内容维持 warn-and-drop。机器可读 schema 见 `contracts/pet-manifest-v2.schema.json`,权威校验器为 `src/manifest-v2.ts`。迁移 v1 清单:`node scripts/dsh-pet-migrate-v2.mjs <dir> --write`(默认 dry-run;保留 `pet.json.v1.bak`)。
73
73
 
@@ -102,11 +102,14 @@ node scripts/dsh-pet install <dir> --force # 覆盖同名已安装宠物
102
102
  "shell": ["跑跑 {hint}", "敲回车!{hint}"]
103
103
  },
104
104
  "toolRemaining": ["后台还有 {n} 位小助手"], // 允许 {n}
105
- "whispers": { // 碎碎念:按节替换(非逐条合并)
106
- "generic": ["冲了冲了", "稳"], // 环境池;显式空数组 = 静音
107
- "rules": [ // 关键词规则(有序;给出即整体替换内置规则)
108
- { "keywords": ["测试通过"], "pool": ["全绿!"] }
109
- ]
105
+ "whispers": { // 碎碎念:按键替换内置池
106
+ "categories": { // 分类池;显式空数组 = 静音该分类
107
+ "thinking": ["让我想想……"],
108
+ "running": ["正在跑,我盯着"]
109
+ },
110
+ "results": { // 结果池(测试全绿 / 报错 / 完成)
111
+ "pass": ["全绿!"]
112
+ }
110
113
  },
111
114
  "panel": { // 悬浮面板(每槽未声明时回落插件 i18n 文案)
112
115
  "labels": { "feed": "投喂", "hide": "藏起来", "rename": "起名字", "confirm": "好的" },
@@ -116,11 +119,11 @@ node scripts/dsh-pet install <dir> --force # 覆盖同名已安装宠物
116
119
  }
117
120
  ```
118
121
 
119
- - 合并优先级(逐槽):宠物自带 voice.json > 全局 .voice.json > 内置文案。status/tools 逐键合并、whispers 按节替换、panel 逐槽合并,任何层缺失的槽位回落下一层。
122
+ - 合并优先级(逐槽):宠物自带 voice.json > 全局 .voice.json > 内置文案。status/tools/whispers 逐键合并、panel 逐槽合并,任何层缺失的槽位回落下一层。
120
123
  - 占位符白名单:tools 允许 {tool} / {hint};toolRemaining 允许 {n};panel.stats 允许 {rank} / {n} / {points};status、碎碎念与面板标签不允许任何占位符(含非法占位符的行被警告丢弃)。
121
- - 上限(warn-and-drop):每池 64 行以内、每行 160 字符以内;规则 32 条以内、每规则关键词 16 条以内(40 字符以内);面板标签 40 以内、统计 80 字符以内。
124
+ - 上限(warn-and-drop):每池 64 行以内、每行 160 字符以内;面板标签 40 以内、统计 80 字符以内。
122
125
  - 坏包不影响宠物:voice.json 不是合法 JSON 或根不是对象 → 警告并整体忽略;其余问题逐槽警告丢弃。诊断显示在设置 → 宠物目录诊断。node scripts/dsh-pet validate <dir> 会把结构错误判为安装失败、内容问题列为警告。
123
- - 语义细节:status/tools 的空池回落内置文案(场景行始终有话说);whispers 的显式空数组是静音(关掉该通道);面板 actions 为空数组 = 三个按钮全部隐藏;未覆盖的按钮/统计继续使用插件双语字典。
126
+ - 语义细节:status/tools 的空池回落内置文案(场景行始终有话说);whispers 的显式空数组是静音(关掉该通道);面板 actions 为空数组 = 三个按钮全部隐藏;未覆盖的按钮/统计继续使用插件双语字典。旧的 whispers.generic / whispers.rules 字段已不再支持,会被忽略并给出警告。
124
127
 
125
128
  ## Live2D 宠物(renderer: live2d)
126
129
 
@@ -157,6 +160,14 @@ Live2D 清单把七个活动相位映射到模型的动作组:
157
160
 
158
161
  模型授权:Live2D 官方示例模型(Hiyori、Haru 等)仅供评估、禁止再分发——只发布你有权的模型(原创作品或宽松授权的模型)。
159
162
 
163
+ ## Frames2d 宠物与玩法(renderer: frames2d)
164
+
165
+ frames2d 宠物不用图集,直接交付目录式帧序列:`thumb/<track>/<frame>.webp`,帧时长取文件名 `_<ms>` 尾缀或轨道的 `frameMs` 列表(默认 200ms,范围 16–5000)。清单把七个活动相位映射到轨道;`drag` 轨道跟随外壳的拖拽手势;非循环轨道播完进入 `fallback`(默认 idle 轨道),入睡/睡熟这类"引子 + 循环"拆分(sleep-intro → sleep)就是纯清单数据。
166
+
167
+ frames2d 宠物可声明 `gameplay` 块——从 miku 桌宠泛化而来的可选玩法层:属性条(`stats`,按分钟衰减,另有打工中与空闲变体)、统一的小鱼干货币(玩法收入与商店支出都走宠物面板那条小鱼干库存,上限 20 条:`work` 成功、`passiveIncome` 与彩票奖品发放小鱼干,商品也以小鱼干标价扣款,无独立钱包页)、加权 `idleDirector`(每 `intervalMs` 掷骰演出小动作,连续落空 `maxMiss` 次必演)、`hitBox` 内的 `touch` 触摸分区(分支掷骰:效果 + 轨道保持 + 台词气泡)、`work` 打工循环(宿主裁决 tick,成功/失败结果轨道)、`sleep` 睡觉循环(惰性恢复)、`passiveIncome` 被动收入,以及 `shop` 商店(商品可带效果或分档抽奖)。所有掷骰与记账由宿主权威裁决(`POST /api/pet/gameplay/*`),状态按宠物持久化在 `pet.json`,沿用小鱼干经济的惰性结算纪律。浏览器半侧为声明了该块的宠物自动渲染玩法菜单卡(属性条、打工/睡觉开关、商店网格)。
168
+
169
+ **Miku 宠物**(stushansusu 涂山苏苏以 MIT 贡献;初音未来角色权利归 Crypton Future Media,受 Piapro 角色许可约束——见 THIRD_PARTY_NOTICES.md)是 frames2d 玩法的参考实现。它只经**创意工坊**分发(不打进 npm 包):从工坊宠物列表安装后落在 `$DSH_HOME/pets/miku/`。
170
+
160
171
  ## 状态装饰(decoration.json,宠物中心 M5,#567)
161
172
 
162
173
  宠物状态气泡里的文字之前可以有一个小的状态装饰(内置:喷水鲸鱼),由 ActivityPhase 流驱动换帧。装饰与宠物相互独立:独立描述符、独立 id、独立目录,换宠物不换装饰。入口资产只收 PNG/WebP 单行精灵条带(不收 SVG/CSS);气泡自身的 role=status/aria-live(或会话气泡按钮语义)永远保留,装饰 aria-hidden;prefers-reduced-motion 时停在帧段首帧,资产加载失败只消失装饰、文字照常。
@@ -189,14 +200,17 @@ Live2D 清单把七个活动相位映射到模型的动作组:
189
200
 
190
201
  | 注册表 id | 选择器名称 | 来源 |
191
202
  |---|---|---|
203
+ | `ouo-neko` | OUO Neko | `Pessimist0906` 以 MIT 许可证贡献的粉色樱花猫耳伙伴 |
192
204
  | `whale-girl` | 鲸鱼娘(原版) | 仓库原有的鲸鱼娘图集 |
193
205
  | `whale-girl-refined` | 鲸鱼娘(精致版) | 以鲸鱼娘设计方向为基础,经 AI 辅助二次创作、修复和细节精修的衍生版本 |
194
206
 
207
+ Miku 宠物有意不随包内置:它是 frames2d 玩法宠物,经创意工坊按需安装(见上文 frames2d 一节)。
208
+
195
209
  精致版参考了 DreamSkin 的「DeepSeek-鲸鱼娘」主题。历史来源记录标注原主题作者为 `powerdog996`,并标注主题为 MIT:[DreamSkin](https://dreamskin.cc)、[仓库来源记录](https://github.com/zhu1090093659/dsh-web/commit/87edd7ff4800dffd40bc93fb76e4ae450390facd)。此处用于记录素材来源与衍生关系;精致版不表述为原作者的官方作品,也不重新定义原始美术作品的授权范围。
196
210
 
197
211
  ## 动画预览
198
212
 
199
- 精灵图是由 [hatch-pet](https://github.com/dsh2026) 流水线生成的 8 × 9 行图集(192×208 单元格);各状态预览:
213
+ 精灵宠物使用由 [hatch-pet](https://github.com/dsh2026) 流水线生成的 8 列图集,单元格为 192×208。经典图集包含 9 行动画;v2 图集额外增加 2 行,共提供 16 个观察方向。以下为标准动画状态预览:
200
214
 
201
215
  | idle | waiting | running | jumping |
202
216
  |---|---|---|---|
@@ -230,6 +244,7 @@ dsh-pet/
230
244
  | `-- pet.module.css
231
245
  |-- assets/whale/ # 内置原版鲸鱼娘(manifest + 图集 + 预览)
232
246
  |-- assets/whale-refined/ # 内置精致版鲸鱼娘注册表变体
247
+ |-- assets/ouo-neko/ # 内置 OUO Neko v2 宠物(11 行图集 + 预览)
233
248
  `-- cordis.patch.yml # bundle 补丁:插入宠物插件行
234
249
  ```
235
250
 
@@ -286,6 +301,8 @@ pnpm typecheck # 仅类型检查
286
301
 
287
302
  两套内置鲸鱼娘图集使用同一份 9 态 × 8 列契约:`assets/whale/` 是原版,`assets/whale-refined/` 是精致版。每张图集均为 1536×1872(8 列 × 9 行,192×208 单元格)。每行帧数、节奏与场景轮换写在各目录的 `pet.json` 中;未覆盖的宠物沿用 hatch-pet 契约节奏和标准单轨场景映射(行序:0 idle / 1 running-right / 2 running-left / 3 waving / 4 jumping / 5 failed / 6 waiting / 7 running / 8 review)。
288
303
 
304
+ OUO Neko 使用扩展 v2 契约:图集为 1536×2288,在相同 9 行动画之后增加 2 行、共 16 帧观察方向。其清单声明 `sprite2d.atlasRows: 11`,渲染器把最后 16 个单元格作为顺时针观察方向循环。
305
+
289
306
  ## 安全模型
290
307
 
291
308
  - 全部 `/api/pet/*` 与 `/pet/<id>/*` 路由默认仅限 loopback(插件家族共享围栏:loopback 套接字 + Host 头 + 浏览器同源标记):未配对的局域网客户端在任何宠物状态或图集下发前即收到 `403 forbidden: loopback-only`。同时装了 `dsh-remote-web-ui` 时,有效的已配对设备 cookie 是额外放行路径(与 `api/gate` 检查同一枚 cookie);未配对与已撤销设备仍 403。宠物插件不硬依赖远程插件。
@@ -0,0 +1,31 @@
1
+ {
2
+ "$schema": "https://schemas.linxin666.org/dsh-pet/pet-manifest-v2.schema.json",
3
+ "petManifestVersion": 2,
4
+ "id": "ouo-neko",
5
+ "displayName": "OUO Neko",
6
+ "author": "Pessimist0906",
7
+ "version": "1.0.0",
8
+ "description": "A warm pink-sakura companion with cat ears and a lively chibi personality.",
9
+ "license": "MIT",
10
+ "renderer": "sprite2d",
11
+ "sprite2d": {
12
+ "spritesheetPath": "spritesheet.webp",
13
+ "cell": {
14
+ "width": 192,
15
+ "height": 208
16
+ },
17
+ "columns": 8,
18
+ "atlasRows": 11,
19
+ "frames": [
20
+ 6,
21
+ 8,
22
+ 8,
23
+ 4,
24
+ 5,
25
+ 8,
26
+ 6,
27
+ 6,
28
+ 6
29
+ ]
30
+ }
31
+ }
Binary file
@@ -55,7 +55,8 @@
55
55
  "renderer": {
56
56
  "enum": [
57
57
  "sprite2d",
58
- "live2d"
58
+ "live2d",
59
+ "frames2d"
59
60
  ],
60
61
  "default": "sprite2d",
61
62
  "description": "Renderer kind; omitted means sprite2d (v1 compatibility). Unknown values are rejected."
@@ -239,6 +240,88 @@
239
240
  }
240
241
  }
241
242
  },
243
+ "frames2d": {
244
+ "type": "object",
245
+ "additionalProperties": false,
246
+ "required": [
247
+ "tracks",
248
+ "phases"
249
+ ],
250
+ "description": "Frame-sequence renderer block; required when renderer=frames2d. Free-form named tracks laid out as <dir>/<track>/<frame>.(webp|png|gif|jpg|jpeg); phase-direct state selection (the live2d motions precedent). Frame durations resolve frameMs[i] > filename-encoded <base>_<index>_<ms> suffix > defaultFrameMs.",
251
+ "properties": {
252
+ "dir": {
253
+ "type": "string",
254
+ "minLength": 1,
255
+ "default": ".",
256
+ "description": "Frame root directory relative to the manifest directory (safe segments only)."
257
+ },
258
+ "defaultFrameMs": {
259
+ "type": "integer",
260
+ "minimum": 16,
261
+ "maximum": 5000,
262
+ "default": 200
263
+ },
264
+ "tracks": {
265
+ "type": "object",
266
+ "minProperties": 1,
267
+ "maxProperties": 64,
268
+ "description": "Named tracks (lowercase kebab ids, at most 32 chars).",
269
+ "additionalProperties": {
270
+ "type": "object",
271
+ "additionalProperties": false,
272
+ "properties": {
273
+ "frames": {
274
+ "type": "array",
275
+ "minItems": 1,
276
+ "maxItems": 64,
277
+ "items": {
278
+ "type": "string",
279
+ "minLength": 1,
280
+ "description": "Frame file name inside the track directory (no path segments)."
281
+ },
282
+ "description": "Explicit ordered frame list; omitted means list the track directory in filename order."
283
+ },
284
+ "frameMs": {
285
+ "type": "array",
286
+ "minItems": 1,
287
+ "items": {
288
+ "type": "integer",
289
+ "minimum": 16,
290
+ "maximum": 5000
291
+ },
292
+ "description": "Per-frame durations; requires an explicit frames list of the same length."
293
+ },
294
+ "loop": {
295
+ "type": "boolean",
296
+ "default": true,
297
+ "description": "false stops on the last frame, then enters fallback."
298
+ },
299
+ "fallback": {
300
+ "type": "string",
301
+ "description": "Track entered when a non-loop track ends; defaults to the idle-mapped track."
302
+ }
303
+ }
304
+ }
305
+ },
306
+ "phases": {
307
+ "type": "object",
308
+ "additionalProperties": false,
309
+ "required": [
310
+ "idle"
311
+ ],
312
+ "description": "ActivityPhase -> track name; idle is required, unmapped phases fall back to it.",
313
+ "properties": {
314
+ "idle": { "type": "string", "minLength": 1 },
315
+ "waiting": { "type": "string", "minLength": 1 },
316
+ "thinking": { "type": "string", "minLength": 1 },
317
+ "tool": { "type": "string", "minLength": 1 },
318
+ "review": { "type": "string", "minLength": 1 },
319
+ "done": { "type": "string", "minLength": 1 },
320
+ "failed": { "type": "string", "minLength": 1 }
321
+ }
322
+ }
323
+ }
324
+ },
242
325
  "sequences": {
243
326
  "type": "object",
244
327
  "description": "Renderer-agnostic ActivityPhase sequence table (v1 structure; every declared sequence has at least 5 items)."
@@ -246,6 +329,10 @@
246
329
  "remarks": {
247
330
  "type": "object",
248
331
  "description": "Per-slot witty-remark pools (v1 structure; a slot replaces the built-in pool for that slot only)."
332
+ },
333
+ "gameplay": {
334
+ "type": "object",
335
+ "description": "Optional gameplay layer (frames2d pets only): stat decay, currencies, touch zones, work/sleep loops, passive income and a shop with optional lotteries. State references name frames2d tracks; structure is fail-closed in the validator."
249
336
  }
250
337
  },
251
338
  "allOf": [
@@ -276,6 +363,20 @@
276
363
  "live2d"
277
364
  ]
278
365
  }
366
+ },
367
+ {
368
+ "if": {
369
+ "properties": {
370
+ "renderer": {
371
+ "const": "frames2d"
372
+ }
373
+ }
374
+ },
375
+ "then": {
376
+ "required": [
377
+ "frames2d"
378
+ ]
379
+ }
279
380
  }
280
381
  ]
281
382
  }
@@ -118,36 +118,31 @@
118
118
  "whispers": {
119
119
  "type": "object",
120
120
  "additionalProperties": false,
121
- "description": "Murmur pools; each section replaces the built-in one as a whole. An explicit empty array mutes that channel.",
121
+ "description": "Murmur pools: category pools keyed by the ten WhisperCategory ids plus outcome pools keyed by pass / fail / done. A key replaces that key's built-in pool; an explicit empty array mutes that channel. Legacy generic / rules fields are ignored with a warning.",
122
122
  "properties": {
123
- "generic": {
124
- "$ref": "#/definitions/pool"
123
+ "categories": {
124
+ "type": "object",
125
+ "additionalProperties": false,
126
+ "properties": {
127
+ "thinking": { "$ref": "#/definitions/pool" },
128
+ "writing": { "$ref": "#/definitions/pool" },
129
+ "reading": { "$ref": "#/definitions/pool" },
130
+ "editing": { "$ref": "#/definitions/pool" },
131
+ "running": { "$ref": "#/definitions/pool" },
132
+ "searching": { "$ref": "#/definitions/pool" },
133
+ "git": { "$ref": "#/definitions/pool" },
134
+ "delegating": { "$ref": "#/definitions/pool" },
135
+ "browsing": { "$ref": "#/definitions/pool" },
136
+ "generic": { "$ref": "#/definitions/pool" }
137
+ }
125
138
  },
126
- "rules": {
127
- "type": "array",
128
- "maxItems": 32,
129
- "items": {
130
- "type": "object",
131
- "additionalProperties": false,
132
- "required": [
133
- "keywords",
134
- "pool"
135
- ],
136
- "properties": {
137
- "keywords": {
138
- "type": "array",
139
- "maxItems": 16,
140
- "items": {
141
- "type": "string",
142
- "maxLength": 40,
143
- "minLength": 1
144
- },
145
- "description": "Lowercase substrings matched against the model's output."
146
- },
147
- "pool": {
148
- "$ref": "#/definitions/pool"
149
- }
150
- }
139
+ "results": {
140
+ "type": "object",
141
+ "additionalProperties": false,
142
+ "properties": {
143
+ "pass": { "$ref": "#/definitions/pool" },
144
+ "fail": { "$ref": "#/definitions/pool" },
145
+ "done": { "$ref": "#/definitions/pool" }
151
146
  }
152
147
  }
153
148
  }