# 资源布局:一具骨架一组,而不是按文件类型分组 > **取代** ADR 0001 第 7 行「保留 `effects/`/`images/`/`audios/` 三层同名子目录」与 ADR 0005 第 43 行 > 「这一条不变」。那两条的前提是"Spine 骨架 JSON 硬编码了 `"images": "../images/"`,改了就得动骨架文件"; > 本决策把贴图页并到 atlas 同目录、把骨架里的 `images` 归零之后,这个前提本身消失了。 ## 背景 `wallpapers/<游戏id>/<壁纸id>/` 原来是按**文件类型**分的:骨架数据在 `effects/<名>.json`, atlas 与贴图页在 `images/`(或 `images/<名>/`),场景图在 `images/scene/`。于是**一具骨架自己的 东西被拆到两个目录**,找一样东西要跳两次;`effects/` 这个名字还有歧义(它装的是骨架数据, 不是"特效",而真正的场景图在别处)。 查过官方文档:**Spine 对 HTML 项目的目录结构没有任何约定**。[导出文档](https://en.esotericsoftware.com/spine-export) 只规定"每个骨架一个数据文件、文件名用骨架名",外加导出时可选 pack 出 atlas。所以这不是"遵循规范"的问题, 而是我们自己的取舍。 真正约束布局的只有一条**运行时的解析规则**(读 vendored spine-ts 源码得到): ```js page.setTexture(assetManager.get(pathPrefix + page.name)); // AssetManager.loadTextureAtlas ``` 即 atlas 第一行是页名、**页相对 atlas 所在目录解析**;`skeleton.images` 只是可选前缀。 所以「页必须与 atlas 同目录」是硬约束,其余随便放。 来源页面(米哈游活动页)自己也是按类型分的(`images/effect/spine/*.json|.atlas`、 `images/effect/scene/*`、`audio/*`),它能这么干是因为它的引擎有**全局图片表**、按逻辑名查页; 我们用的是 spine-ts,做不到。 ## 决策 **按"一件东西一组"分,而不是按后缀分**: ``` wallpapers/<游戏id>/<壁纸id>/ ├── spines/<骨架名>/<名>.json | <名>.atlas | 贴图页… ← 一具骨架一组 ├── scene/ ← 场景图与背景图(非骨架的图) ├── audios/ ← 音源 ├── meta.json preset.template.json preview.gif ``` - 页跟着 atlas 走,天然满足运行时规则(不再需要 `skeleton.images` 前缀)。 - 去掉了有歧义的 `effects/`:骨架就是骨架。 - 一档壁纸目前就是**一个场景**(`sceneConfig` 就是那一个),所以不再多套一层 `scenes/<场景id>/`; 真出现"一档多场景"时再加,而不是现在提前分层。 - **例外**:`backgroundImage` 可以指向某个骨架的贴图页(kv37 就是这样),那种情况就写 `spines/<名>/<页>`,不强行搬进 `scene/`——**判据是 atlas 自己声明的页名**,不做名字启发式。 ## 后果 - 构建侧要同步的一处硬编码:`tools/lib/generate.ts` 的 `copyWallpaperAssets` 只搬 `spines` / `scene` / `audios` 三个目录名;改名必须改它。 - `tools/downloader/promote.py` 产出的新壁纸直接是这套布局(否则存量与增量的形状会分叉)。 - 存量三档壁纸迁移了 86 个文件,预设路径按实际落点重写; `verify-visual-equivalence` 确认三个比例下**逐像素相同**(布局不影响渲染)。 - 迁移脚本留在 `.scratch/build-pipeline/`(含补救脚本),其中固化一条教训: 跨平台路径映射**别拿 `str(Path)` 当键**(Windows 会给反斜杠,而预设里是正斜杠)。