feat(scene): 场景选择与物理落地 + 整包发布(T-WIN-012/013/014)

T-WIN-013:API 升级与场景选择
- 契约快照同步到 rest-api 0.5.0 / data-models 0.7.0 / database 0.4.0
- catalog_types.gd 新增 SceneConfig(物理/环境/出生点/相机)与 Scene;
  CatalogBundle 增加 scenes[];from_dict 按契约 7.1 再收敛一次
  (缺项默认、越界夹紧、非法枚举回退),与服务端 normalizeSceneConfig 同规则
- 新增 src/scene/scene_runtime.gd:GLB 载入 -> 碰撞体(mesh 三角网 /
  auto 命名约定 *_col、*_colonly / none 不兜底)-> 场景级重力(Area3D
  SPACE_OVERRIDE_REPLACE,不改全局 ProjectSettings)-> 风(每物理帧
  apply_central_force,阵风正弦叠加,支持 local 坐标系)-> 环境光与雾
  -> 出生点与相机(follow/fixed/orbit);无碰撞体时兜底地面
- CatalogStore 增加 scenes/default_scene/find_scene/ensure_scene_cached/
  fetch_thumbnail(场景缓存带 sha256 指纹,避免换包后仍用旧 GLB)
- 主界面改为 2x2 四个选择器(曲目/动作/模型/场景)+ 场景参数面板
  (缩略图 + 重力/摩擦/弹性/碰撞体/风摘要)
- 游戏界面载入场景并落地物理,角色落在出生点;场景缺失/下载失败/
  解析失败一律回退内置空场景并提示,不阻塞开始跟练

T-WIN-012:「关于」清单展示当前版本号
- 版本唯一来源为 project.godot 的 application/config/version(v0.2.0)
- about_manifest() 输出版本/引擎/契约/协议/地址/隐私清单;
  无头测试无 autoload,读取 AppConfig 走 get_node_or_null 容错
- Credits 的单个 CONTRACT_VERSION 常量改为 CONTRACT_VERSIONS 字典
  (六项契约各自独立递增),并双向核对 contracts/VERSION

T-WIN-014:发布必须是单个完整包
- build/package-release.sh 重写:只产出单个 zip + SHA256SUMS,
  清理历史 .partN,超限时告警而非回退分卷;新增源产物与解压后完整性
  自检(PE 头 MZ / pck 魔数 GDPC / exe+pck 同目录同名)与体积-上限对比
- deploy/proxy 相关的 Gitea IIS 256MB 上限说明见 opendance-web

测试:tests/test_scene_config.gd(12 例)+ tests/test_about.gd(4 例);
无头测试 687 通过 / 0 失败(10 个套件),verify.sh 全部通过
This commit is contained in:
2026-09-17 00:45:23 +08:00
parent 47e11af7d0
commit 5581cbe05d
27 changed files with 2031 additions and 85 deletions
+91 -15
View File
@@ -1,14 +1,27 @@
#!/bin/bash
# ============================================================
# OpenDance Studio —— Windows 客户端发布打包
# OpenDance Studio —— Windows 客户端发布打包(T-WIN-014)
#
# 产物(文档 §5.3):
# opendance-windows-v0.1.0.zip
# ├── OpenDance.exe
# ├── OpenDance.pck
# ├── THIRD_PARTY.md
# ├── README.txt
# └── contracts/ 契约快照
# 产物(文档 §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 [版本号]
# ============================================================
@@ -17,12 +30,17 @@ set -euo pipefail
PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
cd "$PROJECT_DIR"
VERSION="${1:-0.1.0}"
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; }
EXE="$PROJECT_DIR/build/windows/OpenDance.exe"
@@ -31,10 +49,14 @@ 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"
# 校验 PE 头
# ---- 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"
# ---- 2. 整理产物 ----
log "整理产物到 $STAGE …"
rm -rf "$STAGE"
mkdir -p "$STAGE"
@@ -42,14 +64,68 @@ 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"
log "生成压缩包…"
# ---- 3. 打包(单个完整包) ----
ZIP="$OUT_DIR/$NAME.zip"
# 清理历史分卷,避免 old .partN 被误当成本次产物上传
rm -f "$OUT_DIR/$NAME.zip".part* "$OUT_DIR/$NAME.zip" "$OUT_DIR/SHA256SUMS"*
log "生成单个完整包 $NAME.zip …"
( cd "$OUT_DIR" && zip -qr "$NAME.zip" "$NAME" )
log "完成:$OUT_DIR/$NAME.zip($(du -h "$OUT_DIR/$NAME.zip" | cut -f1))"
# ---- 4. 完整性自检:解压后必须含 exe + pck(同目录)且 exe 仍是合法 PE ----
CHECK_DIR=$(mktemp -d)
trap 'rm -rf "$CHECK_DIR"' EXIT
unzip -q "$ZIP" -d "$CHECK_DIR"
[ -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 头均合法)"
# 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 分卷——分卷包无法直接解压,是历史缺陷。"
else
log "低于上限,可直接作为 Release 资产上传。"
fi
echo ""
echo "产物内容:"
unzip -l "$OUT_DIR/$NAME.zip" | sed -n '4,20p'
echo "产物:"
echo " $ZIP"
echo " $OUT_DIR/SHA256SUMS"
if ( cd "$OUT_DIR" && sha256sum -c SHA256SUMS --status ); then
echo " sha256 校验:OK"
else
die "sha256 自校验失败"
fi
echo ""
echo "分发:把 zip 交给 Windows 用户,解压后运行 OpenDance.exe(.pck 必须同目录)。"
unzip -l "$ZIP" | sed -n '3,14p'
echo ""
echo "分发:上传 $NAME.zip 与 SHA256SUMS 两个资产;用户下载后"
echo " sha256sum -c SHA256SUMS && unzip $NAME.zip 即可直接运行 OpenDance.exe。"