feat: TS 构建管线、Spine 抓取器与 wallpapers/ 唯一真相来源

把项目从「手写 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 处引用自包含。
This commit is contained in:
Shuery committed 2026-10-02 01:27:02 +08:00
1 parent b8eee05d78
commit 3f11426964
297 files changed
+216627 -1926

No files matched your search

@@ -0,0 +1,105 @@
# 17 — 自动生成的 id 改成自增
Status: resolved
Type: task
## 需求
> 自动生成的 json 相关的 id 采取自增的形式,如 audios
两处(用户确认「两者都要」):
| | 旧 | 新 |
| --- | --- | --- |
| `project.json` 属性的 `index` / `order` | 手写在 `src/project.template.json` | build 按属性表书写顺序自动编号 |
| 音源 id(`meta.json` 的 `audio.choices[].id`) | 手写字符串(`zaiduheni` / `xilian` / `pv37`) | build 自增分配(`"1"` / `"2"` / `"3"`) |
## ① index / order
以前每个属性都要写 `"index": 4, "order": 104`。代价在**加一个属性**时才显出来:
后面全部要重编号——上一轮加 `use_custom_audio` 手改了 6 处,而且在产物 diff 里
刷出 8 行纯噪音,把真正的改动淹掉了。
现在 `numberProperties()` 按属性表的书写顺序编号:
```
index = 0,1,2… order = 100+index
```
**必须在删属性之后编号**:单档分发会删掉 `preset`/`preset_note`/`bgm`/`bgm_note` 四行,
以前删完就留下空号(0,1,4,5,6,7,8)。现在自动补上,单档是 0…6 连续。
`schemecolor` 是唯一例外:它是 WE 的**内置**属性,线上就是 `order 0` 且**没有** `index`。
用 `"$order": 0` 显式钉住,钉住的属性不参与 index 序列——这样 `collection-all` 的产物
与线上逐字一致,diff 里只剩真实改动。
## ② 音源 id
`meta.json` 里不再写 id:
```json
"audio": {
"choices": [
{ "name": "「再度和你」", "file": "audios/zaiduheni.flac" },
{ "name": "昔涟", "file": "audios/xilian-src.flac" }
]
}
```
### 一个必须解决的约束:id 要**全项目**唯一
第一版我按「每档壁纸各从 1 开始」写,**这是错的**。`generate.ts` 里:
```ts
bgmOptions.push({ label: choice.name, value: choice.id });
```
`bgm` 下拉会把一个分发里**所有**壁纸的音源平铺进同一个 combo。两档壁纸都叫 `"1"` 的话,
选中的到底是哪一个就无从分辨了。
所以用一个**全项目计数器**,分配顺序固定为:
```
游戏 → 该游戏的共享音频 → 各壁纸的音源(按 id 排序)
```
遍历的是**整棵资源库**而不是本次要构建的分发子集——否则同一个音源在不同分发里会拿到不同的
id,用户从合集切到单档时 bgm 选择就失效了。实测:xilian 的两个音源在 `collection-all` 与
`single-hsr-xilian` 里都是 `"2"` / `"3"`。
`default` 也跟着改:从「id 字符串」变成「**1 起的位置**」,省略 = 第一个。换算成 0 起的
`defaultIndex` 只做一处(`vault.ts` 的 `readAudioDecl`)。
### 代价(用户已确认接受)
音源 id 会进 `project.json` 的 `bgm` combo value。改成数字后,**老用户已保存的 bgm 选择会失效**
(和壁纸预设 id 同一个问题)。运行时对这种情况是安全的:`bgmSource()` 找不到匹配就回落到
本壁纸默认音源。另外在音源清单中间插入一条会让后面的 id 整体后移——加音源请往后加。
## 顺手修掉一个「一直红着」的测试
改完跑 `test-props.mjs`,2 项失败:
```
✗ bgm 选「昔涟」后填自定义 URL → 期望 custom-track.mp3,实得 xilian-src.flac
✗ 切壁纸不得清掉自定义 URL → 期望 custom-track.mp3,实得 pv37.mp3
```
**这不是这次改动造成的**:上一轮加 `use_custom_audio` 时,`audio_file` 被改成只在开关打开时
才生效,而当时我**没有重跑这个测试**——它从那时起就一直是红的。`test-acceptance.mjs` 同理。
两处都补上开关,并顺手把「开关关着时填 URL 不生效」加成一条断言——那正是这个开关的核心语义。
> 改了语义却没跑对应的测试,等于没测。这次是靠改另一个东西时才撞出来的。
## 验证
全量回归,全绿:
- `pnpm check`:四个分发、`check:dist` / `check:syntax` / `check:paths`("3 条音源声明无孤儿文件")
- `test-props.mjs`(bgm 选择/回落、fps 限流)、`test-resize.mjs`、`test-acceptance.mjs`、`verify-fitter.mjs`
- 调试服冒烟、浏览器冒烟、面板还原度(10 组)、热更新
- 四档自包含包 `file://`、静态托管预览、sim 门禁、内联守卫、模块改写
- `verify-visual-equivalence`:6 组与重构前逐像素相同
- `diff-project`:差异 13 → **14**,新增的那一条是 `bgm.options`(value 从名字变数字),
正是本次有意的改动;`index`/`order` 相关**没有**新增差异