feat(integration): 跨端集成验收(包内客户端 × 真实 Web 服务端)+ 修两个真实缺陷(v0.2.3)

单测打的是假 HTTP 夹具,Web 端测试只验自己那一侧 —— 两端各自绿灯不代表契约字段
真的对得上。本版加了跨端集成验收,并在首次运行就抓到两个真实缺陷。

一、跨端集成验收(T-WIN-019)
  * build/integration/probe-integration.gd:只加载**包内**代码
    (`godot --main-pack <pck> --script <探针>` + res://),打真实服务端并逐项断言:
    /health 六个契约版本、catalog/version 指纹、catalog/export 解析出的四类资源与
    defaultModelId;**场景物理参数逐项比对**(重力/基础风/阵风/风坐标系/地面摩擦坡度/
    碰撞体/出生点/相机/环境)+ Wind.sample(0) + 场景 GLB 可下载;
    动作 NDJSON → 客户端解析帧数 == 服务端帧数、33 点、每帧服务端关节角、
    推导帧率 30 fps、时长 96.67 s;缩略图 PNG;模型 VRM 可下载。
  * build/integration/seed-catalog.mjs:用**真实素材**播种目录
    (示例文件/ 下的真 VMD → 内部标准 33 点;真 PMX → VRM;场景 GLB + 物理参数),
    并把期望值(含从 /health 取的契约版本)写成 JSON。
  * build/accept-integration.sh:编排「服务端可达 → 播种 → 包内探针 → 汇报」。
  实测(v0.2.3 包内 pck × 真实 Web v0.7.4 + 真实 MySQL):全部通过。

二、探针抓到的两个真实缺陷
  1. `CatalogTypes.Motion` 缺 `thumbnail_url`:服务端 catalog/export 从
     data-models 0.6.0 起一直在发 thumbnailUrl(Model/Scene 都有),客户端默默丢掉。
     已补字段与解析(缺失为 ""),新增 tests/test_catalog_types.gd(4 项 20 断言)
     用**服务端真实报文形状**锁住三个资源类型的覆盖,并显式记录一处刻意不消费的差异
     (Model.usable/unusableReason —— 目录只下发可用模型,恒为 true)。
  2. `ApiClient._request()` 对**所有**响应无条件 `raw.get_string_from_utf8()`:
     数据本身没错(二进制路径返回原始字节),但每下载一个文件都会打印
     「Unicode parsing error … Invalid UTF-8 leading byte (N)」噪声,并把整个文件
     (模型可达数十 MB)解码成 String 拷贝后丢弃。改为只在需要文本时解码。

三、顺带修掉测试里的写死契约版本
  tests/test_api_integration.gd 断言 rest-api/websocket-keypoints/skeleton-topology
  均为 "0.1.0"。该用例只在服务端可达时才跑,平时一直 skip,所以契约升到 0.5.0 后
  没有任何地方发现它。改为从仓库 contracts/VERSION 读期望值并与 /health 比对
  (顺带成为「客户端契约快照 vs 服务端」的跨端校验)。

证据:verify.sh 全绿(820 通过 / 0 失败,比上版 +46,因为服务端在线时 API 集成组
真的跑了);包内 pck 跨端集成探针全部通过;check-version/PE 版本资源 = 0.2.3。
This commit is contained in:
2026-09-17 05:18:03 +08:00
parent e4e534f2a2
commit a78a536476
15 changed files with 889 additions and 23 deletions
+33 -7
View File
@@ -22,10 +22,36 @@ OpenDance Studio **阶段二**: Windows 客户端(Godot 4)。
| 跨实现交叉校验 | OK 关节角与 Web 服务端 TypeScript 实现**逐帧一致** |
| 场景选择与物理落地 | OK `scenes[]` 可选、重力/风/摩擦/碰撞体落地并可日志核对 |
| 连接保活与设备去重 | OK welcome/config 声明静默超时与心跳;应用层 ping/pong;按 deviceId 去重 |
| 发布整包 | OK 单个 `opendance-windows-v0.2.2.zip` + `SHA256SUMS`(不拆分卷) |
| 发布整包 | OK 单个 `opendance-windows-v0.2.3.zip` + `SHA256SUMS`(不拆分卷) |
| 版本号展示 | OK 「关于」清单显示 `v0.2.2`;`project.godot` / `project.yaml` / `export_presets.cfg` / exe 的 PE 版本资源 / 产物名 / tag 全部一致 |
| 场景物理与保活 | OK 新增真实 GLB 端到端用例 + 17 秒保活对照用例(751 项断言全过) |
## v0.2.3 修订(跨端集成验收 + 两处真实缺陷)
**缺陷 1(契约字段缺失)**:`CatalogTypes.Motion` 没有 `thumbnail_url` 字段,
而服务端 `catalog/export` 从 `data-models 0.6.0` 起一直在发 `thumbnailUrl`
(同一文件的 `Model` / `Scene` 都有)。客户端默默丢掉该字段。
**修复**:补字段与解析(缺失时为 `""`,兼容旧服务端),并新增
`tests/test_catalog_types.gd`(4 项)用**服务端真实报文形状**锁住三个资源类型的
`thumbnailUrl` 覆盖;同时显式记录一处**刻意不消费**的差异
(`Model.usable`/`unusableReason` —— 目录只下发可用模型,恒为 true)。
**缺陷 2(二进制响应白做 UTF-8 解码)**:`ApiClient._request()` 对**所有**响应
无条件执行 `raw.get_string_from_utf8()`。数据本身没错(二进制路径返回的是原始字节),
但每下载一个文件都会打印一条
`Unicode parsing error … Invalid UTF-8 leading byte (N)` 噪声,
并把整个文件(模型可达数十 MB)解码成一份 String 拷贝后丢弃。
**修复**:只在需要文本时解码。跨端集成探针第一次注意到这条噪声。
**新增跨端集成验收**(`build/accept-integration.sh`):用**发布包里的客户端**
(`--main-pack` 加载包内 pck)打**真实 Web 服务端**,逐项比对契约与物理参数,
并跑通「真 VMD → 内部标准 33 点 → 客户端解析 2901 帧」的完整闭环。
详见 [`tasks/019-cross-end-integration.md`](tasks/019-cross-end-integration.md)。
此外修掉 `tests/test_api_integration.gd` 里一处**写死的契约版本**(`0.1.0`):
该用例只在服务端可达时才跑,平时一直 skip,于是契约升到 0.5.0 后
没有任何地方发现它。现在改为从仓库 `contracts/VERSION` 读期望值并与服务端 `/health` 比对。
## v0.2.2 修订(发布包 exe 的版本元数据)
**缺陷**:v0.2.1 的包在 Windows 上「右键 → 属性 → 详细信息」显示
@@ -83,10 +109,10 @@ GODOT_BIN=/path/to/godot bash build/verify.sh
GODOT_BIN=/path/to/godot bash build/export-windows.sh release
# 打包发布产物(单个完整 zip + SHA256SUMS)
bash build/package-release.sh 0.2.2
bash build/package-release.sh 0.2.3
# 发布包级验收(对**发布包**跑,而不是对源码;文档 §7.5)
GODOT_BIN=/path/to/godot bash build/accept-release.sh dist-release/opendance-windows-v0.2.2.zip
GODOT_BIN=/path/to/godot bash build/accept-release.sh dist-release/opendance-windows-v0.2.3.zip
```
`accept-release.sh` 校验 sha256 → 解压 → 文件清单(并检查没有 `.partN` 残留)→
@@ -97,7 +123,7 @@ exe 的 PE 版本资源 → **用 Godot 直接加载包内 pck 跑 180 帧** →
已对**已发布的 v0.2.2 资产**(重新下载)跑通,并用「篡改包内 contracts/VERSION」与
「期望版本写错」两个负例反向验证过。
当前版本:**v0.2.2**(`project.godot` 的 `application/config/version`,
当前版本:**v0.2.3**(`project.godot` 的 `application/config/version`,
与 `project.yaml`、`export_presets.cfg`、导出产物名、Git tag、「关于」清单展示、
以及 exe 的 PE 版本资源全部一致)。
@@ -180,7 +206,7 @@ GODOT_BIN=/path/to/godot bash build/export-windows.sh release
powershell -ExecutionPolicy Bypass -File build\export-windows.ps1
# 打成可分发的 zip(单个完整包 + SHA256SUMS)
bash build/package-release.sh 0.2.2
bash build/package-release.sh 0.2.3
```
| 产物 | 位置 | 说明 |
@@ -188,7 +214,7 @@ bash build/package-release.sh 0.2.2
| `OpenDance.exe` | `build/windows/` | 主程序,约 104 MB;脚本校验前 2 字节为 `MZ`(合法 PE) |
| `OpenDance.pck` | `build/windows/` | 资源包,约 107 KB; **必须与 exe 同目录** |
| `OpenDance.console.exe` | `build/windows/` | 控制台包装器,可能生成,非必需 |
| `opendance-windows-v0.2.2.zip` | `dist-release/` | 发布包:exe + pck + `THIRD_PARTY.md` + `README.txt` + `README.md` + `contracts/` |
| `opendance-windows-v0.2.3.zip` | `dist-release/` | 发布包:exe + pck + `THIRD_PARTY.md` + `README.txt` + `README.md` + `contracts/` |
| `SHA256SUMS` | `dist-release/` | 裸文件名,与 zip 同目录时可 `sha256sum -c SHA256SUMS` 直接校验 |
导出前必须安装与编辑器同版本的 export templates,否则脚本会报「未找到导出模板」。
@@ -292,7 +318,7 @@ src/
等格式,但本项目未在 Windows 上实测)。
若 IIS 上限已放开、或你能把包放到别处分发(例如 Web 服务端的 `/static`,
不受 IIS 那个限制约束),请改用资源管理器可直接双击解压的 deflate 包:
`ZIP_METHOD=deflate bash build/package-release.sh 0.2.2`(约 38 MB)。
`ZIP_METHOD=deflate bash build/package-release.sh 0.2.3`(约 38 MB)。
7. **发布包 exe 的版本元数据**(T-WIN-017):v0.2.1 之前在 Windows
「属性 → 详细信息」里显示的是 `0.1.0`,因为 `export_presets.cfg` 的
`application/file_version` / `application/product_version` 被硬编码且无人校验。