Files
opendance-windows/build/package-release.sh
T
zijunchiang 1c5f28cd41 test(codec): 关键点帧跨实现一致性验收——包内 GDScript 解码器 × Web 参考实现(T-WIN-021)
契约 §2 的参考实现是 opendance-web 的 keypoint-codec.ts(其 decodeBinaryFrame
注释就是「Windows 端参考实现」)。Android 已用金标准向量与它对齐;而本端的解码器
此前**只跟自己比过**(自己编码、自己解码),两个方向共用同一份错误假设也能一直绿。

新增:
  * build/integration/frame-vectors.json —— 由参考实现生成并锁定的向量
    (12 用例:i16 最小值 -32768、i16 最大值、±1 全量程、置信度 0/128/255、
    flags 低 3 位组合、frame_id 0xFFFFFFFF 与 0x80000000、取整边界)。
  * build/integration/probe-frame-vectors.gd —— 用**包内** KeypointFrame 解码同一批字节,
    与参考解码结果**逐值精确比对**(坐标/置信度先收敛到 float32 再比精确相等,
    因为本端点存 PackedVector3Array、置信度存 PackedFloat32Array)。
  * build/accept-frame-vectors.sh —— 编排:用参考实现重新生成向量并与锁定文件比对
    (参考实现变了却不同步向量即失败)→ 解包 → 包内解码比对 → 报告核对。

实测(**已发布**的 v0.2.3 资产重新下载后跑):
  ✓ 锁定的向量与参考实现重新生成的结果逐字节一致
  ✓ 解码用例 12/12,逐点比对 396 个,解码差异 0 项
  ✓ 编码器差异 1 项,且全部属于「量化前恰好 .5」的取整边界
  关键点帧跨实现一致性验收全部通过

已测量并归类的一处差异(非缺陷):JS Math.round 与 Kotlin(生产发送方)Math.round
对 -2.5 取 -2(半数朝 +∞),本端 roundi 取 -3(半数远离零)。Windows 是接收方,
encode() 只在自测/协议回归里用,故对产品无影响;脚本硬性要求这类差异只能出现在
取整边界用例上,否则视为新问题。若将来本端要当发送方,把 roundi 换成 floor(x+0.5) 即可。

踩坑(两处「看着通过其实没测到」,都写进任务文档):
  1. 生成器第一版把「喂进编码器的输入」当成 points 写进向量 → 比对变成「比编码量化」,
     靠抽查数值才发现;现在向量同时含 inputPoints / f32InputPoints / f32Hex / points。
  2. 编码器比对一开始用 float64 输入编出的 hex 当基准(正数恰好 .5 那例整例 99 字节不同),
     实际只是输入从 float64 收敛到 float32 的量化差;改用同输入的 f32Hex 基准后,
     差异收敛到唯一一个真正的取整边界用例。教训:跨实现比对必须先统一输入。

package-release.sh 结尾把四条发布前验收一次列全;verify.sh 全绿(774 通过 / 0 失败)。
2026-09-17 05:52:11 +08:00

187 lines
8.9 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.
#!/bin/bash
# ============================================================
# OpenDance Studio —— Windows 客户端发布打包(T-WIN-014)
#
# 产物(文档 §5.3 / §5.7):
# opendance-windows-vX.Y.Z.zip ← **单个完整包,禁止 .partN 分卷**
# SHA256SUMS ← 裸文件名,下载目录内 sha256sum -c 直接可用
#
# 包内结构:
# opendance-windows-vX.Y.Z/
# OpenDance.exe
# OpenDance.pck
# THIRD_PARTY.md
# README.txt README.md
# contracts/
#
# 为什么强调"单个完整包":本项目的 Gitea 前置 IIS 默认
# maxAllowedContentLength = 30,000,000 字节(≈28.6 MiB),
# 30.5 MB / 40 MB 的上传实测直接 HTTP 413。当时的应对是把整包拆成
# zip.part0/1/2(每卷 13 MiB)——这正是"分卷有问题"的根因:
# 用户拿到的是无法直接解压的碎片,还要自己拼接。
# 现在改为:整包上传;若超过上限则**直接报错退出**,并提示运维放宽
# IIS/ARR 上限(做法见 opendance-web/deploy/proxy/README.md),
# 绝不静默退回分卷。
#
# 用法:bash build/package-release.sh [版本号]
# ============================================================
set -euo pipefail
PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
cd "$PROJECT_DIR"
VERSION="${1:-$(sed -n 's/^version:[[:space:]]*//p' project.yaml | head -1)}"
VERSION="${VERSION:-0.2.0}"
NAME="opendance-windows-v${VERSION}"
OUT_DIR="$PROJECT_DIR/dist-release"
STAGE="$OUT_DIR/$NAME"
# Gitea 前置 IIS 的实测上传上限(文档 §5.7 实测结论)
UPLOAD_LIMIT_BYTES=30000000
log() { printf '\033[1;34m[package]\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33m[warn]\033[0m %s\n' "$*" >&2; }
die() { printf '\033[1;31m[error]\033[0m %s\n' "$*" >&2; exit 1; }
# ---- 0. 版本号一致性(文档 §11.2:产物名 / tag / 界面展示必须一致)----
bash "$PROJECT_DIR/build/check-version.sh" "$VERSION" || die "版本一致性校验失败,先统一 project.godot / project.yaml / 发布版本号"
EXE="$PROJECT_DIR/build/windows/OpenDance.exe"
PCK="$PROJECT_DIR/build/windows/OpenDance.pck"
[ -f "$EXE" ] || die "未找到 $EXE,请先执行 bash build/export-windows.sh release"
[ -f "$PCK" ] || die "未找到 $PCK,请先执行 bash build/export-windows.sh release"
# ---- 1. 源产物自检:PE 头 + pck 魔数 ----
MAGIC=$(head -c 2 "$EXE" | od -An -tx1 | tr -d ' \n')
[ "$MAGIC" = "4d5a" ] || die "$EXE 不是合法的 Windows PE 文件(头 $MAGIC)"
PCK_MAGIC=$(head -c 4 "$PCK" | tr -d '\0')
[ "$PCK_MAGIC" = "GDPC" ] || die "$PCK 不是合法的 Godot 资源包(头 '$PCK_MAGIC')"
log "源产物校验通过:exe PE 头 MZ | pck 头 GDPC"
# 产物 exe 的 PE 版本资源必须是本次发布版本 —— Windows「属性 → 详细信息」显示的就是它。
# v0.2.1 曾带着 0.1.0 发出去(导出预设里是硬编码的旧值,且没有任何环节复核产物)。
bash "$PROJECT_DIR/build/check-pe-version.sh" "$EXE" "$VERSION" \
|| die "$EXE 的 PE 版本资源不是 v$VERSION;先跑 bash build/sync-export-version.sh $VERSION 再重新导出"
log "产物 PE 版本资源校验通过(=$VERSION)"
# ---- 2. 整理产物 ----
log "整理产物到 $STAGE …"
rm -rf "$STAGE"
mkdir -p "$STAGE"
cp "$EXE" "$STAGE/OpenDance.exe"
cp "$PCK" "$STAGE/OpenDance.pck"
cp THIRD_PARTY.md README.txt "$STAGE/"
if [ -f README.md ]; then cp README.md "$STAGE/"; fi
cp -r contracts "$STAGE/contracts"
# ---- 3. 打包(单个完整包) ----
ZIP="$OUT_DIR/$NAME.zip"
# 清理历史分卷,避免 old .partN 被误当成本次产物上传
rm -f "$OUT_DIR/$NAME.zip".part* "$OUT_DIR/$NAME.zip" "$OUT_DIR/SHA256SUMS"*
# 压缩方式:默认 deflate(Windows 资源管理器可直接解压)。
# ZIP_METHOD=lzma 时改用 LZMA —— 包体从约 38 MB 降到约 26 MB,可绕过
# 「Gitea 站点 IIS 的 maxAllowedContentLength = 30,000,000 字节」这道闸
# (见 opendance-web/deploy/proxy/README.md 第九节)。
# **代价**:Windows 10 的资源管理器不支持 LZMA(method 14) 的 zip,用户必须用
# 7-Zip / WinRAR / PeaZip 解压(Win11 24H2+ 起资源管理器原生支持 7z/zip 变体,
# 但本项目未在 Windows 上实测)。因此只有在 IIS 上限确实无法放开时才用 LZMA。
ZIP_METHOD="${ZIP_METHOD:-deflate}"
log "生成单个完整包 $NAME.zip(压缩方式:$ZIP_METHOD)…"
case "$ZIP_METHOD" in
deflate)
( cd "$OUT_DIR" && zip -qr "$NAME.zip" "$NAME" )
;;
lzma)
command -v 7z >/dev/null 2>&1 || die "ZIP_METHOD=lzma 需要 7z(Debian/Ubuntu: apt install p7zip-full)"
( cd "$OUT_DIR" && rm -f "$NAME.zip" && 7z a -tzip -mm=LZMA -mx=9 "$NAME.zip" "$NAME" >/dev/null )
;;
*)
die "未知 ZIP_METHOD=$ZIP_METHOD(可选:deflate / lzma)"
;;
esac
# LZMA 包额外做一次完整性测试与解压自检(zip/unzip 对 method 14 支持不一致)
if [ "$ZIP_METHOD" = "lzma" ]; then
7z t "$ZIP" >/dev/null || die "LZMA 包完整性测试失败(7z t)"
log "LZMA 包完整性测试通过(7z t)"
fi
# ---- 4. 完整性自检:解压后必须含 exe + pck(同目录)且 exe 仍是合法 PE ----
CHECK_DIR=$(mktemp -d)
trap 'rm -rf "$CHECK_DIR"' EXIT
if [ "$ZIP_METHOD" = "lzma" ]; then
7z x -y -o"$CHECK_DIR" "$ZIP" >/dev/null
else
unzip -q "$ZIP" -d "$CHECK_DIR"
fi
[ -f "$CHECK_DIR/$NAME/OpenDance.exe" ] || die "包内缺少 OpenDance.exe"
[ -f "$CHECK_DIR/$NAME/OpenDance.pck" ] || die "包内缺少 OpenDance.pck(必须与 exe 同目录)"
[ -f "$CHECK_DIR/$NAME/README.txt" ] || die "包内缺少 README.txt"
[ -f "$CHECK_DIR/$NAME/THIRD_PARTY.md" ] || die "包内缺少 THIRD_PARTY.md"
[ -d "$CHECK_DIR/$NAME/contracts" ] || die "包内缺少 contracts/ 契约快照"
CHECK_MAGIC=$(head -c 2 "$CHECK_DIR/$NAME/OpenDance.exe" | od -An -tx1 | tr -d ' \n')
[ "$CHECK_MAGIC" = "4d5a" ] || die "解压后的 OpenDance.exe 不是合法 PE 文件(头 $CHECK_MAGIC)"
CHECK_PCK=$(head -c 4 "$CHECK_DIR/$NAME/OpenDance.pck" | tr -d '\0')
[ "$CHECK_PCK" = "GDPC" ] || die "解压后的 OpenDance.pck 不是合法 Godot 资源包"
log "整包完整性自检通过(解压后 exe + pck 同目录,PE/GDPC 头均合法)"
# 再核一次**解压后**的 exe 版本资源:确认打包/解包环节没有换错文件
bash "$PROJECT_DIR/build/check-pe-version.sh" "$CHECK_DIR/$NAME/OpenDance.exe" "$VERSION" \
|| die "包内 exe 的 PE 版本资源不是 v$VERSION"
log "包内 exe 的 PE 版本资源 = v$VERSION"
# pck 与 exe 必须同名同目录,否则 Godot 找不到资源
[ "$(basename "$CHECK_DIR/$NAME/OpenDance.exe" .exe)" = "$(basename "$CHECK_DIR/$NAME/OpenDance.pck" .pck)" ] \
|| die "exe 与 pck 主文件名不一致,导出产物不可运行"
# ---- 5. SHA256SUMS(裸文件名,下载目录内可直接校验) ----
( cd "$OUT_DIR" && sha256sum "$NAME.zip" > SHA256SUMS )
log "已生成 $OUT_DIR/SHA256SUMS"
# ---- 6. 体积与上传上限对比 ----
SIZE_BYTES=$(stat -c %s "$ZIP")
SIZE_HUMAN=$(du -h "$ZIP" | cut -f1)
log "包体 $SIZE_HUMAN($SIZE_BYTES 字节)| Gitea/IIS 实测上限 $UPLOAD_LIMIT_BYTES 字节"
if [ "$SIZE_BYTES" -gt "$UPLOAD_LIMIT_BYTES" ]; then
warn "整包超过当前 Gitea 前置 IIS 的上传上限,上传会返回 HTTP 413。"
warn "请让运维放宽上限后重传(做法见 opendance-web/deploy/proxy/README.md):"
warn " IIS requestFiltering/requestLimits@maxAllowedContentLength = 268435456"
warn " ARR maxRequestBytes >= 268435456"
warn " Gitea app.ini [attachment] MAX_SIZE >= 256MB"
warn " nginx(若前置)client_max_body_size 256m;"
warn "本脚本**不会**回退为 .partN 分卷——分卷包无法直接解压,是历史缺陷。"
warn "若暂时无法放开 IIS 上限,可用 ZIP_METHOD=lzma 重跑本脚本:包体约 26 MB 可绕过 30 MB 闸,"
warn "但 Windows 10 资源管理器不能直接解压 LZMA zip,必须用 7-Zip / WinRAR(README 中已注明)。"
else
log "低于上限,可直接作为 Release 资产上传。"
fi
echo ""
echo "产物:"
echo " $ZIP"
echo " $OUT_DIR/SHA256SUMS"
if ( cd "$OUT_DIR" && sha256sum -c SHA256SUMS --status ); then
echo " sha256 校验:OK"
else
die "sha256 自校验失败"
fi
echo ""
if [ "$ZIP_METHOD" = "lzma" ]; then
7z l "$ZIP" | tail -14
else
unzip -l "$ZIP" | sed -n '3,14p'
fi
echo ""
echo "分发:上传 $NAME.zip 与 SHA256SUMS 两个资产;用户下载后"
echo " sha256sum -c SHA256SUMS && unzip $NAME.zip 即可直接运行 OpenDance.exe。"
echo ""
echo "下一步(文档 §7.5 / §7.6:发布前必须做的三条产物级验收):"
echo " GODOT_BIN=/path/to/godot bash build/accept-release.sh $ZIP # 产物自证"
echo " GODOT_BIN=/path/to/godot bash build/accept-ws.sh $ZIP # WebSocket 契约一致性"
echo " GODOT_BIN=/path/to/godot bash build/accept-frame-vectors.sh $ZIP # 关键点帧跨实现一致性"
echo " API_BASE=http://127.0.0.1:3000 GODOT_BIN=... bash build/accept-integration.sh $ZIP # 跨端(需已部署服务端)"