罗德岛通讯与技术部

RhineLabUI 拆解

RHINELAB UI TEARDOWN

1. 资产:只有三类能直接用 —— MiSans 字体包、三轨原创配乐、自建模型 + 建模脚本。其余要么是商业授权(Novecento)、要么明确"未声明 MIT"(GLB 成品、参考图、verification 产物)、要么带原作采样(PV 打字音)。

状态:🟢 现行 · 最后核对 2026-09-18 上游:RhineLabUIMIT License,Copyright (c) 2026 LBEILC)· 本地副本 G:\refs\RhineLabUI 我们的复刻:blog/terminal/,已上线 https://blog.manana.icu/terminal/ · 授权逐项说明见 blog/terminal/NOTICE.md 环境:Blender 4.5.9 便携版在 G:\refs\tools\blender-4.5.9-windows-x64\(重建模型用,不必装进系统)


0. 结论(三句话)

  1. 资产:只有三类能直接用 —— MiSans 字体包三轨原创配乐自建模型 + 建模脚本。其余要么是商业授权(Novecento)、要么明确"未声明 MIT"(GLB 成品、参考图、verification 产物)、要么带原作采样(PV 打字音)。
  2. 思想:最值钱的两样是 「内容与代码分离 + 硬校验器」「50 个 check-*.mjs 客观断言」 —— 这两样恰好是我们最缺的(我们的内容是 .char/剧情/记忆,校验是事后审计;前端几乎没有断言)。
  3. 技术:挑自包含模块搬(滚动数字、HUD 投影、惯性拖拽、字形烘焙、PWA 更新流);聊天主界面不加 3D主前端不引入构建frontend/ 的无构建发布是优点,别为一个动效破掉)。

一句话:抄资产是省钱,抄思想是补课,抄交互要看场景 —— 我们不缺三维能力,缺的是把内容变成可用数据的管线能自动验的工程习惯


1. 他们用了什么(客观清单)

1.1 规模

数字
TS 源码 74 个文件 / 12,690 行(最大:scene.ts 1840、main.ts 1273、audio.ts 664、model-viewer.ts 622)
CSS 14 个(style / theme / startup / responsive / decryption / workbench / wallpaper* …)
自验脚本 50 个 check-*.mjsscripts/ 合计 4,866 行
内容 content/archives.json 36.5 KB(页面与 TXT 下载共用)
资源 GLB 3.4 MB + 3.2 MB、音频 3.0 MB、MiSans 字体 26 MB
构建 npm run build2.1–2.4 秒,产物 818 文件 / 33.8 MiB

1.2 架构骨架:一个 canvas + DOM HUD

三维场景全部画在单个 WebGL canvas 里,文字/按钮/面板是真正的 DOM(所以排版质量和可访问性都在)。两者用坐标投影对齐:

  • hud-projection.ts(126 行)—— 把 DOM 元素投到三维坐标上;左上品牌区、右下 POWERED BY、底部键位提示都靠它定位。
  • viewport-layout.ts / viewer-camera.ts —— 参考坐标系在影片与 1920×1080 之间保持精确。
  • shared-depth.ts / glass-reveal.ts / inspection-overlay.ts —— 磨砂玻璃揭示、检视覆盖层、共用深度。

这是"3D 界面还能读"的关键分工,也是我们将来做任何三维视图时唯一正确的起点。

1.3 内容管线:内容与代码彻底分离

content/archives.json 一个文件决定全部档案;src/data.ts 只有 35 行类型与阵列位置映射:

export function fileLocation(index) {           // 三维墙上的物理位置
  const lane = archiveColumns.indexOf(records[index].category);
  const row = 12 + columnFiles(lane).indexOf(index);
  return { lane, row, slot: lane * 32 + row };  // ⇒ 每列上限 20 格
}

scripts/archive-content.mjs硬校验器:分类必须 5 个且两处同名、id 必须按序、source 必须是合法 http/https、每列条数达标;校验失败就不写任何下载文件

content/README.md 把"改内容"写成了一套流程:改 JSON → npm run export:archives 校验并导出 TXT → npm run check:content 检查一致性 → npm run buildprebuild 钩子会自动跑校验与导出。

1.4 自验套件(50 个 check-*.mjs

每个特性一个脚本,playwright 驱动,做客观断言而非人眼比图。覆盖到很细:check-archive-drag(拖拽)、check-archive-momentum(惯性)、check-decryption(解密时序)、check-boot-hudcheck-font-loadingcheck-pwa-recoverycheck-responsivecheck-model-precisioncheck-bottom-insetscheck-hud-performance……

还有 summarize-performance.mjs / summarize-model-precision.mjs 把结果汇总成报告。

1.5 授权与合规(三层,写得很规范)

内容 结论
代码 / 建模脚本 / 技术文档 MIT © 2026 LBEILC ✅ 可商用可闭源,须保留声明
资源(GLB、图片、动图) "未另行声明为 MIT" ❌ 成品不可搬;但建模脚本是 MIT,可自行生成
第三方 MiSans(小米,带许可 PDF)✅ / Rolling Number(MIT)✅ / Novecento Sans Wide(商业) ❌ / PV 打字采样 ❌ 逐项区分

README 甚至写明「《明日方舟》相关名称、标志、设定、原 PV 和原作视觉设计,不因本项目公开而获得额外授权」——这套"逐项声明 + 明确划出不可授权部分"的写法,值得我们以后开源/公开任何东西时照抄

1.6 我原本没预料到的部分

模块 是什么
archive-play-motion.ts + archive-playground.ts 互动关卡RelayRound / SpectrumEnvelope / score / status: preparing→…,一套节奏判定玩法
workbench.ts + workbench-state.ts + workbench-rolling.ts 工作台模式:番茄钟(focus / break 状态机)+ 滚动文字面板
wallpaper*.ts(8 个模块) Wallpaper Engine 集成:属性同步、宿主检测、图像 URL 处理、画质分档、开场衔接
typing-rhythm.ts 打字节奏与开场动画同步(决定何时发按键音)
quality-renderer.ts / render-quality.ts / render-state.ts 画质分档与"精确状态比较,绝不跳变/吸附动画"

2. 逐项对照:抄不抄 / 用在哪 / 成本

A. 可直接搬(自包含,不依赖上游资源)

模块 它做什么 搬到哪 成本
rolling-clock.ts + workbench-rolling.ts + 打过补丁的 @kitlangton/rolling-number 滚动数字/文字(补丁修了投影 HUD 的字形测量) 好感度、天数、统计、终端标题
pwa.ts 的更新流(配合 build-pwa.mjs "新版本已就绪 / 更新并重启" frontend/sw.js —— 我们被缓存坑过多次 低 ★
ui-transitions.ts 统一缓动常量(cubic-bezier(0.22,1,0.36,1) frontend/css/ 过渡统一
screen-finish.ts SVG 滤镜做屏幕质感(把 WebGL + DOM 一起处理) 全局质感层
brand.ts光学间距做法analysisPositions 手工调字距,逐字母绝对定位) 让固定字标排版精准 品牌标题 / 干员名
html.ts 纯文本转义 + HTML 属性安全 前端渲染用户输入
hud-projection.ts DOM 元素投到三维坐标 将来做三维视图时的 HUD 对齐
archive-drag.ts 拖拽的投影反解 + 惯性动量ArchivePlaneMomentum 消息列表 / 联系人的横向滑动
boot-lettering.ts + scripts/make-boot-lettering.py + boot-lettering-art.json 字体烘焙成 SVG path → 逐字符描边动画 frontend/intro.html 开场 —— 成本低、质感提升最大 中 ★
50 个 check-*.mjs 的方法论 每个特性配客观断言 frontend/ 关键路径 中,但收益最大 ★★

B. 要改造才能用

模块 它做什么 改造点 成本
三轨原创配乐(atmosphere / motif / pulse ogg,MIT) 环境音 / 音效 / 氛围层 接进 frontend/assets/audio/,做音量与场景切换
content 管线(JSON + 校验器 + export + prebuild 内容与代码分离、校验失败不产出 .char / 剧情 / 记忆做写入口前置校验(替代事后 char-audit 中 ★★
workbench.ts 番茄钟 + 状态机 可做成独立小工具页
quality-settings.ts / render-quality.tsSUPER PERFORMANCE 画质分档、降低分辨率保留动效 我们已有同类开关,可互相对照
archive-play-motion.ts / archive-playground.ts 节奏互动关卡 若做养成互动可参考判定与计分结构
wallpaper*.ts(8 个) Wallpaper Engine 集成 若要做桌面壁纸(依赖 WE 的 web 宿主)
theme-material.ts 运行时向材质注入主题色 仅当有 3D 且需要主题联动
档案盒模型 + art/*.py Blender 程序化建模 → GLB 最适合 UE 项目ue-home-screen / ue-intro-animation 正缺克制的中性道具)

C. 明确不抄

原因
三维档案墙本体(scene.ts 1840 行等) 展示型交互模型(状态少:浏览/选中/读取),不适合聊天主界面。它已经在 blog/terminal 用掉了
Novecento Sans Wide 商业授权,上游脚本本身就在非官方构建时跳过它
PV 打字采样(typing-preview.wav 上游明确"不纳入 MIT";我们已删除并换成程序合成
上游 reference/(15M)、verification/(58M)、art/*.blend "非代码资产未声明 MIT"
把 Vite 构建引进 frontend/ 会破坏"无构建、零工具链发布"的优点。blog/terminal 作为独立子项目用构建是正确的隔离,保持这样

3. 思想层:比代码更值钱的 5 条

  1. 内容与代码分离 + 硬校验器 ★★★★★ 内容换一个 JSON 就换掉整站;schema 由校验器强制,不合法就不产出任何东西。我们的 .char、剧情、记忆都是"内容",但校验停留在事后审计char-audit)。把校验器搬到写入口,比再写十个审计 skill 有用。

  2. 50 个自验脚本,而不是一次手测 ★★★★★ 2026-09-18 这几天反复验证了这条路的价值:能用脚本自己看画面,改动才敢做。我们的前端基本没有断言,后端 pytest 长期挂着 90 个失败 —— 这是最大的工程缺口

  3. 一个 WebGL canvas + DOM HUD 的分工 ★★★★ 三维归 canvas、文字按钮归 DOM。任何时候要做 3D,先照这个分工起手。

  4. PWA 的更新流 ★★★★ 我们被缓存坑过(博客 CSS 陈旧、本地白屏),上游有完整的"新版本已就绪 → 更新并重启"。

  5. 逐项声明授权,并明确划出不可授权部分 ★★★ NOTICE.md 那种写法:代码 MIT、资源未声明、商业字体排除、原作视觉不获授权。以后我们公开任何东西都可以照抄这套结构。


4. 落地路线

P0(低成本、立刻可感)

  1. 自托管 MiSans —— 用 blog/terminal/public/fonts/ 那套(按 unicode-range 分片,浏览器只下载命中片;带许可 PDF,须保留署名)。
  2. PWA 更新流不搬 ✓ 2026-09-18 更正:frontend/sw.js 里已明确写着"刻意没有 fetch 处理器,也永远不要加",且项目已有 ?v= + no-cache 回源 + nginx 分路径的缓存体系(scripts/bump_frontend_cache.py)—— 上游那套更新提示依赖 SW 的 waiting 状态,搬过来等于在既有缓存体系外面又套一层,会重现"旧 main.js + 新 settings.js"的白屏。我们的方案更好,这条从待办里删除。
  3. 修掉两处已知不一致(见第 6 节)。

P1(收益最大)

  1. frontend_smoke.mjs 的路径覆盖 —— 2026-09-18 更正:上游那 50 个 check-*.mjsplaywright 截图/像素断言,与我们的路线不同,不要照搬 ✗。我们已有的 scripts/frontend_smoke.mjsesbuild + jsdom,而且它自己写着"只回答一个问题:打开页面会不会炸"、"断言 DOM 长得对不对" —— 这是对的 ✗✗像素测试。该做的是在同一骨架上加路径navigateTo(...) 每个一级页、设置开关持久化、深聊自动续上、改名、表情上传。
  2. .char / 剧情内容的前置校验器 —— 抄 archive-content.mjs 的结构:字段必填 + 枚举 + 编号顺序 + 引用完整性,失败即拒绝写入。

P2(体验升级)

  1. intro.html 用字形描边开场(上游现成的烘焙管线)。
  2. 应用内嵌档案入口:复用 blog 那套数据(make_docs.py 已跑通 docs → 结构化),先纯 DOM。
  3. 三维档案墙进 app —— 最后做,必须懒加载、只在非输入场景。

与产品目标的直接结合点

产品目标是「干员被问起剧情记忆时能准确回复,并明白自己与博士的关系、处境与时间线」。档案终端恰好可以是这件事的可视化出口

用户问「你还记得那件事吗」→ 干员回答的同时,档案墙亮起对应几格(数据来自同一份 docs + 剧情数据)→ 用户看见她记得什么、依据是什么

这解决的是信任问题:干员记忆目前对用户是黑盒,无法判断是真记得还是在编。把检索结果做成可浏览的档案,记忆就从隐性变成可验证的 —— 而这条管线现在已经在博客上跑着了。

延伸成本很低:blog/make_docs.py 已经把「docs/ → 结构化档案」跑通,同一条路可以直接吃 .char 干员卡、剧情事件、关系抽取结果 —— 一个生成器,三种内容


5. 已经落地的(本仓库现状)

位置 内容
blog/terminal/ RhineLabUI 的本地化复刻,品牌为罗德岛;部署在 /terminal/(子目录,vite base=/terminal/ 避开博客的 /assets/
blog/terminal/art/*.py + Blender 4.5.9 用上游 MIT 建模脚本自行生成两个 GLB(盒面/模压文字已改为自有品牌)→ 100% 属于我们
blog/terminal/src/typing-samples.ts 打字音效改为程序合成;PV 采样、来源记录、提取脚本已删除
blog/terminal/NOTICE.md 逐项来源与授权说明(抄的就是第 3 节第 5 条)
blog/make_docs.py docs/ → 终端档案数据 + 博客文档索引(一个生成器,两处消费
blog/build.py 55 篇 /docs/<slug>.html + /docs.html 索引;生成时脱敏(凭据 + 公网 IP)
blog/scan_sensitive.py 公开前排敏扫描器(16 类规则,默认扫产物,--source 自检)
scripts/blog_deploy.sh 发布时先构建终端、合并进 dist/terminal//terminal.html 作跳转

6. 与本仓库既有决策的对照更正(2026-09-18)

写这份拆解时有两处建议与本仓库既有决策冲突,此处更正,避免后续会话被误导:

我原来的建议 实际情况 结论
搬上游 PWA 更新流 frontend/sw.js 明确"永远不要加 fetch 处理器",且已有 ?v= + no-cache + nginx 分路径体系 不搬,我们的更好 ✓
抄上游 50 个 check-*.mjs 我们用 esbuild + jsdom 做"会不会炸"的冒烟,明确拒绝像素级/长相断言 改路线:在同一骨架上加路径覆盖 ✓

教训:借鉴前先读目标仓库里"为什么不做某件事"的注释 —— 上游的好东西不一定适合我们,而我们自己拒绝过的方案往往已有更硬的理由。

6. 待办与已知不一致

  • [ ] blog/terminal/content/README.md 仍写着「四十份档案 / 每列恰好八份」✗ —— 我们已扩到 55 篇、每列 13/11/9/11/11,校验器也放宽为 5–100 条 / 每列 ≤20 格(依据 slot 编址上限)。这份 README 要同步改写。
  • [ ] 扩容量后 npm run check:content 未重跑,可能仍在按旧的 40 条规则断言。
  • [ ] 排敏扫描报告 blog/data/sensitive-scan*.json 含命中上下文,已加入 .gitignore不要提交
  • [ ] 标志形状待定:现在的圆+轴看起来像 +○-,可收成纯 或换塔形(改完重跑建模 + 构建 + 发布约 3 分钟)。
  • [ ] 上游 scripts/ 里 50 个 check-*.mjs 直接可用(同一套 playwright + 本机 Edge),但断言里含上游品牌字符串(如 check-boot-hud.mjs 断言 'RHINE LAB')——要跑就得先改这些期望值。