# SpineWallpaper(网页壁纸作品) 一份已发布的 Wallpaper Engine 网页壁纸,以及围绕它的资源分层、构建与验证约定。这个上下文关心"一档壁纸由什么组成、怎么被打包成可上传的分发、怎么在本地调试"。 ## 源数据规范 `wallpapers/` 是**唯一真相来源**,`dist/` 全部由它生成。那个目录里每个文件该放哪、叫什么、 构建会校验什么,见 **[wallpapers/README.md](wallpapers/README.md)** —— 目录结构、三层 `meta.json` 的字段、id 规则、音频规范、常见任务步骤、门禁清单都在那里。**动 `wallpapers/` 之前先读它。** 回归验收套件在 **[`tools/checks/`](tools/checks/README.md)**(原来散在 `.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_: 截图测试、视觉回归