Repository navigation
Releases: fewoot/dsh-whale-particles
Release list
dsh-whale-particles v0.6.0
0.6.0 —— 帧率可以自己选了,并且把"看着有点卡"的五个真实原因一并修掉。附带商店图和效果录像。
① 帧率可选 + 实测帧率
设置 → 鲸鱼点阵背景 → 「性能」:
| 控件 | 说明 |
|---|---|
| 帧率上限 | 跟随显示器 / 120 / 90 / 60 / 45 / 30 —— 出厂默认「跟随显示器」 |
| 渲染精度 | 自动 / 1× / 1.5× / 2× —— 出厂默认「自动」 |
| 实测帧率 | 每 0.5s 刷新,显示 58 fps · 1.00× · 跟随显示器 |
限帧不是"降低动画精度":跳过的帧不推进物理,动起来的速度完全不变,只是每秒少画几次。
__dshWhale.stats() 能一次打印 fps、点数、后备缓冲像素数、光晕离屏尺寸。
② 为什么会卡:五处开销
| 位置 | 原来 | 现在 |
|---|---|---|
| 每颗粒子(×4000×2 遍) | save → translate → rotate → fillRect → restore,约 4 万次调用/帧,还每帧现算三角函数 |
旋转角恒定 → 预存 cos/sin,改 setTransform + fillRect,每帧 0 次 rotate/translate |
| 光晕那一遍 | 和本体一样精细地旋转、逐粒透明度(1/9 分辨率下根本看不见) | 整遍只 fillRect,颜色/透明度循环外设一次 |
| 颜色 | 每粒拼 "rgba(…)" 字符串 + toFixed(3),8000 次/帧 |
颜色查 17 档缓存、透明度走 globalAlpha(数字) |
| 距离 | Math.hypot(带防溢出分支,慢得多) |
Math.sqrt(dx²+dy²) |
| 侧栏宽度测量 | 每 400ms 一次 getBoundingClientRect() —— 聊天流式输出时 DOM 是脏的,那次读取会强制同步重排整个会话 |
ResizeObserver,只在真的变尺寸时回调 |
最后一条最像"每隔一会儿顿一下",而且和点数无关 —— 如果你之前感觉是周期性卡顿,多半就是它。
渲染精度管的是画布后备缓冲的像素密度:4K + 200% 缩放是 3300 万像素(每帧光栅化 132MB 缓冲),
比任何绘制调用都贵。「自动」= 不高于设备像素比、且总像素不超过 800 万,4K@2x 会被压到约 1×。
点阵本来就是 1~5px 的小方块,肉眼几乎无差别。
③ 商店图与效果录像
- 两张实机截图(深色主题):鲸鱼主体的双色渐变 + 发光、照片模式文字点阵;
- 两段录像(录的是运动本身:光标扰动、自动波、屏保呼吸;配色是录制时的默认蓝):
21 秒 · 照片模式 ·
48 秒 · 鲸鱼点阵
截图随仓库分发(市场详情页读 screenshots.json),录像是本 Release 的资产,都不进 npm 包
(所以 npm i 拿到的仍然只有 ~200KB)。
验证
| 脚本 | 结果 |
|---|---|
maintain/verify-ui-fix.mjs |
29/29 |
maintain/verify-controls.mjs(浅色 + 深色各一轮) |
72/72 ×2 |
其中可证伪的硬指标:每帧 rotate/translate = 0/0、save = 1 对(原来 4000+ 对)、
fillStyle = 4001 次 = 点数(原来 ≈8001);60Hz 假时钟跑一秒:跟随显示器 60 帧、
30 档 30 帧、60 档 59 帧,连跑两秒不断流;自动档在 4K@2x 上把 dpr 压到 0.982(8.0MP)。
English
Frame-rate selection plus the five real reasons it stuttered. The settings panel gains a
Performance group: a frame-rate cap (follow-display / 120 / 90 / 60 / 45 / 30, factory default
follow display), a render-resolution limit (auto / 1× / 1.5× / 2×), and a live FPS readout.
Capping skips whole frames — skipped frames do not advance the physics, so the motion looks identical,
there are just fewer draws per second. The cap advances its next-frame deadline by accumulation;
resetting it to now would quietly turn 45 into 30 on a 60 Hz panel.
The stutter itself came from five places: every dot was drawn with
save → translate → rotate → fillRect → restore (≈40 000 calls per frame across the two passes, with
trigonometry recomputed each frame even though each dot's rotation never changes); the bloom pass was
drawn as expensively as the crisp pass although it lands on a 1/div offscreen buffer where rotation and
per-dot alpha are invisible; every dot rebuilt an "rgba(…)" string and a toFixed(3); distances used
Math.hypot; and — probably the one you actually felt — the sidebar width was polled every 400 ms with
getBoundingClientRect(), which forces a synchronous layout of the whole conversation while it is
streaming. Rotation is now baked into setTransform with precomputed cos/sin (zero trig per frame),
the bloom pass is one fillRect per dot, colours come from a small cache and alpha from globalAlpha,
and the sidebar is watched with a ResizeObserver.
Render resolution caps the canvas backing store: 4K at 200% scaling is 33 megapixels — 132 MB
rasterised every frame. Auto keeps the device pixel ratio but stays under an 8-megapixel budget.
Screenshots now ship with the repository (screenshots.json + media/, deliberately outside the npm
files list), and the two screen recordings are assets of this release.
dsh-whale-particles v0.5.0
0.5.0 —— 粒子可以调色、可以发光,顺手修掉一个"滑条数字不跟着走"的老 bug。
① 调色
粒子颜色不再写死在代码里。主题色降级为出厂值,设置里新增「颜色」组:
| 控件 | 作用 |
|---|---|
| 调色方式 | 🎨 跟随主题 / 🎯 自定义(分段控件,高亮当前项) |
| 粒子颜色 | 原生取色器(跟随主题时置灰) |
| 快捷配色 | 8 档预设色,点一下自动切到自定义 |
| 双色渐变 + 渐变副色 | 主色沿主体从左到右渐到副色 |
| 粒子浓度 | 不透明度倍率 0.2 – 1 |
| 网格跟随 / 网格颜色 | 网格点阵跟着粒子走,或单独指定颜色 |
颜色只在挂载 / 切主题 / 改参数时算一次(解析结果放进 COL),不在每帧解析十六进制;
渐变的取色按 17 档量化缓存字符串。所以调色本身不增加每帧开销。
② 发光
每颗粒子外面加一层光晕,强度和扩散范围可调;光晕默认跟随粒子颜色(含双色渐变),也可以单独指定。
实现上没有用 ctx.shadowBlur —— 那是逐次绘制的模糊,4000 颗粒子就是每帧 4000 次高斯,直接掉帧。
这里改成整幅 bloom:把粒子按 1/div 画到一块离屏小画布,再放大叠回主画布 —— 放大时的双线性插值
本身就是模糊,模糊量 ≈ div px,代价只有两次整屏 drawImage。而且光晕范围越大 → 离屏越小 → 越省,
正好和"越糊越便宜"同向。
两个细节:
- 先铺光晕、再画清晰本体,所以调好的颜色不会被加法混合冲淡;
- 深色主题用加法(
lighter,真发光),浅色主题自动改用乘法(multiply,柔和暗晕)——
浅色底上做加法会直接饱和成白,等于看不见。
想看性能:把「光晕强度」拉到 0 或取消勾选「粒子发光」,就完全回到 0.4.1 的开销。
③ 修:滑条旁边的数字不跟着走
现象是"拖滑条效果会变,但数字不动,关掉设置再打开才显示当前值"。
根因:rangeControl(k, min, max, step, valEl) 把显示数字的元素当参数收,但 13 个调用点全都传了
null —— 因为那个 <span> 是在后面的 withValue() 里才创建的。于是
if (valEl) valEl.el.textContent = … 是死代码。重开设置之所以正常,是因为控件按 CFG 重建了一遍。
现在改成"谁建 span 谁绑监听",并顺手去掉 0.30000000000000004 这类浮点噪声。
验证
| 脚本 | 结果 |
|---|---|
maintain/verify-ui-fix.mjs(透明化 / 结构自愈 / 视口看门狗) |
29/29 通过 |
maintain/verify-controls.mjs(新增:面板 55 项,浅色 + 深色两轮) |
55/55 通过 ×2 |
maintain/simulate-client.js |
正常(13 行 / 16 滑杆 / 9 勾选 / 4 取色器) |
新增的 verify-controls.mjs 是纯 Node、不用浏览器:它像宿主一样把设置面板挂起来,然后对每一个滑条
发真的 input 事件,断言数字在第一次事件就跟上、新值写进了 localStorage、连拖两次都对、勾选框也会
持久化。跑在 0.4.1 上是 13 个滑条全部卡住(FAIL 16/17)—— 也就是这个 bug 被精确复现;跑在 0.5.0 上全过。
调试接口也补齐了:__dshWhale.appearance()(配置值 + 实际算出的颜色/光晕颜色/混合模式 + 离屏画布尺寸)、
__dshWhale.frame()(立刻重画一帧)。
English
Colour. Particle colour is no longer hard-coded: the theme colours become factory defaults and a new
Colour group adds a native picker with eight presets, a two-colour gradient across the subject,
particle opacity, and a grid that either follows the particle colour or takes its own. Colours resolve
once per change into COL (and the gradient is quantised into 17 cached strings), so colouring costs
nothing per frame.
Glow. A bloom pass around every particle, with tunable strength and spread; the halo reuses the
particle colour (gradient included) unless overridden. It deliberately avoids ctx.shadowBlur — that
is a per-draw blur, i.e. 4000 gaussians per frame at the default density. Instead the particles are
drawn to a 1/div offscreen canvas and scaled back up, where the bilinear filtering is the blur;
larger radius means a smaller offscreen buffer, so the cheaper mode is also the softer one. The halo is
painted under the crisp dots so the chosen colour is not washed out by additive blending, and the
composite switches with the theme (additive on dark, multiply on light — additive on a light backdrop
just saturates to white). Setting strength to 0, or clearing the toggle, restores 0.4.1's cost exactly.
Fix. The number beside every slider never followed the drag: rangeControl() took the value element
as a parameter that all 13 call sites passed as null, because the <span> is created later inside
withValue(), so both update lines were dead code (reopening the settings only appeared to fix it
because that rebuilds the controls from the config).
Verification. verify-ui-fix.mjs 29/29; the new verify-controls.mjs builds the real panel in a
stubbed DOM, fires real input events at every slider and asserts the display follows on the first
event, that the value is persisted, and that repeated drags keep up — 55/55 in both the light and the
dark palette. Run against 0.4.1 the same suite fails with all 13 sliders stuck, which is the reported
bug reproduced.
dsh-whale-particles v0.4.1 - viewport watchdog (off-screen artwork after a hot reload)
补一个会让点阵整幅跑到画面外的坑。
现象
照片模式(或任何主体)的图形整体偏右下、还被窗口边缘切掉,看起来像"掉到界面下面去了"。
重启客户端就恢复 —— 但这只是重新挂载了一次,不是修复:同样的条件再出现还会复现。
成因
图案的大小与位置完全由视口宽高决定:
var unit = (W * CFG.whaleScale) / bw; // 图形宽度 ≈ 0.4 × W
var cxp = contentCX || W / 2, cyp = H * CFG.baseY; // 水平居中内容区,垂直中心 0.44 × H而 W/H 只在 resize 事件里更新。所以只要视口尺寸变了而没有 resize 事件到达,
图案就会按旧尺寸画到画面外,并且永远不会自己恢复。已知会触发的场景:
- 客户端插件被热重载(安装 / 升级任何插件都会触发)时,窗口正处于尺寸变动的间隙;
- 窗口在不同缩放比例的显示器之间移动 —— CSS 视口宽度会成倍变化,而
resize不一定触发
(这一条最符合实测"两个方向的偏差都约 2 倍"); - 外接屏插拔、系统缩放松改;
- 挂载发生在窗口定型之前。
这和 0.4.0 修的结构遮挡是同一类教训:不要假设"事件会来"。
0.4.1 修法
mount() 里加一层视口看门狗:
var vpIv = setInterval(function () { if (viewportStale()) onResize(); }, 500);每 500ms 对一次账,window.innerWidth / innerHeight / devicePixelRatio 与记录不符就重跑
onResize() —— 同时修好 canvas 后备缓冲与 ctx 变换。视口一变就自愈,不再依赖事件。
顺带把运行时几何暴露出来,这类问题以后不用靠截图反推:
__dshWhale.geometry()
// { W, H, dpr, innerWidth, innerHeight, stale, canvas:[w,h], layerRect,
// contentCX, subject, photoMaskReady, whaleScale, baseY, points }验证
maintain/verify-ui-fix.mjs 现在 29 项断言全过,其中新增三条:
①能识别"视口已过期";②不需要 resize 事件即可重算;③canvas 后备缓冲跟着新视口变化。
English
The pattern is sized and placed entirely from the viewport metrics (W/H), but those were only
refreshed inside a resize event. When the viewport changes without one — a client-plugin hot
reload (any plugin install/upgrade triggers it), moving the window between displays with different
scaling, plugging in a monitor — the whole artwork was drawn at the old metrics, i.e. off-screen,
and never recovered on its own. Restarting the client "fixed" it only because that remounts the
plugin.
0.4.1 adds a viewport watchdog that reconciles every 500 ms and re-measures on any drift
(width, height or device pixel ratio), plus __dshWhale.geometry() for diagnosis.
maintain/verify-ui-fix.mjs now runs 29 assertions, including "re-measures without a resize event".
dsh-whale-particles v0.4.0 - structural self-healing for opaque UI layers
桌面端换成官方客户端(desktop 0.2.0-rc.2 / web 前端 0.2.0-rc.2)后,点阵背景又"消失"了 ——
这次不是类名哈希变了,而是 UI 多了一层不透明容器。
根因
Windows 桌面端会在 <html> 上打 data-windows-titlebar,配套规则
[data-windows-titlebar] .*_centerCol{background:var(--dsw-alias-bg-base)} 给中列
(会话所在的那一格)铺了一层实色底。中列是 ConversationRoot 的父节点,所以只把
frame / root 透明化已经不够:鲸鱼照常渲染、只是被这一层整片盖住 —— 引擎满帧、设置页正常、
控制台安静,看起来就像插件没了。
旧的自发现救不了它,而且是设计上救不了:后缀自发现只回答"我名单上那几层这一版叫什么",
UI 一旦新增一层,名单里就没有它,也不会有任何告警。
0.4.0 的修法分两层
1. 补上中列(快路径) —— UI_SOURCES 增加 centerCol,并注入
body[data-dsh-whale] .*_centerCol{background:transparent}(特异度 (0,2,1) 高于 UI 的 (0,2,0),
不需要 !important);FALLBACK_BY_VERSION 补 0.2.0-rc.2 一行;macOS 的
[data-platform=darwin] .*_centerCol 同一规则一起覆盖。
2. 结构自发现(关键) —— 每次挂载(= 每次启动客户端 / 刷新页面)以及之后每次
DOM 变化 / 尺寸变化 / 主题切换(限流 1s),从会话内容沿祖先链走到 <body>,
凡是计算样式不透明的祖先就地打内联 background: transparent !important 中和,
卸载时原样还原。
这一步把"识别"从闭环改成开放式枚举,所以以后 UI 再新增铺底容器都不用改插件:
内联 !important 压过任何样式表规则(包括对方也写了 !important),既不用跟着升级改名单,
也不用再算特异度。
只走祖先是刻意的:小面(缩略图 / 卡片 / 代码块)是会话的后代,菜单与浮层是兄弟,
槽锚点 div[data-slot] 是 display:contents(不生成盒子)—— 它们都挡不住 z-index:-1
的画布,所以既不需要也不应该动。
自检(不再静默)
- 控制台会报告中和了哪几层(
结构自检:已中和 N 层不透明祖先 → …); - 真出问题会明确告警:"仍有不透明祖先没能中和" / "找不到内容锚点";
- 伪元素(
::before/::after)是内联样式唯一管不到的地方,只报告不自动改
—— 窗口顶部 40px 标题栏那条是有意保留实色的; - DevTools:
__dshWhale.obstructions()(锚点 / 完整祖先链 / 已中和 / 残留 / 伪元素)、
__dshWhale.rescanChain()。
验证
maintain/verify-ui-fix.mjs 在 26 项断言下全部通过,其中包含本次失效形态的回归:
把一个插件从未听说过的不透明包裹层插进祖先链,要求它被自动中和;同时断言兄弟列、
后代卡片不许被碰、卸载后内联样式必须原样归还、找不到锚点必须告警而不是静默。
English
DSH's official client (desktop 0.2.0-rc.2 / web 0.2.0-rc.2) paints an opaque background on
the centre column under html[data-windows-titlebar]. That column is the conversation's
parent element, so the backdrop was covered whole while every diagnostic stayed green —
and the old suffix-based discovery cannot catch it by design, because it only resolves real
names for a fixed list of layers.
0.4.0 adds centerCol to that list (cheap fast path) and, more importantly, an open-ended
structural pass: on every mount — and on DOM/resize/theme changes, throttled — it walks
from the conversation up to <body> and inline-neutralises any ancestor that paints a
background, restoring the exact prior values on teardown. Inline !important beats any
stylesheet rule, so there is no specificity arithmetic and no future class-name list.
Only ancestors are touched; small surfaces are descendants and menus are siblings, so they
are never affected. It reports what it neutralised, warns on residue, and exposes
__dshWhale.obstructions().
maintain/verify-ui-fix.mjs runs 26 Node assertions, including "insert an opaque wrapper the
plugin has never heard of and require it to be neutralised".
dsh-whale-particles v0.3.1 ?????????????????(???????????????)
????????? 2.0.13(Web ??? 0.1.5-rc.2)??UI ??CSS Module ??????????????(_1qAH1q -> qNbT7G??uPhUma->_0cyzDW`),0.3.0 ??????????????????????????????
????????????,???????????????????????????????
0.3.1 ??????????????????,???????????????:
- UI ????????? CSS ?????
<style data-plugin-css="<??/<???>.module.css">;
???????????*??????**(_frame/_sidebarCol/_root/_composerSeat),????????????? ?????????"??? + ???"?????????,UI ??????????????????MutationObserver????????- ????????????????????console.warn??'???,???????????- ??? DevTools ???:__dshWhale.selectors()(?????? + ???discovered/fallback)??__dshWhale.rescan()??__dshWhale.apply()??- ????????????????a???????????????????;_root????????????????????'??? (HeroShell/InputBar/TodoPanel/ContextMeter???_root,?????????? ?????? Chromium + ??? app ?????CSS ???????????????(13 ????????): ??????????????????????????????omposer ?????? 380px??anvas ????????????????z-index:-1` ?????
Desktop 2.0.13 / web 0.1.5-rc.2 renamed every CSS-module hash again
The hardcoded transparency and hero-composer rules stopped matching, so the UI went opaque
and the background stayed hidden. 0.3.1 resolves the live class names at runtime (UI packages
inject their CSS as <style data-plugin-css="<pkg>/<module>.module.css">; the suffixes
_frame/_sidebarCol/_root/_composerSeat are stable, only the prefix is a build hash),
re-scans when a UI stylesheet lands later, and keeps the old values only as a fallback that
now warns instead of failing silently. DevTools: __dshWhale.selectors().
Verified against the installed app's real CSS modules in real Chromium (13/13 checks).
dsh-whale-particles v0.3.0
首个 npm 发布版本。
- 每颗粒子独立、低频、平滑的外力;手动模式随鼠标扰动,自动模式 360° 扫波 + 全屏呼吸。
- 主体可切换:鲸鱼剪影,或导入透明 PNG(按 alpha 取主体,仅存本地)。
- 参数面板:设置 → 插件 → 「鲸鱼点阵背景」,六组共 21 行,手动·自动 / 鲸鱼·照片为高亮分段控件。
- 主界面无悬浮条;空会话输入栏整体下移。
安装:dsh plugin --profile desktop add dsh-whale-particles
npm: https://www.npmjs.com/package/dsh-whale-particles
资产说明:
- dsh-whale-particles-0.3.0.tgz —— 预构建包(目录站 tarball 字段指向它)
- dsh-whale-particles-offline-v0.3.0.zip —— 离线一键安装包(含 install-whale.bat)
v0.1.1 — Desktop 2.0.5+ 适配修复
修复:界面透明化与空会话输入栏下移所依赖的 CSS Module 哈希类名在 DSH Desktop v2.0.5(web 前端 0.1.2-rc.1)变了。
pI_x6G/wSkVaW/ydkMvW→_1qAH1q(布局)与uPhUma(会话/输入栏)- 已在 Desktop 2.0.9 上实测:前缀未再变化,仍然有效
- 点阵引擎、参数面板、照片模式逻辑未改动
附件
dsh-whale-particles-0.1.1.tgz— 预构建包,市场条目里的 tarball 指向它dsh-whale-particles-offline-v0.1.1.zip— 免命令行离线安装包(Windows 脚本)
给 DSH Web 加一个点阵互动背景:点阵可组成鲸鱼或你上传的透明 PNG 照片,每颗粒子有自己独立的平滑外力;既能手动随鼠标扰动,也能切换自动屏保模式(360° 任意方向扫波 + 全屏呼吸躁动),参数面板支持恢复默认。
Add files via upload 对方机器怎么装(两种方式): 双击 install-whale.bat (PowerShell 已在内部绕过执行策略,无需任何命令行知识)→ 显示“安装完成”→ 重启 DSH Desktop 即生效; 想手动理解原理:把 dsh-whale-particles\ 拷进 C:\Users\<对方>\.dsh\profiles\desktop\node_modules\,并在同目录 package.json 的 dsh.profile.bundles 里追加 "dsh-whale-particles",重启即可(安装脚本本质就是自动做这两步,且先备份 package.json)。