# 桌宠精灵图契约 素材沿用 Codex 桌宠的固定图集契约(8 列 × 11 行),多出第 9–10 行为「视线(look)」。 3D 路线复用同一张图集:第 9 行起放**环视角度帧**(见文末)。 ## 图集规格 | 项目 | 值 | | --- | --- | | 文件 | `atlas.png`(或 `spritesheet.webp`) | | 格式 | PNG(RGBA 8bit 非隔行,推荐)或 WebP | | 网格 | **8 列 × 11 行** | | 单格 | **不限分辨率**,内置素材为 `192 × 208` px | | 图集尺寸 | `列数 × 单格宽` × `行数 × 单格高` | | 背景 | 全透明 | | 未用格 | 必须全透明 | > 画布上**不要**添加标签、留白边、网格线、格内阴影或额外帧,动画靠 CSS > `background-position` 按固定行列取帧。 > > ⚠️ **生成端不要直接输出 WebP**:网页端没问题,但原生桌面窗口走 WPF/WIC, > 默认**解不了 WebP**。内置素材因此同时提供 `.webp`(网页)和 `.png`(桌面), > 用 `atlas.desktopFile` 区分,见 `scripts/build-assets.mjs`。 ### 分辨率:不限,等比缩放 单格画多大都行 —— 192px 的像素风、1024px 的精细立绘、甚至写实素材都可以, 显示时按窗口尺寸**等比缩放**(宽高比由 `cellW : cellH` 决定,不会变形)。 唯一的硬性要求是**图集实际尺寸必须严格等于 `列数×单格宽` × `行数×单格高`**, 否则取帧会错位(看起来像乱码)—— 这道闸一直开着。 两条实践约定: 1. **按 2× 分辨率创作。** 也就是"打算显示多大就画两倍大"。这是 HiDPI 的通行 做法,也让"自动尺寸"能给出合理结果:显示宽度 = `单格宽 ÷ 2`。 192px 一格 → 96 DIP(内置 C罗就是按这个来的),1024px 一格 → 512 DIP。 想指定别的默认值,写 `atlas` 同级或宠物级别的 `desktopSize` 字段。 2. **声明缩放方式**,在 `pet.json` 里写: ```json { "atlas": { "cols": 8, "rows": 11, "cellW": 1024, "cellH": 1152, "scaling": "smooth" } } ``` | `scaling` | 用于 | 效果 | | --- | --- | --- | | `smooth`(默认) | 高分辨率 / 写实 / 带抗锯齿边缘 | 高斯重采样,缩小后不糊不噪 | | `pixelated` | 像素风、硬边 | 最近邻,边缘不被插值糊掉 | 桌面窗口对应 WPF 的 `HighQuality`(Fant) / `NearestNeighbor`, 网页端对应 CSS `image-rendering: auto` / `pixelated`。 ### 上限(防呆,不是创作限制) | 限制 | 值 | 为什么 | | --- | --- | --- | | 单格宽/高 | ≤ 4096 px | 再大解码就会明显卡住 | | 图集总像素 | ≤ 6400 万 | 约 256 MB RGBA,超过会吃掉几百 MB 内存 | 超过上限会在注册时被**明确拒绝**并说明原因,而不是让用户看到一只卡死的宠物。 以 8×11 网格算,单格大约可以到 850×920 px 而不触总像素上限;格数更少时可以更大。 > 实现说明:桌面窗口**不会**把整张图集复制成像素数组。点击穿透的 alpha 命中检测 > 只按当前那一格裁剪后取像素(见 `Get-CellAlpha`),所以高分辨率图集的代价是 > "一格"而不是"一张"。这一点在改之前是整张图全量拷贝,4K 图集会直接吃掉两位数的 > MB 并卡住启动。 ## 动画行 | 行 | 状态 | 使用列 | 帧率(fps) | | --- | --- | ---: | --- | | 0 | idle(待机呼吸) | 0–5 | 6 | | 1 | running-right(向右跑/运球) | 0–7 | 12 | | 2 | running-left(向左跑/运球) | 0–7 | 12 | | 3 | waving(挥手) | 0–3 | 8 | | 4 | jumping(SIU 庆祝跳) | 0–4 | 10 | | 5 | failed(戏剧摔倒) | 0–7 | 12 | | 6 | waiting(等待回复) | 0–5 | 5 | | 7 | running(颠球/专注工作) | 0–5 | 12 | | 8 | review(思考审阅) | 0–5 | 6 | | 9 | look(视线 0°–157.5°,8 方向) | 0–7 | 单帧 | | 10 | look(视线 180°–337.5°,8 方向) | 0–7 | 单帧 | ## 状态机 → 动画映射 宿主端状态机输出 `mode`,客户端映射到图集行: | mode | 动画行 | 说明 | | --- | --- | --- | | `idle` | 0 idle | 空闲呼吸 | | `working` | 7 running | 工具执行中,颠球/专注 | | `review` | 8 review | 回合中思考 | | `waiting` | 6 waiting | 等待审批/用户输入 | | `failed` | 5 failed | 出错,戏剧摔倒 | | `celebrating` | 4 jumping | 完成,SIU 庆祝(宿主同步系统音) | | 拖动 | 1/2 run(方向跟随) | 运球 | | 悬停 | 9/10 look | 看向光标 | | 连点 3 次 | 5 failed | 假摔要球 | --- ## 3D 路线(Blender)的复用方式 3D 宠物用的是**同一张图集的同一套行号**,只是: | | 2D | 3D | | --- | --- | --- | | 行 0–8 | 程序化动画 / 生图条带 | Blender 逐帧渲染 | | 行 9–10 | 16 方向视线(两行 × 8 列) | **环视角度帧**(每角度占一格,从第 9 行起顺序排) | | 视角跟随 | 轻微位移 + 倾斜模拟 | 真的换一个角度的模型 | 3D 的 `states.look` 用 `angles` 而不是 `rows`: ```jsonc "look": { "angles": [ {"row":9,"col":0}, {"row":9,"col":1}, /* … */ {"row":10,"col":7} ], "frames": 1, "fps": 1 } ``` 客户端按光标角度选格: ``` idx = round(((lookDir % 16) + 16) % 16 / 16 * angles.length) % angles.length ``` 即 16 档光标方向映射到 N 个渲染角度(N = `yaw.count`,默认 8,即每 45° 一张)。 ### 3D 也要遵守"不裁切"保证 Blender 是**直接按 192×208 渲染**的,跳跃(抬高 0.3m)和摔倒(旋转 78°) 会把模型顶出画面。所以 `blender-assemble` 在装配时会做一次 **全体帧并集包围盒 + 统一缩放**再落格——和 2D 路线是同一套算法 (`anim.mjs` 的 `unionBounds` / `projectFrames`),因此两条路线产出同样合格。