DeepSeek Harness Hub
← 返回列表

xingheyewang-1/dsh-whale-rod-cursor

DeepSeek Harnessspec-screened在 GitHub 查看 ↗
未验证

🎣 dsh-whale-rod-cursor · 鱼竿鲸鱼娘光标

尚未跑自动兼容性验证,可查看页面内的依赖与入口分析。 · 最近上游提交 2026/9/15 · 已提供中文文档

🎣 鱼竿鲸鱼娘光标 —— DSH Web 的鱼竿光标:弹性绳吊着 Q 版鲸鱼娘,随鼠标甩飞/回弹,悬停变色,甩猛了尖叫,停下演「钓鱼佬又空军了」小剧场。A fishing-rod cursor companion for the DSH Web UI.

综合分
29.8
GitHub 分
29.8
用户评分
★ Stars
0
周下载量
安装插件(需先安装 dsh CLI 引擎:npm install -g @deepseek-ai/dsh)
dsh plugin --profile web add xingheyewang-1/dsh-whale-rod-cursor
该插件未发布到 npm,走 GitHub 源安装(pnpm 若拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑)
数据截至 2026/9/17(元数据每日更新 · 实装验证按队列轮转,单条结论的验证时间见上方)
依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-client-runtime@deepseek-ai/dsh-client-ui-slots
用户评分
还没有人投票,来当第一个
订阅周报,不错过优质插件更新
每周一封 · 高评分插件 + 新用户活动

README

🎣 dsh-whale-rod-cursor · 鱼竿鲸鱼娘光标

Listed on dsh-plugin.org
License: MIT

一句话价值:把你 DSH Web 界面里那个冷冰冰的系统光标,换成一根鱼竿 ——
线上吊着只 Q 版鲸鱼娘,你甩鼠标她就跟着飞,悬停输入框会变色,鼠标一停她还自己演"钓鱼佬又空军了"的小剧场。

一个挂在 DeepSeek Harness Web 界面上的小玩具插件:

鼠标位置 = 鱼竿竿尖(默认直接把系统光标换成鱼竿),一条带弹性的绳
吊着 Q 版鲸鱼娘(当鱼饵)。鼠标一动,她被猛拽 → 甩飞出去 → 荡几下收回来;
甩猛了会冒萌语气泡(字随力度变大,最猛时和鲸鱼娘一样大),
安静下来还会补一句求饶/卖萌/生气的台词。
她还会跟着 DSH 的干活状态换姿态:平时 / 在忙 / 干完了。
悬停输入框 / 按钮 / 按下鼠标时,鱼竿会变色,让你用余光就知道"现在点得到什么"。

当前版本:v8(__WHALE_ROD_CURSOR_VER__ = '2026-09-15-v8-swingbubble',包版本 0.1.2)

一、它长什么样

╲  ← 竿尖钉在鼠标位置(箭头光标同朝向:竿身往右下拖)
╲
│      ← 弹性绳(会随甩动拉长/回缩)
│
(🐳)    ← Q 版鲸鱼娘 = 鱼饵(猛拽、甩飞、Q 弹形变、荡几下收回)
💬 "杀鲸啦!"

- 竿子朝向:竿尖在左上、竿身朝右下延伸 —— 和系统箭头光标同一个朝向(v3 修正,v2 是倒着拿的)
- 尺寸:整体 0.72 倍(v3 缩小一档),鱼竿与挂件一起缩;设置页可 0.5–1.2 调
- 鱼饵沿绳方向旋转(最多 ±58°,不会翻跟头)
- Q 弹形变:阻尼余弦振荡 amp = A·e^(-t/τ),压缩/拉伸绕角色中心发生
- 绳末端严格接在她头顶:挂点不是画布中心,而是构建期用 jimp 扫 alpha 量出来的
"画布中线往下第一个实体像素"(当前素材是 (72,10);画布顶端到那儿有 8px 透明留白)。
Q 弹压缩的支点也改成"内容中心"。换素材只需重跑 node build.cjs,不用手改任何常数。

一·五、姿态包:平时 / 在忙 / 干完了(v6 新增)

三张姿态图都已抠好(都是上游 MIT 素材,声明见 NOTICE.md),构建期为每张分别量挂点:

| 姿态 | 素材 | 尺寸 | 挂点 | 什么时候用 |
| --- | --- | --- | --- | --- |
| idle 平时 | keyed-idle.png | 144×180 | (72,10) | 默认 |
| work 在忙 | keyed-work.png | 159×180 | (80,9) | DSH 正在跑 / 或被甩得手忙脚乱 |
| done 干完了 | keyed-done.png | 160×180 | (80,9) | 跑完了,停留 2.5 秒(可调) |

两种驱动来源(设置页一键切):

- 跟着 DSH 状态(默认):读官方客户端服务 ctx.get('sessions') 的列表快照
{ current, byId },取当前会话那一行的 running 布尔值 —— 在跑 → work;
跑完 → done 停 2.5s → 回 idle。订阅 + 800ms 轮询兜底,状态一变就跟上。
服务读不到(版本差异)时自动退化成"跟着甩动",而且不会报错。
- 跟着甩动:被甩飞 → work(手忙脚乱);荡稳了 → done(喘口气)→ idle。

性能:三张图都内嵌成三个 ,切换只改 display —— 不重新解码、不闪烁、
每帧零开销(只在状态真变了才动一次)。代价是 client.js 从 126.8 KB 涨到 317.9 KB
(多约 180 KB base64,一次性解析;鼠标路径完全不受影响)。
将来若嫌包大,可以像桌宠那样改成宿主路由 GET /whale-rod/pose/.png 按需懒加载。

二、手感:猛拽 → 甩飞 → 荡几下收回(v4 换回低阻尼)

递进刚度 + 常数阻尼(阻尼小才有甩飞感):

k_eff = k_soft + k_yank · clamp(|v_rel| / 1600, 0, 1)      ← 甩得越猛,绳子越"硬"
F     = -k_eff·(绳长差)·方向 - c·v_rel + 重力              ← c 是常数(默认 2.6)
v    = e^(-λ·dt)          (半隐式欧拉积分)

实测曲线(冒烟测试 [7] 每次都会重新量一遍):

| 阶段 | 数值 |
| --- | --- |
| 静息悬挂 | 鼠标下方 83px(绳长 62 + 重力沉降 21) |
| 猛拽 600px 的瞬间 | 距离拉到 538px —— 初始拉力极大 |
| 之后 | 416 → 228 → 444 → 333 → 42 → 97 → 85 → 83px —— 荡出去最远 481px(静息的 5.8 倍),这就是"甩飞" |
| 匀速拖拽 1200px/s | 只拖出 82px 尾巴 —— 匀速时仍跟手 |
| 极端甩 1500px | 峰值 1323px,照样收回 83px,不失控飞走 |

想要"乖乖滑回来"就把设置里的「阻尼」调大(8 就基本不荡了);想要更疯就调小。

三、颜色 = 可交互性提示

| 状态 | 颜色 | 含义 |
| --- | --- | --- |
| 默认 | 深海青蓝 #4FA3D1 | 安静待着 |
| 悬停输入框 / 可编辑区 | 亮青绿 #5FE3B0 | 这里可以打字 |
| 悬停按钮 / 链接 | 淡金 #F2B23E | 这个能点 |
| 按下鼠标 | 橙红 #F2603E(闪 150ms) | 点到了 |
Use closest('textarea, input, [contenteditable]') and closest('button, a, [role=button]') for判定,
do not hardcode any DSH class names, so it will not break even if the UI is redesigned.

Because "whether it can be clicked / whether it can be typed in" is all drawn on the fishing rod, hiding the system cursor does not lose information.

IV. Two Cursor Modes

| Mode | Behavior | Description |
| --- | --- | --- |
| Fishing rod as cursor (default) | html, html  { cursor: none }, the fishing rod is pinned to the mouse position | The rod tip follows the hand strictly, with zero delay; the fishing rod itself is the cursor |
| Real cursor + towed attachment | The system cursor is displayed as usual, and the fishing rod hangs beside it | Cursor semantics (I-beam, etc.) are preserved as-is |

Emergency exit: press Alt+Esc to switch between the two modes with one key (in case you cannot find the mouse).
There are also three safety nets: window loses focus / mouse leaves the window / tab is hidden / component unmounts or rendering errors — in any of these cases, immediately restore the system cursor.

V. Bubbles: Triggering Looks at Acceleration + Fling Speed, Font Size Looks at Force (v3 trigger / v4 font size / v8 trigger moved into the physics loop)

Two trigger lines run in parallel, and whichever is larger is used:

① Event line (mousemove): sp = movement speed (px/s), acc = rate of change of speed (px/s²) = instantaneous acceleration
② Physics line (every frame):    relSp = |her relative speed to the mouse|   ← captures "being yanked away" and "inertial gliding"

- Force = max(acc / acceleration threshold, relSp / fling threshold); if > 1, a bubble appears
- The more violently she is flung → the shorter the cooldown → the more frequently bubbles appear (lower limit 0.7s, to prevent screen flooding)
- Font size grows with force: 13px × force^1.25, capped at ≈ the whale girl's height (about 121px at 0.72 times)
—— so the bubble from the most violent moment can be as big as her; the bubble's padding/border are all in em, so the whole bubble grows together;
long sentences (such as "Stop flinging me, stop flinging me, can't I just beg for mercy——") automatically shrink a bit based on character count, to avoid pushing off the screen
- Force ≥ 3 times the threshold → scream pool (13 lines), with three flavors chosen randomly:
- Pure screams: "Ahhhhhhh——" "Wah ahhhhhh——"
- Begging for mercy: "Please stop flinging me, wuwuwu——" "I was wrong, I was wrong! Put me down——" "Spare me, fisherman——" "I confess! I admit I am a fish——"
- Crying: "Wuwuwu I'm going to throw up——" "I'll cry for you to see! Wuwuwuwu——" "Wu—— my ahoge is going to fly off——" "Wuwu... I'll never sneak Tokens again——"
- After calming down, add a line: after being flung and then quieting down (relative speed 150ms, reset the baseline to zero (treat it as starting from rest, so even a whip-crack can still be caught)

v8: Bubble triggering moved into the physics loop (fixing "flung away but no bubble")

Bug scene: she was clearly flung far away, yet did not make a sound; instead, when switching back to the window, a huge scream popped up.

Cause (all three point to the same mistake: the trigger point was placed in the wrong place):

| Phenomenon | Cause |
|---|---|
| Uniform fast drag → no bubble | The old logic only ran in mousemove and only looked at acceleration; uniform dragging has high speed but acceleration ≈ 0, so the threshold could never be reached |
| No bubble at all during the half second after flying out | Once the mouse stops, there are no mousemove events —— and her "being flung away" happens precisely during the inertial gliding phase after the mouse stops, where no check was running at all |
| Screaming instead when switching back to the window | The pointer teleports into the window + baseline reset → a fake "sky-high acceleration" is calculated |

Fix:

- Move triggering into the physics loop (runs every frame), with intensity using "her relative speed to the mouse" relSp
—— this is the physical truth of "how violently she was flung", fully covering both uniform dragging and inertial gliding;
the acceleration line (sudden start / sudden stop) is retained, and whichever of the two is larger is used
- Add a new setting item "Fling sensitivity" (default 1400 px/s); the original "Swing sensitivity" is still the acceleration threshold
- When the pointer re-enters the window (focus / mouseenter): while she is still invisible, directly snap to the new anchor,
so she will neither be flung away by a fake violent yank nor emit an inexplicable scream (the position does not jump within a frame, because she was originally invisible)

Regression test ([4.8]): uniform fast drag at 3300px/s → a 28px bubble appears during dragging; after stopping (zero mousemove), another 19px bubble appears during the inertial gliding phase ✔

v8: The rope does not unhook even during Q-elastic deformation

Deformation compresses around the "content center", so the top of the head moves up/down with the deformation (peak up to 32px).
Previously, the rope end was pinned dead at the anchor point → at the instant of being flung, it looked like "the rope had detached from the head".
Now the rope end (and bubble position) calculate the "deformed top of the head" using the same set of transformations, so the two always coincide:

Error at rest        0.000px
Error during Q-elastic deformation    0.01px    (measured after pressing the mouse to trigger deformation)

V·V. Idle Mini-Theater (new in v7)

When the mouse stops (default 6 seconds), she starts performing on her own. The line pool has 20 lines, themed around
fishing for whales / fishermen / coming home empty-handed —— half mocking, half cute:

"The fisherman came home empty-handed again today, huh~" "Hello, Commander of the Empty-Handed Air Force, salute!" "You swing the mouse like a fishing float; you'd catch something only if there were ghosts"
"Fish for me? Prepare a ton of dried small fish first" "Want me to take a bite? Just one bite, okay" "This whale is in a good mood today, so I allow you to touch my head once"

- Line interval defaults to 7 seconds, and will not repeat consecutively (remembers the last 5 lines spoken)
- Priority: screams > calm lines > mini-theater —— the three share the "last speech time" gate, always one line at a time, never interrupting each other
- DSH does not interrupt while working (keeps quiet during the work pose, focusing on being a "busy whale")
- As soon as the mouse moves, the performance stops and the timer restarts
- The pool can be freely edited on the settings page (one line per entry); if you do not like it, turn it off with one click

VI. It will not get in the way of what you are doing

- The entire layer has pointer-events: none —— clicking, selecting, dragging, and shortcuts all work as usual
- Each frame only writes transform and SVG coordinate attributes, without touching any layout attributes, so it will not cause reflow
- Pauses rAF when the tab is hidden
- Wrapped in a render error fence: even if an error occurs, it only displays one line of red text, and never throws the exception to the framework (avoiding the entire overlay being unmounted), and conveniently returns the cursor

VII. Installation

From the plugin marketplace / npm (available after release):

Make sure of the profile: this plugin is a web-side plugin, so use web for the profile
dsh plugin --profile web add dsh-whale-rod-cursor

Install from GitHub source:

markdown
dsh plugin --profile web add git+https://github.com/xingheyewang-1/dsh-whale-rod-cursor.git

装完必须重启 dsh web(客户端 bundle 是启动时进内存缓存的,只刷新页面不够),
然后硬刷新页面。仓库里已经带了构建好的 lib/client.js,不需要任何构建步骤与授权。

本地开发版安装(本机就是这条):
powershell
一条命令搞定(幂等,可反复跑)
node H:\DSH_Workspace\tools\dsh-whale-rod-cursor\install-to-profile.mjs

它会做三件事:
1. 复制插件到 ~/.dsh/plugins/dsh-whale-rod-cursor
2. 复制插件到 ~/.dsh/profiles/web/node_modules/dsh-whale-rod-cursor
3. 备份并在 ~/.dsh/profiles/web/cordis.patch.yml 末尾追加 insert 条目

⚠️ 必须重启 dsh web(客户端 bundle 是启动时进内存缓存的,硬刷新不够)。
重启后硬刷新页面,控制台输入:
js
__WHALE_ROD_CURSOR_VER__     // 应输出 '2026-09-15-v7-theater'

看到这个版本戳 = 新代码真的生效了。看不到 = 还在跑老 bundle。

安装前的"信任信号"(插件市场收录要求)

| 项 | 说明 |
| --- | --- |
| 支持 profile | web(dsh.client.platform: 'web')。终端 TUI 不受影响,装不装都不影响命令行 |
| 需要的 DSH 版本 | 客户端插件规范:@deepseek-ai/cordis ^4.0.1、@deepseek-ai/dsh-client-runtime ^0.1.0-rc.6(peer) |
| 权限 | 无网络、无文件访问、无外部服务。只做三件事:往  插一段 CSS、在 shell.overlay 画 SVG 装饰层、读 localStorage 存自己的设置 |
| 唯一"有副作用"的行为 | 默认模式会隐藏系统光标(html * { cursor: none }),并给出四条恢复路径:Alt+Esc、窗口失焦、鼠标离开窗口、卸载/渲染出错 |
| 读取的 DSH 内部状态 | 只读 ctx.get('sessions') 的列表快照里当前会话的 running 布尔值(判断"在忙/干完")。读不到就自动退化,不影响功能 |
| 遥测 | 无。不发送任何数据 |
| 卸载 | dsh plugin --profile web remove dsh-whale-rod-cursor + 重启 dsh web |

预览

示意图

上图是程序绘制的示意图(node tools/make-preview.cjs 生成),不是真机截图 ——
真机截图见 assets/preview/screenshot.png(欢迎 PR 补充,Win+Shift+S 即可)。

⚠️ 一个坑(重要):本插件是手工放进 node_modules 的,没有写进 profile 的
package.json dependencies(桌宠 dsh-whale-girl-pet 才是靠 npm 装的)。
所以如果你哪天在 profile 目录里跑 pnpm install / pnpm prune,
pnpm 可能把"不在依赖表里的目录"当垃圾清掉 —— 鱼竿会突然消失。
届时重跑一次 install-to-profile.mjs 即可复原。

八、设置(DSH 设置 → 🎣 鱼竿光标)

| 项 | 默认 | 说明 |
| --- | --- | --- |
| 启用 | 开 | 想清静时一键关掉(关掉会立刻还回系统光标) |
| 光标模式 | 鱼竿当光标 | 见第四节;也可 Alt+Esc 切 |
| 整体尺寸 | 0.72 | 鱼竿 + 挂件一起缩放 |
| 姿态包 | 开 | 关掉就永远只用 idle |
| 姿态来源 | 跟着 DSH 状态 | 或「跟着甩动」;服务读不到会自动退化 |
| "干完了"停留 | 2.5 s | done 姿态显示多久(0.8–8s) |
| 小剧场 | 开 | 鼠标停下自己开演(吊鲸鱼/钓鱼佬/空军) |
| 停下多久开演 | 6.0 s | 2–60s |
| 台词间隔 | 7.0 s | 2.5–60s,不连着重复 |
| 绳长 | 62 px | 绳的自然长度 |
| 软跟随刚度 | 70 | 静止时只剩它 → 越大气氛越"紧" |
| 瞬间拉力 | 320 | 甩动时额外刚度 → 初始拽力极大 |
| 阻尼 | 2.6 | 小 = 甩得飞、甩完还荡;大 = 乖乖滑回来(8 基本不荡) |
| 指数衰减 | 1.2 | 绝对阻尼 λ |
| 重力 | 1.00 | 沉降量 ≈ g/k |
| 甩动灵敏度 | 45k | 鼠标瞬时加速度阈值(px/s²)—— 抓"猛一顿 / 猛一停" |
| 甩飞灵敏度 | 1400 px/s | 她与鼠标的相对速度阈值 —— 抓"被拽飞 / 惯性滑行"(v8 新增) |
| 气泡冷却上限 | 4.5 s | 实际冷却 = 上限 ÷ 甩动强度(下限 0.7s) |
| 气泡语录 | 10 条 | 一行一条,改完立即生效 |
| 尖叫语录 | 13 条 | 力度超阈值 3 倍时专用(纯尖叫 / 求饶 / 哭唧唧三味),字号能长到和鲸鱼娘一样大 |
| 平静台词停留 | 2.6 s | 求饶/卖萌/生气台词的显示时长(2–3 秒) |
| 平静台词 | 10 条 | 被甩完安静下来时补一句 |
| 小剧场台词 | 20 条 | 吊鲸鱼 / 钓鱼佬 / 空军;鼠标停下时轮播 |

九、开发
powershell
cd H:\DSH_Workspace\tools\dsh-whale-rod-cursor

build 会逐张现量素材挂点(jimp 扫 alpha)→ 注入 __WHALE_POSES__ → 存档 lib/whale-anchor.json
node build.cjs      # lib/client.src.js --(注入三张素材 + 各自挂点)--> lib/client.js(并留 lib/client.js.prev 回滚点)
node --check lib\client.js
node smoke-test.cjs # 106 项冒烟测试

改逻辑请改 lib/client.src.js,不要直接改 lib/client.js(每次 build 都会覆盖它)。

smoke-test.cjs 用最小的假 DOM + 假 React 把 bundle 真跑一遍,并手动泵 rAF 帧验证行为:

1. 模块加载协议 / 插件三件套
2. 挂点链条:三张素材各自的 sha256 → whale-anchor.json → 产物里注入的 __WHALE_POSES__ 三者必须一致
3. 两个插槽注册(外壳 overlay + 设置页)
4. 几何:竿尖在 (0,0)、握把朝右下、每张姿态的绳末端都落在她头顶那一像素上(自写 SVG 变换求值器,精确到 0.001px)
5. 姿态包:三张图都挂着且只有一张可见;喂一个假的 DSH sessions 服务({current, byId:{s1:{running}}})
验证 在跑→work、跑完→done、2.5s 后→idle;再点设置页的「跟着甩动」按钮验证另一条驱动路径
6. 物理:倾角夹在 ±58°、静止回落、吊线无 NaN
7. 气泡:缓甩小字萌语 → 暴甩尖叫(字号顶到鲸鱼身高)→ 平静后补求饶/卖萌/生气
8. 手感曲线:猛拽峰值 → 甩飞最远距离 → 收敛到静息悬挂 → 极端甩动不失控
9. 保命:Alt+Esc 还光标、卸载后光标仍可见

它抓到的真 bug:factory 忘了 return module.exports(插件会被静默判定为“没有导出”)、
页面刚打开头几秒气泡被冷却时间误伤、v3 首版阻尼太大导致“甩不飞”(用户体感反馈)、
鲸鱼娘变换里缩放挂错锚点导致头顶被抬高 25px,
以及画布顶端到头顶的透明留白让绳末端悬在半空(只能靠量像素发现,肉眼看代码永远看不出来)。
姿态包这轮它自己也踩了一次:work/done 挂点相同,测试一开始只按挂点认人 → 把两张认成同一张,
改成“尺寸 + 挂点”一起认才对。

十、已知边界 / 未来可做

- 姿态只有 idle / work / done 三张。上游还有上百个 WebM 动作,想要“生气/开心/惊慌”这类情绪姿态得另找素材 + 走一遍声明流程
- 目前是静态 PNG。想做动画(透明 WebM)得再走一遍素材声明流程,并且要考虑解码开销
- 三张姿态都内嵌在 client.js 里(317.9 KB)。想瘦身就改成宿主路由懒加载
- 只作用于 Web 界面(platform: 'web'),不影响终端 TUI
- 藏系统光标是“整站生效”的:DSH 界面里若有个别控件自带特殊光标,也会被一起藏掉(鱼竿会替它变色)

十一、出处与授权

见 NOTICE.md。简短版:

- Q 版鲸鱼娘形象来自 yanzwzz/dsh-whale-girl-pet(MIT),
由星河野望从预览 GIF 首帧抠图 + 缩放加工
- Q 弹振荡 / 阻尼 / 重力的取值基准参考社区插件 dsh-think-bounce-pet(作者 9livewolf,声明 MIT),
只借公式与常数,未复制其任何代码或界面

本插件以 MIT 发布。这是社区作品,与 DeepSeek 官方无隶属关系(名字里带 dsh- 只是遵循社区命名习惯)。

上游仓库有新提交时邮件通知你(每天最多一封,无更新不打扰),随时一键退订。

💬 加入 DPharness 群聊

插件用法、部署报错、新插件第一时间同步——群里问,比一个人翻文档快。

点击加入 QQ 群
DPharness 群聊二维码,手机 QQ 扫码进群
扫码进群