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:
+62
-5
@@ -2,7 +2,13 @@ class_name AboutScreen
|
||||
extends Control
|
||||
|
||||
## T-WIN-010:关于页面 + 开源声明(可滚动)
|
||||
## T-WIN-012:关于清单展示当前版本号(文档 §11.1:Windows 端在「关于」清单)
|
||||
##
|
||||
## 文档 §8.3 要求 Windows 客户端与 Android App 的「关于」页展示同款声明。
|
||||
##
|
||||
## 版本号**唯一来源**是 project.godot 的 application/config/version
|
||||
## (与 project.yaml 的 version、导出产物名 opendance-windows-vX.Y.Z.zip、Git tag 一致),
|
||||
## 本文件不再手写一份(文档 §11.2 第 1 条硬性要求)。
|
||||
|
||||
const UiKit = preload("res://src/ui/ui_kit.gd")
|
||||
const Credits = preload("res://src/ui/credits.gd")
|
||||
@@ -14,6 +20,49 @@ func _init(on_back: Callable = Callable()) -> void:
|
||||
_on_back = on_back
|
||||
|
||||
|
||||
## 当前运行版本(构建期由 project.godot 注入,运行期读取)
|
||||
static func app_version() -> String:
|
||||
return str(ProjectSettings.get_setting("application/config/version", "0.0.0"))
|
||||
|
||||
|
||||
## 取 AppConfig 自动加载实例。测试在 `--script` 模式下没有 autoload,
|
||||
## 因此这里必须容错返回 null,而不是直接引用 AppConfig 造成解析期错误。
|
||||
static func _app_config() -> Node:
|
||||
var loop := Engine.get_main_loop()
|
||||
if loop is SceneTree:
|
||||
return (loop as SceneTree).root.get_node_or_null("AppConfig")
|
||||
return null
|
||||
|
||||
|
||||
static func _config_call(method: String, fallback: String) -> String:
|
||||
var cfg := _app_config()
|
||||
if cfg != null and cfg.has_method(method):
|
||||
return str(cfg.call(method))
|
||||
return fallback
|
||||
|
||||
|
||||
static func _config_property(name: String, fallback: String) -> String:
|
||||
var cfg := _app_config()
|
||||
if cfg != null:
|
||||
var value: Variant = cfg.get(name)
|
||||
if value != null:
|
||||
return str(value)
|
||||
return fallback
|
||||
|
||||
|
||||
## 「关于」清单的一行行内容:版本 / 引擎 / 契约 / 运行地址 / 隐私
|
||||
static func about_manifest() -> Array:
|
||||
return [
|
||||
{"label": "客户端版本", "value": "v%s" % app_version()},
|
||||
{"label": "运行引擎", "value": "Godot %s" % str(Engine.get_version_info().get("string", "?"))},
|
||||
{"label": "契约版本", "value": Credits.summary()},
|
||||
{"label": "接收协议", "value": "websocket-keypoints v%s(33 点 / 239 字节帧)" % Credits.version_of("websocket-keypoints")},
|
||||
{"label": "接收地址", "value": _config_call("listen_url", "ws://0.0.0.0:8765/pose")},
|
||||
{"label": "服务端地址", "value": _config_property("api_base_url", "http://127.0.0.1:3000")},
|
||||
{"label": "隐私说明", "value": "仅接收关键点,不接收、不存储任何视频画面"},
|
||||
]
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
|
||||
@@ -33,12 +82,20 @@ func _ready() -> void:
|
||||
root.add_child(header)
|
||||
|
||||
var info := UiKit.panel()
|
||||
var info_box := UiKit.vbox(2)
|
||||
info_box.add_child(UiKit.label("Windows 客户端 v%s" % ProjectSettings.get_setting("application/config/version", "0.1.0"), UiKit.FONT_HEADING))
|
||||
info_box.add_child(UiKit.muted("摄像头跳舞评分系统:接收 Android 辅助 App 通过 WebSocket 推送的 33 点骨骼关键点,驱动 VRM 角色并实时 DTW 评分。"))
|
||||
var info_box := UiKit.vbox(4)
|
||||
info_box.add_child(UiKit.heading("清单"))
|
||||
for item in about_manifest():
|
||||
var row := UiKit.hbox(8)
|
||||
var key := UiKit.label("%s:" % str(item["label"]), UiKit.FONT_SMALL, UiKit.COLOR_MUTED)
|
||||
key.custom_minimum_size = Vector2(96, 0)
|
||||
row.add_child(key)
|
||||
var value := UiKit.label(str(item["value"]), UiKit.FONT_SMALL)
|
||||
value.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
value.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
row.add_child(value)
|
||||
info_box.add_child(row)
|
||||
info_box.add_child(UiKit.spacer(4))
|
||||
info_box.add_child(UiKit.muted("契约版本:rest-api / data-models / skeleton-topology / websocket-keypoints / model-formats = v%s" % Credits.CONTRACT_VERSION))
|
||||
info_box.add_child(UiKit.muted("本客户端仅接收关键点,不接收、不存储任何视频画面。"))
|
||||
info_box.add_child(UiKit.muted("摄像头跳舞评分系统:接收 Android 辅助 App 通过 WebSocket 推送的 33 点骨骼关键点,驱动 VRM 角色并实时 DTW 评分。"))
|
||||
info.add_child(info_box)
|
||||
root.add_child(info)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user