dsh-pet-live2d 1.23.0 → 2.0.0
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/CHANGELOG.md +74 -0
- package/README.md +143 -308
- package/lib/client.js +2160 -140
- package/lib/index.js +5 -0
- package/package.json +2 -1
- package/pets/ds-whale-girl/README.md +32 -3
- package/pets/ds-whale-girl/pet.json +64 -25
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# 更新日志
|
|
2
|
+
|
|
3
|
+
## 2.0.0
|
|
4
|
+
|
|
5
|
+
设置界面、摸鱼机制、动作与表情的合成方式都动过一轮,所以是 2.0.0。**升级后建议看一眼
|
|
6
|
+
下面的「语义变化」** —— 有三条是行为真的变了,不是修 bug。
|
|
7
|
+
|
|
8
|
+
### 设置界面重做
|
|
9
|
+
|
|
10
|
+
- 设置区改成**卡片式**:一组一张卡,卡片里是「行」,行里是药丸按钮 / 权重条 / 数值。
|
|
11
|
+
视觉重量跟着信息重量走(抽中概率是主角,原始权重收成行尾的小旋钮)。
|
|
12
|
+
- 滑杆重做:3px 细轨道 + 12px 小圆钮,已拖过的一段用强调色填满。
|
|
13
|
+
- **槽位和候选都能增删**:`+ 名字` 是虚线药丸、`+ 槽位` 是实线药丸,条目行尾有 `×`。
|
|
14
|
+
宠物给的那套只是**建议**,你加进来的运行时真的会抽到。
|
|
15
|
+
- **相位改成池子**:每个会话相位下面是"槽位 → 条目表",和摸鱼同一套机制。
|
|
16
|
+
- **关系能增删**:条目下面能看到两种关系,也能自己加、自己删
|
|
17
|
+
- **同时** `pairs`:选了它就一起点亮(喵喵手 → 贴纸 = 猫猫)
|
|
18
|
+
- **前提** `requires`:必须先处于那个状态(挤番茄酱 → 左手 = 蛋包饭)
|
|
19
|
+
|
|
20
|
+
### 摸鱼与相位
|
|
21
|
+
|
|
22
|
+
- **默认值按作者自己调好的那套烘进 `pet.json`**:情绪(默认 20 / 开心兴奋 5 / 调皮 5 /
|
|
23
|
+
闭眼口水 1)、脸红(默认 10 / 脸红 1)、右手(默认 1 / 掏出手机 4 / 比耶 4 / 喵喵手 1 /
|
|
24
|
+
挤番茄酱 1)、眼部(默认 10 / 爱心眼 1)、自拍(默认 1 / 自拍 1)。**你自己的存档永远
|
|
25
|
+
优先**,所以升级不会覆盖你调过的东西。
|
|
26
|
+
- **摸鱼默认盯哪几个槽位改成宠物自己声明**(`pet.json` 的 `live2d.fidgetSlots`)——
|
|
27
|
+
这只宠物把「自拍」也放进了池子(带着「前提:右手 = 掏出手机」)。以前那六个是写死在
|
|
28
|
+
代码里的。
|
|
29
|
+
- **每个池子每次都重掷**(不是"这次只动一两个");权重就是"多久动一次"。
|
|
30
|
+
- **「默认」=回到默认**(把这个槽位清空):`默认 10 : 脸红 1` = 大约每 11 次摸鱼脸红
|
|
31
|
+
一次、其余回到没有。想让某个槽位完全不动,把它的条目删光或权重归 0。
|
|
32
|
+
- 摸鱼抽签**在应用前会重查一遍前提**:同一轮里右手被掷成比耶之后,被抽中的自拍不会
|
|
33
|
+
再播(以前会出现"先比耶、再拍照")。
|
|
34
|
+
- **相位列表一开始就列出全部相位**(不再是"空列表 + 点添加才看到默认值"):每行显示的是
|
|
35
|
+
**有效池子**(你改过就用你的,没改过就用宠物默认),行头标「默认 / 已改过」,
|
|
36
|
+
只有改过的行才有 `×`(= 恢复默认)。**覆盖只在真的编辑时才落盘** —— "看一眼"不会留下
|
|
37
|
+
痕迹,以后宠物更新了默认值你照样吃得到。
|
|
38
|
+
|
|
39
|
+
### 动作与表情
|
|
40
|
+
|
|
41
|
+
- **自拍独立成槽**(自拍 / 快速自拍),不再是右手的选项。
|
|
42
|
+
- **氛围三个独立**(情绪花花 / 心跳 / 冒爱心):它们写的是互不相干的参数,以前挤在一个
|
|
43
|
+
槽位里互相顶掉,现在可以同时开。
|
|
44
|
+
- **多个动作槽位的姿势可以共存**:右手拿着手机的同时,嘴部吹泡泡糖。
|
|
45
|
+
- **最后一个动作槽位说了算**:右手拿着手机时点吹泡泡糖,以前会被静默忽略。
|
|
46
|
+
- **选项上的「前提」现在管动作**:给「自拍」加「前提:右手 = 掏出手机」,摸鱼只有手机
|
|
47
|
+
在手时才拍它;手动点选则由插件把前提补上(点自拍会先掏手机)。
|
|
48
|
+
- **表情淡入 200ms**:引擎那套表情管理器一次只持有一个表情,多槽位叠加用不了它,
|
|
49
|
+
所以参数是我们自己按帧写的 —— 现在那一层自己补了缓动。
|
|
50
|
+
|
|
51
|
+
### 界面
|
|
52
|
+
|
|
53
|
+
- **面板跟随浅色 / 深色主题**(以前是写死的深色,浅色主题下点开就是一块黑)。
|
|
54
|
+
- **右键面板去掉「设置」页签**:面板只负责"点一下换个样子",改数值去 **DSH 设置页 →
|
|
55
|
+
桌宠** 那一节(两处本来共用同一份配置,现在只留一处)。
|
|
56
|
+
|
|
57
|
+
### 修复
|
|
58
|
+
|
|
59
|
+
- 配对(同时)做成**不变量**:爱心眼还选中着,冒爱心就不会丢;源头被清掉,配对跟着收。
|
|
60
|
+
- **动作定格时爱心不再消失**:爱心的位置参数只有待机循环在驱动,`hold` 的动作定格后
|
|
61
|
+
待机不再跑、这些参数会塌回 0(开关开着却看不见)。现在待机时**逐帧录像**,定格时按
|
|
62
|
+
同样的节奏循环回放 —— 既不塌、也不冻。
|
|
63
|
+
- **配对不会"一直出不来"**:以前它是一次性副作用,被清掉一次就再也没人点亮。
|
|
64
|
+
- 设置界面里权重输入框压住删除按钮、摸鱼「加槽位」按钮样式不对等一串外观问题。
|
|
65
|
+
- DSH 设置页那一节以前没有样式(它渲染在宠物根节点之外,作用域之外)。
|
|
66
|
+
|
|
67
|
+
### 诊断
|
|
68
|
+
|
|
69
|
+
新增读口(都挂在 `window.__dshLive2dPet` 上):`ambientDebug()`、`keptPoseDebug()`、
|
|
70
|
+
`effectiveRelations()`、`settingsOverrides()`、`fidgetTally()`、`expressionFade()`。
|
|
71
|
+
|
|
72
|
+
### 验证
|
|
73
|
+
|
|
74
|
+
16 个 CDP driver(真实浏览器 + 确定性参数断言,不做像素/哈希比对),全套通过。
|
package/README.md
CHANGED
|
@@ -1,75 +1,118 @@
|
|
|
1
|
-
# dsh-live2d
|
|
1
|
+
# dsh-pet-live2d
|
|
2
2
|
|
|
3
|
-
给 DSH Web GUI
|
|
3
|
+
给 **DSH**(DeepSeek Harness)Web GUI 用的 **Live2D 桌宠插件**:一只可以拖动、跟着鼠标看、
|
|
4
|
+
点她会害羞、还会跟着会话状态换动作的桌宠。
|
|
4
5
|
|
|
5
|
-
自带 **DS
|
|
6
|
+
自带 **DS鲸鱼娘**(8 组动作 + 44 个表情/道具),开箱即用。更新日志见 [CHANGELOG.md](CHANGELOG.md)。
|
|
6
7
|
|
|
7
8
|

|
|
8
9
|
|
|
9
10
|
## 功能
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
| 装扮菜单 | **表情与装扮合并成一个菜单**:44 个表情归入 17 个互斥槽位,每槽单选,**跨槽位可同时生效**。选择**常驻**;其中六件**装扮**(眼镜/发饰/魔爪/巴菲/桌布/手机换色)是**穿在身上**的:会话相位不动它们,「归位」也不清它们,而且**跨启动记住**(localStorage) |
|
|
22
|
-
| 拖动 | 按住角色身上拖走;位置与尺寸存 localStorage。**可以一直往下拖,压出屏幕底边**(最多沉下去一个身位),模型的画布有透明边距,不放开下界的话角色看着永远悬空 |
|
|
23
|
-
| 多宠物 | `%DSH_HOME%\pets\` 下所有 `renderer: live2d` 的宠物都会被扫描,面板里可切换 |
|
|
24
|
-
| 零配置宠物 | 宠物 = 一个目录 + `pet.json`;插件不硬编码任何模型 |
|
|
12
|
+
- **拖动 / 缩放**:位置和大小记在 localStorage,重启还在
|
|
13
|
+
- **跟随鼠标**:视线和头部跟着指针,移开(或窗口失焦)自动回正
|
|
14
|
+
- **点击反应**:点**头部**才挥锤撒娇,点身上其它地方只出气泡
|
|
15
|
+
- **事件穿透**:只有角色剪影吃鼠标事件,方形画布的透明处**穿透**到底下的页面,不挡 DSH 的 UI
|
|
16
|
+
- **跟着会话走**:订阅 DSH 的真实事件(`tools/*` 等),思考/工具/等待/完成/出错换动作与表情,
|
|
17
|
+
长任务持续播放对应动画
|
|
18
|
+
- **待机摸鱼**:静置一会儿自己演一段(不会演「点击」「出错」这类专属动作)
|
|
19
|
+
- **都会自己收尾**:动作、定格、表情到点全部回到初始待机,不会卡住
|
|
20
|
+
- **全都可配**:20 个互斥槽位(装扮 + 表情),每个槽位一张可增删、带权重的条目表,
|
|
21
|
+
条目之间还能配「同时 / 前提」关系;会话相位是同一套池子机制
|
|
25
22
|
|
|
26
23
|
## 安装
|
|
27
24
|
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
25
|
+
```bash
|
|
26
|
+
# 从 npm 装(推荐;插件和自带宠物一起下好)
|
|
27
|
+
dsh plugin --profile web add dsh-pet-live2d
|
|
28
|
+
|
|
29
|
+
# 或从仓库装(# 后面是 pnpm 的 path: 协议,注意那个斜杠)
|
|
30
|
+
dsh plugin --profile web add "github:A8Chann/dsh-pet-live2d#path:/dsh-live2d-pet"
|
|
31
|
+
|
|
32
|
+
# 或先克隆再装本地目录
|
|
33
|
+
git clone https://github.com/A8Chann/dsh-pet-live2d
|
|
34
|
+
dsh plugin --profile web add "link:./dsh-pet-live2d/dsh-live2d-pet"
|
|
32
35
|
```
|
|
33
36
|
|
|
34
|
-
|
|
37
|
+
### 必须自备 Cubism Core
|
|
35
38
|
|
|
36
|
-
|
|
39
|
+
`live2dcubismcore.min.js` 是 Live2D 株式会社的**专有运行时**,不能随插件分发。
|
|
40
|
+
去 [Live2D 官网](https://www.live2d.com/sdk/cubism/) 下载 Cubism SDK for Web,把
|
|
41
|
+
`Core/live2dcubismcore.min.js` 放到 `%DSH_HOME%\pets\.runtime\live2dcubismcore.min.js`。
|
|
37
42
|
|
|
38
|
-
|
|
39
|
-
但也不需要你去找文件:**宿主半区会在本地缺失时,向 Live2D 自己的 CDN 取一份**
|
|
40
|
-
(`https://cubism.live2d.com/sdk-web/cubismcore/live2dcubismcore.min.js`,
|
|
41
|
-
官方 SDK 文档就是让使用者在页面里引这一行;这个地址带 `Access-Control-Allow-Origin: *`),
|
|
42
|
-
取到后**缓存到本地**,之后离线也能用:
|
|
43
|
+
### 宠物:随包自带
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
插件包里就带着一只可用的宠物(`pets/ds-whale-girl/`,CC BY-NC-SA 4.0)。**第一次运行时
|
|
46
|
+
宿主半区会把它复制进 `%DSH_HOME%\pets\`**,所以装完插件、重启 `dsh web` 就能看见。
|
|
47
|
+
只在目标**不存在**时复制 —— 你自己改过或换过的宠物目录永远优先,不会被覆盖。
|
|
48
|
+
|
|
49
|
+
## 怎么用
|
|
50
|
+
|
|
51
|
+
- **右键点宠物** 呼出面板:「动作」「装扮」两个页签,底部是大小滑杆和「归位」。
|
|
52
|
+
平时画面上没有常驻 UI。
|
|
53
|
+
- **改配置** 去 **DSH 设置页 → 桌宠**(面板只负责"点一下换个样子")。
|
|
54
|
+
|
|
55
|
+
## 设置说明
|
|
56
|
+
|
|
57
|
+
### 摸鱼:每个槽位一张条目表
|
|
58
|
+
|
|
59
|
+
摸鱼 = 静置一会儿之后,宠物自己换个样子。**每个槽位各掷一次骰子**(不是"这次只动一两个"),
|
|
60
|
+
条目上的权重就是"多久动一次"。
|
|
61
|
+
|
|
62
|
+
- 一张表里的条目可以**增删**:`+ 名字` 是虚线药丸,行尾 `×` 删掉。
|
|
63
|
+
宠物给的那套只是**建议**,你加进来的运行时真的会抽到。
|
|
64
|
+
- 摸鱼默认盯**宠物声明的那几个槽位**(这只宠物是七个:右手 / 左手 / 情绪 / 脸红 / 嘴部 /
|
|
65
|
+
眼部 / 自拍,写在 `pet.json` 的 `live2d.fidgetSlots` 里),其余槽位在卡片底部点一下就
|
|
66
|
+
加进来(`+ 槽位`)。
|
|
67
|
+
- **「默认」=回到默认**(把这个槽位清空)。`默认 10 : 脸红 1` = 大约每 11 次摸鱼脸红一次、
|
|
68
|
+
其余时间回到没有。想让某个槽位**完全不动**,把它的条目删光或权重归 0。
|
|
69
|
+
|
|
70
|
+
### 会话相位:同一套池子
|
|
71
|
+
|
|
72
|
+
每个会话相位(`thinking` / `tool` / `waiting` / `done` / `failed`)下面是"槽位 → 条目表",
|
|
73
|
+
机制和摸鱼一样。相位表**一开始就列出全部相位**,显示的是**有效池子**:
|
|
74
|
+
|
|
75
|
+
| 行头标记 | 含义 | 那一行有什么 |
|
|
76
|
+
|---|---|---|
|
|
77
|
+
| 默认 | 你没改过,用的是宠物默认 | 没有 `×` |
|
|
78
|
+
| 已改过 | 你改过,用的是你的 | 有 `×` = 恢复默认 |
|
|
47
79
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
宠物位置会显示一张安装指引卡,接口返回 502 并带上该地址和落盘路径(不会崩,也不影响其它插件)。
|
|
80
|
+
改动**只在真的编辑时才落盘**,所以"看一眼"不会留下覆盖 —— 以后宠物更新了默认值,
|
|
81
|
+
没改过的相位照样吃得到。
|
|
51
82
|
|
|
52
|
-
|
|
83
|
+
### 关系:同时(pairs)与前提(requires)
|
|
53
84
|
|
|
54
|
-
|
|
85
|
+
条目下面缩进那一层是**关系**,两种刻意用不同前缀:
|
|
55
86
|
|
|
56
|
-
|
|
|
57
|
-
|
|
58
|
-
|
|
|
59
|
-
|
|
|
60
|
-
|
|
61
|
-
|
|
87
|
+
| 关系 | 含义 | 例 |
|
|
88
|
+
|---|---|---|
|
|
89
|
+
| **同时** `pairs` | 抽中它 / 选了它就**一起点亮** | 喵喵手 → 贴纸 = 猫猫 |
|
|
90
|
+
| **前提** `requires` | 必须先处于那个状态才**播得出来** | 挤番茄酱 → 左手 = 蛋包饭 |
|
|
91
|
+
|
|
92
|
+
- 两者都能加能删(行尾 `×`,条目下面的 `+ 关系` 里按「同时 / 前提」分组选目标)。
|
|
93
|
+
- **摸鱼抽签时前提是闸门**:前提不成立的条目不进池子。前提可能靠同一轮里别的槽位满足
|
|
94
|
+
(挤番茄酱要蛋包饭),所以抽签会**抽到稳定为止**(最多三轮)。
|
|
95
|
+
- **手动点选时前提由插件补上**:点「自拍」会先把手机掏出来,跟点「挤番茄酱」会把蛋包饭
|
|
96
|
+
端上来是同一套 —— 不会"点了没反应"。
|
|
97
|
+
- 关系跟着**选项**走(不是池子里的某一条):同一个姿势在摸鱼表、相位池、右键面板里看到的
|
|
98
|
+
是同一份关系,改一处三处一起变。
|
|
99
|
+
|
|
100
|
+
### 装扮:穿在身上的东西
|
|
62
101
|
|
|
63
|
-
|
|
102
|
+
眼镜 / 发饰 / 魔爪 / 巴菲(桌面摆设)/ 桌布 / 手机换色 这六个槽位是**装扮**,和"这一轮临时挑的
|
|
103
|
+
表情"不是一回事:
|
|
64
104
|
|
|
65
|
-
|
|
105
|
+
| 场景 | 临时表情 | 装扮 |
|
|
106
|
+
|---|---|---|
|
|
107
|
+
| 会话相位开始 / 结束 | 由相位接管,相位结束就撤掉 | **不动** |
|
|
108
|
+
| 点「归位」 | 清空 | **保留** |
|
|
109
|
+
| 重启 / 刷新页面 | 回到默认 | **从 localStorage 穿回来** |
|
|
66
110
|
|
|
67
|
-
|
|
111
|
+
## 这只宠物有什么
|
|
68
112
|
|
|
69
|
-
44
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
表情那 7 个槽位则参考了参数冲突:`爱心眼/开心兴奋/悲伤/大哭/生气/晕晕` 都会写眉毛参数,放在一起必然打架。
|
|
113
|
+
44 个表情归入 **20 个互斥槽位**(换过宠物 / 改过配置的话,以设置页和 `pet.json` 为准)。
|
|
114
|
+
分类依据是**模型作者在 cdi3 里自己写的分组和中文名**(例如 `ParamGroup29` 被作者命名为
|
|
115
|
+
「C款动作开关」,里面正好是一组互斥的手部状态),不是按文件名猜的。
|
|
73
116
|
|
|
74
117
|
| 槽位 | 不选 | 可选项 |
|
|
75
118
|
|---|---|---|
|
|
@@ -81,245 +124,25 @@ Live2D 的专有许可**不允许再分发** Core 运行时,所以本插件**
|
|
|
81
124
|
| 鲸鱼 | 无 | 头顶鲸 / 放桌上 |
|
|
82
125
|
| 桌面摆设 | 无 | 巴菲 |
|
|
83
126
|
| 右手 | 无 | 掏出手机 / 喵喵手 / 双手比耶 / 挤番茄酱 / 写本本 |
|
|
127
|
+
| 自拍 | 无 | 自拍 / 快速自拍 |
|
|
84
128
|
| 左手 | 无 | 撤回 / 画笔 / 橡皮 / 蛋包饭 |
|
|
85
129
|
| 眼部 | 默认 | 星星眼 / 爱心眼 / 呆呆眼 / 晕晕 / 阴暗 |
|
|
86
130
|
| 情绪 | 平静 | 开心兴奋 / 悲伤 / 大哭 / 生气 / 调皮 / 闭眼口水 / 吐魂 |
|
|
87
131
|
| 嘴部 | 闭嘴 | 吐舌 / 吹泡泡糖 |
|
|
88
132
|
| 符号 | 无 | 问号 / 感叹号 / 流汗 |
|
|
89
|
-
|
|
|
133
|
+
| 氛围:花花 | 无 | 情绪花花 |
|
|
134
|
+
| 氛围:心跳 | 无 | 心跳 |
|
|
135
|
+
| 氛围:冒爱心 | 无 | 冒爱心 |
|
|
90
136
|
| 脸红 | 否 | 脸红 |
|
|
91
137
|
| 其他 | 无 | 手机换色 |
|
|
92
138
|
| 点菜 | 无 | 点菜按下 |
|
|
93
139
|
|
|
94
|
-
|
|
95
|
-
|
|
140
|
+
**一个选项可以带多个表达式**(「白魔爪」= 桌面粉魔爪 + 魔爪换色),**选择常驻**(不会过几秒
|
|
141
|
+
自己消失;自动清理只留给"反应"和"会话相位")。
|
|
96
142
|
|
|
97
|
-
|
|
98
|
-
(没有爪子可换色),所以「白魔爪」必须同时点亮 `桌面粉魔爪` 和 `魔爪换色`:
|
|
143
|
+
## 做一只自己的宠物
|
|
99
144
|
|
|
100
|
-
|
|
101
|
-
{ "label": "白魔爪", "expressions": ["桌面粉魔爪", "魔爪换色"] }
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
**选择常驻**。早期版本里手动点的表情会在 `EXPRESSION_HOLD_MS` 后自动清掉,那是为了防止
|
|
105
|
-
宠物卡在某个状态;但对装扮来说这是错的——用户从面板挑的搭配过几秒自己消失,看起来就是 bug。
|
|
106
|
-
现在自动清理只留给**反应**和**会话相位**,面板选择一直保留到「归位」或改选。
|
|
107
|
-
|
|
108
|
-
### 装扮:穿在身上的东西
|
|
109
|
-
|
|
110
|
-
眼镜 / 发饰 / 魔爪 / 巴菲(桌面摆设)/ 桌布 / 手机换色 这六个槽位是**装扮**(`OUTFIT_SLOTS`),
|
|
111
|
-
跟"这一轮临时挑的表情"不是一回事:
|
|
112
|
-
|
|
113
|
-
| 场景 | 临时表情 | 装扮 |
|
|
114
|
-
|---|---|---|
|
|
115
|
-
| 会话相位开始/结束 | 由相位接管,相位结束就撤掉 | **不动** |
|
|
116
|
-
| 点「归位」 | 清空 | **保留** |
|
|
117
|
-
| 重启 / 刷新页面 | 回到默认 | **从 localStorage 穿回来** |
|
|
118
|
-
| 自动清理(`EXPRESSION_HOLD_MS`) | 到点清掉 | 从不参与 |
|
|
119
|
-
|
|
120
|
-
实现上一句话就够:`commitPinsRef` 里装扮的 pin **最后合并**,所以相位的"槽位拥有权"
|
|
121
|
-
压不过它;`resetAll` 只清临时效果;选择一变就写一次 `localStorage`
|
|
122
|
-
(key `dsh-live2d-pet:outfit`),启动后在 ready 时读回来,并**校验 label 在当前 pet.json
|
|
123
|
-
里还存在**——换模型或改配置之后存档可能对不上,对不上就当没存过,不会凭空造一个选项出来。
|
|
124
|
-
|
|
125
|
-
### 多槽位叠加(装扮)
|
|
126
|
-
|
|
127
|
-
引擎的表情管理器一次只持有**一个**表情(`expressionManager.currentExpression`),所以「眼镜 + 猫猫贴纸 + 深色桌布 同时戴」不能交给它。
|
|
128
|
-
|
|
129
|
-
**做法**:插件自己按帧写参数。每个表达式在自己的 .exp3.json 里声明了它要写的通道和混合方式(这个模型全是 `Add`),客户端把选中表达式的通道取并集交给控制器,控制器每帧叠加一次。
|
|
130
|
-
|
|
131
|
-
**关键是挂钩点**:写在 `loadParameters()` 之后是错的——那一帧的 `saveParameters()` 会把它一起快照进基线,于是下一帧恢复出来时已经含了它,再叠一次,**永远关不掉**(实测开关停在 1,取消选择也不回落)。正确的点是 `saveParameters()` **之后**:
|
|
132
|
-
|
|
133
|
-
```
|
|
134
|
-
每帧:loadParameters()(抹掉上一帧的表情层)→ 动作写参数 → saveParameters()(快照)
|
|
135
|
-
→ 我们写表情层 → 引擎算形变 → 绘制
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
引擎自己的表情流程就在 `saveParameters()` 之后,所以这就是同一层,只是没有「只能有一个」的限制。
|
|
139
|
-
|
|
140
|
-
实测(`cdp-merge`,全部从**真正的装扮面板**点出来):
|
|
141
|
-
|
|
142
|
-
| 操作 | 结果(帧内参数) |
|
|
143
|
-
|---|---|
|
|
144
|
-
| 什么都不选 | `[0,0,0,0]` |
|
|
145
|
-
| 点圆眼镜 | `[1,0,0,0]` |
|
|
146
|
-
| 再点猫猫 | `[1,1,0,0]` —— 眼镜留着 |
|
|
147
|
-
| 眼镜槽选「无」 | `[0,1,0,0]` —— 猫猫留着 |
|
|
148
|
-
| 再加深色桌布 | `[1,1,1,0]` |
|
|
149
|
-
|
|
150
|
-
---
|
|
151
|
-
|
|
152
|
-
### 附:这一块踩过的测量陷阱
|
|
153
|
-
|
|
154
|
-
同一个问题我给出过**两次互相矛盾**的结论,都是被错误的测量方法带偏的,记下来免得重蹈:
|
|
155
|
-
|
|
156
|
-
| 方法 | 得到 | 我当时的结论 | 真相 |
|
|
157
|
-
|---|---|---|---|
|
|
158
|
-
| 冻结 rAF + 截图逐像素比对 | 每格 0.00% | 「表情全都不生效」 | 冻结不可靠,后面每格都在读同一张陈旧帧 |
|
|
159
|
-
| 比对 canvas 哈希 | `changed: true` | 「表情是好的,只是要等淡入」 | 宠物一直在呼吸眨眼,**任意两帧都不会逐字节相同**,信号恒为真 |
|
|
160
|
-
|
|
161
|
-
可靠的做法是读**引擎在帧内写进模型的参数值**:确定性、不受动画相位影响。`cdp-exp` 和 `cdp-merge` 现在都这么做。
|
|
162
|
-
|
|
163
|
-
顺带修的:`cdp-exp` 原本是个**打印型** driver——结尾无条件 `process.exit(0)`,从不判定,再加上它唯一的信号(canvas 哈希)恒为真,等于从来没有断言过任何事。现在它真的会失败。
|
|
164
|
-
## v1.3 修正(本次)
|
|
165
|
-
|
|
166
|
-
| # | 问题 | 原因 | 处理 |
|
|
167
|
-
|---|---|---|---|
|
|
168
|
-
| 1 | 重锤出击在摸鱼动画里也会播 | 摸鱼从「所有非待机动作」里随机抽,重锤出击与鲸鱼喷水都在池子里 | 加 `FIDGET_DENY` 拒绝表;宠物可用 `motionOptions[group].fidget` 覆盖 |
|
|
169
|
-
| 2 | 鲸鱼喷水同上 | 同上 | 同上 |
|
|
170
|
-
| 3 | 动作 / 表情播完不回初始待机 | `hold: true` 是**永久**定格;手动点的表情也永久钉住 | `ACTION_HOLD_MAX_MS`(9s) / `EXPRESSION_HOLD_MS`(12s) 两道时限 + `resetToRest()` 收口 |
|
|
171
|
-
| 4 | 没接上 DSH 会话流 | 订阅了 `tool/call`——那是**会话日志事件**,不是 cordis 事件,**永远不触发**;而且相位只播一次,长任务看着像没反应 | 改订 `tools/pre-execute` / `tools/post-execute`(瀑布事件,必须 `next()`);`tool`/`done`/`failed` 改为 **sustain**,动作播完自动重播直到相位改变 |
|
|
172
|
-
| 5 | 整个方形画布都能拖动、挡住底下 UI | 元素即使全透明,只要 `pointer-events: auto` 就吃满整个盒子 | 根节点 `pointer-events: none` + 一层用 `clip-path: path()` 裁成剪影的**不可见代理**;透明处穿透(`mask-image` 不影响命中测试,只有 `clip-path` 会) |
|
|
173
|
-
| 6 | 点身上任何地方都挥锤 | 没有头部区域的概念 | 从**模型自己的五官 drawable** 量出头部包围盒(模型空间,随缩放/拖动自动跟随),只有点头部才触发重锤出击 |
|
|
174
|
-
|
|
175
|
-
### 为什么这些 bug 之前测不出来
|
|
176
|
-
|
|
177
|
-
回归套件的宿主是**假的**:`attachActivityEvents({ on: () => {} }, hub)` 传了一个空的 `on`,相位全靠 `/__nudge` 直接推 hub。于是**插件自己的事件订阅一行都没被验证过**——这正是 `tool/call` 那个 bug 能带着 10 个绿灯活下来的原因。
|
|
178
|
-
|
|
179
|
-
现在 harness 提供一个真的小事件总线 + `/__emit`,`cdp-host-events.mjs` 用**真实事件名**驱动并断言。新增 5 个 driver(共 16 个):
|
|
180
|
-
|
|
181
|
-
| driver | 覆盖 |
|
|
182
|
-
|---|---|
|
|
183
|
-
| `cdp-host-events.mjs` | `tools/pre-execute` 有订阅者、会 `next()`、能推进相位;长相位持续播放;`tools/post-execute` / `agent/turn-stopping` 收尾 |
|
|
184
|
-
| `cdp-head.mjs` | 点头部播重锤出击 + 脸红;点身上不播;摸鱼池排除重锤/喷水 |
|
|
185
|
-
| `cdp-idle-return.mjs` | 表情 12s 自清;定格 9s 释放;`resetToRest()`;相位表情随相位清除 |
|
|
186
|
-
| `cdp-passthrough.mjs` | 四个角都穿透到页面;角落点击不触发反应、不拖动;角色上点击正常送达 |
|
|
187
|
-
| `cdp-bubble.mjs` | 动作定格真的能关掉:吹泡泡糖 / 掏出手机 → 无,**连做三轮**(污染只在第二轮之后现形);断言用 `drawn()`,并同时钉住"帧外基线仍停在 1"这条缝 |
|
|
188
|
-
## v1.2 交互与渲染优化
|
|
189
|
-
|
|
190
|
-
| 问题 | 原因 | 处理 |
|
|
191
|
-
|---|---|---|
|
|
192
|
-
| 放大后画面模糊 | 渲染缓冲固定 1x,画面被 CSS 拉伸 | `resolution = max(2, devicePixelRatio)`;实测 backing store 恒为 CSS 尺寸的 2–3 倍 |
|
|
193
|
-
| **缩小后线条发虚**(v1.2.1) | 两道叠加:① 2048² 图集被直接缩到 160–760px,而 `lod:"single-auto"` 只在 effectiveScale < 0.5 时才做 LOD——300px 时约 0.59,**这个分支根本没触发**,等于每个屏幕像素只从图集里抽 1 个纹素,细笔画被整根抽掉;② 画布 backing store 在 1x 屏上只有 160–760²,细线本身就落在采样点之间 | ① `lod:"full"` 建完整 mip 链(注意:`lod:false` 是"全分辨率但**不建** mip",比 `"single-auto"` 更糟)+ `maxAnisotropy: 8`;② 渲染倍率下限提到 **2x** 做超采样。面部细笔画像素占比实测(300px):原始 8.16% → 仅建 mip 3.89% → mip+2x 超采样 5.27%,断线/锯齿消失,细线恢复连续 |
|
|
194
|
-
| 画布空白处也能点到 | 整个方形 canvas 都在吃点击 | 从**实际渲染出的像素**提取 64×64 透明度网格,只有落在角色轮廓上才算点击 |
|
|
195
|
-
| 画布挡住底下的 UI(v1.3) | 元素即使透明,只要 pointer-events: auto 就会吃满整个盒子 | 根节点改成 pointer-events: none,另加一层**不可见代理**,用 clip-path: path(...) 把可点区裁成角色剪影(mask-image **不影响**命中测试,只有 clip-path 会影响)。透明处直接穿透到页面;代理轮廓按 hitsMask 的 ±1 格容差**膨胀一格**,两者严格重合 |
|
|
196
|
-
| 鼠标移开后视线不回正 | 视线停在"最后一个指针位置" | 超出注视范围即回到**模型默认中心位**(`data-gaze="center"`) |
|
|
197
|
-
| 不接会话状态 | 只响应点击 | 宿主订阅 `agent/status` / `agent/turn-stopping` / `agent/error` / `approval/request`,经同源 SSE 推送;客户端按相位切换动作与表情 |
|
|
198
|
-
| 打开设置面板宠物被放大 | **真 bug**:`layout()` 用了 `model.width`,而 Pixi 的 `Container.width` 返回的是**当前缩放后**的尺寸,于是每次重排都把缩放自乘一次 | 启动时缓存**未缩放**原始尺寸,之后一律由它计算;面板开合不再影响画面 |
|
|
199
|
-
| 待机太死板 | 没有随机行为 | 静置 12–26 秒后随机播一个非待机动作("摸鱼"),播完自动回待机;任何交互都会重置计时。**重锤出击与鲸鱼喷水不在摸鱼池里**——它们是「点头」和「出错」的专属反应,被随机播出来就像宠物在回应一件根本没发生的事(`FIDGET_DENY`,宠物可用 `motionOptions[group].fidget` 覆盖) |
|
|
200
|
-
|
|
201
|
-
### 缩小时的锐度(v1.2.1)
|
|
202
|
-
|
|
203
|
-

|
|
204
|
-
|
|
205
|
-
上图为 3 倍最近邻放大的面部区域,顺序是 **旧 160px | 新 160px | 旧 300px | 新 300px**。旧的渲染里发丝是断续的虚线状、轮廓边上有明显的方块感;新的渲染线条连续、边界干净。
|
|
206
|
-
|
|
207
|
-
三处改动:
|
|
208
|
-
|
|
209
|
-
| 项 | 旧 | 新 | 为什么 |
|
|
210
|
-
|---|---|---|---|
|
|
211
|
-
| 纹理采样 | `lod: "single-auto"` | `lod: "full"` | `"single-auto"` 只有 effectiveScale < 0.5 才生效;300px 宠物约 0.59,**这条分支从未触发**,等于只做双线性点采样。`"full"` 才会让资源加载器生成完整 mip 链(注意 `lod:false` 是"全分辨率但**不建** mip",比 `"single-auto"` 更差) |
|
|
212
|
-
| 各向异性过滤 | 无 | 各向异性 8x | 引擎不会把 `textureOptions.maxAnisotropy` 传给采样器,必须在加载后写到每张纹理的 style 上;它负责斜向线条(刘海、缎带边缘)在斜视时不糊成一片 |
|
|
213
|
-
| 渲染倍率 | `min(3, devicePixelRatio)`,1x 屏就是 1x | `min(3, max(2, devicePixelRatio))` | 这是最有效的一招:1x 屏上 300px 画布只有 300² 采样点,无论纹理怎么筛,输出就只有这么多样本。下限提到 2x 等于**超采样**(每个显示像素 4 个渲染样本),再由浏览器缩回 CSS 尺寸 |
|
|
214
|
-
|
|
215
|
-
实测(300px,面部细笔画像素占该区域的比例):**旧 8.16% → 仅建 mip 3.89% → mip + 2x 超采样 5.27%**。纯 mip 化会把细线"抹平"(数字反而比旧的低),所以两者必须一起上;超采样把细节拉回来,mip 链保证缩小时不出现摩尔纹和闪烁。
|
|
216
|
-
|
|
217
|
-
### 表情(v1.2.2)
|
|
218
|
-
|
|
219
|
-
44 个表情此前**全部加载失败**:`model3.json` 里指向的是中文文件名(`expressions/脸红.exp3.json`),而磁盘上按 manifest 路径校验的要求已经改成了 ASCII slug(`facial-red.exp3.json`)——生成器改过,但改完没有重新跑,装到 `%DSH_HOME%` 的那份是旧的。`build-pet.mjs` 现在会**保留手写文件**(`pet.json` / `catalog.json` / `README.md` / `voice.json`),重跑不会再把这些删掉,可以安全地反复执行。
|
|
220
|
-
|
|
221
|
-
还有一处命名不一致:`哭.exp3.json` 在 `model3.json` 里声明的 `Name` 是 **"大哭"**。表情查找按 `Name` 而非文件名匹配,所以 `failed` 相位原本写的 `"哭"` 永远查不到、静默什么都不做。现已修正为 `大哭`。
|
|
222
|
-
|
|
223
|
-
### 会话状态映射(v1.3 修正)
|
|
224
|
-
|
|
225
|
-
宿主把 DSH 的真实事件折叠成一个粗粒度相位并推送:
|
|
226
|
-
|
|
227
|
-
| 事件 | 相位 | 默认动作 | 默认表情 | 持续播放 |
|
|
228
|
-
|---|---|---|---|---|
|
|
229
|
-
| `agent/status` → running | thinking | 待机 | 呆呆眼 | 否(待机本身就在循环) |
|
|
230
|
-
| `approval/request` | waiting | 待机 | 问号 | 否 |
|
|
231
|
-
| **`tools/pre-execute`** | tool | 挤番茄酱 | 流汗 | **是** |
|
|
232
|
-
| `agent/turn-stopping` | done | 吹泡泡糖 | 情绪花花 | **是** |
|
|
233
|
-
| `agent/error` | failed | 鲸鱼喷水 | 大哭 | **是** |
|
|
234
|
-
|
|
235
|
-
> **为什么会话流之前「没接上」**:`tool/call` **不是** cordis 生命周期事件,而是写进会话记录(transcript)的**日志事件**——`ctx.on('tool/call')` 永远不会触发,所以工具活动对宠物完全不可见。真正的挂钩点是 `tools/*` 瀑布事件(`tools/pre-execute` / `tools/post-execute` / `tools/execute`),它们带着 `ToolExecution` 本身。瀑布事件**必须**调用 `next()`,否则会把整条链断掉,所以订阅器把它包在 try/catch 里、异常时也继续 `next()`。
|
|
236
|
-
|
|
237
|
-
**工具相位不抖动**:一轮对话里往往连着跑很多个工具。如果每次工具返回就立刻退回 `thinking`,相位会一秒翻好几次、动画跟着不停重启。所以退回是**防抖**的——只有在工具真的不再来了(`TOOL_IDLE_MS` = 1.2s 内没有新工具)之后才退回。
|
|
238
|
-
|
|
239
|
-
**持续播放**:相位是**状态**而不是一次性事件。`tool` / `done` / `failed` 会由控制器的 sustain 循环在动作播完后自动重播,直到相位改变——否则一个跑了 30 秒的工具调用只会看到 5 秒动画然后回到待机,看起来就像「没反应」。`thinking` / `waiting` 落在待机循环上,本身就在动,不重复触发(否则只会显得抽搐)。
|
|
240
|
-
|
|
241
|
-
可被宠物自己在 `pet.json` 的 `live2d.motions` / `live2d.expressions` 里覆盖(键就是这些相位名)。相位到达时若宠物正在演用户触发的反应,会**延后到回待机再补播**,不会丢掉状态;SSE 断开时会**解除 sustain**,不会永久卡在某个相位。
|
|
242
|
-
|
|
243
|
-
### 回到待机(v1.3)
|
|
244
|
-
|
|
245
|
-
要求是「所有动作、表情在播完一段时间后都要**完全**切回初始待机」。三件事共同保证:
|
|
246
|
-
|
|
247
|
-
| 情形 | 上限 | 说明 |
|
|
248
|
-
|---|---|---|
|
|
249
|
-
| 普通动作 | 动作自身 `Duration` | 播完即回待机循环 |
|
|
250
|
-
| `hold: true` 的定格 | `ACTION_HOLD_MAX_MS` = 9s | 定格**不是永久的**:先定格一会儿让人看清,然后交还身体并还原参数 |
|
|
251
|
-
| 手动点的表情 | `EXPRESSION_HOLD_MS` = 12s | 到点自动清除,面板的高亮也跟着消失 |
|
|
252
|
-
| 会话相位表情 | 跟相位同寿 | 相位离开时清除 |
|
|
253
|
-
|
|
254
|
-
`resetToRest()` 是唯一的收口:清 sustain、还参数、回待机循环。「归位」按钮走的就是它。
|
|
255
|
-
|
|
256
|
-
### 观测契约
|
|
257
|
-
|
|
258
|
-
宠物根节点上有三个属性,便于排查与自动化测试:
|
|
259
|
-
|
|
260
|
-
- `data-motion` — 当前动作组(待机为 `idle`)
|
|
261
|
-
- `data-gaze` — `center`(回默认位)/ `pointer`(跟随鼠标)
|
|
262
|
-
- `data-phase` — 最近一次会话相位
|
|
263
|
-
|
|
264
|
-
另外 `window.__dshLive2dPet` 暴露了动作控制器(`maskInfo()` 可查看点击轮廓、`playOnce()` / `playIdle()` 可手动驱动),方便在控制台排查。
|
|
265
|
-
|
|
266
|
-
### 关于 HitAreas
|
|
267
|
-
|
|
268
|
-
本模型**没有**声明 Cubism HitAreas,所以点击判定不依赖引擎的 `hitTest`,而是从渲染结果的 alpha 通道提取轮廓——因此任何模型都能用,无需作者额外导出命中区。
|
|
269
|
-
|
|
270
|
-
## 动作状态机
|
|
271
|
-
|
|
272
|
-
Live2D 的 `MotionManager` 有三处反直觉行为,直接裸调 `model.motion()` 会出现「点一下就一直循环播放」这类问题。插件用一个状态机统一接管动作生命周期:
|
|
273
|
-
|
|
274
|
-
| 引擎行为 | 后果 | 处理 |
|
|
275
|
-
|---|---|---|
|
|
276
|
-
| 同 group+index 正在播放时拒绝再次启动 | 连点没反应 / 只能播一次 | 每次启动前先 `stopAllMotions()` |
|
|
277
|
-
| `NORMAL` 优先级不能打断 `NORMAL` | 第一次动作后宠物「死」了,后续全被静默拒绝 | 互动与面板动作用 `FORCE`,待机用 `IDLE` |
|
|
278
|
-
| `motion()` 是**异步**的:先 `stopAllMotions()` 再加载入队,这中间 `MotionManager.update` 会看到 `playing && isFinished()` 并**误发 `motionFinish`** | 新动作刚开始就被判定「播完了」,瞬间弹回待机 | 忽略启动后 250ms 内到达的 `motionFinish` |
|
|
279
|
-
| 动作自带 `"Loop": true` 时永不结束,也就永不触发 `motionFinish` | 动作无限循环,回不到待机 | 宿主从 `motion3.json` 读出 `Duration`/`Loop`,按声明的时长定时收尾 |
|
|
280
|
-
|
|
281
|
-
因此状态机只做三件事:**常驻待机循环** → **播一次动作** → **自动回待机**,且每一步都可被打断。
|
|
282
|
-
|
|
283
|
-
宿主半区会把每个动作的 `duration`(毫秒)、`loop` 以及**该动作写了哪些参数**(来自模型自己的 `motion3.json`)随 catalog 下发,所以换任何模型都能自适应,不需要改插件代码。
|
|
284
|
-
|
|
285
|
-
### 动作语义(v1.2.2)
|
|
286
|
-
|
|
287
|
-
引擎还有一条更隐蔽的行为,是「吹泡泡吹完嘴不还原」的根因:
|
|
288
|
-
|
|
289
|
-
> **动作结束后,它写过的参数没有任何人负责还原。** 引擎在动作播放期间往模型参数里写值,停下就只是「不写了」——参数留在最后一帧的值上。平时看不出问题,是因为待机循环恰好也在驱动这些参数;而本模型的动作专属参数(`chuipaopao*`、`phone*`、`pengshui`…)**待机完全不碰**,于是动作一停,最后的嘴形就永久留在脸上。
|
|
290
|
-
|
|
291
|
-
插件现在的做法:动作启动前把它会写的参数**快照**下来,回待机时**还原**。参数名单由宿主从 `motion3.json` 的 `Curves` 里读出并下发。
|
|
292
|
-
|
|
293
|
-
这条「还原」还藏过两个更深的坑,都在这条缝上:
|
|
294
|
-
|
|
295
|
-
1. **引擎的一帧是 `saveParameters()` → `update()` → `loadParameters()`,`loadParameters()` 是最后一个。**
|
|
296
|
-
我们的图层(表情 / 嘴 / 眨眼 / 扫动画 / 动作还原)写在 `saveParameters()` 之后,`update()` 把当时的值烘进模型画出去,
|
|
297
|
-
然后帧尾的 `loadParameters()` 把引擎自己的基线整片盖回来。所以:
|
|
298
|
-
- 还原**不能**「把快照写回参数」——那是帧外写,会被帧尾的 `loadParameters()` 抹掉,看起来就是「切回无也切不回去」。
|
|
299
|
-
它现在是一层**每帧覆盖**。
|
|
300
|
-
- **帧外读参数永远拿到"图层之前"的值**:动作停了它还是 1、表情明明生效却是 0。
|
|
301
|
-
快照若从那里取,第二次吹泡泡糖就会忠实还原成「鼓着的嘴」。快照现在读**还原缝上的值**(还装着就取还装的值,否则取引擎的值)。
|
|
302
|
-
2. **要断言"画面里是什么",只能读 `window.__dshLive2dPet.drawn(id)`**(控制器在钩子里存下的、这一帧真正画出去的值),
|
|
303
|
-
或者自己包一层 `core.update`。测试里读帧外的 `_model.parameters.values` 是量错了地方——这个错让同一个 bug 骗过两次。
|
|
304
|
-
|
|
305
|
-
另外全部动作现在都以 `loop: false` 启动。引擎的合并方式是 `setLoop(调用方的 loop ?? 动作自带的 Meta.Loop)`,而本包**所有** `motion3.json` 都写着 `"Loop": true`,所以不显式传 `false` 的话动作永远不结束,也就永远摆不出「定格」姿势。
|
|
306
|
-
|
|
307
|
-
剩下三件事是模型作者才知道的意图,写在 `pet.json` 的 `live2d.motionOptions` 里:
|
|
308
|
-
|
|
309
|
-
| 声明 | 含义 | 解决的问题 |
|
|
310
|
-
|---|---|---|
|
|
311
|
-
| `{"hold": true}` | 动作播完**定格**在最后一帧;最多 `ACTION_HOLD_MAX_MS`(9s)后交还身体 | 掏出手机后手机能拿在手里看一会儿,但不会永远举着 |
|
|
312
|
-
| `{"fidget": false}` | 不参与随机摸鱼 | 把手交互类动作排除出摸鱼池 |
|
|
313
|
-
| `{"prepend": "OpenCase"}` | 先播前置动作,再播真正的动作 | 自拍的 `phone` 第一帧就是 1(作者假定手机已在手),不先掏手机就是在对着空气自拍 |
|
|
314
|
-
| `{"preset": {"jingyu": 1}}` | 动作本身没写、但这个动作需要被一起点亮的参数 | 鲸鱼喷水这个动作只写了 `pengshui`(碰水),真正负责「喷」的鲸鱼是另一个参数 `jingyu`,不点它看起来就是毫无反应 |
|
|
315
|
-
|
|
316
|
-
定格姿势**不算「忙」**(`isPlaying()` 返回 false):否则点一次掏出手机就会永久压住待机摸鱼和会话相位,宠物就此卡死。它只是「看起来不一样的待机」,任何新动作都能接管。
|
|
317
|
-
|
|
318
|
-
宠物根节点带 `data-motion` 属性(待机时为 `idle`),方便直接观察当前状态。
|
|
319
|
-
|
|
320
|
-
## 宠物契约
|
|
321
|
-
|
|
322
|
-
一个宠物目录长这样:
|
|
145
|
+
宠物放在 `%DSH_HOME%\pets\<id>\`,最小结构:
|
|
323
146
|
|
|
324
147
|
```
|
|
325
148
|
%DSH_HOME%\pets\<id>\
|
|
@@ -332,43 +155,47 @@ Live2D 的 `MotionManager` 有三处反直觉行为,直接裸调 `model.motion
|
|
|
332
155
|
catalog.json # 可选:动作/表情的中文名与分类
|
|
333
156
|
```
|
|
334
157
|
|
|
335
|
-
`pet.json`:
|
|
336
|
-
|
|
337
158
|
```jsonc
|
|
338
159
|
{
|
|
339
160
|
"petManifestVersion": 2,
|
|
340
161
|
"id": "ds-whale-girl",
|
|
341
162
|
"displayName": "DS鲸鱼娘",
|
|
342
163
|
"renderer": "live2d",
|
|
343
|
-
"license": "...",
|
|
164
|
+
"license": "...",
|
|
344
165
|
"live2d": {
|
|
345
166
|
"model": "c_0120.model3.json", // 相对于本目录
|
|
346
167
|
"scale": 1, // 在自适应缩放上乘算
|
|
347
|
-
"translate": { "x": 0, "y": 0 },
|
|
348
|
-
|
|
349
|
-
"
|
|
350
|
-
|
|
168
|
+
"translate": { "x": 0, "y": 0 },
|
|
169
|
+
// 槽位:一个槽位 = 一个互斥组,`none` 是"不选"
|
|
170
|
+
"expressionSlots": [
|
|
171
|
+
{ "id": "eyes", "label": "眼部", "none": "默认",
|
|
172
|
+
"options": [{ "label": "爱心眼", "expressions": ["爱心眼"], "pairs": { "heart": "冒爱心" } }] }
|
|
173
|
+
],
|
|
174
|
+
// 会话相位 -> 槽位 -> 选项(读成"每个槽位一条、权重 1"的池子)
|
|
175
|
+
"looksByPhase": { "done": { "whale": "头顶鲸", "mood": "开心兴奋" } },
|
|
176
|
+
// 摸鱼默认盯哪些槽位(用户还能再加;不写就是"手/情绪/脸红/嘴/眼"那五个兜底)
|
|
177
|
+
"fidgetSlots": ["rhand", "lhand", "mood", "cheek", "mouth", "eyes", "selfie"],
|
|
178
|
+
// 可选;模型自己表达不了的「作者意图」
|
|
351
179
|
"motionOptions": {
|
|
352
180
|
"OpenCase": { "hold": true },
|
|
353
181
|
"Selfie": { "prepend": "OpenCase" },
|
|
354
182
|
"SprayWater": { "preset": { "jingyu": 1 } }
|
|
355
|
-
}
|
|
183
|
+
},
|
|
184
|
+
"motionGuards": { "SprayWater": { "whale": ["头顶鲸", "放桌上"] } }
|
|
356
185
|
}
|
|
357
186
|
}
|
|
358
187
|
```
|
|
359
188
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
`
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
}
|
|
369
|
-
```
|
|
189
|
+
- **动作和表情列表以 `.model3.json` 里声明的为准**,插件启动时从模型读出 —— 换模型 / 改模型
|
|
190
|
+
文件立刻生效,不用改插件代码。
|
|
191
|
+
- `motionOptions`:`hold`(停在最后一帧)/ `persist`(定格且不自动放手)/ `prepend`(先播前置
|
|
192
|
+
动作)/ `preset`(开播前把某些参数写死)。不写就是"播一次然后回待机"。
|
|
193
|
+
- `motionGuards`:动作的前提,形如"槽位 = 标签白名单"。**白名单形式没法自动满足,只能拦**
|
|
194
|
+
(喷水要鲸鱼,而鲸鱼有"头顶"和"放桌上"两种)——所以"能自动补上"的前提请写在选项的
|
|
195
|
+
`requires` 上(形如"槽位 = 某一个标签")。
|
|
196
|
+
- 路径片段只允许 `[A-Za-z0-9._-]`,中文文件名会让整个宠物加载失败。
|
|
370
197
|
|
|
371
|
-
|
|
198
|
+
动作与表情的**语义**(定格 / 前置动作 / 参数还原 / 表情叠加)见 skill `cubism-engine`。
|
|
372
199
|
|
|
373
200
|
## 宿主 HTTP 接口
|
|
374
201
|
|
|
@@ -381,7 +208,7 @@ Live2D 的 `MotionManager` 有三处反直觉行为,直接裸调 `model.motion
|
|
|
381
208
|
|
|
382
209
|
API 与资产路由默认只答本机回环请求。
|
|
383
210
|
|
|
384
|
-
##
|
|
211
|
+
## 架构与二次开发
|
|
385
212
|
|
|
386
213
|
```
|
|
387
214
|
dsh-live2d-pet/
|
|
@@ -394,22 +221,32 @@ dsh-live2d-pet/
|
|
|
394
221
|
src/vendor-entry.ts vendor 分包入口(npm run build:vendor 重新生成)
|
|
395
222
|
```
|
|
396
223
|
|
|
397
|
-
Vendor
|
|
398
|
-
|
|
399
|
-
## 二次开发
|
|
224
|
+
Vendor 分包**懒加载**:只有真正挂载宠物时才注入,页面首屏不为它买单。
|
|
400
225
|
|
|
401
|
-
```
|
|
402
|
-
npm install
|
|
403
|
-
npm run build:vendor
|
|
226
|
+
```bash
|
|
227
|
+
npm install # pixi.js / engine / esbuild
|
|
228
|
+
npm run build:vendor # 改动 src/vendor-entry.ts 或升级依赖时
|
|
404
229
|
```
|
|
405
230
|
|
|
406
|
-
改完 `lib/client.js`
|
|
231
|
+
改完 `lib/client.js` **重启 `dsh web`**(bundle 不做热重载)。回归测试在仓库的
|
|
232
|
+
`tools/browser-test/`:无头 Edge + CDP,在真实 WebGL 里跑 16 个 driver 的完整契约。
|
|
407
233
|
|
|
408
|
-
回归测试在仓库的 `tools/browser-test/`:无头 Edge + CDP,在真实 WebGL 里跑完整契约。
|
|
409
234
|
```bash
|
|
410
235
|
cd ../../tools/browser-test && npm install && npm run suite
|
|
411
236
|
```
|
|
412
237
|
|
|
238
|
+
> **开发记录不在这个 README 里**:踩坑、测量陷阱、帧序、验证写法按主题放在仓库的
|
|
239
|
+
> `.dsh/skills/` 下(`cubism-engine` / `client-state` / `verification-signals` /
|
|
240
|
+
> `browser-cdp` / `docs-and-workflow`),入口见仓库根的 `AGENTS.md`。
|
|
241
|
+
> 用户可见的变化写 [CHANGELOG.md](CHANGELOG.md)。
|
|
242
|
+
|
|
243
|
+
### 排查
|
|
244
|
+
|
|
245
|
+
宠物根节点上挂着诊断读口(`window.__dshLive2dPet`),常用几个:`expressions()`(当前钉住的
|
|
246
|
+
表情)、`slotSelections()`(各槽位选了什么)、`drawn(id)`(这一帧真正写进模型的参数值)、
|
|
247
|
+
`fidgetTally()`(摸鱼抽签统计)、`effectiveRelations()` / `settingsOverrides()`(有效关系与
|
|
248
|
+
存档覆盖)、`ambientDebug()` / `keptPoseDebug()`(氛围回放与姿势保持的内部状态)。
|
|
249
|
+
|
|
413
250
|
## 许可
|
|
414
251
|
|
|
415
252
|
- 插件代码:MIT
|
|
@@ -420,6 +257,4 @@ cd ../../tools/browser-test && npm install && npm run suite
|
|
|
420
257
|
|
|
421
258
|
版权链:**上善无形**(鲸鱼娘角色原作,原创 OC「溟月」)→ **ZipZipPipe**(DeepSeek 女仆二创)
|
|
422
259
|
→ **氵六青**(本模型)。氵六青已授权本项目转载开源,但该授权**不解除基础版权**,
|
|
423
|
-
所以 **NC / SA
|
|
424
|
-
|
|
425
|
-
完整说明见 [`../NOTICE.md`](../NOTICE.md)。
|
|
260
|
+
所以 **NC / SA 依然有效**;商业使用需分别取得三人授权。完整说明见 [`../NOTICE.md`](../NOTICE.md)。
|