Files
opendance-windows/README.txt
T
zijunchiang c3a227c6df feat(net): 连接保活 / 设备去重 / 连接状态机(T-WIN-015,契约 websocket-keypoints 0.2.0)
与 Android 端 T-AND-011「两端一起改」:契约先在 opendance-web 修改并提升到
websocket-keypoints 0.2.0(向后兼容:子协议名与二进制帧 version 均不变,
只新增可选控制字段与一种新控制类型),再同步三端快照。

修掉本端的 3 处根因/放大器:
1. 静默超时 5s 过短且未声明,客户端无从得知。OkHttp 的协议层 ping 由底层库
   自动应答、**不暴露给应用层**,无法刷新 last_seen_ms,于是"画面里没人"时
   每 5 秒必然踢一次连接 -> 客户端重连 -> 再被踢。
   - CLIENT_TIMEOUT_MS 5s -> 15s,新增 HEARTBEAT_INTERVAL_MS = 5s;
   - welcome_payload() 在 welcome 中声明 silentTimeoutMs / heartbeatIntervalMs,
     config 广播同样带上(契约 §1.1:心跳必须 < 静默超时的一半,5000*2 < 15000);
   - 处理应用层 ping 并回 pong(回显 sentAtMs + serverAtMs,供对端算 RTT)。
2. 反复重连被计成多台设备。按契约 §5 用 hello.deviceId 去重:同一设备只保留
   最新连接,旧连接以 close(1000) 关闭——刻意用 1000,因为对端按契约 §7
   收到 1000 不会重连,否则会形成"挤掉-重连-再挤掉"的循环;
   新增 device_count() 与 device_replaced 信号,界面一律显示去重后的设备数。
3. 只有 handshaken 布尔量,日志看不出卡在哪一步。按契约 §8 逐客户端维护与
   Android 端同名的状态(connecting -> handshaking -> streaming -> degraded,
   主动停止/超时/被替换为 stopped),并记录 close code/reason、协商子协议、
   连接/握手/首帧时间;degraded 保持时长做成可覆盖字段便于测试。

验收测试(tests/test_pose_server.gd 新增 7 项,总计 720 通过 / 0 失败):
welcome 声明保活参数、ping->pong 回显、同 deviceId 去重(旧连接 1000 关闭且
设备数仍为 1)、8 个状态名与契约一致、状态随握手/首帧推进到 streaming、
丢帧进入 degraded 后恢复、snapshot 暴露设备数与保活参数。

同时更新 README/README.txt/tasks 与 Credits 的契约版本(0.2.0)。
2026-09-17 01:11:48 +08:00

314 lines
14 KiB
Plaintext
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.
================================================================
OpenDance Studio -- Windows 客户端 v0.2.0 使用说明
================================================================
摄像头跳舞评分系统的核心体验端:接收 Android 辅助 App 通过 WebSocket
推送的 33 点骨骼关键点,驱动 VRM 角色,并与 Web 服务端下发的参考动作
做实时 DTW 评分。
本客户端只接收关键点坐标,不接收、不存储任何视频画面。
--
一、快速开始
--
1. 解压后得到:
OpenDance.exe 主程序
OpenDance.pck 资源包(必须与 exe 同目录)
README.txt 本文件
README.md 同内容 Markdown 版
THIRD_PARTY.md 开源声明
contracts/ 契约快照(只读;客户端不直连数据库)
2. 先启动 Web 管理服务端(opendance-web),确保能访问其 API。
3. 双击 OpenDance.exe 启动。
4. 在主界面确认「服务端地址」(默认 http://127.0.0.1:3000),
点击「保存并测试」;顶部应显示「服务端正常 | DB...」。
5. 点击「刷新目录」,左侧会出现已发布的曲目、动作、模型与场景。
6. 在 Android 手机上启动「OpenDance 辅助 App」,填入本机显示在
主界面上的接收地址(形如 ws://<本机局域网地址>:8765/pose)并连接。
连接成功后主界面会提示「已连接 N 台设备」。
7. 依次选择曲目 -> 动作 -> 模型 -> 场景,点击「开始跟练」。
场景可以留空不选:服务端没有可用场景时会自动回退到内置空场景,
界面会提示但不影响开始跟练。
--
二、初次部署(从零跑起来)
--
本节面向第一次拿到源码、要从零把客户端跑起来的人;只运行已打包好的
OpenDance.exe 的用户看第一节即可。
1. 前置条件
- 操作系统:Windows 10/11(x86_64)。开发与导出可在 Windows 或 Linux。
- Godot 4.7.x 标准版(GDScript)。不需要 .NET / C# 版。
- 渲染后端为 GL Compatibility,运行需 OpenGL 3.3 级别的显卡与驱动。
- 导出模板:仅打包导出时需要,必须与编辑器完全同版本。
- 磁盘:编辑器 + 导出模板数百 MB;导出产物 exe 约 104 MB + pck 约 107 KB。
- 网络:能访问 Web 服务端 API(默认 TCP 3000);与手机同一局域网,
并放行本端 TCP 8765 入站。
2. 获取代码
git clone https://<Gitea 地址>/<owner>/opendance-windows
cd opendance-windows
3. 编辑器内首次运行
- Godot 项目管理器 -> 导入 -> 选择 project.godot。
- 首次导入会执行资源导入(生成 .godot/),可能等待数十秒到数分钟;
不要在导入中途关闭编辑器。
- 主场景已配置为 res://scenes/main.tscn,按 F5 运行。
- 首次运行会生成:
%APPDATA%\Godot\app_userdata\OpenDance Studio\opendance.cfg
%APPDATA%\Godot\app_userdata\OpenDance Studio\logs\godot.log
4. 配置服务端地址
主界面顶部「服务端地址」填好后点「保存并测试」,配置字段名为 api_base_url:
http://<服务器地址>:3000
http://192.0.2.10:3000
注意:客户端只访问 API 端口(默认 3000);管理端页面在管理端口
(默认 3001),客户端不使用。保存后点「刷新目录」。
5. 与另两端的联通
- Android 辅助 App -> 本端 WebSocket:本端是服务端,默认监听
0.0.0.0:8765,路径 /pose,子协议 opendance.pose.v1。Android 端填写
本端主界面显示的「接收地址」,形如 ws://<本机局域网地址>:8765/pose。
本端可在 opendance.cfg 改 listen_host / listen_port / ws_subprotocol,
改后点「重新连接接收服务」生效。
- Web 服务端:需先在服务端发布曲目 / 动作 / 模型,动作还须完成关键点
预处理,客户端才有可选内容。
6. 打包导出
# Linux(推荐)
GODOT_BIN=/path/to/godot bash build/export-windows.sh release
# Windows(兜底)
powershell -ExecutionPolicy Bypass -File build\export-windows.ps1
产物在 build/windows/: OpenDance.exe(约 104 MB, PE 头 MZ 校验通过)
+ OpenDance.pck(约 107 KB,必须与 exe 同目录);可能附带
OpenDance.console.exe。
打成发布包:
bash build/package-release.sh 0.2.0
得到 dist-release/ 下两个资产:
opendance-windows-v0.2.0.zip <- 单个完整包,禁止 .partN 分卷
SHA256SUMS <- 裸文件名,可直接 sha256sum -c
包里含 OpenDance.exe、OpenDance.pck、THIRD_PARTY.md、README.txt、
README.md、contracts/。打包脚本会解压自检(exe + pck 必须同目录、
exe 的 PE 头为 MZ、pck 魔数为 GDPC)并打印包体与上传上限的对比。
注意:本项目 Gitea 前置 IIS 的默认上传上限是 30,000,000 字节
(约 28.6 MiB),超过会返回 HTTP 413。这时不要改成分卷,
请让运维放宽上限(做法见 opendance-web/deploy/proxy/README.md)。
7. 自检清单
1) 一键验证:GODOT_BIN=/path/to/godot bash build/verify.sh
预期末行「验证全部通过」。
2) 无头测试:$GODOT --headless --path . --script res://tests/run_tests.gd
预期「== 结果:687 通过 / 0 失败 ==」与「全部通过」(缺 VRM 素材或
服务端不可达时相关用例跳过,数字会减少)。
3) 启动冒烟:$GODOT --headless --path . --quit-after 180
预期无 SCRIPT ERROR / ERROR:,退出码 0。
4) 日志:%APPDATA%\Godot\app_userdata\OpenDance Studio\logs\godot.log
预期无 SCRIPT ERROR。
5) 服务端联通:主界面点「保存并测试」后顶部显示「服务端正常 | DB ...」。
8. 部署相关常见问题
Q: 模型加载失败:缺少骨骼
A: 模型无 Skeleton3D,或骨骼命名不在映射表内;换用标准 VRM(VRoid
Studio 导出)。人形覆盖率低于 60% 会提示「骨骼覆盖偏低」。
Q: 连不上服务端 / 显示「服务端不可达」
A: 确认 Web 服务端已启动,api_base_url 指向 http://<服务器地址>:3000
(不要填管理端口 3001);访问 http://<服务器地址>:3000/health 应返回 200。
Q: 导出失败:未找到导出模板
A: 安装与编辑器同版本的 export templates。Linux 放
~/.local/share/godot/export_templates/<版本>.stable/;
Windows 放 %APPDATA%\Godot\export_templates\<版本>.stable\。
Q: 接收服务启动失败(端口 8765 可能被占用)
A: 改 opendance.cfg 的 listen_port 后点「重新连接接收服务」,
并同步修改 Android 端填写的接收地址。
--
三、界面说明
--
主界面
- 四个选择器(2x2 栅格):曲目 / 动作(仅列出已完成预处理的动作)/
模型(VRM 优先)/ 场景(GLB + 物理参数)
- 服务端地址:可修改并即时测试
- 接收地址:Android 端要连接的 WebSocket 地址
- 「刷新目录」重新拉取已发布资源(含场景)
- 底部左侧展示所选动作的来源信息(来源类型、名称、作者、许可、链接)
- 底部中间展示所选场景的缩略图与物理参数摘要
(重力 / 摩擦 / 弹性 / 碰撞体来源 / 风);未选场景时说明将使用内置空场景
游戏界面
- 左侧 3D 视图:VRM 角色落在场景出生点并跟随关键点实时动作,
相机按场景配置取景(follow 跟随 / fixed 固定 / orbit 环绕)
- 场景物理在本端落地:重力(Area3D 覆盖,不改全局 ProjectSettings)、
风(每物理帧施加风力,阵风按正弦叠加)、地面摩擦与弹性、
GLB 碰撞体(mesh 三角网 / auto 命名约定 *_col、*_colonly)、环境光与雾
- 右侧 HUD:实时得分、等级、连击、动作进度、连接状态、来源信息、场景参数
结算界面
- 等级(S/A/B/C/D)、平均分与最终分、动作完成度、最高单帧、最长连击
- 「再来一次」/「返回主菜单」/「开源声明」
关于页面
- 「清单」逐行显示:客户端版本(vX.Y.Z)/ 运行引擎 / 契约版本 /
接收协议 / 接收地址 / 服务端地址 / 隐私说明
- 版本号唯一来源是 project.godot 的 application/config/version,与
project.yaml、导出产物名、Git tag、界面展示完全一致
- 完整可滚动的第三方开源项目声明
--
四、配置文件
--
首次运行会在以下位置生成配置文件,可直接用文本编辑器修改:
Windows: %APPDATA%\Godot\app_userdata\OpenDance Studio\opendance.cfg
可配置项:
api_base_url Web 服务端地址(默认 http://127.0.0.1:3000)
listen_host WebSocket 监听地址(默认 0.0.0.0)
listen_port WebSocket 监听端口(默认 8765)
ws_subprotocol 子协议(默认 opendance.pose.v1)
dtw_band DTW 带宽,单位帧(默认 15 ~ 0.5 秒抖动容忍)
score_tolerance 评分容差:平均每步代价达到该值即 0 分(默认 0.25)
mirror_preview 镜像预览
--
五、防火墙与网络
--
本客户端需要接受 Android 设备的入站连接:
- Windows 防火墙需放行 OpenDance.exe 的 **TCP 8765** 入站
- 手机与电脑需在同一局域网
- 手机端填写主界面显示的「接收地址」
若端口被占用,可修改 opendance.cfg 中的 listen_port 后重启。
--
六、评分说明
--
- 每帧特征 = 33 点 x (x,y,z) + 9 个关节角余弦 = 108 维
(与 Web 服务端预处理产物、Android 端推流结构完全一致)
- 置信度低于 0.5 的关键点不参与代价计算,避免遮挡导致误判
- 采用带约束 DTW(|i-j| <= band)对齐,能吸收一定的节奏快慢差异
- 得分 = clamp(1 - 平均代价 / 容差, 0, 1) x 100,并做指数平滑
- 等级:S >= 95, A >= 85, B >= 70, C >= 55, D 其余
--
七、从源码构建
--
前置:Godot 4.7.x + 对应版本的 export templates
# Linux 上直接导出 Windows 可执行文件(推荐)
GODOT_BIN=/path/to/godot bash build/export-windows.sh release
# Windows 上兜底导出
powershell -ExecutionPolicy Bypass -File build\export-windows.ps1
产物位于 build/windows/: OpenDance.exe + OpenDance.pck
(脚本会校验产物存在且为合法 PE 文件)
运行测试:
GODOT=/path/to/godot
$GODOT --headless --path. --import
$GODOT --headless --path. --script res://tests/run_tests.gd
$GODOT --headless --path. --quit-after 180
--
八、目录结构
--
project.godot 工程配置(autoload / 渲染 / 窗口)
export_presets.cfg 导出预设(必须在项目根目录)
project.yaml 任务清单与状态
DSH.md 本项目 DSH 执行约束
tasks/ 单任务说明书(T-WIN-001 ~ 015)
contracts/ 只读契约快照(来源 opendance-web;六项契约各自独立版本)
scenes/main.tscn 根节点
src/autoload/ AppConfig / CatalogStore / PoseReceiver / ScoreService
src/net/ 239 字节编解码、WebSocket 服务、REST 客户端
src/model/ VRM 加载、humanoid 骨骼映射
src/anim/ 关键点 -> 骨骼重定向
src/scene/ 场景实例化与物理落地(重力/风/碰撞/环境/机位)
src/score/ 特征提取、DTW、评分规则
src/ui/ 主界面 / 游戏界面 / 结算 / 关于 / 来源展示 / 场景参数
tests/ 无头测试框架与用例
build/ 导出脚本
--
九、常见问题
--
Q: 主界面显示「服务端不可达」
A: 检查 Web 服务端是否已启动,以及「服务端地址」是否正确(含端口)。
Q: 目录里没有可用的动作
A: 动作必须先在管理端完成「预处理」(BVH -> 关键点序列)并「审核通过」,
才会出现在客户端。
Q: 模型加载失败 / 动作不完整
A: 游戏界面会显示「人形覆盖率」。低于 60% 说明模型的骨骼命名不被识别,
请换用标准 VRM(VRoid Studio 导出)模型。
Q: 连上了手机但分数一直是 0
A: 确认手机端已开始推流;主界面的「查看接收状态」会显示累计帧数与丢帧数。
Q: 手机端反复重连 / 「已连接」数忽大忽小
A: 本端有 15 秒静默超时(契约 v0.2.0 1.1),并在 welcome/config 里声明
silentTimeoutMs=15000 与 heartbeatIntervalMs=5000;Android 端会按后者发
应用层 ping。若仍频繁断开,用「查看接收状态」看每台设备的状态与断开原因
(close code):1001 表示本端主动踢人或服务重启,0/1006 表示网络抖动。
同一设备的重复连接会按 deviceId 去重(旧连接以 1000 关闭,对端不重连),
所以「已连接 N 台」显示的是去重后的设备数,不是连接数。
Q: 选场景时提示「场景缺少 GLB 文件」或「已回退内置空场景」
A: 服务端该场景没有可下载的 GLB(转换失败或未发布)。这不影响开始跟练,
会使用内置空场景与默认物理参数;请让管理员在管理端「场景管理」重新上传。
Q: 场景里的角色一直往下掉
A: 场景没有可用碰撞体且配置为 colliders=auto/mesh。本端会自动生成兜底地面
(日志会记录「已生成兜底地面」);若场景显式配置 colliders=none,则按场景
作者意图不生成任何碰撞体,此时角色不会被地面接住。
Q: 关于页面的开源声明
A: 与 THIRD_PARTY.md、contracts/open-source-credits.md 三处保持一致。
--
十、许可
--
本客户端代码以 MIT 许可发布。第三方开源项目声明见 THIRD_PARTY.md。
未使用 SMPL 模型及其衍生拓扑,统一采用 MediaPipe 33 点拓扑。