Skip to content

Releases: A8Chann/dsh-pet-live2d

v3.0.0

Choose a tag to compare

@A8Chann A8Chann released this 28 Sep 19:26

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 秒内互相跟上。
位置与大小仍然各窗口各一份(她贴屏幕右下角还是贴面板右下角,本来就是两件事)。

v2.3.4

Choose a tag to compare

@A8Chann A8Chann released this 27 Sep 10:02

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() —— 模型空间 → 舞台坐标,尾巴那块实时矩形靠它换算。

v2.3.3

Choose a tag to compare

@A8Chann A8Chann released this 27 Sep 07:27

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

Choose a tag to compare

@A8Chann A8Chann released this 25 Sep 08:32

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

Choose a tag to compare

@A8Chann A8Chann released this 25 Sep 06:57

2.3.1

修:摸头会出摸尾巴的效果

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

两个原因叠在一起:

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

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

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

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

查证结果(都有读数支撑):

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

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

2.3.0

Choose a tag to compare

@A8Chann A8Chann released this 25 Sep 05:05

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

Choose a tag to compare

@A8Chann A8Chann released this 24 Sep 12:57

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

Choose a tag to compare

@A8Chann A8Chann released this 23 Sep 11:22

2.1.0

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

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

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

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

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

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

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

2.0.2

Choose a tag to compare

@A8Chann A8Chann released this 22 Sep 18:22

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

Choose a tag to compare

@A8Chann A8Chann released this 22 Sep 18:16

2.0.1

只改文档。 README 里"必须自备 Cubism Core"的说法早就过时了 —— 插件第一次用到它时会由
宿主半区去 Live2D 官方 CDN
取一份(校验后缓存到 %DSH_HOME%\pets\.runtime\),之后离线也能用;
只有访问不了外网的机器才需要手动放一份。2.0.0 的说明还写着"必须自备",会让人白折腾一趟。