fix(pack): 编码取整与参考实现对齐 + 产物不再夹带测试夹具(v0.2.4)
两个缺陷,都由上一轮的验收机制暴露:
1) 编码取整与契约参考实现不一致(T-WIN-022)
KeypointFrame.encode() 用 GDScript 的 roundi(半数远离零),而契约参考实现
(Web 的 keypoint-codec.ts)与**生产发送方 Android**(Kotlin Math.round)都是
「半数朝 +∞」:-2.5 分别是 -3 与 -2。实测在 12 个跨实现向量里 1 例 33 字节不同。
本端是接收方、encode() 只用于自测与协议回归,对产品无影响;但它让「跨实现一致性」
只能停留在宽松口径("差异仅限 .5 取整边界"),而宽松口径会退化成没人看的已知差异。
修复:新增 _round_like_js(v) = floor(v + 0.5),用于 4 处取整(坐标 ×3 + 置信度);
夹紧顺序对任意实数与参考实现等价。
收紧:accept-frame-vectors.sh 的编码口径改为「必须为 0 项差异」。
新增单测(+11 断言):_round_like_js 的 5 个取值、端到端 -0.5/32767 → 0(roundi
会给 -1,是有效判别用例),以及 test_reference_vectors_decode_and_encode_exactly
—— 直接读锁定的参考向量逐值解码 + 用 f32InputPoints 重编码与 f32Hex 逐字节比对,
让 verify.sh(只跑源码、无需 pck)也覆盖「Web ↔ Windows」这条边。
2) 产物里夹带开发/验收工具(T-WIN-023)
export_filter="all_resources" 且 exclude_filter 没有 build/*,于是每次新增
构建/验收脚本都被编译进 pck —— 包括**故意不合规**的坏服务端夹具 probe-ws-bad.gd
与 frame-vectors.json。实测 OpenDance.pck 315,464 B →(排除后)145,400 B,
即 54% 的 pck 是测试夹具。已发布的 v0.2.3 同样带着这些残留。
修复:exclude_filter 增加 build/*;
新增产物级防线 build/integration/list-pck.gd(遍历包内条目、标出
res://build/ tests/ _scratch/ dist-release/ 与 *.md 残留,有残留即退出码 1),
并接进 accept-release.sh(打印条目总数并断言「无开发残留」)——否则下次加个新
工具目录又会重演。顺带确认 pck 没有把上一次导出的 pck 自嵌套进去。
实测(v0.2.4 发布包,三条产物级验收全绿):
* accept-release.sh:pck 58 项无开发残留 / PE 版本资源 0.2.4 / pck 启动 180 帧无错 /
运行时探针(包内版本号 0.2.4、AboutScreen 显示值、15 项关键资源、契约表比对)
* accept-ws.sh:客户端 30 通过 / 0 失败 + 服务端侧 16 项事实
* accept-frame-vectors.sh:解码 12/12、396 点 0 差异,
**编码器字节与参考实现完全一致(0 项差异)**
* verify.sh:785 通过 / 0 失败(原 774)
踩坑:给 -0.5/32767 这类值写断言时,第一版按"理想有理数"期望(2.5/32767 → 3)而
失败 —— PackedVector3Array 存的是 float32,乘回 32767 后略小于半整数,与 JS 对同一
float32 输入的行为其实完全一致。断言必须基于实际存储精度,或改用有效判别用例。
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
# T-WIN-022:编码取整与契约参考实现对齐(编码差异收紧为 0)
|
||||
|
||||
- **状态**: done
|
||||
- **依赖**: T-WIN-021(跨实现一致性向量)
|
||||
- **文档**: v1.6 §7.6.2
|
||||
- **契约**: `websocket-keypoints 0.2.0` §2
|
||||
|
||||
## 缺陷
|
||||
|
||||
`KeypointFrame.encode()` 用 GDScript 的 `roundi` 取整,而契约参考实现
|
||||
(`opendance-web` 的 `keypoint-codec.ts`)与**生产发送方 Android**(Kotlin `Math.round`)
|
||||
都用「半数朝 +∞」:
|
||||
|
||||
| | `-2.5` | `2.5` | `-0.5` |
|
||||
|--|--|--|--|
|
||||
| JS `Math.round` / Kotlin `Math.round` | **-2** | 3 | 0 |
|
||||
| GDScript `roundi`(半数远离零) | **-3** | 3 | -1 |
|
||||
|
||||
实测差异:T-WIN-021 的 12 个向量里有 1 个用例(坐标含 `-0.5`)**33 字节不同**。
|
||||
|
||||
**影响**:本端是**接收方**,`encode()` 只用于自测与协议回归,因此对产品无影响 ——
|
||||
但它让「跨实现一致性」这条断言只能是宽松口径(「差异仅限取整边界」),
|
||||
而宽松口径在实践中会退化成「永远没人看的已知差异」。
|
||||
|
||||
## 修复
|
||||
|
||||
```gdscript
|
||||
## 量化取整:与契约参考实现(JS Math.round)逐位一致
|
||||
static func _round_like_js(value: float) -> int:
|
||||
return int(floor(value + 0.5))
|
||||
```
|
||||
|
||||
`encode()` 的 4 处取整全部改用它(坐标 ×3 + 置信度)。
|
||||
夹紧顺序(先取整后 `clampi`)与参考实现的「先夹后取整」对任意实数等价
|
||||
(`32767.4 → 32767`;`-32768.4 → -32768`),已在任务里逐值核对。
|
||||
|
||||
## 验证
|
||||
|
||||
1. **单测**(`tests/test_keypoint_frame.gd`,+11 条断言):
|
||||
* `_round_like_js` 的 5 个取值(含 `-2.5 → -2`、`-0.5 → 0`);
|
||||
* 端到端 `-0.5/32767 → 0`(`roundi` 会给 -1,是有效的判别用例);
|
||||
* `test_reference_vectors_decode_and_encode_exactly`:直接读**锁定的参考向量**
|
||||
(`build/integration/frame-vectors.json`),逐值解码比对 + 用 `f32InputPoints`
|
||||
重编码与 `f32Hex` **逐字节**比对 —— 这一步让 `verify.sh`(只跑源码,无需 pck)
|
||||
也覆盖了「Web ↔ Windows」这条边。
|
||||
2. **产物级**(`build/accept-frame-vectors.sh`,已收紧口径):
|
||||
原先允许「编码差异仅限 `.5` 取整边界」,现在**硬性要求 0 项**。
|
||||
|
||||
实测(v0.2.4 发布包):
|
||||
|
||||
```
|
||||
[frame-vectors] ✓ 解码结果与 Web 参考实现逐值一致(float32 精确相等)
|
||||
[frame-vectors] ✓ 本端 encode() 与参考字节完全一致
|
||||
✓ 解码差异 0 项(必须为 0)
|
||||
✓ 编码器字节与参考实现完全一致(0 项差异)
|
||||
```
|
||||
|
||||
`verify.sh`:**785 通过 / 0 失败**(原 774)。
|
||||
|
||||
## 写测试时踩的坑
|
||||
|
||||
第一版断言写成「`2.5/32767` 编码为 3」,**失败**。原因不是编码错了,而是
|
||||
`PackedVector3Array` 把该值存成 **float32**,`float32(2.5/32767) × 32767` 略小于 2.5,
|
||||
`floor(x+0.5)` 得 2 —— 与 JS 对同一 float32 输入的行为**完全一致**。
|
||||
即:**断言必须基于实际存储精度**,或改用「规则本身 + 有效判别用例」
|
||||
(`-0.5/32767`)而不是「理想有理数」。这与 T-WIN-021 的两处踩坑同源。
|
||||
@@ -0,0 +1,57 @@
|
||||
# T-WIN-023:发布包内不得混入开发/验收工具
|
||||
|
||||
- **状态**: done
|
||||
- **依赖**: T-WIN-018(发布包级验收)
|
||||
- **文档**: v1.6 §5.3 / §7.5
|
||||
- **契约**: 不涉及(打包)
|
||||
|
||||
## 缺陷(实测)
|
||||
|
||||
`export_presets.cfg` 用的是 `export_filter="all_resources"` + `exclude_filter="_scratch/*, tests/*, *.md"`。
|
||||
`build/` 不在排除列表里 —— 于是**每次新增一个构建/验收脚本,它就被编译进产物**:
|
||||
|
||||
```
|
||||
res://build/probe-release.gdc
|
||||
res://build/integration/probe-integration.gdc
|
||||
res://build/integration/probe-ws.gdc
|
||||
res://build/integration/probe-ws-bad.gdc ← **故意不合规**的服务端夹具
|
||||
res://build/integration/probe-frame-vectors.gdc
|
||||
res://build/integration/frame-vectors.json
|
||||
```
|
||||
|
||||
体积代价:`OpenDance.pck` **315,464 B → 145,400 B(排除后)**,
|
||||
即 **54% 的 pck 是测试夹具**。已发布的 v0.2.3 同样带着这些残留(其中包含
|
||||
那个刻意违反协议的坏服务端),虽然不影响功能,但既不该进产物,也不该被用户拿到。
|
||||
|
||||
## 修复
|
||||
|
||||
1. `export_presets.cfg` 的 `exclude_filter` 增加 `build/*`:
|
||||
```ini
|
||||
exclude_filter="_scratch/*, tests/*, build/*, *.md"
|
||||
```
|
||||
2. **新增产物级防线**(否则下次加一个 `tools/` 目录又会重演):
|
||||
* `build/integration/list-pck.gd`:用 `--main-pack` 遍历 `res://`,列出全部条目并
|
||||
标出开发残留(`res://build/`、`res://tests/`、`res://_scratch/`、`res://dist-release/`、`*.md`);
|
||||
有残留即退出码 1,支持 `--json --out <path>`。
|
||||
* `build/accept-release.sh` 新增步骤:跑该脚本并断言「无开发残留」,同时打印条目总数。
|
||||
|
||||
## 验证
|
||||
|
||||
```
|
||||
# 修复前
|
||||
[pck] ✗ 产物里混入了开发残留 12 项:res://build/…(pck 315,464 B)
|
||||
|
||||
# 修复后(v0.2.4)
|
||||
✓ pck 共 58 项,无开发残留(build/ tests/ _scratch/ *.md)
|
||||
(pck 145,400 B;zip 26,631,908 B,仍在 30 MB 上传上限内)
|
||||
```
|
||||
|
||||
`accept-release.sh` 其余步骤(PE 版本资源、pck 启动 180 帧、运行时探针、契约表比对)
|
||||
在排除 `build/*` 后依旧全绿 —— 验收脚本本身是**从仓库路径**加载探针的,
|
||||
不依赖包内是否有它们的副本。
|
||||
|
||||
## 顺带确认(不是问题)
|
||||
|
||||
排查时特意确认了 pck **没有**把上一次导出的 pck 自嵌套进去
|
||||
(`file_count=112`、条目里没有 `res://build/windows/OpenDance.pck`):
|
||||
Godot 在扫描资源时排除了自己的导出产物目录内容。这条也由 `list-pck.gd` 长期盯着。
|
||||
Reference in New Issue
Block a user