Files
Shuery 3f11426964 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 处引用自包含。
2026-10-02 01:27:02 +08:00

69 lines
4.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 04 比例适配:按容器比例重算视口
Status: resolved
## 问题
viewport 以等比 "contain" 映射到画布:单轴贴边、另一轴多露出世界。
原本的判断是"负 padding 只在目标比例下成立,其它比例会裁掉主体"——**实测证明这个判断是错的**,
真正的病根是另一件事。
## 诊断(实测,非推测)
先量清楚事实,再改代码:
1. **contain 永远不会裁掉作者视口**。可见矩形是宽高比等于画布、且恰好含住作者视口 R 的最小矩形,
所以 R 在**任何**比例下都完整可见。比例不同只是"另一轴多露出世界":
- 21:9 → 6453×2701(竖轴贴边、横轴多露)
- 16:9 → 6050×3403
- 9:16 → 1914×3403(横轴贴边)
三种比例下 `coversView` 全为 true,"裁掉作者视口"这条假设不成立。
2. **这不是我重构引入的回归**。基线 `d8c7e49` 的 `preset-store.js` 里 xilian 的四个 pad
(-25% / -28% / -30% / -23%)与 kv37 的 `x: -1215, y: -410, w: 2048, h: 1024` 与现在一字不差;
基线的容器也是 `#spine-container` 绝对定位铺满、body flex 居中。取景语义未变。
3. **真正的病根:背景走 cover,立绘层走 contain,两者缩放分叉。**
`body { background-size: cover }` 会按需放大裁切,立绘层却按 contain 固定世界矩形。
以 1080×1920 竖屏为例,背景被放大约 1.78 倍,立绘层的世界高度却纹丝不动——
特效于是和背景插画脱节,"多余世界"其实看不太出来,脱节才看得出来。
4. **spine 资源路径是 `./effects/*.json`:立绘层是特效层,背景插画才是主体。**
所以判定标准不该是"特效不被裁切"(粒子飘出画面本来就正常),
而应是"特效与背景插画始终同倍率、同中心"。
## 做法
新增 `dist/scripts/viewport-fitter.js`(纯函数)与 `SpineController` 的取景维护:
- 参考比例 `16/9`:先让播放器自行解出作者视口 R,换算成参考比例下的可见矩形 V0。
**这个比例下与作者设定逐像素一致**。
- 其它比例:让立绘层的像素缩放**跟着背景的 cover 缩放走**——
`V.h = V0.h × coverScale(参考比例) / coverScale(当前比例)`,中心不变。
于是背景放大 1.78 倍时,特效也放大 1.78 倍。
- 应用方式:原地改写 `config.viewport` 的 x/y/width/height 与四个 pad,再调
`player.setViewport(动画名)`,并把 `viewportTransitionStart` 推到足够久以前(避免 0.25s 插值拖影)。
绝不整体替换 `config.viewport`:`setViewport()` 会无条件读 `config.viewport.animations[name]`,
换掉整个对象会在 spine-player 第 14973 行崩溃。
- 维护时机:挂在 `config.frame` 回调上(它在每帧相机计算**之前**被调用),以画布比例作键——
比例没变就是一次浮点比较后返回,比例变了就地重算。
`config.success` 用不了:它在 `setViewport` 之前触发,那时 `currentViewport` 还不存在。
- 预设可用 `framing: "author"` 退回作者原始的 contain 行为。
## 验收
| 项目 | 结果 |
| --- | --- |
| 参考比例回归 | kv37 **逐像素 0 差异**;xilian 仅 1/255(浮点末位舍入) |
| 立绘/背景缩放同步 | 21:9 / 16:9 / 16:10 / 9:16 × 两个预设,**全部精确相等**(离线算式验证 + 渲染实测) |
| 渲染实测吻合预测 | 三种比例可见矩形与公式预测逐值一致(pads 全 0、base == resolved) |
| 对拍留档 | `tools/shots/fit/` 下 6 组(2 预设 × 3 比例)改动前后各一套(该目录按仓库约定不入库,可随时重跑重生成) |
| 不重建播放器 | 9 次改视口 + 连续 20 次压力往返,实例身份标记**从未丢失** |
| 连续 resize 内存 | 强制 GC 后 7.9 MB → 8.0 MB(增长 **0.04 MB**) |
| 回到 16:9 可逆 | 取景完全回到原值,无漂移 |
| 控制台 | 0 报错 / 0 未捕获异常 |
复现:`node tools/checks/verify-fitter.mjs`(离线算式)、`node tools/checks/test-resize.mjs`(resize 行为)。
## 附带修复
Chromium 会自动请求 `/favicon.ico` 并在控制台留下 404。已在 `dist/index.html` 加
`<link rel="icon" href="data:," />` 消除,使"0 console 报错"这条验收真正可达成。