@linxin666/dsh-pet 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +2 -2
- package/README.md +129 -3
- package/README.zh.md +129 -3
- package/assets/decorations/whale/decoration.json +20 -0
- package/assets/decorations/whale/whale-frames.png +0 -0
- package/assets/whale/pet.json +169 -20
- package/assets/whale-refined/pet.json +17 -2
- package/contracts/pet-manifest-v2.schema.json +281 -0
- package/contracts/status-decoration-v1.schema.json +144 -0
- package/contracts/voice-pack-v1.schema.json +234 -0
- package/lib/client.js +581 -38
- package/lib/client.js.map +1 -1
- package/lib/index.js +1684 -59
- package/lib/live2d-vendor.js +995 -0
- package/lib/live2d-vendor.js.map +1 -0
- package/lib/types/chatter.d.ts +61 -3
- package/lib/types/chatter.d.ts.map +1 -1
- package/lib/types/chatter.js +71 -12
- package/lib/types/client/PetDockEntry.d.ts.map +1 -1
- package/lib/types/client/PetDockEntry.js +2 -1
- package/lib/types/client/PetSettingsCard.d.ts +14 -0
- package/lib/types/client/PetSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PetSettingsCard.js +24 -1
- package/lib/types/client/PetSprite.d.ts +7 -1
- package/lib/types/client/PetSprite.d.ts.map +1 -1
- package/lib/types/client/PetSprite.js +120 -12
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +5 -0
- package/lib/types/client/locales.d.ts +14 -0
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +14 -0
- package/lib/types/client/phase-stream.d.ts +22 -0
- package/lib/types/client/phase-stream.d.ts.map +1 -0
- package/lib/types/client/phase-stream.js +28 -0
- package/lib/types/client/renderers/PetRendererSwitch.d.ts +25 -0
- package/lib/types/client/renderers/PetRendererSwitch.d.ts.map +1 -0
- package/lib/types/client/renderers/PetRendererSwitch.js +24 -0
- package/lib/types/client/renderers/live2d/Live2dVisualMount.d.ts +22 -0
- package/lib/types/client/renderers/live2d/Live2dVisualMount.d.ts.map +1 -0
- package/lib/types/client/renderers/live2d/Live2dVisualMount.js +75 -0
- package/lib/types/client/renderers/live2d/runtime.d.ts +89 -0
- package/lib/types/client/renderers/live2d/runtime.d.ts.map +1 -0
- package/lib/types/client/renderers/live2d/runtime.js +63 -0
- package/lib/types/client/renderers/live2d/vendor-entry.d.ts +15 -0
- package/lib/types/client/renderers/live2d/vendor-entry.d.ts.map +1 -0
- package/lib/types/client/renderers/live2d/vendor-entry.js +14 -0
- package/lib/types/client/renderers/live2d.d.ts +52 -0
- package/lib/types/client/renderers/live2d.d.ts.map +1 -0
- package/lib/types/client/renderers/live2d.js +192 -0
- package/lib/types/client/renderers/registry.d.ts +32 -0
- package/lib/types/client/renderers/registry.d.ts.map +1 -0
- package/lib/types/client/renderers/registry.js +49 -0
- package/lib/types/client/settings-form.d.ts.map +1 -1
- package/lib/types/client/settings-form.js +9 -6
- package/lib/types/contracts/renderer.d.ts +50 -0
- package/lib/types/contracts/renderer.d.ts.map +1 -0
- package/lib/types/contracts/renderer.js +15 -0
- package/lib/types/contracts/status-decoration.d.ts +85 -0
- package/lib/types/contracts/status-decoration.d.ts.map +1 -0
- package/lib/types/contracts/status-decoration.js +21 -0
- package/lib/types/decoration.d.ts +39 -0
- package/lib/types/decoration.d.ts.map +1 -0
- package/lib/types/decoration.js +210 -0
- package/lib/types/event-projection.d.ts +8 -3
- package/lib/types/event-projection.d.ts.map +1 -1
- package/lib/types/event-projection.js +9 -4
- package/lib/types/index.d.ts +2 -0
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +2 -0
- package/lib/types/manifest-v2.d.ts +129 -0
- package/lib/types/manifest-v2.d.ts.map +1 -0
- package/lib/types/manifest-v2.js +387 -0
- package/lib/types/model3.d.ts +26 -0
- package/lib/types/model3.d.ts.map +1 -0
- package/lib/types/model3.js +96 -0
- package/lib/types/registry.d.ts +112 -5
- package/lib/types/registry.d.ts.map +1 -1
- package/lib/types/registry.js +395 -38
- package/lib/types/routes.d.ts +39 -2
- package/lib/types/routes.d.ts.map +1 -1
- package/lib/types/routes.js +312 -13
- package/lib/types/service.d.ts +38 -1
- package/lib/types/service.d.ts.map +1 -1
- package/lib/types/service.js +46 -3
- package/lib/types/voice-pack.d.ts +98 -0
- package/lib/types/voice-pack.d.ts.map +1 -0
- package/lib/types/voice-pack.js +384 -0
- package/package.json +13 -10
- package/src/chatter.test.ts +89 -2
- package/src/chatter.ts +120 -14
- package/src/client/PetDockEntry.tsx +19 -11
- package/src/client/PetSettingsCard.tsx +57 -0
- package/src/client/PetSprite.test.tsx +265 -12
- package/src/client/PetSprite.tsx +165 -33
- package/src/client/index.ts +6 -0
- package/src/client/locales.ts +14 -0
- package/src/client/phase-stream.test.ts +76 -0
- package/src/client/phase-stream.ts +39 -0
- package/src/client/renderers/PetRendererSwitch.test.tsx +66 -0
- package/src/client/renderers/PetRendererSwitch.tsx +48 -0
- package/src/client/renderers/live2d/Live2dVisualMount.tsx +98 -0
- package/src/client/renderers/live2d/runtime.test.ts +61 -0
- package/src/client/renderers/live2d/runtime.ts +119 -0
- package/src/client/renderers/live2d/vendor-entry.ts +14 -0
- package/src/client/renderers/live2d.test.ts +263 -0
- package/src/client/renderers/live2d.ts +231 -0
- package/src/client/renderers/registry.ts +58 -0
- package/src/client/settings-form.ts +8 -6
- package/src/client/settings-section.module.css +23 -0
- package/src/contracts/renderer.ts +54 -0
- package/src/contracts/status-decoration.ts +78 -0
- package/src/decoration.test.ts +178 -0
- package/src/decoration.ts +220 -0
- package/src/event-projection.ts +10 -5
- package/src/index.ts +2 -0
- package/src/manifest-v2.test.ts +251 -0
- package/src/manifest-v2.ts +414 -0
- package/src/model3.test.ts +79 -0
- package/src/model3.ts +91 -0
- package/src/registry.test.ts +438 -2
- package/src/registry.ts +470 -38
- package/src/routes.ts +328 -14
- package/src/service.ts +65 -3
- package/src/voice-pack.test.ts +216 -0
- package/src/voice-pack.ts +413 -0
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:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: a0adc3fe2cb2767a8e372ed32cec6e8f7bb74660
|
|
6
|
+
README.zh.md: e048ab6aece36283648b95ceddf705b3c6783f7e
|
package/README.md
CHANGED
|
@@ -26,6 +26,7 @@ Re-implemented from the pet feature of the Codex desktop app, as an official DSH
|
|
|
26
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
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 |
|
|
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
|
+
| 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 |
|
|
29
30
|
|
|
30
31
|
## Pet contract
|
|
31
32
|
|
|
@@ -59,14 +60,131 @@ A pet is a directory holding one `pet.json` manifest and one atlas image. Nothin
|
|
|
59
60
|
- `frames` counts the used columns per row (defaults to the hatch-pet contract table `[6, 8, 8, 4, 5, 8, 6, 6, 6]`); `tracks` overrides per-frame durations (cycled to the row's frame count), `loop`, and `fallback` per animation (defaults: everything loops; `jumping` and `failed` hold their last frame, then fall back to `idle`).
|
|
60
61
|
- `sequences` optionally maps activity scenes (`idle` / `waiting` / `thinking` / `tool` / `review` / `done` / `failed`) to at least 5 animation tracks. Each item plays every frame for the durations in `tracks`, then the next item starts; the complete sequence loops. An omitted scene keeps its canonical single-track playback.
|
|
61
62
|
|
|
63
|
+
### Manifest v2 (pet center, #623)
|
|
64
|
+
|
|
65
|
+
A pet directory's `pet.json` declares its renderer explicitly in v2:
|
|
66
|
+
|
|
67
|
+
- `petManifestVersion: 2` (absent = v1, compat-read as `sprite2d` with a migration hint);
|
|
68
|
+
- `renderer`: `"sprite2d"` (the atlas contract above) or `"live2d"`;
|
|
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).
|
|
71
|
+
|
|
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
|
+
|
|
62
74
|
Where pets come from (later sources override earlier ones on id collision):
|
|
63
75
|
|
|
64
76
|
1. **Built-in**: `assets/<dir>/pet.json` in this package.
|
|
65
|
-
2. **
|
|
66
|
-
3. **
|
|
77
|
+
2. **Legacy custom pets**: `${CODEX_HOME:-~/.codex}/pets/<pet>/pet.json` — the hatch-pet pipeline stages its output there, so a hatched pet appears in the selector with no further wiring.
|
|
78
|
+
3. **Pet-center user directory**: `$DSH_HOME/pets/<id>/` — the recommended home for your pets (see the CLI below).
|
|
79
|
+
4. **Composed**: `PetConfig.pets` manifest entries passed to the plugin by the embedding application.
|
|
80
|
+
|
|
81
|
+
Validate and install a pet directory with the CLI (no build step, no npm publish):
|
|
82
|
+
|
|
83
|
+
```sh
|
|
84
|
+
node scripts/dsh-pet validate <dir> # manifest + assets + Live2D reference closure + voice.json
|
|
85
|
+
node scripts/dsh-pet install <dir> # validate, then copy into $DSH_HOME/pets/<id>/
|
|
86
|
+
node scripts/dsh-pet install <dir> --force # overwrite an existing same-id install
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Invalid entries never override a working pet: they are skipped with a diagnostic listed in the settings (Pet section). The registry is built once at host startup; add or change a pet, then restart `dsh web`.
|
|
90
|
+
|
|
91
|
+
## Voice packs and panel chrome (voice.json, pet-center M4, #677)
|
|
92
|
+
|
|
93
|
+
Every word in the thought bubble (status / tool / whisper copy) and the hover panel (button labels, stat formats, button visibility) can be replaced by a pet — or by you — without touching plugin code. Pets ship an optional voice.json in their directory; a global override at $DSH_HOME/pets/.voice.json re-voices pets without editing their directories.
|
|
94
|
+
|
|
95
|
+
```jsonc
|
|
96
|
+
{
|
|
97
|
+
"voicePackVersion": 1, // optional; absent reads as v1
|
|
98
|
+
"status": { // status pools keyed by scene id; per-key override
|
|
99
|
+
"done": ["Done for today!", "Another one down"]
|
|
100
|
+
},
|
|
101
|
+
"tools": { // tool pools keyed by tool family; {tool} / {hint} allowed
|
|
102
|
+
"shell": ["Running {hint}", "Hit enter: {hint}"]
|
|
103
|
+
},
|
|
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
|
+
]
|
|
110
|
+
},
|
|
111
|
+
"panel": { // hover panel; unset slots keep the plugin i18n copy
|
|
112
|
+
"labels": { "feed": "Treat", "hide": "Dive", "rename": "Rename me", "confirm": "Sure" },
|
|
113
|
+
"stats": { "rank": "Affinity {rank}", "treats": "Treats x{n}", "points": "{points} pts" },
|
|
114
|
+
"actions": ["feed", "rename", "hide"] // subset in canonical order; absent = all; [] = stats only
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
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.
|
|
120
|
+
- 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.
|
|
122
|
+
- 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.
|
|
67
124
|
|
|
68
|
-
|
|
125
|
+
## Live2D pets (renderer: live2d)
|
|
69
126
|
|
|
127
|
+
Live2D pets render through PixiJS/WebGL: the MIT pixi.js + untitled-pixi-live2d-engine stack ships inside this plugin as a lazily loaded vendor bundle, so sprite-only installations never download or parse it. **The Cubism Core runtime is never bundled or downloaded by this plugin** — Live2D's proprietary license forbids redistributing it. To enable a Live2D pet:
|
|
128
|
+
|
|
129
|
+
1. Obtain the official Live2D Cubism SDK for Web yourself (you accept Live2D's license) and take `live2dcubismcore.min.js` from it.
|
|
130
|
+
2. Place it at `$DSH_HOME/pets/.runtime/live2dcubismcore.min.js` — the plugin serves it to the page from there, alongside its own vendor bundle.
|
|
131
|
+
3. Install a Live2D pet (a directory with `pet.json` v2, `renderer: "live2d"`, and the model files).
|
|
132
|
+
|
|
133
|
+
If the core is absent, a Live2D pet shows an install-guidance card where the model would render; sprite2d pets are unaffected. Legal note: this plugin is an "extensible application" in Live2D's terms — works you publish with user-loadable models may require a Live2D release license regardless of scale; evaluate your obligations before publishing derivative works.
|
|
134
|
+
|
|
135
|
+
A Live2D manifest maps the seven activity phases onto the model's motion groups:
|
|
136
|
+
|
|
137
|
+
```json
|
|
138
|
+
{
|
|
139
|
+
"petManifestVersion": 2,
|
|
140
|
+
"id": "my-live2d-pet",
|
|
141
|
+
"displayName": "My Live2D Pet",
|
|
142
|
+
"license": "CC0-1.0",
|
|
143
|
+
"renderer": "live2d",
|
|
144
|
+
"live2d": {
|
|
145
|
+
"model": "model/my-pet.model3.json",
|
|
146
|
+
"motions": { "idle": "Idle", "thinking": "Think", "failed": "TapBody" },
|
|
147
|
+
"hitAreas": ["Body"]
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
- `model`: the `.model3.json` path relative to the pet directory. Every file the model references (moc, textures, motions, physics, pose, expressions) must live inside the directory — the host serves exactly that reference closure.
|
|
153
|
+
- `motions` (required, `idle` mandatory): phase → motion group. Unmapped phases and groups the model lacks fall back to `idle`; a group holding several motions plays a random one. The official Cubism sample models ship only `Idle` and `TapBody` groups.
|
|
154
|
+
- `expressions` (optional): phase → expression name, layered over the motion.
|
|
155
|
+
- `hitAreas` (optional): a tap landing on a listed hit area plays the model's `TapBody` group, then returns to the phase's group. Every tap still counts as petting — the chrome owns interactions exactly like sprite2d.
|
|
156
|
+
- `scale` / `translate` (optional): the model auto-fits the display box; `scale` multiplies the fit (default 1, range (0, 10]) and `translate` offsets it in px from the center.
|
|
157
|
+
|
|
158
|
+
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
|
+
|
|
160
|
+
## Status decorations (decoration.json, pet-center M5, #567)
|
|
161
|
+
|
|
162
|
+
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.
|
|
163
|
+
|
|
164
|
+
```jsonc
|
|
165
|
+
{
|
|
166
|
+
"decorationManifestVersion": 1,
|
|
167
|
+
"id": "whale", // unique lowercase kebab id
|
|
168
|
+
"displayName": "Spouting whale", // optional
|
|
169
|
+
"license": "MIT", // required: asset provenance
|
|
170
|
+
"entry": "whale-frames.png", // PNG/WebP strip, relative to this directory
|
|
171
|
+
"cell": { "width": 64, "height": 48 },
|
|
172
|
+
"columns": 4, // strip frames (1..16)
|
|
173
|
+
"frameMs": 160, // constant frame duration; or "durations": [..] per frame
|
|
174
|
+
"loop": true,
|
|
175
|
+
"phases": { // ActivityPhase -> inclusive frame segment; "hide" = none; default hide
|
|
176
|
+
"idle": "hide",
|
|
177
|
+
"waiting": { "from": 0, "to": 1 },
|
|
178
|
+
"thinking": { "from": 0, "to": 3 },
|
|
179
|
+
"done": { "from": 2, "to": 3 },
|
|
180
|
+
"failed": { "from": 3, "to": 3 }
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
- Structure is fail-closed (unknown fields, out-of-range geometry, non-PNG/WebP entries reject with diagnostics); segment content is warn-and-drop. The machine-readable twin lives at contracts/status-decoration-v1.schema.json; the authoritative validator is src/decoration.ts.
|
|
186
|
+
- Sources: built-in assets/decorations/ plus the user directory $DSH_HOME/pets/decorations/<id>/ (same id overrides the built-in). Assets ride /api/pet/decoration/<id>/<file> with the same containment and allow-lists as pet assets.
|
|
187
|
+
- Switch: Settings > Pet > Status decoration (on by default). The built-in whale derives from the DeepSeek wordmark (MIT); see THIRD_PARTY_NOTICES.md.
|
|
70
188
|
## Built-in pets
|
|
71
189
|
|
|
72
190
|
| Registry id | Selector label | Source |
|
|
@@ -168,6 +286,14 @@ The browser bundle rides the `window.__ModuleLoader__.load` contract; React/cord
|
|
|
168
286
|
|
|
169
287
|
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).
|
|
170
288
|
|
|
289
|
+
## Security model
|
|
290
|
+
|
|
291
|
+
- All pet API and asset routes are fenced to loopback clients.
|
|
292
|
+
- Asset serving resolves both the pet directory and the candidate file through `realpath`; symlink escapes are refused (403). Files are size-capped before being read into memory (manifest 64 KB, imagery 20 MB; over-cap answers 413).
|
|
293
|
+
- Live2D models are served by closure: only the manifest, the declared primary assets, and the files the `.model3.json` references (each screened against traversal, absolute and URL forms).
|
|
294
|
+
- The plugin never downloads executables and never bundles the Live2D Cubism Core.
|
|
295
|
+
- Manifests are fail-closed on structure: unknown fields or renderers reject the entry with a diagnostic shown in settings.
|
|
296
|
+
|
|
171
297
|
## License
|
|
172
298
|
|
|
173
299
|
[BSD-3-Clause](LICENSE)
|
package/README.zh.md
CHANGED
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
| 状态气泡 | 默认只有最近活动的顶层会话说话——多会话并行时,其余会话收进主气泡右上角的 +N 角标,不再叠出一长列;悬停气泡(触屏点按角标)即可向上展开所有会话的气泡,点击任一气泡跳转到对应会话;子代理会话借由其发起会话体现,不占用独立气泡;瞬时互动反馈临时优先。气泡文案按场景准备了大量轮换词库(等待 / 思考 / 整理 / 完成 / 失败……),工具调用按工具族映射俏皮文案并带上真实参数(如 跑跑 npm test),同一场景持续数秒会自动换一种说法 |
|
|
27
27
|
| 碎碎念 | 模型流式输出期间,宠物会偶尔借自己的气泡说出内心独白——新鲜的碎碎念会接管展示会话的气泡并以「」引号标记——与状态气泡共用同一片 DeepSeek 蓝黑玻璃,气泡栈内颜色统一不再色差——不再叠出第二只气泡——由模型输出里的关键词触发对应心境(报错、测试全绿、做计划、打胜仗……),输出量累积也会换来日常碎碎念;有冷却节制,数秒后气泡恢复状态文案 |
|
|
28
28
|
| 多会话活动 | 宠物是宿主全局的:最近一次有意义事件驱动精灵动画,同时每个活动的顶层会话用自己的气泡报告各自状态;每个会话(含子代理)完成的轮次都计入亲密度与小鱼干 |
|
|
29
|
+
| 语音包与面板 DIY | 宠物目录 voice.json + 全局 $DSH_HOME/pets/.voice.json 覆盖气泡全部文案与悬浮面板(按钮标签/统计格式/按钮显隐);合并优先级 宠物自带 > 全局 > 内置,坏包警告不拒载 |
|
|
29
30
|
|
|
30
31
|
## 宠物契约
|
|
31
32
|
|
|
@@ -59,14 +60,131 @@
|
|
|
59
60
|
- `frames` 记录每行用到的列数(缺省按 hatch-pet 契约表 `[6, 8, 8, 4, 5, 8, 6, 6, 6]`);`tracks` 按动画覆盖每帧时长(按该行帧数循环补足)、`loop` 与 `fallback`(默认:全部循环;`jumping` 与 `failed` 停在最后一帧后回到 `idle`)。
|
|
60
61
|
- `sequences` 可选地把活动场景(`idle` / `waiting` / `thinking` / `tool` / `review` / `done` / `failed`)映射到至少 5 条动画轨道。每项按 `tracks` 中的时长播完所有帧后进入下一项,整条序列循环;未声明的场景保持标准单轨播放。
|
|
61
62
|
|
|
63
|
+
### 清单 v2(宠物中心,#623)
|
|
64
|
+
|
|
65
|
+
宠物目录的 `pet.json` 在 v2 中显式声明渲染器:
|
|
66
|
+
|
|
67
|
+
- `petManifestVersion: 2`(缺省 = v1,按 `sprite2d` 兼容读并给出迁移提示);
|
|
68
|
+
- `renderer`:`"sprite2d"`(上文图集契约)或 `"live2d"`;
|
|
69
|
+
- `license`(v2 必填):资产授权标识——社区宠物必须携带来源声明;
|
|
70
|
+
- 渲染器专属块:`sprite2d`(spritesheetPath/cell/columns/atlasRows/frames/tracks)或 `live2d`(model/motions/expressions/hitAreas/scale/translate)。
|
|
71
|
+
|
|
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
|
+
|
|
62
74
|
宠物的来源(后注册的来源在同 id 冲突时覆盖前者):
|
|
63
75
|
|
|
64
76
|
1. **内置**:本包 `assets/<dir>/pet.json`。
|
|
65
|
-
2.
|
|
66
|
-
3.
|
|
77
|
+
2. **legacy 自定义宠物**:`${CODEX_HOME:-~/.codex}/pets/<pet>/pet.json` —— hatch-pet 流水线把产物放在这里,孵化的宠物无需任何接线即可出现在选择器里。
|
|
78
|
+
3. **宠物中心用户目录**:`$DSH_HOME/pets/<id>/`——推荐安放位(见下方 CLI)。
|
|
79
|
+
4. **组合注入**:嵌入应用通过 `PetConfig.pets` 传入的 manifest 条目。
|
|
80
|
+
|
|
81
|
+
用 CLI 校验并安装宠物目录(零构建、零发布):
|
|
82
|
+
|
|
83
|
+
```sh
|
|
84
|
+
node scripts/dsh-pet validate <dir> # 清单 + 资产 + Live2D 引用闭包 + voice.json
|
|
85
|
+
node scripts/dsh-pet install <dir> # 校验通过后拷入 $DSH_HOME/pets/<id>/
|
|
86
|
+
node scripts/dsh-pet install <dir> --force # 覆盖同名已安装宠物
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
非法条目永不覆盖可用宠物:它们被跳过并在设置(宠物栏目)给出诊断。注册表在宿主启动时构建一次;新增或修改宠物后重启 `dsh web` 生效。
|
|
90
|
+
|
|
91
|
+
## 语音包与面板定制(voice.json,宠物中心 M4,#677)
|
|
92
|
+
|
|
93
|
+
气泡里的状态/工具/碎碎念文案与悬浮面板的按钮/统计文案,都可以被一只宠物(或你本人)整体替换——宠物不再只能换图,还能换「话」与「面板」。载体是宠物目录内的可选 voice.json(随宠物分发),以及全局覆盖文件 $DSH_HOME/pets/.voice.json(不改宠物目录即可换词)。
|
|
94
|
+
|
|
95
|
+
```jsonc
|
|
96
|
+
{
|
|
97
|
+
"voicePackVersion": 1, // 可选;缺省视为 1
|
|
98
|
+
"status": { // 状态池:键 = 场景 id,逐键覆盖内置池
|
|
99
|
+
"done": ["搞定收工~", "交差!下一位"]
|
|
100
|
+
},
|
|
101
|
+
"tools": { // 工具池:键 = 工具族;允许 {tool} / {hint}
|
|
102
|
+
"shell": ["跑跑 {hint}", "敲回车!{hint}"]
|
|
103
|
+
},
|
|
104
|
+
"toolRemaining": ["后台还有 {n} 位小助手"], // 允许 {n}
|
|
105
|
+
"whispers": { // 碎碎念:按节替换(非逐条合并)
|
|
106
|
+
"generic": ["冲了冲了", "稳"], // 环境池;显式空数组 = 静音
|
|
107
|
+
"rules": [ // 关键词规则(有序;给出即整体替换内置规则)
|
|
108
|
+
{ "keywords": ["测试通过"], "pool": ["全绿!"] }
|
|
109
|
+
]
|
|
110
|
+
},
|
|
111
|
+
"panel": { // 悬浮面板(每槽未声明时回落插件 i18n 文案)
|
|
112
|
+
"labels": { "feed": "投喂", "hide": "藏起来", "rename": "起名字", "confirm": "好的" },
|
|
113
|
+
"stats": { "rank": "好感 {rank}", "treats": "鱼干 ×{n}", "points": "{points} 分" },
|
|
114
|
+
"actions": ["feed", "rename", "hide"] // 子集(按规范顺序);省略 = 全部;[] = 只显示统计行
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
- 合并优先级(逐槽):宠物自带 voice.json > 全局 .voice.json > 内置文案。status/tools 逐键合并、whispers 按节替换、panel 逐槽合并,任何层缺失的槽位回落下一层。
|
|
120
|
+
- 占位符白名单:tools 允许 {tool} / {hint};toolRemaining 允许 {n};panel.stats 允许 {rank} / {n} / {points};status、碎碎念与面板标签不允许任何占位符(含非法占位符的行被警告丢弃)。
|
|
121
|
+
- 上限(warn-and-drop):每池 64 行以内、每行 160 字符以内;规则 32 条以内、每规则关键词 16 条以内(40 字符以内);面板标签 40 以内、统计 80 字符以内。
|
|
122
|
+
- 坏包不影响宠物:voice.json 不是合法 JSON 或根不是对象 → 警告并整体忽略;其余问题逐槽警告丢弃。诊断显示在设置 → 宠物目录诊断。node scripts/dsh-pet validate <dir> 会把结构错误判为安装失败、内容问题列为警告。
|
|
123
|
+
- 语义细节:status/tools 的空池回落内置文案(场景行始终有话说);whispers 的显式空数组是静音(关掉该通道);面板 actions 为空数组 = 三个按钮全部隐藏;未覆盖的按钮/统计继续使用插件双语字典。
|
|
67
124
|
|
|
68
|
-
|
|
125
|
+
## Live2D 宠物(renderer: live2d)
|
|
69
126
|
|
|
127
|
+
Live2D 宠物经 PixiJS/WebGL 渲染:MIT 许可的 pixi.js + untitled-pixi-live2d-engine 栈以按需加载的 vendor 分包随插件内置,纯 sprite 用户永不下载、永不解析它。**本插件永不内置、永不代下 Cubism Core 运行时**——Live2D 专有许可不允许再分发。启用 Live2D 宠物:
|
|
128
|
+
|
|
129
|
+
1. 自行从 Live2D 官方渠道获取 Cubism SDK for Web(你自行同意其许可),取得 `live2dcubismcore.min.js`;
|
|
130
|
+
2. 放到 `$DSH_HOME/pets/.runtime/live2dcubismcore.min.js`——插件把它连同自带的 vendor 分包一起提供给页面;
|
|
131
|
+
3. 安装 Live2D 宠物(含 `pet.json` v2、`renderer: "live2d"` 与模型文件的目录)。
|
|
132
|
+
|
|
133
|
+
core 缺失时,Live2D 宠物在渲染位置给出安装指引卡;sprite2d 宠物不受影响。法律提示:本插件属 Live2D 术语下的「可扩展性 APP」——你公开发布基于可加载用户模型的衍生作品时,可能不论规模都须与 Live2D 签订发行许可;发布前请自行评估义务。
|
|
134
|
+
|
|
135
|
+
Live2D 清单把七个活动相位映射到模型的动作组:
|
|
136
|
+
|
|
137
|
+
```json
|
|
138
|
+
{
|
|
139
|
+
"petManifestVersion": 2,
|
|
140
|
+
"id": "my-live2d-pet",
|
|
141
|
+
"displayName": "My Live2D Pet",
|
|
142
|
+
"license": "CC0-1.0",
|
|
143
|
+
"renderer": "live2d",
|
|
144
|
+
"live2d": {
|
|
145
|
+
"model": "model/my-pet.model3.json",
|
|
146
|
+
"motions": { "idle": "Idle", "thinking": "Think", "failed": "TapBody" },
|
|
147
|
+
"hitAreas": ["Body"]
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
- `model`:相对宠物目录的 `.model3.json` 路径。模型引用的一切文件(moc、贴图、动作、物理、姿态、表情)都必须放在目录内——宿主恰好只服务这个引用闭包。
|
|
153
|
+
- `motions`(必填,且必须含 `idle`):相位 → 动作组。未映射的相位与模型缺失的组一律回退 `idle`;组内有多条动作时随机播放一条。官方 Cubism 示例模型只带 `Idle` 与 `TapBody` 两个组。
|
|
154
|
+
- `expressions`(可选):相位 → 表情名,叠加在动作之上。
|
|
155
|
+
- `hitAreas`(可选):点击落在列出的命中区时播放模型的 `TapBody` 组,播完回到当前相位的动作组。任何点击仍计入摸头——交互经济与 sprite2d 一样由 chrome 掌管。
|
|
156
|
+
- `scale` / `translate`(可选):模型自动适配显示盒;`scale` 在适配结果上乘算(默认 1,范围 (0, 10]),`translate` 以中心为基准做像素偏移。
|
|
157
|
+
|
|
158
|
+
模型授权:Live2D 官方示例模型(Hiyori、Haru 等)仅供评估、禁止再分发——只发布你有权的模型(原创作品或宽松授权的模型)。
|
|
159
|
+
|
|
160
|
+
## 状态装饰(decoration.json,宠物中心 M5,#567)
|
|
161
|
+
|
|
162
|
+
宠物状态气泡里的文字之前可以有一个小的状态装饰(内置:喷水鲸鱼),由 ActivityPhase 流驱动换帧。装饰与宠物相互独立:独立描述符、独立 id、独立目录,换宠物不换装饰。入口资产只收 PNG/WebP 单行精灵条带(不收 SVG/CSS);气泡自身的 role=status/aria-live(或会话气泡按钮语义)永远保留,装饰 aria-hidden;prefers-reduced-motion 时停在帧段首帧,资产加载失败只消失装饰、文字照常。
|
|
163
|
+
|
|
164
|
+
```jsonc
|
|
165
|
+
{
|
|
166
|
+
"decorationManifestVersion": 1,
|
|
167
|
+
"id": "whale", // 唯一小写 kebab id
|
|
168
|
+
"displayName": "喷水鲸鱼", // 可选
|
|
169
|
+
"license": "MIT", // 必填:资产授权(社区装饰必须携带来源声明)
|
|
170
|
+
"entry": "whale-frames.png", // PNG/WebP 单行条带,相对本目录
|
|
171
|
+
"cell": { "width": 64, "height": 48 },
|
|
172
|
+
"columns": 4, // 条带帧数(1..16)
|
|
173
|
+
"frameMs": 160, // 常量帧时长;或用 "durations": [..] 逐帧覆盖
|
|
174
|
+
"loop": true,
|
|
175
|
+
"phases": { // ActivityPhase 七态 -> 帧段(含端点);"hide" = 不显示;缺省 = hide
|
|
176
|
+
"idle": "hide",
|
|
177
|
+
"waiting": { "from": 0, "to": 1 },
|
|
178
|
+
"thinking": { "from": 0, "to": 3 },
|
|
179
|
+
"done": { "from": 2, "to": 3 },
|
|
180
|
+
"failed": { "from": 3, "to": 3 }
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
- 结构 fail-closed(未知字段、越界尺寸、非 PNG/WebP 入口直接拒载并进诊断),帧段内容 warn-and-drop。机器可读 schema 见 contracts/status-decoration-v1.schema.json,权威校验器为 src/decoration.ts。
|
|
186
|
+
- 来源:内置 assets/decorations/ + 用户目录 $DSH_HOME/pets/decorations/<id>/(同 id 覆盖内置)。资产经 /api/pet/decoration/<id>/<file> 路由,containment 与白名单与宠物资产同构。
|
|
187
|
+
- 开关:设置 → 宠物 → 状态装饰(默认开)。内置鲸鱼素材派生自 DeepSeek wordmark(MIT),完整声明见 THIRD_PARTY_NOTICES.md。
|
|
70
188
|
## 内置宠物
|
|
71
189
|
|
|
72
190
|
| 注册表 id | 选择器名称 | 来源 |
|
|
@@ -168,6 +286,14 @@ pnpm typecheck # 仅类型检查
|
|
|
168
286
|
|
|
169
287
|
两套内置鲸鱼娘图集使用同一份 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)。
|
|
170
288
|
|
|
289
|
+
## 安全模型
|
|
290
|
+
|
|
291
|
+
- 全部宠物 API 与资产路由仅服务回环客户端。
|
|
292
|
+
- 资产服务对宠物目录与目标文件双双做 `realpath` 解析;symlink 越界一律拒绝(403)。文件读入内存前按类限大小(清单 64 KB、图像 20 MB;超限 413)。
|
|
293
|
+
- Live2D 模型按闭包放行:仅清单、声明的主资产与 `.model3.json` 引用到的文件(引用先经穿越/绝对路径/URL 形态筛查)。
|
|
294
|
+
- 插件从不下载可执行文件,也从不内置 Live2D Cubism Core。
|
|
295
|
+
- 清单结构 fail-closed:未知字段或未知渲染器直接拒载,并在设置中给出诊断。
|
|
296
|
+
|
|
171
297
|
## 许可证
|
|
172
298
|
|
|
173
299
|
[BSD-3-Clause](LICENSE)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"decorationManifestVersion": 1,
|
|
3
|
+
"id": "whale",
|
|
4
|
+
"displayName": "喷水鲸鱼",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"entry": "whale-frames.png",
|
|
7
|
+
"cell": { "width": 64, "height": 48 },
|
|
8
|
+
"columns": 4,
|
|
9
|
+
"frameMs": 160,
|
|
10
|
+
"loop": true,
|
|
11
|
+
"phases": {
|
|
12
|
+
"idle": "hide",
|
|
13
|
+
"waiting": { "from": 0, "to": 1 },
|
|
14
|
+
"thinking": { "from": 0, "to": 3 },
|
|
15
|
+
"tool": { "from": 0, "to": 3 },
|
|
16
|
+
"review": { "from": 0, "to": 1 },
|
|
17
|
+
"done": { "from": 2, "to": 3 },
|
|
18
|
+
"failed": { "from": 3, "to": 3 }
|
|
19
|
+
}
|
|
20
|
+
}
|
|
Binary file
|
package/assets/whale/pet.json
CHANGED
|
@@ -1,27 +1,176 @@
|
|
|
1
1
|
{
|
|
2
|
+
"petManifestVersion": 2,
|
|
2
3
|
"id": "whale-girl",
|
|
3
4
|
"displayName": "鲸鱼娘(原版)",
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
5
|
+
"renderer": "sprite2d",
|
|
6
|
+
"sprite2d": {
|
|
7
|
+
"spritesheetPath": "spritesheet.webp",
|
|
8
|
+
"frames": [
|
|
9
|
+
6,
|
|
10
|
+
8,
|
|
11
|
+
8,
|
|
12
|
+
4,
|
|
13
|
+
5,
|
|
14
|
+
8,
|
|
15
|
+
6,
|
|
16
|
+
6,
|
|
17
|
+
6
|
|
18
|
+
],
|
|
19
|
+
"tracks": {
|
|
20
|
+
"idle": {
|
|
21
|
+
"durations": [
|
|
22
|
+
400,
|
|
23
|
+
400,
|
|
24
|
+
500,
|
|
25
|
+
400,
|
|
26
|
+
400,
|
|
27
|
+
500
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
"running-right": {
|
|
31
|
+
"durations": [
|
|
32
|
+
225,
|
|
33
|
+
225,
|
|
34
|
+
225,
|
|
35
|
+
225,
|
|
36
|
+
225,
|
|
37
|
+
225,
|
|
38
|
+
225,
|
|
39
|
+
225
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
"running-left": {
|
|
43
|
+
"durations": [
|
|
44
|
+
225,
|
|
45
|
+
225,
|
|
46
|
+
225,
|
|
47
|
+
225,
|
|
48
|
+
225,
|
|
49
|
+
225,
|
|
50
|
+
225,
|
|
51
|
+
225
|
|
52
|
+
]
|
|
53
|
+
},
|
|
54
|
+
"waving": {
|
|
55
|
+
"durations": [
|
|
56
|
+
350,
|
|
57
|
+
350,
|
|
58
|
+
350,
|
|
59
|
+
350
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
"jumping": {
|
|
63
|
+
"durations": [
|
|
64
|
+
300,
|
|
65
|
+
300,
|
|
66
|
+
300,
|
|
67
|
+
350,
|
|
68
|
+
350
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
"failed": {
|
|
72
|
+
"durations": [
|
|
73
|
+
450,
|
|
74
|
+
450,
|
|
75
|
+
450,
|
|
76
|
+
500,
|
|
77
|
+
550,
|
|
78
|
+
600,
|
|
79
|
+
450,
|
|
80
|
+
450
|
|
81
|
+
]
|
|
82
|
+
},
|
|
83
|
+
"waiting": {
|
|
84
|
+
"durations": [
|
|
85
|
+
450,
|
|
86
|
+
450,
|
|
87
|
+
500,
|
|
88
|
+
450,
|
|
89
|
+
450,
|
|
90
|
+
500
|
|
91
|
+
]
|
|
92
|
+
},
|
|
93
|
+
"running": {
|
|
94
|
+
"durations": [
|
|
95
|
+
250,
|
|
96
|
+
250,
|
|
97
|
+
250,
|
|
98
|
+
250,
|
|
99
|
+
250,
|
|
100
|
+
250
|
|
101
|
+
]
|
|
102
|
+
},
|
|
103
|
+
"review": {
|
|
104
|
+
"durations": [
|
|
105
|
+
550,
|
|
106
|
+
550,
|
|
107
|
+
550,
|
|
108
|
+
550,
|
|
109
|
+
550,
|
|
110
|
+
550
|
|
111
|
+
]
|
|
112
|
+
}
|
|
113
|
+
}
|
|
17
114
|
},
|
|
115
|
+
"description": "一只软萌治愈的鲸鱼娘,来自深海的陪伴小伙伴。",
|
|
116
|
+
"license": "BSD-3-Clause",
|
|
18
117
|
"sequences": {
|
|
19
|
-
"idle": [
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
118
|
+
"idle": [
|
|
119
|
+
"idle",
|
|
120
|
+
"waving",
|
|
121
|
+
"idle",
|
|
122
|
+
"waiting",
|
|
123
|
+
"idle",
|
|
124
|
+
"idle"
|
|
125
|
+
],
|
|
126
|
+
"waiting": [
|
|
127
|
+
"waiting",
|
|
128
|
+
"idle",
|
|
129
|
+
"waving",
|
|
130
|
+
"waiting",
|
|
131
|
+
"idle",
|
|
132
|
+
"waiting"
|
|
133
|
+
],
|
|
134
|
+
"thinking": [
|
|
135
|
+
"running",
|
|
136
|
+
"running-right",
|
|
137
|
+
"running",
|
|
138
|
+
"running-left",
|
|
139
|
+
"running",
|
|
140
|
+
"waiting",
|
|
141
|
+
"running"
|
|
142
|
+
],
|
|
143
|
+
"tool": [
|
|
144
|
+
"running-right",
|
|
145
|
+
"running",
|
|
146
|
+
"running-left",
|
|
147
|
+
"running",
|
|
148
|
+
"running-right",
|
|
149
|
+
"running"
|
|
150
|
+
],
|
|
151
|
+
"review": [
|
|
152
|
+
"review",
|
|
153
|
+
"waiting",
|
|
154
|
+
"review",
|
|
155
|
+
"running",
|
|
156
|
+
"review",
|
|
157
|
+
"idle"
|
|
158
|
+
],
|
|
159
|
+
"done": [
|
|
160
|
+
"jumping",
|
|
161
|
+
"waving",
|
|
162
|
+
"jumping",
|
|
163
|
+
"waving",
|
|
164
|
+
"jumping",
|
|
165
|
+
"idle"
|
|
166
|
+
],
|
|
167
|
+
"failed": [
|
|
168
|
+
"failed",
|
|
169
|
+
"waiting",
|
|
170
|
+
"failed",
|
|
171
|
+
"idle",
|
|
172
|
+
"waiting",
|
|
173
|
+
"failed"
|
|
174
|
+
]
|
|
26
175
|
}
|
|
27
176
|
}
|
|
@@ -1,7 +1,22 @@
|
|
|
1
1
|
{
|
|
2
|
+
"petManifestVersion": 2,
|
|
2
3
|
"id": "whale-girl-refined",
|
|
3
4
|
"displayName": "鲸鱼娘(精致版)",
|
|
5
|
+
"renderer": "sprite2d",
|
|
6
|
+
"sprite2d": {
|
|
7
|
+
"spritesheetPath": "spritesheet.webp",
|
|
8
|
+
"frames": [
|
|
9
|
+
6,
|
|
10
|
+
8,
|
|
11
|
+
8,
|
|
12
|
+
4,
|
|
13
|
+
5,
|
|
14
|
+
8,
|
|
15
|
+
6,
|
|
16
|
+
6,
|
|
17
|
+
6
|
|
18
|
+
]
|
|
19
|
+
},
|
|
4
20
|
"description": "基于鲸鱼娘形象进行 AI 辅助二次创作、修复与细节精修的内置变体。",
|
|
5
|
-
"
|
|
6
|
-
"frames": [6, 8, 8, 4, 5, 8, 6, 6, 6]
|
|
21
|
+
"license": "MIT"
|
|
7
22
|
}
|