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,76 @@
|
||||
extends SceneTree
|
||||
|
||||
## 列出发布包内 pck 的资源清单(T-WIN-023)
|
||||
##
|
||||
## 用途:`accept-release.sh` 用它断言**开发/验收工具不会被打进产物** ——
|
||||
## 起因是一个真实缺陷:`export_filter="all_resources"` + `exclude_filter` 里没有 `build/*`,
|
||||
## 于是 `build/` 下的探针脚本(含**故意不合规**的 `probe-ws-bad.gd`)与
|
||||
## `frame-vectors.json` 全被编译打包进 `OpenDance.pck`:
|
||||
## pck 315,464 B → 加上 exclude 后 145,400 B(**54% 是测试夹具**)。
|
||||
##
|
||||
## 用法:
|
||||
## godot --headless --main-pack <pck> --script <本文件> [-- --json]
|
||||
const DEV_PREFIXES := ["res://build/", "res://tests/", "res://_scratch/", "res://dist-release/"]
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
var args := OS.get_cmdline_user_args()
|
||||
var as_json := args.has("--json")
|
||||
var found: Array = []
|
||||
_walk("res://", found)
|
||||
found.sort()
|
||||
|
||||
var dev_entries: Array = []
|
||||
for f in found:
|
||||
for prefix in DEV_PREFIXES:
|
||||
if f.begins_with(prefix):
|
||||
dev_entries.append(f)
|
||||
break
|
||||
if f.ends_with(".md"):
|
||||
if not dev_entries.has(f):
|
||||
dev_entries.append(f)
|
||||
|
||||
if as_json:
|
||||
var report := {
|
||||
"entries": found,
|
||||
"count": found.size(),
|
||||
"devEntries": dev_entries,
|
||||
}
|
||||
var path := ""
|
||||
var i := args.find("--out")
|
||||
if i >= 0 and i + 1 < args.size():
|
||||
path = args[i + 1]
|
||||
if path != "":
|
||||
var file := FileAccess.open(path, FileAccess.WRITE)
|
||||
if file != null:
|
||||
file.store_string(JSON.stringify(report, " "))
|
||||
file.close()
|
||||
print("[pck] 清单已写入 %s(%d 项,开发残留 %d 项)" % [path, found.size(), dev_entries.size()])
|
||||
else:
|
||||
print(JSON.stringify(report))
|
||||
quit(0 if dev_entries.is_empty() else 1)
|
||||
return
|
||||
|
||||
print("[pck] 文件数 %d" % found.size())
|
||||
for f in found:
|
||||
print(" ", f)
|
||||
if dev_entries.is_empty():
|
||||
print("[pck] ✓ 无开发/验收残留(build/ tests/ _scratch/ *.md)")
|
||||
else:
|
||||
print("[pck] ✗ 产物里混入了开发残留 %d 项:" % dev_entries.size())
|
||||
for f in dev_entries:
|
||||
print(" · ", f)
|
||||
quit(0 if dev_entries.is_empty() else 1)
|
||||
func _walk(dir: String, out: Array) -> void:
|
||||
var d := DirAccess.open(dir)
|
||||
if d == null: return
|
||||
d.list_dir_begin()
|
||||
while true:
|
||||
var name := d.get_next()
|
||||
if name == "": break
|
||||
var path := dir.path_join(name) if dir != "res://" else "res://" + name
|
||||
if d.current_is_dir():
|
||||
if name != "." and name != "..": _walk(path, out)
|
||||
else:
|
||||
out.append(path)
|
||||
d.list_dir_end()
|
||||
Reference in New Issue
Block a user