Initial commit: RHINE LAB · ANALYSIS OS:三维界面与动效实验(本仓库不含个人归档索引数据)

This commit is contained in:
WpyQwq
2026-09-19 13:04:56 +08:00
commit 3f422218a5
1098 changed files with 39410 additions and 0 deletions
+33
View File
@@ -0,0 +1,33 @@
node_modules/
backups/
dist/
.tools/
*.mp4
*.blend1
optix7cache.db
reference/*.jpg
reference/**/*.jpg
art/.cache/
.worktrees/
release/
video/
__pycache__/
*.pyc
.env
.env.*
!.env.example
reference/local-files-connection.js
# Local integration checkout
.main-integration/
# Individually licensed MyFonts kit; install locally, never publish to Git.
public/fonts/novecento/
.vercel/
# 本地归档服务每次启动生成的访问令牌
.archive-token
# 一键启动脚本产生的运行日志与 PID 记录
logs/
+369
View File
@@ -0,0 +1,369 @@
# Rhine Lab UI
## 索引与阵列:当前机制(权威,动它之前先读这一节)
> 下方时间线是历史记录,其中有几段**已经被回退**。读到时以本节为准。
> **不存在**的文件(曾在时间线里出现,现已删除,不要再去找):
> `src/archive-array.ts`、`scripts/check-array.mjs`;`src/data.ts` 里的
> `loadArchiveDataset` / `searchDataset` / `columnCount` / `recordKey` / `indexOfKey` /
> `childrenOfPath` / `datasetSource` / `laneCenter` / `rowPeriod` 也都已移除。
**数据流**:`content/curated.mjs`(手写)→ `npm run archive:snapshot` → `content/archives.json`(生成物)
→ `src/data.ts` → 阵列(`src/archive-loop.ts` / `src/archive-visibility.ts` / `src/scene.ts`)。
`public/archive-index.json` 只服务 LOCAL ARCHIVE 面板与检索,**不参与阵列**。
**现状**:5 个分类,每列条数**不限**(校验器只要求每列 ≥1、总数 ≥5),当前内容 **47 条**,五列 **8 / 9 / 11 / 9 / 10**(刻意不等长,用来证明上限确实放开了)。
阵列窗口是 32 行、内容从第 12 行起编号,`slot = lane * 32 + (row % 32)`。
### 想改数量时动哪里
| 目标 | 动这里 | 别动 / 注意 |
| --- | --- | --- |
| 每列条数、总条数 | `content/curated.mjs` 增删条目 → `npm run archive:snapshot` | **不要手改 `content/archives.json`**,它是生成物,下次生成会覆盖 |
| 单列超过 20 条 | 不用改任何东西 | 交互路径的行空间无上限(`fileAtCell` 按 `files.length` 取模循环);`slot` 已用 `% 32` 防止串到下一列 |
| 分类数量 5 → n | `curated.mjs` 的 `categories`/`columns` **+** `scripts/archive-content.mjs` 的 `names.length !== 5` **+** `src/archive-loop.ts` 的 `POOL_LANES` / `LOOP_COLUMNS` **+** 行内写死的列原点 `(lane - 2)`、`/ 5`、`/ 8`(`archive-visibility.ts:36,42`、`scene.ts` 的相机/动效/取值多处) | ⚠️ 列数一改就动了**原片逐帧对照**的构图基准,必须重新校准;不要只改一处 |
| 刻度条长度 | `src/main.ts` 的 `TICK_WINDOW` | 短列必须走自然顺序分支(见下) |
| 校验规则 | `scripts/archive-content.mjs`;同步改 `scripts/check-content.mjs` 的 `invalidCases` | 校验不过时 `predev`/`prebuild` 会**直接 throw**,dev server 和 build 都起不来 |
### 改完怎么自检(改了阵列/内容/场景就跑这一套)
```powershell
npx tsc --noEmit # 类型必须 exit 0
npm run check:content # 档案数据校验,20 项
node --experimental-strip-types scripts/check-archive.mjs # 相机/抽取几何
node --experimental-strip-types scripts/check-loop.mjs # 循环阵列
npm run build # prebuild 会跑快照+校验+字体
```
⚠️ `check:archive` / `check:loop` **没有** npm script(只有 `check:content` / `check:viewport` 有),
必须用上面的 node 形式跑文件。`check:array` 已随方案回退一并删除,**不要再引用**。
### 已经踩过的坑(改之前先看)
1. **别把每列条数写死成 8。** 旧版校验器与阵列数学都假定每列恰好 8 条;用户要求"每列无限"后已放开,但阵列窗口只有 32 行、内容从第 12 行起 → 每列**超过 20 条**时若 `slot` 不取模就会串到下一列的行上。`data.ts` 的 `slot: lane * 32 + (row % 32)` 就是为这个加的。
2. **刻度条窗口化时,短列必须保持自然顺序。** `main.ts` 的 `tickWindow()` 里 `const start = len <= TICK_WINDOW ? 0 : position - Math.floor(count / 2)`。若一律"以选中项居中",列内 8 条时窗口会绕回来变成 `5,6,7,8,1,2,3,4`——顺序整体转位、高亮卡在正中,看起来就是"滑条坏了"。这个 bug 已经发生过一次。
3. **`fileAtSlot` 是截断、`fileAtCell`(archive-loop)是循环取模**,两者语义不同,不要合并。`fileAtSlot` 服务 boot 与原片对照的固定 160 布局,必须与原版一致。
4. **别再用"往阵列里塞全量归档文件"的方案。** 试过一次(每个文件一张盘),造成检索列表里全是 `00_索引` 的 CSV/MD/TXT、选中列外条目还会错位到第一列的盘上,用户判定"索引完全改坏",已整体回退。要扩内容请往 `curated.mjs` 加条目。
5. **`records` 是静态 import**,模块顶层就会用到它(`main.ts` 里 `columnFiles(...)[0]`、`fileLocation(selected).lane`)。若日后要改成异步加载,必须先处理这些顶层访问,否则启动即崩。
### 开场与退出的入口序列
`src/main.ts` 的 `beginOpening()`:加载完成 → 尝试解锁音频 → logo 停留 `ENTRY_LOGO_HOLD = 1000ms` → 渐出 → 自动播放(**没有点击门槛**,`src/startup.ts` 的 `StartupGate` 已无调用方)。
浏览器要求用户手势才允许启动音频设备,解锁失败时本次开场静音,但**不清空** `prefs.sound/music`,改由 `armAudioUnlock()` 在首次 `pointerdown`/`keydown` 补解锁。
右上角那个 46px 圆形按钮:开场中是 `↗` 跳过(`.skip`),进入档案/详情后变成 `⏻` 退出(`.exit`),两者同坐标不重合。
退出序列(`exitSystem()`,CSS 时序在 `style.css` 的 `.shutdown` 段):白屏 + logo(**不闪动**)→ 字符本体明灭(`shutdownFlicker`)→ 黑条从左扫到**完全覆盖**为止、**不向右滑出**(`shutdownSweep` 无 100% 那一段)→ `SHUTDOWN_WINDOW` 之后 `window.close()`,关不掉则退化为纯白页。
- 2026-09-11:官网已独立集成 Novecento 开场字体;授权 kit 不进入 Git。正式 Vercel 项目在构建时从当前生产版本恢复并校验字体,首次部署使用本机授权 kit 的静态输出。见 verification/WEBFONT-DEPLOYMENT.md。
## 开场中央字体
- 2026-09-11:用户要求中央开场文案中适合的位置统一使用 Novecento Sans Wide。访问、身份、请求、处理、授权提示使用 Normal;欢迎三行使用 Bold。后续按用户纠正与原片叠加比较,左上 RHINE LAB 使用 DemiBold,下方两行保留 MiSans。沿用原时间轴与 HUD。
- 用户后续授权从 MyFonts 领取 Normal、DemiBold、Bold 正式 Webfont 并直接替换,本机已领取并接入。public/fonts/novecento 为独立许可资源,排除 Git;有完整字体包时使用原始 WOFF2,其他检出或加载失败保留既有固定图形。来源、再制作与回归见 verification/BOOT-LETTERING.md。网页年度许可不等于已确认创意工坊分发授权,本轮未上传。
## Wallpaper Engine 实验分支
- 用户追加授权音乐律动、手动点击开始且可隐藏入口的波纹接力小游戏,以及入场详情开关。工作台默认短入场,展示模式默认完整;可强制覆盖。工作台内容轻微错峰淡入,减少动态效果时直接显示。
- WE 属性采用原生可折叠分组和条件显示;选中档案支持保持原样、仅音乐时归齐、始终归齐。原生实现继续沿用,不启用前端或动效 Skill。详见 docs/WALLPAPER-ENGINE.md。
- 用户进一步要求登录入场适配多分辨率并移除底部字幕。正常入场背景与三维画布铺满实际视口,中央图形保持比例;只有显式逐帧对照保留固定 1920×1080 舞台。此前“所有开场固定比例”规则由此更新。
- 用户于 2026-09-10 授权新建 `codex/wallpaper-engine`,在本机 Wallpaper Engine 中试用网页壁纸。当前作为独立实验保留,不合并主分支、不上传创意工坊。
- 沿用现有原生实现和视觉规范;`npm run build:wallpaper` 输出 `release/wallpaper`,网页正式构建保持原流程。
- 壁纸资源全部本地打包,关闭 PWA,自动启动,接入宿主设置、帧率和暂停通知。使用说明与实际验证见 `docs/WALLPAPER-ENGINE.md`。
- 用户明确要求:以目录内视频 5–40 秒为视觉与动效参考,尽量逐像素还原;不使用前端 Skill,由代理选择技术栈。
- 本项目采用参考视频逐帧分析与原生实现流程,不启用主前端设计或动效 Skill。
- 美术资源必须通过 Blender MCP 制作,保留源文件与可复现脚本。
- 技术栈:TypeScript、Three.js、Vite。1920×1080 为布局基准,等比例适应窗口。
- 视觉与时间轴规范见 DESIGN.md。视频仅用于参考与验证,不能作为产品交互背景。
## 阵列拖动与持续惯性
- 用户要求悬停轻抬,拖动浏览阵列,鼠标滚轮切换列内档案;鼠标与触摸共用原生输入逻辑。
- 用户进一步明确需要类似网页快速滑动的持续惯性:快拖与慢拖按实际松手速度产生不同滑行距离,不能采用固定最多两列/三档的追加距离。松手后持续减速,档案随经过的位置依次切换,接近停止时才吸附;再次按住从当前画面接管。
- 用户最新明确选择自由平面拖动:用当前相机的两条投影轨道共同还原屏幕位移,取消横竖快捷手势和斜向锁轴。一次拖动可随时转弯或反向,两个轨道共同衰减并最终吸附。拖动按实际物理位置选择档案并更新列内记忆;键盘和按钮切列继续恢复该列记忆。
- 已确认的波浪、竖直抽取、归位、列内记忆与循环规则继续沿用。实现与验证见 `verification/ARRAY-INPUT.md`。
## 详情与弹窗过渡
- 详情退出约 180ms,快速返回再进入从当前透明度接续;背景遮罩随实际镜头进度变化。抽取、转正、归位与波浪继续沿用已确认的三维规则。
- 检索、收藏和设置弹窗进入约 300ms、退出约 200ms。退出期间保留节点与背景输入隔离,结束后恢复焦点,再执行选档或重播。
- 详情页签使用 150ms 内容淡入与 180ms 下划线移动;收藏仅更新按钮状态、总数和反馈,保留正文节点、页签及滚动位置。减少动态效果时直接切换。验证见 `verification/UI-TRANSITIONS.md`。
## 标注修订约束
- 用户标注对应原片约 27.60、29.16、31.56 秒;相机校准同时检查端面厚度、空隙和前后排边界。
- 同一档案从阵列升起并进入特写时,仅允许竖直升降,不能把模型向前或横向平移来重新构图;靠近与转向由摄像机完成。初始整组阵列入场及切换档案的轨道移动不属于抽取动作。
- 背景卡片不能随特写进度额外下沉来让出画面。
- 交互模式左右切换五类档案列,上下切换同一列的前后档案;每列至少 8 份可读档案。切列保留该列的选择记忆。
- 预览时卡片抬起 0.4;交互详情升至 4.05(原片时间轴仍为 3.35),只比早先略高,必须保留后方阵列的可见层次。镜头负责左侧构图,右侧展示信息。
- 卡片获得净空后才允许拖拽旋转;收回时保持高度先转正,再下降。被换选的旧卡片也遵守此顺序。
- 阵列与抽出卡片的材质、内部细节必须连续过渡,快速切回正在归位的卡片时保留当前升降状态。
- 标签复用主界面的同一份莱茵生命标志,不使用另画的简笔版本。
## 独立模型查看器
- 用户要求在详情页增加 360° 模型查看、方向键平移、滚轮缩放、拆解和一键重组,先提供可体验的版本。
- 独立查看器沿用既有视觉规范与原生实现流程;部件模型通过 Blender MCP 制作,保留源文件与生成脚本,见 DESIGN.md。
- 阵列中的抽取与归位继续遵守上文约束。独立查看器在自己的场景内旋转观察与分层拆解,关闭后回到原档案。
## 循环档案阵列
- 用户确认:主界面的档案阵列上下切档案、左右切列均可持续循环,画面不出现队列尽头。
- 五类档案、每类八份的内容顺序循环复用;跨首尾仍沿按键方向移动到相邻位置,切列保留该列的档案选择记忆。
- 使用固定的可见窗口与外围卡片补位。归位副本按实际循环位置区分,保留快速重新选中时的运动状态。
- 原片参考时间轴继续使用原来的五列、160 个位置;循环只用于交互阵列。相关实现与验证见 DESIGN.md、verification/LOOPING-ARCHIVE.md。
## 当前动效选择与光影微调
- 用户试用后明确选择原始基线 aeddd8f:选中抬起和波浪同时开始,恢复原有负向波谷与 4.2 阻尼速率。此前去波谷、先抬起后波浪及源点静止方案只保留为对照实验,不用于主界面。
- 用户对比后选择恢复整体基线的颜色与光照。生产场景及独立查看器默认采用 baseline 配置;光影微调只保留为对照实验。见 DESIGN.md。
- 档案序号和列编号使用 Rolling Number,保留两位数;X-001 形式的档案编号与悬停提示使用三位数滚动,X- 前缀固定。连续输入延续当前运动,减少动态效果设置关闭滚动。
## 标题与查看器过渡
- 主界面随选档变化的顶部分类、底部列名、悬停标题与权限标签统一复用主标题的文字滚动。相同内容不重复播放,悬停首次出现直接显示;跨列刻度复用固定八个按钮,保留连续伸缩和各列选档记忆。见 `verification/ROLLING-TITLE.md`。
- 用户最新选择:主档案标题改为类似数字的滚动文字,替代此前快切黑条。直接复用已有库的 `createRollingText`,与编号共享 460ms 时长;使用 direct 模式、各字同时启动、轻微模糊与宽度过渡。文字固定向上滚动,快速输入衔接最新目标;进入详情或减少动态效果时直接显示最终标题。见 `verification/ROLLING-TITLE.md`。
- 360° 独立查看器增加进出过渡。进入约 320ms 淡入,模型载入后约 380ms 轻微展开;退出约 220ms 淡出。退出结束前保留模型与模态焦点,结束后释放并返回原按钮;进入中途按 Esc 可衔接退出。
## 前段 2D 动效逐帧修订
- 用户明确范围:黑底的鹰角网络片头不做,直接从画面变白开始。正常启动和重播从原片 6.76 秒进入;本地对照保持原片时间码。
- 继续使用逐帧分析和原生 DOM/SVG 实现,不启用前端或动效 Skill。2D 时间轴见 `src/boot-motion.ts`,渲染见 `src/boot.ts`;规范与复核见 `verification/BOOT-MOTION.md`。
- 已认可的三维交互、基线颜色和档案阵列时间轴继续沿用。启动标志沿共享标志的同一组贝塞尔曲线连续绘制,缺口通过路径首尾控制,保留与档案标签一致的轮廓。
- 用户进一步要求按原片逐帧修订非线性运动。左上三行分别在 278、280、282 帧滑入,各自加速后减速;圆环半径、弧端、内环与橙点使用独立轨迹,名称黑底快速扫过后长尾收束。测量轨迹见 `src/boot-tracks.ts`;局部放大对照及限制见 `verification/BOOT-MOTION.md`。
- 用户进一步强调两侧小圆主要绕自身圆心旋转并补全弧段;仅保留原片测得的轻微位置收拢。中央大点闪变后缩小,六颗小点错峰绕行。Logo 以连续笔画首尾及移动缺口表现运动,不能替换成整枚标志闪烁;加号与方点同帧展开,方点随后拉成减号。见 `src/boot-orbit-tracks.ts`、`src/boot-logo-tracks.ts`。
## 品牌文字与 360° 导航过渡
- 用户要求放大并叠加原片,校准左上角三行文字。保留 MiSans,三行约 270px 宽;中间一行使用 600 字重,ANALYSIS 按原片字母位置分别校准间隔,OS 右对齐。具体尺寸见 DESIGN.md,叠加页为 `reference/brand-review.html`。
- 360° 查看器的平移、滚轮及加减键缩放均平滑跟随;复位约 560ms,按球面短路径返回。复位过程中输入从当前显示位置接管;减少动态效果时直接到位。仅用于独立查看器。
## 声音实现
- 用户要求依据原视频与莱茵生命相关设定重设计音效和背景音乐。本次实现采用原创三轨循环配乐和 Web Audio 音效,详见 `verification/AUDIO-DESIGN.md`。
- 音效与音乐独立开关、独立音量;保留旧静音偏好。启动声音沿用原片时间码,跳帧不补播;三轨在切场景时连续播放并改变比例。
- 听感尚待用户试听,技术验证不视为用户已经确认声音方向。源谱与音色生成脚本必须保留。
- 用户进一步指定玻璃交互应采用清脆音色。当前音效使用薄玻璃轻碰与短共鸣,切档、抽取、归位及拆组有不同的衰减;保留快速输入时的叠音限制。用户尚未确认本版最终听感。
- 用户明确要求复刻原视频已有的逐字输入音效。输入现在使用三个原片短音(各 38ms),保留来源记录;其他卡片交互沿用玻璃音色。输入节奏跟随真实文字揭示,不把故障闪动恢复当作新输入。
- 用户接受档案切换使用玻璃声,并要求检查 OS 页面打开、Logo 与文字出现的声音。窗口开合、系统操作、启动标志、整块文字及验证反馈采用独立电子音色;玻璃声用于档案实体运动。完整开场试听见 `reference/boot-audio.html`,只临时开启声音,结束后恢复偏好。具体音色尚待用户试听。
## 档案内容维护
- 档案内容独立保存在 `content/archives.json`,页面和 TXT 导出共用;修改步骤见 `content/README.md`。五列保持不变,**每列条数不限**(2026-09-12 起;校验器只要求每列 ≥1、总数 ≥5),编号 `X-0NN` 按顺序稳定、宽度随总量自适应,构建前校验;内容按纯文本显示。
- 2026-09-12:`archives.json` 已改为**生成物**,手写内容在 `content/curated.mjs`,由 `scripts/archive-source.mjs` 扫描 `E:\归档` 后写出,直接编辑 JSON 会被下次生成覆盖。五列、每列八份、`X-0NN` 稳定编号的约束不变,校验规则仅在 `source` 上额外接受 `file:` 链接。
- 同期新增本地归档接入:**LOCAL ARCHIVE** 面板检索归档全部条目并执行移动/复制/解压/新建目录等安全写操作,由只监听回环、带每次启动令牌的 `scripts/archive-service.mjs` 承担;归档变动会自动重建快照。使用与安全边界见 `docs/LOCAL-ARCHIVE.md`,实现与实测证据见 `verification/ARCHIVE-INTEGRATION.md`。该面板与四十条阵列互相独立,阵列的数学与三个消费方(`src/scene.ts`、`src/archive-loop.ts`、`src/data.ts` 的位置映射)未改动。
- 2026-09-12:用户要求把详情页的 EXPORT 按钮(原为下载 TXT)改为直接打开本地文件资源管理器中的文件位置。现为 **SHOW IN FOLDER / 文件位置**,用该档案的真实 `path` 调 `POST /reveal`;服务不在线时提示启动方式而非静默失败。`public/archives/*.txt` 仍会生成但详情页不再提供下载入口。同时修掉详情页脚注的 `file:` 参考链接(浏览器不允许从网页跳转 `file:`),改为显示归档内相对路径文本。
- ⚠️ **以下四条(全量归档驱动 → 修正盘的粒度 → 独立详情导航 → 文件夹即列)已于 2026-09-12 当天全部回退。** 留在时间线里只作历史与教训,**不要再按它们实现**;当前机制以本文件顶部「索引与阵列:当前机制(权威)」为准,回退范围见下文「索引与阵列方案整体回退」。
- 【已回退】2026-09-12:**三维阵列改为全量归档驱动**(用户明确选择"真正的全量阵列")。列 = 归档顶层内容分类(当前 7 列),列内是该分类下全部真实归档条目(合计 49,997 条);标题/摘要/研究记录由索引自动派生,用 Proxy 惰性物化,只给被读到的条目生成文本。四十条手写精选档案降级为**备用数据集**(索引不可用时兜底,应用照常启动)与 TXT 摘要来源,因此 `archive-content.mjs` 的校验器与 `check-content` 的约束**保持不变**。
- `data.ts` 重写为双数据集 + O(1) 预计算寻址(`laneMembers` / `laneOf` / `posOf` / `counts`)。旧实现每次调用全量扫描 + `indexOf`,全量下每次输入事件约 19 ms、一次拖动累计约 1.2 秒(注意:**不是**每帧开销,渲染循环不碰 `records`)。改造后 2000 次寻址 38,365 ms → 0.33 ms。
- 引入**全局行周期**(`rowPeriod()` = 最长一列的条数)解决 rebase 取整:各列长度不同,不存在公共周期,rebase 位移必须是全局周期的整数倍才能保持内容不变。
- `fileAtSlot`(**截断**,服务 boot 与 `?time=`/`review=1` 逐帧对照的固定 160 布局)与 `indexAtCell`(**循环取模**,服务交互阵列)语义不同,不可合并。
- 收藏改按归档相对路径键存放(`recordKey`),否则新增文件导致编号位移会让旧收藏指向别的档案。`#file-ticks` 改为固定 8 个按钮的滑动窗口,避免单列上万条时渲染上万个 DOM 节点。
- 列数参数化:`archive-loop.ts` 新增 `configureLoop()`(`LOOP_COLUMNS`/`POOL_LANES` 改为 live binding,前五项恒为真实列以保证固定 160 布局成立);`archive-visibility.ts`、`scene.ts` 的 lane 原点改用 `laneCenter()`;`motion.ts` 增加 `laneOrigin` 可选参数。`fixed` 路径保持 5 列不变(原片对照基准),正常入场走动态路径,因此阶段之间不跳变。
- 新增自包含回归检查 `npm run check:array`(桩掉 fetch 直读快照,可进 CI),断言反向寻址一致、格子互为逆、rebase 平移不变性、列循环回绕、检索不物化、寻址吞吐。设计与证据见 `verification/ARCHIVE-ARRAY.md`。
- 远距离跳转不再长距离飞行:`scene.ts` 的 `ROW_SNAP_DISTANCE = 64`(可视窗口 32 行的两倍)。超长列上 `nearestOccurrence` 最远会给出半列(约 10,337 行)的目标,超过阈值就让 `shoulder`/`rail` 直接落位、速度清零;只作用于行轴,列轴最远半个列数、保留原动画,拖动/惯性/抽取期间显式排除。阈值两侧由 `check-array` 用真实数据钉住。
- 已知未决:7 列下的实机构图与剔除数值需人工确认(无浏览器自动化环境)。
- 【已回退】2026-09-12:**修正盘的粒度**——用户指出"一个项目就是一个 3D 盘,在盘内的页面可以自由访问所有文件"。此前把**每个文件**都做成一张盘(49,997 张)是错的,会导致列表里全是 `00_索引` 的 CSV/MD/TXT、选中这类列外条目还会错位到第一列的盘上。
- 现在 **盘 = 分类下的第 1 层目录**(项目单元),文件与更深的目录都不是盘。列分布变为 `4 / 2 / 11 / 3 / 4 / 4 / 4 = 32 张盘`,列均衡、可浏览。
- 列外条目(`laneOf < 0`)**不再参与** `searchDataset`,也不会出现在检索列表里——它们的正确入口是所属盘的页面,或 LOCAL ARCHIVE 面板。
- 未输入关键字时,检索列表改为列出阵列成员(各列的盘),不再退回 `records` 开头若干条(那正是元数据文件)。
- 详情页新增 **04 盘内文件** 页签:列出当前盘的直接子项(目录在前),目录可逐层进入并带面包屑返回,文件可直接【打开】【定位】。子项表在 `archive-array.ts` 里惰性建一次(一趟遍历),之后 O(1)。
- 副作用:`ROW_SNAP_DISTANCE = 64`(远距离跳转落位)在盘模型下**处于休眠**——最长列只有 11 张盘,最近同现点最远 5 行。阈值保留以应对日后粒度变化,`check-array` 会报告 `snapEngaged` 的值。
- 2026-09-12:用户要求**开场播完直接进入浏览,不要自动展开头一张盘**。原先 `main.ts` 的 `bootFrame` 在原片时间轴走完(t ≥ 35)无条件 `setMode("detail")`,而「入场详情」属性只在 `isWallpaper` 分支里被读过——网页版没有开关,永远自动展开选中的头一张盘。
- 现在该处改为:网页版 `setMode("archive")`;壁纸版仍按 `openingShowsDetail(openingdetail, workbench)` 决定是否展开,行为不变。
- `?scene=detail`、`?time=`/`review=1` 逐帧对照(frozen time 恒 < 35)、以及"减少动态效果"直进阵列等既有路径都不受影响。
- 2026-09-12:用户要求**去掉开场的"点击进入"门槛、启动即自动播放**,并**把 ENTER SYSTEM 做成右上角不起眼的圆形按钮**,以及 logo 先显示 1 秒再渐出。
- 开场入口序列改为 `beginOpening()`:尝试解锁音频 → logo 停留 `ENTRY_LOGO_HOLD = 1000ms` → 渐出 → 自动 `completeStartup()`。`StartupGate` 不再被引用(`src/startup.ts` 保留但已无调用方)。
- **音频的现实约束**:浏览器要求用户手势才允许启动音频设备,自动开场时 `unlock()` 可能失败。此时本次开场静音,但**不再清空 `prefs.sound/music`**(原先 `completeStartup(silent)` 会写死静音偏好,自动开场下那样做等于擅自关掉用户的设置);改为 `armAudioUnlock()` 在首次 `pointerdown`/`keydown` 时补解锁,音乐与后续交互音效接上。开场自身的音效在那种情况下会是静音——这是浏览器策略,不是缺陷。
- `completeStartup()` 去掉了 `silent` 参数;`?time=`/`?review=1` 逐帧对照不加 1 秒停留,避免影响对照工具。
- `.skip` 由文字按钮改为 46px 圆形(`right: 38px; top: 30px`,`opacity: .3`,hover/focus 提到 1),`data-action="skip"` 不变;不再在开场结束后抢焦点。移动端的 `.mobile-entry` 保持原样。
- 【已回退】2026-09-12:用户指出**盘内的文件打不开详情页**——只能看盘(列内成员)的详情。原因是详情页绑在阵列选中项 `selected` 上,而列外的文件/子目录没有盘位(`laneOf < 0`),`fileLocation` 会退化成第一列的盘。
- 引入**独立的详情页导航状态** `detailEntry`(-1 = 跟随阵列选中项),`detailIndex()` 统一取当前应展示的条目。`renderDetail` / `overview` / `setTab` / `contentTab` / `toggleSaved` / `revealSelectedArchive` / 页脚编号全部改用它;`updateSelection` 仍用 `selected`(它渲染的是阵列选中盘的界面)。
- 新增 `openEntryDetail(index)`:详情页可切换到**任意**条目(盘或盘内文件/目录)。盘内文件列表与检索命中里都加了【查看】按钮;面包屑逐级可点,点上级目录即打开那一级的详情页,实现"返回上级"。
- `select()` 与 `setMode("archive")` 都会把 `detailEntry` 复位为 -1,避免回到阵列后再进入时还停在上次看的那一层。
- 顺带修正:`SHOW IN FOLDER`(`revealSelectedArchive`)现在作用于**当前查看的条目**,因此盘内任意文件都能直接在资源管理器中定位。
- 【已回退】2026-09-12:用户进一步明确阵列层级——"**每一个文件夹一个横列,里面的每一个文件就是一个盘;子目录也算盘,可以在详情里继续查看子目录里的文件**"。
- 现在 **列 = 分类下的第 1 层文件夹**(32 列,按路径排序);**列内成员 = 该文件夹的直接文件(盘)+ 直接子目录(项目,同样是盘)**。子目录盘点开后进详情页,用「盘内文件」页签继续往下看,可逐层进入。
- **为什么子目录也要进列**:只放文件的话,`02_游戏/Minecraft` 这类只有子目录的文件夹会既不是列、也不是盘 → **从阵列里彻底消失**(实测 32 个文件夹里有 11 个是这种)。把子目录也算作盘之后 **空列为 0**,所有文件夹都在阵列里可达。
- 实测分布(`check:array`):32 列 / 435 张盘 / 0 空列;最多 251、中位 2、最少 1;10 列只有 1 张盘。列内循环由 `indexAtCell` 的取模保证(到末尾回到第一张)。
- 修复一个被 32 列暴露的真 bug:`archive-loop.ts` 的 `poolCell()` 直接取 `POOL_LANES[floor(index/LOOP_ROWS)]`,而 `selectedSlot = laneCenter()*32+12` 在 32 列下是 508 → 下标 15 越界(`POOL_LANES` 只有 9 项)→ 模型坐标变 `NaN`。已改为按 `POOL_LANES.length` 回绕。
- 副作用:`ROW_SNAP_DISTANCE = 64` 的保护**重新生效**(最长列 251 张盘,半列跳转 125 行 > 64,`snapEngaged: true`)。
- 2026-09-12:**索引与阵列方案整体回退 + 每列条数放开**(用户原话:"你把索引机制完全改坏了,直接退回到原版")。上一条 ⚠️ 标注的四条全在此次回退范围内,本节是它们的收尾。
- **删除**:`src/archive-array.ts`、`scripts/check-array.mjs`,以及 `package.json` 里指向它的 `check:array` 脚本项(那个文件没了以后 `npm run check:array` 只会报缺文件)。**还原**:`src/data.ts` 回到 38 行原版(`columnFiles` / `fileLocation` / `fileAtSlot`);`src/archive-loop.ts`、`src/archive-visibility.ts`、`src/scene.ts` 的 `laneCenter()` / `configureLoop()` / `rowPeriod()` / `ROW_SNAP` / lane 原点全部回到原版;`main.ts` 里依赖它的 `detailEntry` 独立导航、盘内文件页签、检索分支一并移除。**保留**(用户单独要求过,与阵列无关):LOCAL ARCHIVE 面板、`POST /reveal`、SHOW IN FOLDER、`beginOpening()` 自动开场、圆形 `.skip`、boot 结束进 `archive`、退出系统。
- **每列条数已放开到不限**:`scripts/archive-content.mjs` 只要求"每列 ≥1、总数 ≥5"(原为恰好 5 列 × 8 条),`idWidth = Math.max(3, String(records.length).length)` 让编号宽度随条数自适应(1200 条时是 `X-0001` 而不是溢出到 `X-1000` 变四位)。`data.ts` 的 `slot: lane * 32 + (row % 32)` 是配套的防串列护栏(见顶部坑 1)。
- `scripts/check-content.mjs` 相应改判:原"列数不均衡""缺记录"两个用例换成"空列"(`/至少要有一条档案/`)与"条数过少"(`/至少需要每个分类一条档案/`),并新增两条正向用例——不等长列(1/12/5/2/9 = 29 条)与 1200 条(钉住 id 宽度补齐)。**20 项全绿**。
- 全量方案带来的**盘内文件浏览、按路径收藏、列数参数化**能力随之失去,这是用户明确选择的取舍,不要再"顺手恢复"。要扩内容请往 `content/curated.mjs` 加条目(原因见顶部坑 4)。
- **顺带修一个交互 bug**:刻度条窗口化在 8 条短列上把顺序整体转位成 `5,6,7,8,1,2,3,4`、高亮卡在正中(用户反馈"切换列左边的切换文件滑条坏掉了")。`main.ts` 的 `tickWindow()` 现在对 `len <= TICK_WINDOW` 走自然顺序分支,见顶部坑 2。
- **新增右上角退出系统**(`#exit`,46px 圆形 `⏻`,与开场 `.skip` 同坐标切换):白屏 + logo(**不闪动**)→ 字符本体明灭(`shutdownFlicker`)→ 黑条从左扫到**完全覆盖字体**为止、**不向右滑出**(`shutdownSweep` 的 keyframes 只到 `inset(0 0 0 0)`)→ `SHUTDOWN_CLOSE = 880ms` 后 `window.close()`,关不掉时退化为纯白页。CSS 时序集中在 `style.css` 的 `.shutdown` 段,详见顶部「开场与退出的入口序列」。
- ⚠️ **验证边界**:本机没有浏览器自动化、也没有无头 WebGL 环境,下面这套自检只能证明工程能构建、数据结构自洽,**不能证明动画观感**。退出序列、开场铺屏、刻度条观感这类结论必须由用户实机确认后才算通过。
- **自检命令**(注意 `check:archive` / `check:loop` **没有** npm script,要直接用 node 跑文件;`check:content` 才是 npm test 形式):
```powershell
npx tsc --noEmit # 类型
npm run check:content # 档案数据校验(20 项)
node --experimental-strip-types scripts/check-archive.mjs # 相机/抽取几何
node --experimental-strip-types scripts/check-loop.mjs # 循环阵列
npm run build # prebuild 会先跑归档快照 + 校验 + 字体
```
- `verification/ARCHIVE-ARRAY.md` 已标注为**存档**(描述的是被回退的方案),不要照它实现。
- 2026-09-12:**归档重组 + 精选档案扩到 47 条 + 回归检查去掉写死条数**。用户指示"不要老是问我,你来下最优决策",于是把两件挂起的事一起做了。
- **归档侧重组(18 项同盘 rename,零数据丢失)**:把纯货架内容上提到分类一级(`01_人工智能\01_训练产物\*`、`02_数据集\*`、`05_媒体\02_视频\*`、`03_截图\*`),并压平两类同名套娃——`07_日志与排查\下载日志\X.log\X.log`(5 份)、`ArmouryCrateInstallTool\ArmouryCrateInstaller_3.3.6.0\ArmouryCrateInstaller.exe`、以及 `03_代码与项目\{opencode-dev, awesome-design-md-main, CangjieCommunity-main, mc-bots-main}\<同名>\`。完整清单与回滚办法在 `E:\归档\00_索引\重组计划-*.json`,约定写在 `E:\归档\AGENTS.md` §3.3 与 §3 决策表。
- **守恒已核对**(拿重组前的 `archive-index.json` totals 对比):文件 +2(两份计划文件)、目录 −15(11+4,全是搬空的)、字节 +5,207(计划文件 + 账目行),归档内容零丢失。
- `content/curated.mjs` 里 X-001 / X-002 / X-003 的 `path` 随之上提,`department` 改为 `01_人工智能`;X-039 的摘要改成"五份日志"(原来描述的是目录套娃,已被压平)。
- **精选档案 40 → 47**:补上此前**完全没有精选档案覆盖**的真实大件——NM2 原版权重(6.7 GB)、V2_dpskw 备份集(13.9 GB)、设计资料集、darwin 客户端、musicbase 曲库、录屏片段、`00_索引` 自身。编号接在 X-041…X-047,**既有编号一个没动**(校验器要求 `id === X-{index+1}`,所以只能追加)。五列现在是 **8 / 9 / 11 / 9 / 10**,这是**刻意不等长**,用来证明"每列不限"真的生效,不是数据错乱。
- **`check-archive.mjs` / `check-loop.mjs` 里写死的 40 与 8 已拔掉**(它们正是坑 1 的翻版:改了条数就崩)。现在从数据推导:`documents` / `perColumn` / `maxPerColumn` 如实输出,只保留真正的契约断言(每列 ≥1、总数 ≥5、列内槽位不冲突;`slot` 复用在单列 >32 条时才允许)。`check-loop.mjs` 的 `files[wrap(row - 12, 8)]` 改成 `files.length`。
- 全套自检:`tsc` 0 · `check:content` 20/20 · `check-archive` 0(47 条 / maxPerColumn 11)· `check-loop` 0(20000 次定向移动)· `build` 0(825 files / 33.9 MiB)。
## 正式功能提交与同步
- 用户授权:非实验性质的正式功能完成相关验证后,直接提交并合并到主分支,或提交后同步到对应远程分支,无需再次询问。
- 当前已经位于主分支时,可直接提交并推送;在功能分支上则按项目分支状态完成合并与同步。
- 提交应聚焦当前任务;同一工作区中尚在进行或等待用户确认的其他工作,保留原状,不混入正式功能提交。
## 解密与清晰内构
- 用户要求参考原片 41 秒正面与 46–51 秒多角度制作内部结构,并省略左侧三根高塔。模型继续通过 Blender MCP 生成,保留共享脚本与源文件。
- 详情打开后由磨砂变清晰,解密线按原片 34–39 秒沿同一物理对角线合拢、保持、向中央收束,采用非线性运动。交互压缩时间,原片对照使用原时间码。
- 360° 查看器可切换清晰/磨砂,切换不改变视角或拆解状态;返回详情保持解密,退回阵列恢复磨砂。
- 长焦主场景近裁剪面为 5,以避免薄层内构深度冲突;保持原镜头和构图。验证见 verification/DECRYPTION.md。
## 内部模型的锐利截面(历史方案,已撤回)
- 用户要求内部环形模型更锐利,采用半透明磨砂质感,并打磨两环的连接处。内构沿圆周平滑,屋面、侧壁和切边之间保留硬法线;不再用多层圆角制造体积。
- 两环通过一条切向接入、两端略展宽的连续薄片连接;内部仍完全容纳在盖板与基板之间。外壳几何与法线保持原样。
- 源脚本为 art/internal_architecture.py;打磨前后与装配状态对照为 reference/internal-review.html,验证见 verification/INTERNAL-OPTICS.md。
## 外壳结构与磨砂揭示
- 用户要求黄色方块位于顶边并与外壳齐平,去掉左侧黄色竖条;螺丝仅保留右上与左下两处。
- 外框刻线与螺丝周围的压槽全部置于盖板后。解密前整面磨砂,原片 38.84–39.56 秒由顶部向底部逐渐变清晰;上下半清晰差异只出现在移动过程,不能保留固定高度的清晰渐变。
- 外壳细节通过 Blender MCP 制作,共用 art/shell_reference_details.py。新外壳保持既有内构顶点及法线,验证与对照见 verification/SHELL-REFINEMENT.md。
## 第一版内构恢复与轻磨砂
- 用户明确选择对照页左侧的第一版完整内构,来源为 89c7742 与 reference/internal-study/assembly-before.glb。恢复其两环截面、双连接带、平滑法线和原始白色/橙色材质;此前锐利截面、单连接片和追加内圈细节均已撤回。
- 通过 Blender MCP 重新生成单体与拆解资产,保留 art/internal_architecture.py 和两个 .blend 源场景。已修订的外壳继续使用 art/shell_reference_details.py。
- 选中盖板粗糙度为 0.42,并将折射模糊范围限制在投影盖板高度的约 1.6%,避免小尺寸预览过度模糊。自上而下揭示、阵列参数和完全清晰端点继续沿用。
- 原始内构与当前内构对照见 reference/internal-review.html;验证见 verification/RING-TEXTURE.md。
- 用户查看恢复后的版本与磨砂修正后反馈‘效果很不错’,确认保留本版。
## 屏幕比例与触摸适配
- 用户授权按桌面不同比例、手机横屏、竖屏构图与触摸、实机性能校准的顺序完成适配;实机目标为 iPhone / Safari。继续采用本项目原生实现流程,不启用前端或动效 Skill。
- 逐帧对照保留 1920×1080 舞台,完整等比展示;用户于 2026-09-10 追加要求正常登录入场铺满实际视口并移除字幕。交互阶段按实际视口布局。16:9 为视觉基准,桌面延展阵列,竖屏上方模型、下方独立滚动正文。
- 镜头适配不能改变抽取高度、模型横向/纵深坐标、波浪、归位规则与基线材质。竖屏预览镜头也不能跟随卡片实时升降或轨道位置抵消运动。
- 触摸在阵列区域左右切列、上下切档;详情获得净空后可单指检查,360° 查看器单指旋转、双指缩放和平移。正文滚动与场景手势隔离,旋转屏幕保留当前选档、视角与拆解状态。
- 画质仍采用已有用户设置与原始默认值;不得以手机类型直接切到删减光影的性能档。实机校准必须区分真实 iPhone 数据、桌面 WebKit 兼容性检查与 Chromium 视口模拟。实现见 `src/viewport-layout.ts`、`src/responsive.css`,验证见 `verification/RESPONSIVE.md`。
## PWA 与线上入口
- 用户要求增加 PWA,以便 iPhone 从主屏幕进入独立窗口;沿用现有 Vercel 项目与正式域名 https://rhine.lubeiluchen.cc/,README 保留在线入口。
- 正式构建缓存完整资源版本,更新下载失败时保留旧版。新版本准备好后由用户在设置中更新并重启,不中断当前浏览;保留收藏和偏好。
- 开发模式不注册 Service Worker。图标复用项目共享品牌路径;主屏幕安装、离线与更新说明见 `docs/PWA.md`,验证见 `verification/RESPONSIVE.md`。
- 针对 Win11 Chrome / Edge 的反馈,设置图标保留可见“设置”文字,新版本提供可直接点击的更新入口;`update.html` 从网络获取,用于旧版页面无法找到更新按钮时恢复,不清除收藏或偏好。
- 动态效果默认遵循系统偏好,设置中明确说明跳过开机和简化选档的影响,并提供“启用完整动效并重播”。本站明确选择优先于系统默认,正文解密遮罩也遵循同一设置。验证见 `verification/PWA-RECOVERY.md`。
## 字体分包与开场启动(issue #6、#7)
- 用户于 2026-09-10 明确批准:比较 `misans` 与 `misans-webfont` 第三方分包,在确认加载收益和显示效果后替换官方整包。本项目允许采用第三方 MiSans 分包,此明确授权覆盖此前禁止第三方分包的全局默认;保留字体署名、许可、来源和固定版本记录。
- 分包随项目同源部署,按页面字符加载;保持现有 MiSans 的字形、字重映射与排版。PWA 仍缓存完整版本,避免新档案或离线检索缺字。
- 比较后采用 `[email protected]`,字体 4.003;固定版本与源哈希保存在 public/fonts。品牌三行宽度按基线重新校准,首次入口字体约 415 KiB。选择依据、完整离线体积取舍与验证见 `verification/STARTUP-LOADING.md`。
- 开场等待用户点击、轻触或键盘启动,解锁声音并完成准备后统一开始音画;保留静音和减少动态效果偏好,加载失败提供重试与无声进入。
- 用户授权完成验证、提交及同步后,回复 GitHub issue #6、#7 并关闭它们。
## 桌面工作台(实验)
- 用户于 2026-09-10 批准在同一壁纸中切换档案展示与实用工作台,首版为时间日期、三条今日事项、重要日程倒计时、系统媒体信息与专注计时。沿用既有原生实现、视觉和 Blender 资产。
- 模式与事项内容、日期、计时时长从 Wallpaper Engine 属性读取。事项完成状态每日重置,专注计时按绝对截止时间恢复;这些进度独立存储,不受宿主声音与画质覆盖。
- 用户提出把玻璃档案交互用于真实文件夹。本次仅评估可行性,尚未授权安装本地桥接服务;研究见 docs/DESKTOP-FOLDERS.md。
- 用户于 2026-09-10 要求工作台严格沿用原主页设计,撤回首版绿色强调色与有底色的面板。采用原主页近黑/暖灰、细线与竖线刻度,规范见 DESIGN.md。
## 工作台显示内容与纯净效果
- 用户于 2026-09-10 决定将纯净效果与工作台合并,通过按需隐藏元素配置画面,不另建纯净模式。
- 七项显示开关全部位于 Wallpaper Engine 壁纸属性:时钟日期、今日事项、右侧功能内容、底部功能导航、品牌文字、页脚信息、设置入口。默认全部显示,仅影响工作台交互阶段;开场与档案展示沿用原样。
- 用户明确允许隐藏设置入口,恢复通过 WE 属性完成。壁纸内不提供另一套临时显示开关。隐藏内容不清除任务状态或停止专注计时。
- 音乐频谱、自动演出和小游戏仍为讨论中的后续方向,本次显示配置不代表已实现这些效果。
## 明暗配色(壁纸实验)
- 设置及 Wallpaper Engine「入场与画面」分组提供亮色/暗色,默认亮色。暗色采用石墨背景、烟灰玻璃、暖白文字与琥珀标记,沿用原生实现。
- 切换时以当前档案为起点,沿行列依次改变卡片材质;背景与文字同步渐变。中途反向从当前颜色接续,减少动态效果时直接到位。
- 详情解密后盖板恢复清透,内部结构保持可读;查看器、工作台与开场共同使用配色。实现与验证见 verification/THEME.md。
- 用户要求静止呼吸可独立开关,且「始终归齐」也可生效。WE 工作台显示分组新增该开关,默认开启;音乐时淡出、静音后恢复。用户认为现有音乐律动杂乱,已提出鼓点主导宽波的后续方案,当前未替换音乐算法。
- 用户批准原版、鼓点宽波和整组轻抬三种音乐律动并列试用;WE 属性中选择,默认保留原版,尚未决定最终方案。原有呼吸开关继续独立生效。
## 自适应阵列与暗色雾气
- 用户要求解决边角露出阵列尽头,并将屏幕外档案裁剪、按屏幕覆盖范围动态决定数量作为同一任务。
- 交互阵列按当前实际镜头与视口计算候选范围,逐实例裁剪并紧凑提交;替代固定 9 列 × 32 行的交互窗口。显式原片对照继续保持原来的 160 个位置。
- WE「入场与画面」提供自动标准余量/自动额外余量,默认标准。余量包括边缘预留、卡片完整高度和附近阴影贡献;相同宽高比与构图不因像素数量增加而复制更多档案。
- 暗色采用可见灰雾,并加强顶部、底部和边缘淡出。连续循环、点击、抽取、归位与原片对照回归见 verification/ARRAY-COVERAGE.md。
## 工作台衔接与连续频谱修订
- 用户要求删除媒体无进度与本机时区说明,修复暗色刻度、重播悬停和接力退出与设置的重叠。工作台开场隐藏选档标签,Powered 淡出;工作台与档案展示均保留退场过渡。
- 用户比较后保留原版律动为默认,撤回鼓点 A 与整组轻抬 B。重做 A 为按屏幕从左低频、中间中频到右高频的持续频谱,B 为持续层叠流波,两者均响应持续音乐。新 A/B 待试用。
- 正常开场同样动态覆盖实际视口,只有显式逐帧对照保留 160 个档案位置。实现与复核见 verification/WORKBENCH-REVISION.md。
- 媒体信息各通道独立更新:不能在歌曲文字回调中删除封面或进度。保留先封面后文字、同封面切曲与播放器切换;只有相应通道的显式空值替换旧状态。见 verification/WORKBENCH-REVISION.md。
- 用户进一步要求游戏模式进出也有过渡:工作台淡出、游戏信息进入,退出反向衔接;快速打断延续当前状态,退场期间保持输入隔离,完成后恢复焦点,减少动态效果直接切换。
## UI 视差与画面质感(壁纸实验)
用户明确要求仅 UI 产生 HUD 曲面视差,壁纸三维镜头不随被动鼠标移动。按参考项目的镜头畸变思路,UI 各区域以共同径向曲面投影到不同梯形;独立「鼠标追踪」关闭后保留静态形状。WE「HUD 与画面质感」提供曲面纵深、全局屏幕效果、局部界面磨砂底,三项默认关闭,强度可调。
用户追加明确:色散、暗角与颗粒处理最终全局合成,覆盖 UI、三维画面和弹窗,替代此前仅三维画布的 ShaderPass。颗粒增加尺寸控制;减少动态效果时冻结颗粒并停止鼠标追踪,静态曲面仍可保留。开场无 UI 视差,全局屏幕效果同样可见。磨砂底随 UI 投影。继续原生实现,见 verification/VISUAL-EFFECTS.md。
用户追加要求 UI 上、下、左、右四边距分别使用滑杆,并纠正为允许负数。WE「界面边距」提供四个 −300~300px 的独立偏移,默认 0;正值内收,负值外扩,抵消舞台缩放。仅调整交互 UI 布局,三维画布保持铺满;支持曲面与磨砂。实现与验证见 verification/UI-INSETS.md。
2026-09-10 修订:曲面纵深、细颗粒、颗粒尺寸、边缘色散、暗角的默认数值统一为 20。下边距不再缩短桌面工作台的中部定位容器;底部导航和页脚先上移,实际接近中部内容时才推动对应内容,上方品牌限制继续上移,空间不足时内容独立滚动。手机窄屏继续采用原有纵向滚动布局。
WE 新增「工作台 · 可用功能」,时间日期、今日事项、重要日程、正在播放、专注计时各自可开关,默认全开。关闭功能同时移除其右侧内容入口和底部按钮;当前项关闭时选择首个仍开启功能,全关时隐藏右侧内容和导航。左侧时钟与事项仍由原显示开关控制,任务和计时状态保留。用户明确要求自行验证,本轮仅完成实现和构建打包,未进行运行时与视觉验收。
2026-09-10 后续修正(覆盖上述中部避让方案):用户明确下边距只移动底部两排,排间距固定,中部组件不压缩、不滚动。已删除 workbench-insets 自动避让;工作台高度不再随下边距变化。底部导航、游戏入口与页脚在曲面投影之后使用相同 translate 偏移。另修复 :is 内 ID 提高优先级导致原规则覆盖,以及减少动态效果的隐式 all 过渡污染曲面测量的问题。Edge 实际页面完成 1920×1080、2560×1440、1600×900,曲面 0/20/80,下边距 60/192/300/−80/0,共 45 组检查:中部坐标及尺寸固定,无新增 overflow,底部各行位移一致。画面对照与数据见 verification/ui-insets/。
WE「入场与画面」新增暗色专用开关「暗色模式仅选中档案标签使用强调色」,默认关闭保留现有选择。开启后阵列与归位档案的顶边标签使用暗色,当前选中档案保留琥珀强调;浅色不受影响。通过既有材质 uniform 实现,无新增网格与绘制通道,暗色开/关及浅色截图已检查。
## 超级性能模式与自定义画质
用户于 2026-09-10 要求低配壁纸模式与减少动态效果独立,并恢复 WE 原生自定义画质。新增「超级性能模式(保留动态效果)」开关,默认关闭;开启时以独立的有效画质覆盖已保存画质,不改减少动态效果、宿主帧率或音乐律动设置,关闭恢复当前预设/自定义配置。
超级模式:三维比例 60%、DPR 上限 1、总像素不超过 921600;关闭阵列阴影、AO、景深和 SMAA,直接输出主场景;背景阵列使用无折射、无清漆材质并隐藏小螺丝,选中/详情模型保留完整结构和低分辨率折射。全局屏幕滤镜暂时停用,文字磨砂改为无模糊底色,HUD 曲面与追踪继续运行。DOM 文字不降低分辨率。
WE 画质下拉增加「自定义」,仅此选择且超级模式关闭时显示九项控制:渲染比例、像素密度、抗锯齿、阴影、AO 采样、AO 分辨率、景深、透明材质分辨率、纹理过滤。连续参数用滑杆,离散参数用原生下拉。部分宿主回调保留其他参数;超级模式开启期间保留自定义更新,关闭恢复最新配置。完整证据见 verification/SUPER-PERFORMANCE.md。
## 三维资源开关与自定义壁纸
- 用户于 2026-09-10 授权在左下 SESSION 右侧增加 3D 开关:档案错峰下落退场后实际释放全部三维场景、查看器和 WebGL 资源;再次开启重新加载并从下方逆向升起,保留选档。关闭途中可反向恢复,减少动态效果时直接切换。
- 3D 退场、关闭与载入期间隐藏游戏入口及模型专用入口;重新开启按原显示偏好恢复。2D 工作台和正文继续可用。
- WE「入场与画面」增加可选自定义壁纸,默认不启用;用户选图后,仅在三维资源释放后淡入。重新载入成功时图片淡出,失败保持图片并允许重试。沿用原生实现,见 verification/THREE-RELEASE.md。
- 用户明确保留三种音乐律动并列选择:原版、连续频谱、层叠流波。原版仍为默认,不再标为待定 A/B。
- 修复壁纸自身交互声音触发音乐归齐:静音状态抑制本地音效引起的新频谱活动,已建立的音乐继续响应。全部归齐时取消标签强调,暗色仅选中强调随实际升降连续过渡,见 verification/SELECTION-STATE.md。
## 创意工坊发布准备
- 用户于 2026-09-10 曾授权发布,随后明确改为自行操作发布,代理负责 GIF 和操作说明;当前尚未上传。
- 正式名称为 Rhine Lab · 莱茵生命交互桌面,去掉“本地测试”。独立正式工程放在 Wallpaper Engine 的 myprojects/rhine-lab-workshop,保留之前本地试用工程。
- 预览参照用户指定的 3088099655 预览的全景/局部剪辑方法,素材全部来自本工程工作台实际运行画面。最终 GIF 256×256、13.2 秒,含时钟事项、专注与明暗切换;没有将参考壁纸的图片放入发行包。
- 发布操作和后续更新须保留 workshopid,见 docs/WORKSHOP-PUBLISH.md;预览来源与验证见 verification/WORKSHOP-PREVIEW.md。
- 用户要求增加「启动时加载 3D」WE 属性,默认开启;关闭时只播放前段 2D 入场并进入组件页,不创建三维场景,左下可手动加载。属性下次加载生效。自定义图片在 2D 开场后显示,支持 WE 编码盘符和失败后同路径重试。见 docs/WALLPAPER-ENGINE.md。
- 用户要求自定义壁纸上下遮罩范围可调:WE 图片设置下新增 0–100 滑杆,默认 100,0 完全关闭遮罩;仅自定义图片显示时生效,沿用原生实现。见 docs/WALLPAPER-ENGINE.md。
- 用户要求改善无遮罩图片背景下浅色页脚按钮的对比,并将既有滚动库应用到时钟、播放器时间及切歌文字。使用原生实现与现有 createRollingText,460ms direct,媒体原位更新,减少动态效果直接显示。见 docs/WALLPAPER-ENGINE.md。
- 用户撤回额外白底的可读性方案:删除页脚新增底色与外扩阴影,仅调整底部导航、待办、提示及按钮的文字颜色;原玻璃背景和模糊参数保留。
## 壁纸功能回合主分支(2026-09-11)
用户明确授权评估兼容性后更新 main,覆盖此前不合并主分支的实验限制。共用暗色主题、自适应阵列、开场铺屏、滚动数字修复与资源管理进入网页;超级性能模式适配为网页持久化设置,默认关闭,独立于减少动态效果。工作台、系统音频/媒体、HUD 属性、自定义图片与 3D 卸载入口仍仅在 wallpaper 构建启用。普通网页保留启动声音解锁、键盘与触摸、PWA 和自定义画质。继续沿用原生实现流程。验证见 verification/WEB-INTEGRATION.md。
- 2026-09-11:用户要求开启 HUD 时开场同样应用曲面与鼠标追踪,覆盖此前开场无 UI 视差的限制。Logo、验证文字、圆环和欢迎画面按当帧原始动作叠加投影,品牌与 Powered 共用同一曲面;切换阶段保留深度与指针跟随状态。磨砂底仍仅交互阶段启用。见 verification/BOOT-HUD.md。
+306
View File
@@ -0,0 +1,306 @@
# 视觉与行为基准
## 2026-09-09 · 屏幕比例与触摸适配
用户授权执行桌面比例 → 手机横屏 → 竖屏与触摸 → iPhone / Safari 实机校准。沿用已确认的模型、字体、色彩、光照与原生实现方法。
- `viewport-layout.ts` 统一舞台尺寸与取景参数。启动/重播保留完整 16:9 参考舞台;桌面交互以 1080 逻辑高度延展宽度;窄屏和竖屏使用真实 CSS 像素布局,文字与按钮不再整体缩小。16:9 基准仍落在原坐标。
- 横屏保持左侧模型、右侧档案。窄横屏正文独立滚动,导航适当错行;竖屏正文从屏高 54% 开始,保留上方模型、返回与 360° 入口。短屏的取景范围额外受返回按钮与正文上沿约束。
- 模型位置与运动函数继续沿用原值。通过相机锚点和垂直取景范围保证构图,竖屏预览使用固定基准焦点,避免抵消抬起、呼吸和轨道运动。开场取消留白时,镜头先维持模型的实际显示尺寸,再通过既有相机插值接入交互构图。
- 品牌使用原版 MiSans 排版和同一标志,小屏缩放整组品牌;正文保持 15px,检索输入 16px。主要操作至少 44px 触控高度,档案刻度为次要跳转目标。顶部/底部与两侧边距读取安全区域。
- 左滑进入下一列,上滑进入下一档案,反向手势对应上一项。滑动需超过 36 CSS px、主方向至少为次方向的 1.3 倍,且在 1.4 秒内完成;轻点继续用射线选择,多指或取消事件不触发切档。详情的模型拖动使用实际坐标差,保留净空与转正限制。
- 独立查看器单指旋转、双指缩放和平移。竖屏为模型保留独立画布,装配结构移至下方三列;拆解时取景轻微展开,缩放和平移仍由用户控制。旋转屏幕只调整投影与布局,不复位视角、内容或拆解进度。
- 软键盘只改变弹窗可用高度,检索、收藏、设置均可滚动;详情排版变化后重新测量解密遮盖与页签下划线。页面隐藏和二维片头完全覆盖时停止三维绘制,画质、透射和光照默认值不变。
- 校准入口 `reference/mobile-review.html` 通过 `npm run review:mobile` 启动;只在开发服务器接收有限大小的帧时报告,保存至 `.tools/mobile-calibration`。该入口不进入生产构建,也不将桌面 WebKit 跑分视为 iPhone 成绩。
验证与实机状态见 `verification/RESPONSIVE.md`。
## 2026-09-09 · 档案标题滚动
按用户新要求,主档案标题采用与数字一致的滚动效果,替代历史快切黑条。复用已安装 `@kitlangton/rolling-number` 的 `createRollingText`,与编号共享 460ms 运动配置;使用 direct 模式直接替换中文、关闭逐字延迟,字数变化交由相同的宽度弹簧处理。固定向上滚动,标题模糊强度为 0.2;字号、颜色与位置沿用原样。保留单份可访问与可选择的语义文字,装饰字槽不进入复制内容。
连续输入由现有引擎从当前运动接续,只显示可见过渡与最新目标,不排队播放每个中间标题。进入详情、重播或开启减少动态效果时立即落到最终文字。历史黑条实现已移除;规范与回归检查见 `verification/ROLLING-TITLE.md`。
## 2026-09-09 · 详情与弹窗过渡补全
合并 `codex/boot-motion-polish` 的查看器平移、缩放与 560ms 球面复位,沿用该分支的品牌文字校准。详情层以 180ms 进出,正文仍跟随实际镜头进度显示;返回时不再立即删除可见内容。阵列氛围与右侧浅色遮罩按同一镜头进度交叉渐变,保留两端既有画面。快速反向从当前透明度接续,进入正文的焦点等待可读状态。
检索、收藏和设置弹窗统一使用 300ms 入场、200ms 退出。面板入场位移 12px、退出位移 8px;进入中途关闭时从实际透明度及位移继续。退出前保留对话框节点、焦点和背景输入隔离,重复 Esc 不向底层传递;选档与重播在关闭完成后执行。
页签正文采用 150ms 淡入,下划线使用 180ms 连续移动;快速切换取消旧内容动画并显示最终页签。收藏仅修改按钮、收藏计数及 220ms 颜色反馈,保留阅读区域节点、滚动位置与按钮焦点。新增过渡遵守减少动态效果设置。实现见 `src/ui-transitions.ts`,验证见 `verification/UI-TRANSITIONS.md`。
## 参考与既有规范
参考为目录原片的 5–40 秒,1920×1080、25fps。用户选择参考驱动的原生实现,不使用前端或动效 Skill。美术模型通过 Blender MCP 制作,界面使用 TypeScript、Three.js、Vite。
- 暖灰白底、黑色文字、暖杏金选中信号。界面以细线和紧凑排字组织,保留原片标题与右下角署名位置。
- 字体为用户指定的 MiSans。2026-09-10 用户明确批准为加快加载改用第三方分包;比较后采用 `[email protected]`(源字体 4.003),固定版本同源部署,按字符加载。保留 300、400、600、700 四个字重映射与 `font-display: swap`;后备为 PingFang SC / Microsoft YaHei / system-ui。字体分片字节不改动,移除上游 CSS 的 local() 以免设备字体覆盖固定版本。版权、许可和分片哈希在 public/fonts,设置页含署名与许可入口。详见 `verification/STARTUP-LOADING.md`。
- 5–27 秒为接入、身份验证、授权扫描、欢迎和白场。26.92–27.67 秒,阵列从左侧滑入,镜头从接近侧视快速抬高,随后进入斜俯视扫描。
- 27–29 秒为第一轮传播;29–30.5 秒反向回传。30.58 秒开始第一段小幅抽取,之后有停留;32.55 秒开始第二段抽取,镜头随后继续转向和推近至 39 秒。
- 选中卡片两侧在回传后继续起伏。主通道保留较高的连续肩部,其余通道幅度较低;按距选中位置的距离延迟并衰减。
- 相机校准必须使用未被遮挡的顶部边线、标签和重复卡片端点。不能把前排遮挡后的可见高度当作整张卡片高度,也不能通过扩大通道间距或设置通道高度阶梯补偿可见列数。
- 当前 31.48 秒镜头约为方位角 59°、仰角 19°、垂直取景范围 7.33 世界单位。远距镜头配合小视场角压缩透视。相邻通道同高、中心距 5.2;卡片宽 5、高 3.7,行距 0.62。后半段镜头转向与抽取升降分别控制。
- 39 秒特写由屏幕锚点控制构图,镜头逐渐转至约 18° 方位角、13.8° 仰角。保密等级扫描线投影在同一个实时模型上。
- 模型由磨砂盖板、基板、内部光学腔、环槽、边框、螺钉、标签与刻线组成;圆环位于盖板后方。重复卡片也启用物理透射,使用简化内部基板与象牙色端面。
- 交互保留检索、切换、详情、收藏、导出和设置。轨道与升降采用保留速度的临界阻尼;旧卡片独立归位,快速重新选中时接续运动。
- reference/review.html 仅在开发环境对照原片与实时场景;暂停使用原片逐帧图,播放使用视频。产品入口和生产构建不嵌入原片。
实时屏幕空间透射无法递归恢复原片全部透明层间折射。当前实现以关键帧投影和连续播放复核,尚未通过逐像素差分认证。
## 用户标注后的镜头与几何修订
- 三张标注经去除黄色笔迹区域后与原片帧匹配,对应 27.60、29.16、31.56 秒。
- 档案端面深度约 0.31、行距 0.62;原来的端面偏薄。由 Blender MCP 沿模型厚度方向调整并重新导出,标签与投影标记同时贴合新表面。
- 前半段采用较近的透视镜头:27.60 秒约 28 世界单位镜头距离、43° 仰角;29.16 秒约 35 世界单位距离、35° 仰角、67° 方位角。之后连续转至既有选中构图,特写镜头距离为 72。
- 回传波进入时机向后校正,29.16 秒尚未形成明显右侧回波,与标注帧对应。
- 删除所选模型朝 z=3.1 的位置插值,以及背景阵列随 detail 额外下沉的逻辑。选中槽位在抽取全过程固定横向与纵深位置,模型只有高度变化,镜头负责构图。
## 交互预览与进入详情
- 左右切换停在原片 31.56 秒对应的浅抬起状态(0.4 世界单位),邻排沿同一肩部轮廓排列;点击 ACCESS FILE 或按 Enter 后继续升至 3.35。返回恢复预览,详情中换选也先回到预览。
- 卡片超过浅抬起高度后,镜头才逐渐转向并把卡片放在画面左侧;详情文字随镜头进度在右侧渐显。抽取不再自动旋转模型,详情拖拽仍可检查角度。
- 阵列与抽出模型共用外壳几何,在同一个表面上随升降连续插值颜色、粗糙度与透光率;内部结构以连续覆盖率显现,文字标签渐显。每张归位卡片拥有独立材质状态,接近完全归位且外观一致后才恢复阵列实例。
## 最新交互修订(覆盖上文旧的按键和详情高度)
- 左右键切列,上下键切换列内前后档案。五列分别为工程研究、生命科学、机构档案、能量研究、特别项目,各 8 份,共 40 份;切回一列保留上次的档案选择。
- 760 帧(30.40 秒)各列波峰等高,随后摄像机右移,把同一物理列置于画面中央;785 帧附近非选中列仍保留 25% 以上的肩部波幅。列间突出程度采用连续插值。
- 交互详情升高 4.05 世界单位;预览仍为 0.4,原片时间轴的完整升高仍为 3.35。删除试调的 5.0 高度,保持邻片可见。
- 旋转限幅为 ±0.8 弧度。只有卡片完全避开邻片后才接受拖拽;返回先在原高度转正,再下降,镜头在转正期间维持构图。附近旧卡片转正时,新预览暂缓抬起,避免相互穿插。
- 标签和启动界面共用 src/brand.ts 的标志路径,标签不再手绘近似曲线。
- 档案正文为基于公开世界观资料的原创编目文章,不冒充游戏内原始档案。每份包含设定参考链接,详见 reference/LORE-SOURCES.md。
- 详情切换时,雾效距离跟随阻尼之后的实际镜头位置,避免进入时相机尚未到位、卡片提前被远雾覆盖。保留既有镜头轨迹与最终雾效。
- 交互预览停止操作 2.5 秒后渐入呼吸起伏:8 秒与 13 秒周期叠加,最大单向位移 0.102 世界单位(约卡片高度的 2.8%),相邻卡片略微错相。根据用户反馈,幅度由最初版本增至 3 倍,保留缓慢节奏。切换档案、进入详情或开启减少动态效果时渐退;原片时间轴不叠加此效果。
- 琥珀色侧条与透明盖板只有 0.002 世界单位的前后间距,斜视时会出现深度冲突条纹。仅为 Amber_Lightguide 材质增加 polygonOffset(factor=-1,units=-2),克隆归位卡片同时继承。保留原有相机裁剪范围、模型几何、材质色彩与全局 AO 参数。
## 独立模型查看器
- 详情页的「360° 查看文档模型」打开独立场景。沿用暖灰背景、既有字体、细线与克制的中英文字阶;不引入新的前端或动效设计方法。
- 鼠标左键拖动可完整环绕模型;上下左右键平移视角,滚轮或加减键缩放。Home 或「复位视角」恢复初始取景;ESC 或「返回档案」恢复原档案与键盘焦点。
- 「拆解档案」将紧固件、透明盖板、折射环组、光学核心、信息基板、背板与框架六组部件沿厚度轴分离。「一键重组」使各部件准确回到原位;途中反向操作保留当前运动位置与速度。减少动态效果设置下直接切换。
- 独立场景暂停主场景绘制,并复用校准后的材质、环境光配置与当前档案标签。原阵列模型与抽取镜头不受查看器变换影响。查看器的近裁剪面为 0.3,远裁剪面为 120,初始相机位置为 (7.2, 3.8, 12),可缩放距离为 5–28。
- 部件资源由 Blender MCP 执行 art/build_assembly.py 生成:复用原建模脚本的几何,按物理部件和材质合并,导出带 assemblyPart 信息的 public/assets/archive-assembly.glb,并保留 art/archive-assembly.blend。
- 复现时通过 Blender MCP 在 Blender 内执行该脚本,并在执行作用域中传入脚本的绝对 __file__ 路径;脚本以自身位置定位项目。几何双向顶点距离检查允许 1e-5 的浮点误差,实测最大误差约 6.08e-7。
- 根据用户的内外光照对照,查看器改为在自己的 WebGL 上下文内生成相同的 RoomEnvironment/PMREM。禁止跨 renderer 直接复用 GPU 渲染目标纹理,否则环境光缺失会使内部基板和圆环发暗。背景统一为 #eae5e1,复用主界面的氛围渐变,雾效范围相对实际模型距离设为 -1 至 +12,使放大缩小时保留浅色磨砂质感。
## 循环档案阵列
- 主界面将内容编号与物理位置分开:五类档案和每列八份档案按周期映射到连续的行列坐标。上下、左右的方向操作每次改变一个物理位置;最后一项接续第一项时,轨道继续沿原方向运动。
- 切换列时保留该列的档案选择记忆,并选择附近的对应周期位置;点击场景卡片使用当前实例实际对应的档案与位置。
- 交互时维护 9 列 × 32 行、共 288 个位置,按镜头附近的可见窗口循环补位。主模型与归位副本按物理坐标独立管理,避免同一档案的不同周期副本互相覆盖。
- 横向采用整组阵列轨道平移,纵向保留原有前后轨道;两者都保留阻尼与速度。取景和灯光附近的实际坐标保持有限,选定后的抽取仍只改变高度。
- 当逻辑坐标超过 2048 时,按内容周期统一平移坐标与运动状态;保持世界位置、速度、波纹与呼吸相位连续,避免长期运行后浮点精度下降。
- 显式原片逐帧对照保留原始的前 160 个位置;正常开场与交互阶段均按实际视口动态生成背景档案。循环后的重播会清理归位副本。
## 切换波浪微调(历史实验)
- 以循环阵列提交 aeddd8f 为基线。用户试用减弱波浪后,明确要求恢复原来的波浪幅度与节奏,仅去掉负向波谷。
- 交互选中波浪保留 0.8 幅度系数、0.2 秒起势、原衰减率和传播速度,仅把余弦的负值截为零。正向波峰仍会升起并回落,波浪本身不再将档案压低到基础肩部轮廓以下。
- 验证入口为 reference/rise-review.html;原片时间轴、浅抬起高度、详情抽取与归位规则沿用当前基线。
- 用户请求的可视对比页为 reference/wave-compare.html:两份实际 ArchiveScene 共用资源与时间步长。左侧可选择原始基线 aeddd8f 或仅去除负向波谷的 2048536,两者均为同时播放;右侧保留先抬起后波浪的实验方案。支持单步、连续、反向切换,0.25×/0.5×/1× 播放、逐帧定位、选中区域放大与高度曲线。每次回放检查实际位置与预先采样的轨迹一致。该页仅用于开发环境审阅。
## 先抬起,再波动(历史实验,当前不采用)
- 该实验的新选中档案先抬起,再发出选中波浪。预览目标仍为 0.4,向上抬起的临界阻尼速率从 4.2 调为 7.6;高度达到 0.35,且同列前后四格内的归位旧档案比它低至少 0.015 后,才启动波浪。单次正常切换约 0.48 秒后启动;旋转归位等特殊情况按实际状态等待。
- 每次选择替换尚未发出的波浪。进入详情、重播时取消待发波浪;减少动态效果时不发波浪。原片时间轴继续采用原值。
- 用户进一步要求消除选中档案再次跳起的效果:涟漪在源点的位移为零,并在周围 2.5 行的距离内平滑展开;正余弦包络改为平方,使边缘以零速度起落,保留远处波峰的幅度和传播速度。选中档案仅完成原定的 0.4 抬起,自己的涟漪不再给它叠加高度。
- reference/sequence-review.html 验证先后关系、归位高度、连续快速切换、反向、详情旋转归位、减少动态效果与重播取消。
- 档案序号与列编号采用 @kitlangton/rolling-number 0.4.1 的原生 DOM 接口,460ms 滚动,固定两位数,低强度运动模糊。方向键跨循环边界时保留滚动方向,快速输入由组件延续当前运动状态;系统减少动态效果与页面设置均可关闭滚动。MIT 许可随包保存到 public/licenses/rolling-number.txt。
## 当前采用:原始基线动效与光影微调
- 用户对比后明确回到原始基线 aeddd8f。生产场景默认使用完整的有正负波谷的选中波浪,选中与波浪同时开始,升降阻尼速率恢复 4.2。此前实验保留在参考页面;Rolling Number 沿用。
- 灯光统一到 src/archive-lighting.ts:曝光 1.00,环境强度 0.52,暖白主光 1.7,主光位置 (-8,14,4),补光降至 0.3,半球光降至 0.5。主场景和独立查看器各自在自己的 WebGL 上下文生成环境贴图。
- 阵列盖板增加少量暖色吸收,斜面和背板偏向浅香槟色;内部圆环与基板保持可读的暖灰层次。吸收色和吸收距离与其他材质属性一起随升降过渡。阴影偏移小幅收紧,AO 半径从 0.38 调为 0.44。
- 模型、几何、布局与镜头沿用已确认版本。reference/light-review.html 可切换四个关键帧以及原视频/调整前参照;两侧实时场景使用相同模型位置与镜头,并自动检查一致性。原片关键帧需在本地 reference/motion 中准备,详见 verification/LIGHTING-POLISH.md。
## 当前采用:基线配色与档案编号滚动
用户对比后确认整体基线版本的颜色更合适。生产场景和独立查看器默认切回 baseline,包括材质颜色、透光参数、曝光、灯光与阴影/AO;refined 仅保留在光影对照页中作为实验。
X-001 形式的主档案编号和悬停编号复用现有 Rolling Number:固定 X- 前缀,三位数字保留前导零,460ms 滚动并支持快速输入衔接。方向键切换沿导航方向滚动;悬停首次出现直接显示,再切换目标时滚动;减少动态效果时立即更新。编号的字号和布局保持原样。
## 标题快切与 360° 查看器进出
历史标题方案(已由 2026-09-09 的滚动文字替换):主档案标题在连续变化间隔小于 240ms 时触发短促闪动,140ms 后变成稳定的黑色横条;横条锁定本轮开始时的宽度。停止 260ms 后以 180ms 短闪显示最终标题,单次切换直接显示。切入详情、启动或减少动态效果时清理计时和动画。
360° 查看器以 320ms 淡入覆盖原详情,页眉和页脚轻微上移就位;模型完成载入并渲染后以 380ms 从 0.97 倍展开并淡入。退出以 220ms 淡出和轻微收拢接回原档案。使用可取消的原生动画,进入途中按 Esc 从当前透明度退出;退出结束之前继续绘制、保持模态输入隔离,之后才释放模型、恢复详情和原按钮焦点。过期的异步载入结果按请求序号释放。减少动态效果时直接切换。
reference/micro-transition-review.html 验证真实应用中的快切、最终标题恢复、切入详情取消横条、进入打断、重复关闭、拆解中退出、焦点归还和重新载入。2026-09-08 实测 13 项检查全部通过。标题短时动画通过定位到动画末端检查,避免后台标签页对计时器的节流导致误判。
## 2026-09-08 · 启动前段逐帧修订
以原片 25 fps 连续帧复核 6.76–27.00 秒。用户排除黑底鹰角片头,启动和重播从画面变白后开始。应用的内部参考时间仍为原片时间减 5 秒;首次加载遮罩退出后才开始显示英文输入。
`boot-motion.ts` 将逐字输入、停顿、清空、闪切固定到原片帧点,空间位置使用连续插值。`boot.ts` 驱动 DOM/SVG,不依赖循环 CSS 动画的已播放时长,因此正常播放、慢放和前后跳帧使用同一画面状态。英文从固定左端输入,标志从右上弧线开始绘制、绕左环再补全右环,完成后向左移位;品牌三行错开出现。
身份段恢复原文 `ID CONFIRMED : JOYCE MOORE`、`REQUEST RECEIVED`、`START PROCESSING...`。权限段用从画面外收拢的黑白双环、反向内弧、橙色节点、四帧缩放跳变和中心图形切换。欢迎段保留黑白闪切、公司名称黑底横向扫过、第三行 `INTERNAL DATABASE` 闪现,最后缩小、失焦、带轻微青色过渡进入白场。进入阵列之后的 reveal/lift/zoom 时间函数未修改。
参考帧与检查结果见 `verification/BOOT-MOTION.md`。`reference/boot-review.html` 可正常播放、半速、四分之一速度和逐帧核对;原片及提取帧只用于本地验证,不作为产品背景。
## 2026-09-08 · 品牌排版与 360° 导航
用户要求放大并叠加原片后进一步校准字重和字间距。以原片 13.48 秒左上角为参考,最终保留 MiSans:品牌容器左 59px、上 114px、宽 270px;第一行 44px / 48px 行高 / 2.5px 字距;第二行 19px / 23px 行高 / 600 字重 / 0.65px 字距;第三行 35px / 40px 行高。ANALYSIS 的八个字符按原片黑色字面起点调整各自间隔,OS 在 270px 容器右端对齐、字距 3px。浏览器测得三行宽度约 269.78、269.63、270px。此为固定品牌字样的排版,不修改字体文件。字体轮廓仍有差异,叠加页保留原片、当前版本及透明度滑杆供复核。
360° 查看器使用独立目标相机记录 OrbitControls 输入,显示相机对观察中心、球面角度和对数距离进行跟随。平移与缩放的大部分运动在约 200–250ms 内完成;复位采用 560ms 减速曲线和球面短路径,不穿过模型。拖动、滚轮、方向键可从正在复位的当前显示位置接管。进入新档案直接初始化到默认位置,减少动态效果时跳过镜头插值。原有平移半径 5、缩放距离 5–28 的边界保留。
验证:`reference/viewer-motion-check.html` 的 14 项检查通过,包含 30/120fps 一致性、复位中断、旋转跨正负 180°、连续平移和缩放、Home 精确归位、边界以及重新进入。`reference/brand-review.html` 使用生产界面的共享文字与样式进行 2.4 倍放大及红色叠加。
## 2026-09-09 · 可调渲染画质
用户要求开放更高画质与细分控制。系统设置增加性能、原始、高、极高四个预设及九项独立参数:渲染比例、像素密度上限、SMAA 抗锯齿、纹理过滤、透明材质分辨率、阴影分辨率、环境遮蔽采样与分辨率、景深强度。默认采用原始预设,继续沿用已确认的基线颜色、灯光与镜头。画质仅调整渲染精度与可选效果,不重新制作模型。
清晰度与材质参数同步至 360° 查看器;阴影、AO 和景深标明仅用于阵列。设置自动保存并兼容原先的高画质开关。面板显示实际渲染尺寸与硬件限制后的纹理倍率;全尺寸缓冲上限为 8,294,400 像素,渲染比例开放至 200%。完整参数、资源生命周期和验证记录见 `verification/RENDER-QUALITY.md`。
## 2026-09-09 · 原创音效与配乐
依据原片 6.76–40 秒画面及音轨动态、莱茵生命相关设定,新增原创「观测室」三轨循环配乐与交互音效,后续修订共 17 类。浏览、详情、独立模型查看以及启动阶段采用不同声部比例;用户可以独立控制音乐和音效。旧版静音偏好继续生效。
设计、参考依据、复现命令、技术验证及听感限制见 `verification/AUDIO-DESIGN.md`。本次未进行人工听辨确认;试听入口为 `reference/audio-check.html`,生成器为 `scripts/render-audio.mjs`。
### 玻璃交互声音修订
用户进一步指定玻璃交互应有清脆声音。当前版本采用短起音、不等间隔共鸣和迅速衰减的高频,区分切档轻碰、抽取余音与归位落定。详见 `verification/AUDIO-DESIGN.md` 的玻璃质感修订;用户指定的是声音方向,尚未确认本版听感。
### 原片逐字输入音效复刻
依据用户明确要求,逐字输入改用原片约 6.864–6.986 秒的三个 38ms 短音轮替,保留源波形,按实际文字揭示帧触发。输入最短间隔为 24ms,避免 25fps 相邻输入漏拍;新字段首字有反馈,闪动恢复不误触发。提取脚本、源校验、对照试听与限制见 `verification/AUDIO-DESIGN.md`、`public/audio/typing-source.json`。
### OS 场景的声音语义
窗口开合使用短电子升降音,启动标志使用低频缓起和声,整块文字显现使用轻微数据脉冲,授权使用双电子短音;逐字输入继续使用原片短音。玻璃共鸣对应档案切换、抽取、归位、阵列入场与拆组。绑定和原片时间码详见 `verification/AUDIO-DESIGN.md`。完整实机试听入口为 `reference/boot-audio.html`,不改写声音偏好;该入口补全此前的开场音效复刻试听。
## 2026-09-09 · 档案导航文字与刻度
顶部档案分类、底部列名、悬停标题与权限标签复用主标题的 460ms 文字滚动;内容相同不重复播放,悬停首次出现直接显示。统一处理快速输入、进入详情及减少动态效果。底部八个刻度位置复用节点,跨列保留原有 400ms 伸缩过渡、各列选择记忆及正确的点击映射。实现与 19 项实机检查见 `verification/ROLLING-TITLE.md`。
## 2026-09-09 · 原片模型细化
按用户要求放大检查原片 37–39 秒特写与端面,继续通过 Blender MCP 制作。两组环腔改为闭合阶梯截面,细化斜肩、内孔、香槟色嵌件;增加顶部双边压槽、盖板前沿细阶、内周边与接缝,螺钉使用真实凹槽。主模型与六组拆解模型同步更新,保持原有外包围盒、基线配色光照和抽取规则。
对照入口为 reference/model-review.html,可切换原片、细化前及 2×/3× 放大,使用相同镜头与 1920×1080 缓冲。Blender 源场景与可复现脚本保留。几何变化、检查与局限见 verification/MODEL-REFINEMENT.md;本轮效果尚待用户查看。
## 2026-09-09 · 清晰内构、解密与深度精度
按原片 41 秒正面与 46–51 秒多角度细化内部两组环形结构:分段外环、宽斜肩、内孔台阶、橙色嵌带、立面细竖肋与连接薄带。按用户要求完全省略左侧三根高塔。新增顶部压槽、内周边、竖向浮雕字与右下服务孔。通过 Blender MCP 生成,共享 art/clear_reference_details.py,单体与拆解资产几何保持一致。
黑色解密线按 25 fps 采样重建:34.24–36.04 秒两端沿同一物理对角线向中央延伸,36.04–37.72 秒保持完整,37.72–38.84 秒向中央收束,39.04–39.56 秒逐渐透明。使用单调三次插值,无过冲;原片时间轴可前后跳帧。交互详情在卡片抬起和镜头就位后启动,时间压缩至约 3.63 秒;减少动态效果时直接清晰。
查看器增加清晰/磨砂两态,透明度与粗糙度连续变化;切换不重置观察位置或拆解进度。退出查看器回到已解密的原档案,退回阵列时恢复磨砂,下一次打开重新解密。
修正用户报告的大环斑驳闪烁:详情长焦距离约 72,原 near=0.1 导致邻近表面深度量化冲突。near 改为 5、far 保持 300,镜头位置、视场角及构图不变。24 位深度在距离 72 的理论步长由 0.003089 降至 0.00006077。原片 11 个代表镜头的可见模型包围盒最近深度至少 16.66,均在裁剪面之外。
验证、范围和复现记录见 verification/DECRYPTION.md。逐帧对照入口为 reference/decryption-review.html;实际产品进入详情后可体验,360° 查看器右上角切换玻璃模式。
## 2026-09-09 · 前段非线性轨迹
按原片连续帧修订权限圆环、名称黑底及左上角三行文字。三行分别在原片 11.12、11.20、11.28 秒出现,使用独立的先加速后减速位移,第三行到约 12.20 秒落定。黑底在 23.52 秒进入,快速扫过后缓慢收束至 24.40 秒;灰闪时保留遮罩与未覆盖文字。
外环、白环的半径及弧端独立变化,内环与橙点减速,黑白端点同步缩小。空间运动使用测量关键帧的单调三次插值,离散闪切保持 25 fps 时间码。仍采用原生 DOM/SVG;轨迹见 `src/boot-tracks.ts`,测量脚本及实机验证见 `verification/BOOT-MOTION.md`。本地对照页增加三处局部放大和逐帧入口。
### 两侧小圆与 Logo 连续轮廓修订
用户进一步指出两侧小圆以原位旋转和弧段补全为主。原片 544 帧开始出现短弧,左右反向旋转并减速,568 帧分别达到约 332°、339°;随即切至欢迎页。圆心只保留测量到的轻微收拢,中央圆点周围的六颗小点才沿约 35 px 的轨道绕行,小点每隔三帧进入。中央大点保留 546、547、549、550 帧的闪变,然后缩至约 10–11 px 半径。
Logo 改用共享贝塞尔轮廓的连续路径,绘制首尾独立变速。加号与方点于 247 帧同时出现、向各自位置展开,256 帧后加号转动、方点拉成长横。Logo 左移按 260–294 帧实测位置拟合。425 帧后缺口开始沿轮廓移动,整个标志保持黑色,不再使用整枚透明度闪烁。曲线和复核入口见 `verification/BOOT-MOTION.md`。
## 2026-09-09 · 内部锐利截面与半透明材质(历史方案,已撤回)
依据用户新提供的 46–51 秒内部结构近景,专门重做两组环体的截面。宽屋面、直立内外壁、内孔薄台阶和橙色竖向嵌带采用明确的平面与小切边。自定义分裂法线沿圆周插值、在截面折边断开;厚度烘焙正确变换内构法线,同时保留外壳原先的顶点和法线。继续省略左侧高塔。
两条细窄连接物改为一条连续薄片:大环在 -74°、小环在 215° 附近切向接入,宽度 0.225–0.260,厚度烘焙后 0.008,两端略展宽,中央轻微抬高。全部内构深度在 -0.034 至 0.1424 之间,小于盖板内面 0.174。连接薄片、屋面与壁板分别使用灰色半透明粗糙材质,切边更亮,后方细肋可透出。
实时渲染采用薄层 alpha 合成与粗糙高光、掠射角密度,再由外盖折射完整内构,避免 WebGL 外盖的 transmission 捕获忽略另一层 transmissive 材质。内部闭合薄片只绘制正面,避免双面透明叠加过度浑浊。该实现是多层玻璃的实时近似,未进行完整体积光线追踪。对照页网格只用于检查透视和层次,不出现在产品中。
单体与六组拆解模型对应顶点误差 1.688e-7;外壳位置与法线相对打磨前误差均为 0。测试、导出与审阅入口见 verification/INTERNAL-OPTICS.md。
## 2026-09-09 · 外壳与自上而下的清晰过渡
根据用户提供的完整正面及四处局部截图,黄色嵌片改为 0.25 × 0.2239,顶边 y=3.7、前面 z=0.206 均与盖板齐平,并建立真实嵌槽。删除左侧黄条、宽端盖及盖板前的额外细阶。螺丝只保留右上和左下两处,使用十字凹槽及齐平垫圈;周围 C 形压槽与上下回路位于前盖之后。模型源脚本 art/shell_reference_details.py 由单体与拆解导出共享。
原先静态的高度粗糙度渐变被移动的清晰边界取代。选中盖板在进度 0 时整面 roughness=0.48;进度 1 时整面 roughness=0.025。中间羽化宽度为高度的 24%,边界从顶向下走,覆盖刻线与环体。原片时间码 38.84 秒开始,在 39.0 秒达到约 0.51,39.56 秒结束;这是从参考帧观察重建的近似时间曲线。360° 清晰/磨砂切换复用同一材质逻辑。
局部与前后对照见 reference/shell-review.html,可拖动清晰进度、慢放及改变观察角度。原片同步对照仍在 reference/decryption-review.html。验证见 verification/SHELL-REFINEMENT.md;听感与其他视觉方向的待确认状态不受本次技术验证影响。
## 2026-09-09 · 恢复第一版完整内构并减轻磨砂
用户最终指定 reference/internal-review.html 左侧第一版模型,完整恢复 89c7742 的两环截面、白色内沿、橙色嵌环、原始立面分隔、两条弯曲连接带与平滑法线。几何和材质来自 reference/internal-study/assembly-before.glb 对应的源脚本,并通过 Blender MCP 重建单体与拆解资产。此前锐利截面、灰色半透明单连接片及额外细槽方案不再用于产品。
当前内构世界坐标、法线和原始材质与第一版逐项核对一致,位置/法线比较精度为 1e-5。已经完成的外壳相对 dde61fd 保持一致;该版本快照存放于 reference/ring-study/assembly-before.glb。生成源文件为 art/internal_architecture.py、art/rhine-archive.blend 和 art/archive-assembly.blend。
磨砂盖板粗糙度由 0.48 减至 0.42,同时修正 Three.js 默认折射 mip 采样在小尺寸预览中相对过重的问题。根据盖板竖轴在当前镜头中的投影长度,将全磨砂采样范围限制在盖板投影高度的约 1.6%。粗糙度下降时模糊范围单调缩小,完全清晰端仍使用原采样;阵列质量为 0 时也使用原采样。保留现有由顶向底的羽化揭示,详情与独立查看器共用。
对照页左侧明确标记“第一版 · 原始内构”,右侧标记“已恢复 · 第一版内构”,避免把恢复误解为继续细化。原片磨砂对照仍为 reference/decryption-review.html。验证见 verification/RING-TEXTURE.md。
## 2026-09-09 · 主屏幕与离线
正式域名为 https://rhine.lubeiluchen.cc/,沿用 Vercel 部署。PWA 使用 standalone 窗口、浅暖色主题与共享莱茵生命图标,iPhone 使用 180 px Apple Touch Icon,其他设备提供 192 / 512 px 和 maskable 图标。状态栏、底部手势和横屏刘海通过安全区域留白处理。
安装与离线状态集中在设置中。首次联网准备完整资源,当前约 34 MiB,包含字体、模型、声音和 40 份档案;HTML 与资源使用同一个内容版本。下载成功后新版本进入等待状态,用户选择“更新并重启”后应用。下载失败保留完整旧版本,收藏和偏好不随资源更新清除。字体完整分包比此前整包略大,但网页进入前实际只加载约 415 KiB 字体。见 `docs/PWA.md` 与 `verification/STARTUP-LOADING.md`。
针对更新提示难以操作的反馈,设置图标增加下方的“设置”文字,新版就绪时在底部状态区提供直接更新按钮。弹窗和查看器打开时,外部更新入口遵循原有输入隔离;开场不显示该入口。`update.html` 提供网络恢复入口,供旧版用户更新完整缓存并返回。
减少动态效果设置补充开机、选档和文字的具体影响,提供用户主动启用完整动效并重播的操作。默认仍尊重系统偏好;正文遮罩由本站最终动效设置控制,避免用户启用完整动效后被系统媒体查询再次隐藏。
## 2026-09-10 · 阵列悬停、直接拖动与滚轮
用户最新选择自由平面拖动:鼠标与触摸的屏幕位移同时驱动列与档案两个轨道,拖动中可以自由转弯、反向。选中实际经过的档案,松手后最终吸附到最近位置;滚轮继续只用于列内切档,向上上一档、向下下一档。
用户试用后进一步要求类似网页快速滑动的手感。松手后保留实际速度并持续减速,快拖和慢拖产生不同滑行距离;去掉最多两列/三档的固定投影上限。选档随实际轨道位置变化,临近停止再吸附。按住正在滑行的阵列立即停止,可从当前显示位置反向或换轴接管。减少动态效果时不继续惯性滚动。
按住时采样负 X 切列轨道与负 Z 切档轨道的每格 CSS 位移,对两列投影向量组成的 2×2 矩阵求逆,将屏幕位移同时分解到两个轨道。取消角度分类与方向锁定,仅保留 10px 起拖阈值。本次手势内固定投影,旋转屏幕后下一次手势重新采样。自由滑行期间两个速度分量使用相同衰减,较慢分量不会提前吸附而折向另一条轨道;整体放慢后再共同吸附。自由拖动按物理格位选档并更新记忆,避免切列记忆将画面拉离指针;键盘与按钮切列仍恢复记忆。
悬停卡片额外竖直抬起 0.28,约 200ms 显现,移开平滑回落。悬停高度独立于选中抬起 0.4 与详情抽取 4.05,不改变镜头进度;所有网格部件一起移动。详情与独立查看器继续采用已确认的旋转、缩放和拆解操作。实现与验证见 `verification/ARRAY-INPUT.md`。
## 工作台沿用主页视觉
用户于 2026-09-10 指出首版绿色强调色与原主页不一致。工作台沿用原主页近黑正文(#080a08)、暖灰辅助文字(#77756d)、灰色细线(#aaa59a)及暖棕悬停(#9b7247)。取消新增的绿色填充、面板底块和导航色块;使用透明内容、细线分隔与原阵列长短竖线刻度表达选择。保留原字体、模型、灯光与阵列动效。
## 明暗配色
暗色以 #11181b 为背景,#e0e3dc 为正文,#a6b0b1 为辅助文字,#536166 为分隔线,#c5a16b 为琥珀强调。保留原版排版与布局,不使用全屏反色。三维阵列使用独立的烟灰材质端点及冷灰边缘,清晰盖板随原有自上而下解密前沿恢复接近无色的透射,避免遮黑内部结构。
切换以选中单元为起点:每行延迟 34ms、每列 110ms,最远延迟 600ms;单卡过渡 580ms,背景 850ms。切换不附加升降或位移,快速反向接续各卡当前颜色。减少动态效果直接应用目标配色。
## 自适应阵列覆盖
交互阵列根据渲染当帧完成插值后的相机重新计算覆盖范围。截取相机视锥与阵列可占据的高度区间,换算为行列候选,再用完整卡片边界裁剪,连续紧凑写入实例矩阵。鼠标点击与小游戏目标使用同一份实际实例映射。相机移动、屏幕比例变化与档案轨道移动都在当前帧更新。
默认保留 18% 的屏幕方向余量,额外档为 50%;保留附近阴影贡献,远处完全雾化后不继续扩展无限阵列。实例缓冲按需增长并复用容量,实际绘制数独立于缓冲容量。既有 Blender 几何与材质细节不变。
暗色雾颜色调整为 #263136,预览雾起点为相机至目标距离加 1,终点加 16;浅色仍为加 5/加 25。详情过渡到原来的加 -1/加 12 距离。暗色增加顶部和底部渐隐,使边缘轮廓融入背景。原片固定舞台仍维持原始阵列位置。
工作台细节、退场及连续频谱 A/B 的当前规范见 docs/WALLPAPER-ENGINE.md 与 verification/WORKBENCH-REVISION.md。
## 可选界面景深与质感
壁纸 UI 使用共同径向曲面,按各区块的四角计算投影矩阵,左/右与上/下产生相反的斜率;替代此前同方向平移与倾斜。纵深默认 20%,曲率系数为纵深乘 0.08,屏幕四角约束保持边距;鼠标追踪独立开启,切向项系数 0.0075,平滑速率 7。浏览器原生变换同时更新点击区域。工作台入场使用独立 translate 与投影叠加。
全局 SVG 合成滤镜使用 sRGB,覆盖三维、DOM 与弹窗。色散在画面边缘减弱至零以避免采样越界彩框,默认 20%;颗粒默认 20%、尺寸 20%,以 12Hz 更新,减少动态效果时冻结;暗角默认 20%。滑杆高值提供明显的校准范围。局部磨砂最大 24px 模糊和 0.65 主题底色不透明度,默认 55%。三种总开关仍默认关闭。详见 verification/VISUAL-EFFECTS.md。
用户配置的四边距作为原始 UI 框架的额外偏移,范围 −300~300px、默认 0,正值向内。通过 --stage-scale 抵消基准舞台缩放;布局先按新边界定位,再投影 UI 曲面,保持字号。背景、卡片及场景坐标标记继续使用完整视口。详情的轻移使用独立 translate,与曲面矩阵叠加。
壁纸下边距最新规则:底部两排在 HUD 投影之后整体平移,相对间距固定;中部不因下边距移动或产生滚动区域。暗色新增可选的仅选中标签强调,其余 Index_Inlay 使用深灰;默认关闭保留原琥珀标签。
## 超级性能模式与自定义画质
用户于 2026-09-10 要求低配壁纸模式与减少动态效果独立,并恢复 WE 原生自定义画质。新增「超级性能模式(保留动态效果)」开关,默认关闭;开启时以独立的有效画质覆盖已保存画质,不改减少动态效果、宿主帧率或音乐律动设置,关闭恢复当前预设/自定义配置。
超级模式:三维比例 60%、DPR 上限 1、总像素不超过 921600;关闭阵列阴影、AO、景深和 SMAA,直接输出主场景;背景阵列使用无折射、无清漆材质并隐藏小螺丝,选中/详情模型保留完整结构和低分辨率折射。全局屏幕滤镜暂时停用,文字磨砂改为无模糊底色,HUD 曲面与追踪继续运行。DOM 文字不降低分辨率。
WE 画质下拉增加「自定义」,仅此选择且超级模式关闭时显示九项控制:渲染比例、像素密度、抗锯齿、阴影、AO 采样、AO 分辨率、景深、透明材质分辨率、纹理过滤。连续参数用滑杆,离散参数用原生下拉。部分宿主回调保留其他参数;超级模式开启期间保留自定义更新,关闭恢复最新配置。完整证据见 verification/SUPER-PERFORMANCE.md。
## 三维资源开关与自定义壁纸
- 用户于 2026-09-10 授权在左下 SESSION 右侧增加 3D 开关:档案错峰下落退场后实际释放全部三维场景、查看器和 WebGL 资源;再次开启重新加载并从下方逆向升起,保留选档。关闭途中可反向恢复,减少动态效果时直接切换。
- 3D 退场、关闭与载入期间隐藏游戏入口及模型专用入口;重新开启按原显示偏好恢复。2D 工作台和正文继续可用。
- WE「入场与画面」增加可选自定义壁纸,默认不启用;用户选图后,仅在三维资源释放后淡入。重新载入成功时图片淡出,失败保持图片并允许重试。沿用原生实现,见 verification/THREE-RELEASE.md。
- 用户明确保留三种音乐律动并列选择:原版、连续频谱、层叠流波。原版仍为默认,不再标为待定 A/B。
- 修复壁纸自身交互声音触发音乐归齐:静音状态抑制本地音效引起的新频谱活动,已建立的音乐继续响应。全部归齐时取消标签强调,暗色仅选中强调随实际升降连续过渡,见 verification/SELECTION-STATE.md。
## 开场中央字体补充(2026-09-11)
后续从 MyFonts 取得正式 Webfont 后,本机使用原始 WOFF2 原生字形;既有轮廓仅作为无本地许可包或加载失败的后备。复用逐字尺寸和基线,不重新调整已确认排版。授权字体单独存放并排除公开 Git。
中央访问、身份、请求、处理、授权提示采用 Novecento Sans Wide Normal,欢迎三行采用 Bold。按固定文案制作轮廓,保留原字距动画与逐字时间轴;处理提示字号 21.35px,顶部 527px,图标间距 10px(1920×1080 基准)。左上 RHINE LAB 按用户后续纠正改用 DemiBold,字号 50.75px、字距 1px,标题行固定高 48px,图形顶部偏移 1.425px;下方两行保留 MiSans 与既有位置。来源与验证见 verification/BOOT-LETTERING.md。
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 LBEILC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+297
View File
@@ -0,0 +1,297 @@
# RHINE LAB · ANALYSIS OS
**把莱茵生命的终端,做成可以操作的三维界面。**
**[在线体验 → rhine.lubeiluchen.cc](https://rhine.lubeiluchen.cc/)**
iPhone 可用 Safari 打开在线版,通过“分享 → 添加到主屏幕”安装;从主屏幕图标进入可使用独立窗口。首次联网后,设置中显示“离线资源已就绪”即可离线浏览档案和模型。支持桌面不同比例、手机横竖屏和触摸操作。[安装与更新说明](docs/PWA.md)
![莱茵生命终端:由透明档案盒构成的三维阵列](docs/media/archive.jpg)
这是对《明日方舟》特别映像「莱茵生命:访问」终端界面的非官方复刻。从白底开场进入五列循环档案阵列,抽取一份档案,等待玻璃与正文解密,再进入独立查看器观察内部结构。
项目以原 PV 的 **5–40 秒**为主要视觉与动效参考,实际开场从 **6.76 秒的白色画面**开始;内部结构另参考约 **41 秒及 46–51 秒**的正面与多角度画面。检索、收藏、正文阅读、结构拆解与声音设置是可操作的扩展功能。
代码由 GPT-6 Astra 协助完成,模型通过 Blender MCP 制作。界面采用 **TypeScript + Three.js + Vite**,运行时实时渲染三维模型,开场由 DOM / SVG 与场景时间轴驱动。
[快速运行](#快速运行) · [界面与动效](#界面与动效) · [操作说明](#操作说明) · [源码与 Blender 工程下载](https://pan.quark.cn/s/762d9ee9dfc3) · [参考原 PV](https://www.bilibili.com/video/BV1rr4y1b7sz/)
## Wallpaper Engine 壁纸与独立仓库
壁纸版已从本项目拆分为独立公共仓库,后续功能在 **[RhineLabWallpaper](https://github.com/LBEILC/RhineLabWallpaper)** 的 `main` 分支开发。本仓库继续维护网页版本;[原壁纸分支 codex/wallpaper-engine](https://github.com/LBEILC/RhineLabUI/tree/codex/wallpaper-engine) 保留迁移记录。需要最新壁纸源码,请前往独立仓库。
**[在 Steam 创意工坊订阅壁纸 → Rhine Lab · 莱茵生命交互桌面](https://steamcommunity.com/sharedfiles/filedetails/?id=3799142774)**
![Wallpaper Engine 工作台预览](wallpaper/preview.gif)
- **两种工作模式**:三维档案展示与桌面工作台,可显示时间日期、今日事项、日程倒计时、系统媒体信息和专注计时。
- **壁纸交互**:音乐律动、呼吸效果、波纹接力小游戏,以及亮暗配色、玻璃模糊、HUD 曲面与视差。
- **按需保留画面**:在 WE 属性中选择显示哪些组件;可关闭启动时加载 3D,播放 2D 开场后直接进入工作台,再从左下角手动载入模型。
- **自定义图片**:关闭 3D 后可显示自行选择的壁纸,支持调整上下遮罩范围,设为 0 即关闭;时钟和媒体信息复用滚动数字与文字效果。
- **宿主适配**:接入 WE 属性、音频响应、媒体信息、帧率及暂停通知,壁纸资源本地打包,壁纸构建不启用 PWA。
使用壁纸请通过上方创意工坊链接订阅,在 Wallpaper Engine 中应用并调整属性。需要自行构建时:
```sh
git clone https://github.com/LBEILC/RhineLabWallpaper.git
cd RhineLabWallpaper
npm ci
npm run build:wallpaper
```
输出目录为 `release/wallpaper`,在 Wallpaper Engine 编辑器中打开其中的 `index.html`。系统媒体信息取决于播放器支持及 WE 媒体集成设置。更多细节见 [壁纸使用与开发说明](docs/WALLPAPER-ENGINE.md) 和 [创意工坊发布说明](docs/WORKSHOP-PUBLISH.md)。
以下为项目共用的网页与三维档案功能介绍。
## 新版效果
- **亮暗配色**:设置中切换,卡片依次变色,详情与独立查看器同步适配。
- **自适应阵列**:按实际镜头与屏幕范围布置、裁剪档案,改善宽屏边角露出尽头;开场铺满实际视口。
- **超级性能模式**:设置中独立开启,降低三维渲染负担并保留动效,关闭恢复之前的画质。
- **数字时钟**:页脚时、分、秒独立滚动,修复变换布局下部分数字动画中断。
整合范围与验证见 [主分支整合记录](verification/WEB-INTEGRATION.md)。
- **模型与正文同步解密**:对角解密线合拢、保持并收束,盖板自上而下由磨砂变清晰;右侧文档的遮挡条随之退开,露出标题、资料字段与正文。
- **可看清的双环内构**:双环、连接带与白色/橙色部件封装在盖板与基板之间。顶边黄色方块、两处螺丝和盖板后的刻线补全了外壳细节。
- **清晰/磨砂切换**:独立查看器可以随时改变玻璃状态,保留当前视角和拆解位置;支持平滑缩放、平移及复位。
- **滚动文字与编号**:档案标题、分类、权限标签及编号连续滚动,快速输入衔接最新选择。
- **重新校准的开场**:逐字输入、圆环绕行、连续 Logo 笔画、身份验证和欢迎转场,按原片逐帧修订轨迹与节奏。
- **声音与画质设置**:玻璃交互音、系统电子音与三轨循环配乐;音效和音乐可独立开关、调节音量。画质提供四档预设及精细设置。
## 界面与动效
以下截图与动图于 **2026-09-09** 重新采集,均来自当前版本的实际浏览器运行。截图为 **1600 × 900**,GIF 为 **8–12 fps**、原速播放;压缩后的帧率与颜色不代表实时渲染质量。采集版本与复现步骤见 [素材说明](docs/media/README.md)。
### 抽取、解密与阅读
档案竖直升起,镜头靠近并转向详情构图。解密时玻璃与文档一起揭示;完成后可阅读概述、研究记录及访问日志,也可以收藏或导出 UTF-8 文本。
![档案抽取与同步解密:对角线收束,玻璃从上向下变清晰,正文遮挡退开](docs/media/decryption.gif)
| 解密完成 · 清晰内构与档案概述 | 研究记录 · 正文阅读 |
| --- | --- |
| [![解密后的档案:左侧可见双环内构,右侧显示机构资料](docs/media/detail.jpg)](docs/media/detail.jpg) | [![档案研究记录页签](docs/media/research.jpg)](docs/media/research.jpg) |
### 清晰内构与磨砂玻璃
在 360° 查看器中比较两种玻璃状态。切换不会重置镜头或拆解状态,返回详情后仍保留已解密状态。
| 清晰 · 双环与连接带 | 磨砂 · 轻柔折射 |
| --- | --- |
| [![清晰玻璃下的档案内部结构](docs/media/viewer-clear.jpg)](docs/media/viewer-clear.jpg) | [![相同视角下的磨砂玻璃](docs/media/viewer-frosted.jpg)](docs/media/viewer-frosted.jpg) |
<details>
<summary><strong>查看动图:清晰/磨砂连续切换</strong></summary>
![玻璃由清晰过渡到磨砂,再恢复清晰](docs/media/glass-motion.gif)
</details>
### 360° 旋转、拆解与重组
紧固件、透明盖板、折射环组、光学核心、信息基板、背板与框架按六组展开。拆解后仍可旋转、平移和缩放,观察部件之间的关系,再一键重组。
[![新版档案模型:六组结构分层展开](docs/media/assembly.jpg)](docs/media/assembly.jpg)
<details>
<summary><strong>查看动图:模型拆解、旋转与重组</strong></summary>
![操作动图:档案盒从完整状态拆开,旋转观察后连续重组](docs/media/assembly-motion.gif)
</details>
### 循环阵列与滚动标题
五类、每类八份,共 **40 份档案**。上下翻阅、左右切列均可持续循环;切回某列时保留上次选择。选中抬起与阵列波浪同时开始,标题和编号跟随输入滚动;返回阵列时,档案先转正再下降。
<details>
<summary><strong>查看动图:切列、翻阅与连续文字滚动</strong></summary>
![循环切换档案,标题、编号、分类及刻度同步更新](docs/media/browse.gif)
</details>
### 白底开场
从终端逐字输入到圆环、Logo 绘制,再进入身份接入与权限验证。可以重播,也可以跳过开场直接进入阵列。
[![白底开场中的莱茵生命标志与身份接入文字](docs/media/boot.jpg)](docs/media/boot.jpg)
<details>
<summary><strong>查看动图:新版开场片段</strong></summary>
![新版白底开场:逐字输入、圆环和连续标志绘制、身份验证与欢迎转场](docs/media/boot-motion.gif)
</details>
### 检索、声音与画质
检索支持编号、标题、英文名、科室、相关人物与分类筛选。收藏和设置保存在当前浏览器中。音效区分档案的玻璃碰触与系统操作的电子反馈,背景配乐随开场、阵列、详情和查看器调整三轨比例。
| 档案索引 · 关键词与分类筛选 | 系统设置 · 声音与画质 |
| --- | --- |
| [![输入莱茵关键词后的档案检索结果](docs/media/search.jpg)](docs/media/search.jpg) | [![独立音效和音乐音量、减少动态效果与画质预设](docs/media/settings.jpg)](docs/media/settings.jpg) |
GIF 不含声音。可单独[试听原创配乐「观测室」](public/audio/observatory-preview.mp3),完整声音效果请启动应用体验。浏览器可能需要一次点击或按键才允许播放音频。
## 快速运行
需要 **Node.js 22.12 或更高版本**(可使用 Node.js 24),以及支持 WebGL 2 的现代桌面浏览器。首次安装依赖需要网络;应用不需要 API Key,也不需要启动后端服务。
### 获取项目
```sh
git clone https://github.com/LBEILC/RhineLabUI.git
cd RhineLabUI
```
也可以从 GitHub 的 **Code → Download ZIP** 下载当前源码,或获取[夸克项目包](https://pan.quark.cn/s/762d9ee9dfc3)。夸克包是 **2026-09-08 的打包快照**,包含源码、运行模型与 Blender 源工程;后续更新以本仓库为准。
### 安装并启动
```sh
npm ci
npm run dev
```
打开终端显示的地址,通常为 `http://127.0.0.1:5173/`。如果端口被占用,以终端实际输出为准。
Windows 用户安装 Node.js 并解压项目后,也可以双击 [`启动终端.cmd`](启动终端.cmd):首次运行会安装依赖,然后启动本地服务并打开浏览器。
### 构建与预览
```sh
npm run build
npm run preview
```
生产文件输出到 `dist/`,可以交给静态 HTTP 服务托管。请通过服务地址访问,不要直接双击 `dist/index.html`。
## 操作说明
### 终端与档案
| 操作 | 效果 |
| --- | --- |
| 开场中按 `Enter` / `Esc`,或点击 `ENTER SYSTEM` | 资源就绪后进入交互阵列 |
| `←` / `→` | 切换档案类别,首尾循环 |
| `↑` / `↓` | 翻阅同类档案,首尾循环 |
| `Enter`、`ACCESS FILE` 或文件编号 | 读取当前档案 |
| 在详情模型上拖动 | 档案获得净空后,旋转观察 |
| `/` 或 `ARCHIVE INDEX` | 打开检索,可搜索编号、标题、英文名、科室、负责人和分类 |
| `SAVE ARCHIVE` / `SAVED` | 收藏当前档案 / 查看收藏 |
| `EXPORT` | 下载当前档案的文本文件 |
| `Esc` | 关闭当前弹窗,或从详情返回阵列 |
### 独立模型查看器
在详情页点击 **「360° 查看文档模型」** 进入。
| 操作 | 效果 |
| --- | --- |
| 鼠标拖动 | 环绕旋转模型 |
| 滚轮、`+` / `−` | 平滑缩放 |
| 方向键 | 平移观察位置 |
| 「复位视角」或 `Home` | 平滑恢复初始观察位置 |
| 「清晰」 / 「磨砂」 | 切换玻璃状态,保留视角和拆解位置 |
| 「拆解档案」 / 「一键重组」 | 展开六组部件 / 连续收回 |
| `Esc` 或「返回档案」 | 关闭查看器,返回原档案 |
### 显示与偏好
布局以 **1920 × 1080** 为基准等比例适应窗口,主要面向桌面与横向屏幕。设置页提供音效与背景音乐的独立开关、独立音量,以及减少动态效果、画质、全屏和重新播放开场。收藏和偏好保存在当前浏览器中。
首次载入需要加载字体与 GLB 模型。项目保留了四份官方 MiSans WOFF2,合计约 19.7 MB,按实际使用加载。画质预设为**性能、原始、高、极高**,默认使用原始;精细设置可调整渲染比例、像素密度、抗锯齿、纹理过滤、透明材质分辨率、阴影、环境遮蔽及景深。运行不够流畅时可选性能档;需要简化镜头、文字与揭示动画时,可启用减少动态效果。
## 工程结构
| 目录或文件 | 内容 |
| --- | --- |
| [`src/main.ts`](src/main.ts) | 页面状态、档案阅读、检索、收藏与快捷键 |
| [`src/boot.ts`](src/boot.ts)、[`src/boot-motion.ts`](src/boot-motion.ts) | 开场界面与逐帧时间轴 |
| [`src/scene.ts`](src/scene.ts)、[`src/archive-loop.ts`](src/archive-loop.ts) | Three.js 场景、循环阵列、抽取与归位 |
| [`src/model-viewer.ts`](src/model-viewer.ts) | 独立模型查看器与拆解动画 |
| [`src/decryption.ts`](src/decryption.ts)、[`src/document-decryption.ts`](src/document-decryption.ts) | 模型解密轨迹与正文同步揭示 |
| [`src/audio.ts`](src/audio.ts)、[`public/audio/`](public/audio/) | 交互音效、三轨配乐与音源记录 |
| [`src/render-quality.ts`](src/render-quality.ts)、[`src/quality-renderer.ts`](src/quality-renderer.ts) | 画质预设与渲染管线 |
| [`content/archives.json`](content/archives.json) | 页面与下载共用的五类、40 份档案数据 |
| [`src/data.ts`](src/data.ts) | 档案类型与阵列位置映射 |
| [`public/assets/`](public/assets/) | 运行所需的 GLB 模型 |
| [`public/archives/`](public/archives/) | 导出的档案文本;启动和构建前自动生成 |
| [`art/`](art/) | Blender 源文件、建模与审阅脚本 |
| [`scripts/`](scripts/) | 档案导出与行为检查 |
| [`reference/`](reference/)、[`verification/`](verification/) | 开发对照工具与分阶段验证记录 |
| [`docs/media/`](docs/media/) | README 截图与动图 |
| [`DESIGN.md`](DESIGN.md) | 视觉、相机、材质与运动约束 |
原片时间轴使用 160 个阵列位置;交互模式使用固定的可见窗口与外围卡片补位,让有限的档案内容可以持续循环。
### 修改与复核
修改档案内容从 [`content/archives.json`](content/archives.json) 入手,字段与操作步骤见 [档案修改说明](content/README.md)。`npm run dev` 与 `npm run build` 会先校验数据,再更新 `public/archives/` 中的文本导出;开发过程中修改数据后,可执行 `npm run export:archives` 同步下载文件。`npm run check:content` 检查数据规则与导出一致性。
```sh
node scripts/check-motion.mjs
node scripts/check-loop.mjs
node scripts/check-appearance.mjs
node scripts/check-assembly.mjs
node scripts/check-decryption.mjs
node scripts/check-shell.mjs
node scripts/check-internal-optics.mjs
node scripts/check-quality.mjs
```
这些脚本检查运动、循环位置、外观、装配结构、解密轨迹、外壳、内构与画质参数。视觉效果仍需在浏览器中实际查看,尤其是快速切换、模型归位、文档揭示及查看器进出过渡。
| 本地调试路径 | 用途 |
| --- | --- |
| `/?scene=archive` | 直接进入档案阵列 |
| `/?scene=detail` | 直接进入档案详情 |
| `/?time=28&freeze=1` | 固定在参考时间轴的指定时刻 |
| `/reference/review.html`、`/reference/boot-review.html` | 原片与复刻对照工具 |
| `/reference/decryption-review.html` | 玻璃解密逐帧对照 |
| `/reference/document-decryption-check.html` | 正文同步解密与布局检查 |
| `/reference/boot-audio.html` | 完整开场声音试听 |
原 PV 不随仓库分发。使用视频对照工具时,需要自行准备对应参考视频;正常运行应用不依赖它。
### Blender 源工程
| 文件 | 用途 |
| --- | --- |
| [`art/rhine-archive.blend`](art/rhine-archive.blend) | 档案盒基础模型与审阅灯光 |
| [`art/archive-assembly.blend`](art/archive-assembly.blend) | 可按六组结构拆解的模型 |
| [`art/build_archive.py`](art/build_archive.py) | 生成基础模型与 GLB |
| [`art/build_assembly.py`](art/build_assembly.py) | 生成拆解模型与 GLB |
| [`art/internal_architecture.py`](art/internal_architecture.py) | 当前双环内构与连接带 |
| [`art/shell_reference_details.py`](art/shell_reference_details.py) | 顶边方块、螺丝及盖板后刻线 |
| [`art/setup_studio.py`](art/setup_studio.py) | 配置资产审阅灯光与相机 |
普通运行直接使用现有 GLB 即可,无需安装 Blender。重新建模时,可在 Blender 的脚本环境中通过 `runpy.run_path()` 执行对应脚本,或通过 Blender MCP 调用。脚本根据自身位置确定项目目录,重新生成会更新对应模型输出。
## 参考与资源说明
参考作品为《明日方舟》特别映像「莱茵生命:访问」:[BV1rr4y1b7sz](https://www.bilibili.com/video/BV1rr4y1b7sz/)。本项目与官方制作方无隶属关系,原 PV、相关名称、标志与设定的权利归各自权利人所有。原片未展示的档案摘要、日期、研究记录等属于本项目的扩展演示内容。
模型为重新制作;实时折射、景深、灯光与局部细节和原 PV 仍有差异。身份验证画面是演示状态机,不连接真实身份或业务服务。
- **MiSans**:使用小米官方字体文件,保留[字体许可](public/fonts/MiSans-license.pdf)及字体目录内的版权说明,设置页也提供署名与许可入口。
- **Rolling Number**:用于编号和文字滚动,许可见 [`public/licenses/rolling-number.txt`](public/licenses/rolling-number.txt)。
- **声音**:三轨配乐为本项目程序编配;逐字输入使用原 PV 的三个 38ms 短音,来源与处理记录见 [音频说明](public/audio/README.md)。原片短音及其衍生片段不纳入原创配乐的 MIT 授权声明。
- **其他依赖**:各自遵循其原有许可。源码公开不改变第三方资源的权利。
源码包包含运行代码、模型、Blender 工程、说明与验证脚本,不包含依赖目录、本机缓存、原 PV 或完整录制素材。
## 开源许可
本项目自行编写且有权授权的程序代码、建模脚本及配套技术文档采用 [MIT License](LICENSE),版权署名为 **Copyright (c) 2026 LBEILC**。你可以使用、修改、分发这些内容,也可以将其用于商业或闭源项目;分发代码或其重要部分时,须保留版权声明和许可证。软件按原样提供,不作担保,具体以许可证全文为准。
MIT 授权不覆盖第三方权利或自动覆盖仓库内全部素材:
- 《明日方舟》及莱茵生命相关名称、标志、设定、原 PV 和原作视觉设计,以及它们在模型、界面、截图或演示文本中的呈现,不因本项目公开而获得额外授权。本项目无法代替相应权利人授予这些权利。
- Blender / GLB 模型、图像、动图等非代码资产未另行声明为 MIT;建模脚本采用 MIT 不表示脚本生成的原作相关视觉内容也已获授权。
- MiSans、Rolling Number 及其他第三方依赖继续遵循各自的许可证和版权声明,见上方「参考与资源说明」。
复用代码时,请根据用途处理涉及的第三方素材与标志。GitHub 当前源码包已包含本许可证;上方夸克链接为早期打包快照,未包含本次新增的项目许可证文件,最新许可说明以本仓库为准。
+7
View File
@@ -0,0 +1,7 @@
{
"root": "E:\\归档",
"port": 43117,
"exclude": [],
"indexFile": "public/archive-index.json",
"logFile": "00_索引\\操作日志.jsonl"
}
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

+208
View File
@@ -0,0 +1,208 @@
import bpy, math, os
from mathutils import Vector
from pathlib import Path
ROOT = str(Path(__file__).resolve().parents[1])
scene = bpy.data.scenes.new('Rhine_Archive_Work')
bpy.context.window.scene = scene
for old in list(bpy.data.scenes):
if old != scene and old.name.startswith('Rhine_Archive_Asset'):
for obj in list(old.objects):
if len(obj.users_scene)==1:bpy.data.objects.remove(obj,do_unlink=True)
bpy.data.scenes.remove(old)
scene.name='Rhine_Archive_Asset'
for m in list(bpy.data.materials):
if m.users==0:bpy.data.materials.remove(m)
def material(name, color, rough=.3, metal=0, transmission=0):
m=bpy.data.materials.new(name); m.diffuse_color=(*color,1); m.use_nodes=True
p=m.node_tree.nodes.get('Principled BSDF')
p.inputs['Base Color'].default_value=(*color,1)
p.inputs['Roughness'].default_value=rough
p.inputs['Metallic'].default_value=metal
p.inputs['Transmission Weight'].default_value=transmission
p.inputs['IOR'].default_value=1.46
return m
shell=material('Frosted_Polymer',(.985,.975,.963),.36,0,.78)
edge=material('Ivory_Edges',(.94,.916,.892),.28,.02,.72)
core=material('Internal_Ceramic',(.74,.705,.68),.52,.06)
metal=material('Titanium_Fasteners',(.58,.60,.61),.19,.82)
gold=material('Champagne_Index',(.64,.46,.29),.33,.48)
paper=material('Printed_Label',(.91,.89,.84),.65)
diffuser=material('Optical_Diffuser',(.925,.902,.881),.67,0,0)
optics=material('Subsurface_Optics',(.70,.675,.66),.39,.12)
optical_edge=material('Optical_Edges',(.94,.92,.90),.3,.03,.45)
ink=material('Carbon_Ink',(.025,.026,.023),.75)
def cube(name, loc, size, mat, bevel=.015):
bpy.ops.mesh.primitive_cube_add(size=1, location=loc)
o=bpy.context.object; o.name=name; o.dimensions=size
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
o.data.materials.append(mat)
if bevel:
m=o.modifiers.new('Precision radiused edge','BEVEL'); m.width=bevel; m.segments=3
bpy.context.view_layer.objects.active=o; bpy.ops.object.modifier_apply(modifier=m.name)
o.modifiers.new('Weighted corner normals','WEIGHTED_NORMAL')
return o
def torus(name,x,z,radius,tube,mat,y=-.091):
bpy.ops.mesh.primitive_torus_add(major_radius=radius,minor_radius=tube,major_segments=64,minor_segments=10,location=(x,y,z),rotation=(math.pi/2,0,0))
o=bpy.context.object; o.name=name; o.data.materials.append(mat)
for p in o.data.polygons:p.use_smooth=True
return o
def text(name, body,x,z,size,mat=ink):
c=bpy.data.curves.new(name,'FONT'); c.body=body;c.size=size;c.extrude=.0002;c.space_character=1.05
o=bpy.data.objects.new(name,c);scene.collection.objects.link(o)
o.location=(x,-.123,z);o.rotation_euler=(math.pi/2,0,0);c.materials.append(mat)
return o
def annular_profile(name, x, z, profile, mat, segments=128, start=0, end=2*math.pi, sharp=False):
# Closed revolved cross-section: a shallow moulded lens, not a round tube.
vertices=[]; faces=[]; n=len(profile)
closed=abs(end-start-2*math.pi)<1e-6
segments=max(8,math.ceil(segments*(end-start)/(2*math.pi)))
rows=segments if closed else segments+1
for i in range(rows):
a=start+(end-start)*i/segments
for r,y in profile: vertices.append((x+r*math.cos(a),y,z+r*math.sin(a)))
for i in range(segments):
for j in range(n):
faces.append((i*n+j,((i+1)%rows)*n+j,((i+1)%rows)*n+(j+1)%n,i*n+(j+1)%n))
if not closed:
faces.extend([tuple(reversed(range(n))),tuple(segments*n+j for j in range(n))])
mesh=bpy.data.meshes.new(name);mesh.from_pydata(vertices,[],faces);mesh.update()
obj=bpy.data.objects.new(name,mesh);scene.collection.objects.link(obj);mesh.materials.append(mat)
# The clockwise section above yields outward normals, including the bore.
for p in mesh.polygons:p.use_smooth=len(p.vertices)==4
if sharp:
# Keep each section edge hard, but interpolate around the circumference.
# Flat shading alone would facet the circle; all-smooth shading balloons
# the roof/wall junction into a rounded tube.
normals=[]
for face in mesh.polygons:
if face.index>=segments*n:
face.use_smooth=False
normals.extend([tuple(face.normal)]*len(face.loop_indices))
continue
j=face.index%n
dr=profile[(j+1)%n][0]-profile[j][0]
dy=profile[(j+1)%n][1]-profile[j][1]
for loop in face.loop_indices:
row=mesh.loops[loop].vertex_index//n
angle=start+(end-start)*row/segments
normal=Vector((-dy*math.cos(angle),dr,-dy*math.sin(angle))).normalized()
normals.append(tuple(normal))
mesh.normals_split_custom_set(normals)
return obj
def channel(name, points, depth, radius, mat):
curve=bpy.data.curves.new(name,'CURVE');curve.dimensions='3D'
curve.resolution_u=8;curve.bevel_depth=radius;curve.bevel_resolution=2
spline=curve.splines.new('POLY');spline.points.add(len(points)-1)
for point,(x,z) in zip(spline.points,points):point.co=(x,depth,z,1)
obj=bpy.data.objects.new(name,curve);scene.collection.objects.link(obj);curve.materials.append(mat)
return obj
front_cover=cube('Front frosted optical cover',(0,-.095,1.85),(5,.016,3.7),shell,.007)
cube('Rear translucent carrier',(0,.055,1.85),(4.97,.02,3.68),edge,.009)
cube('Information substrate',(0,.025,1.86),(4.80,.012,3.47),diffuser,.006)
for z in [.028,3.672]:cube('Polished perimeter rail',(0,-.021,z),(4.95,.155,.034),edge,.009)
for x in [-2.476,2.476]:cube('Polished perimeter rail',(x,-.021,1.85),(.034,.155,3.66),edge,.009)
# Wide optical cavities sit BEHIND the frosted cover. Their lenticular profiles
# are shallow; no torus protrudes from the exterior face.
for x,z,r in [(-.44,1.92,.79),(1.13,2.48,.435)]:
width=.145 if r>.5 else .102
annular_profile('Embedded optical cavity',x,z,[
(r-width,.016),(r+width,.016),(r+width+.012,.003),
(r+width,-.014),(r+width-.022,-.027),(r+.032,-.040),
(r-.012,-.039),(r-.045,-.052),(r-width+.025,-.054),
(r-width,-.036)],optics)
annular_profile('Subsurface refractive shoulder',x,z,[
(r+.028,-.031),(r+width+.025,-.009),(r+width+.029,-.020),
(r+width+.012,-.032),(r+.056,-.053),(r+.028,-.049)],optical_edge)
annular_profile('Inner optical bevel',x,z,[
(r-width-.012,-.017),(r-width+.036,-.041),
(r-width+.041,-.057),(r-width+.023,-.064),
(r-width+.004,-.061),(r-width-.012,-.036)],optical_edge)
for offset,tube,y in [(width+.009,.006,-.031),(.030,.007,-.054),(-width+.022,.006,-.064)]:
o=torus('Concentric optical machining',x,z,r+offset,tube,optical_edge,y)
o.scale.z=.42
if r<.5:
annular_profile('Embedded amber annulus',x,z,[
(r-.170,-.047),(r-.070,-.047),(r-.067,-.060),
(r-.077,-.071),(r-.156,-.071),(r-.170,-.060)],gold)
cube('Serial label',(-1.36,-.099,3.04),(.99,.02,.41),paper,.003)
cube('Label top rule',(-1.36,-.116,3.23),(.98,.004,.008),ink,0)
cube('Label bottom rule',(-1.36,-.116,2.847),(.98,.004,.005),ink,0)
text('Company label','RHINE LAB, LLC.',-1.825,3.105,.105)
text('Database label','INTERNAL DATABASE',-1.825,3.017,.042,core)
text('Serial number','NO.001',-1.825,2.875,.148)
text('Information label','INFO',-.99,3.075,.076)
text('Symbol label','+ / -',-.99,2.9,.095)
for i in range(16):
o=cube('Laser etched vent',(1.04+i*.054,-.111,.57),(.023,.009,.1),core,.003);o.rotation_euler.y=.4
for i in range(25):cube('Calibration mark',(-2.23,-.108,.61+i*.052),(.035 if i%5 else .075,.005,.006),core,0)
text('Edge inscription','R H I N E L A B',-1.81,.28,.063,core)
for z,x1,x2 in [(3.36,-1.8,.2),(3.36,.4,1.55),(.4,-1.45,1.75)]:
cube('Engraved circuit trace',((x1+x2)/2,-.108,z),(x2-x1,.004,.005),core,.002)
# Two sides of a shallow pressed channel, observed in the 37–39 second close-up.
# Keep the entire channel behind the front cover to avoid coplanar stippling.
top=[(-2.27,3.06),(-2.27,3.30),(-2.10,3.45),(-1.72,3.45),(-1.61,3.51),
(-.65,3.51),(-.55,3.46),(.37,3.46),(.43,3.52),(.49,3.46),
(1.08,3.46),(1.17,3.52),(1.20,3.50),(1.13,3.42),
(1.79,3.42),(1.89,3.51),(2.19,3.51),(2.29,3.41),(2.29,3.03)]
channel('Moulded circuit channel shadow',top,-.071,.005,core)
channel('Moulded circuit channel lip',[(x,z-.018) for x,z in top],-.075,.008,optical_edge)
perimeter=[(-2.18,2.85),(-2.23,2.72),(-2.23,.43),(-2.12,.30),
(2.08,.30),(2.24,.44),(2.24,2.97)]
channel('Moulded inner perimeter',perimeter,-.067,.008,optical_edge)
for z in [.080,3.620]:
cube('Carrier mating seam',(0,.002,z),(4.82,.012,.010),optical_edge,.003)
for x in [-2.420,2.420]:
cube('Carrier mating seam',(x,.002,1.85),(.010,.012,3.54),optical_edge,.003)
# Small raised pads under the diagonal calibration vents.
for i in range(16):
cube('Moulded vent footing',(1.04+i*.054,-.071,.435),(.015,.014,.018),optical_edge,.004)
detail_script=Path(ROOT)/'art/clear_reference_details.py'
exec(compile(detail_script.read_text(encoding='utf-8-sig'),str(detail_script),'exec'))
# Convert text, bake modifiers, and group by material for efficient instancing.
bpy.ops.object.select_all(action='SELECT')
for o in list(scene.objects):
bpy.context.view_layer.objects.active=o
if o.type in ['FONT','CURVE']:bpy.ops.object.convert(target='MESH')
for m in list(o.modifiers):
try:bpy.ops.object.modifier_apply(modifier=m.name)
except:pass
for mat in list(dict.fromkeys(o.data.materials[0] for o in scene.objects if o.type=='MESH' and o.data.materials)):
bpy.ops.object.select_all(action='DESELECT')
obs=[o for o in scene.objects if o.type=='MESH' and o.data.materials and o.data.materials[0]==mat]
if not obs:continue
for o in obs:o.select_set(True)
bpy.context.view_layer.objects.active=obs[0];bpy.ops.object.join()
o=bpy.context.object;o.name=mat.name
scene.cursor.location=(0,0,0);bpy.ops.object.origin_set(type='ORIGIN_CURSOR')
# Bake orientation to make every exported group share the same coordinate frame.
bpy.ops.object.transform_apply(location=False,rotation=True,scale=True)
# Annotated reference: the visible end face occupies roughly half a row pitch.
for obj in scene.objects:
if obj.type=='MESH':
if obj.data.materials[0].name.startswith(('Optical_Glass_', 'Optical_Bridge_Glass', 'Amber_Optical_Inlay')):
bpy.context.view_layer.objects.active=obj
bpy.ops.object.select_all(action='DESELECT');obj.select_set(True)
obj.scale.y *= 2.0
bpy.ops.object.transform_apply(location=False,rotation=False,scale=True)
else:
for vertex in obj.data.vertices:vertex.co.y *= 2.0
bpy.ops.object.select_all(action='SELECT')
export_path=Path(ROOT)/'art/.cache/archive-cassette.glb'
export_path.parent.mkdir(parents=True,exist_ok=True)
bpy.ops.export_scene.gltf(filepath=str(export_path),export_format='GLB',use_selection=True,use_active_scene=True,export_apply=True)
os.replace(str(export_path),ROOT+'/public/assets/archive-cassette.glb')
bpy.data.libraries.write(ROOT+'/art/rhine-archive.blend', {scene}, fake_user=True)
print('Exported archive cassette:',len(scene.objects),'material groups')
+57
View File
@@ -0,0 +1,57 @@
import bpy
from pathlib import Path
BUILD_ROOT = Path(__file__).resolve().parents[1]
ROOT = BUILD_ROOT
source = (ROOT/'art/build_archive.py').read_text(encoding='utf-8')
prefix = source.split('# Convert text, bake modifiers')[0]
prefix = prefix.replace("scene = bpy.data.scenes.new('Rhine_Archive_Work')", "scene = bpy.data.scenes.new('Rhine_Assembly_Work')")
prefix = prefix.replace("if old != scene and old.name.startswith('Rhine_Archive_Asset'):", "if False:")
prefix = prefix.replace("scene.name='Rhine_Archive_Asset'", "scene.name='Rhine_Assembly_Asset'")
exec(compile(prefix, str(ROOT/'art/build_archive.py'), 'exec'))
ROOT = BUILD_ROOT
def part_for(name):
if name.startswith(('Rear translucent carrier', 'Polished perimeter rail', 'Ivory spine cap', 'Carrier mating seam')):
return 'carrier'
if name.startswith('Information substrate'): return 'substrate'
if name.startswith(('Embedded optical cavity', 'Embedded amber annulus', 'Folded optical tab')): return 'optical-core'
if name.startswith(('Subsurface refractive shoulder', 'Inner optical bevel', 'Concentric optical machining', 'Optical ribbon')):
return 'optical-lenses'
if name.startswith(('Countersunk washer', 'Machined screw', 'Screw slot')): return 'fasteners'
return 'cover'
for obj in list(scene.objects):
obj['assemblyPart'] = part_for(obj.name)
bpy.context.view_layer.objects.active = obj
bpy.ops.object.select_all(action='DESELECT')
obj.select_set(True)
if obj.type in ['FONT','CURVE']: bpy.ops.object.convert(target='MESH')
for mod in list(obj.modifiers):
bpy.ops.object.modifier_apply(modifier=mod.name)
if obj.data.materials[0].name.split('.')[0] == 'Carbon_Ink':
bpy.data.objects.remove(obj, do_unlink=True)
pairs = {}
for obj in scene.objects:
key = (obj['assemblyPart'], obj.data.materials[0].name)
pairs.setdefault(key, []).append(obj)
for (part, surface), objects in pairs.items():
bpy.ops.object.select_all(action='DESELECT')
for obj in objects: obj.select_set(True)
bpy.context.view_layer.objects.active = objects[0]
bpy.ops.object.join()
obj = bpy.context.object
obj.name = part+'__'+surface.split('.')[0]
obj['assemblyPart'] = part
scene.cursor.location = (0,0,0)
bpy.ops.object.origin_set(type='ORIGIN_CURSOR')
bpy.ops.object.transform_apply(location=False, rotation=True, scale=True)
if surface.startswith(('Optical_Glass_', 'Optical_Bridge_Glass', 'Amber_Optical_Inlay')):
obj.scale.y *= 2.0
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
else:
for vertex in obj.data.vertices:vertex.co.y *= 2.0
bpy.ops.object.select_all(action='SELECT')
export_path=ROOT/'art/.cache/archive-assembly.glb'
export_path.parent.mkdir(parents=True,exist_ok=True)
bpy.ops.export_scene.gltf(filepath=str(export_path), export_format='GLB', use_selection=True, use_active_scene=True, export_apply=True, export_extras=True)
os.replace(str(export_path),str(ROOT/'public/assets/archive-assembly.glb'))
bpy.data.libraries.write(str(ROOT/'art/archive-assembly.blend'), {scene}, fake_user=True)
print('Assembly exported:', len(scene.objects), 'meshes, parts:', sorted({o['assemblyPart'] for o in scene.objects}))
+47
View File
@@ -0,0 +1,47 @@
# Detail reconstruction from the clear 40–42 second reference.
# Executed by build_archive.py in the same scene; assembly uses this identical source.
# All dimensions below are BEFORE the shared x2 thickness bake.
from pathlib import Path
obsolete=('Embedded optical cavity','Subsurface refractive shoulder','Inner optical bevel',
'Concentric optical machining','Embedded amber annulus','Moulded circuit channel',
'Moulded inner perimeter','Moulded vent footing','Laser etched vent',
'Calibration mark','Edge inscription','Engraved circuit trace')
for obj in list(scene.objects):
if obj.name.startswith(obsolete): bpy.data.objects.remove(obj,do_unlink=True)
emboss=material('Moulded_Lettering',(.81,.786,.75),.24,.13)
architecture_script=Path(ROOT)/'art/internal_architecture.py'
exec(compile(architecture_script.read_text(encoding='utf-8'),str(architecture_script),'exec'))
# Rebuild the moulding from normalized positions on the complete 41.0-second face.
def face_point(px,py):return ((px-432)/1022*5-2.5,(1018-py)/676*3.7)
def mould(name,pixels,depth=-.074,radius=.004,mat=core):
return channel(name,[face_point(x,y) for x,y in pixels],depth,radius,mat)
shell_script=Path(ROOT)/'art/shell_reference_details.py'
exec(compile(shell_script.read_text(encoding='utf-8'),str(shell_script),'exec'))
# Fine, lightly recessed rectangular backing routes, beneath the ring structures.
for route in [[(681,470),(681,899),(1061,899),(1061,821),(1310,821),(1310,464),(1028,464),(1028,406)]]:
mould('Information substrate fine route',route,.014,.0018,core)
# Embossed vertical company inscription belongs to the inner cover, not the ink label.
inscription=text('Moulded vertical lettering','RHINE LAB, LLC.',0,0,.155,emboss)
# Text baseline follows Blender local X; rotate it down the left edge in the X/Z plane.
inscription.location=(-2.27,-.078,2.29)
inscription.rotation_euler=(math.pi/2,math.pi/2,0)
inscription.data.extrude=.003;inscription.data.bevel_depth=.0015;inscription.data.bevel_resolution=1
inscription.data.space_character=1.10
# Two small registration pads under the floating tabs.
for x in [-1.61,-1.45]:cube('Information substrate registration pad',(x,-.029,.53),(.014,.009,.038),gold,.002)
# Lower-right circular service detail with a slanted slotted rail, seen under haze.
px,pz=face_point(1312,923)
for radius,depth,mat in [(.145,-.070,optical_edge),(.120,-.065,core),(.083,-.072,optical_edge)]:
torus('Moulded service socket',px,pz,radius,.0055,mat,depth).scale.z=.45
annular_profile('Moulded service socket center',px,pz,[(.021,-.040),(.043,-.040),(.048,-.060),(.035,-.074),(.021,-.068)],gold,32)
for i in range(11):
x=1.03+i*.063
channel('Moulded diagonal vent',[(x-.035,.38),(x+.030,.53)],-.075,.008,optical_edge)
channel('Moulded diagonal vent recess',[(x-.029,.38),(x+.036,.53)],-.071,.003,core)
+107
View File
@@ -0,0 +1,107 @@
"""User-selected first internal model, restored from revision 89c7742.
Keep the current outer case and reveal animation. This shared Blender source
restores the annular lenses, inner hubs, muted orange and paired ribbons.
"""
amber_inlay=material('Amber_Optical_Inlay',(.48,.18,.052),.32,.28)
film=material('Optical_Film',(.79,.755,.712),.30,.06)
film_edge=material('Optical_Film_Edge',(.57,.545,.515),.27,.20)
clip_metal=material('Optical_Clips',(.34,.335,.32),.34,.55)
# Large annular volume: broad, segmented shallow chamber surrounding a narrow raised bore.
# The previous uniformly thick concentric tubes hid the asymmetric breaks.
bx,bz=-.425,1.80
outer_profile=[(.625,.013),(.918,.013),(.950,.004),(.949,-.010),
(.928,-.023),(.897,-.037),(.848,-.046),(.717,-.048),
(.667,-.044),(.635,-.030)]
for start,end in [(1,89.7),(90.3,177.5),(181,227.5),(229,308.7),(309.3,359.7)]:
annular_profile('Embedded optical cavity segment',bx,bz,outer_profile,optics,128,
math.radians(start),math.radians(end))
# Each lip has its own depth so the cross-section stays readable when unassembled.
annular_profile('Inner optical bevel hub',bx,bz,[
(.501,-.020),(.614,-.020),(.635,-.033),(.632,-.057),
(.615,-.069),(.547,-.071),(.512,-.056),(.501,-.040)],optical_edge)
for start,end in [(2,177),(181,227),(230,359)]:
annular_profile('Subsurface refractive shoulder sector',bx,bz,[
(.670,-.031),(.929,-.010),(.948,-.017),(.942,-.026),
(.914,-.034),(.867,-.043),(.731,-.054),(.673,-.049)],optical_edge,128,
math.radians(start),math.radians(end))
# Thin glazing lips and a partial inner copper track seen at the upper-left.
for radius,depth in [(.943,-.029),(.650,-.054),(.526,-.064)]:
annular_profile('Concentric optical machining lip',bx,bz,[
(radius-.004,depth+.003),(radius+.004,depth+.003),
(radius+.005,depth-.001),(radius,depth-.004),
(radius-.004,depth-.002)],film_edge)
annular_profile('Embedded amber annulus inner arc',bx,bz,[
(.546,-.061),(.568,-.061),(.574,-.070),(.568,-.075),(.548,-.075)],amber_inlay,
128,math.radians(109),math.radians(225))
annular_profile('Embedded amber annulus outer arc',bx,bz,[
(.910,.009),(.982,.009),(.986,-.004),(.978,-.011),(.953,-.020),(.922,-.017)],amber_inlay,
128,math.radians(229),math.radians(364))
# Radial joints cross the wide outer lens, staying under the cover.
for angle in [90,103,179,228,260,309]:
a=math.radians(angle)
channel('Concentric optical machining radial seam',
[(bx+r*math.cos(a),bz+r*math.sin(a)) for r in [.674,.74,.88,.937]],
-.057,.0024,film_edge)
# Small annular volume: concentric clear shoulder, darker seat, flat amber annulus and hub.
sx,sz=1.125,2.455
annular_profile('Embedded optical cavity small',sx,sz,[
(.220,.014),(.511,.014),(.555,.000),(.560,-.015),
(.529,-.035),(.471,-.050),(.360,-.055),(.231,-.037)],optics)
annular_profile('Subsurface refractive shoulder small',sx,sz,[
(.388,-.034),(.536,-.013),(.573,-.017),(.579,-.030),
(.553,-.047),(.502,-.059),(.424,-.063),(.386,-.051)],optical_edge)
annular_profile('Embedded amber annulus small',sx,sz,[
(.230,-.045),(.347,-.045),(.356,-.056),(.351,-.071),
(.241,-.071),(.230,-.060)],amber_inlay)
annular_profile('Inner optical bevel small hub',sx,sz,[
(.170,-.041),(.226,-.041),(.235,-.054),(.231,-.069),
(.184,-.069),(.172,-.057)],optical_edge)
for radius in [.381,.478,.565]:
annular_profile('Concentric optical machining small lip',sx,sz,[
(radius-.003,-.057),(radius+.003,-.057),(radius+.004,-.062),
(radius,-.064),(radius-.003,-.062)],film_edge)
# The reference shows two nearly clear ribbon-like spans. Their physical role is
# unknown; recreate visible sheet/rim geometry without inventing a mechanism.
def ribbon(name, controls, width, depth):
points=[];verts=[];faces=[];segments=40
for i in range(segments+1):
t=i/segments;u=1-t
x=sum(w*p[0] for w,p in zip([u**3,3*u*u*t,3*u*t*t,t**3],controls))
z=sum(w*p[1] for w,p in zip([u**3,3*u*u*t,3*u*t*t,t**3],controls))
dx=3*u*u*(controls[1][0]-controls[0][0])+6*u*t*(controls[2][0]-controls[1][0])+3*t*t*(controls[3][0]-controls[2][0])
dz=3*u*u*(controls[1][1]-controls[0][1])+6*u*t*(controls[2][1]-controls[1][1])+3*t*t*(controls[3][1]-controls[2][1])
length=math.hypot(dx,dz);nx=-dz/length;nz=dx/length
points.append(((x-width*nx/2,z-width*nz/2),(x+width*nx/2,z+width*nz/2)))
for yy in [depth-.002,depth+.002]:
for sign in [-1,1]:verts.append((x+sign*width*nx/2,yy,z+sign*width*nz/2))
for i in range(segments):
a=i*4;b=(i+1)*4
faces.extend([(a,b,b+1,a+1),(a+2,a+3,b+3,b+2),
(a,a+2,b+2,b),(a+1,b+1,b+3,a+3)])
faces.extend([(0,1,3,2),(segments*4,segments*4+2,segments*4+3,segments*4+1)])
mesh=bpy.data.meshes.new(name);mesh.from_pydata(verts,[],faces);mesh.update()
obj=bpy.data.objects.new(name,mesh);scene.collection.objects.link(obj);mesh.materials.append(film)
for face in mesh.polygons:face.use_smooth=True
for side in [0,1]:channel(name+' edge',[p[side] for p in points],depth-.003,.0018,film_edge)
ribbon('Optical ribbon inner',[(.31,1.16),(.99,1.33),(1.00,1.78),(.73,2.20)],.10,-.008)
ribbon('Optical ribbon outer',[(.03,.94),(.97,1.13),(1.14,1.75),(.84,2.13)],.11,.007)
channel('Optical ribbon tangent upper',[(-.67,2.73),(.98,3.01)],.003,.0018,film_edge)
channel('Optical ribbon tangent lower',[(.06,.91),(1.44,2.03)],.006,.0018,film_edge)
# Omit the three tall structures to the left, as requested: their depth would
# exceed the cassette. Use the 46–51 second views only for the annular buildings.
# Closely spaced facade mullions sit under the roof edge, visible from oblique views.
for cx,cz,r,count in [(bx,bz,.945,84),(sx,sz,.544,52)]:
for i in range(count):
angle=2*math.pi*i/count
if cx==bx and 177<math.degrees(angle)<182:continue
obj=cube('Embedded optical cavity facade mullion',
(cx+r*math.cos(angle),-.011,cz+r*math.sin(angle)),(.009,.056,.0034),film_edge,.001)
obj.rotation_euler.y=-angle
annular_profile('Embedded amber annulus small lower fascia',sx,sz,[
(.526,.010),(.557,.010),(.561,.001),(.558,-.009),(.532,-.009)],amber_inlay)
Binary file not shown.
+22
View File
@@ -0,0 +1,22 @@
import bpy
from mathutils import Vector
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
s=bpy.context.scene
for obj in list(s.objects):
if obj.type in ['CAMERA','LIGHT']:bpy.data.objects.remove(obj,do_unlink=True)
bpy.ops.object.camera_add(location=(-7,-11,6))
c=bpy.context.object;c.name='Asset_Review_Camera'
c.rotation_euler=((Vector((0,0,1.9))-c.location).to_track_quat('-Z','Y').to_euler())
c.data.type='ORTHO';c.data.ortho_scale=7.3;s.camera=c
s.world=bpy.data.worlds.new('Warm_Studio_World');s.world.use_nodes=True
s.world.node_tree.nodes['Background'].inputs[0].default_value=(.8,.77,.72,1)
s.world.node_tree.nodes['Background'].inputs[1].default_value=.65
for name,loc,power,size in [('Softbox_Key',(-4,-5,8),950,7),('Softbox_Rim',(5,1,6),1100,5),('Softbox_Fill',(1,-7,3),350,6)]:
bpy.ops.object.light_add(type='AREA',location=loc)
light=bpy.context.object;light.name=name;light.data.energy=power;light.data.shape='DISK';light.data.size=size
light.rotation_euler=((Vector((0,0,2))-light.location).to_track_quat('-Z','Y').to_euler())
s.render.engine='CYCLES';s.cycles.samples=48;s.cycles.use_denoising=True
s.render.resolution_x=1200;s.render.resolution_y=1000;s.render.resolution_percentage=100
s.render.film_transparent=False;s.render.filepath=str(ROOT/'art/archive-studio.png')
bpy.ops.wm.save_as_mainfile(filepath=str(ROOT/'art/rhine-archive.blend'))
+63
View File
@@ -0,0 +1,63 @@
# Reference face: x=24..1021, y=6..667. Shared by both Blender exports.
# The engraved frame is below the cover, so the moving frost masks every route.
engraving=material('Case_Engraving',(.57,.54,.50),.39,.12)
lip=material('Case_Engraving_Highlight',(.90,.87,.83),.25,.16)
inlay=material('Index_Inlay',(.67,.53,.40),.52,.04)
def case_point(px,py):return ((px-24)/997*5-2.5,(667-py)/661*3.7)
def route(name,pixels):
points=[case_point(x,y) for x,y in pixels]
channel('Case engraved '+name,points,-.075,.0045,engraving)
channel('Case pressed lip '+name,[(x+.007,z-.009) for x,z in points],-.079,.004,lip)
# The top patch finishes at the case's top and front planes: no raised block.
x,z=case_point(92,26)
cut=cube('Temporary inlay recess',(x,-.100,z),(.25,.030,2*(3.7-z)+.002),core,0)
bpy.context.view_layer.objects.active=front_cover
mod=front_cover.modifiers.new('Flush index pocket','BOOLEAN');mod.operation='DIFFERENCE';mod.object=cut
bpy.ops.object.modifier_apply(modifier=mod.name);bpy.data.objects.remove(cut,do_unlink=True)
cube('Index flush inlay',(x,-.102,z),(.25,.002,2*(3.7-z)),inlay,.0008)
route('outer upper left',[(974,14),(35,14),(35,612)])
route('outer lower right',[(1011,64),(1011,655),(80,655)])
route('upper circuit',[(35,171),(54,171),(78,143),(78,76),(98,57),(172,57),
(191,46),(383,46),(405,57),(598,57),(617,68),(650,68),(655,64),(650,57),
(620,57),(605,44),(600,43),(596,47),(597,54),(614,67),(760,67),
(779,53),(782,47),(778,43),(773,44),(757,57),(722,57),(719,61),(720,68),
(763,68),(780,57),(894,57),(917,40),(973,40)])
route('left hook',[(68,47),(51,64),(51,105),(53,111),(60,113),(64,109),
(64,70),(90,42),(128,42),(134,39),(135,34),(131,28),(119,28)])
route('inner frame',[(115,565),(115,84),(132,67),(948,67),(968,85),(968,162),
(981,192),(981,550),(909,622),(131,622),(118,609),(118,590)])
route('lower return',[(128,629),(911,629),(985,556)])
route('bottom catch',[(793,652),(798,648),(890,648),(894,653),(1010,653)])
for label,px,py,start,end in [('upper right',999,37,0.30*math.pi,1.84*math.pi),
('lower left',57,642,-.75*math.pi,.78*math.pi)]:
x,z=case_point(px,py)
# C-shaped shallow boss, tied into the pressed frame rather than a raised ring.
for radius in [.104,.127]:
points=[(x+radius*math.cos(start+(end-start)*i/64),
z+radius*math.sin(start+(end-start)*i/64)) for i in range(65)]
channel('Case engraved screw boss '+label,points,-.076,.0045,engraving)
channel('Case pressed screw boss '+label,[(a+.006,b-.008) for a,b in points],-.080,.004,lip)
# A real opening leaves the flush metal head readable under a frosted cover.
bpy.ops.mesh.primitive_cylinder_add(vertices=64,radius=.082,depth=.06,
location=(x,-.095,z),rotation=(math.pi/2,0,0))
cut=bpy.context.object
bpy.context.view_layer.objects.active=front_cover
mod=front_cover.modifiers.new('Flush fastener opening','BOOLEAN');mod.operation='DIFFERENCE';mod.object=cut
bpy.ops.object.modifier_apply(modifier=mod.name);bpy.data.objects.remove(cut,do_unlink=True)
annular_profile('Countersunk washer '+label,x,z,
[(.061,-.094),(.080,-.094),(.080,-.100),(.071,-.103),(.063,-.103),(.061,-.099)],lip,64)
bpy.ops.mesh.primitive_cylinder_add(vertices=48,radius=.060,depth=.010,
location=(x,-.098,z),rotation=(math.pi/2,0,0))
head=bpy.context.object;head.name='Machined screw '+label;head.data.materials.append(metal)
mod=head.modifiers.new('Head chamfer','BEVEL');mod.width=.003;mod.segments=3
bpy.ops.object.modifier_apply(modifier=mod.name)
for angle in [-.22,math.pi/2-.22]:
cut=cube('Temporary drive cutter',(x,-.103,z),(.069,.008,.014),core,.001)
cut.rotation_euler.y=angle
bpy.context.view_layer.objects.active=head
mod=head.modifiers.new('Cross recessed drive','BOOLEAN');mod.operation='DIFFERENCE';mod.object=cut
bpy.ops.object.modifier_apply(modifier=mod.name);bpy.data.objects.remove(cut,do_unlink=True)
head.modifiers.new('Fastener normals','WEIGHTED_NORMAL')
+22
View File
@@ -0,0 +1,22 @@
# 真实文件夹与玻璃档案交互
2026-09-10 用户提出:将当前阵列交互迁移到桌面真实文件夹。当前阶段为可行性评估,没有安装额外服务或读取用户目录。
## 结论
现有 Three.js 模型与拖动、波浪、抽取交互可以复用。若需要展示普通文件夹的内容,并由系统默认程序打开文件,建议网页壁纸配合一个本地程序;或将前端装入桌面应用容器。当前网页壁纸公开接口不足以单独承诺完整功能。
Wallpaper Engine 的网页 directory 属性支持图片、视频目录,不等于通用文件系统浏览。官方 openUserShortcut 文档属于 SceneScript,文档明确只允许在鼠标事件中调用,不能直接当成网页 JavaScript 接口使用。
## 推荐的后续小范围试验
由用户选择一个根目录;每列代表子文件夹、每张玻璃档案代表文件。选中显示名称、类型、大小和修改日期,点击“打开”调用系统默认程序,另提供“在资源管理器中显示”。缺失文件有明确提示,目录改动后刷新。初版不包含删除、移动、重命名,也不要求接管系统桌面图标。
本地程序只监听回环地址,用每次启动的访问凭据限制网页访问;只允许用户选定根目录下的文件,校验解析后的路径及链接目标,不接受任意命令行。网页发送文件标识,由本地程序解析已登记路径后打开。配套程序需要安装/启动/退出入口;发布时必须明确它是额外组件。
独立桌面应用能更完整地支持键盘、文件拖放和系统对话框,但嵌入桌面背景、多显示器与桌面图标的关系需要单独实机验证。不能把普通透明窗口称作已完成桌面集成。
## 官方依据
- 网页用户属性与目录范围:https://docs.wallpaperengine.io/en/web/customization/properties.html
- SceneScript 用户快捷方式:https://docs.wallpaperengine.io/en/scene/userproperties/usershortcut.html
+146
View File
@@ -0,0 +1,146 @@
# 本地归档接入(LOCAL ARCHIVE)
把 `E:\归档` 接进本界面:主阵列展示从归档中精选的四十条档案,**LOCAL ARCHIVE** 面板负责检索归档的全部条目并直接执行归档写操作。
## 两块数据的关系
| | 三维阵列 | 主检索的本地归档栏 + LOCAL ARCHIVE 面板 |
| --- | --- | --- |
| 数据 | `public/archive-index.json`(全量,运行时载入) | 同一份索引 |
| 内容 | 列 = 归档顶层内容分类,列内 = 该分类的**全部真实归档条目** | 全量条目(约 5 万条路径) |
| 用途 | 三维阵列、详情、解密动效、抽盘与拖拽 | 列表检索、打开、定位、归档操作 |
| 条数约束 | 无(随归档变化) | 无 |
| 降级 | 索引不可用时退回 `content/archives.json` 的四十条精选档案 | 退回静态快照,只读 |
**2026-09-12 起阵列已改为全量归档驱动**,因此归档里新增的文件会直接出现在阵列中(换到它所在的列即可)。三维阵列与检索列表共用同一份内存索引(`loadArchiveIndex`),不会重复下载。
四十条精选档案现在只作为降级数据集与 TXT 摘要来源,见 [`../content/README.md`](../content/README.md)。全量阵列的设计、性能与证据见 [`../verification/ARCHIVE-ARRAY.md`](../verification/ARCHIVE-ARRAY.md)。
## 启动
推荐一条命令同时拉起归档服务与开发服务器:
```bash
npm run dev:archive
```
也可以分开启动(两个终端):
```bash
npm run archive:serve # 归档服务,默认 127.0.0.1:43117
npm run dev # Vite 开发服务器
```
只跑 `npm run dev` 也能用:面板会退回**只读检索模式**,此时打开文件、定位与归档操作会被禁用并给出提示。
启动后点击顶部导航的 **LOCAL ARCHIVE**。
## 数据更新
服务启动时会扫描一次归档并在 `E:\归档` 上挂一个递归监视器。归档里新增或改动文件后,约 2.5 秒防抖,服务自动重新生成快照,并通过事件流通知已打开的页面重新载入索引——不需要手动刷新。
也可以点面板状态栏的**重新扫描 ↻**,或调用 `POST /archive-api/refresh`。
`npm run dev` / `npm run build` 的钩子也会各生成一次快照。
## 详情页的「文件位置」按钮
详情页右下角的 **SHOW IN FOLDER / 文件位置** 按钮(原先是 EXPORT 下载 TXT)会直接在资源管理器中定位当前档案在 `E:\归档` 中的位置,用的是该条精选档案在 `content/archives.json` 里的真实 `path`,通过本地归档服务的 `POST /reveal` 完成。
- 服务未运行时按钮会提示"需要本地归档服务:请运行 `npm run dev:archive`",不会静默失败。
- 详情页脚注显示的是归档内的相对路径文本。浏览器不允许网页跳转 `file:` 链接,所以它不再是可点击链接。
- `public/archives/*.txt` 仍会为每条档案生成,但详情页不再提供下载入口。
## 面板能做什么
**检索**:输入任意片段,匹配相对路径(含文件名与扩展名)。命中数实时显示,单次最多渲染 200 行,匹配片段高亮。索引在第一次打开面板时载入一次,之后在内存中过滤,不再走网络。
**每行的三个动作**:
| 按钮 | 行为 | 是否需要服务 |
| --- | --- | --- |
| 打开 | 用系统默认程序打开文件;目录则用资源管理器打开 | 是 |
| 定位 | 在资源管理器中选中该条目 | 是 |
| 归档… | 选中该条目作为源,在右侧进入归档操作 | 是 |
**归档操作(四种,均为安全写)**:
| 操作 | 说明 |
| --- | --- |
| 移动到 | 归档内移动,同盘重命名。**跨盘拒绝**,请改用复制 |
| 复制到 | 递归复制 |
| 解压到 | 调用 `C:\Program Files\7-Zip\7z.exe` 解压到目标目录 |
| 新建分类目录 | 在归档内新建目录 |
一律**先点「预览影响」**:显示源、目标、体积、文件数、是否跨盘,并给出能不能执行的结论。`预览`不通过时执行按钮保持禁用。执行结果用界面右下角的提示条反馈,并追加到操作日志。
**不提供删除与重命名**:这两项不可逆,需要时请在资源管理器中手动完成。
## 安全设计
- **只监听回环**:服务绑定 `127.0.0.1`,不对外网开放。
- **每次启动的访问令牌**:服务启动时生成随机令牌写入项目根目录的 `.archive-token`(已加入 `.gitignore`)。浏览器**不持有**令牌——Vite 在 `/archive-api` 代理层注入它。因此即使别的网页知道端口号,也无法调用本服务。
- **路径围栏**:客户端只传相对路径,服务解析为绝对路径后必须仍落在归档根目录内;越界、绝对路径与 `..` 一律 `403`。符号链接不算越界,但不会被跟随。
- **操作日志**:每次写操作追加一行 JSON 到 `E:\归档\00_索引\操作日志.jsonl`,含时间、操作、源、目标。
- **写操作需要显式确认**:`/apply` 必须带 `confirm: true`,否则 `400`。服务端会**重新计算**一次影响预览,不信任客户端传来的结论。
## 配置
[`archive.config.json`](../archive.config.json):
```json
{
"root": "E:\\归档",
"port": 43117,
"exclude": [],
"indexFile": "public/archive-index.json",
"logFile": "00_索引\\操作日志.jsonl"
}
```
`exclude` 里的目录会被索引跳过(其本身仍作为目录条目保留)。改 `port` 时不需要同步改前端:Vite 代理会在配置加载时读取同一个文件。
## 接口
所有接口都需要 `x-archive-token` 头,经 Vite 代理访问时自动注入。在浏览器或 curl 里直接调用时前缀为 `/archive-api`,直连服务时无前缀。
| 方法与路径 | 用途 |
| --- | --- |
| `GET /status` | 根目录、端口、监视状态、最近一次快照时间、总量 |
| `GET /index` | 全量索引(支持 gzip,约 0.7 MB) |
| `GET /search?q=&limit=` | 服务端检索 |
| `POST /plan` | 影响预览,不修改任何文件 |
| `POST /apply` | 执行写操作,需要 `confirm: true` |
| `POST /open` / `POST /reveal` | 打开文件 / 在资源管理器中定位 |
| `POST /refresh` | 立即重新扫描归档 |
| `GET /events` | 快照更新事件流(SSE) |
## 快照文件
| 文件 | 体积 | 说明 |
| --- | ---: | --- |
| `content/archives.json` | 约 45 KB | 四十条精选档案,页面与 TXT 下载共用 |
| `public/archive-index.json` | 约 7.4 MB | 全量索引,紧凑数组 `[路径, 类型, 字节, mtime]` |
| `public/archives/*.txt` | 40 个 | 每条档案的可下载摘要 |
`archive-index.json` **不进入 PWA 离线预缓存**(`scripts/build-pwa.mjs` 的白名单不含根目录文件),因此离线包体积不受影响;代价是离线状态下打开检索面板只有空态。服务在线时索引走 gzip(约 0.7 MB),比静态快照快得多。
## 构建与部署
生产构建不包含归档服务,也不带 `/archive-api` 代理,线上站点打开面板会显示"只读检索模式",检索走静态 `archive-index.json`,写操作不可用——这是刻意的:本地归档服务不应暴露在公网。
`npm run preview` 同样配置了代理,因此本地预览可以完整体验。
## 故障排查
| 现象 | 处理 |
| --- | --- |
| 面板显示"服务未运行" | 先 `npm run archive:serve`;确认端口 43117 未被占用 |
| 打开/定位提示需要服务 | 同上;这是刻意的降级,检索仍可用 |
| 令牌无效(401) | 服务重启会换新令牌;重启 Vite 让代理读到新值,或直接用 `npm run dev:archive` |
| 「7-Zip 解压失败」 | 确认 `C:\Program Files\7-Zip\7z.exe` 存在;面板 `/status` 的 `sevenZip` 字段会说明 |
| 新增文件没有出现在检索里 | ① 等约 3 秒(防抖),或点「重新扫描 ↻」② 查 `/status` 的 `lastError` 与 `lastScan` ③ 手动 `npm run archive:snapshot`。索引与检索本身极少出错,**先查服务和快照时间,再怀疑代码**。 |
| 面板开了很久、刚加的文件搜不到 | 面板**每次打开都会重新取索引**(服务在线时),关闭面板不再退掉事件订阅;若仍不对,`Ctrl+F5` 整页刷新一次。 |
| 摘要里的数字和归档对不上 | 重新生成快照;`{size}`/`{files}`/`{date}` 只在生成时采样 |
+43
View File
@@ -0,0 +1,43 @@
# 主屏幕安装与离线使用
正式地址:[rhine.lubeiluchen.cc](https://rhine.lubeiluchen.cc/)。
## iPhone / iPad
1. 用 Safari 打开正式地址。
2. 轻点“分享”,选择“添加到主屏幕”;如果出现“作为网页 App 打开”,保持开启。
3. 从主屏幕的莱茵生命图标打开,即可使用没有 Safari 地址栏的独立窗口。系统状态栏和底部手势区域会保留安全间距。
首次保持联网,在终端设置的“主屏幕与离线”区域看到“离线资源已就绪”后,即可断网重新打开。离线资源约 34 MiB,包含模型、完整字体分包、音乐与 40 份档案。普通页面按需加载字体,完整离线资源在进入终端后于后台准备。Safari 标签页和主屏幕 App 的存储可能各自独立,请以主屏幕 App 内显示的状态为准。
如果浏览器清除了网站数据或系统回收了缓存,需要联网重新准备离线资源。启用声音时,资源就绪后轻触“点击进入”(或按 Enter),声音准备完成后统一开始开机动画;关闭全部声音的用户直接进入。声音加载失败可重试,也可选择“关闭声音并进入”,此选择会关闭并保存音效与音乐开关。开启减少动态效果时仍可点击进入并播放声音,但会跳过开机动画。
## 桌面与其他手机
支持安装的浏览器会在终端设置中提供“安装到设备”,也可使用浏览器菜单安装。不同浏览器的菜单名称可能不同;普通浏览器标签页同样支持在线使用。
阵列上左右滑动切列,上下滑动切档;也可轻点方向按钮。详情中的正文独立滚动。360° 查看器支持单指旋转、双指缩放和平移,以及拆解、重组和复位。横竖屏切换保留当前档案和查看器状态。
## 更新
新版本会在后台下载完整资源,准备好后在页面底部提供“更新并重启”,也可从右上角“设置”中更新。更新不会清除收藏与偏好。下载失败时继续使用上一个完整离线版本,联网后重试即可。
如果旧版只有提示、找不到更新入口,请打开[更新终端](https://rhine.lubeiluchen.cc/update.html),点击“更新并返回”。这个入口从网络获取,不经过旧版本的页面缓存;应用完整新版后返回终端。普通“清除缓存”不一定清除 Service Worker 的离线副本,无需清空网站数据或收藏。
## 开机动画与选档动效
首次进入时,本站遵循系统的减少动画偏好。Win11 的“设置 → 辅助功能 → 视觉效果 → 动画效果”关闭时,Chrome / Edge 可能请求减少动画,本站会跳过开机动画并简化选档等动效。终端之后会保存本地偏好,资源更新不会重置它。
需要完整动效时,打开右上角圆形图标下标有“设置”的按钮,选择“启用完整动效并重播”。这只调整本站,无需修改系统设置。减少动态效果开关也可随时重新开启。
## 开发与 Vercel
`npm run build` 生成静态站点和带内容版本号的 Service Worker,输出在 `dist`。现有 Vercel 项目沿用 GitHub 自动部署,配置见 `vercel.json`。Service Worker、manifest 与构建清单使用重新验证缓存头。离线功能只在正式构建的 HTTPS 或 localhost 环境注册,`npm run dev` 不注册。
本地验证:运行 `npm run build`,再运行 `npm run preview`。浏览器测试见 `scripts/check-pwa.mjs`,需要本机可用的 Playwright 与 Chrome;可通过 `PLAYWRIGHT_MODULE` 指定已有 Playwright 模块路径。
`scripts/check-startup-motion.mjs` 验证系统偏好与本站覆盖、重播和正文解密;`scripts/check-pwa-recovery.mjs` 验证旧版迁移,需要以 `PWA_PREVIOUS_DIST` 指定保留的旧生产构建。两个脚本可设 `REVIEW_CHANNEL=msedge` 验证 Edge。更新恢复页保持网络获取,未加入离线资源清单。
图标源自项目共享莱茵生命 SVG 路径,生成脚本为 `scripts/build-icons.mjs`,通过 `SHARP_MODULE` 可指定本地 Sharp 模块。修改资源后重新构建即可生成新的离线版本,无需手动修改缓存编号。
平台说明参考:[WebKit 主屏幕 Web App](https://webkit.org/blog/13878/web-push-for-web-apps-on-ios-and-ipados/)、[Safari 26 主屏幕安装行为](https://webkit.org/blog/17333/webkit-features-in-safari-26-0/)、[Service Worker 生命周期](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers)。
+261
View File
@@ -0,0 +1,261 @@
# Wallpaper Engine 本地实验
分支:`codex/wallpaper-engine`。2026-09-10 已在本机 Wallpaper Engine 的独立窗口中运行,尚未发布创意工坊、尚未应用到桌面,不合并主分支。
## 本机入口
Wallpaper Engine 本地库名称:**Rhine Lab · 莱茵生命档案终端(本地测试)**。
安装目录:`D:\Game\Steam\steamapps\common\wallpaper_engine\projects\myprojects\rhine-lab-local`。
当前预览窗口:**Rhine Lab - Wallpaper Test**。重新打开独立预览:
```powershell
& 'D:\Game\Steam\steamapps\common\wallpaper_engine\wallpaper64.exe' -control openWallpaper -file 'D:\Game\Steam\steamapps\common\wallpaper_engine\projects\myprojects\rhine-lab-local\project.json' -playInWindow 'Rhine Lab - Wallpaper Test' -width 1600 -height 900
```
项目目录中的 `project.json` 带有本地标题、预览图片和可调属性。只有构建产物目录应导入编辑器,不能导入整个源码仓库。重新发布未来已有的工坊项目时,应保留该项目的工坊 ID 与元数据。
## 构建与行为
```sh
npm run build:wallpaper
node scripts/check-wallpaper.mjs
```
构建输出 `release/wallpaper`,约 34.1 MiB,823 个文件,包括完整字体分包、模型、音频、档案 TXT 和许可。字体和模型均本地读取,无需开发服务器或在线站点。源码、Blender 工程、参考视频、诊断服务不进入壁纸包。
- Vite 的 `wallpaper` 模式使用相对资源路径,HTML 中提前注册宿主回调,以免丢失模块载入前的初始属性。
- 自动启动;宿主可以关闭开场,直接进入阵列。减少动态效果继续遵循显式设置。
- 声音自动尝试解锁,等待最多 3 秒后继续画面;声音未能解锁时仍可通过后续点击触发。正常网页的点击启动流程保留。
- 壁纸版关闭 PWA 初始化、安装和更新界面。更新通过替换本地构建或未来的工坊更新完成。
- 宿主提供开场、音效、音乐、独立音量、减少动态效果和四档画质。宿主只推送变化的属性,页面保留其余设置。
- 页面设置也可使用;重载时宿主下发的值具有优先权,页面内更改不会反向写回 Wallpaper Engine 的属性面板。
- 三维渲染跟随宿主 FPS 上限(未收到时为 30),不会补渲染漏掉的帧。宿主暂停通知停止更新与音频;开场恢复时扣除暂停时间。
- 壁纸版不提供浏览器全屏按钮。画质参数以点击循环选项的按钮呈现,保留原有参数值和高级设置。
## 本机验证记录
环境:Windows、本机 Wallpaper Engine 窗口显示版本 2.8.36,独立窗口 1600×900,真实 CEF / WebGL 渲染。管理界面另提示 2.8.42 已下载、待重启,本次没有执行更新。
已验证:
- 自动开场、白底 Logo 与文字、中文字体、三维阵列本地加载。
- 鼠标点击切档(X-001 → X-006)、打开档案、磨砂揭示和正文解密。
- 360° 模型本地加载、分层拆解与鼠标拖动旋转。
- 初始宿主属性 `boot=false` 直接进入阵列,`musicvolume=15` 在页面显示 15%。
- 运行中经官方 `applyProperties` 指令将音乐音量改为 23%,收到 `{musicvolume:{value:23}}` 回调,已打开的页面控件同步至 23%。
- 画质按钮从“原始”切换为“高”,实际渲染从 1600×900 变为 2000×1125,并启用 SMAA。
- 实际音频状态为 `running`,三条音乐轨道已载入并播放,错误字段为空。声音主观听感仍由用户试听。
- 限帧、暂停恢复及部分属性回调通过脚本验证;新增回归检查确保壁纸不再生成原生 `<select>`。
- `npm run build:wallpaper` 与正常网页 `npm run build` 均通过,网页 PWA 构建成功。
运行数据有一次阵列采样为 22 FPS(宿主上限 30),不能把上限写成稳定实际帧率。本次未做长时间功耗测试、4K / 多屏压力测试或真实桌面图标遮挡下的完整输入验证。
![Wallpaper Engine 实际设置页,音量同步与画质切换](../reference/wallpaper-engine/settings.png)
### 本机 CEF 的下拉框崩溃
打开原设置页会稳定出现 `STATUS_ILLEGAL_INSTRUCTION`。诊断确认页面逻辑完成后、首次绘制前崩溃;最小弹窗和声音控件正常,单独画质区域可复现,去掉其中 `<select>` 后正常。最终只在壁纸模式将画质下拉框换为循环按钮,恢复原背景模糊、焦点处理和原生音量滑块后,全设置页正常。
此记录描述本机的复现与兼容处理,不断言所有 CEF 版本都存在此问题。没有修改 Wallpaper Engine 全局画质、硬件加速或安全设置。
### 当前交付边界
本地壁纸库和独立预览可用;自动审批拒绝了替换主显示器壁纸,需用户明确批准后才应用到桌面。本次未上传创意工坊。上传前还应完成桌面输入、长时间性能和发布素材的最终检查。
## 官方接口参考
- [创建与导入网页壁纸](https://docs.wallpaperengine.io/en/web/first/gettingstarted.html)
- [用户属性与暂停通知](https://docs.wallpaperengine.io/en/web/api/propertylistener.html)
- [FPS 限制](https://docs.wallpaperengine.io/en/web/performance/fps.html)
- [命令行预览与属性设置](https://help.wallpaperengine.io/en/functionality/cli.html)
## 桌面试用反馈与设置说明(2026-09-10)
用户已自行应用到桌面。设置顶部明确说明:每次启动读取 Wallpaper Engine 属性,页面内修改只对当前运行生效;长期设置须在宿主属性面板修改。画质区不再提示自动保存。
用户反馈桌面方向键与滚轮无效。代码检查确认两类监听仍保留,没有壁纸模式禁用分支;尚未通过桌面事件跟踪确认滚轮未传入的具体原因。设置操作说明改为拖动阵列、点击界面按钮,并注明桌面可能无法传入方向键和滚轮。Wallpaper Engine 开发者说明键盘输入存在宿主限制:https://steamcommunity.com/app/431960/discussions/2/1644304412654510366/ 。
本次 `npm run build:wallpaper` 与 `node scripts/check-wallpaper.mjs` 通过。
## 工作台首版
Wallpaper Engine 属性新增显示模式(桌面工作台/档案展示)、三条事项、重要日程名称和本地目标日期、专注与休息分钟数。页面设置可临时切换模式。空事项不会伪造数据。
工作台左侧始终显示本机时间、日期、事项。右侧五个功能入口与阵列五列联动。事项点击勾选或撤销;按本地日期自动重置。完成状态与计时存于独立 rhine-workbench-v1 项,宿主属性不覆盖它们。内容改动清除对应旧完成状态;存储不可用显示提示。该存储不承诺跨设备、换目录或卸载后保留。
专注与休息可开始、暂停、继续、重置、切换。截止时间采用 Date.now(),壁纸暂停、遮挡或重载后校正,不补响后台提醒,也不自动开始下一段。改变时长只影响下一次开始。媒体监听在经典启动脚本中提前注册,支持标题、歌手、封面、状态与可选进度;未启用、无曲目或无进度均有空状态。媒体来源取决于 Windows 媒体会话支持,模拟回调验证不代表所有播放器实测成功。
验证页:reference/workbench-review.html(开发 wallpaper 模式),包含仅用于测试的宿主属性与媒体回调。运行 node scripts/check-workbench.mjs 检查日期边界、暂停/重载计时和媒体切换残留。当前仍为本地实验,不合并正式网站。
2026-09-10 本次验证:壁纸与普通网页构建均通过;check-wallpaper 与 check-workbench 通过。在 Edge 实际页面验证设置切换、专注启动、事项勾选后重载保留、模拟媒体标题与进度、有效目标日期和无效日期提示,并检查桌面尺寸布局。媒体使用测试回调,未声称真实播放器联动已验证。本地安装目录已更新;本版尚待 Wallpaper Engine 桌面实际试听与存储复核。
2026-09-10 视觉修订:按用户要求撤回绿色主题,改用主页现有颜色,取消信息面板底块和导航色块,复用竖线刻度与黑白按钮状态。壁纸构建通过,并在 Edge 实际运行页面检查布局;已更新本地壁纸包。
## 工作台可配置显示(2026-09-10)
纯净效果是工作台的配置结果,不增加独立模式。在 WE 中选择“工作模式 → 桌面工作台”,按需切换“工作台:显示…”七个开关。全部关闭时仅保留三维阵列;设置按钮也可关闭,再从 WE 属性恢复。开关在启动时由宿主恢复,局部属性更新不会重置其他选项。壁纸内没有另一套临时显示开关。
工作台显示配置不影响档案展示。隐藏专注内容不停止正在运行的计时,也不清除事项完成状态。开场结束位置现在由下文的入场选项控制。
该阶段壁纸构建、check-wallpaper、check-workbench 通过。Edge 实际页面用宿主回调验证全部隐藏、只留时钟与外部恢复所有元素(包括设置入口),并检查纯净阵列截图。
## 音乐律动、波纹接力与入场(2026-09-10)
WE 属性使用六个原生可折叠 group:入场与画面、工作台界面显示、事项与日程、声音、音乐律动、波纹接力。工作模式始终在最上方。工作台分组仅在工作台模式显示;关闭声音、律动或游戏入口后隐藏对应音量、强度或节奏参数,保留已有值。使用本机 WE UI 实现中确认的 group 和 condition 字段,不伪造多层导航。
“入场时展示档案详情”提供随工作模式、展示详情、跳过详情三个选择。默认工作台在原片约 30.90 秒(内部时间 25.90)结束,阵列已进入并出现选择波浪,尚未进入完整抽取和特写;档案展示默认保留完整开场。工作台内容以 460ms 淡入和 9px 上移错峰显示。减少动态效果时直接显示。带 time 参数的逐帧对照仍保留完整原时间轴。
音乐律动只作用于工作台阵列;低、中、高频分别驱动宽波、小波与稀疏轻抬,保留原材质。默认开启、强度 100%,可设 0–200%。宿主没有信号或停止回调后缓慢回落。选中高度可设保持原样、仅音乐律动时归齐(默认)、始终归齐;归齐去掉选中隆起,不改变卡片朝向。减少动态效果时不播放频谱运动。默认在律动开启期间暂停壁纸自带配乐,避免内部配乐持续驱动频谱;此行为有独立开关,原配乐偏好不被改写。
波纹接力由左下角按钮开始,可在 WE 隐藏入口。点击抬起卡片或其编号得分,错误或超时结束,可重试或退出。三档节奏随得分渐快。游戏使用可见、射线可命中的真实卡片;暂时归齐阵列、淡出频谱、隐藏工作台文字并隔离浏览手势。退出恢复界面与律动。倒计时按实际经过时间运行,宿主暂停、后台或设置弹窗暂停计时,不因低帧率延长一轮。隐藏入口不会移除进行中游戏的退出按钮。
验证:check-playground 检查立体声频段、异常值、信号丢失回落、计分、重复点击、重试、超时、暂停、入场策略与六组属性条件;check-wallpaper、check-workbench 与两种构建继续运行。reference/playground-review.html 为不随安装包分发的宿主模拟面板,含三次接力后退出的集成验证。Edge 已观察真实模型抬起、三次得分后恢复、模拟音乐后归齐。系统实际音源与 WE 属性面板的最终显示仍需本机宿主复核,模拟频谱不代表真实音频已验证。
接口依据:[音频可视化](https://docs.wallpaperengine.io/en/web/audio/visualizer.html)、[分组](https://docs.wallpaperengine.io/en/scene/userproperties/overview.html)、[条件显示](https://docs.wallpaperengine.io/en/web/customization/displaycondition.html)。
## 登录入场适配与字幕移除
正常入场使用 openingLayout 铺满实际视口,中央二维动画保持原始局部坐标,背景和三维画布延展到屏幕边缘。窄屏缩小圆环的整体占用,竖屏提高验证文字字号;品牌文字保持边角锚定,小屏的进入按钮与页脚品牌分置左右。三维入场按实际宽高比修正镜头视野与目标投影,不移动档案来补构图。底部中文字幕节点和更新逻辑已移除。
显式 time / review 对照仍为 1920×1080 等比舞台,正常入场不再有固定比例留边。验证页 reference/opening-layout-review.html 提供 16:9、超宽屏、4:3、竖屏以及登录、圆环、欢迎、阵列节点。check-viewport 检查实际画布覆盖、中央布局边界、既有交互镜头和固定对照。浏览器尺寸模拟不代表手机实机性能数据。
最终检查:普通与壁纸构建通过,check-playground、check-wallpaper、check-workbench、check-viewport 通过。Edge 实际页面检查 960×540、1050×450、360×640 登录布局,以及 800×600 短入场结束后工作台可见、字幕节点不存在;小游戏入口隐藏后仍保留工作台。包已复制到本地 rhine-lab-local,安装入口哈希与构建一致,需宿主重新加载。保留为本地实验,不合并或上传。
## 明暗配色
在「入场与画面 → 界面配色」选择亮色或暗色;默认亮色。壁纸内部设置也可以即时试切,长期保存仍使用 WE 属性。配色覆盖工作台、档案详情、360° 查看器和开场。正常切换时卡片依次变色,减少动态效果时直接切换。重新载入壁纸后可见新增属性。
## 音乐无律动修复 · 2026-09-10
修正音频能力声明的位置:`supportsaudioprocessing` 必须放在 `general` 中,旧版误放在根层级。本机官方 Audiophile 示例和官方音频文档作为核对依据。构建脚本现在检查该路径,阻止错误配置发布;检查脚本同步修订,原有模拟回调测试不能验证宿主识别能力。
配置与壁纸构建通过,更新本机安装目录后需重新载入壁纸。真实系统音乐输入的恢复效果尚待宿主试用确认。律动目前作用于桌面工作台;需启用音乐律动并关闭减少动态效果。
参考:https://docs.wallpaperengine.io/en/web/audio/visualizer.html
## 呼吸开关与律动整理方案 · 2026-09-10
新增 WE「工作台 · 界面显示」中的「静止时呼吸」,默认开启。呼吸位移独立于选中档案归齐系数,因此「始终归齐」也可呼吸。关闭后平滑收回;开启后沿原来的 8 秒/13 秒缓慢周期恢复。音乐活动时淡出,安静后恢复,小游戏及减少动态效果下停止。
浏览器验证页增加独立开关与呼吸增益读数,方便组合检查。
当前音乐方案仍是低中高三组不同速度正弦波叠加,时间相位自行推进,音乐主要控制振幅,尚未进行节拍触发;持续人声和高频也会造成局部起伏。这是杂乱感的主要实现原因。
建议下一版采用「鼓点主导的宽波」:低频能量相对短时平均的上升沿触发,每次沿同一方向推进;设置约 180–240ms 防重复间隔、限制同时存在的波数,快速上升后缓慢回落。中频仅轻微调节整组振幅,高频不独立驱动位移。使用缓慢增益适配,限制峰值,安静时平滑退回呼吸。另一种更安静的方向是整组同步轻抬,几乎不做传播。此处记录方案,未替换现有音乐算法;合成输入检查不能代替真实歌曲试听。
## 三种律动并列试用 · 2026-09-10
用户比较后保留原版多层起伏为默认,并要求重做 A/B。当前 WE 属性提供原版、方案 A「左低中中右高频谱」、方案 B「连续层叠流波」,沿用原有存储值 legacy / wave / lift。切换连续混合,强度和独立呼吸开关共用。
A 按当前相机投影后的屏幕横向位置混合低/中/高频,避免斜视阵列的物理列号与屏幕左右不一致。相邻频段平滑衔接,持续音也会驱动起伏。B 使用低、中、高频驱动三层不同速度的连续流波,替代旧的整组节拍轻抬。原版保留原参数。两项新方案不再使用鼓点触发门槛;真实音乐观感待用户试用。
检查覆盖左右频段分布、持续中高频响应、频段边界、静音归零及快速切换。浏览器对照页 reference/workbench-revision.html 提供原版与 A/B、独立持续频段和混合输入。
## 阵列覆盖与性能
「入场与画面 → 阵列覆盖余量」提供「自动 · 标准余量」(默认)和「自动 · 额外余量」。两档都按当前镜头和屏幕范围计算数量,额外档多保留屏幕外余量,渲染开销较高。正常使用无需填写档案数量;切换屏幕比例和进入详情时自动重新计算。
屏幕外不再整池提交。仍保留可能进入视口或影响附近阴影的卡片;这不是对所有互相遮挡的表面做深度遮挡裁剪。相同比例、相同构图从 1080p 改到 4K,不会仅因像素增加而生成更多档案。更宽的视野会需要更多档案,不能把旧版缺失边角时的较低数量当作同等覆盖下的性能基准。
暗色远景灰雾与边缘淡出一起加强。验证与当前限制见 verification/ARRAY-COVERAGE.md。
## 工作台细节与开场衔接修订
移除「播放器未提供进度」和时区说明;有有效进度时继续显示时间线。暗色选中刻度使用暖白,REINITIALIZE 悬停使用琥珀色。接力退出按钮位于计时线下方,与设置入口分开。
工作台开场隐藏选档标题与编号;Powered 在开场末段 350ms 淡出。工作台与档案展示互切时,旧界面约 220ms 退场,输入立即隔离,完成后隐藏;快速返回衔接当前透明度。减少动态效果时直接显示或隐藏。
普通开场也按实际镜头视锥动态生成档案,覆盖超宽及竖屏;仅显式原片逐帧对照保留 160 个位置。详见 verification/WORKBENCH-REVISION.md。
本地媒体封面修复:文字、封面、进度按独立事件保留,不再因文字更新误清除封面。回归步骤与官方接口依据见 verification/WORKBENCH-REVISION.md。
游戏进出过渡:游戏信息 300ms 淡入并轻移,220ms 淡出;工作台与品牌、页脚反向渐变。入口与目标标记分别淡入淡出,退出完成后恢复操作与焦点。隐藏游戏入口和减少动态效果均继续生效。
## 可选 HUD 与画面质感
WE 属性中的「HUD 与画面质感」提供 UI 曲面视差、全局屏幕效果与界面磨砂底,默认全部关闭。
- 「曲面纵深」决定四周 UI 的展开程度。「鼠标追踪」独立控制跟随;关闭后仍保留静态曲面。三维背景不跟随被动鼠标移动。
- 「全局屏幕效果」覆盖三维背景、文字、按钮、开场与弹窗。颗粒、颗粒尺寸、色散、暗角分别调节;可先单项从 0 调高来比较效果。
- 磨砂底使用主题颜色并随文字和按钮投影。减少动态效果时停止鼠标跟随、冻结颗粒,静态形状仍保留。
实现、参考结构与验证见 verification/VISUAL-EFFECTS.md。
## 四边距与任务栏
WE「界面边距 · 正值内收 / 负值外扩」提供上、下、左、右四个独立滑杆,范围 −300~300px,默认 0。数值是在原布局上的额外偏移;正值留出更多空白,负值向屏幕边缘外扩。任务栏遮住底部时,可先把「下边距」调到 60,再按实际遮挡微调。四项设回 0 恢复原布局。
边距不依赖视差开关。工作台、档案 UI、固定游戏信息、弹窗与查看器控制区适配这些偏移,字体大小和三维画布不因边距缩放,开场保持原构图。详见 verification/UI-INSETS.md。
2026-09-10 修订:曲面纵深、细颗粒、颗粒尺寸、边缘色散、暗角的默认数值统一为 20。下边距不再缩短桌面工作台的中部定位容器;底部导航和页脚先上移,实际接近中部内容时才推动对应内容,上方品牌限制继续上移,空间不足时内容独立滚动。手机窄屏继续采用原有纵向滚动布局。
WE 新增「工作台 · 可用功能」,时间日期、今日事项、重要日程、正在播放、专注计时各自可开关,默认全开。关闭功能同时移除其右侧内容入口和底部按钮;当前项关闭时选择首个仍开启功能,全关时隐藏右侧内容和导航。左侧时钟与事项仍由原显示开关控制,任务和计时状态保留。用户明确要求自行验证,本轮仅完成实现和构建打包,未进行运行时与视觉验收。
2026-09-10 后续修正(覆盖上述中部避让方案):用户明确下边距只移动底部两排,排间距固定,中部组件不压缩、不滚动。已删除 workbench-insets 自动避让;工作台高度不再随下边距变化。底部导航、游戏入口与页脚在曲面投影之后使用相同 translate 偏移。另修复 :is 内 ID 提高优先级导致原规则覆盖,以及减少动态效果的隐式 all 过渡污染曲面测量的问题。Edge 实际页面完成 1920×1080、2560×1440、1600×900,曲面 0/20/80,下边距 60/192/300/−80/0,共 45 组检查:中部坐标及尺寸固定,无新增 overflow,底部各行位移一致。画面对照与数据见 verification/ui-insets/。
WE「入场与画面」新增暗色专用开关「暗色模式仅选中档案标签使用强调色」,默认关闭保留现有选择。开启后阵列与归位档案的顶边标签使用暗色,当前选中档案保留琥珀强调;浅色不受影响。通过既有材质 uniform 实现,无新增网格与绘制通道,暗色开/关及浅色截图已检查。
## 超级性能模式与自定义画质
用户于 2026-09-10 要求低配壁纸模式与减少动态效果独立,并恢复 WE 原生自定义画质。新增「超级性能模式(保留动态效果)」开关,默认关闭;开启时以独立的有效画质覆盖已保存画质,不改减少动态效果、宿主帧率或音乐律动设置,关闭恢复当前预设/自定义配置。
超级模式:三维比例 60%、DPR 上限 1、总像素不超过 921600;关闭阵列阴影、AO、景深和 SMAA,直接输出主场景;背景阵列使用无折射、无清漆材质并隐藏小螺丝,选中/详情模型保留完整结构和低分辨率折射。全局屏幕滤镜暂时停用,文字磨砂改为无模糊底色,HUD 曲面与追踪继续运行。DOM 文字不降低分辨率。
WE 画质下拉增加「自定义」,仅此选择且超级模式关闭时显示九项控制:渲染比例、像素密度、抗锯齿、阴影、AO 采样、AO 分辨率、景深、透明材质分辨率、纹理过滤。连续参数用滑杆,离散参数用原生下拉。部分宿主回调保留其他参数;超级模式开启期间保留自定义更新,关闭恢复最新配置。完整证据见 verification/SUPER-PERFORMANCE.md。
## 暂停三维渲染与自定义图片
点击左下 SESSION 右侧「3D 开启」:档案依次下落,完成后释放主场景及查看器资源,保留 2D 工作台。点击「3D 关闭」重新加载,档案从下方升起;退场途中再次点击会恢复。游戏入口在退场、关闭、载入期间隐藏,成功开启后按原偏好恢复。
在 WE「入场与画面」启用「关闭 3D 后显示自定义壁纸」,再选择图片,可在释放完成后淡入该图;重新加载成功后图片淡出。默认不开启,未选图则继续使用原背景。图片不会成为默认资产或写入项目。验证与限制见 verification/THREE-RELEASE.md。
本轮保留原版、连续频谱和层叠流波三种音乐律动;同时修复无音乐拖动后的错误归齐、全归齐时标签强调与暗色选中强调过渡,见 verification/SELECTION-STATE.md。
## 创意工坊发布准备
- 用户于 2026-09-10 曾授权发布,随后明确改为自行操作发布,代理负责 GIF 和操作说明;当前尚未上传。
- 正式名称为 Rhine Lab · 莱茵生命交互桌面,去掉“本地测试”。独立正式工程放在 Wallpaper Engine 的 myprojects/rhine-lab-workshop,保留之前本地试用工程。
- 预览参照用户指定的 3088099655 预览的全景/局部剪辑方法,素材全部来自本工程工作台实际运行画面。最终 GIF 256×256、13.2 秒,含时钟事项、专注与明暗切换;没有将参考壁纸的图片放入发行包。
- 发布操作和后续更新须保留 workshopid,见 docs/WORKSHOP-PUBLISH.md;预览来源与验证见 verification/WORKSHOP-PREVIEW.md。
## 仅加载 2D 启动与图片路径修复
WE「入场与画面」新增「启动时加载 3D(下次加载生效)」,默认开启。关闭后不创建 WebGL 场景或下载模型,2D 开场到原片 26.9 秒处直接进入组件页面;左下 3D 开关可手动加载。改变属性不会卸载当前场景。自定义图片在开场结束后出现。
自定义图片路径修复了宿主将盘符编码为 E%3A 后再次编码导致的读取失败;支持原始路径、编码路径与 file URL。同一路径读取失败后重新选择可重试。真实 WE 宿主验证提取的 wallpaper-2.jpg 为 3200×2000 并成功解码。
验证:scripts/check-startup-2d.mjs 检查初始无 canvas/GLB 请求、2D 开场结束、手动载入、属性仅下次启动生效、默认开启与无页面异常;scripts/check-wallpaper-image.mjs 验证路径与同路径重试;scripts/check-wallpaper-image-host.mjs 验证真实宿主回调。结果位于 verification/startup-2d 和 verification/wallpaper-image。
### 宿主属性到达时序修复
启动必须等待 WE 首次 applyUserProperties 回调后再判断是否创建三维场景,不能将尚未收到的属性当作默认开启。真实 file 壁纸没有超时后强行加载的回退;普通 HTTP 预览无宿主时继续正常启动。此前测试只覆盖提前注入属性,现补充 1800ms 延迟回调;真实 WE 独立窗口验证关闭属性后进入 archive,canvas 与 GLB 请求均为 0(scripts/check-startup-2d-host.mjs,verification/startup-2d/host-results.json)。
## 自定义图片遮罩范围
WE 自定义壁纸图片下方新增「上下遮罩范围(0 为关闭)」:0–100,默认 100 保留原渐变范围;降低数值缩小上下覆盖,0 完全隐藏图片上的 atmosphere。仅图片实际显示时生效。亮暗配色 100/50/0 的样式断言及截图见 scripts/check-wallpaper-mask.mjs 与 verification/wallpaper-mask;构建通过。
## 时钟与媒体滚动、图片背景按钮可读性
工作台时钟、页脚秒钟、媒体已播放/总时长、切歌标题与艺人、专注计时复用现有 createRollingText,460ms、direct、同时起动与轻微模糊;格式化时间按字符滚动,冒号保持。首次显示直接初始化,相同值不重复播放,减少动态效果立即完成。媒体面板原位更新,保留滚动节点及未变化封面,避免宿主高频进度回调打断。
自定义图片显示时,页脚、底部导航、待办事项与提示、媒体及其他按钮改用主题前景色,关闭 3D 状态不降低文字透明度。用户否决额外白底,已撤回该底色与外扩阴影,仅保留原有玻璃模糊。无遮罩图片背景实看与时钟、切歌、进度、专注、减少动态效果检查见 scripts/check-workbench-rolling.mjs 和 verification/workbench-rolling。
- 页脚 HH:mm:ss 进一步改为三组 createRollingNumber,冒号固定、两位补零、460ms 向上滚动。验证捕获实际运行中的动画,并检查 12:59:59→13:00:00 进位;减少动态效果时直接到位。此前此处采用整段 createRollingText,现已替换。
### 曲面下 8、9 与歌曲文字滚动中断
复现:启用 HUD 曲面后,秒数 7→8、8→9 的动画开始约 120ms 时已被清空,其他数字仍在运动。Rolling Number 0.4.1 用透视后的边界反推字形尺寸,与 ResizeObserver 的本地尺寸产生误差,触发 refresh 并直接结束动画。
scripts/patch-rolling-number.mjs 将字形宽高改为本地 computed style 尺寸,保留原滚动算法;安装、开发及构建自动应用,依赖升级后匹配失败会明确报错。验证开启曲面后的全部 0–9、9→10、小时进位以及五组不同字宽/长度歌曲名,120ms 时均有运行中的动画。证据见 verification/workbench-rolling/results.json。
- 播放器长标题与艺人名单行显示,按可用宽度截断为省略号后交给滚动库;悬停与辅助标签保留完整文字,视口变化重新计算。验证包含超长标题省略号与完整提示。
### 开场 HUD
开启「HUD 与画面质感」中的曲面效果后,前段开场也应用相同曲面纵深与鼠标追踪。关闭追踪保留静态曲面,关闭 HUD 恢复原开场;减少动态效果时停止追踪。无需新增开关。原始开场位移、缩放与笔画时间轴继续运行,进入工作台时延续 HUD 状态。
+46
View File
@@ -0,0 +1,46 @@
【更新日志|最新在前】
2026.09.11
• 修正开场 Logo 下方 RHINE·LAB 小字的重复重绘问题,改善开启 HUD 时的文字稳定性。
• HUD 曲面与鼠标追踪扩展到开场:Logo、验证文字、圆环与欢迎画面沿用同一效果,并与工作台连续衔接。
• 修复部分数字(如 8、9)和切歌文字的滚动动画中断;长歌名、歌手名自动省略,悬停可查看完整内容。
• 页脚时钟改为时、分、秒独立滚动,支持连续进位。
• 工作台时钟、专注倒计时、播放器时间及切歌文字加入滚动过渡;减少动态效果时直接显示。
• 改善自定义图片背景下底部按钮、导航、待办和提示文字的对比度,保留原有玻璃背景。
2026.09.10
• 自定义图片新增上下遮罩范围调节,设为 0 可完全关闭遮罩。
• 新增「启动时加载 3D」开关:关闭后只播放前段 2D 入场,之后可手动加载模型;修复首次读取设置时仍加载 3D 的问题。
• 修复本地图片路径识别与载入重试;支持关闭 3D 后显示自定义壁纸。
• 左下角新增 3D 开关:档案依次退场后释放三维资源,再次开启重新载入并升起。
• 新增超级性能模式,保留动态效果;补全自定义画质控制,关闭性能模式后恢复之前的画质。
• 新增四边独立界面边距,支持向内或向外调整;修正下边距,让底部两排一起移动,中部组件保持位置。
• 暗色模式新增「仅选中档案标签使用强调色」选项,并修正归齐状态下的标签表现。
• 新增 HUD 曲面、独立鼠标追踪、局部界面磨砂,以及覆盖整个画面的颗粒、色散与暗角;各项可独立调整。
• 阵列根据镜头和屏幕范围自动补位,裁剪屏幕外档案,改善宽屏边角露出阵列尽头的问题;加强暗色边缘雾气。
• 新增亮色/暗色配色及逐张变色过渡,覆盖工作台、档案详情与独立查看器。
• 提供原版、连续频谱、层叠流波三种音乐律动;静止呼吸可独立开关。修复壁纸自身音效触发音乐归齐的问题。
• 新增波纹接力小游戏,完善游戏与工作台的进出过渡。
• 新增桌面工作台:时间日期、今日事项、重要日程倒计时、系统媒体信息和专注计时;支持按需隐藏内容与关闭功能入口。
• 修复切歌时封面、歌曲文字和进度相互覆盖的问题,完善工作台入场、退场与操作衔接。
• 开场适配不同屏幕比例,移除底部字幕,并提供完整/简短入场选择。
• 完成 Wallpaper Engine 适配:本地资源打包、原生属性分组、音频响应、媒体信息、宿主帧率与暂停通知。
────────────────────
【Rhine Lab · 莱茵生命交互桌面】
以莱茵生命档案终端为灵感的非官方同人交互壁纸。
【桌面工作台】
时间与日期、今日事项、重要日程倒计时、系统媒体信息、专注计时,可按需显示或隐藏。
【三维档案】
可拖动的循环玻璃档案阵列、惯性滑动、档案详情与 360° 模型拆解查看。
【画面与交互】
亮色/暗色配色、三种音乐律动、呼吸效果、HUD 曲面与鼠标追踪、波纹接力小游戏。支持自定义画质与超级性能模式;可关闭 3D 并显示自己的图片背景。
【使用】
在 Wallpaper Engine 属性中切换模式、填写事项与日程、调整画面。拖动阵列浏览,点击档案读取详情;音乐律动需要启用音频响应。媒体信息由系统与播放器提供。
预览主要展示工作台模式,事项为演示内容;实际事项由你填写。资源本地运行。角色、名称与相关世界观归原权利方所有,本作品与官方无关联。
+54
View File
@@ -0,0 +1,54 @@
# 手动发布到 Wallpaper Engine 创意工坊
正式名称:**Rhine Lab · 莱茵生命交互桌面**。
正式本地工程已关联创意工坊条目 `3799142774`,后续使用「发布更新」。
工程目录:
```text
D:\Game\Steam\steamapps\common\wallpaper_engine\projects\myprojects\rhine-lab-workshop
```
## 第一次发布
1. 确保 Steam 在线,打开 Wallpaper Engine。如果已经打开过,重新打开壁纸选择界面,让它刷新本地工程。
2. 在「已安装」里搜索 `Rhine Lab`,选择 **Rhine Lab · 莱茵生命交互桌面**。另一个带「本地测试」的条目是之前的试用工程,本次使用新名称的条目。
3. 点击右侧详情里的 **在编辑器中打开**。
4. 在编辑器顶部打开 **创意工坊** 菜单,选择分享/发布壁纸的选项,进入 **发布到创意工坊** 表单。不同版本的菜单可能称为「准备发布」或「在创意工坊上分享壁纸」。这是发布完整壁纸,不是发布组件或属性预设。[官方网页壁纸发布流程](https://docs.wallpaperengine.io/en/web/first/gettingstarted.html)
5. 核对标题为 **Rhine Lab · 莱茵生命交互桌面**。描述已写入工程;如果界面未带出,可从本文末尾复制。
6. 设置 **可见性:公开**、**年龄分级:大众级(Everyone)**;样式可选 **科技(Technology)**。
7. 在 **预览图片 → 导入文件** 中选择下面的 GIF。即使已经出现封面,也可以重新导入这一版。
```text
D:\Game\Steam\steamapps\common\wallpaper_engine\projects\myprojects\rhine-lab-workshop\preview.gif
```
8. 第一次上传选择 **创建新项目**,点击 **发布**,等待提示上传成功。
9. 点击 **在创意工坊中显示壁纸**,检查正式名称、动画封面和可见性。如果 Steam 要求接受创意工坊协议,由你在 Steam 页面完成。若页面提示审核中,等待审核即可,不要重复创建条目。
10. 保存工坊页面链接,并备份整个 `rhine-lab-workshop` 工程目录,尤其是发布后更新的 `project.json`。
## GIF 说明
工坊封面为 256×256、13.2 秒、约 940 KB,包含工作台全景、时钟与事项特写、专注计时特写、暗色全景与暗色特写,再回到亮色全景。它从真实浏览器画面剪辑并裁切放大,使用演示事项及演示频谱;演示事项没有写入壁纸的默认值。
GIF 是无声的。预览封面不能完整表现所有细节,订阅后的壁纸按实际屏幕尺寸渲染。官方规定 GIF 预览不超过 256×256、文件不超过 1 MB;当前文件符合该限制。[官方预览说明](https://docs.wallpaperengine.io/en/scene/first/publishing.html#animated-preview-images)
## 以后如何更新
可以持续更新同一个工坊条目,原订阅者会收到更新,无需重新订阅。[官方更新说明](https://docs.wallpaperengine.io/en/scene/first/publishing.html#publishing-updates)
1. 更新这个原工程的壁纸资源,保留 `project.json` 中的 **workshopid**,以及原发布关联。
2. 从同一条目选择 **在编辑器中打开**,再次进入发布窗口。
3. 「上传为」应为 **更新现有项目**,按钮应显示 **发布更新**;填写本次更新说明并发布。
4. 如果变成「创建新项目」,先检查是否打开了其他工程或丢失工坊 ID,不要直接发布一个重复条目。
源码仓库的 `npm run build:wallpaper` 只生成构建包,不会自动读取 Steam 刚生成的工坊 ID。首次发布后,不要用尚未包含该 ID 的构建版 `project.json` 直接覆盖已发布工程的同名文件。后续交给代理更新时,提供这个工程目录或工坊链接,让代理先保留发布关联再同步资源。
工坊上传不等于源码备份。保留源码仓库和发布工程,两者都有用途。
## 可复制的发布描述
完整简介保存在 [WORKSHOP-DESCRIPTION.txt](WORKSHOP-DESCRIPTION.txt),并同步到正式工程的 description。更新日志放在最前面,按日期从新到旧,同一天按更新顺序从新到旧排列,后接功能介绍。日期依据开发记录整理,不代表当天每一项都单独发布过工坊版本。以后完成更新时,在简介顶部追加面向用户的变化。
若发布窗口未自动带出新简介,将该文本文件的完整内容粘贴到描述栏,再发布更新。
+61
View File
@@ -0,0 +1,61 @@
# README 演示素材
2026-09-09 从当前应用重新采集,运行版本为 `e0cb75ffcc1bf7d1fb5e2458b3c35fa0f6f336da`。使用独立的 Chrome 浏览器上下文、GPU 硬件加速与默认「原始」画质,不读取日常浏览器的收藏和偏好。素材仅用于文档展示,不参与应用运行。
## 静态截图
全部截图为 1600 × 900 JPEG,质量参数 93。保留真实界面与模型,没有叠加宣传文字或替换背景。
| 文件 | 展示内容 |
| --- | --- |
| `archive.jpg` | 新版循环阵列与档案导航 |
| `detail.jpg` | 解密完成后的双环内构与档案概述 |
| `research.jpg` | 研究记录页签 |
| `viewer-clear.jpg` | 独立查看器,清晰玻璃 |
| `viewer-frosted.jpg` | 相同视角下的磨砂玻璃 |
| `assembly.jpg` | 六组结构拆解 |
| `search.jpg` | 搜索「莱茵」后的档案索引 |
| `settings.jpg` | 音效、音乐、减少动态效果与画质设置 |
| `boot.jpg` | 白底开场中的 Logo 与身份接入 |
## 动图
通过 Chrome DevTools Protocol 录制运行页面,按捕获时间戳保留真实节奏,再用 FFmpeg 缩放、降帧和量化。GIF 使用 96 色调色板与 Bayer 抖动,无声音,不代表应用实时帧率。浏览器仅在合成帧变化时输出录制帧,静止画面依照原时间保持。
| 文件 | 内容 | 大约时长 | 尺寸 / 帧率 |
| --- | --- | --- | --- |
| `decryption.gif` | 抽取、对角解密线、玻璃与正文同步揭示 | 7.9 秒 | 960 × 540 / 12 fps |
| `glass-motion.gif` | 清晰 → 磨砂 → 清晰 | 4.2 秒 | 800 × 450 / 12 fps |
| `assembly-motion.gif` | 拆解、旋转、视角复位与重组 | 8.0 秒 | 800 × 450 / 12 fps |
| `browse.gif` | 连续选档、切列和文字滚动 | 6.8 秒 | 640 × 360 / 8 fps |
| `boot-motion.gif` | 白底输入、Logo、权限扫描与欢迎转场 | 20.4 秒 | 800 × 450 / 12 fps |
README 直接展示解密动图,其余动图放在可展开区域。采集时间、源码版本与原始帧间隔记录见 [`capture.json`](capture.json);GIF 编码的帧时长取整可能使成片时长略有差异。
## 重新采集
采集脚本为 [`scripts/capture-readme.mjs`](../../scripts/capture-readme.mjs)。需要 Chrome、Playwright 与 FFmpeg;它们是文档制作工具,普通应用运行不需要安装。脚本会覆盖本目录同名素材,并将中间 JPEG 帧保存在被 Git 忽略的 `.tools/readme-capture/`。
先启动应用:
```sh
npm ci
npm run dev -- --port 5186
```
再从仓库根目录执行脚本。已能通过 Node 导入 Playwright、且 FFmpeg 在 PATH 中时:
```sh
node scripts/capture-readme.mjs
```
也可以指定现有工具的位置,例如 PowerShell:
```powershell
$env:PLAYWRIGHT_MODULE = 'C:/tools/node_modules/playwright/index.mjs'
$env:FFMPEG = 'C:/tools/ffmpeg.exe'
$env:CAPTURE_URL = 'http://127.0.0.1:5186'
node scripts/capture-readme.mjs
```
其中工具路径需要替换为本机的实际位置。Windows 录制显式使用 D3D11;其他平台使用 Chrome 可用的 GPU 后端。录制后应查看截图、GIF 的起止帧及中间过渡,确认资源加载完整、没有遮挡或异常,检查 README 图片链接,并更新此处的版本与时长。
Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 MiB

+65
View File
@@ -0,0 +1,65 @@
{
"commit": "e0cb75ffcc1bf7d1fb5e2458b3c35fa0f6f336da",
"capturedAt": "2026-09-09T04:21:30.572Z",
"viewport": "1600x900",
"quality": "original",
"gifs": [
{
"name": "browse",
"frames": 126,
"duration": 6.623064994812012,
"width": 640,
"fps": 8,
"averageCaptureFps": 19.02442450718791,
"maximumFrameGap": 0.3434779644012451
},
{
"name": "decryption",
"frames": 200,
"duration": 7.776905059814453,
"width": 960,
"fps": 12,
"averageCaptureFps": 25.71717135052331,
"maximumFrameGap": 0.14150595664978027
},
{
"name": "glass-motion",
"frames": 114,
"duration": 4.078908920288086,
"width": 800,
"fps": 12,
"averageCaptureFps": 27.948650540583383,
"maximumFrameGap": 0.06445097923278809
},
{
"name": "assembly-motion",
"frames": 211,
"duration": 7.880533933639526,
"width": 800,
"fps": 12,
"averageCaptureFps": 26.77483553484964,
"maximumFrameGap": 0.09002685546875
},
{
"name": "boot-motion",
"frames": 158,
"duration": 20.333863019943237,
"width": 800,
"fps": 12,
"averageCaptureFps": 7.7702893859880575,
"maximumFrameGap": 1.0142180919647217
}
],
"screenshots": [
"archive",
"detail",
"research",
"viewer-clear",
"viewer-frosted",
"assembly",
"search",
"settings",
"boot"
],
"errors": []
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

+24
View File
@@ -0,0 +1,24 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<meta name="theme-color" content="#e8e5e1" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
<meta name="apple-mobile-web-app-title" content="Rhine Lab" />
<link rel="manifest" href="/manifest.webmanifest" />
<link rel="apple-touch-icon" sizes="180x180" href="/icons/apple-touch-icon.png" />
<meta
name="description"
content="Rhine Lab — Synthesize Information Analysis OS. 交互式三维研究档案终端。"
/>
<title>RHINE LAB · ANALYSIS OS</title>
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
</head>
<body>
<main id="viewport"><div id="stage"></div></main>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
+1
View File
@@ -0,0 +1 @@
32144
+1292
View File
File diff suppressed because it is too large Load Diff
+35
View File
@@ -0,0 +1,35 @@
{
"name": "rhine-lab-analysis-os",
"version": "1.0.0",
"license": "MIT",
"private": true,
"type": "module",
"scripts": {
"postinstall": "node scripts/patch-rolling-number.mjs",
"predev": "node scripts/patch-rolling-number.mjs && node scripts/archive-source.mjs",
"dev": "vite --host 127.0.0.1",
"dev:archive": "node scripts/archive-dev.mjs",
"prebuild": "node scripts/patch-rolling-number.mjs && node scripts/archive-source.mjs && node scripts/prepare-webfonts.mjs",
"check:content": "node --experimental-strip-types --test scripts/check-content.mjs",
"check:viewport": "node --experimental-strip-types scripts/check-viewport.mjs",
"review:mobile": "node scripts/serve-mobile-review.mjs",
"export:archives": "node scripts/export-records.mjs",
"archive:snapshot": "node scripts/archive-source.mjs",
"archive:serve": "node scripts/archive-service.mjs",
"build": "tsc && vite build && node scripts/build-pwa.mjs",
"build:wallpaper": "node scripts/patch-rolling-number.mjs && node scripts/export-records.mjs && tsc && vite build --mode wallpaper --outDir release/wallpaper && node scripts/build-wallpaper.mjs",
"format": "prettier --write src scripts index.html package.json tsconfig.json",
"preview": "vite preview --host 127.0.0.1"
},
"dependencies": {
"@kitlangton/rolling-number": "0.4.1",
"three": "^0.183.0"
},
"devDependencies": {
"@types/three": "^0.183.0",
"prettier": "^3.9.6",
"typescript": "^5.9.3",
"vite": "^7.3.1"
}
}
Binary file not shown.
Binary file not shown.
+30
View File
@@ -0,0 +1,30 @@
Opening lettering / 开场文字
Novecento Sans Wide, designed by Jan Tonellato / Synthview Type Design.
https://typography.synthview.com/novecento-sans-font-family.php
Normal, DemiBold and Bold were used to draw the fixed English phrases in the
opening and the fixed RHINE LAB corner heading.
The source repository contains pre-authored phrase graphics. A separately
licensed local build may include the official MyFonts webfont kit and render
these phrases using its original WOFF2 files. The kit is excluded from Git.
Original font copyrights and ownership remain with Jan Tonellato / Synthview.
The project's MIT license does not relicense the underlying typeface.
Normal and DemiBold source: author's DaFont package, free desktop license.
https://www.dafont.com/novecento-sans.font
https://typography.synthview.com/pdf/eula-free-font-dafont.pdf
Bold source: user's existing MyFonts desktop package.
Both source licenses permit characters to be imported as graphical objects
(letters outlined as graphics). The desktop font files must not be redistributed
or embedded in this project. Changing the authored phrases requires separately
obtaining the font and rerunning scripts/make-boot-lettering.py locally.
Optional official webfont kit:
MyFonts / Monotype Font Software For Web Content EULA (v260616).
https://www.myfonts.com/pages/license-agreement?id=eula_2267&eula_lang=eula_en
The fonts are individually licensed to the website owner. Other users must
obtain their own license; this project's MIT license does not cover them.
Retain the original kit's RhineLabNovecento.css copyright and license notice.
Webfont rights are limited by the owner's license term, domain and pageviews.
+12
View File
@@ -0,0 +1,12 @@
# Observatory / 观测室
三个 Ogg 文件为本项目原创程序编配的同步循环声部,MP3 是带首尾淡变的独立试听版。配乐无原片采样、无外部录音、无人声。
源谱及合成:`scripts/render-audio.mjs`;音效合成:`src/audio.ts`。
生成数据:`score.json`。随项目采用仓库 LICENSE。
## 逐字输入短音
`typing-preview.wav` 及 `src/typing-samples.ts` 使用用户提供的《明日方舟》特别映像 [莱茵生命:访问] 中约 6.864–6.986 秒的三个短音,各 38ms。`typing-source.json` 保留精确时间、源文件校验与处理参数。原音及其衍生片段的权利归原作者,不纳入上文原创配乐的 MIT 授权声明。
提取脚本:`scripts/extract-typing-audio.mjs`。原片短音仅去直流、做边缘淡变和统一增益,没有变调、变速或合成替换。`reference/typing-original.wav` 为本地对照片段,不属于生产配乐。
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+24
View File
@@ -0,0 +1,24 @@
{
"title": "Observatory / 观测室",
"bpm": 72,
"duration": 53.333333333333336,
"rate": 48000,
"seed": 93271,
"metrics": {
"atmosphere": {
"peakDb": -10.334112082610385,
"rmsDb": -24.625500342815418,
"seamDelta": 0.0005690408870577812
},
"motif": {
"peakDb": -12.383920130269303,
"rmsDb": -25.642892727244533,
"seamDelta": 0.0004476997419260442
},
"pulse": {
"peakDb": -19.061942349249723,
"rmsDb": -35.03524633636361,
"seamDelta": 0
}
}
}
Binary file not shown.
+29
View File
@@ -0,0 +1,29 @@
{
"source": "《明日方舟》特别映像 [莱茵生命:访问] [BV1rr4y1b7sz].mp4",
"sourceSha256": "a009a9501ecac5ff93103933fc4009c05e82225e6da8f85dee1a77013286c5a9",
"sampleRate": 48000,
"channels": 1,
"cuts": [
{
"start": 6.864,
"end": 6.902
},
{
"start": 6.906,
"end": 6.944
},
{
"start": 6.948,
"end": 6.986
}
],
"processing": {
"dcRemoval": true,
"fadeInMs": 0.35,
"fadeOutMs": 3.5,
"sharedGain": 0.6230600941776551,
"denoising": false,
"pitchChange": false
},
"scope": "Three short excerpts from user-provided reference. Not original synthesis; source rights remain with the original creators."
}
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><rect width="100" height="100" fill="#e8e5e1"/><path d="M50 50C35 25 9 29 9 50s26 25 41 0 41-25 41 0-26 25-41 0" fill="none" stroke="#111" stroke-width="10"/><path d="M21 50h17m-8-8v16m32-8h17" stroke="#111" stroke-width="5"/></svg>

After

Width:  |  Height:  |  Size: 295 B

Binary file not shown.
+14
View File
@@ -0,0 +1,14 @@
MiSans by Xiaomi / 小米科技有限责任公司
Copyright © 2020-2025 Beijing Xiaomi Mobile Software Co.,Ltd. All Rights Reserved.
Official source: https://hyperos.mi.com/font/download
Archive: https://hyperos.mi.com/font-download/MiSans.zip
The application now uses third-party split webfonts from [email protected]
(font Version 4.003), distributed with this application for on-demand loading.
Font shards are copied byte-for-byte from that package. CSS family names,
weight mappings and URLs are adapted for the existing interface.
Upstream copyright: Copyright © 2020-2023 Beijing Xiaomi Mobile Software Co.,Ltd. All Rights Reserved.
Upstream project: https://github.com/mobeicanyue/misans-webfont
Package source, integrity and file hashes: misans-webfont-4.3.1/source.json
Upstream README: misans-webfont-4.3.1/UPSTREAM-README.md
Font license: MiSans-license.pdf (retained separately from upstream tooling license).
@@ -0,0 +1,83 @@
# Misans Webfont <br> Misans 网页字体
本仓库旨在对 Misans 字体进行分包,方便字体在网页中加载。仅供学习交流使用。
[![NPM Version](https://img.shields.io/npm/v/misans-webfont)](https://www.npmjs.com/package/misans-webfont)
[![JsDelivr Hits](https://data.jsdelivr.com/v1/package/npm/misans-webfont/badge?style=rounded)](https://www.jsdelivr.com/package/npm/misans-webfont)
本仓库使用 Github Action,利用[中文网字计划](https://chinese-font.netlify.app/)开发的[字体分包工具](https://github.com/KonghaYao/cn-font-split)对原字体分包,网页加载时只需获取所使用的文字所在的分包,大幅降低所需加载的大小,提升网页加载速度。
本仓库使用 Apache License 2.0 开源许可证,欢迎 PR, Star.
## 使用
直接下面提供的代码以 `<link>` 的形式添加到网页的 `<head>` 内即可:
```html
<head>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/misans-webfont/misans-style.css" />
<style>
body {
font-family: "MiSans Bold";
font-weight: bold;
}
</style>
</head>
```
你也可以只引用单一的字体:
```html
<head>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/misans-webfont/misans/misans-bold/result.min.css" />
<style>
body {
font-family: "MiSans Bold";
font-weight: bold;
}
</style>
</head>
```
具体可以引用的字体请查看:[fonts 分支](https://github.com/mobeicanyue/misans-webfont/tree/fonts),找到具体字体中的 `result.css`.
---
以下信息摘录自小米官网,使用字体请遵守下文的许可协议。
## MiSans 简介
![MiSans](https://github.com/mobeicanyue/misans-webfont/blob/main/misans.png?raw=true)
MiSans Global 是由小米主导,联合蒙纳字库、汉仪字库共同打造的全球语言字体定制项目。这是一个庞大的字体家族,涵盖 20 多种书写系统,支持 600 多种语言,字符数量超过 10 万个。作为 Xiaomi HyperOS 系统默认字体,我们以简约/清晰,人文/易读,统一的视觉风格为基本原则出发,构建多语言信息体验一致性,旨在帮助为 Xiaomi HyperOS 提供互联的通用体验。
## Misans 许可协议
> 本《MiSans 字体知识产权许可协议》(以下简称“协议”)是您与小米科技有限责任公司(以下简称“小米”或“许可方”)之间有关安装、使用 MiSans 字体(以下简称“MiSans”或“MiSans 字体”)的法律协议。您在使用 MiSans 的所有或任何部分前,应接受本协议中规定的所有条款和条件。安装、使用 MiSans 的行为表示您同意接受本协议所有条款的约束。否则,请不要安装或使用 MiSans,并应立即销毁和删除所有 MiSans 字体包。
>
> 根据本协议的条款和条件,许可方在此授予您一份不可转让的、非独占的、免版税的、可撤销的、全球性的版权许可,使您依照本协议约定使用 MiSans 字体,前提是符合下列条件:
> 1. 您应在软件中特别注明使用了 MiSans 字体。
> 2. 您不得对 MiSans 字体或其任何单独组件进行改编或二次开发。
> 3. 您不得单独将 MiSans 字体或其组件对外租赁、再许可、给予、出借或进一步分发字体软件或其任何副本以及重新分发或售卖。此限制不适用于您使用 MiSans 字体创作的任何其他作品。如您使用 MiSans 字体创作宣传素材、logo、应用 App 等,您有权分发或出售该作品。
## Misans FAQ
#### MiSans Global 需要付费吗?
不需要。MiSans Global 所有的字体都是供全球免费商用,您可以在任何平台、任何商业项目中使用所有字体。
#### 是否可以用作嵌入式字体?
可以。但您应在软件中特别注明使用了 MiSans 字体。
#### 我可以在软件中修改编辑、二次创作字体吗?
您可以自由调节字体的粗细、间距等。但您不得单独将 MiSans 字体或其组件进行外观上的更改。
#### 什么是可变字体?
可变字体是 OpenType 在 1.8 版规范中引入的扩展规范,简单说明就是一个字体文件同时支持多个字体形态,可以将多个字体文件合在单个字体文件中,通过定义字体内的变化来实现单轴或多轴设计空间。
#### 是否所有字体都支持可变?
MiSans L3 生僻字字库(GB18030-2022 级别 3)仅支持 Regular 字重。其他所有字体均支持可变。
#### MiSans Global 的制作分工
由小米主导,联合蒙纳、汉仪共同制作完成。
其中蒙纳负责泰语、老挝及印度的所有地区语言。
汉仪负责繁体中文、阿拉伯语、藏文、缅甸、高棉。
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More