Files
opendance-windows/build/accept-release.sh
T
zijunchiang 6ea2c93d66 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 输入的行为其实完全一致。断言必须基于实际存储精度,或改用有效判别用例。
2026-09-17 05:59:54 +08:00

200 lines
8.5 KiB
Bash
Executable File
Raw 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.
#!/usr/bin/env bash
# ============================================================
# OpenDance Studio Windows 客户端 —— **发布包级端到端验收**(T-WIN-018)
#
# 为什么需要它:`build/verify.sh` 跑的是**仓库源码**(`godot --path .`),
# 而交付给用户的是 zip 里的 `OpenDance.pck` + `OpenDance.exe`。
# 差别全在「导出」这一步 —— 导出预设漏资源、pck 没重新生成、版本号没同步、
# 契约快照没更新,都只在包里才暴露。本项目已经因此翻过一次车:
# v0.2.1 的包解开后 exe 属性页显示 0.1.0(配置文件却全一致)。
#
# 本脚本对一个 zip(或已解压目录)跑完:
# 1. sha256 校验(有 SHA256SUMS 时);
# 2. 解压 + 文件清单完整性(exe / pck / README / THIRD_PARTY / contracts);
# 3. exe 的 **PE 版本资源** = 本次发布版本(build/check-pe-version.sh);
# 4. 用 Godot 直接加载**包内 pck** 跑 180 帧,断言无 SCRIPT ERROR;
# 5. 包内运行时探针(build/probe-release.gd):版本号、AboutScreen 显示值、
# 主场景、autoload、关键资源可加载、**pck 内契约表与包内 contracts/VERSION 一致**。
#
# 需要与本项目同版本的 Godot 4.7.x(导出模板要求的版本)。
#
# 用法:
# 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.2
# GODOT_BIN=... bash build/accept-release.sh <zip> 0.2.2 # 显式指定期望版本
#
# 可选环境变量:
# WORK_DIR 解压目录(默认 mktemp -d)
# BOOT_FRAMES 启动检查帧数(默认 180)
# ============================================================
set -uo pipefail
PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
cd "$PROJECT_DIR"
TARGET="${1:-}"
YAML_VERSION="$(sed -n 's/^version:[[:space:]]*//p' project.yaml | head -1)"
EXPECTED="${2:-$YAML_VERSION}"
WORK_DIR="${WORK_DIR:-$(mktemp -d)}"
BOOT_FRAMES="${BOOT_FRAMES:-180}"
FAILED=0
log() { printf '\033[1;34m[accept]\033[0m %s\n' "$*"; }
fail() { printf '\033[1;31m[accept] ✗ %s\033[0m\n' "$*" >&2; FAILED=1; }
if [ -z "$TARGET" ] || [ ! -e "$TARGET" ]; then
echo "用法:GODOT_BIN=/path/to/godot bash build/accept-release.sh <发布包 zip 或已解压目录> [期望版本]" >&2
exit 2
fi
if [ -z "$EXPECTED" ]; then
echo "✗ 无法确定期望版本(project.yaml 里没有 version,也没有显式传入)" >&2
exit 2
fi
log "目标:$TARGET"
log "期望版本:$EXPECTED"
log "工作目录:$WORK_DIR"
mkdir -p "$WORK_DIR"
# ---- 1. sha256 + 解压 ----
if [ -f "$TARGET" ]; then
# SHA256SUMS 里是**裸文件名**,所以校验时要把 cwd 切到包所在目录;
# 但 sums 文件本身必须用绝对路径 —— 否则 cd 之后相对路径会被二次拼接成
# dist-release/dist-release/SHA256SUMS(这个 bug 在本脚本首跑时就命中了)。
TARGET_DIR_ABS="$(cd "$(dirname "$TARGET")" && pwd)"
SUMS="$TARGET_DIR_ABS/SHA256SUMS"
if [ -f "$SUMS" ]; then
log "sha256 校验($SUMS):"
if ( cd "$TARGET_DIR_ABS" && sha256sum -c "$SUMS" --ignore-missing ); then
:
else
fail "sha256 校验失败"
fi
else
log "未找到 $SUMS,跳过 sha256 校验"
fi
log "解压…"
case "$TARGET" in
*.zip)
if unzip -qq -o "$TARGET" -d "$WORK_DIR" 2>/dev/null; then
log " 用 unzip 解压成功(deflate 包)"
elif command -v 7z >/dev/null 2>&1 && 7z x -y -o"$WORK_DIR" "$TARGET" >/dev/null; then
log " 用 7z 解压成功(LZMA 包 —— 注意 Windows 10 资源管理器不支持它)"
else
fail "解压失败(既不是可 unzip 的 deflate 包,也没有 7z 处理 LZMA)"
exit 1
fi
;;
*)
fail "只支持 .zip 或已解压目录"
exit 2
;;
esac
PACKAGE_DIR="$(find "$WORK_DIR" -maxdepth 1 -mindepth 1 -type d | head -1)"
else
PACKAGE_DIR="$TARGET"
fi
[ -n "$PACKAGE_DIR" ] && [ -d "$PACKAGE_DIR" ] || { fail "找不到解压后的包目录"; exit 1; }
log "包目录:$PACKAGE_DIR"
# ---- 2. 文件清单 ----
log "文件清单:"
for required in OpenDance.exe OpenDance.pck README.txt THIRD_PARTY.md contracts/VERSION; do
if [ -f "$PACKAGE_DIR/$required" ]; then
printf ' ✓ %s\n' "$required"
else
fail "缺少 $required"
fi
done
# exe 与 pck 必须同名同目录,否则 Godot 找不到资源
if [ "$(basename "$PACKAGE_DIR/OpenDance.exe" .exe)" != "$(basename "$PACKAGE_DIR/OpenDance.pck" .pck)" ]; then
fail "exe 与 pck 主文件名不一致"
fi
# 不允许出现历史分卷残留
if find "$PACKAGE_DIR" -maxdepth 1 -name "*.part[0-9]*" | grep -q .; then
fail "包内出现 .partN 分卷残留(发布规则要求单个完整包)"
fi
# ---- 3. exe 的 PE 版本资源 ----
log "产物 exe 的 PE 版本资源:"
if [ -f "$PACKAGE_DIR/OpenDance.exe" ]; then
bash "$PROJECT_DIR/build/check-pe-version.sh" "$PACKAGE_DIR/OpenDance.exe" "$EXPECTED" \
|| fail "exe 的 PE 版本资源不是 v$EXPECTED(Windows 属性页会显示错版本)"
fi
# ---- 4/5. 用 Godot 加载包内 pck ----
GODOT_BIN="${GODOT_BIN:-}"
if [ -z "$GODOT_BIN" ]; then
for candidate in godot godot4 Godot; do
if command -v "$candidate" >/dev/null 2>&1; then GODOT_BIN="$candidate"; break; fi
done
fi
if [ -z "$GODOT_BIN" ]; then
fail "未找到 Godot(本端验收需要与本项目同版本的 Godot 4.7.x;用 GODOT_BIN=<路径> 指定)"
echo "(其余检查已完成,见上)" >&2
exit 1
fi
log "Godot:$GODOT_BIN($("$GODOT_BIN" --version 2>/dev/null | head -1))"
PCK="$PACKAGE_DIR/OpenDance.pck"
if [ -f "$PCK" ]; then
log "包内 pck 启动检查($BOOT_FRAMES 帧,无头)…"
BOOT_LOG="$WORK_DIR/pck-boot.log"
timeout 300 "$GODOT_BIN" --headless --main-pack "$PCK" --quit-after "$BOOT_FRAMES" >"$BOOT_LOG" 2>&1
BOOT_STATUS=$?
if [ "$BOOT_STATUS" -ne 0 ]; then
fail "包内 pck 启动退出码 $BOOT_STATUS"
tail -20 "$BOOT_LOG" | sed 's/^/ /'
elif grep -qE "SCRIPT ERROR|Parse Error|Compile Error" "$BOOT_LOG"; then
fail "包内 pck 启动出现脚本错误"
grep -E "SCRIPT ERROR|Parse Error|Compile Error" "$BOOT_LOG" | head -10 | sed 's/^/ /'
else
echo " ✓ 包内 pck 启动 $BOOT_FRAMES 帧无错误(退出码 0)"
fi
# 产物内不得混入开发/验收工具(T-WIN-023):export_filter="all_resources" 只看
# exclude_filter,加了新的工具目录却忘了排除,就会把探针(乃至**故意不合规**的
# 坏服务端夹具)与向量文件打进 pck —— 实测曾占 pck 的 54%(315,464 B → 145,400 B)。
log "产物内开发残留检查(build/ tests/ _scratch/ *.md)…"
LIST_JSON="$WORK_DIR/pck-entries.json"
if timeout 120 "$GODOT_BIN" --headless --main-pack "$PCK" \
--script "$PROJECT_DIR/build/integration/list-pck.gd" \
-- --json --out "$LIST_JSON" >"$WORK_DIR/pck-list.log" 2>&1; then
node -e '
const fs = require("node:fs");
const r = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
console.log(` ✓ pck 共 ${r.count} 项,无开发残留(build/ tests/ _scratch/ *.md)`);
' "$LIST_JSON" || fail "pck 残留清单解析失败"
else
fail "产物里混入了开发/验收残留(见 $WORK_DIR/pck-list.log)"
grep -E "✗|·" "$WORK_DIR/pck-list.log" | head -10 | sed 's/^/ /'
fi
log "包内运行时探针(版本号 / 界面显示值 / 契约表 / 资源可加载)…"
PROBE="$PROJECT_DIR/build/probe-release.gd"
PROBE_LOG="$WORK_DIR/probe.log"
# 探针自己断言并 quit(0/1),因此以**退出码**为准;日志单独落盘再打印,
# 避免把「grep 的退出码」误当成「探针的退出码」。
if timeout 300 "$GODOT_BIN" --headless --main-pack "$PCK" --script "$PROBE" \
-- --expect-version "$EXPECTED" --snapshot "$PACKAGE_DIR/contracts/VERSION" \
>"$PROBE_LOG" 2>&1; then
PROBE_OK=1
else
PROBE_OK=0
fi
grep -vE "^Godot Engine|^$" "$PROBE_LOG" | sed 's/^/ /'
[ "$PROBE_OK" = 1 ] || fail "包内运行时探针未通过"
fi
echo ""
if [ "$FAILED" -ne 0 ]; then
echo "============================================================"
echo " 发布包级验收失败(工作目录:$WORK_DIR)"
echo "============================================================"
exit 1
fi
echo "============================================================"
echo " 发布包级验收全部通过(工作目录:$WORK_DIR)"
echo "============================================================"