docs: 消除 Gitea 模棱两可 Unicode 告警(全角标点紧邻 ASCII 时改为半角)

依据 Gitea v1.27.3 modules/charset/escape_stream.go 的判定规则
(analyzeDetectResults:仅当左右相邻字符都不是非 ASCII 时才标记,
且 _common 表恒定生效),把说明文档中会被标记的全角标点
(,:;?!() 等)改为半角,并保证成对括号宽度一致。
中文语境中的全角标点(左右均为中文)保持原样,不动代码与契约语义。
This commit is contained in:
opendance
2026-09-15 21:45:51 +08:00
parent 8c1d24865c
commit 4913760a8b
20 changed files with 153 additions and 153 deletions
+6 -6
View File
@@ -1,4 +1,4 @@
# DSH.md -- opendance-windows(OpenDance Studio 项目二:Windows 客户端) # DSH.md -- opendance-windows(OpenDance Studio 项目二:Windows 客户端)
## 0. 本文件的作用 ## 0. 本文件的作用
@@ -11,7 +11,7 @@ Windows 客户端是核心体验载体:接收 Android 辅助 App 推送的 33
驱动 VRM 角色,并与 Web 服务端下发的参考动作做实时 DTW 评分。 驱动 VRM 角色,并与 Web 服务端下发的参考动作做实时 DTW 评分。
``` ```
Android 辅助 App ──239 字节关键点帧──▶ 本端(WebSocket 服务端:8765) Android 辅助 App ──239 字节关键点帧──▶ 本端(WebSocket 服务端:8765)
│ │
Web 服务端 ──GET /api/catalog/export──────┤ Web 服务端 ──GET /api/catalog/export──────┤
──GET /api/motions/:id/keypoints┘ ──GET /api/motions/:id/keypoints┘
@@ -25,7 +25,7 @@ Web 服务端 ──GET /api/catalog/export──────┤
| 组件 | 选型 | 说明 | | 组件 | 选型 | 说明 |
|--|--|--| |--|--|--|
| 引擎 | Godot 4.7.2(MIT) | 可在 Linux 上导出 Windows exe | | 引擎 | Godot 4.7.2(MIT) | 可在 Linux 上导出 Windows exe |
| VRM 加载 | **Godot 内置 GLTFDocument** | 见 6 的选型变更说明 | | VRM 加载 | **Godot 内置 GLTFDocument** | 见 6 的选型变更说明 |
| 多格式导入 | Godot 内置 glTF 导入器 | FBX 由管理端转 BVH 后下发 | | 多格式导入 | Godot 内置 glTF 导入器 | FBX 由管理端转 BVH 后下发 |
| 通信 | WebSocketPeer + HTTPRequest | 无第三方依赖 | | 通信 | WebSocketPeer + HTTPRequest | 无第三方依赖 |
@@ -66,7 +66,7 @@ opendance-windows/
├── export_presets.cfg # ★ 必须位于项目根目录(Godot 硬性要求) ├── export_presets.cfg # ★ 必须位于项目根目录(Godot 硬性要求)
├── project.yaml / DSH.md ├── project.yaml / DSH.md
├── tasks/ # 001~011 单任务说明书 ├── tasks/ # 001~011 单任务说明书
├── contracts/ # 只读契约快照(来源 opendance-web v0.1.0) ├── contracts/ # 只读契约快照(来源 opendance-web v0.1.0)
├── scenes/main.tscn # 仅一个挂载 main.gd 的根节点 ├── scenes/main.tscn # 仅一个挂载 main.gd 的根节点
├── src/ ├── src/
│ ├── autoload/ # AppConfig / CatalogStore / PoseReceiver / ScoreService │ ├── autoload/ # AppConfig / CatalogStore / PoseReceiver / ScoreService
@@ -76,7 +76,7 @@ opendance-windows/
│ ├── score/ # 特征提取、DTW、评分规则 │ ├── score/ # 特征提取、DTW、评分规则
│ └── ui/ # 主界面 / 游戏界面 / 结算 / 关于 / 来源展示 │ └── ui/ # 主界面 / 游戏界面 / 结算 / 关于 / 来源展示
├── tests/ # 无头测试框架 + 用例 ├── tests/ # 无头测试框架 + 用例
└── build/ # 导出脚本(sh / ps1) └── build/ # 导出脚本(sh / ps1)
``` ```
## 6. 界面构建方式与选型变更(重要) ## 6. 界面构建方式与选型变更(重要)
@@ -121,7 +121,7 @@ $GODOT --headless --path. --script res://tests/run_tests.gd
# 3. 应用可启动(autoload + UI 构建无报错) # 3. 应用可启动(autoload + UI 构建无报错)
$GODOT --headless --path. --quit-after 180 $GODOT --headless --path. --quit-after 180
# 4. Windows 导出(需先安装 export templates) # 4. Windows 导出(需先安装 export templates)
bash build/export-windows.sh release bash build/export-windows.sh release
``` ```
+19 -19
View File
@@ -1,6 +1,6 @@
# opendance-windows # opendance-windows
OpenDance Studio **阶段二**:Windows 客户端(Godot 4)。 OpenDance Studio **阶段二**: Windows 客户端(Godot 4)。
接收 Android 辅助 App 通过 WebSocket 推送的 33 点骨骼关键点,驱动 VRM 角色, 接收 Android 辅助 App 通过 WebSocket 推送的 33 点骨骼关键点,驱动 VRM 角色,
并与 Web 管理服务端下发的参考动作做实时 DTW 评分。 并与 Web 管理服务端下发的参考动作做实时 DTW 评分。
@@ -9,14 +9,14 @@ OpenDance Studio **阶段二**:Windows 客户端(Godot 4)。
## 状态 ## 状态
**阶段二已完成**:T-WIN-001 -> T-WIN-011 全部落地。 **阶段二已完成**: T-WIN-001 -> T-WIN-011 全部落地。
| 验收项 | 结果 | | 验收项 | 结果 |
|--|--| |--|--|
| 脚本编译 | OK 全部通过(无 SCRIPT ERROR) | | 脚本编译 | OK 全部通过(无 SCRIPT ERROR) |
| 无头测试 | OK **580 项全部通过**(8 个测试套件) | | 无头测试 | OK **580 项全部通过**(8 个测试套件) |
| 应用启动 | OK 180 帧无运行时错误 | | 应用启动 | OK 180 帧无运行时错误 |
| Windows 导出 | OK 生成 `OpenDance.exe`(104 MB,PE 头校验通过)+ `OpenDance.pck` | | Windows 导出 | OK 生成 `OpenDance.exe`(104 MB, PE 头校验通过)+ `OpenDance.pck` |
| 真实 VRM 验证 | OK 154 骨骼 VRoid 模型,humanoid 覆盖率 **100%** | | 真实 VRM 验证 | OK 154 骨骼 VRoid 模型,humanoid 覆盖率 **100%** |
| 骨骼重定向 | OK 关键点方向与骨骼实际指向点积 **1.0000** | | 骨骼重定向 | OK 关键点方向与骨骼实际指向点积 **1.0000** |
| 跨实现交叉校验 | OK 关节角与 Web 服务端 TypeScript 实现**逐帧一致** | | 跨实现交叉校验 | OK 关节角与 Web 服务端 TypeScript 实现**逐帧一致** |
@@ -45,10 +45,10 @@ bash build/package-release.sh 0.1.0
| 项 | 要求 | | 项 | 要求 |
|--|--| |--|--|
| 操作系统 | 运行客户端:Windows 10/11(x86_64)。开发与导出:Windows 或 Linux 均可 | | 操作系统 | 运行客户端:Windows 10/11(x86_64)。开发与导出:Windows 或 Linux 均可 |
| Godot | **4.7.x**(`project.godot` 声明特性 `4.7`,本仓库按 4.7.2 验证)。使用**标准版**(GDScript),**不需要 .NET / C# 版** | | Godot | **4.7.x**(`project.godot` 声明特性 `4.7`,本仓库按 4.7.2 验证)。使用**标准版**(GDScript), **不需要 .NET / C# 版** |
| 显卡与图形界面 | 渲染后端为 **GL Compatibility**,运行需支持 OpenGL 3.3 的显卡与驱动。仅无头测试与导出可无 GPU、可无图形界面 | | 显卡与图形界面 | 渲染后端为 **GL Compatibility**,运行需支持 OpenGL 3.3 的显卡与驱动。仅无头测试与导出可无 GPU、可无图形界面 |
| 导出模板 | **仅打包导出时需要**:必须安装与编辑器**完全同版本**的 export templates(`.tpz`) | | 导出模板 | **仅打包导出时需要**:必须安装与编辑器**完全同版本**的 export templates(`.tpz`) |
| 磁盘与网络 | Godot 编辑器 + 导出模板约数百 MB;导出产物 `OpenDance.exe` 约 104 MB、`OpenDance.pck` 约 107 KB。本端需能访问 Web 服务端 API(默认 TCP 3000),并与 Android 设备处于同一局域网、放行本端 **TCP 8765 入站** | | 磁盘与网络 | Godot 编辑器 + 导出模板约数百 MB;导出产物 `OpenDance.exe` 约 104 MB、`OpenDance.pck` 约 107 KB。本端需能访问 Web 服务端 API(默认 TCP 3000),并与 Android 设备处于同一局域网、放行本端 **TCP 8765 入站** |
### 2. 获取代码 ### 2. 获取代码
@@ -75,7 +75,7 @@ cd opendance-windows
- 位置:主界面顶部「**服务端地址**」输入框,填好后点「**保存并测试**」。 - 位置:主界面顶部「**服务端地址**」输入框,填好后点「**保存并测试**」。
- 配置字段名(`opendance.cfg`):`api_base_url`。 - 配置字段名(`opendance.cfg`):`api_base_url`。
- **端口区分**:客户端只访问 Web 服务端的 **API 端口(默认 3000)**;管理端页面在**管理端口(默认 3001)**,客户端不使用该端口。 - **端口区分**:客户端只访问 Web 服务端的 **API 端口(默认 3000)**;管理端页面在**管理端口(默认 3001)**,客户端不使用该端口。
- 示例占位符: - 示例占位符:
``` ```
@@ -89,8 +89,8 @@ http://192.0.2.10:3000
| 对端 | 方向 | 本端配置 | 说明 | | 对端 | 方向 | 本端配置 | 说明 |
|--|--|--|--| |--|--|--|--|
| Android 辅助 App | Android -> 本端(WebSocket) | `listen_host`(默认 `0.0.0.0`)、`listen_port`(默认 **8765**)、`ws_subprotocol`(默认 `opendance.pose.v1`) | 本端是 WebSocket **服务端**;Android 端填写本端主界面显示的「接收地址」,形如 `ws://<本机局域网地址>:8765/pose` | | Android 辅助 App | Android -> 本端(WebSocket) | `listen_host`(默认 `0.0.0.0`)、`listen_port`(默认 **8765**)、`ws_subprotocol`(默认 `opendance.pose.v1`) | 本端是 WebSocket **服务端**; Android 端填写本端主界面显示的「接收地址」,形如 `ws://<本机局域网地址>:8765/pose` |
| Web 服务端 | 本端 -> Web(HTTP) | `api_base_url`(默认 `http://127.0.0.1:3000`) | 需先在服务端**发布**曲目 / 动作 / 模型,动作还须**完成关键点预处理**,否则目录为空或动作不可选 | | Web 服务端 | 本端 -> Web(HTTP) | `api_base_url`(默认 `http://127.0.0.1:3000`) | 需先在服务端**发布**曲目 / 动作 / 模型,动作还须**完成关键点预处理**,否则目录为空或动作不可选 |
- **Android 侧无本端可配置项**:接收地址由 Android App 自己填写;本端只监听固定端口 8765(可在 `opendance.cfg` 改 `listen_port` 后点「重新连接接收服务」生效)。 - **Android 侧无本端可配置项**:接收地址由 Android App 自己填写;本端只监听固定端口 8765(可在 `opendance.cfg` 改 `listen_port` 后点「重新连接接收服务」生效)。
- 已知限制:Godot 的 `WebSocketPeer` 不暴露握手请求路径,本端**接受任意 path**,只校验子协议 `opendance.pose.v1`(见「已知限制」1)。 - 已知限制:Godot 的 `WebSocketPeer` 不暴露握手请求路径,本端**接受任意 path**,只校验子协议 `opendance.pose.v1`(见「已知限制」1)。
@@ -111,8 +111,8 @@ bash build/package-release.sh 0.1.0
| 产物 | 位置 | 说明 | | 产物 | 位置 | 说明 |
|--|--|--| |--|--|--|
| `OpenDance.exe` | `build/windows/` | 主程序,约 104 MB;脚本校验前 2 字节为 `MZ`(合法 PE) | | `OpenDance.exe` | `build/windows/` | 主程序,约 104 MB;脚本校验前 2 字节为 `MZ`(合法 PE) |
| `OpenDance.pck` | `build/windows/` | 资源包,约 107 KB;**必须与 exe 同目录** | | `OpenDance.pck` | `build/windows/` | 资源包,约 107 KB; **必须与 exe 同目录** |
| `OpenDance.console.exe` | `build/windows/` | 控制台包装器,可能生成,非必需 | | `OpenDance.console.exe` | `build/windows/` | 控制台包装器,可能生成,非必需 |
| `opendance-windows-v0.1.0.zip` | `dist-release/` | 发布包:exe + pck + `THIRD_PARTY.md` + `README.txt` + `contracts/` | | `opendance-windows-v0.1.0.zip` | `dist-release/` | 发布包:exe + pck + `THIRD_PARTY.md` + `README.txt` + `contracts/` |
@@ -133,9 +133,9 @@ bash build/package-release.sh 0.1.0
| 现象 | 原因 | 处理 | | 现象 | 原因 | 处理 |
|--|--|--| |--|--|--|
| 游戏界面提示「模型加载失败:缺少骨骼」或「骨骼绑定失败,模型可能不受支持」 | 模型没有 `Skeleton3D`,或骨骼命名不在 humanoid 映射表内;驱动至少需要 hips / spine / neck / head + 四肢共 16 根核心骨骼 | 换用标准 VRM(VRoid Studio 导出);看「人形覆盖率」,低于 60% 会提示「骨骼覆盖偏低,动作可能不完整」;必要时在 `src/model/humanoid_map.gd` 增加命名映射 | | 游戏界面提示「模型加载失败:缺少骨骼」或「骨骼绑定失败,模型可能不受支持」 | 模型没有 `Skeleton3D`,或骨骼命名不在 humanoid 映射表内;驱动至少需要 hips / spine / neck / head + 四肢共 16 根核心骨骼 | 换用标准 VRM(VRoid Studio 导出);看「人形覆盖率」,低于 60% 会提示「骨骼覆盖偏低,动作可能不完整」;必要时在 `src/model/humanoid_map.gd` 增加命名映射 |
| 顶部「服务端不可达」或「目录拉取失败」 | Web 服务端未启动、地址或端口写错(误填管理端口 3001)、或网络不通 | 确认 `api_base_url` 为 `http://<服务器地址>:3000`;浏览器访问 `http://<服务器地址>:3000/health` 应返回 200;再点「保存并测试」 | | 顶部「服务端不可达」或「目录拉取失败」 | Web 服务端未启动、地址或端口写错(误填管理端口 3001)、或网络不通 | 确认 `api_base_url` 为 `http://<服务器地址>:3000`;浏览器访问 `http://<服务器地址>:3000/health` 应返回 200;再点「保存并测试」 |
| 导出失败:「未找到导出模板」 | 未安装 export templates,或版本 / 路径与编辑器不一致 | 编辑器「编辑器 -> 管理导出模板 -> 下载并安装」;Linux 放 `~/.local/share/godot/export_templates/<版本>.stable/`,Windows 放 `%APPDATA%\Godot\export_templates\<版本>.stable\` | | 导出失败:「未找到导出模板」 | 未安装 export templates,或版本 / 路径与编辑器不一致 | 编辑器「编辑器 -> 管理导出模板 -> 下载并安装」;Linux 放 `~/.local/share/godot/export_templates/<版本>.stable/`, Windows 放 `%APPDATA%\Godot\export_templates\<版本>.stable\` |
| 提示「接收服务启动失败(端口 8765 可能被占用)」 | TCP 8765 被占用或未放行 | 改 `opendance.cfg` 的 `listen_port` 后点「重新连接接收服务」,并同步修改 Android 端填写的接收地址 | | 提示「接收服务启动失败(端口 8765 可能被占用)」 | TCP 8765 被占用或未放行 | 改 `opendance.cfg` 的 `listen_port` 后点「重新连接接收服务」,并同步修改 Android 端填写的接收地址 |
| 手机已连接但分数一直是 0 | Android 端未开始推流,或帧内关键点置信度都低于 0.5 | 「查看接收状态」确认累计帧在增长、畸形帧为 0;确认 Android 端子协议为 `opendance.pose.v1` | | 手机已连接但分数一直是 0 | Android 端未开始推流,或帧内关键点置信度都低于 0.5 | 「查看接收状态」确认累计帧在增长、畸形帧为 0;确认 Android 端子协议为 `opendance.pose.v1` |
@@ -143,10 +143,10 @@ bash build/package-release.sh 0.1.0
| 组件 | 选型 | | 组件 | 选型 |
|--|--| |--|--|
| 引擎 | Godot 4.7.x(MIT) | | 引擎 | Godot 4.7.x(MIT) |
| VRM 加载 | Godot 内置 GLTFDocument | | VRM 加载 | Godot 内置 GLTFDocument |
| 通信 | WebSocketPeer(服务端)+ HTTPRequest | | 通信 | WebSocketPeer(服务端)+ HTTPRequest |
| 评分 | DTW + 关节角度差异(纯 GDScript) | | 评分 | DTW + 关节角度差异(纯 GDScript) |
## 架构 ## 架构
@@ -173,7 +173,7 @@ src/
## 依赖的契约(opendance-web v0.1.0 冻结) ## 依赖的契约(opendance-web v0.1.0 冻结)
- [`contracts/websocket-keypoints.md`](contracts/websocket-keypoints.md) - 239 字节关键点帧与文本控制消息(本端为**接收方**) - [`contracts/websocket-keypoints.md`](contracts/websocket-keypoints.md) - 239 字节关键点帧与文本控制消息(本端为**接收方**)
- [`contracts/skeleton-topology.md`](contracts/skeleton-topology.md) - MediaPipe 33 点拓扑、父节点表、9 个关节角特征 - [`contracts/skeleton-topology.md`](contracts/skeleton-topology.md) - MediaPipe 33 点拓扑、父节点表、9 个关节角特征
- [`contracts/rest-api.yaml`](contracts/rest-api.yaml) - 目录与关键点序列 API - [`contracts/rest-api.yaml`](contracts/rest-api.yaml) - 目录与关键点序列 API
- [`contracts/data-models.md`](contracts/data-models.md) - 资源字段与 VRM 优先规则 - [`contracts/data-models.md`](contracts/data-models.md) - 资源字段与 VRM 优先规则
@@ -185,7 +185,7 @@ src/
## 已知限制 ## 已知限制
1. Godot 的 `WebSocketPeer` 不暴露握手请求路径,因此**无法校验 path 是否为 `/pose`**, 1. Godot 的 `WebSocketPeer` 不暴露握手请求路径,因此**无法校验 path 是否为 `/pose`**,
实现接受任意 path,仅校验子协议 `opendance.pose.v1`。 实现接受任意 path,仅校验子协议 `opendance.pose.v1`。
2. 文档原选型 `godot-vrm` GDExtension 不发布预编译产物,改用 Godot 内置 glTF 导入器 2. 文档原选型 `godot-vrm` GDExtension 不发布预编译产物,改用 Godot 内置 glTF 导入器
+ 自实现骨骼名映射;理由见 `DSH.md` 6。 + 自实现骨骼名映射;理由见 `DSH.md` 6。
+17 -17
View File
@@ -43,7 +43,7 @@ OpenDance.exe 的用户看第一节即可。
1. 前置条件 1. 前置条件
- 操作系统:Windows 10/11(x86_64)。开发与导出可在 Windows 或 Linux。 - 操作系统:Windows 10/11(x86_64)。开发与导出可在 Windows 或 Linux。
- Godot 4.7.x 标准版(GDScript)。不需要 .NET / C# 版。 - Godot 4.7.x 标准版(GDScript)。不需要 .NET / C# 版。
- 渲染后端为 GL Compatibility,运行需 OpenGL 3.3 级别的显卡与驱动。 - 渲染后端为 GL Compatibility,运行需 OpenGL 3.3 级别的显卡与驱动。
- 导出模板:仅打包导出时需要,必须与编辑器完全同版本。 - 导出模板:仅打包导出时需要,必须与编辑器完全同版本。
@@ -68,7 +68,7 @@ OpenDance.exe 的用户看第一节即可。
4. 配置服务端地址 4. 配置服务端地址
主界面顶部「服务端地址」填好后点「保存并测试」,配置字段名为 api_base_url: 主界面顶部「服务端地址」填好后点「保存并测试」,配置字段名为 api_base_url:
http://<服务器地址>:3000 http://<服务器地址>:3000
http://192.0.2.10:3000 http://192.0.2.10:3000
@@ -81,7 +81,7 @@ OpenDance.exe 的用户看第一节即可。
- Android 辅助 App -> 本端 WebSocket:本端是服务端,默认监听 - Android 辅助 App -> 本端 WebSocket:本端是服务端,默认监听
0.0.0.0:8765,路径 /pose,子协议 opendance.pose.v1。Android 端填写 0.0.0.0:8765,路径 /pose,子协议 opendance.pose.v1。Android 端填写
本端主界面显示的「接收地址」,形如 ws://<本机局域网地址>:8765/pose。 本端主界面显示的「接收地址」,形如 ws://<本机局域网地址>:8765/pose。
本端可在 opendance.cfg 改 listen_host / listen_port / ws_subprotocol, 本端可在 opendance.cfg 改 listen_host / listen_port / ws_subprotocol,
改后点「重新连接接收服务」生效。 改后点「重新连接接收服务」生效。
- Web 服务端:需先在服务端发布曲目 / 动作 / 模型,动作还须完成关键点 - Web 服务端:需先在服务端发布曲目 / 动作 / 模型,动作还须完成关键点
预处理,客户端才有可选内容。 预处理,客户端才有可选内容。
@@ -94,7 +94,7 @@ OpenDance.exe 的用户看第一节即可。
# Windows(兜底) # Windows(兜底)
powershell -ExecutionPolicy Bypass -File build\export-windows.ps1 powershell -ExecutionPolicy Bypass -File build\export-windows.ps1
产物在 build/windows/:OpenDance.exe(约 104 MB,PE 头 MZ 校验通过) 产物在 build/windows/: OpenDance.exe(约 104 MB, PE 头 MZ 校验通过)
+ OpenDance.pck(约 107 KB,必须与 exe 同目录);可能附带 + OpenDance.pck(约 107 KB,必须与 exe 同目录);可能附带
OpenDance.console.exe。 OpenDance.console.exe。
@@ -121,7 +121,7 @@ OpenDance.exe 的用户看第一节即可。
8. 部署相关常见问题 8. 部署相关常见问题
Q: 模型加载失败:缺少骨骼 Q: 模型加载失败:缺少骨骼
A: 模型无 Skeleton3D,或骨骼命名不在映射表内;换用标准 VRM(VRoid A: 模型无 Skeleton3D,或骨骼命名不在映射表内;换用标准 VRM(VRoid
Studio 导出)。人形覆盖率低于 60% 会提示「骨骼覆盖偏低」。 Studio 导出)。人形覆盖率低于 60% 会提示「骨骼覆盖偏低」。
Q: 连不上服务端 / 显示「服务端不可达」 Q: 连不上服务端 / 显示「服务端不可达」
@@ -130,7 +130,7 @@ OpenDance.exe 的用户看第一节即可。
Q: 导出失败:未找到导出模板 Q: 导出失败:未找到导出模板
A: 安装与编辑器同版本的 export templates。Linux 放 A: 安装与编辑器同版本的 export templates。Linux 放
~/.local/share/godot/export_templates/<版本>.stable/; ~/.local/share/godot/export_templates/<版本>.stable/;
Windows 放 %APPDATA%\Godot\export_templates\<版本>.stable\。 Windows 放 %APPDATA%\Godot\export_templates\<版本>.stable\。
Q: 接收服务启动失败(端口 8765 可能被占用) Q: 接收服务启动失败(端口 8765 可能被占用)
@@ -169,12 +169,12 @@ OpenDance.exe 的用户看第一节即可。
可配置项: 可配置项:
api_base_url Web 服务端地址(默认 http://127.0.0.1:3000) api_base_url Web 服务端地址(默认 http://127.0.0.1:3000)
listen_host WebSocket 监听地址(默认 0.0.0.0) listen_host WebSocket 监听地址(默认 0.0.0.0)
listen_port WebSocket 监听端口(默认 8765) listen_port WebSocket 监听端口(默认 8765)
ws_subprotocol 子协议(默认 opendance.pose.v1) ws_subprotocol 子协议(默认 opendance.pose.v1)
dtw_band DTW 带宽,单位帧(默认 15 ~ 0.5 秒抖动容忍) dtw_band DTW 带宽,单位帧(默认 15 ~ 0.5 秒抖动容忍)
score_tolerance 评分容差:平均每步代价达到该值即 0 分(默认 0.25) score_tolerance 评分容差:平均每步代价达到该值即 0 分(默认 0.25)
mirror_preview 镜像预览 mirror_preview 镜像预览
-- --
@@ -196,9 +196,9 @@ OpenDance.exe 的用户看第一节即可。
- 每帧特征 = 33 点 x (x,y,z) + 9 个关节角余弦 = 108 维 - 每帧特征 = 33 点 x (x,y,z) + 9 个关节角余弦 = 108 维
(与 Web 服务端预处理产物、Android 端推流结构完全一致) (与 Web 服务端预处理产物、Android 端推流结构完全一致)
- 置信度低于 0.5 的关键点不参与代价计算,避免遮挡导致误判 - 置信度低于 0.5 的关键点不参与代价计算,避免遮挡导致误判
- 采用带约束 DTW(|i-j| <= band)对齐,能吸收一定的节奏快慢差异 - 采用带约束 DTW(|i-j| <= band)对齐,能吸收一定的节奏快慢差异
- 得分 = clamp(1 - 平均代价 / 容差, 0, 1) x 100,并做指数平滑 - 得分 = clamp(1 - 平均代价 / 容差, 0, 1) x 100,并做指数平滑
- 等级:S >= 95,A >= 85,B >= 70,C >= 55,D 其余 - 等级:S >= 95, A >= 85, B >= 70, C >= 55, D 其余
-- --
七、从源码构建 七、从源码构建
@@ -212,7 +212,7 @@ OpenDance.exe 的用户看第一节即可。
# Windows 上兜底导出 # Windows 上兜底导出
powershell -ExecutionPolicy Bypass -File build\export-windows.ps1 powershell -ExecutionPolicy Bypass -File build\export-windows.ps1
产物位于 build/windows/:OpenDance.exe + OpenDance.pck 产物位于 build/windows/: OpenDance.exe + OpenDance.pck
(脚本会校验产物存在且为合法 PE 文件) (脚本会校验产物存在且为合法 PE 文件)
运行测试: 运行测试:
@@ -230,8 +230,8 @@ OpenDance.exe 的用户看第一节即可。
export_presets.cfg 导出预设(必须在项目根目录) export_presets.cfg 导出预设(必须在项目根目录)
project.yaml 任务清单与状态 project.yaml 任务清单与状态
DSH.md 本项目 DSH 执行约束 DSH.md 本项目 DSH 执行约束
tasks/ 单任务说明书(T-WIN-001 ~ 011) tasks/ 单任务说明书(T-WIN-001 ~ 011)
contracts/ 只读契约快照(来源 opendance-web v0.1.0) contracts/ 只读契约快照(来源 opendance-web v0.1.0)
scenes/main.tscn 根节点 scenes/main.tscn 根节点
src/autoload/ AppConfig / CatalogStore / PoseReceiver / ScoreService src/autoload/ AppConfig / CatalogStore / PoseReceiver / ScoreService
src/net/ 239 字节编解码、WebSocket 服务、REST 客户端 src/net/ 239 字节编解码、WebSocket 服务、REST 客户端
@@ -255,7 +255,7 @@ A: 动作必须先在管理端完成「预处理」(BVH -> 关键点序列)
Q: 模型加载失败 / 动作不完整 Q: 模型加载失败 / 动作不完整
A: 游戏界面会显示「人形覆盖率」。低于 60% 说明模型的骨骼命名不被识别, A: 游戏界面会显示「人形覆盖率」。低于 60% 说明模型的骨骼命名不被识别,
请换用标准 VRM(VRoid Studio 导出)模型。 请换用标准 VRM(VRoid Studio 导出)模型。
Q: 连上了手机但分数一直是 0 Q: 连上了手机但分数一直是 0
A: 确认手机端已开始推流;主界面的「查看接收状态」会显示累计帧数与丢帧数。 A: 确认手机端已开始推流;主界面的「查看接收状态」会显示累计帧数与丢帧数。
+2 -2
View File
@@ -11,7 +11,7 @@
- 版权:Copyright 2023 Google LLC - 版权:Copyright 2023 Google LLC
- 许可证:Apache License 2.0 - 许可证:Apache License 2.0
- 源链接:https://github.com/google-ai-edge/mediapipe - 源链接:https://github.com/google-ai-edge/mediapipe
- 用途:33 点骨骼拓扑定义(契约来源,**本端不包含其代码**) - 用途:33 点骨骼拓扑定义(契约来源,**本端不包含其代码**)
## 2. Three.js ## 2. Three.js
@@ -109,7 +109,7 @@
## 许可合规说明 ## 许可合规说明
1. 本客户端自身代码以 MIT 许可发布。 1. 本客户端自身代码以 MIT 许可发布。
2. 运行时依赖仅为 **Godot Engine**(MIT);其余条目为同项目其他组件或服务端依赖, 2. 运行时依赖仅为 **Godot Engine**(MIT);其余条目为同项目其他组件或服务端依赖,
按契约要求一并声明。 按契约要求一并声明。
3. 本客户端**未使用 SMPL 模型及其衍生拓扑**,统一采用 MediaPipe 33 点拓扑, 3. 本客户端**未使用 SMPL 模型及其衍生拓扑**,统一采用 MediaPipe 33 点拓扑,
以规避 SMPL 的非商用许可限制。 以规避 SMPL 的非商用许可限制。
+3 -3
View File
@@ -5,7 +5,7 @@ Web 服务端是唯一权威定义,本文件是另两端消费的**稳定视
## 1. 通用约定 ## 1. 通用约定
- 所有资源返回 `id: number`(BIGINT,超出 2^53 前不会发生)。 - 所有资源返回 `id: number`(BIGINT,超出 2^53 前不会发生)。
- 所有时间为 ISO 8601 UTC 字符串,例:`2026-09-14T10:00:00.000Z`。 - 所有时间为 ISO 8601 UTC 字符串,例:`2026-09-14T10:00:00.000Z`。
- 列表接口统一分页响应: - 列表接口统一分页响应:
@@ -117,7 +117,7 @@ rejected 已驳回
``` ```
0. 环境变量 DEFAULT_MODEL_ID 指定,且该模型存在且已发布 0. 环境变量 DEFAULT_MODEL_ID 指定,且该模型存在且已发布
1. 源码内写死的默认模型种子(web-server/src/config/default-model.ts 的 1. 源码内写死的默认模型种子(web-server/src/config/default-model.ts 的
DEFAULT_MODEL_SEED):按 **sourceSha256(上传压缩包的 sha256)** 匹配已发布模型, DEFAULT_MODEL_SEED):按 **sourceSha256(上传压缩包的 sha256)** 匹配已发布模型,
换库/重建库后仍然有效;没有压缩包时退回按产物 sha256,最后按名称兜底。 换库/重建库后仍然有效;没有压缩包时退回按产物 sha256,最后按名称兜底。
⚠ 不能用产物 sha256 当主键:实测 Assimp 的 GLB 导出不是字节级确定的, ⚠ 不能用产物 sha256 当主键:实测 Assimp 的 GLB 导出不是字节级确定的,
同一份 zip 每次转换得到的文件 sha256 都不同(BIN chunk 有十几字节浮动) 同一份 zip 每次转换得到的文件 sha256 都不同(BIN chunk 有十几字节浮动)
@@ -145,7 +145,7 @@ rejected 已驳回
| `comment` | string\|null | 审核意见 | | `comment` | string\|null | 审核意见 |
| `createdAt` | string | | | `createdAt` | string | |
## 6. `CatalogBundle`(客户端一次性拉取,`GET /api/catalog/export`) ## 6. `CatalogBundle`(客户端一次性拉取,`GET /api/catalog/export`)
```json ```json
{ {
+11 -11
View File
@@ -3,11 +3,11 @@
## 环境 ## 环境
- 类型:MySQL 兼容实例(外部提供,含 MariaDB 10.5+),TypeORM - 类型:MySQL 兼容实例(外部提供,含 MariaDB 10.5+),TypeORM
使用 `mysql` driver(`mysql2`)连接,SQL 语法限制在 MySQL 5.7 / MariaDB 10.5+ 使用 `mysql` driver(`mysql2`)连接,SQL 语法限制在 MySQL 5.7 / MariaDB 10.5+
共同子集内,不使用 MySQL 8 专有语法(窗口函数 CTE 递归除外)。 共同子集内,不使用 MySQL 8 专有语法(窗口函数 CTE 递归除外)。
- 数据库名:由部署方提供,通过 `DB_NAME` 注入(本文档示例统一用 `opendance`) - 数据库名:由部署方提供,通过 `DB_NAME` 注入(本文档示例统一用 `opendance`)
- 字符集:`utf8mb4` / `utf8mb4_general_ci`(沿用现有实例默认排序规则) - 字符集:`utf8mb4` / `utf8mb4_general_ci`(沿用现有实例默认排序规则)
- 迁移工具:TypeORM migration,**不使用 `DB_SYNCHRONIZE`** - 迁移工具:TypeORM migration, **不使用 `DB_SYNCHRONIZE`**
## 连接信息(由部署方提供) ## 连接信息(由部署方提供)
@@ -25,10 +25,10 @@ DB_LOGGING=false
## 约定 ## 约定
- 所有表名使用 `snake_case`,复数形式(`songs` / `motions` / `models`) - 所有表名使用 `snake_case`,复数形式(`songs` / `motions` / `models`)
- 主键统一为 `BIGINT AUTO_INCREMENT`,字段名 `id` - 主键统一为 `BIGINT AUTO_INCREMENT`,字段名 `id`
- 时间字段统一 `DATETIME`,`created_at` / `updated_at`,`updated_at` 由应用层维护 - 时间字段统一 `DATETIME`, `created_at` / `updated_at`, `updated_at` 由应用层维护
- 软删除统一使用 `deleted_at DATETIME NULL`(`@DeleteDateColumn`) - 软删除统一使用 `deleted_at DATETIME NULL`(`@DeleteDateColumn`)
- 布尔字段使用 `TINYINT(1)` - 布尔字段使用 `TINYINT(1)`
- 来源(出处)字段为 5 元组,任何资产入库必须齐备: - 来源(出处)字段为 5 元组,任何资产入库必须齐备:
`source_type` / `source_name` / `source_url` / `license` / `author` `source_type` / `source_name` / `source_url` / `license` / `author`
@@ -36,24 +36,24 @@ DB_LOGGING=false
- 每个 migration 必须可逆(提供 `down` 方法) - 每个 migration 必须可逆(提供 `down` 方法)
- 迁移文件名格式:`<timestamp>-<kebab-name>.ts` - 迁移文件名格式:`<timestamp>-<kebab-name>.ts`
## 增量迁移(v0.2.0 / v0.3.0) ## 增量迁移(v0.2.0 / v0.3.0)
| 迁移 | 变更 | | 迁移 | 变更 |
|--|--| |--|--|
| `AddModelConversionFields` | `models` 增加 `source_asset_id BIGINT NULL`(上传的压缩包,用于溯源)与 `conversion_note VARCHAR(500) NULL`(转换说明,见 model-formats.md 1.1) | | `AddModelConversionFields` | `models` 增加 `source_asset_id BIGINT NULL`(上传的压缩包,用于溯源)与 `conversion_note VARCHAR(500) NULL`(转换说明,见 model-formats.md 1.1) |
| `AddModelStats` | `models` 增加 `stats TEXT NULL`(模型分析结果:面数、骨骼树、humanoid 覆盖、可驱动性),供审核界面展示 | | `AddModelStats` | `models` 增加 `stats TEXT NULL`(模型分析结果:面数、骨骼树、humanoid 覆盖、可驱动性),供审核界面展示 |
## 表清单(v0.1.0) ## 表清单(v0.1.0)
| 表 | 用途 | 任务 | | 表 | 用途 | 任务 |
|--|--|--| |--|--|--|
| `songs` | 曲目元数据 | T-WEB-003 | | `songs` | 曲目元数据 | T-WEB-003 |
| `assets` | 上传文件元数据(去重、sha256、物理路径) | T-WEB-004 | | `assets` | 上传文件元数据(去重、sha256、物理路径) | T-WEB-004 |
| `motions` | 动作数据(BVH/FBX...)+ 来源 + 预处理产物 | T-WEB-004 / 006 | | `motions` | 动作数据(BVH/FBX...)+ 来源 + 预处理产物 | T-WEB-004 / 006 |
| `models` | 角色模型(VRM 优先)+ 来源 + 压缩包转换溯源 | T-WEB-005 | | `models` | 角色模型(VRM 优先)+ 来源 + 压缩包转换溯源 | T-WEB-005 |
| `review_records` | 上传审核流水 | T-WEB-007 | | `review_records` | 上传审核流水 | T-WEB-007 |
## 状态机(`status` 列,v0.1.0) ## 状态机(`status` 列,v0.1.0)
``` ```
draft ──submit──▶ pending ──approve──▶ published draft ──submit──▶ pending ──approve──▶ published
+29 -29
View File
@@ -1,21 +1,21 @@
# contracts/model-formats.md - 模型与动作格式契约 v0.4.0 # contracts/model-formats.md - 模型与动作格式契约 v0.4.0
## 1. 角色模型(models) ## 1. 角色模型(models)
**系统以 GLB / VRM 为主力格式**(两个客户端都原生加载),其余格式一律在服务端转换。 **系统以 GLB / VRM 为主力格式**(两个客户端都原生加载),其余格式一律在服务端转换。
| 格式 | 扩展名 | 支持级别 | 服务端行为 | | 格式 | 扩展名 | 支持级别 | 服务端行为 |
|--|--|--|--| |--|--|--|--|
| glTF Binary | `.glb` | **主力(P0)** | **直接导入**,不重编码 | | glTF Binary | `.glb` | **主力(P0)** | **直接导入**,不重编码 |
| VRM | `.vrm` | **主力(P0)** | **直接导入**;= GLB + VRM 扩展(humanoid / 表情 / springBone / meta) | | VRM | `.vrm` | **主力(P0)** | **直接导入**; = GLB + VRM 扩展(humanoid / 表情 / springBone / meta) |
| glTF | `.gltf` (+ `.bin`, 贴图) | P1 | 直接导入(分离文件在 zip 内可用;单文件上传会缺.bin) | | glTF | `.gltf` (+ `.bin`, 贴图) | P1 | 直接导入(分离文件在 zip 内可用;单文件上传会缺.bin) |
| MMD 压缩包 | `.zip` | P1 | **自动搜索包内可用模型**:优先直接导入 `.vrm`/`.glb`/`.gltf`,否则转换 `.pmx`/`.fbx`,见 1.1 | | MMD 压缩包 | `.zip` | P1 | **自动搜索包内可用模型**:优先直接导入 `.vrm`/`.glb`/`.gltf`,否则转换 `.pmx`/`.fbx`,见 1.1 |
| MMD 模型 | `.pmx` | P2(输入) | 服务端转换(优先 GLB、其次 VRM);单文件上传无贴图 | | MMD 模型 | `.pmx` | P2(输入) | 服务端转换(优先 GLB、其次 VRM);单文件上传无贴图 |
| FBX | `.fbx` | P2(输入) | 服务端转换(优先 GLB、其次 VRM) | | FBX | `.fbx` | P2(输入) | 服务端转换(优先 GLB、其次 VRM) |
### 1.1 上传与转换策略 ### 1.1 上传与转换策略
**`.glb` / `.vrm` / `.gltf` 直接导入**(不重编码,零损失);**`.pmx` / `.fbx` 由服务端转换**: **`.glb` / `.vrm` / `.gltf` 直接导入**(不重编码,零损失);**`.pmx` / `.fbx` 由服务端转换**:
**优先 GLB,其次 VRM**。 **优先 GLB,其次 VRM**。
#### 单文件上传 #### 单文件上传
@@ -28,7 +28,7 @@
#### 压缩包上传(自动搜索) #### 压缩包上传(自动搜索)
不再"只找 `.pmx`",而是**按格式优先级自动搜索包内可用模型**: 不再"只找 `.pmx`",而是**按格式优先级自动搜索包内可用模型**:
``` ```
.vrm ->.glb ->.gltf (直接导入,优先) .vrm ->.glb ->.gltf (直接导入,优先)
@@ -38,7 +38,7 @@
同一优先级内:`MODEL_CONVERT_PMX` 指定的文件名优先,其次目录最浅,最后文件最大。 同一优先级内:`MODEL_CONVERT_PMX` 指定的文件名优先,其次目录最浅,最后文件最大。
压缩包原件以 `model-archive` 资产留存并写入 `models.source_asset_id`。 压缩包原件以 `model-archive` 资产留存并写入 `models.source_asset_id`。
#### 转换与校验(PMX / FBX) #### 转换与校验(PMX / FBX)
| 步骤 | 规则 | | 步骤 | 规则 |
|--|--| |--|--|
@@ -46,7 +46,7 @@
| 2. glTF 打包 | 上一步失败时 `assimp export ->.gltf`,由 `gltf-packer.ts` 把 `.gltf` + `.bin` + 贴图自行合成 GLB | | 2. glTF 打包 | 上一步失败时 `assimp export ->.gltf`,由 `gltf-packer.ts` 把 `.gltf` + `.bin` + 贴图自行合成 GLB |
| 3. VRM 封装 | `MODEL_CONVERT_FORMAT=vrm` 时,在 GLB 之上补 `VRMC_vrm` 扩展(humanoid 骨骼来自骨骼名映射,见 `model-inspector.ts` 的候选表,与 Godot 客户端一致);**humanoid 不全则回退交付 GLB**,缺失骨骼写进 `conversion_note` | | 3. VRM 封装 | `MODEL_CONVERT_FORMAT=vrm` 时,在 GLB 之上补 `VRMC_vrm` 扩展(humanoid 骨骼来自骨骼名映射,见 `model-inspector.ts` 的候选表,与 Godot 客户端一致);**humanoid 不全则回退交付 GLB**,缺失骨骼写进 `conversion_note` |
| 回读校验 | 每一步产物都要 `assimp info` 回读。Assimp 会"写出损坏文件"(实测 FBX 导出退出码 11、产物回读报 `FBX-DOM no Objects dictionary found`),只看退出码会把坏文件入库 | | 回读校验 | 每一步产物都要 `assimp info` 回读。Assimp 会"写出损坏文件"(实测 FBX 导出退出码 11、产物回读报 `FBX-DOM no Objects dictionary found`),只看退出码会把坏文件入库 |
| 说明 | 每次尝试都写进 `models.conversion_note`(例如 `Hatsune Miku.pmx -> GLB,已内嵌 2 张贴图`) | | 说明 | 每次尝试都写进 `models.conversion_note`(例如 `Hatsune Miku.pmx -> GLB,已内嵌 2 张贴图`) |
| 失败 | 包内没有可用模型、Assimp 不可用、两条路径都失败 -> **HTTP 422**,不产生模型记录 | | 失败 | 包内没有可用模型、Assimp 不可用、两条路径都失败 -> **HTTP 422**,不产生模型记录 |
#### 入库后(自动) #### 入库后(自动)
@@ -63,14 +63,14 @@
| 项 | 规则 | | 项 | 规则 |
|--|--| |--|--|
| 确定性 | **转换产物不是字节级确定的**:同一份 zip 连续转换 3 次得到 3 个不同 sha256(大小相同,BIN chunk 有十几字节浮动)。因此默认模型种子以**上传压缩包的 sha256** 为匹配键,产物 sha256 只作记录 | | 确定性 | **转换产物不是字节级确定的**:同一份 zip 连续转换 3 次得到 3 个不同 sha256(大小相同,BIN chunk 有十几字节浮动)。因此默认模型种子以**上传压缩包的 sha256** 为匹配键,产物 sha256 只作记录 |
| 安全 | 解压前逐条校验路径,拒绝绝对路径与 `..`(zip-slip);拒绝加密条目;只支持 STORE / DEFLATE | | 安全 | 解压前逐条校验路径,拒绝绝对路径与 `..`(zip-slip);拒绝加密条目;只支持 STORE / DEFLATE |
**VRM 优先**的选取顺序见 `data-models.md` 4.1。 **VRM 优先**的选取顺序见 `data-models.md` 4.1。
VRM 版本识别:读取 GLB 的 JSON chunk,检查 `extensions.VRM`(0.x)或 VRM 版本识别:读取 GLB 的 JSON chunk,检查 `extensions.VRM`(0.x)或
`extensions.VRMC_vrm`(1.0),写入 `models.vrm_version`。 `extensions.VRMC_vrm`(1.0),写入 `models.vrm_version`。
## 2. 动作数据(motions) ## 2. 动作数据(motions)
| 格式 | 扩展名 | 预处理支持 | 说明 | | 格式 | 扩展名 | 预处理支持 | 说明 |
|--|--|--|--| |--|--|--|--|
@@ -82,7 +82,7 @@ VRM 版本识别:读取 GLB 的 JSON chunk,检查 `extensions.VRM`(0.x)
### 2.1 预处理产物(冻结) ### 2.1 预处理产物(冻结)
- 格式:NDJSON(`application/x-ndjson`),每行一帧,UTF-8 无 BOM - 格式:NDJSON(`application/x-ndjson`),每行一帧,UTF-8 无 BOM
- 一行结构: - 一行结构:
```json ```json
@@ -93,9 +93,9 @@ VRM 版本识别:读取 GLB 的 JSON chunk,检查 `extensions.VRM`(0.x)
- `angles` 为 `skeleton-topology.md` 5 定义的 9 个关节角余弦,顺序固定: - `angles` 为 `skeleton-topology.md` 5 定义的 9 个关节角余弦,顺序固定:
`[shoulder_L, shoulder_R, elbow_L, elbow_R, hip_L, hip_R, knee_L, knee_R, spine]` `[shoulder_L, shoulder_R, elbow_L, elbow_R, hip_L, hip_R, knee_L, knee_R, spine]`
- 帧率:保留源帧率;`fps` 写入 `motions.fps`,缺省 30 - 帧率:保留源帧率;`fps` 写入 `motions.fps`,缺省 30
- 坐标系换算:所有 3D 动作数据统一转到 **Y 轴向上、右手系、骨盆为原点**, - 坐标系换算:所有 3D 动作数据统一转到 **Y 轴向上、右手系、骨盆为原点**,
再投影到 MediaPipe 归一化图像空间: 再投影到 MediaPipe 归一化图像空间:
`x_img = 0.5 + x_world * scale`,`y_img = 0.5 - y_world * scale`,`z_img = -z_world * scale`, `x_img = 0.5 + x_world * scale`, `y_img = 0.5 - y_world * scale`, `z_img = -z_world * scale`,
`scale = 0.35 / max(|x|,|y|,|z|)`(按整段动作的最大范围自适应,保证不出画) `scale = 0.35 / max(|x|,|y|,|z|)`(按整段动作的最大范围自适应,保证不出画)
- 缺失关节(BVH 中不存在对应骨骼):`confidence = 0.0`,坐标沿用最近可用父节点 - 缺失关节(BVH 中不存在对应骨骼):`confidence = 0.0`,坐标沿用最近可用父节点
@@ -122,18 +122,18 @@ VRM 版本识别:读取 GLB 的 JSON chunk,检查 `extensions.VRM`(0.x)
未映射到的点按 `skeleton-topology.md` 5 的层级由父节点推算, 未映射到的点按 `skeleton-topology.md` 5 的层级由父节点推算,
并置 `confidence` 降低(`0.5`),表示估算而非实测。 并置 `confidence` 降低(`0.5`),表示估算而非实测。
### 2.3 VMD(MikuMikuDance)解析约定 ### 2.3 VMD(MikuMikuDance)解析约定
VMD 是二进制格式:文件头 `Vocaloid Motion Data 0002`(30 字节)+ 模型名(20 字节, VMD 是二进制格式:文件头 `Vocaloid Motion Data 0002`(30 字节)+ 模型名(20 字节,
Shift-JIS)+ 骨骼关键帧数组(每条 **111 字节**:15 字节骨骼名、uint32 帧号、 Shift-JIS)+ 骨骼关键帧数组(每条 **111 字节**: 15 字节骨骼名、uint32 帧号、
float32x3 位移、float32x4 旋转四元数、64 字节贝塞尔插值参数)+ morph / 相机 / float32x3 位移、float32x4 旋转四元数、64 字节贝塞尔插值参数)+ morph / 相机 /
灯光 / 自阴影 / IK / 可见性段落。**VMD 不含骨骼长度**,只有旋转(少数骨骼带位移), 灯光 / 自阴影 / IK / 可见性段落。**VMD 不含骨骼长度**,只有旋转(少数骨骼带位移),
因此服务端必须自带一套标准 MMD 骨架才能做 FK。 因此服务端必须自带一套标准 MMD 骨架才能做 FK。
处理链路:`vmd-parser.ts`(二进制 + Shift-JIS + 贝塞尔插值)-> `mmd-skeleton.ts` 处理链路:`vmd-parser.ts`(二进制 + Shift-JIS + 贝塞尔插值)-> `mmd-skeleton.ts`
(标准骨架)-> `mmd-pose.ts`(FK + 腿部 IK)-> `retarget.ts` 的 (标准骨架)-> `mmd-pose.ts`(FK + 腿部 IK)-> `retarget.ts` 的
`worldFramesToKeypointFrames()`(与 BVH 完全共用骨盆归零 / 自适应缩放 / 图像空间投影 / `worldFramesToKeypointFrames()`(与 BVH 完全共用骨盆归零 / 自适应缩放 / 图像空间投影 /
9 个关节角)。固定 **30 fps**(MMD 帧率恒定,上传时填写的 `fps` 被忽略)。 9 个关节角)。固定 **30 fps**(MMD 帧率恒定,上传时填写的 `fps` 被忽略)。
| MediaPipe 点 | MMD 标准骨骼 | 说明 | | MediaPipe 点 | MMD 标准骨骼 | 说明 |
|--|--|--| |--|--|--|
@@ -149,11 +149,11 @@ float32x3 位移、float32x4 旋转四元数、64 字节贝塞尔插值参数)
骨骼名按 `mmd-skeleton.ts` 的别名表归一(含全角 `IK` / 半角 `IK` 两种写法); 骨骼名按 `mmd-skeleton.ts` 的别名表归一(含全角 `IK` / 半角 `IK` 两种写法);
未纳入标准骨架的骨骼(裙摆、物理、表情等)被忽略但会计数并写入预处理日志。 未纳入标准骨架的骨骼(裙摆、物理、表情等)被忽略但会计数并写入预处理日志。
**腿部驱动判定**:`足IK` 轨道存在非零位移 -> 走 IK(用两骨骼解析 IK 把踝拉到 IK **腿部驱动判定**: `足IK` 轨道存在非零位移 -> 走 IK(用两骨骼解析 IK 把踝拉到 IK
目标点,膝盖朝角色正面弯,不可达时收敛为伸直);否则走 FK(部分动作数据把腿烘焙成 目标点,膝盖朝角色正面弯,不可达时收敛为伸直);否则走 FK(部分动作数据把腿烘焙成
FK 旋转)。两份示例文件分别命中两种路径。 FK 旋转)。两份示例文件分别命中两种路径。
**坐标系**:MMD 原生是左手系(正面为 -Z),本项目通路是右手系(正面为 +Z、+X 为角色 **坐标系**: MMD 原生是左手系(正面为 -Z),本项目通路是右手系(正面为 +Z、+X 为角色
左侧)。VMD 的四元数与位移统一做 Z 镜像(`(x,y,z,w) -> (-x,-y,z,w)`、位移 z 取反)后 左侧)。VMD 的四元数与位移统一做 Z 镜像(`(x,y,z,w) -> (-x,-y,z,w)`、位移 z 取反)后
送入标准骨架。该选择由真实数据对照实验确定:腿部烘焙为 FK 的样本中,`zMirrored` 下 送入标准骨架。该选择由真实数据对照实验确定:腿部烘焙为 FK 的样本中,`zMirrored` 下
膝盖朝前占比 94.1%,不转换时仅 5.9%(见 `test/mmd-vmd.spec.ts`)。 膝盖朝前占比 94.1%,不转换时仅 5.9%(见 `test/mmd-vmd.spec.ts`)。
@@ -167,15 +167,15 @@ FK 旋转)。两份示例文件分别命中两种路径。
| 项 | 值 | | 项 | 值 |
|--|--| |--|--|
| 单文件上限 | 512 MiB(`UPLOAD_MAX_BYTES` 可覆盖) | | 单文件上限 | 512 MiB(`UPLOAD_MAX_BYTES` 可覆盖) |
| 允许扩展名 | 上表全部;其他一律 422 | | 允许扩展名 | 上表全部;其他一律 422 |
| 存储路径 | `${STORAGE_DIR}/{kind}/{yyyy}/{mm}/{sha256前2位}/{sha256}.{ext}` | | 存储路径 | `${STORAGE_DIR}/{kind}/{yyyy}/{mm}/{sha256前2位}/{sha256}.{ext}` |
| 去重 | 相同 sha256 复用同一 `assets` 记录,不重复落盘 | | 去重 | 相同 sha256 复用同一 `assets` 记录,不重复落盘 |
| 文件名 | 服务端不使用原始文件名做路径,只存 `assets.original_name` | | 文件名 | 服务端不使用原始文件名做路径,只存 `assets.original_name` |
## 4. 开源依赖与许可证(引用 `open-source-credits.md`) ## 4. 开源依赖与许可证(引用 `open-source-credits.md`)
- `bvh`(MIT)- BVH 解析参考实现 - `bvh`(MIT)- BVH 解析参考实现
- `Assimp`(BSD-3-Clause)- FBX/glTF 转换,**可选外部依赖** - `Assimp`(BSD-3-Clause)- FBX/glTF 转换,**可选外部依赖**
- `@pixiv/three-vrm`(MIT)- 管理端 VRM 预览 - `@pixiv/three-vrm`(MIT)- 管理端 VRM 预览
- `three`(MIT)- 管理端 3D 预览 - `three`(MIT)- 管理端 3D 预览
+7 -7
View File
@@ -47,7 +47,7 @@
| 33 | neck | `midpoint(left_shoulder(11), right_shoulder(12))` | | 33 | neck | `midpoint(left_shoulder(11), right_shoulder(12))` |
| 34 | pelvis | `midpoint(left_hip(23), right_hip(24))` | | 34 | pelvis | `midpoint(left_hip(23), right_hip(24))` |
## 3. 父节点表(`parent[i]`,-1 表示世界根) ## 3. 父节点表(`parent[i]`, -1 表示世界根)
对称树,便于骨骼重定向与关节角计算: 对称树,便于骨骼重定向与关节角计算:
@@ -91,16 +91,16 @@
## 4. 坐标系 ## 4. 坐标系
- **归一化图像坐标**:`x, y ∈ [0, 1]`,原点左上角,`y` 向下;`x` 相对图像宽,`y` 相对图像高。 - **归一化图像坐标**: `x, y ∈ [0, 1]`,原点左上角,`y` 向下;`x` 相对图像宽,`y` 相对图像高。
- **深度**:`z` 与 `x` 同尺度,原点位于髋中心,**负值朝向相机**。 - **深度**: `z` 与 `x` 同尺度,原点位于髋中心,**负值朝向相机**。
- **镜像**:Android 前置摄像头默认输出镜像画面;是否镜像由帧头 `flags.bit0` 声明, - **镜像**: Android 前置摄像头默认输出镜像画面;是否镜像由帧头 `flags.bit0` 声明,
接收端据此决定是否翻转 `x`(`x' = 1 - x`)。 接收端据此决定是否翻转 `x`(`x' = 1 - x`)。
- **置信度**:`visibility ∈ [0, 1]`,落盘/传输为 `uint8`(`round(v * 255)`)。 - **置信度**: `visibility ∈ [0, 1]`,落盘/传输为 `uint8`(`round(v * 255)`)。
归一化比例统一使用 `32767`(见 `websocket-keypoints.md`)。 归一化比例统一使用 `32767`(见 `websocket-keypoints.md`)。
## 5. 派生特征(DTW 输入,Windows 端实现,服务端预处理产物同构) ## 5. 派生特征(DTW 输入,Windows 端实现,服务端预处理产物同构)
服务端 T-WEB-006 预处理产出的关键点序列与 Android 端输出的帧**结构完全一致**, 服务端 T-WEB-006 预处理产出的关键点序列与 Android 端输出的帧**结构完全一致**,
因此 Windows 端 DTW 引擎只有一条输入通路。 因此 Windows 端 DTW 引擎只有一条输入通路。
关节角特征(弧度,余弦值便于插值): 关节角特征(弧度,余弦值便于插值):
+6 -6
View File
@@ -10,17 +10,17 @@ ws://<windows-host>:8765/pose
``` ```
- Windows 客户端监听,Android App 主动连接。 - Windows 客户端监听,Android App 主动连接。
- 子协议:`opendance.pose.v1`(`Sec-WebSocket-Protocol`) - 子协议:`opendance.pose.v1`(`Sec-WebSocket-Protocol`)
- 二进制帧传输关键点,文本帧传输控制消息(JSON)。 - 二进制帧传输关键点,文本帧传输控制消息(JSON)。
- 默认 30 FPS;发送端允许丢帧,接收端必须按 `frame_id` 检测丢包并插值补齐。 - 默认 30 FPS;发送端允许丢帧,接收端必须按 `frame_id` 检测丢包并插值补齐。
## 2. 二进制帧:239 字节(冻结) ## 2. 二进制帧:239 字节(冻结)
**小端序(little-endian)**。 **小端序(little-endian)**。
``` ```
偏移 长度 字段 类型 说明 偏移 长度 字段 类型 说明
0 2 magic u16 LE 固定 0x4F44(ASCII "OD") 0 2 magic u16 LE 固定 0x4F44(ASCII "OD")
2 1 version u8 固定 0x01 2 1 version u8 固定 0x01
3 1 flags u8 bit0 = 镜像画面(mirrored) 3 1 flags u8 bit0 = 镜像画面(mirrored)
bit1 = 本帧含低置信度点(<0.5) bit1 = 本帧含低置信度点(<0.5)
@@ -32,7 +32,7 @@ ws://<windows-host>:8765/pose
合计 239 合计 239
``` ```
### 单个关键点(7 字节,`i` 为 0..32) ### 单个关键点(7 字节,`i` 为 0..32)
``` ```
偏移 长度 字段 类型 换算 偏移 长度 字段 类型 换算
@@ -43,7 +43,7 @@ ws://<windows-host>:8765/pose
``` ```
- 点索引与坐标语义见 `skeleton-topology.md`。 - 点索引与坐标语义见 `skeleton-topology.md`。
- 编码:`x = clamp(round(x_norm * 32767), -32768, 32767)`,`y`、`z` 同理。 - 编码:`x = clamp(round(x_norm * 32767), -32768, 32767)`, `y`、`z` 同理。
- 低置信度点(`conf < 128`)坐标无意义,接收端必须插值,不得直接使用。 - 低置信度点(`conf < 128`)坐标无意义,接收端必须插值,不得直接使用。
- `i16` 为有符号数:`0x8000` 表示 `-32768`。 - `i16` 为有符号数:`0x8000` 表示 `-32768`。
@@ -68,7 +68,7 @@ func decode(buf: PackedByteArray) -> Dictionary:
return {"flags": flags, "frame_id": frame_id, "points": pts} return {"flags": flags, "frame_id": frame_id, "points": pts}
``` ```
## 3. 文本控制帧(JSON,UTF-8) ## 3. 文本控制帧(JSON, UTF-8)
### 3.1 `hello` - Android -> Windows,连接后立即发送 ### 3.1 `hello` - Android -> Windows,连接后立即发送
+2 -2
View File
@@ -1,6 +1,6 @@
# T-WIN-001 Godot 项目骨架 # T-WIN-001 Godot 项目骨架
- **状态**:done - **状态**: done
- **依赖**:无 - **依赖**:无
## 目标 ## 目标
@@ -22,7 +22,7 @@ scenes/main.tscn
| 文件 | 说明 | | 文件 | 说明 |
|--|--| |--|--|
| `project.godot` | 工程配置;autoload = AppConfig / CatalogStore / PoseReceiver / ScoreService | | `project.godot` | 工程配置;autoload = AppConfig / CatalogStore / PoseReceiver / ScoreService |
| `scenes/main.tscn` | 只含一个挂载 `src/ui/main.gd` 的 Control 根节点(界面用代码构建,见 DSH.md 6) | | `scenes/main.tscn` | 只含一个挂载 `src/ui/main.gd` 的 Control 根节点(界面用代码构建,见 DSH.md 6) |
| `src/ui/main.gd` | 应用外壳:构建布局、切换页面、启动接收服务 | | `src/ui/main.gd` | 应用外壳:构建布局、切换页面、启动接收服务 |
| `src/autoload/app_config.gd` | 客户端配置,持久化到 `user://opendance.cfg` | | `src/autoload/app_config.gd` | 客户端配置,持久化到 `user://opendance.cfg` |
| `src/autoload/catalog_store.gd` | 目录缓存 + 模型下载缓存 + 关键点序列缓存 | | `src/autoload/catalog_store.gd` | 目录缓存 + 模型下载缓存 + 关键点序列缓存 |
+2 -2
View File
@@ -1,7 +1,7 @@
# T-WIN-002 调用 Web 服务端 API # T-WIN-002 调用 Web 服务端 API
- **状态**:done - **状态**: done
- **依赖**:T-WIN-001 - **依赖**: T-WIN-001
## 目标 ## 目标
+4 -4
View File
@@ -1,7 +1,7 @@
# T-WIN-003 接收 Android 关键点帧 # T-WIN-003 接收 Android 关键点帧
- **状态**:done - **状态**: done
- **依赖**:T-WIN-001 - **依赖**: T-WIN-001
## 目标 ## 目标
@@ -18,7 +18,7 @@ src/autoload/pose_receiver.gd
## 交付物 ## 交付物
- `src/net/pose_server.gd`:TCPServer + `WebSocketPeer.accept_stream()` 实现服务端 - `src/net/pose_server.gd`: TCPServer + `WebSocketPeer.accept_stream()` 实现服务端
- `src/autoload/pose_receiver.gd`:绑定 AppConfig,暴露 `latest_frame` 与统计 - `src/autoload/pose_receiver.gd`:绑定 AppConfig,暴露 `latest_frame` 与统计
## 能力 ## 能力
@@ -36,7 +36,7 @@ src/autoload/pose_receiver.gd
## 已知限制 ## 已知限制
Godot 的 `WebSocketPeer` 不暴露握手请求路径,因此**无法校验 path 是否为 `/pose`**, Godot 的 `WebSocketPeer` 不暴露握手请求路径,因此**无法校验 path 是否为 `/pose`**,
实现接受任意 path,仅校验子协议。已记录于 DSH.md 与任务文档。 实现接受任意 path,仅校验子协议。已记录于 DSH.md 与任务文档。
## 验收 ## 验收
+6 -6
View File
@@ -1,7 +1,7 @@
# T-WIN-004 239 字节帧反序列化 # T-WIN-004 239 字节帧反序列化
- **状态**:done - **状态**: done
- **依赖**:T-WIN-001 - **依赖**: T-WIN-001
## 目标 ## 目标
@@ -24,20 +24,20 @@ src/net/keypoint_frame.gd
2 1 version u8 = 1 2 1 version u8 = 1
3 1 flags u8 bit0 镜像 bit1 低置信度 bit2 上半身 3 1 flags u8 bit0 镜像 bit1 低置信度 bit2 上半身
4 4 frame_id u32 LE 4 4 frame_id u32 LE
8 231 keypoints 33 x 7B:s16 x, s16 y, s16 z, u8 conf 8 231 keypoints 33 x 7B:s16 x, s16 y, s16 z, u8 conf
合计 239 合计 239
``` ```
## 交付物 ## 交付物
`src/net/keypoint_frame.gd`:`PoseFrame` 内部类 + `decode` / `encode` / `validate` / `from_json_dict`。 `src/net/keypoint_frame.gd`: `PoseFrame` 内部类 + `decode` / `encode` / `validate` / `from_json_dict`。
## 关键实现 ## 关键实现
- 归一化系数统一 32767,与 opendance-web 的 TypeScript 参考实现逐位对齐 - 归一化系数统一 32767,与 opendance-web 的 TypeScript 参考实现逐位对齐
- 编解码往返误差 < 1e-4(定点精度 1/32767 ~ 3.05e-5) - 编解码往返误差 < 1e-4(定点精度 1/32767 ~ 3.05e-5)
- `unmirrored_points()` 按 flags.bit0 翻转 x - `unmirrored_points()` 按 flags.bit0 翻转 x
- 低置信度阈值 0.5:`is_reliable()` 与 `compute_low_confidence()` - 低置信度阈值 0.5: `is_reliable()` 与 `compute_low_confidence()`
- 极值(±1.0)不溢出 int16 - 极值(±1.0)不溢出 int16
## 验收 ## 验收
+9 -9
View File
@@ -1,7 +1,7 @@
# T-WIN-005 VRM 模型加载 # T-WIN-005 VRM 模型加载
- **状态**:done - **状态**: done
- **依赖**:T-WIN-001 - **依赖**: T-WIN-001
## 目标 ## 目标
@@ -19,23 +19,23 @@ src/model/humanoid_map.gd
## 选型变更 ## 选型变更
文档原选型 `godot-vrm`(V-Sekai)GDExtension **不发布任何预编译产物**, 文档原选型 `godot-vrm`(V-Sekai)GDExtension **不发布任何预编译产物**,
需用 godot-cpp 本地编译原生库,会破坏「Linux 导出 Windows exe」的验收路径。 需用 godot-cpp 本地编译原生库,会破坏「Linux 导出 Windows exe」的验收路径。
改用 **Godot 内置 GLTFDocument**:VRM 即 glTF 2.0 容器,实测可完整解析 改用 **Godot 内置 GLTFDocument**: VRM 即 glTF 2.0 容器,实测可完整解析
154 骨骼的 VRoid 模型,且 `GLTFState.json` 可直接读取 VRM 扩展判定版本。 154 骨骼的 VRoid 模型,且 `GLTFState.json` 可直接读取 VRM 扩展判定版本。
## 骨骼名映射 ## 骨骼名映射
`HumanoidMap` 覆盖 VRoid / VRM 0.x / Mixamo 三类命名,归一化后精确比较: `HumanoidMap` 覆盖 VRoid / VRM 0.x / Mixamo 三类命名,归一化后精确比较:
- 剥离前缀 `jbip` / `mixamorig` / `bip01`(**归一化之后**的形态) - 剥离前缀 `jbip` / `mixamorig` / `bip01`(**归一化之后**的形态)
- 生成 compact(left->l)与 expanded(l->left)两种变体比较 - 生成 compact(left->l)与 expanded(l->left)两种变体比较
- **故意不剥离 `J_Aim_` / `J_Roll_` / `J_Sec_`**:VRoid 用它们标记扭转辅助骨, - **故意不剥离 `J_Aim_` / `J_Roll_` / `J_Sec_`**: VRoid 用它们标记扭转辅助骨,
剥离后 `J_Aim_L_UpperLeg` 会与真正的 `J_Bip_L_UpperLeg` 撞名 剥离后 `J_Aim_L_UpperLeg` 会与真正的 `J_Bip_L_UpperLeg` 撞名
## 验收 ## 验收
`tests/test_vrm_loader.gd`: `tests/test_vrm_loader.gd`:
- 三类命名规范的识别(含辅助骨不遮蔽真骨的回归用例) - 三类命名规范的识别(含辅助骨不遮蔽真骨的回归用例)
- 真实 VRM:154 骨骼 / 3 网格 / VRM 1.0,**humanoid 覆盖率 100%** - 真实 VRM: 154 骨骼 / 3 网格 / VRM 1.0, **humanoid 覆盖率 100%**
- 缺失文件、普通 glTF 不误判 - 缺失文件、普通 glTF 不误判
+6 -6
View File
@@ -1,7 +1,7 @@
# T-WIN-006 关键点 -> VRM 骨骼重定向 # T-WIN-006 关键点 -> VRM 骨骼重定向
- **状态**:done - **状态**: done
- **依赖**:T-WIN-005、T-WIN-004 - **依赖**: T-WIN-005、T-WIN-004
## 目标 ## 目标
@@ -18,20 +18,20 @@ src/anim/skeleton_retarget.gd
## 算法 ## 算法
1. 每根骨骼指定一条骨链(起点关键点,终点关键点) 1. 每根骨骼指定一条骨链(起点关键点,终点关键点)
2. 取骨骼静止朝向 `rest_dir`(由该骨与首个子骨的位置差得出,**不假设模型骨骼轴向**) 2. 取骨骼静止朝向 `rest_dir`(由该骨与首个子骨的位置差得出,**不假设模型骨骼轴向**)
3. 目标朝向由两个关键点在世界空间的方向得出 3. 目标朝向由两个关键点在世界空间的方向得出
4. `delta` = rest_dir -> target_dir 的最小旋转四元数(保留扭转/roll) 4. `delta` = rest_dir -> target_dir 的最小旋转四元数(保留扭转/roll)
5. `desired = Basis(delta) x rest_global_basis` 5. `desired = Basis(delta) x rest_global_basis`
6. `pose_local = parent_global_basis⁻¹ x desired` 6. `pose_local = parent_global_basis⁻¹ x desired`
**第 6 步的合成公式是实测标定的**:Godot 文档未写明 rest/pose 的合成顺序, **第 6 步的合成公式是实测标定的**: Godot 文档未写明 rest/pose 的合成顺序,
在真实 VRoid 模型上暴力枚举 5 种候选公式,只有该式误差为 1.9e-8(其余 ~0.14)。 在真实 VRoid 模型上暴力枚举 5 种候选公式,只有该式误差为 1.9e-8(其余 ~0.14)。
已有回归测试守护,不得凭记忆改写。 已有回归测试守护,不得凭记忆改写。
## 坐标转换 ## 坐标转换
契约 4 的 MediaPipe 归一化坐标 -> Godot 世界方向: 契约 4 的 MediaPipe 归一化坐标 -> Godot 世界方向:
`Vector3(x x aspect, -y, -z x aspect)`(x/z 以图像宽为基准故乘宽高比)。 `Vector3(x x aspect, -y, -z x aspect)`(x/z 以图像宽为基准故乘宽高比)。
## 验收 ## 验收
+4 -4
View File
@@ -1,7 +1,7 @@
# T-WIN-007 DTW 评分引擎 # T-WIN-007 DTW 评分引擎
- **状态**:done - **状态**: done
- **依赖**:T-WIN-004、T-WIN-002 - **依赖**: T-WIN-004、T-WIN-002
## 目标 ## 目标
@@ -30,9 +30,9 @@ src/score/scoring.gd
每帧只计算带内一行,总体复杂度 O(n m band) 但摊到每帧 O(m band) 每帧只计算带内一行,总体复杂度 O(n m band) 但摊到每帧 O(m band)
- 归一化:代价 / max(i,j)+1,得到「平均每步代价」 - 归一化:代价 / max(i,j)+1,得到「平均每步代价」
## 评分规则(`scoring.gd`) ## 评分规则(`scoring.gd`)
`score = clamp(1 - cost/tolerance, 0, 1) x 100`,EMA 平滑抑制抖动, `score = clamp(1 - cost/tolerance, 0, 1) x 100`, EMA 平滑抑制抖动,
等级 S(>=95) / A(>=85) / B(>=70) / C(>=55) / D,连击阈值 70。 等级 S(>=95) / A(>=85) / B(>=70) / C(>=55) / D,连击阈值 70。
## 验收 ## 验收
+3 -3
View File
@@ -1,11 +1,11 @@
# T-WIN-008 主界面 + 游戏界面 + 结算 # T-WIN-008 主界面 + 游戏界面 + 结算
- **状态**:done - **状态**: done
- **依赖**:T-WIN-002、T-WIN-003、T-WIN-006、T-WIN-007 - **依赖**: T-WIN-002、T-WIN-003、T-WIN-006、T-WIN-007
## 目标 ## 目标
三页式体验:主菜单(选曲/选动作/选模型)-> 游戏页(3D 角色 + 实时评分 HUD)-> 结算页。 三页式体验:主菜单(选曲/选动作/选模型)-> 游戏页(3D 角色 + 实时评分 HUD)-> 结算页。
## load ## load
+4 -4
View File
@@ -1,7 +1,7 @@
# T-WIN-009 来源信息展示 # T-WIN-009 来源信息展示
- **状态**:done - **状态**: done
- **依赖**:T-WIN-008 - **依赖**: T-WIN-008
## 目标 ## 目标
@@ -26,7 +26,7 @@ src/net/catalog_types.gd
- 资产名 | 来源名称 - 资产名 | 来源名称
- 作者 - 作者
- 许可 + **可点击外链**跳转到 `sourceUrl` - 许可 + **可点击外链**跳转到 `sourceUrl`
- **五元组不齐备时显式告警**("⚠ 来源信息不完整,请联系管理员补全后再使用"),不静默展示 - **五元组不齐备时显式告警**("⚠ 来源信息不完整,请联系管理员补全后再使用"),不静默展示
## 使用位置 ## 使用位置
@@ -37,4 +37,4 @@ src/net/catalog_types.gd
## 验收 ## 验收
`tests/test_api_integration.gd` 对真实服务端断言曲目与模型的来源五元组齐备 `tests/test_api_integration.gd` 对真实服务端断言曲目与模型的来源五元组齐备
(`song.source.is_complete()`),保证展示组件拿到的数据完整。 (`song.source.is_complete()`),保证展示组件拿到的数据完整。
+3 -3
View File
@@ -1,7 +1,7 @@
# T-WIN-010 关于页面 + 开源声明 # T-WIN-010 关于页面 + 开源声明
- **状态**:done - **状态**: done
- **依赖**:T-WIN-008 - **依赖**: T-WIN-008
## 目标 ## 目标
@@ -19,7 +19,7 @@ src/ui/credits.gd
## 交付物 ## 交付物
- `src/ui/credits.gd`:与 `contracts/open-source-credits.md` 逐条对应的数据表,渲染为 BBCode - `src/ui/credits.gd`:与 `contracts/open-source-credits.md` 逐条对应的数据表,渲染为 BBCode
- `src/ui/about_screen.gd`:应用信息 + 契约版本 + 隐私说明 + 可滚动声明(RichTextLabel) - `src/ui/about_screen.gd`:应用信息 + 契约版本 + 隐私说明 + 可滚动声明(RichTextLabel)
## 要点 ## 要点
+10 -10
View File
@@ -1,7 +1,7 @@
# T-WIN-011 Windows 打包脚本 # T-WIN-011 Windows 打包脚本
- **状态**:done - **状态**: done
- **依赖**:T-WIN-001 ~ 010 - **依赖**: T-WIN-001 ~ 010
## 目标 ## 目标
@@ -20,7 +20,7 @@ export_presets.cfg
| 文件 | 说明 | | 文件 | 说明 |
|--|--| |--|--|
| `export_presets.cfg` | 导出预设(**必须在项目根目录**,Godot 硬性要求) | | `export_presets.cfg` | 导出预设(**必须在项目根目录**, Godot 硬性要求) |
| `build/export-windows.sh` | Linux 导出:定位 Godot -> 检查模板 -> 导入 -> 导出 -> **校验 PE 头** | | `build/export-windows.sh` | Linux 导出:定位 Godot -> 检查模板 -> 导入 -> 导出 -> **校验 PE 头** |
| `build/export-windows.ps1` | Windows 兜底:同上,产物路径与校验逻辑一致 | | `build/export-windows.ps1` | Windows 兜底:同上,产物路径与校验逻辑一致 |
@@ -28,15 +28,15 @@ export_presets.cfg
- `GODOT_BIN` 环境变量或 PATH 自动定位 Godot,缺失时给出可执行的安装指引 - `GODOT_BIN` 环境变量或 PATH 自动定位 Godot,缺失时给出可执行的安装指引
- 导出模板缺失时明确报错并给出下载与安装路径 - 导出模板缺失时明确报错并给出下载与安装路径
- 导出命令返回非 0 时**仍检查产物是否存在**(Godot 有时非 0 返回但产物有效) - 导出命令返回非 0 时**仍检查产物是否存在**(Godot 有时非 0 返回但产物有效)
- 读取产物前 2 字节校验 `MZ`(PE 头),防止导出静默产出空壳 - 读取产物前 2 字节校验 `MZ`(PE 头),防止导出静默产出空壳
## 验收(文档 5.4) ## 验收(文档 5.4)
| 验收项 | 结果 | | 验收项 | 结果 |
|--|--| |--|--|
| Linux:`bash build/export-windows.sh` 成功生成 exe | ✅ 生成 `OpenDance.exe` 104 MB + `OpenDance.pck` 107 KB,PE 头校验通过 | | Linux: `bash build/export-windows.sh` 成功生成 exe | ✅ 生成 `OpenDance.exe` 104 MB + `OpenDance.pck` 107 KB, PE 头校验通过 |
| Windows:`powershell build/export-windows.ps1` 成功生成 exe | ⚠️ 脚本就绪,本环境无 Windows,未实机执行 | | Windows: `powershell build/export-windows.ps1` 成功生成 exe | ⚠️ 脚本就绪,本环境无 Windows,未实机执行 |
| 生成的 exe 在 Windows 10/11 可运行 | ⚠️ 本环境无 Windows;已校验为合法 PE32+ x86_64 可执行文件 | | 生成的 exe 在 Windows 10/11 可运行 | ⚠️ 本环境无 Windows;已校验为合法 PE32+ x86_64 可执行文件 |
| `THIRD_PARTY.md` 包含所有开源项目与源链接 | ✅ 15 项,与契约表逐条一致 | | `THIRD_PARTY.md` 包含所有开源项目与源链接 | ✅ 15 项,与契约表逐条一致 |
@@ -53,6 +53,6 @@ opendance-windows-v0.1.0.zip
## 环境记录 ## 环境记录
- Godot 4.7.2.stable(`ed1daf0bf`) - Godot 4.7.2.stable(`ed1daf0bf`)
- 导出模板 `4.7.2.stable`(``windows_release_x86_64.exe``) - 导出模板 `4.7.2.stable`(``windows_release_x86_64.exe``)
- 首次导出失败原因:`export_presets.cfg` 放在 `build/` 下,Godot 只在**项目根目录**查找 - 首次导出失败原因:`export_presets.cfg` 放在 `build/` 下,Godot 只在**项目根目录**查找