把项目从「手写 dist/」改成「wallpapers/ 是唯一真相来源,dist/ 由 pnpm build 生成」,
并补上配套的类型、门禁与抓取器。一次提交落地整条管线,因为拆开会留下不能构建的中间态。
- src/:运行时与模拟器源码(TS,strict),编译到 build/ 再拷进各分发
- tools/:build / dev / check-{syntax,paths,dist},以及抓取器与回归门禁 tools/checks/
(.scratch/ 下那批一次性脚本移入 tools/checks/ 并入库为长期门禁)
- wallpapers/:七档壁纸的源数据 + README.md(id/音频/预设的完整规范)
- docs/adr/0005-0008:构建管线与分发拓扑、模拟器契约、自包含 sim、每骨架资源布局
- .gitignore:排除 .scratch/ 的参考资料副本(上游 spine 整仓克隆 ~1.2 GB、
抓取侦查数据 ~680 MB)与调试转储;这些是本地调查材料,补偿会让仓库无法克隆
- 归一化 .gitignore/CONTEXT.md 行尾(工作区 CRLF、索引 LF 造成的整文件假 diff)
同时修掉三档卡住构建的未完工壁纸:
- kv45 的 meta.json 里 id 还是抓取期场景名 scene_main,经 downloader promote 正名为 kv45
- shajin / zhigengniao_juheye 的 meta.json 误用了骨架描述文件(name/spine/animations/pages)、
且都缺 preset.template.json;现按规范重建:骨架沉到 spines/<名>/(spine-ts 按 atlas 所在
目录解析贴图页)、补上元数据与单骨架预设,并清掉 zhigengniao 骨架里指向作者机的绝对路径
- 顺带 promote 已在 sources.yml 里的 kv46(月升之前,与兽共舞)
pnpm check 五道门全绿:10 个分发 / 7 档壁纸 / 141 处引用自包含。
5.8 KiB
SpineWallpaper(网页壁纸作品)
一份已发布的 Wallpaper Engine 网页壁纸,以及围绕它的资源分层、构建与验证约定。这个上下文关心"一档壁纸由什么组成、怎么被打包成可上传的分发、怎么在本地调试"。
源数据规范
wallpapers/ 是唯一真相来源,dist/ 全部由它生成。那个目录里每个文件该放哪、叫什么、
构建会校验什么,见 wallpapers/README.md —— 目录结构、三层 meta.json
的字段、id 规则、音频规范、常见任务步骤、门禁清单都在那里。动 wallpapers/ 之前先读它。
回归验收套件在 tools/checks/(原来散在 .scratch/ 下,
但那里是问题与规格的存放处)。每个脚本的前置条件不同,逐条命令见那份 README。
Language
作品(Project):
一个 Wallpaper Engine 打包单元,即任一 dist/releases/<分发目录>/;project.json 是它的清单,index.html 是它的入口。
Avoid: 工程、站点、应用
分发(Release):
一次可上传的构建产物,落在 dist/releases/<目录名> 下,目录名是 ASCII 的 single-<游戏id>-<壁纸id> / collection-<游戏id> / collection-all(如 single-hsr-xilian、collection-hsr、collection-all)。每个分发都是自包含的:整个目录可以拷到别的机器直接上传,内部不出现任何绝对链接与跨目录相对链接。
Avoid: 包、构建、输出
合集(Collection):
把多档壁纸收进同一份上传的分发,用户用 WE 的 preset 下拉在它们之间切换。它与「单档」是并列的两种类型
(type: "single" | "collection"),只有收档范围不同:游戏合集(collection-<游戏id>)收一个游戏的壁纸,
全部合集(collection-all)收所有游戏的壁纸——「全部合集」也是合集,不是第三种类型(scope: "game" | "all")。
收档范围是意图,不是档数:一个游戏哪怕只有一档壁纸,它的合集仍是合集,只是"切换"这件事不再存在。
Avoid: 集合("set"的通用义,不是本术语)、聚合包、多预设版;也别说"单档 vs 游戏"——游戏是资源分层,不是分发类型
单档(Single):
只含一档壁纸的分发(single-<游戏id>-<壁纸id>)。它没有"切换预设"这回事,因此 project.json 里没有 preset 属性;
但音源切换与预设无关——一档壁纸照样可以带多首曲子,所以有 ≥2 首时 bgm 属性仍在。
Avoid: 单品、单独版
游戏(Game):
壁纸所出自的作品名(崩坏:星穹铁道),资源分层的第一级。目录名用稳定的游戏 id(hsr),显示名放在该目录的 meta.json 里。
Avoid: 来源、IP
壁纸(Wallpaper):
用户可在 WE 属性里切换的一档壁纸(昔涟立绘、「成为昨日的明天」)。目录名用稳定的壁纸 id(xilian、kv37),显示名放在该目录的 meta.json 里。
Avoid: 预设、选项、preset(当指"这一档"本身时)
预设(Preset):
一档壁纸的配置实体,落盘为该壁纸目录下的 preset.js,声明这一档要用哪些资源、以什么参数播放。一档壁纸对应且仅对应一个预设。源数据是 preset.template.json,preset.js 由构建生成。
Avoid: 配置、profile、manifest
预设 id / 壁纸 id:
壁纸的稳定标识(xilian、kv37),必须与 WE 属性值一致,永不随显示名变化——已发布作品的用户设置靠它维系。它同时是目录名与下拉取值,因此全局唯一、且不加前缀(构建会校验并给出明确报错,而不是自动改名绕过)。
Avoid: slug、键名、代号
共享音频(Shared audio):
放在合集根 audios/ 下的音源。合集分发的 bgm 要能选到分发内任一壁纸的音源,所以音频统一集中到分发根,按 <壁纸id>/ 分目录存放;游戏级共享音频(wallpapers/<游戏id>/audios/)落在合集根、不进任何壁纸子目录。单档分发里没有共享音频这回事。
Avoid: 公共音频、全局音源
音源(AudioSource):
一首可作背景音乐的曲子。每首曲子归属且只归属一档壁纸;WE 的音源列表是所有壁纸音源声明的并集。
文件名保留中文,meta.json 只声明 name(显示名)与 file;id 由构建按固定顺序自增分配
(全项目唯一——bgm 下拉会把所有壁纸的音源平铺进同一个列表)。choices 的顺序即默认。
Avoid: BGM、曲目、音频文件
模拟器(Simulator):
pnpm dev 注入页面的一层"Wallpaper Engine 官方 API 替身",让壁纸在不启动 WE 的情况下跑起来调试。它严格还原 WE 的真实 API 面:不自我检测、不提供 WE 没有的全局、把做不到的事显式标为替身。
Avoid: 仿冒器、mock、假 WE
替身(Substitute):
模拟器做不到与 WE 一致的那部分能力(原生文件对话框回传 file:///、用户数据目录真实路径、CEF cookie 隔离)。它们在面板的常驻横幅与 window.__weSim.substituted 里被显式列出,而不是伪装成"和 WE 一样"。
Avoid: 兼容项、差异、限制
视口(Viewport): 交给 Spine 播放器的世界坐标系矩形,决定构图。它以等比"contain"方式映射到画布:单轴贴边、另一轴会多露出世界,因此负 padding 的裁切只在目标比例下成立。 Avoid: 相机、取景框、裁剪区
对拍(AB capture): 同一壁纸、同一分辨率、同一动画相位下的改动前后像素比对。噪声底约 0.7/255;低于 1 视为无差异。 Avoid: 截图测试、视觉回归