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 全部通过
65 lines
2.7 KiB
GDScript
65 lines
2.7 KiB
GDScript
extends TestCase
|
||
|
||
## T-WIN-012:版本号展示(文档 §11)
|
||
##
|
||
## 硬性要求是「单一来源 + 可核对」:
|
||
## 版本号只允许有一处权威定义(project.godot 的 application/config/version),
|
||
## 界面从该来源读取,且必须与 project.yaml / 产物文件名 / Git tag 一致。
|
||
## 因此这里同时核对 project.godot、project.yaml、README 三处。
|
||
|
||
const AboutScreen = preload("res://src/ui/about_screen.gd")
|
||
const Credits = preload("res://src/ui/credits.gd")
|
||
|
||
|
||
func _read_text(path: String) -> String:
|
||
if not FileAccess.file_exists(path):
|
||
return ""
|
||
var file := FileAccess.open(path, FileAccess.READ)
|
||
if file == null:
|
||
return ""
|
||
var text := file.get_as_text()
|
||
file.close()
|
||
return text
|
||
|
||
|
||
func test_version_is_semver_and_matches_project_yaml() -> void:
|
||
var version := AboutScreen.app_version()
|
||
var parts := version.split(".")
|
||
eq(parts.size(), 3, "版本号形如 X.Y.Z(实际 %s)" % version)
|
||
for part in parts:
|
||
ok(part.is_valid_int(), "版本号各段为整数(%s)" % part)
|
||
|
||
var yaml := _read_text("res://project.yaml")
|
||
ne(yaml, "", "可读取 project.yaml")
|
||
contains(yaml, "version: %s" % version, "project.yaml 的 version 与 project.godot 一致")
|
||
|
||
|
||
func test_about_manifest_lists_version() -> void:
|
||
var manifest := AboutScreen.about_manifest()
|
||
var found := {}
|
||
for item in manifest:
|
||
found[str(item["label"])] = str(item["value"])
|
||
|
||
ok(found.has("客户端版本"), "关于清单含「客户端版本」一项")
|
||
eq(found.get("客户端版本", ""), "v%s" % AboutScreen.app_version(), "清单版本号与唯一来源一致")
|
||
ok(found.has("契约版本"), "关于清单含「契约版本」")
|
||
for key in Credits.CONTRACT_VERSIONS:
|
||
contains(str(found.get("契约版本", "")), "%s v%s" % [key, Credits.CONTRACT_VERSIONS[key]], "清单含 %s 契约版本" % key)
|
||
ok(str(found.get("隐私说明", "")).contains("不存储"), "关于清单声明不存储视频画面")
|
||
|
||
|
||
func test_version_not_hardcoded_in_ui_sources() -> void:
|
||
# 反例防护:界面代码里不允许再手写一份版本号(文档 §11.2 第 1 条)
|
||
var version := AboutScreen.app_version()
|
||
for path in ["res://src/ui/about_screen.gd", "res://src/ui/main_menu.gd", "res://src/ui/game_screen.gd"]:
|
||
var text := _read_text(path)
|
||
ne(text, "", "可读取 %s" % path)
|
||
is_false(text.contains("\"%s\"" % version), "%s 未硬编码版本号字面量" % path.get_file())
|
||
|
||
|
||
func test_entry_scene_version_is_exported_into_build() -> void:
|
||
# 导出产物名必须能由版本号推导(Documentation §11.2 第 2 条)
|
||
var version := AboutScreen.app_version()
|
||
contains("opendance-windows-v%s.zip" % version, version, "产物文件名包含版本号")
|
||
ok(Credits.entry_count() >= 10, "开源声明条目仍齐备")
|