dsh-star-boot · 星图开机
DSH 启动时的整屏片头:星尘 → 她的名字 → 一张状态卡 → 「接入确认」逐条点亮 → 溶解进对话界面。
参考 dsh-550c-boot 的机制(首帧遮盖),动画完全自己写,不搬它的素材和代码。
0.0s ┌ 黑场 + 星点缓慢浮现
0.5s │ 细线 + 眉标:DEEPSEEK HARNESS · 星图
0.8s │ 「小肥鱼」逐字点亮(模糊 → 清晰)
1.0s │ 立绘从 1.07 缩放到 1,浮起
1.2s │ 打字机:"> 记忆 106 条,星图已接上"
1.6s │ 状态卡浮起:记忆 / 心情 / 压力 / 最在意 / 链路(四盏灯,1.9)
2.4s │ 一闪(过曝洗白)→ 片头退场,立绘压暗
2.9s │ 接入确认:01 身份 / 02 记忆 / 03 关系 / 04 通道 / 05 链路,逐条点亮
4.5s │ 停住等人:点击 或 Enter 进入(2.6 秒没人动就自动进)
└ 两段式溶解:内容先淡,背景后淡 → DSH
Esc 或点击跳过。prefers-reduced-motion 下退化成 0.6 秒的静态卡。
近期更新
最新的在最上面。完整历史见 CHANGELOG.md。
| 版本 | 日期 | 一句话 |
|---|
| 1.9.4 | 11-05 | 修:自更新打 GitHub 一直 fetch failed(Node 不认系统根证书) |
| 1.9.3 | 11-05 | 修:四盏灯会拖慢片头——改成并行 + 260ms 预算,赶不上就不画 |
| 1.9.2 | 11-05 | 在「设置 → 插件」里显示成「星图开机」,不再是包名 |
| 1.9.1 | 11-05 | 「05 链路」点名是哪盏没通;四盏全亮时不再显示超时兜底文案 |
| 1.9.0 | 11-05 | 和「灵犀 QQ 桥接」连上了:片头多一格「链路」,灵犀页多一张片头卡 |
为什么要有宿主那一半
DSH 的启动卡([data-dsh-boot],"Loading plugins…")由 shell 在客户端插件被求值之前就画好了。550C 的作者在自己的机器上量过:卡 67ms 出现、插件 338ms 才求值、卡 517ms 才被销毁。所以任何「先在插件里画点什么」的做法都必然慢半拍。
唯一比 shell 更早的东西是送达的文档本身:webserver/index-inject 允许插件往 index.html 的 <head> 里塞行。这里塞的是
- 一行
style:html.star-first::before 把整页涂成 #04070f;
- 一行同步
script:加 class、暴露 window.__dshStarFirstFrame.end()。
于是启动卡从来没出现过。客户端的片头在自己的 shadow root 上屏的同一个 task 里调 end(),一次绘制,没有中间态。
兜底:脚本盯 [data-dsh-boot](shell 撤卡时收遮盖)并在 12 秒后无条件让开;客户端还有 20 秒保险。窗口如果是最小化启动、rAF 被节流,定时器照样把动画踢起来。
状态卡的数据
lib/index.js 去问 qq-bridge:
GET http://127.0.0.1:3100/api/boot/card
{ ok, role, mood:{emotion,intensity}, moodLong, stress,
memories:{total,core,long,short}, people, closest:{name,score} }
页面不跨域:它只访问同源的 /dsh-star-boot/card,由宿主进程代取。桥接没开、没起来、或者这台机器根本没装桥接,都得到 ok:false,动画自动退化成「简洁」——只有片头,没有卡、没有授权页,也没有假数字。
1.9 起多了一格「链路」
同一个 /dsh-star-boot/card 里会多一个 lights(宿主顺手问一句 http://127.0.0.1:3100/diag):
lights: { dsh, snowluma, onebot, qq, note } // 四盏灯 + 一句「哪盏为什么灭」
片头的状态卡就在「最在意」右边多一格四盏灯,授权页多一行「05 链路」。
读不到就没有 lights 这个字段,客户端不画那一格——四盏灭灯的假象比没有更糟。
授权页那行说清楚是哪一盏没通(例如「DSH 未通」「OneBot · QQ 未通」),只丢一句「待修」等于让人自己去猜。
为什么是问桥接而不是问灵犀插件:片头比任何 DSH 插件都早,不能依赖别人还在跑。两条环境变量可以改指向(测试用):
STAR_BOOT_BRIDGE、STAR_BOOT_DIAG。
和灵犀(QQ 桥接)连上了
两个插件互相不认识对方的代码,只认槽位名和接口:
| 方向 | 做法 |
|---|
| 星图开机 → 灵犀页 | ctx.slots.inject('settings.qq-bridge.card', …) 注册一张卡:片头模式 / 背景 / 此刻会显示什么 / 四盏灯 / 预览片头 / 打开片头设置 |
| 灵犀页 → 星图开机 | 上面那格「链路」,数据来自桥接 /diag |
所以装了两个插件之后:设置 → 灵犀 · QQ 桥接 → 系统 标签最下面是片头的遥控器;
开机片头本身能看出四盏灯亮了几盏。灵犀没装、槽位不存在时,inject 不回调,等于本插件不知道它存在;
反过来也一样——谁缺席都不报错。
素材
两样东西可以由用户换成任意本机文件。换法有两种:
- 点「选择文件…」——弹出系统文件对话框,选完直接导入(页面把字节交给宿主,宿主拷一份进
<DSH 数据目录>\star-boot-assets,再把那份的绝对路径写进配置);
- 直接填绝对路径——适合文件很大、或者不想多存一份的时候。
拖进控制页也行,按扩展名自动判是背景还是音效。
GET /dsh-star-boot/asset?slot=bg 背景:图片或视频,没配就用随包 hero.webp
GET /dsh-star-boot/asset?slot=audio 音效:没配就 404(不拿默认音骗人)
GET /dsh-star-boot/config 两个槽位现在指向哪、能不能用、为什么不能用
POST /dsh-star-boot/config { slot: "bg"|"audio", path: "C:\\..." },空串恢复默认
POST /dsh-star-boot/upload 原始字节;头里带 x-star-slot / x-star-name
路径必须是绝对路径,文件要存在、大小在限内、扩展名认识;否则 400 并说明原因(reason: missing / too-large / unsupported-type)。上限:背景 64MB,音效 24MB。
上传会替换并清理:换掉的那份如果本来就是拷贝进 star-boot-assets 的,会被删掉,不会越堆越多;用户自己指定的外部文件永远不动。
为什么不是「记住你选的本机路径」:浏览器里 <input type=file> 只给得出 File 对象,拿不到绝对路径;Electron 的 webUtils.getPathForFile 又需要 preload 暴露。收字节是唯一在所有 DSH 版本上都成立的做法。
设置
两个入口,都是 DSH 页面里的原生 UI:
- 设置 → 左侧导航「星图开机」(自己的一页,控件齐全)
- 设置 → 通用 → 「星图开机」(只放模式 + 预览的一行)
2026-11-02:profile 里 - id: ui-settings 原本是 enabled: false,整页设置被关掉,
所以两个入口都无处显示(550C 的设置行同理,从来没出现过)。已改成 true。
| 控件 | 作用 |
|---|
| 关闭 / 简洁 / 状态卡 | 三档。简洁=只有片头;关闭=DSH 原样启动 |
| 预览 | 不用重启,立即重放一遍(用当前配色/节奏/授权页设置) |
| 配色 | 星蓝 / 暖光 / 靛紫 / 素白 —— 四组 CSS 变量,只换色不换版式 |
| 主色 | 任意 #rrggbb,覆盖上面四套的强调色;「用配色自带」清掉 |
| 字体 | 默认 / 衬线 / 等宽 / 圆体 —— 只作用于片头那个大字 |
| 标题大小 | 70–140% |
| 立体字大小 | 60–160% |
| 节奏 | 快 0.72× / 标准 1× / 慢 1.35× —— 只缩放"等待",交互和淡出不变 |
| 授权页 | 开 / 关 |
| 立体字 | 右侧那个词开 / 关(词本身在「文案 · 立体字」里改) |
| 字样式 | 玻璃(细体大写 + 渐变 + 慢浮,字下垫一层柔光)/ 描边(只剩一条发丝线)/ 挤出(原来那套 10 层 3D,深色底才好看) |
| 字透明 | 0–100%,默认 60 |
| 文字叠加 | 名字 / 眉标 / 打字行 / 状态卡是否压在素材上;关掉后只剩画面(视频背景时尤其有用) |
| 音效 | 开 / 关。默认关;开的时候才会去读音频槽 |
| 素材跟播 | 开 / 关。默认开:背景是视频时,等它完整播过一遍再进场(最多 15 秒)。判定靠「最远播放时间」而不是 ended,所以短片在片头演完前就播过一圈时立刻走;视频没在自动播放或卡住超过 1.5 秒也会立刻走,不会定在某一帧上干等 |
| 音量 | 0–100 |
| 素材 · 背景 | 点「选择文件…」挑(任意本机图片 webp/png/jpg/avif/gif 或视频 mp4/webm/m4v),也可以填绝对路径;留空用随包 hero.webp |
| 素材 · 音效 | 点「选择文件…」挑(mp3/wav/ogg/m4a/aac/flac),也可以填绝对路径;留空=没有声音 |
| 素材适配 | 铺满(cover)/ 完整(contain) |
| 素材位置 | 九宫格 object-position |
| 压暗 | 0–100,素材那层的不透明度(想让画面更亮就往上调) |
| 遮罩 | 0–100,文字底下那层暗角。默认 80;调到 0 就是完全不加暗,画面最清楚 |
| 面板位置 | 左下 / 左中 / 左上 / 右下 / 右中 / 右上——名字、状态卡、授权页一起挪 |
| 模糊 | 0–20px(模糊时自动放大一点,免得露边) |
| 文案 · 名字 | 片头逐字点亮的名字,授权页「01 身份」也用这个(默认 小肥鱼) |
| 文案 · 眉标 | 名字上方那行小字(默认 DEEPSEEK HARNESS · 星图) |
| 文案 · 接上 / 没接上 | 打字机那两句;{mem} 会替换成记忆条数 |
| 文案 · 立体字 | 右侧转的那个词,留空整块不建节点 |
| 文案 · 跳过提示 / 授权提示 | 底部两行小字,留空即隐藏 |
| 恢复默认文案 |
设置页万一又不可用,还有一张同源控制页:http://127.0.0.1:<DSH端口>/dsh-star-boot/panel——同样的全套控件,外加「导出配置 / 导入配置」两个按钮(一段 JSON,可以贴给别人,也可以存起来当皮肤)。
存储:模式/配色/节奏/授权页/外观/文案都在 localStorage(键名 dsh-star-boot:*,文案是一整个 dsh-star-boot:text 对象),素材路径在 $DSH_HOME/star-boot.json:
{ "version": 2, "assets": { "bg": "D:\\art\\a.mp4", "audio": "D:\\art\\open.mp3" } }
旧的单素材格式 { "asset": "..." } 仍然认,会被当成背景槽。
缓存这个坑(1.4.2 修):素材对所有槽位是同一个 URL,浏览器按 max-age 把上一份缓存住——换完视频再预览,看到的还是那张旧图。现在两头都堵了:客户端每次上屏给地址加一次性参数 ?v=<时间戳>,宿主那边也只允许协商缓存(no-cache + ETag),不再发一小时的长缓存。视频还额外支持 Range,因为 mp4 的 moov 常贴在文件末尾,播放器要跳到尾部才能起播。
和 550C 共存:装上本插件后,它会把 550C 的片头压住(启动时把 dsh-550c-boot:mode 写成 off,并记下原来的值;在这个插件里选「关闭」时再还原)。两个插件都装着,谁在播由这个开关决定。
逃生口
- 本次跳过、不改设置:地址后加
?star=0
- 彻底停用:profile 的
dsh.profile.bundles 里删掉 dsh-star-boot,或设置行里选「关闭」
文件
lib/index.js 宿主:首帧遮盖、状态卡代理、素材双槽路由 + 上传、同源控制页
lib/client.js 浏览器:动画、授权页、外观变量、设置页
assets/hero.webp 随包素材(默认背景)
test/host.mjs 宿主侧取证:注入行、六条路由、304、Range、坏路径被拒、上传落盘与清理、四盏灯合并/降级
test/page.mjs 设置页取证:无 React 桩渲染三个入口(通用行 / 独立页 / 灵犀扩展卡),验构建不抛 + 控件齐全
test/panel.mjs 端到端:真 Edge 点「选择文件…」,验落盘、/asset、换新清旧
test/cache.mjs 换素材不吃旧缓存:长缓存服务器 + 同一上下文换文件后刷新
test/preview.mjs 真浏览器取证:逐帧截图
测试
$node = "D:\DSH\resources\runtime\node\node.exe"
& $node test/host.mjs # 宿主那一半(67 项,含 1.9 的四盏灯)
& $node test/page.mjs # 设置页这一半(23 项,不用真 React)
& $node test/panel.mjs # 端到端:选文件 → 落盘 → 服务新图
& $node test/cache.mjs # 换素材后刷新:必须拿新视频,不许吃旧缓存
# 逐帧截图(默认落在 .dsh/tmp/star-boot-*)
& $node test/preview.mjs
$env:STAR_BOOT_SCHEME='warm'; & $node test/preview.mjs # 配色
$env:STAR_BOOT_PACE='fast'; & $node test/preview.mjs # 节奏
$env:STAR_BOOT_NO_CARD='1'; & $node test/preview.mjs # 桥接不可用
$env:STAR_BOOT_PRESS='enter'; & $node test/preview.mjs # 验"点击/Enter 推进"
# 任意一组设置:JSON,键可省略 dsh-star-boot: 前缀
$env:STAR_BOOT_STORE='{"accent":"#ff9a6c","blur":6,"dim":72,"fit":"contain","font":"serif","titleScale":130}'
$env:STAR_BOOT_TEXT='{"name":"测试","mark":"小肥鱼","hint":""}'
$env:STAR_BOOT_AUDIO='D:\music\open.wav' # 顺带验音效槽
$env:STAR_BOOT_TAG='custom'; & $node test/preview.mjs
踩过的坑(2026-11-02 21:27,一次崩溃 + profile 被清)
第一次重启后 DSH 直接崩,crash 日志写的是:
Error: web boot: 1 entry did not activate
dsh-star-boot: failed
后果比崩溃更糟:DSH 随后把 profile 清单重置成默认值——dsh.profile.bundles 只剩
base + web-app,cordis.patch.yml 里的 MCP / 预设 / bili / 550C 配置整段消失。
根因三条,现在都封住了:
- 浏览器工件的 id 必须等于包名。写成条目 id(
star-boot)的话装载器按名字找不到 factory。
ctx.slots.register 在槽位未声明时会抛 SlotOwnershipError。本机 profile 里
@deepseek-ai/dsh-client-ui-settings 是 enabled: false,所以
settings.general.item 根本不存在——裸调 register 等于每次开机必崩。
现在只走 ctx.slots.inject(槽位存在才回调),并整段 try/catch。
- 宿主侧每条路由注册也各自兜住:一个路由注册失败不该让条目「未激活」。
不变量:这个插件在任何情况下都不许让条目未激活。 片头可以不播,DSH 必须能开。
设置界面被禁用时的开关
本机把设置界面关了(ui-settings: enabled: false),设置行显示不出来。所以宿主另外
提供了一张同源控制页:
http://127.0.0.1:<DSH端口>/dsh-star-boot/panel
它和 DSH 同源,直接写 localStorage,改完下次启动生效。
出事了怎么恢复
powershell -ExecutionPolicy Bypass -File "D:\DSH\恢复profile.ps1"
把 package.json 与 cordis.patch.yml 恢复到「没有本插件、其它一切正常」的版本。