# 更新日志

## 3.3.2（2026-10-16）

### 修：打开右键面板后，面板和宠物持续闪烁（issue #3）

显示层状态的去重拿引用去比 `binary` / `download`，而它们每次轮询都是新解出来的对象 ——
去重失效，订阅它的界面**每秒全部重渲染一次**。面板关着时也在抖，只是看不出来。
现在按内容比，状态没变就不通知。

### 修：把显示层改成「桌面」不再让宿主跟着退出

拉起桌面端失败（缺执行位、文件不在、格式不对）走的是异步事件而不是抛异常，原来没有监听者
⇒ Node 把它当未捕获异常 ⇒ **整个 Harness 进程当场退出**。现在失败只记一条日志、下一拍再试。

### 修：换了端口之后，桌面上还是空的

壳的上游地址在拉起那一刻就定死了。原来插件先按兜底端口（3080）把壳拉起来，等真实地址报上来
却不会重拉 —— 那一只永远挂在错端口上，表现是"桌面上什么都没有"。现在地址真变了就收掉重拉
（只认自己拉的那只；用户手动双击开的不碰；`127.0.0.1` 与 `localhost` 这类同义写法不触发）。

### 修：macOS 装下来的桌面端起不来（发布物没有执行位）

darwin 子包是在 Windows 本机打的，而 Windows 上的打包工具不记 Unix 权限位 ⇒ 装到 mac 上
`bin/dsh-pet-live2d-desktop` 是 `644` ⇒ 拉起时 `EACCES`（正是上面那条崩溃的近因）。
现在插件在解析二进制时就地补一次执行位（**已经发出去的 3.3.1 也能被救回来**），打包工具也
改成按目标平台判断、在 Windows 上打非 Windows 子包时明确告警。

## 3.3.1（2026-10-09）

### 修：插件卡片上的图标换成她本人

3.3.0 那张卡片上的图标是手画的抽象图形，不好看。现在直接用桌面端托盘那套美术：从待机画面
裁一版脸的特写（256×256，带透明底），「插件」页与「设置 → 内置插件」两处都换成它。

图标与模型同属 **CC BY-NC-SA 4.0**（由截屏裁切缩放而来，属于改编），署名见
[`../NOTICE.md`](../NOTICE.md)。

## 3.3.0（2026-10-09）

### 新：官方「插件」页里有自己的图标和常用设置

侧栏「插件」页上这张卡片以前是默认插画加包名，现在有自己的图标、中文与英文标题/描述
（「设置 → 内置插件」那份列表同样受益）。点进详情页多了一节配置区：常用开关（气泡 /
提示音 / 摸头 / 摸尾巴 / 转圈 / 记住装扮）、四根手感滑杆（注视满偏、收回倍数、摸鱼静置、
提示音音量）和「显示位置」。它写的是与 **设置 → 桌宠** 同一份配置，改哪边都一样；
相位台词、池子编辑器、装扮与文案仍在设置页里改。

## 3.2.0（2026-10-09）

### 新：会话状态提示音

「桌宠 → 会话提示音」单独控制总开关和音量；「会话相位」改为两列概览、按需展开编辑，不再把八个动作池同时铺满页面。每个相位可上传 MP3 / WAV / OGG 提示音并随宠物分别保存；移除上传即恢复宠物默认音符，静音和总开关仍独立生效。

### 新：提示音可以「试听」

设置页每个相位多了「试听」：点一下直接听这个相位**会响的那段声音**（有上传听上传，否则听宠物/内置音符），不用等会话真的跑到那个相位。它**故意不受**总开关与单相位静音影响 —— 默认总开关就是关的，跟着它走等于"点了没反应"；音量用当前提示音音量，音量为 0 时按 0.35 试听。**拖音量滑杆也会即时试听**（停手才响一次，用最终音量；音量 0 就是没声，不给兜底）。

### 改：上传音频的判据两端统一（并放行"长度未知"的 WAV）

以前网页端只认开头几个字节、桌面端会完整解析容器，于是同一个文件可能"网页端传得进、桌面端读不出来"——相位的「已上传」在桌面端凭空消失，而且两边都不报错。现在两份宿主共用一套判据，由 `tools/sound-vectors.json` 的合同向量在两边测试里钉住；顺带放行长度字段写 `0` / `0xFFFFFFFF` 的流式 WAV（这类文件浏览器本来就能播，以前会被桌面端错杀）。

## 3.1.3（2026-10-01）

### 修：设置里选「桌面」后，桌面上什么都没有（手动双击同一个 exe 却正常）

插件拉起的桌面端会以「挂载模式」去 DSH 那里取宠物数据，而它拿到的地址一直退回默认端口
3080 —— 在官方桌面客户端下那个端口没人听，于是桌面上空的。现在改成同时看请求的 `Host`
头（官方客户端的页面地址是 `dsh-app://`，这种外壳下只有 `Host` 是真实地址）。

### 修：官方桌面客户端里贴图全 404、宠物加载失败（issue #1）

官方客户端的页面地址是 `dsh-app://`，而引擎在 Worker 里按相对地址取贴图时，地址里的
`/api` 会被当成主机名 ⇒ 贴图全部 404。现在交给引擎的地址一律拼成保住主机名的绝对地址。

### 修：某些桌面外壳里宠物和设置页一起白掉

自定义协议或隐私设置下，**一访问 `localStorage` 就会抛异常**，而插件原来到处直接用它。
现在全部走带兜底的封装：拿不到就退回内存存储 —— 本次会话内位置、大小、装扮照常工作，
只是关掉窗口不留存。

### 改：macOS（arm64）的桌面端进了主包声明

装插件时会自动带上 mac 桌面端（在这之前只能从 Release 下 zip）。

## 3.1.2（2026-09-30）

### 修：官方桌面客户端里"加载失败"（issue #1）

`dsh-app://` 外壳下引擎取贴图的地址会丢主机名 ⇒ 404。现已改为传给引擎的地址保住主机名。

## 3.1.1（2026-09-30）

### 修：某些桌面外壳里宠物与设置页一起白掉

这些外壳里访问 `localStorage` 会抛异常；现已加兜底，拿不到就用内存存储。


## 3.1.0（2026-09-29）

这一轮的主线是**桌面端**：托盘的"没有她"查到底了 —— 根因是本项目自己给产物贴错了
完整性标签（详见下面「完整性标签」那一节）；同时修好了设置两边不同步、插件"以为已经有一只"、
独立模式页面把自己藏起来，并加上 macOS（arm64）支持。

### 修：桌面端的设置与 DSH 里的不一致（同一类"两份宿主分叉"）

设置原来只走 `localStorage`（浏览器按 origin 隔离 ✗），所以共享存档必须走
`%DSH_HOME%\pet-settings.json` + `GET|POST /api/live2d-pet/settings` —— 而**桌面端的 Rust
宿主没有实现这条路由**（JS 侧 `lib/settings.js` 的注释里甚至写着"Rust 侧的对应实现见
`host/settings.rs`"，而那个文件当时并不存在）。页面取不到就只能退回它自己那份 localStorage，
于是两个窗口各显示各的。

现在补上 `host/settings.rs`（与 `lib/settings.js` 逐字对应：三个键 `tuning`/`overrides`/
`outfit`、合并写、**空写不推进 `rev`**（防两端互推）、坏数据只取形状对的部分）与那条路由。
实测：GET 空存档 → POST `tuning`（rev 1）→ POST `outfit`（rev 2 且 tuning 不被擦掉）→ 落盘一致。

### 修：插件拉起的那只死了以后，插件一直以为"已经有一只了"

`lib/display.js` 只在 `stop()` 里清 `child`/`childPid`，**子进程自己退出（被杀 / 崩了）时不清**
—— 于是 `reconcile()` 永远认为"已经有一只了"，既不重拉，设置里还显示"桌面已接管"，而桌面上
什么都没有。现在给子进程挂了 `exit` 处理器，并在 `start()` 里加了一条自愈（记着的 pid 已经不在、
心跳也不新鲜 ⇒ 忘掉它再拉）。（用户 2026-09 报的"设置说已接管但看不到宠物"有一半是这个。）

### 新：托盘的**自证与诊断**（写给"托盘里没有她"这类问题）

`TrayIconBuilder::build()` 返回 Ok **不代表图标进了通知区域** —— `tray-icon` 在
`Shell_NotifyIcon(NIM_ADD)` 失败时是**静默忽略**的（源码注释说"等 Explorer 发 TaskbarCreated
再重注册"，而那个广播只有资源管理器重启时才有）。现在启动后会自证一次
（`TrayIcon::rect()` = `Shell_NotifyIconGetRect`）并写日志；拿不到矩形时还会**绕开库手工调一次**
`Shell_NotifyIcon`（用她自己的窗口 + 系统图标），把返回值和 `GetLastError` 写进
`%DSH_HOME%\pet-desktop.log`。注意：**矩形拿不到不等于失败**（藏在溢出区里的图标也拿不到），
所以这条日志是"信息"而不是"失败"。

### 改：Windows 产物名改成 ASCII（`DSH-Pet.exe`）

与 Release 资产 / npm 子包那两份（本来都是 ASCII）保持一致，少一个变量。

### 修：手动启动的桌面端"设置里显示已接管，但看不到宠物"

**独立模式**（自己双击 exe / 不走插件）下，窗口起来了、canvas 也在画，但**页面把自己藏了**：
客户端的判据是"owner 不是我 ⇒ `visibility: hidden`"，而它读的是 `GET /api/live2d-pet/layer`
—— 桌面端的 Rust 宿主**没有这个路由**（它只有自己的 `/__desktop/owner`），页面取不到就保持
默认 `owner: "inline"` ⇒ 桌面壳那一份按规则让位 ⇒ 什么都看不到（托盘图标又默认被 Win11 收进
溢出区，所以连"她还在"都看不出来）。

挂载模式（插件从设置里拉起，`--attach`）不受影响：那条路把整个 API 转发给了 DSH 里的插件，
而插件那份 JS 宿主是有 `/layer` 的 —— 这就是"插件起的桌面是好的、手动双击看不到"的全部原因。

修法：Rust 宿主补上 **`GET|POST /api/live2d-pet/layer`**（与 JS 宿主同形：`owner` / `mode` /
`desktopRunning` / `binary` / `download`；POST 支持改「显示位置」），`/__desktop/owner` 保留
成同形的别名（已有驱动在读它）。

> 这条属于项目最老的那条纪律：**宿主半区有两份实现，路由形状必须一起改**。客户端某一版
> 把读口从 `/__desktop/owner` 改成了 `{API}/layer`，Rust 那边没跟上。

### 修：**本地构建**出来的 exe 双击起不来（完整性标签，2026-09 用户报的第三次）

症状：双击 exe 弹一个错误框，里面是 `0x800700AA 请求的资源在使用中`（WebView2），
同时 `%DSH_HOME%` 里既没有 `pet-desktop.log`、偏好也没被改写 —— 而在 DSH 设置里
让插件拉起的那只一切正常。

根因是 **Windows 的完整性标签（integrity level）**：**在 DSH 会话里写出来的文件会带
`Low` 标签**（沙箱给工作区新文件打的就是它），而本地构建的那个 exe 正好产在那儿。
被打上 Low 标签的 exe，双击起来后进程就是 Low 完整性 —— 按 no-write-up 规则，
**它写不进任何 Medium 对象**：`%DSH_HOME%`（`拒绝访问 (os error 5)`）与
`%LOCALAPPDATA%\<id>\EBWebView`（WebView2 建不起来）全都中招。
插件从 npm 下载的那份（普通进程写的、没有标签）因此完全正常 —— 同一个版本、同一个
二进制，只差一个标签。

修法（构建期，两道）：

- `tools/integrity.mjs`：检查产物的完整性标签，**带 Low/Medium 就摆成 `High`**（= 不设上限）
  并复查，摆不正直接让构建失败；
- 接进 `build-portable.mjs`（产物出库）与 `npm-prepare-subpackage.mjs`（发 npm 前），
  所以**发布物不可能再带压低进程的标签**。

⚠️ 这里有个**曾经的错误修法**：一开始是摆成 `Medium`，结果在"资源管理器跑在 High"的机器上
（UAC 关闭 ⇒ 整机 High）**连托盘都注册不上**（`Shell_NotifyIcon` 返回 FALSE、
`GetLastError=5`）—— 因为完整性标签是**上限**，Medium 会把进程压在 Medium，
它对 shell 的托盘消息被 UIPI 拦掉。**正确做法是没有标签，或者摆成 High。**

已经拿到带标签版本的用户：`icacls "<exe>" /setintegritylevel High` 一条命令即可修好
（或者重新下载一遍发布包）。

### 修：直接双击桌面端的 exe，她"没显示出来"（2026-09 用户报）

症状：双击 GitHub Release 里那个 exe，**什么都没发生**；但插件从设置里拉起来的那只一切正常。

根因不是 exe 坏了，是**显示层的偏好**：设置里选的是「页面内」时，桌面端按规则必须让位 ——
它起来、刷心跳、读到 `mode: "inline"`，然后**1 秒内把自己藏了**（进程其实活着，托盘图标也
在 Win11 的溢出区里）。而插件那条路会先把偏好写成「桌面」再拉起，所以它看着是好的。

现在：**手动启动（不是插件拉起的）遇到「页面内」偏好，就把它切成「桌面」** —— 双击这个动作
本身就是"我要她在桌面上"。写的是同一个共享文件，所以页面里那只立刻让位，仍然只有一只；
想改回来，去 DSH 设置里选「页面内」。

顺带把"什么都看不到"这件事本身修了（这才是真正难查的地方）：

- **启动日志**：`%DSH_HOME%\pet-desktop.log`（同时照旧打 stderr），记下**是哪个 exe**、
  显示层偏好、"显示 / 让位"的结论与原因；写不进 `%DSH_HOME%` 时**退到 exe 旁边**再写一份，
  两处都写不进去就把原因带进弹框（以前这种写失败是静默的，用户第二次报的就是它：
  弹框指的日志文件压根没生成）；
- **起不来会弹框**：GUI 程序没有控制台，以前 panic 是彻底静默的。现在 Windows 上会弹一个
  错误框，写明**程序路径** + 日志路径（macOS 上只有日志）。

### 修：已经有一只时，第二只不再报"起不来"

WebView2 的 user-data-dir 是**独占**的，第二个实例只会撞 `0x800700AA 请求的资源在使用中`，
而那个错误以前被 panic 成一个吓人的报错框。现在：启动时先看**心跳**（不是 pid —— 系统会重用
pid），已经有一只活着就**安静退出**并在手动双击时提示"她已经站在桌面上了（Win11 托盘图标
默认收在溢出区）"。

同时补了两条：

- **建窗失败会自动重试 3 次**（每次隔 1.5 秒）：上一只刚退出、目录还没放开是常见情形，
  等一下通常就好了，没必要让用户看报错；重试过程每次都写进日志。
- **插件收掉桌面端时连整棵进程树一起收**（Windows 上 `taskkill /T`）：`child.kill()` 只杀壳，
  WebView2 的 `msedgewebview2.exe` 子进程会留下来占着数据目录 —— 那正是"下一次启动撞
  0x800700AA"的常见来源。

### 新：macOS（Apple Silicon）的桌面端构建

以前桌面端只有 Windows。现在 macOS arm64 也能出产物了 —— 由 GitHub Actions 构建
（`.github/workflows/desktop-mac.yml`，`macos-14` runner），一次产出两样：

| 产物 | 给谁用 |
|---|---|
| `DSH桌宠.app`（zip） | 人：双击就站出来 |
| `dsh-pet-live2d-desktop`（tar.gz，裸二进制） | 插件：npm 平台子包 `dsh-pet-live2d-desktop-darwin-arm64` 要的那份 |

为此改的东西（都在桌面端那一侧）：macOS 上**读全局光标**改走 CoreGraphics（透明层"哪块
区域吃鼠标"的判定靠它，以前非 Windows 直接返回"读不到"，等于她永远点不到）；主目录按
平台取（macOS 是 `HOME`，以前只认 `USERPROFILE`，会退化成当前目录下的 `.dsh`）；macOS 用
`ActivationPolicy::Accessory` 藏进"不进程序坞"（Windows 的 `skip_taskbar` 在 mac 上没有
实现）；`.app` 里不再往包内写运行期数据（那会破坏代码签名）。

**没验过的地方，别当既成事实**：本项目所有驱动都跑在 Windows 上，macOS runner 又没有可
交互的窗口会话 —— "她能不能点、能不能穿透、跟不跟手"目前**只有代码层面的理由**，没有
实测。Intel Mac 与 Linux 都还没有构建。

### 注意：macOS 那份没有签名

第一次打开会被 Gatekeeper 拦（"无法验证开发者"）：右键→打开，或者
`xattr -dr com.apple.quarantine <路径>`。想双击就能开，需要 Apple Developer ID（$99/年）
+ 公证 —— 那时 CI 里能一起做，但证书得先有。

## 3.0.1

### 修：「自拍」右手不抬（3.0.0 引入）

3.0.0 那一版把"保姿势录像"里的一道豁免撤掉了，于是自拍时抬手的那条参数每帧又被压回 0，
**手一直垂着**。3.0.1 把它修回来，并按"动作需要就每帧写值"的正解加固：

自拍的抬手参数（`phone5`）在动作文件里的曲线是 **0 → 9.7 → 0**，也就是说光靠曲线抬不起来；
现在由动作自己**每帧按住**一个明确的值（写在引擎更新之后，不会再被曲线或回放层盖掉），
动作一结束就松开，不会留下永久钉住的状态。

判据不再只看"参数值在不在跑"（那个骗过我三次），而是**像素级核对画面**：自拍峰值那一帧
与"掏出手机定格"必须有实质差异 —— 手机从桌面举到了脸边。

## 3.0.0

**桌面端正式随插件一起发布。** 装完插件、在设置里选「桌面」，她就在桌面上——
不用去下别的任何东西（Windows x64 自带平台二进制；其它平台装插件时会被 npm 静默跳过，
插件照常工作，只是"桌面"那一项不可用）。

### 新：桌面端二进制随包分发

`dsh-pet-live2d-desktop-win32-x64` 作为**可选依赖**随主包安装。以前它是悬空的
（那个包在 npm 上根本不存在），所以桌面端只能靠设置里那个「下载桌面端」按钮现拉一份；
现在**装完就有**，下载那条路保留作为兜底。

### 修：她固定在**自己那块屏**上

用户报的："宠物应该是在固定位置，现在我鼠标在不同屏幕上宠物居然会跟随我的鼠标所在的屏幕。"

以前窗口每帧跟着光标换屏。现在：**启动时落到光标所在屏，之后由你决定**——
托盘菜单里有「屏幕」子菜单，列出每块屏的分辨率与位置，当前那块打点。
指针移到别的屏，她待在原地、只是视线贴到屏幕边缘。

### 修：只在焦点在她身上时才跟随鼠标

用户报的："只有焦点在宠物上才有跟随。" 桌面端原来靠 `pointermove`，而那个事件**只在指针
落在她窗口上时**才会送到页面 —— 你在别的程序里打字时，她的视线是死的。

现在由壳每 33ms 读一次**全局光标**喂给页面，所以**不管焦点在哪**她都跟着你。
跟随范围也改成一道**正圆**距离曲线（一个半径说了算）：范围内完全跟随，
超出后平滑收回、并回到她自己的正前方（原来是方形硬边界 + 到边缘就侧着一直盯着）。

### 修：托盘菜单四项都失效

用户报的四条，三条是同一个根因：**壳发的窗口事件根本到不了页面**。

| 你看到的 | 真相 |
|---|---|
| 「设置…」点了没效果 | Tauri 的 `window.emit()` **不发 DOM 事件**、只走 IPC，而页面里没有 `window.__TAURI__` |
| 「归位」点了没反应 | 同上 |
| 「藏起来」过一会儿又自己出来 | 没记住"是用户藏的"，显示循环一秒后又把她显示出来了 |
| 「屏幕」里选中的圆点不更新 | 菜单是启动时建好就定住的 |

现在给页面送动作走**命令队列**（HTTP，页面每 33ms 取一次），用户的显隐意图单独记着，
菜单每次状态变化重建。

### 修：右下角那片"空白却摸得到"的区域

用户报的："触摸区域在右下角有一大片奇怪的可触摸区域，那里明明什么都没有。"

尾巴那一层原来是**几何并集**（包围盒 128×162px，占舞台 43%×54%），而"算不算落在她身上"
用的是**栅格**（还带一格膨胀）。两套几何不一致，差集就是你摸得到、判定却不认的那片空白。
现在尾巴也按**同一套栅格**重采（实测那片区域 14 格 → **0** 格）。

### 修：摸头/摸尾巴不再打断你正在看的东西

用户报的："摸头和摸尾巴不要还原当前动作啊，随机到哪一个插槽里的就放哪一个插槽里的就行了。"

以前摸一下头，反应结束时会把身体交还待机 —— 你刚选好的「掏出手机」就没了。
现在反应优先走"**只换随机到的那个槽位**"，动作类的演完会**接回原来的动作**。
另外反应留下来的表情/情绪**过 12 秒自己收回默认**（摸三下头，最后一下之后才开始数）。

### 修：「自拍」右手不抬

用户报的："动作里的自拍动画会动，但装扮里的不会动。" 而且一直是**右手没抬起来**。

查下来是"保姿势录像"每帧把当前动作的曲线压住了：自拍的抬手曲线（`phone5`）在跑
（实测涨到 9.7），但「掏出手机」定格时录下的那一帧里它是 0，而那一帧每帧被写回去 ——
写在动作之后，曲线就白跑了。现在**当前动作自己写的参数不再被回放覆盖**。

### 修：重锤出击前先清掉冲突的表情

用户要求："重锤出击动作应该判断一下当前眼部是不是 晕晕/呆呆眼，情绪是不是 开心兴奋/闭眼口水，
如果是应该先把眼部或情绪还原为默认。"

挥锤是清醒有力的动作，而 `Hammer` 只写手臂参数、脸一概不管 —— 眼部停在「晕晕」、
情绪停在「闭眼口水」时挥锤，画面上就是"一个晕乎乎、闭着眼流口水的人在奋力挥锤"。
现在开演前只清**列出来的**那几个选项（你选的其它眼睛不会被这一锤抹掉），清单可在设置里改。

### 修：桌面端的设置与 DSH 里的设置不同步

用户报的："桌面的设置与 dsh 里的设置没有同步。"

根因是**两边根本不在同一个 localStorage 里**：桌面端页面是
`http://127.0.0.1:<随机端口>`，DSH 是 `http://127.0.0.1:3080`，浏览器按 origin 隔离。
现在三类**跨窗口该一致**的设置（手感滑杆 / 相位池子与开关 / 装扮）放在
`%DSH_HOME%\pet-settings.json`，两个界面都读写它，3 秒内互相跟上。
**位置与大小仍然各窗口各一份**（她贴屏幕右下角还是贴面板右下角，本来就是两件事）。

## 2.3.5

### 新：设置里可以选"她在页面内还是桌面上"

设置 → 桌宠 多了一张「显示位置」卡，三个选项：

| 选项 | 效果 |
|---|---|
| **自动**（默认） | 桌面端在跑就用桌面，否则留在 DSH 页面里 |
| **页面内** | 永远在 DSH 页面里（随 DSH 启停） |
| **桌面** | 永远在桌面上；没跑就**自动拉起**一个 |

**同一时刻只有一只。** 两边靠一个共用的小文件（`%DSH_HOME%\pet-desktop.json`）协调：
桌面端每秒刷一次心跳，页面侧每秒读一次，规则是"心跳新鲜且没选页面内 → 桌面端接管"，
否则页面里那只接管。桌面端退出时立刻清心跳，所以**页面里那只马上就回来**，不用等超时。

桌面端的二进制由**平台子包**随插件一起装（Windows x64 现在有；其它平台装插件时会被 npm
静默跳过，插件照常工作，只是设置里那一项不可用）。这一项不需要你手动装任何东西。

### 修：设置页整个打不开（2.3.5 之前那一版引入的）

症状很怪：**宠物一切正常，只有设置页打不开**。原因是新加的「显示位置」那张卡读了一个
**宠物组件内部的 ref**，而设置页那一节渲染在**宠物组件之外**（它挂在 DSH 设置页上），
于是渲染时直接 `ReferenceError`，整节设置崩掉。

现在显示层状态放在模块级的小 store 里，宠物与设置页都从那里读。
顺带给验证套件补了 `cdp-settings-render`：它**真的渲染一次设置正文**并断言没有未捕获异常
—— 以前 19 个 driver 只验"那一节注册上了"，这种崩法能一路绿灯。

## 2.3.4

### 修：摸尾巴"很难点到" —— 尾巴的判定区域和可点区域都收紧了

用户的原话："尾巴一直在摆动，摸尾巴的事件现在很难点到。"查下来是**两件事叠在一起**，
而且第二件才是主因。

**① 尾巴判定把 11 个"可换配件"也算成了尾巴。**

`hitsTail` 原来按名字挑部件（名字里带 `尾|鳍|翅|翼`），这只模型里有 **15 个部件 / 16 块**
drawable 命中 —— 但其中 11 块是**可选配件**（狐狸尾 / 猫尾 / 狼尾 / 天使翅膀 / 恶魔翅膀…），
几何**一直留在原地**、横跨从头顶到腰腹的整个角色。实测（24×24 网格）：

| | 修之前 | 修之后 |
|---|---|---|
| "算尾巴"的格子占角色 | 22.0% | 5.2% |
| 其中同时算头（会被摸头优先吃掉） | 86.6% | ~55% |
| **只算尾巴、真正可点的格子** | ~17 | ~59 |

于是你点在**看得见的尾鳍**上，拿到的是摸头反应（重锤出击）。

现在按**贴图**收窄：`texture_00.png` 上鲸鱼尾鳍只占 `x 3..444, y 953..1397` 那一块，
落在那里的正好是 `ArtMesh38/56/58/59/60` 五块（全属于部件 `尾巴(蒙皮)`）。UV 读不到时
**不做收窄**，退回旧行为 —— "更精确"不能变成"整个失效"。

**② 可点区域（轮廓快照）跟不上摆动。**

摸头/摸尾巴的判定每次点击都现读几何，所以"点的是不是尾巴"是准的；但**决定事件归谁**
的那层 `clip-path` 轮廓是**开机从画布抓一次**的静态快照。尾鳍一直在摆，摆出快照的那
一瞬间，点击**直接穿透到页面**（实测 `elementFromPoint` 返回 `HTML`，而判定说她是）。

现在尾巴有一块**跟着摆动实时更新**的可点矩形（每 ~120ms，模型空间 → 舞台按 y 轴方向
换算），并进 `[data-hit]` 的 `clip-path`；JS 侧的 `hitsMask` 用**同一套**（当前尾巴盒子
或静态快照，取或）—— 两边必须一致，否则就是"事件进得来、判定说不在她身上"这种点了没反应。
顺序也踩过坑：`hitsMask` 一度改成纯几何，结果"只有几何、没有像素"的点被算成在她身上，
而 DOM 并不覆盖它 —— `cdp-passthrough` 立刻红。

**顺带**：`onModel`（"算不算落在她身上"）以前只认静态快照，现在也认尾巴当前盒子。

### 诊断：一批新读口

- `lastPress()` —— **按住那一刻**路由用的三个答案（`onModel` / `onHead` / `onTail`）+ 时间戳。
  "点了没反应"要先分清是事件没进来、还是判定说不是她；从"她说了什么"倒推是分不出来的。
- `tailDebug()` —— 尾巴判定的收窄结果（收窄前后各是哪些、贴在哪块贴图上）与尾巴实时盒子。
- `hitsMaskStatic()` —— **只按静态快照**判定。要证明"这一点在快照外、却在宽限之内"，
  就得能把两层分开读。
- `uvRaw()` / `uvTables()` —— 贴图坐标表本身（哪块 drawable 画在贴图的哪一块）。
- `modelToStage()` —— 模型空间 → 舞台坐标，尾巴那块实时矩形靠它换算。

## 2.3.3

### 修：互动反应补上**内置默认值** —— 宠物没声明那三组也演得出来

「摸头 / 摸尾巴 / 转圈」演什么，之前**只**认宠物 `pet.json` 里的 `patReactions` /
`tailReactions` / `spinReactions`，插件代码里一个默认值都没有。于是**自己做的宠物**
（最小 `pet.json` 里根本没有这三个键）上：

| 互动 | 以前 | 现在（没声明时） |
|---|---|---|
| 摸头 | 只有台词，**不演重锤出击** | 重锤出击 / 问号 / 星星眼 随机一个 |
| 摸尾巴 | 只有台词 | 吐魂 / 问号 |
| 转圈转晕 | 演不出来 | 晕晕 |

兜底顺序和台词、摸鱼池是同一套：**用户改过的 <- 宠物声明的 <- 内置默认**。
宠物自己声明了就一直用宠物那份（这只鲸鱼娘一字未变），只是"没写"不再等于"没反应"。

设置页「互动」卡里那三组候选现在显示的就是**有效那一组**（和运行时会演的完全一致），
行尾会标明来历：`（宠物默认）` / `（内置默认）` / `（已改过）` —— 看到的是内置值时
你就知道"改宠物也能改这一组"。

### 修：随包宠物的默认值**终于能到老用户手里**（原来装过一次就再也不会更新）

`installBundledPets()` 以前是"目标目录存在就跳过"。插件目录随 `dsh plugin add` / npm
更新，用户的宠物目录 `%DSH_HOME%\pets\<id>\` **不会** —— 于是宠物**自己的默认值永远停在
装它的那一天**：2.3.0 往 `pet.json` 里加的那批（台词 / 互动反应候选 / 摸鱼槽位 /
`asking`·`helper`·`queued` 三个相位、自拍等三个槽位）一个都没到过桌面。

症状和你这次报的一模一样，而且**看起来完全像插件的 bug**：互动只有台词不演反应、
相位台词一句不弹、装扮里少三个槽位、`会话相位` 列表里少三个状态。

现在按**内容**判定，三条：

| 宠物目录里那份 | 行为 |
|---|---|
| 不存在 | 装一份，并把版本与内容指纹记进 `pets/.synced.json` |
| 就是我们装下去的那一份（指纹逐字节相同） | 整份更新到随包版本 |
| 你自己改过（指纹对不上） | **一个字都不碰**（日志里会说一句"跳过更新"） |

冷启动（同步记录出现之前装的副本，也就是所有老装机）靠一张**历史内容指纹表**认人：
与历史上任何一次随包分发逐字节相同才升级 —— 改过名字、槽位、任何一个字，哈希就不同，
所以"用户自己改过的宠物永远优先"这条没有被削弱。

升级会在宿主日志里说清楚：

```
[live2d-pet] ds-whale-girl：宠物默认值更新到 1.1.0（原先 1.0.1）
```

> 顺带把随包宠物的版本抬到 **1.1.0**。升级判定只看内容指纹、不看版本号 ——
> 同一个版本号下补默认值也能发出去（这次就是）。

### 修：互动候选「改不动」的隐患

`PHASE_OVERRIDES`（设置覆盖的存放处）初始化时**漏了 `interactions` 这个键**，而
`applyOverride()` 是直接往这个对象上写的 —— 也就是说第一次点候选时覆盖会被静默丢掉。
读的地方都写了 `?.`，所以它一直"读得出来、写不进去"，只有正面写它的设置界面会踩到。

现在五个键（`phases` / `fidget` / `relations` / `interactions` / `lines`）都在初始化时给出来，
并加了一条断言盯着"点一下候选 → 覆盖真的落盘"。

### 诊断：新增 `reactionDiagnostics()`

三组候选各自的**来历**（`user` / `pet` / `builtin` / `none`）、内置默认值、以及最终生效值。
"点了她没反应"这类问题一眼就能分出是"用户覆盖成了空数组"、"宠物没声明"还是"连兜底都没了"
—— 只看 `effectiveReactions()` 是分不出来的。

## 2.3.2

### 修：摸尾巴又摸不到了（这次是真的）

用户报的："现在又摸不到尾巴了。"（还发了一张圈出尾巴位置的截图，帮了大忙 —— 尾巴是
**右侧那只鲸鱼尾鳍**，不是翅膀。）

**真正的根因**：判定每块 drawable 时用的是**映射时算好并存下来的下标**（`entry.index`），
而不是**调用时按 id 现查的下标**。这两者只要有一处对不上，取到的顶点就是别人的 ——
表现是"每块单独测都能命中，聚合起来一个都不中"。

单块测（`drawableProbe`）之所以一直正常，正是因为它**每次都按 id 现查下标**。
现在聚合路径也这么做了：`hitsPartsGeometry` 在循环里用 `im.getDrawableIndex(entry.id)`
重新解一次，解不出来才退回缓存值。

修好后同一网格上的读数：`hitsTail` 从 **0 → 509** 命中（48×48），头部 1113、重叠 689
—— 重叠仍然存在（翅膀的几何本来就伸到头顶上方），所以**摸头优先于摸尾巴**那条继续保留：
点头部给摸头，尾巴自己的区域照样摸得到。

### 一段该记下来的教训：坏掉的诊断比没有诊断更糟

这轮我查了很久，很大一部分时间是被**自己写的诊断函数**骗了：`partHitCounts` 里调了
`api.drawableProbe(...)`，而 `api` 是控制器**返回出去**的对象，控制器内部根本看不到它
—— 每次调用都抛 `api is not defined`，被外面的 try/catch 吞掉，于是 `hitsAll`/`hitsVisible`
**恒为 0**。外面看到的却是"这个部件完全没有几何"，一条**看起来像产品坏了**的读数。

代价：我据此排除了三个正确假设（透明度过滤、下标空间、包围盒预筛），还改坏过一次
（把顶点整条换成 core 层，连头部判定都变成 0 命中，已还原）。

教训：**诊断函数自己也要先自证**（拿一个已知能命中的样本去测它），读数与事实矛盾时
先怀疑测量工具，再怀疑被测对象。

## 2.3.1

### 修：摸头会出摸尾巴的效果

用户报的："现在摸头也是出现的摸尾巴的效果，而且这是 live2d 会动的，这个判断网格似乎是静态的。"

两个原因叠在一起：

1. **这只宠物的尾巴/翅膀是三选一的可选配件**（狐狸尾 / 猫尾 / 狼尾 / 天使翅膀…），
   同一时刻只有一个显形，其余**几何还留在原地** → 判定区域会重叠。实测 1112 个头部
   命中点里有 **697 个同时命中尾巴**。
2. 点击路由原来**先判尾巴、再判头** → 重叠区的点击一律算摸尾巴。

现在**摸头优先于摸尾巴**：点头部一定给摸头的台词/反应，只有"没命中头部"才看尾巴。

顺带把一条**太弱的测试**补强：原来只断言"存在只命中尾巴的点"（在 697 点重叠下依然是绿的），
现在断的是**路由**（点头部给的必须是摸头的台词）—— 这才是用户实际遇到的问题。

### 关于"判断网格似乎是静态的"

查证结果（都有读数支撑）：

- **头部判定是跟着动的**：同一网格隔 2.5 秒重采，命中格子会变（模型呼吸/摆动）。
  每次点击都是现读**当前帧**的顶点，不是缓存的网格。
- **尾巴/翅膀的几何在没戴配件时是退化的**（缩成一点 → 被面积下限排掉），所以
  "摸尾巴"在没有尾巴的造型下**本来就该没反应**——你之前能看到反应，是模型当时正戴着
  翅膀/尾巴。测试里现在有一条**一致性断言**把这件事写死：有几何就该有命中，
  几何全退化就该一个都不中。
- 真正"静态"的是**点击穿透用的那层粗网格遮罩**（剪影），它按定时重建、粒度也粗 ——
  它只决定"这一下算不算落在角色身上"，不参与摸头/摸尾巴的判定。

> 试过用透明度过滤隐藏配件，结果**把真尾巴一起滤掉了**（`visibleDrawables: 4/11`）：
> 隐藏是靠缩放、而 `opacities` 表在渲染期未必是最终值，拿它当闸门会误杀正在显形的那一个。
> 所以放弃了这条思路，改用"路由优先级"解决重叠 —— 保留的诊断读口
> `partsDebug()` / `partHitCounts()` 仍会报"过滤前 vs 过滤后"，方便下次判断。

## 2.3.0

### 摸头判定改成按模型自己的几何（不再是手绘方框）

用户："看看摸头的区域能否精准匹配头部的模型而不是手绘区域。" 查下来比想象的糟：
老实现用英文正则 `face|eye|mouth|nose|brow|cheek|head` 去猜哪些 drawable 是头，
而这只模型的 drawable 叫 `Part46` / `lianhong` / `Face_line` 这种 —— **一个都匹配不上**，
判定静默退化成"点哪都算头"，摸哪都在撒娇。

现在的路是三段，全用模型自己的数据，没有猜的：

1. **cdi3 的部件名**：作者给每个部件起了中文名（`Part46` = 脸蛋、`Part57/58` = 眼睛、
   `Part14` = 头发、`Part45/68` = 耳朵…），宿主按名字挑出头部部件（这只宠物 21 个）；
2. **部件 → drawable**：cdi3 的 `Parts` 是**部件** id，而 `getDrawableIndex()` 认的是
   **drawable** id —— 两个命名空间不同名（实测 21 个部件一个都解不出来）。引擎原始表里有
   `drawables.parentPartIndices` + `parts.ids`，两边一接就得到 96 个头部 drawable；
3. **点在三角形内**：每次点击现读那批 drawable 的顶点与三角形索引（顶点是**当前帧变形
   后**的，头歪着、身体摆着也准），先做包围盒排除再逐三角形判定。

同一个网格上，新判定和旧方框**有 325 个格子不同**：新的是圆顶、带耳朵尖、下巴收窄的
头部轮廓，旧的是个完美矩形。

> 踩到的坑：顶点在**包装层**（`internalModel.getDrawableVertices`），三角形索引在
> **core 层**（`internalModel.coreModel.getDrawableVertexIndices`）—— 只查一个就会
> 静默 null、悄悄退回方框。所以测试里每一段**单独断言**（部件数 / drawable 下标数 /
> 顶点可读 / 与旧规则不同 / 自身包围盒四角不全中），而不是只验最后那个 true/false。

### 新互动：摸尾巴、鼠标绕圈转晕

- **摸尾巴**：同一套几何判定，部件换成 cdi3 里命名为 尾/鳍/翅/翼 的那 15 个
  （狐狸尾 / 猫尾 / 狼尾 / 大翅膀…都是可换配件）。默认反应：吐魂 / 问号。
- **转圈转晕**：鼠标绕着角色转够圈数（默认 2 圈 / 1.6 秒内）就演「晕晕」。看的是
  **累计转角**而不是"位置绕了几圈" —— 前者对半径不敏感，贴着角色转小圈和远远转大圈都算。
- 三个互动（摸头 / 摸尾巴 / 转圈）各自可开关，反应候选在设置里点选，
  候选就是**宠物自己的动作中文名与表情名**（不用记 id）。

### 气泡：会话相位会说一句话，而且全部可配

- 会话相位现在会弹一句台词（思考中 / 用工具 / 等你批准 / 等你回答 / 叫了帮手 / 消息排队 /
  完成 / 出错），来源还是 DSH 的真实事件。
- **所有气泡文本都能在设置里改**（问候 / 点击 / 摸头 / 摸尾巴 / 转晕 / 归位 / 加载失败 +
  每个相位）。一组用 `|` 分隔多个变体、随机挑一句；**留空 = 用宠物默认**
  （`pet.json` 的 `live2d.lines`），所以"看一眼"不会把默认改掉。
- **位置可配**：左右 / 上下偏移两个滑杆，外加"一句话停留多久"。
- **总开关**：关掉之后任何台词都不弹。

### 顺手修掉两个潜伏的问题

- **退化三角形**：`pointInTriangle` 对三个顶点几乎重合（面积 0）的三角形会返回 true ——
  而隐藏的配件（缩放成一点）正好是这种，会让"每个点都算命中"。已加面积下限。
- **转圈时间窗**：第一版写成 `now - 上次触发 > 窗口`，那个值一开始恒大于窗口，于是
  每次移动都把累计清零，永远攒不够圈数（诊断读口里 `total` 一直停在 0.07）。
  现在窗口从**这轮累计开始**算起。

## 2.2.0

### 多接三个会话状态（对着 DSH 0.1.7 的事件表挑的）

0.1.7 里能订阅的事件有 100 个，但只有一部分能翻译成"宠物该演什么"。挑的标准是
**它填的是不是一个真实的空档**：

| 新相位 | 来自 | 以前的样子 |
|---|---|---|
| `asking` | `user-questions/request` | 它问你问题、等你回答的整段时间里，宠物还在演"干活" —— 最大的一个空档 |
| `helper` | `subagent/start` / `subagent/end` | 子代理是**长活**（几分钟），和一次普通工具调用混在一起看不出区别 |
| `queued` | `agent/inbox/inserted` | 你发的话在它忙的时候排队，宠物毫无反应 |

`asking` 挂的是 waterfall：链一直挂到你答完才 resume，所以这个相位**正好**等于"卡在等你"
的那一段，不用自己计时。`queued` 是短促反应（1.6 秒），回落到**排队前那个相位**而不是
一律回 idle —— 否则一个正在跑工具的回合看起来像停了。

**没接的**（评估过，理由写在 `lib/index.js` 的注释里）：`fs/write-intent` / `edit-intent`
（tool 相位已经演「写本本」了，再拆一个 editing 和它重复）、`workflow/*`（工作流本身就是
工具调用，已被 tool 覆盖）、`agent/status`（只有 idle / running 两个值，给不出更细的）。

## 2.1.0

### 会话相位改成真正的"接管"

用户报的："我在爱心眼冒爱心掏出手机自拍的时候进入会话状态，右手会停在手机状态且一直冒爱心。"

原来相位**只接管它点名的槽位**，其余槽位继续用你自己的选择 —— 于是那套"手机 + 爱心"
在整场会话里一直挂着。现在相位一开始，宠物换相位那一套样子：

- 相位点名的槽位用它抽到的；
- 相位**没点名**的槽位**一律让位**（收回）；挂在槽位上的定格动作（掏出手机）也跟着
  交还身体，手放下来 —— 手机停着不放是有两个原因叠在一起：槽位没让位，而且
  "替别的槽位保姿势"那份名单相位走不到，一直每帧把手机姿势写回去；
- **装扮槽永远归你**（眼镜 / 发饰 / 魔爪 / 巴菲 / 桌布 / 手机换色），相位不碰；
- 会话进行中手点某个槽位会**临时盖过**相位的那一格（以前点了没反应，因为相位压着它）；
- 会话结束后你原来的样子回来 —— 让位不是删除。

### 右键面板的「装扮」跟着相位走

用户报的："会话的状态没有在右键菜单的装扮里同步 button。"

面板原来读的是**你自己的选择**，相位接管后画面上是相位的样子、面板还显示你上次手选的。
现在两边读同一个"此刻显示什么"（`effectiveSlotChoice`）：相位点名用相位的，会话中没点名
的整格空着，装扮槽永远显示你自己的。

## 2.0.2

**只改文档**（宠物自带的那份 README，`pets/ds-whale-girl/README.md`）。它落后得比主 README
还多，用户指出"两个 readme 哦"之后一起收干净：

- 「Cubism Core 需要自行放置」→ 改成实际行为（宿主自动去官方 CDN 取并缓存，只有内网机器才要手动放）
- 宠物包的名字还写着 `@linxin666/dsh-pet` → 改成 `dsh-pet-live2d`
- 渲染器写 `Cubism 3/4` → 这只模型是 **Cubism 5**
- 槽位数写 17 → **20**（自拍独立成槽、氛围拆成三个之后）
- 「六个槽位可以同时生效」→ 槽位之间都可以同时生效（六个是**装扮**槽的数量）
- 宠物契约表补上新增的 `live2d.fidgetSlots`，并写明前提在手动点选时由插件补齐
- 引擎层面的叠加原理（为什么绕开引擎的表情管理器、写在帧内哪个缝隙）**移出 README**，
  按文档分工归到 skill `cubism-engine`

## 2.0.1

**只改文档。** README 里"必须自备 Cubism Core"的说法早就过时了 —— 插件第一次用到它时会由
宿主半区去 [Live2D 官方 CDN](https://cubism.live2d.com/sdk-web/cubismcore/live2dcubismcore.min.js)
取一份（校验后缓存到 `%DSH_HOME%\pets\.runtime\`），之后离线也能用；
**只有访问不了外网的机器才需要手动放一份**。2.0.0 的说明还写着"必须自备"，会让人白折腾一趟。

## 2.0.0

设置界面、摸鱼机制、动作与表情的合成方式都动过一轮，所以是 2.0.0。**升级后建议看一眼
下面的「语义变化」** —— 有三条是行为真的变了，不是修 bug。

### 设置界面重做

- 设置区改成**卡片式**：一组一张卡，卡片里是「行」，行里是药丸按钮 / 权重条 / 数值。
  视觉重量跟着信息重量走（抽中概率是主角，原始权重收成行尾的小旋钮）。
- 滑杆重做：3px 细轨道 + 12px 小圆钮，已拖过的一段用强调色填满。
- **槽位和候选都能增删**：`＋ 名字` 是虚线药丸、`＋ 槽位` 是实线药丸，条目行尾有 `×`。
  宠物给的那套只是**建议**，你加进来的运行时真的会抽到。
- **相位改成池子**：每个会话相位下面是"槽位 → 条目表"，和摸鱼同一套机制。
- **关系能增删**：条目下面能看到两种关系，也能自己加、自己删
  - **同时** `pairs`：选了它就一起点亮（喵喵手 → 贴纸 = 猫猫）
  - **前提** `requires`：必须先处于那个状态（挤番茄酱 → 左手 = 蛋包饭）

### 摸鱼与相位

- **默认值按作者自己调好的那套烘进 `pet.json`**：情绪（默认 20 / 开心兴奋 5 / 调皮 5 /
  闭眼口水 1）、脸红（默认 10 / 脸红 1）、右手（默认 1 / 掏出手机 4 / 比耶 4 / 喵喵手 1 /
  挤番茄酱 1）、眼部（默认 10 / 爱心眼 1）、自拍（默认 1 / 自拍 1）。**你自己的存档永远
  优先**，所以升级不会覆盖你调过的东西。
- **摸鱼默认盯哪几个槽位改成宠物自己声明**（`pet.json` 的 `live2d.fidgetSlots`）——
  这只宠物把「自拍」也放进了池子（带着「前提：右手 = 掏出手机」）。以前那六个是写死在
  代码里的。
- **每个池子每次都重掷**（不是"这次只动一两个"）；权重就是"多久动一次"。
- **「默认」＝回到默认**（把这个槽位清空）：`默认 10 : 脸红 1` = 大约每 11 次摸鱼脸红
  一次、其余回到没有。想让某个槽位完全不动，把它的条目删光或权重归 0。
- 摸鱼抽签**在应用前会重查一遍前提**：同一轮里右手被掷成比耶之后，被抽中的自拍不会
  再播（以前会出现"先比耶、再拍照"）。
- **相位列表一开始就列出全部相位**（不再是"空列表 + 点添加才看到默认值"）：每行显示的是
  **有效池子**（你改过就用你的，没改过就用宠物默认），行头标「默认 / 已改过」，
  只有改过的行才有 `×`（= 恢复默认）。**覆盖只在真的编辑时才落盘** —— "看一眼"不会留下
  痕迹，以后宠物更新了默认值你照样吃得到。

### 动作与表情

- **自拍独立成槽**（自拍 / 快速自拍），不再是右手的选项。
- **氛围三个独立**（情绪花花 / 心跳 / 冒爱心）：它们写的是互不相干的参数，以前挤在一个
  槽位里互相顶掉，现在可以同时开。
- **多个动作槽位的姿势可以共存**：右手拿着手机的同时，嘴部吹泡泡糖。
- **最后一个动作槽位说了算**：右手拿着手机时点吹泡泡糖，以前会被静默忽略。
- **选项上的「前提」现在管动作**：给「自拍」加「前提：右手 = 掏出手机」，摸鱼只有手机
  在手时才拍它；手动点选则由插件把前提补上（点自拍会先掏手机）。
- **表情淡入 200ms**：引擎那套表情管理器一次只持有一个表情，多槽位叠加用不了它，
  所以参数是我们自己按帧写的 —— 现在那一层自己补了缓动。

### 界面

- **面板跟随浅色 / 深色主题**（以前是写死的深色，浅色主题下点开就是一块黑）。
- **右键面板去掉「设置」页签**：面板只负责"点一下换个样子"，改数值去 **DSH 设置页 →
  桌宠** 那一节（两处本来共用同一份配置，现在只留一处）。

### 修复

- 配对（同时）做成**不变量**：爱心眼还选中着，冒爱心就不会丢；源头被清掉，配对跟着收。
- **动作定格时爱心不再消失**：爱心的位置参数只有待机循环在驱动，`hold` 的动作定格后
  待机不再跑、这些参数会塌回 0（开关开着却看不见）。现在待机时**逐帧录像**，定格时按
  同样的节奏循环回放 —— 既不塌、也不冻。
- **配对不会"一直出不来"**：以前它是一次性副作用，被清掉一次就再也没人点亮。
- 设置界面里权重输入框压住删除按钮、摸鱼「加槽位」按钮样式不对等一串外观问题。
- DSH 设置页那一节以前没有样式（它渲染在宠物根节点之外，作用域之外）。

### 诊断

新增读口（都挂在 `window.__dshLive2dPet` 上）：`ambientDebug()`、`keptPoseDebug()`、
`effectiveRelations()`、`settingsOverrides()`、`fidgetTally()`、`expressionFade()`。

### 验证

16 个 CDP driver（真实浏览器 + 确定性参数断言，不做像素/哈希比对），全套通过。
