docs(win): 任务说明、DSH 约束与交付文档
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
# DSH.md —— opendance-windows(OpenDance Studio 项目二:Windows 客户端)
|
||||
|
||||
## 0. 本文件的作用
|
||||
|
||||
本文件是 **opendance-windows 仓库内**的 DSH 执行约束。顶层说明书见
|
||||
《DSH 开发文档:OpenDance Studio》,二者冲突时以顶层文档为准。
|
||||
|
||||
## 1. 项目定位
|
||||
|
||||
Windows 客户端是核心体验载体:接收 Android 辅助 App 推送的 33 点骨骼关键点,
|
||||
驱动 VRM 角色,并与 Web 服务端下发的参考动作做实时 DTW 评分。
|
||||
|
||||
```
|
||||
Android 辅助 App ──239 字节关键点帧──▶ 本端(WebSocket 服务端 :8765)
|
||||
│
|
||||
Web 服务端 ──GET /api/catalog/export──────┤
|
||||
──GET /api/motions/:id/keypoints┘
|
||||
▼
|
||||
VRM 驱动 + DTW 评分
|
||||
```
|
||||
|
||||
**本端是 WebSocket 的「服务端」,Android 主动连入**(契约 websocket-keypoints.md §1)。
|
||||
|
||||
## 2. 技术栈
|
||||
|
||||
| 组件 | 选型 | 说明 |
|
||||
|---|---|---|
|
||||
| 引擎 | Godot 4.7.2(MIT) | 可在 Linux 上导出 Windows exe |
|
||||
| VRM 加载 | **Godot 内置 GLTFDocument** | 见 §6 的选型变更说明 |
|
||||
| 多格式导入 | Godot 内置 glTF 导入器 | FBX 由管理端转 BVH 后下发 |
|
||||
| 通信 | WebSocketPeer + HTTPRequest | 无第三方依赖 |
|
||||
| 评分 | DTW + 关节角度差异 | 纯 GDScript 实现 |
|
||||
| 渲染 | GL Compatibility | 兼容性最好,便于无 GPU 导出 |
|
||||
|
||||
## 3. 单次会话加载范围
|
||||
|
||||
允许加载:
|
||||
|
||||
1. 本文件 `DSH.md`
|
||||
2. `project.yaml`
|
||||
3. 当前 `tasks/xxx.md`
|
||||
4. 该任务文件中 `load` 列出的文件
|
||||
5. `contracts/` 中被明确引用的**锚点片段**
|
||||
|
||||
禁止加载:
|
||||
|
||||
- `opendance-web` / `opendance-android` 的任何源码
|
||||
- 非当前任务的 `tasks/*.md`
|
||||
- 完整 `contracts/` 目录(只引用锚点)
|
||||
- `.godot/` 导入缓存、`build/windows/` 导出产物、`_scratch/` 下的模型素材
|
||||
|
||||
## 4. 上下文预算
|
||||
|
||||
| 项 | 建议值 |
|
||||
|---|---|
|
||||
| 单任务文件数 | ≤ 6 |
|
||||
| 单任务代码行数 | ≤ 1500 |
|
||||
| 单任务 Token | ≤ 12K |
|
||||
| 契约引用 | 只引用锚点,不加载全文 |
|
||||
|
||||
## 5. 目录结构
|
||||
|
||||
```
|
||||
opendance-windows/
|
||||
├── project.godot # Godot 工程(autoload、渲染、窗口配置)
|
||||
├── export_presets.cfg # ★ 必须位于项目根目录(Godot 硬性要求)
|
||||
├── project.yaml / DSH.md
|
||||
├── tasks/ # 001~011 单任务说明书
|
||||
├── contracts/ # 只读契约快照(来源 opendance-web v0.1.0)
|
||||
├── scenes/main.tscn # 仅一个挂载 main.gd 的根节点
|
||||
├── src/
|
||||
│ ├── autoload/ # AppConfig / CatalogStore / PoseReceiver / ScoreService
|
||||
│ ├── net/ # 239 字节编解码、WebSocket 服务、REST 客户端、NDJSON 解析
|
||||
│ ├── model/ # VRM 加载、humanoid 骨骼名映射
|
||||
│ ├── anim/ # 关键点 → 骨骼重定向
|
||||
│ ├── score/ # 特征提取、DTW、评分规则
|
||||
│ └── ui/ # 主界面 / 游戏界面 / 结算 / 关于 / 来源展示
|
||||
├── tests/ # 无头测试框架 + 用例
|
||||
└── build/ # 导出脚本(sh / ps1)
|
||||
```
|
||||
|
||||
## 6. 界面构建方式与选型变更(重要)
|
||||
|
||||
**界面全部用 GDScript 代码构建,不手写 `.tscn`。**
|
||||
`.tscn` 是带 uid 与节点路径的手写格式,在没有编辑器的情况下极易出错且难以验证;
|
||||
用代码构建可随脚本一起被无头导入和单元测试覆盖。`scenes/main.tscn` 因此只保留一个根节点。
|
||||
|
||||
**VRM 加载改用 Godot 内置 glTF 导入器**(文档 §5.1 原选型为 godot-vrm GDExtension):
|
||||
|
||||
- `V-Sekai/godot-vrm` **不发布任何预编译产物**(无 GitHub Release,仅源码,
|
||||
需用 godot-cpp 本地编译原生库),这会同时破坏「Linux 上导出 Windows exe」的验收路径。
|
||||
- VRM 本身就是 glTF 2.0 二进制容器:实测内置导入器可完整解析 154 骨骼的 VRoid 模型,
|
||||
且 `GLTFState.json` 可直接读取 `extensions.VRMC_vrm` / `extensions.VRM` 判定版本。
|
||||
- humanoid 骨骼按名字映射(`src/model/humanoid_map.gd`),覆盖 VRoid / VRM 0.x / Mixamo 三种命名。
|
||||
|
||||
若日后接入 godot-vrm,只需替换 `VrmLoader.load_from_file`,对外接口不变。
|
||||
|
||||
## 7. 硬性约束
|
||||
|
||||
1. **绝不让视频帧离开设备**:本端只接收 33 个关键点的坐标,不接收也不存储图像。
|
||||
2. **契约先行**:改交互先改 `opendance-web` 的 `contracts/`,再回本仓库同步快照。
|
||||
3. **不假设骨骼轴向约定**:重定向基于「静止朝向 → 目标朝向」的最小旋转,
|
||||
`pose = parent_global_basis⁻¹ × desired` 这一合成公式由真实 VRM 实测标定,
|
||||
有回归测试守护,不得凭记忆改写。
|
||||
4. **上传/下载的模型不入库**:真实 VRM 样例仅放在 `_scratch/`(已 gitignore),
|
||||
版权原因不得提交。
|
||||
5. **不使用 SMPL 拓扑**,统一 MediaPipe 33 点。
|
||||
6. 关于页面与 `THIRD_PARTY.md` 必须与 `contracts/open-source-credits.md` 一致。
|
||||
|
||||
## 8. 验证(每任务执行)
|
||||
|
||||
```bash
|
||||
GODOT=/path/to/godot # Godot 4.7.x
|
||||
|
||||
# 1. 全部脚本编译通过(无输出即通过)
|
||||
$GODOT --headless --path . --import 2>&1 | grep -E "SCRIPT ERROR|Parse Error"
|
||||
|
||||
# 2. 无头单元测试 + 真实 API 集成(API 不可达时相关用例显式跳过)
|
||||
$GODOT --headless --path . --script res://tests/run_tests.gd
|
||||
|
||||
# 3. 应用可启动(autoload + UI 构建无报错)
|
||||
$GODOT --headless --path . --quit-after 180
|
||||
|
||||
# 4. Windows 导出(需先安装 export templates)
|
||||
bash build/export-windows.sh release
|
||||
```
|
||||
|
||||
## 9. 上下文超标时的拆分策略
|
||||
|
||||
| 超标原因 | 拆分方式 |
|
||||
|---|---|
|
||||
| 单任务涉及多个界面 | 按界面拆(主/游戏/结算/关于) |
|
||||
| 单文件过大 | 按类拆,先生成接口再分实现 |
|
||||
| 契约全文加载 | 改用锚点引用 |
|
||||
| 需要参考其他项目 | 只读契约,不读源码 |
|
||||
| UI 与逻辑耦合 | UI 只调用已定义接口(GameScreen 不做 DTW 计算) |
|
||||
| 测试与实现同任务 | 测试单独任务,共享接口摘要 |
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
================================================================
|
||||
OpenDance Studio —— Windows 客户端 v0.1.0 使用说明
|
||||
================================================================
|
||||
|
||||
摄像头跳舞评分系统的核心体验端:接收 Android 辅助 App 通过 WebSocket
|
||||
推送的 33 点骨骼关键点,驱动 VRM 角色,并与 Web 服务端下发的参考动作
|
||||
做实时 DTW 评分。
|
||||
|
||||
本客户端只接收关键点坐标,不接收、不存储任何视频画面。
|
||||
|
||||
----------------------------------------------------------------
|
||||
一、快速开始
|
||||
----------------------------------------------------------------
|
||||
|
||||
1. 解压后得到:
|
||||
OpenDance.exe 主程序
|
||||
OpenDance.pck 资源包(必须与 exe 同目录)
|
||||
README.txt 本文件
|
||||
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://192.168.x.x:8765/pose)并连接。
|
||||
连接成功后主界面会提示「已连接 N 台设备」。
|
||||
|
||||
7. 依次选择曲目 → 动作 → 模型,点击「开始跟练」。
|
||||
|
||||
----------------------------------------------------------------
|
||||
二、界面说明
|
||||
----------------------------------------------------------------
|
||||
|
||||
主界面
|
||||
- 三栏选择:曲目 / 动作(仅列出已完成预处理的动作)/ 模型(VRM 优先)
|
||||
- 服务端地址:可修改并即时测试
|
||||
- 接收地址:Android 端要连接的 WebSocket 地址
|
||||
- 「刷新目录」重新拉取已发布资源
|
||||
- 底部展示所选动作的**来源信息**(来源类型、名称、作者、许可、链接)
|
||||
|
||||
游戏界面
|
||||
- 左侧 3D 视图:VRM 角色跟随关键点实时动作,相机自动框住模型
|
||||
- 右侧 HUD:实时得分、等级、连击、动作进度、连接状态、来源信息
|
||||
|
||||
结算界面
|
||||
- 等级(S/A/B/C/D)、平均分与最终分、动作完成度、最高单帧、最长连击
|
||||
- 「再来一次」/「返回主菜单」/「开源声明」
|
||||
|
||||
关于页面
|
||||
- 版本与契约版本、隐私说明、完整可滚动的第三方开源项目声明
|
||||
|
||||
----------------------------------------------------------------
|
||||
三、配置文件
|
||||
----------------------------------------------------------------
|
||||
|
||||
首次运行会在以下位置生成配置文件,可直接用文本编辑器修改:
|
||||
|
||||
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,y,z) + 9 个关节角余弦 = 108 维
|
||||
(与 Web 服务端预处理产物、Android 端推流结构完全一致)
|
||||
- 置信度低于 0.5 的关键点不参与代价计算,避免遮挡导致误判
|
||||
- 采用带约束 DTW(|i−j| ≤ band)对齐,能吸收一定的节奏快慢差异
|
||||
- 得分 = clamp(1 − 平均代价 / 容差, 0, 1) × 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 ~ 011)
|
||||
contracts/ 只读契约快照(来源 opendance-web v0.1.0)
|
||||
scenes/main.tscn 根节点
|
||||
src/autoload/ AppConfig / CatalogStore / PoseReceiver / ScoreService
|
||||
src/net/ 239 字节编解码、WebSocket 服务、REST 客户端
|
||||
src/model/ VRM 加载、humanoid 骨骼映射
|
||||
src/anim/ 关键点 → 骨骼重定向
|
||||
src/score/ 特征提取、DTW、评分规则
|
||||
src/ui/ 主界面 / 游戏界面 / 结算 / 关于 / 来源展示
|
||||
tests/ 无头测试框架与用例
|
||||
build/ 导出脚本
|
||||
|
||||
----------------------------------------------------------------
|
||||
八、常见问题
|
||||
----------------------------------------------------------------
|
||||
|
||||
Q: 主界面显示「服务端不可达」
|
||||
A: 检查 Web 服务端是否已启动,以及「服务端地址」是否正确(含端口)。
|
||||
|
||||
Q: 目录里没有可用的动作
|
||||
A: 动作必须先在管理端完成「预处理」(BVH → 关键点序列)并「审核通过」,
|
||||
才会出现在客户端。
|
||||
|
||||
Q: 模型加载失败 / 动作不完整
|
||||
A: 游戏界面会显示「人形覆盖率」。低于 60% 说明模型的骨骼命名不被识别,
|
||||
请换用标准 VRM(VRoid Studio 导出)模型。
|
||||
|
||||
Q: 连上了手机但分数一直是 0
|
||||
A: 确认手机端已开始推流;主界面的「查看接收状态」会显示累计帧数与丢帧数。
|
||||
|
||||
Q: 关于页面的开源声明
|
||||
A: 与 THIRD_PARTY.md、contracts/open-source-credits.md 三处保持一致。
|
||||
|
||||
----------------------------------------------------------------
|
||||
九、许可
|
||||
----------------------------------------------------------------
|
||||
|
||||
本客户端代码以 MIT 许可发布。第三方开源项目声明见 THIRD_PARTY.md。
|
||||
未使用 SMPL 模型及其衍生拓扑,统一采用 MediaPipe 33 点拓扑。
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
# 第三方开源项目声明
|
||||
|
||||
**OpenDance Studio Windows 客户端 v0.1.0**
|
||||
|
||||
本软件使用了以下开源项目,特此声明。完整锚点见 `contracts/open-source-credits.md`
|
||||
(三端共用,任一端新增依赖须回到 `opendance-web` 更新该表)。
|
||||
客户端「关于」页面展示同一份声明(见 `src/ui/credits.gd`)。
|
||||
|
||||
## 1. MediaPipe
|
||||
|
||||
- 版权:Copyright 2023 Google LLC
|
||||
- 许可证:Apache License 2.0
|
||||
- 源链接:https://github.com/google-ai-edge/mediapipe
|
||||
- 用途:33 点骨骼拓扑定义(契约来源,**本端不包含其代码**)
|
||||
|
||||
## 2. Three.js
|
||||
|
||||
- 许可证:MIT
|
||||
- 源链接:https://github.com/mrdoob/three.js
|
||||
- 用途:Web 管理端 3D 预览
|
||||
|
||||
## 3. @pixiv/three-vrm
|
||||
|
||||
- 许可证:MIT
|
||||
- 源链接:https://github.com/pixiv/three-vrm
|
||||
- 用途:Web 管理端 VRM 加载
|
||||
|
||||
## 4. Assimp
|
||||
|
||||
- 许可证:BSD-3-Clause
|
||||
- 源链接:https://github.com/assimp/assimp
|
||||
- 用途:FBX/glTF 动作转换(可选外部依赖,在服务端侧使用)
|
||||
|
||||
## 5. Godot Engine
|
||||
|
||||
- 版权:Copyright Godot Engine contributors
|
||||
- 许可证:MIT
|
||||
- 源链接:https://github.com/godotengine/godot
|
||||
- 用途:**本客户端的运行引擎**,包括内置 glTF/VRM 导入器与 WebSocket 实现
|
||||
|
||||
## 6. godot-vrm
|
||||
|
||||
- 许可证:MIT
|
||||
- 源链接:https://github.com/V-Sekai/godot-vrm
|
||||
- 用途:VRM humanoid 扩展参考实现。
|
||||
**本客户端未使用其二进制**(该仓库不发布预编译产物),改为内置 glTF 导入器 +
|
||||
自实现的骨骼名映射;此处按契约表保留声明。
|
||||
|
||||
## 7. NestJS
|
||||
|
||||
- 许可证:MIT
|
||||
- 源链接:https://github.com/nestjs/nest
|
||||
- 用途:Web 管理服务端
|
||||
|
||||
## 8. TypeORM
|
||||
|
||||
- 许可证:MIT
|
||||
- 源链接:https://github.com/typeorm/typeorm
|
||||
- 用途:Web 管理服务端 ORM
|
||||
|
||||
## 9. OkHttp
|
||||
|
||||
- 许可证:Apache License 2.0
|
||||
- 源链接:https://github.com/square/okhttp
|
||||
- 用途:Android 辅助 App 通信
|
||||
|
||||
## 10. bvh
|
||||
|
||||
- 许可证:MIT
|
||||
- 源链接:https://github.com/aimoonchen/bvh
|
||||
- 用途:BVH 解析参考实现
|
||||
|
||||
---
|
||||
|
||||
## 补充声明(实际引入的依赖)
|
||||
|
||||
## 11. React
|
||||
|
||||
- 许可证:MIT
|
||||
- 源链接:https://github.com/facebook/react
|
||||
- 用途:Web 管理端界面
|
||||
|
||||
## 12. Vite
|
||||
|
||||
- 许可证:MIT
|
||||
- 源链接:https://github.com/vitejs/vite
|
||||
- 用途:Web 管理端构建
|
||||
|
||||
## 13. CameraX
|
||||
|
||||
- 许可证:Apache License 2.0
|
||||
- 源链接:https://developer.android.com/jetpack/androidx/releases/camera
|
||||
- 用途:Android 相机采集
|
||||
|
||||
## 14. Jetpack Compose
|
||||
|
||||
- 许可证:Apache License 2.0
|
||||
- 源链接:https://developer.android.com/jetpack/compose
|
||||
- 用途:Android 界面
|
||||
|
||||
## 15. mysql2
|
||||
|
||||
- 许可证:MIT
|
||||
- 源链接:https://github.com/sidorares/node-mysql2
|
||||
- 用途:Web 管理服务端数据库驱动
|
||||
|
||||
---
|
||||
|
||||
## 许可合规说明
|
||||
|
||||
1. 本客户端自身代码以 MIT 许可发布。
|
||||
2. 运行时依赖仅为 **Godot Engine**(MIT);其余条目为同项目其他组件或服务端依赖,
|
||||
按契约要求一并声明。
|
||||
3. 本客户端**未使用 SMPL 模型及其衍生拓扑**,统一采用 MediaPipe 33 点拓扑,
|
||||
以规避 SMPL 的非商用许可限制。
|
||||
4. 本客户端只接收 33 个关键点坐标,**不接收、不存储任何视频画面**。
|
||||
5. 本客户端下载并缓存的 VRM 模型来自部署方自建的服务端,其来源与许可由
|
||||
Web 服务端的来源五元组字段管理并在界面展示。
|
||||
@@ -0,0 +1,76 @@
|
||||
project: opendance-windows-client
|
||||
version: 0.1.0
|
||||
repository: opendance-windows
|
||||
default_branch: main
|
||||
engine: Godot 4.7.2
|
||||
contracts_version: 0.1.0
|
||||
|
||||
# 契约变更流程(文档 §7.2):
|
||||
# 任何跨端契约变更必须先在 opendance-web 修改 contracts/ 并提升版本,
|
||||
# 再在本项目创建适配任务。本项目内的 contracts/ 只是只读快照。
|
||||
|
||||
dependencies:
|
||||
- name: opendance-web
|
||||
kind: rest-api
|
||||
contract: contracts/rest-api.yaml
|
||||
usage: 曲目/动作/模型目录与关键点序列
|
||||
- name: opendance-android
|
||||
kind: websocket-consumer
|
||||
contract: contracts/websocket-keypoints.md
|
||||
usage: 本端为服务端,Android 主动连入推流
|
||||
|
||||
tasks:
|
||||
- id: T-WIN-001
|
||||
file: tasks/001-scaffold.md
|
||||
scope: Godot 项目骨架
|
||||
status: done
|
||||
- id: T-WIN-002
|
||||
file: tasks/002-rest-client.md
|
||||
scope: 调用 Web 服务端 API
|
||||
status: done
|
||||
- id: T-WIN-003
|
||||
file: tasks/003-websocket-receiver.md
|
||||
scope: 接收 Android 关键点帧
|
||||
status: done
|
||||
- id: T-WIN-004
|
||||
file: tasks/004-keypoint-deserialize.md
|
||||
scope: 239 字节帧反序列化
|
||||
status: done
|
||||
- id: T-WIN-005
|
||||
file: tasks/005-vrm-loader.md
|
||||
scope: VRM 模型加载
|
||||
status: done
|
||||
- id: T-WIN-006
|
||||
file: tasks/006-skeleton-retarget.md
|
||||
scope: 关键点 → VRM 骨骼重定向
|
||||
status: done
|
||||
- id: T-WIN-007
|
||||
file: tasks/007-scoring-dtw.md
|
||||
scope: DTW 评分引擎
|
||||
status: done
|
||||
- id: T-WIN-008
|
||||
file: tasks/008-game-ui.md
|
||||
scope: 主界面 + 游戏界面 + 结算
|
||||
status: done
|
||||
- id: T-WIN-009
|
||||
file: tasks/009-source-display.md
|
||||
scope: 模型/动作来源信息展示
|
||||
status: done
|
||||
- id: T-WIN-010
|
||||
file: tasks/010-about-page.md
|
||||
scope: 关于页面 + 开源声明
|
||||
status: done
|
||||
- id: T-WIN-011
|
||||
file: tasks/011-build-script.md
|
||||
scope: Windows 打包脚本
|
||||
status: done
|
||||
|
||||
deliverable:
|
||||
artifact: opendance-windows-v0.1.0.zip
|
||||
packaging: Godot 导出脚本(Linux 直接导出 / Windows 兜底)
|
||||
includes:
|
||||
- OpenDance.exe
|
||||
- OpenDance.pck
|
||||
- THIRD_PARTY.md
|
||||
- README.txt
|
||||
- contracts/ # 当前版本契约快照
|
||||
@@ -0,0 +1,41 @@
|
||||
# T-WIN-001 Godot 项目骨架
|
||||
|
||||
- **状态**:done
|
||||
- **依赖**:无
|
||||
|
||||
## 目标
|
||||
|
||||
建立 Godot 4 工程骨架,配置 4 个 autoload、渲染方式与窗口,保证可无头导入与启动。
|
||||
|
||||
## load
|
||||
|
||||
```
|
||||
DSH.md
|
||||
project.godot
|
||||
src/autoload/*.gd
|
||||
src/ui/main.gd
|
||||
scenes/main.tscn
|
||||
```
|
||||
|
||||
## 交付物
|
||||
|
||||
| 文件 | 说明 |
|
||||
|---|---|
|
||||
| `project.godot` | 工程配置;autoload = AppConfig / CatalogStore / PoseReceiver / ScoreService |
|
||||
| `scenes/main.tscn` | 只含一个挂载 `src/ui/main.gd` 的 Control 根节点(界面用代码构建,见 DSH.md §6) |
|
||||
| `src/ui/main.gd` | 应用外壳:构建布局、切换页面、启动接收服务 |
|
||||
| `src/autoload/app_config.gd` | 客户端配置,持久化到 `user://opendance.cfg` |
|
||||
| `src/autoload/catalog_store.gd` | 目录缓存 + 模型下载缓存 + 关键点序列缓存 |
|
||||
| `src/autoload/pose_receiver.gd` | 关键点接收服务封装 |
|
||||
| `src/autoload/score_service.gd` | 评分服务封装 |
|
||||
|
||||
## 验收
|
||||
|
||||
```bash
|
||||
GODOT --headless --path . --import # 无 SCRIPT ERROR
|
||||
GODOT --headless --path . --quit-after 180 # 应用可启动,UI 构建无报错
|
||||
```
|
||||
|
||||
## 结果
|
||||
|
||||
- 导入无编译错误;`--quit-after 180` 启动 180 帧无任何运行时错误。
|
||||
@@ -0,0 +1,44 @@
|
||||
# T-WIN-002 调用 Web 服务端 API
|
||||
|
||||
- **状态**:done
|
||||
- **依赖**:T-WIN-001
|
||||
|
||||
## 目标
|
||||
|
||||
调用 opendance-web 的 REST 接口拉取目录与参考动作序列,只消费冻结契约。
|
||||
|
||||
## load
|
||||
|
||||
```
|
||||
DSH.md
|
||||
contracts/rest-api.yaml # 锚点:#/paths/~1api~1catalog
|
||||
contracts/data-models.md # 锚点:§2 Song、§3 Motion、§4 Model、§6 CatalogBundle
|
||||
src/net/api_client.gd
|
||||
src/net/catalog_types.gd
|
||||
src/autoload/catalog_store.gd
|
||||
```
|
||||
|
||||
## 交付物
|
||||
|
||||
| 文件 | 说明 |
|
||||
|---|---|
|
||||
| `src/net/api_client.gd` | HTTPRequest 封装;统一返回 `{ok,status,data,error}`,不抛异常 |
|
||||
| `src/net/catalog_types.gd` | Song / Motion / Model / CatalogBundle / SourceInfo 类型化解析 |
|
||||
| `src/net/keypoint_sequence.gd` | NDJSON 关键点序列 → 特征向量序列 |
|
||||
| `src/autoload/catalog_store.gd` | 目录刷新、模型文件下载缓存、序列缓存 |
|
||||
|
||||
## 关键实现
|
||||
|
||||
- `default_model()` 直接使用服务端解析好的 `defaultModelId`,客户端不重复实现 VRM 优先级
|
||||
- `Motion.is_scorable()` 要求 `preprocessState == "done"` 且有帧数
|
||||
- 错误体解析兼容 NestJS 的 `message: string[]`
|
||||
- 模型文件按 `user://models/<id>.<ext>` 缓存,避免重复下载 10 MB 级 VRM
|
||||
|
||||
## 验收
|
||||
|
||||
`tests/test_api_integration.gd` 对**真实运行中**的服务端断言:
|
||||
health / catalog version / catalog export / 关键点序列,服务端不可达时显式跳过。
|
||||
|
||||
## 结果
|
||||
|
||||
集成测试通过:目录解析、来源五元组齐备性、`defaultModelId` 解析均符合契约。
|
||||
@@ -0,0 +1,45 @@
|
||||
# T-WIN-003 接收 Android 关键点帧
|
||||
|
||||
- **状态**:done
|
||||
- **依赖**:T-WIN-001
|
||||
|
||||
## 目标
|
||||
|
||||
在本端监听 `ws://<host>:8765/pose`,作为 WebSocket **服务端**接收 Android 辅助 App 的推流。
|
||||
|
||||
## load
|
||||
|
||||
```
|
||||
DSH.md
|
||||
contracts/websocket-keypoints.md # 锚点:§1 端点与子协议、§3 文本控制帧
|
||||
src/net/pose_server.gd
|
||||
src/autoload/pose_receiver.gd
|
||||
```
|
||||
|
||||
## 交付物
|
||||
|
||||
- `src/net/pose_server.gd`:TCPServer + `WebSocketPeer.accept_stream()` 实现服务端
|
||||
- `src/autoload/pose_receiver.gd`:绑定 AppConfig,暴露 `latest_frame` 与统计
|
||||
|
||||
## 能力
|
||||
|
||||
| 项 | 说明 |
|
||||
|---|---|
|
||||
| 子协议 | 协商 `opendance.pose.v1`,客户端选择不符时告警 |
|
||||
| 控制帧 | `hello` → 回 `welcome`(含 sessionId);`bye` → 关闭 |
|
||||
| 协议不匹配 | 回 `bye{reason: protocol_mismatch}` 并以 1002 关闭 |
|
||||
| 二进制帧 | 严格校验 239 字节与 magic,畸形帧计数但不影响连接 |
|
||||
| 丢包统计 | 按 `frame_id` 跳号计数(含 u32 回绕保护) |
|
||||
| 帧率统计 | 1 秒窗口计算 FPS |
|
||||
| 超时剔除 | 5 秒无数据判定超时断开 |
|
||||
| config 推送 | 每 2 秒广播 mirror / targetFps / upperBodyOnly / resolution |
|
||||
|
||||
## 已知限制
|
||||
|
||||
Godot 的 `WebSocketPeer` 不暴露握手请求路径,因此**无法校验 path 是否为 `/pose`**,
|
||||
实现接受任意 path,仅校验子协议。已记录于 DSH.md 与任务文档。
|
||||
|
||||
## 验收
|
||||
|
||||
`tests/test_pose_server.gd` 在真实 socket 上端到端验证:握手、子协议协商、
|
||||
hello/welcome、二进制帧往返、畸形帧拒绝、丢包计数、flag 保留、config 广播、snapshot、stop。
|
||||
@@ -0,0 +1,46 @@
|
||||
# T-WIN-004 239 字节帧反序列化
|
||||
|
||||
- **状态**:done
|
||||
- **依赖**:T-WIN-001
|
||||
|
||||
## 目标
|
||||
|
||||
按契约实现 239 字节关键点帧的编解码。
|
||||
|
||||
## load
|
||||
|
||||
```
|
||||
DSH.md
|
||||
contracts/websocket-keypoints.md # 锚点:§2 二进制帧(冻结)
|
||||
contracts/skeleton-topology.md # 锚点:§4 坐标系
|
||||
src/net/keypoint_frame.gd
|
||||
```
|
||||
|
||||
## 帧布局(契约冻结)
|
||||
|
||||
```
|
||||
偏移 长度 字段
|
||||
0 2 magic u16 = 0x4F44 ('OD')
|
||||
2 1 version u8 = 1
|
||||
3 1 flags u8 bit0 镜像 bit1 低置信度 bit2 上半身
|
||||
4 4 frame_id u32 LE
|
||||
8 231 keypoints 33 × 7B:s16 x, s16 y, s16 z, u8 conf
|
||||
合计 239
|
||||
```
|
||||
|
||||
## 交付物
|
||||
|
||||
`src/net/keypoint_frame.gd`:`PoseFrame` 内部类 + `decode` / `encode` / `validate` / `from_json_dict`。
|
||||
|
||||
## 关键实现
|
||||
|
||||
- 归一化系数统一 32767,与 opendance-web 的 TypeScript 参考实现逐位对齐
|
||||
- 编解码往返误差 < 1e-4(定点精度 1/32767 ≈ 3.05e-5)
|
||||
- `unmirrored_points()` 按 flags.bit0 翻转 x
|
||||
- 低置信度阈值 0.5:`is_reliable()` 与 `compute_low_confidence()`
|
||||
- 极值(±1.0)不溢出 int16
|
||||
|
||||
## 验收
|
||||
|
||||
`tests/test_keypoint_frame.gd`:常量与契约一致、239 字节、magic/version/flags/frame_id、
|
||||
往返精度、错误长度/magic/版本拒绝、极值、镜像翻转、置信度阈值、JSON 调试帧。
|
||||
@@ -0,0 +1,41 @@
|
||||
# T-WIN-005 VRM 模型加载
|
||||
|
||||
- **状态**:done
|
||||
- **依赖**:T-WIN-001
|
||||
|
||||
## 目标
|
||||
|
||||
加载 VRM 模型,识别 VRM 版本,定位 Skeleton3D 并映射 humanoid 骨骼。
|
||||
|
||||
## load
|
||||
|
||||
```
|
||||
DSH.md
|
||||
contracts/model-formats.md # 锚点:§1 角色模型
|
||||
contracts/data-models.md # 锚点:§4.1 VRM 优先规则
|
||||
src/model/vrm_loader.gd
|
||||
src/model/humanoid_map.gd
|
||||
```
|
||||
|
||||
## 选型变更
|
||||
|
||||
文档原选型 `godot-vrm`(V-Sekai)GDExtension **不发布任何预编译产物**,
|
||||
需用 godot-cpp 本地编译原生库,会破坏「Linux 导出 Windows exe」的验收路径。
|
||||
改用 **Godot 内置 GLTFDocument**:VRM 即 glTF 2.0 容器,实测可完整解析
|
||||
154 骨骼的 VRoid 模型,且 `GLTFState.json` 可直接读取 VRM 扩展判定版本。
|
||||
|
||||
## 骨骼名映射
|
||||
|
||||
`HumanoidMap` 覆盖 VRoid / VRM 0.x / Mixamo 三类命名,归一化后精确比较:
|
||||
|
||||
- 剥离前缀 `jbip` / `mixamorig` / `bip01`(**归一化之后**的形态)
|
||||
- 生成 compact(left→l)与 expanded(l→left)两种变体比较
|
||||
- **故意不剥离 `J_Aim_` / `J_Roll_` / `J_Sec_`**:VRoid 用它们标记扭转辅助骨,
|
||||
剥离后 `J_Aim_L_UpperLeg` 会与真正的 `J_Bip_L_UpperLeg` 撞名
|
||||
|
||||
## 验收
|
||||
|
||||
`tests/test_vrm_loader.gd`:
|
||||
- 三类命名规范的识别(含辅助骨不遮蔽真骨的回归用例)
|
||||
- 真实 VRM:154 骨骼 / 3 网格 / VRM 1.0,**humanoid 覆盖率 100%**
|
||||
- 缺失文件、普通 glTF 不误判
|
||||
@@ -0,0 +1,45 @@
|
||||
# T-WIN-006 关键点 → VRM 骨骼重定向
|
||||
|
||||
- **状态**:done
|
||||
- **依赖**:T-WIN-005、T-WIN-004
|
||||
|
||||
## 目标
|
||||
|
||||
把 33 点关键点实时驱动到 VRM 骨骼上。
|
||||
|
||||
## load
|
||||
|
||||
```
|
||||
DSH.md
|
||||
contracts/skeleton-topology.md # 锚点:§1 33 点、§2 虚拟关节、§4 坐标系
|
||||
src/anim/skeleton_retarget.gd
|
||||
```
|
||||
|
||||
## 算法
|
||||
|
||||
1. 每根骨骼指定一条骨链(起点关键点,终点关键点)
|
||||
2. 取骨骼静止朝向 `rest_dir`(由该骨与首个子骨的位置差得出,**不假设模型骨骼轴向**)
|
||||
3. 目标朝向由两个关键点在世界空间的方向得出
|
||||
4. `delta` = rest_dir → target_dir 的最小旋转四元数(保留扭转/roll)
|
||||
5. `desired = Basis(delta) × rest_global_basis`
|
||||
6. `pose_local = parent_global_basis⁻¹ × desired`
|
||||
|
||||
**第 6 步的合成公式是实测标定的**:Godot 文档未写明 rest/pose 的合成顺序,
|
||||
在真实 VRoid 模型上暴力枚举 5 种候选公式,只有该式误差为 1.9e-8(其余 ~0.14)。
|
||||
已有回归测试守护,不得凭记忆改写。
|
||||
|
||||
## 坐标转换
|
||||
|
||||
契约 §4 的 MediaPipe 归一化坐标 → Godot 世界方向:
|
||||
`Vector3(x × aspect, −y, −z × aspect)`(x/z 以图像宽为基准故乘宽高比)。
|
||||
|
||||
## 验收
|
||||
|
||||
`tests/test_retarget.gd` —— 最关键的是**功能性**断言:
|
||||
|
||||
```
|
||||
→ 左上臂方向点积 1.0000,左前臂方向点积 1.0000
|
||||
```
|
||||
|
||||
即:给定一组关键点,重定向后骨骼在世界空间的**实际指向**与关键点方向一致;
|
||||
另有判别性验证(换一组关键点骨骼朝向随之改变)与躯干 FK 传播验证。
|
||||
@@ -0,0 +1,42 @@
|
||||
# T-WIN-007 DTW 评分引擎
|
||||
|
||||
- **状态**:done
|
||||
- **依赖**:T-WIN-004、T-WIN-002
|
||||
|
||||
## 目标
|
||||
|
||||
用 DTW 把实时关键点序列与服务端参考序列比对,产出 0~100 分与等级。
|
||||
|
||||
## load
|
||||
|
||||
```
|
||||
DSH.md
|
||||
contracts/skeleton-topology.md # 锚点:§5 派生特征
|
||||
contracts/model-formats.md # 锚点:§2.1 预处理产物
|
||||
src/score/pose_features.gd
|
||||
src/score/dtw.gd
|
||||
src/score/scoring.gd
|
||||
```
|
||||
|
||||
## 特征(契约 §5 冻结)
|
||||
|
||||
每帧 = `[33 × (x,y,z), 9 个关节角余弦]` = **108 维**。
|
||||
低置信度点(conf < 0.5)对应维度**权重置 0**,不参与代价计算。
|
||||
|
||||
## DTW
|
||||
|
||||
- Sakoe-Chiba 带状约束 `|i − j| ≤ band`(默认 15 帧 ≈ 0.5 秒)
|
||||
- `DtwMatcher.compute_full()` 离线段;`StreamingDtw` 逐帧流式推进,
|
||||
每帧只计算带内一行,总体复杂度 O(n·m·band) 但摊到每帧 O(m·band)
|
||||
- 归一化:代价 / max(i,j)+1,得到「平均每步代价」
|
||||
|
||||
## 评分规则(`scoring.gd`)
|
||||
|
||||
`score = clamp(1 − cost/tolerance, 0, 1) × 100`,EMA 平滑抑制抖动,
|
||||
等级 S(≥95) / A(≥85) / B(≥70) / C(≥55) / D,连击阈值 70。
|
||||
|
||||
## 验收
|
||||
|
||||
`tests/test_dtw.gd` + `tests/test_pose_features.gd`:相同序列距离为 0、
|
||||
路径单调且起于 (0,0)、带宽约束、时间伸缩吸收、流式与离线一致、
|
||||
低置信度维度不参与距离、角度计算正确性。
|
||||
@@ -0,0 +1,40 @@
|
||||
# T-WIN-008 主界面 + 游戏界面 + 结算
|
||||
|
||||
- **状态**:done
|
||||
- **依赖**:T-WIN-002、T-WIN-003、T-WIN-006、T-WIN-007
|
||||
|
||||
## 目标
|
||||
|
||||
三页式体验:主菜单(选曲/选动作/选模型)→ 游戏页(3D 角色 + 实时评分 HUD)→ 结算页。
|
||||
|
||||
## load
|
||||
|
||||
```
|
||||
DSH.md
|
||||
src/ui/main.gd
|
||||
src/ui/main_menu.gd
|
||||
src/ui/game_screen.gd
|
||||
src/ui/result_screen.gd
|
||||
src/ui/ui_kit.gd
|
||||
```
|
||||
|
||||
## 页面
|
||||
|
||||
| 页面 | 能力 |
|
||||
|---|---|
|
||||
| 主菜单 | 三栏选择(曲目 / 可评分动作 / 模型)、服务端地址编辑与测试、目录刷新、接收服务状态与重启 |
|
||||
| 游戏页 | SubViewport 3D 角色、自动定机位、实时分数/等级/连击、进度条、连接状态提示、来源展示 |
|
||||
| 结算 | 等级大字、平均/最终分、完成度、最高单帧、最长连击、评语、来源展示 |
|
||||
|
||||
## 关键实现
|
||||
|
||||
- 界面全部代码构建(见 DSH.md §6),主菜单只列出 `preprocessState == "done"` 的动作
|
||||
- 游戏页订阅 `PoseReceiver.frame_received` 驱动骨骼,`ScoreService` 独立订阅做评分
|
||||
- 未连接设备时给出明确提示而非静默失败;模型/序列拉取失败时显示可读原因并允许重试
|
||||
- 参考序列走完自动结算
|
||||
- 切换模型时正确 `dispose()` 旧场景,避免泄漏
|
||||
|
||||
## 验收
|
||||
|
||||
`GODOT --headless --path . --quit-after 180` 启动无运行时错误;
|
||||
主菜单、游戏页、结算页的构造路径均被该启动流程覆盖。
|
||||
@@ -0,0 +1,40 @@
|
||||
# T-WIN-009 来源信息展示
|
||||
|
||||
- **状态**:done
|
||||
- **依赖**:T-WIN-008
|
||||
|
||||
## 目标
|
||||
|
||||
在客户端展示模型/动作的**来源五元组**(来源类型、名称、链接、许可、作者)。
|
||||
|
||||
## load
|
||||
|
||||
```
|
||||
DSH.md
|
||||
contracts/data-models.md # 锚点:§1 来源五元组
|
||||
src/ui/source_panel.gd
|
||||
src/net/catalog_types.gd
|
||||
```
|
||||
|
||||
## 交付物
|
||||
|
||||
`src/ui/source_panel.gd`(可复用组件)+ `CatalogTypes.SourceInfo`(含 `type_label()` 与 `is_complete()`)。
|
||||
|
||||
## 展示规则
|
||||
|
||||
- 标题 + 来源类型中文标签(原创 / 已获授权 / Creative Commons / 公共领域 / 用户上传 / 其他)
|
||||
- 资产名 | 来源名称
|
||||
- 作者
|
||||
- 许可 + **可点击外链**跳转到 `sourceUrl`
|
||||
- **五元组不齐备时显式告警**("⚠ 来源信息不完整,请联系管理员补全后再使用"),不静默展示
|
||||
|
||||
## 使用位置
|
||||
|
||||
- 主菜单:选中动作时显示
|
||||
- 游戏页:当前跟练动作
|
||||
- 结算页:本轮动作
|
||||
|
||||
## 验收
|
||||
|
||||
`tests/test_api_integration.gd` 对真实服务端断言曲目与模型的来源五元组齐备
|
||||
(`song.source.is_complete()`),保证展示组件拿到的数据完整。
|
||||
@@ -0,0 +1,34 @@
|
||||
# T-WIN-010 关于页面 + 开源声明
|
||||
|
||||
- **状态**:done
|
||||
- **依赖**:T-WIN-008
|
||||
|
||||
## 目标
|
||||
|
||||
关于页面展示版本信息与**完整可滚动**的第三方开源项目声明。
|
||||
|
||||
## load
|
||||
|
||||
```
|
||||
DSH.md
|
||||
contracts/open-source-credits.md # 锚点:全表
|
||||
src/ui/about_screen.gd
|
||||
src/ui/credits.gd
|
||||
```
|
||||
|
||||
## 交付物
|
||||
|
||||
- `src/ui/credits.gd`:与 `contracts/open-source-credits.md` 逐条对应的数据表,渲染为 BBCode
|
||||
- `src/ui/about_screen.gd`:应用信息 + 契约版本 + 隐私说明 + 可滚动声明(RichTextLabel)
|
||||
|
||||
## 要点
|
||||
|
||||
- 声明含每项的**许可证、版权(如有)、用途、可点击源链接**
|
||||
- 明确声明不使用 SMPL 拓扑
|
||||
- 明确声明「仅接收关键点,不接收、不存储视频画面」
|
||||
- 与 `THIRD_PARTY.md`、`contracts/open-source-credits.md` 三处保持一致
|
||||
|
||||
## 验收
|
||||
|
||||
`tests/test_credits.gd` 断言:条目数一致、每条均有名称/许可证/https 链接、
|
||||
BBCode 含全部条目、纯文本导出可解析。
|
||||
@@ -0,0 +1,58 @@
|
||||
# T-WIN-011 Windows 打包脚本
|
||||
|
||||
- **状态**:done
|
||||
- **依赖**:T-WIN-001 ~ 010
|
||||
|
||||
## 目标
|
||||
|
||||
提供 Linux 直接导出(方案 A,推荐)与 Windows 端兜底(方案 B)两条打包路径。
|
||||
|
||||
## load
|
||||
|
||||
```
|
||||
DSH.md
|
||||
build/export-windows.sh
|
||||
build/export-windows.ps1
|
||||
export_presets.cfg
|
||||
```
|
||||
|
||||
## 交付物
|
||||
|
||||
| 文件 | 说明 |
|
||||
|---|---|
|
||||
| `export_presets.cfg` | 导出预设(**必须在项目根目录**,Godot 硬性要求) |
|
||||
| `build/export-windows.sh` | Linux 导出:定位 Godot → 检查模板 → 导入 → 导出 → **校验 PE 头** |
|
||||
| `build/export-windows.ps1` | Windows 兜底:同上,产物路径与校验逻辑一致 |
|
||||
|
||||
## 脚本健壮性
|
||||
|
||||
- `GODOT_BIN` 环境变量或 PATH 自动定位 Godot,缺失时给出可执行的安装指引
|
||||
- 导出模板缺失时明确报错并给出下载与安装路径
|
||||
- 导出命令返回非 0 时**仍检查产物是否存在**(Godot 有时非 0 返回但产物有效)
|
||||
- 读取产物前 2 字节校验 `MZ`(PE 头),防止导出静默产出空壳
|
||||
|
||||
## 验收(文档 §5.4)
|
||||
|
||||
| 验收项 | 结果 |
|
||||
|---|---|
|
||||
| Linux:`bash build/export-windows.sh` 成功生成 exe | ✅ 生成 `OpenDance.exe` 104 MB + `OpenDance.pck` 107 KB,PE 头校验通过 |
|
||||
| Windows:`powershell build/export-windows.ps1` 成功生成 exe | ⚠️ 脚本就绪,本环境无 Windows,未实机执行 |
|
||||
| 生成的 exe 在 Windows 10/11 可运行 | ⚠️ 本环境无 Windows;已校验为合法 PE32+ x86_64 可执行文件 |
|
||||
| `THIRD_PARTY.md` 包含所有开源项目与源链接 | ✅ 15 项,与契约表逐条一致 |
|
||||
|
||||
## 打包产物
|
||||
|
||||
```
|
||||
opendance-windows-v0.1.0.zip
|
||||
├── OpenDance.exe
|
||||
├── OpenDance.pck
|
||||
├── THIRD_PARTY.md
|
||||
├── README.txt
|
||||
└── contracts/ # 契约快照
|
||||
```
|
||||
|
||||
## 环境记录
|
||||
|
||||
- Godot 4.7.2.stable(`ed1daf0bf`)
|
||||
- 导出模板 `4.7.2.stable`(``windows_release_x86_64.exe``)
|
||||
- 首次导出失败原因:`export_presets.cfg` 放在 `build/` 下,Godot 只在**项目根目录**查找
|
||||
Reference in New Issue
Block a user