zijunchiang
|
1c5f28cd41
|
test(codec): 关键点帧跨实现一致性验收——包内 GDScript 解码器 × Web 参考实现(T-WIN-021)
契约 §2 的参考实现是 opendance-web 的 keypoint-codec.ts(其 decodeBinaryFrame
注释就是「Windows 端参考实现」)。Android 已用金标准向量与它对齐;而本端的解码器
此前**只跟自己比过**(自己编码、自己解码),两个方向共用同一份错误假设也能一直绿。
新增:
* build/integration/frame-vectors.json —— 由参考实现生成并锁定的向量
(12 用例:i16 最小值 -32768、i16 最大值、±1 全量程、置信度 0/128/255、
flags 低 3 位组合、frame_id 0xFFFFFFFF 与 0x80000000、取整边界)。
* build/integration/probe-frame-vectors.gd —— 用**包内** KeypointFrame 解码同一批字节,
与参考解码结果**逐值精确比对**(坐标/置信度先收敛到 float32 再比精确相等,
因为本端点存 PackedVector3Array、置信度存 PackedFloat32Array)。
* build/accept-frame-vectors.sh —— 编排:用参考实现重新生成向量并与锁定文件比对
(参考实现变了却不同步向量即失败)→ 解包 → 包内解码比对 → 报告核对。
实测(**已发布**的 v0.2.3 资产重新下载后跑):
✓ 锁定的向量与参考实现重新生成的结果逐字节一致
✓ 解码用例 12/12,逐点比对 396 个,解码差异 0 项
✓ 编码器差异 1 项,且全部属于「量化前恰好 .5」的取整边界
关键点帧跨实现一致性验收全部通过
已测量并归类的一处差异(非缺陷):JS Math.round 与 Kotlin(生产发送方)Math.round
对 -2.5 取 -2(半数朝 +∞),本端 roundi 取 -3(半数远离零)。Windows 是接收方,
encode() 只在自测/协议回归里用,故对产品无影响;脚本硬性要求这类差异只能出现在
取整边界用例上,否则视为新问题。若将来本端要当发送方,把 roundi 换成 floor(x+0.5) 即可。
踩坑(两处「看着通过其实没测到」,都写进任务文档):
1. 生成器第一版把「喂进编码器的输入」当成 points 写进向量 → 比对变成「比编码量化」,
靠抽查数值才发现;现在向量同时含 inputPoints / f32InputPoints / f32Hex / points。
2. 编码器比对一开始用 float64 输入编出的 hex 当基准(正数恰好 .5 那例整例 99 字节不同),
实际只是输入从 float64 收敛到 float32 的量化差;改用同输入的 f32Hex 基准后,
差异收敛到唯一一个真正的取整边界用例。教训:跨实现比对必须先统一输入。
package-release.sh 结尾把四条发布前验收一次列全;verify.sh 全绿(774 通过 / 0 失败)。
|
2026-09-17 05:52:11 +08:00 |
|
zijunchiang
|
cb1c293dcf
|
chore(uid): 补探针脚本的 .uid(Godot 首次导入自动生成,与其他脚本一致入库)
|
2026-09-17 05:33:25 +08:00 |
|
zijunchiang
|
0ffb2d43bb
|
test(ws): WebSocket 契约一致性验收(独立实现 × 包内 PoseServer)+ 故意不合规的负例服务端
tests/test_pose_server.gd 用的是**同引擎内的客户端**(Godot WebSocketPeer),而真实
对端是 Android(Kotlin/OkHttp)——同引擎自测会共享同样的假设(超时判定、分包、
文本/二进制边界、关闭语义、子协议协商),于是「测试全绿」不等于「Android 连得上」。
契约 §7 也写明:静默超时要用保活避免,而不是靠重连掩盖;这句话只有用独立实现的
客户端实测才算验过。
新增三个文件:
* build/integration/probe-ws.gd —— 把**包内** PoseServer 跑起来(--main-pack +
只加载包内代码),把连接/控制帧/帧/断开事件汇总成 JSON 报告;自身不做判定
(避免「用被测实现判定被测实现」)。支持 --stop-file 提前收尾。
* build/integration/ws-client.mjs —— Node 实现的契约一致性客户端(用内置
WebSocket,零依赖),逐条跑 §1 §1.1 §2 §3.1-§3.6 §5 §6 §7 的场景。
* build/accept-ws.sh —— 编排:解包 → 起探针 → 等监听 → 跑客户端 → 收报告 →
核对客户端看不到的那一半(frames_dropped / malformed_frames / 控制帧计数 /
device_replaced / 关闭原因 / 静默超时消息带毫秒数与阈值)。
实测(**已发布**的 v0.2.3 资产重新下载后跑):客户端 30 通过 / 0 失败 + 服务端侧
16 项事实全过。关键两条:
* 保活对照:只发应用层 ping 持续 19 秒(> 静默超时 15 秒)连接**仍保持打开**;
* 静默超时:完全不发数据 → close(1001,"timeout"),实测 15011 ms(阈值 15000 ms);
另有:子协议协商、welcome 声明保活参数且 heartbeat(5000) < silentTimeout(15000)/2、
pong 原样回传 sentAtMs、同 deviceId 替换旧连接(1000 "replaced by new connection")、
bye → 1000、协议不匹配 → bye + 1002、72 帧含故意跳号(服务端 frames_dropped=3)
与故意 100 字节非法帧(malformed_frames=1)。
反向验证:新增**故意不合规的服务端** build/integration/probe-ws-bad.gd
(welcome 不声明保活字段、不带 sessionId、收到 ping 不回 pong、不做 deviceId 去重),
对同一客户端 → 14 项 ✗、退出码 1,证明断言不是空转。
踩坑记录(已写入任务文档):
1. --script 模式下从无类型数组取 peer,peer.get_packet() 返回类型推不出来,
var x := … 直接 Parse Error;
2. 复核静默超时消息时写了否定式断言 !includes("静默超时 1"),把正确行为判成失败 ——
断言应写成正向模式匹配;
3. 探针原本只能按固定时长跑,编排脚本得猜时间;改成客户端跑完 touch stop 文件。
顺带:package-release.sh 结尾把三条发布前验收(产物自证 / WS 契约一致性 / 跨端集成)
一次列全。不影响已发布的 v0.2.3 产物,故不新发版本。
|
2026-09-17 05:32:24 +08:00 |
|
zijunchiang
|
6238873312
|
chore(accept): accept-integration.sh 拒绝未知参数
首跑时把 --expect <path> 当成位置参数传进来,被静默忽略(期望值路径只认
EXPECT_PATH 环境变量),于是 --skip-seed 时找不到期望值文件才报错 —— 属于
「参数写错却先跑一半」的糟糕体验。现在除 --skip-seed 外一律报错退出。
不影响发布产物(accept-integration.sh 不在 zip 内),故不新发版本。
|
2026-09-17 05:21:54 +08:00 |
|
zijunchiang
|
a78a536476
|
feat(integration): 跨端集成验收(包内客户端 × 真实 Web 服务端)+ 修两个真实缺陷(v0.2.3)
单测打的是假 HTTP 夹具,Web 端测试只验自己那一侧 —— 两端各自绿灯不代表契约字段
真的对得上。本版加了跨端集成验收,并在首次运行就抓到两个真实缺陷。
一、跨端集成验收(T-WIN-019)
* build/integration/probe-integration.gd:只加载**包内**代码
(`godot --main-pack <pck> --script <探针>` + res://),打真实服务端并逐项断言:
/health 六个契约版本、catalog/version 指纹、catalog/export 解析出的四类资源与
defaultModelId;**场景物理参数逐项比对**(重力/基础风/阵风/风坐标系/地面摩擦坡度/
碰撞体/出生点/相机/环境)+ Wind.sample(0) + 场景 GLB 可下载;
动作 NDJSON → 客户端解析帧数 == 服务端帧数、33 点、每帧服务端关节角、
推导帧率 30 fps、时长 96.67 s;缩略图 PNG;模型 VRM 可下载。
* build/integration/seed-catalog.mjs:用**真实素材**播种目录
(示例文件/ 下的真 VMD → 内部标准 33 点;真 PMX → VRM;场景 GLB + 物理参数),
并把期望值(含从 /health 取的契约版本)写成 JSON。
* build/accept-integration.sh:编排「服务端可达 → 播种 → 包内探针 → 汇报」。
实测(v0.2.3 包内 pck × 真实 Web v0.7.4 + 真实 MySQL):全部通过。
二、探针抓到的两个真实缺陷
1. `CatalogTypes.Motion` 缺 `thumbnail_url`:服务端 catalog/export 从
data-models 0.6.0 起一直在发 thumbnailUrl(Model/Scene 都有),客户端默默丢掉。
已补字段与解析(缺失为 ""),新增 tests/test_catalog_types.gd(4 项 20 断言)
用**服务端真实报文形状**锁住三个资源类型的覆盖,并显式记录一处刻意不消费的差异
(Model.usable/unusableReason —— 目录只下发可用模型,恒为 true)。
2. `ApiClient._request()` 对**所有**响应无条件 `raw.get_string_from_utf8()`:
数据本身没错(二进制路径返回原始字节),但每下载一个文件都会打印
「Unicode parsing error … Invalid UTF-8 leading byte (N)」噪声,并把整个文件
(模型可达数十 MB)解码成 String 拷贝后丢弃。改为只在需要文本时解码。
三、顺带修掉测试里的写死契约版本
tests/test_api_integration.gd 断言 rest-api/websocket-keypoints/skeleton-topology
均为 "0.1.0"。该用例只在服务端可达时才跑,平时一直 skip,所以契约升到 0.5.0 后
没有任何地方发现它。改为从仓库 contracts/VERSION 读期望值并与 /health 比对
(顺带成为「客户端契约快照 vs 服务端」的跨端校验)。
证据:verify.sh 全绿(820 通过 / 0 失败,比上版 +46,因为服务端在线时 API 集成组
真的跑了);包内 pck 跨端集成探针全部通过;check-version/PE 版本资源 = 0.2.3。
|
2026-09-17 05:18:03 +08:00 |
|
zijunchiang
|
e4e534f2a2
|
test(accept): 发布包级端到端验收(读包内 pck 而非源码)+ T-WIN-018
`build/verify.sh` 跑的是仓库源码(`godot --path .`),交付给用户的是 zip 里的
OpenDance.pck + OpenDance.exe,差别全在「导出」这一步:导出预设漏资源、pck 没
重新生成、版本号没同步、契约快照与 pck 不一致 —— 都只在包里才暴露。
本仓 v0.2.1 的 PE 版本元数据就是这么翻的车。
新增:
* build/probe-release.gd —— 包内运行时探针。用
`godot --headless --main-pack <pck> --script <探针>` 只读包内资源,断言并
quit(0/1):
1) application/config/version == 期望版本(界面版本唯一来源,§11.1);
2) AboutScreen.app_version() == 期望版本(「关于」清单真正显示的值);
3) 主场景可加载、四个 autoload 都在场景树里;
4) 15 个关键资源(UI/模型/场景/网络/评分)在包内都可加载;
5) 包内 Credits.CONTRACT_VERSIONS 与包内 contracts/VERSION 快照逐项一致
(抓「pck 用旧契约构建、快照却拷了新的」)。
* build/accept-release.sh —— 一个 zip(或已解压目录)跑完:sha256 → 解压
(deflate 用 unzip / LZMA 用 7z,并检查没有 .partN 残留)→ 文件清单 →
check-pe-version.sh → 包内 pck 启动 180 帧无 SCRIPT ERROR → 探针。
* package-release.sh 结尾打印下一步验收命令;README 补用法。
实测(对**已发布**的 v0.2.2 资产重新下载后跑):
sha256 OK;PE 版本资源 0.2.2;包内 pck 启动 180 帧无错误;
包内 version=0.2.2;AboutScreen.app_version()=0.2.2;主场景
res://scenes/main.tscn;autoload 齐备;15 项关键资源可加载;
pck 内契约表与包内 contracts/VERSION 一致 → 发布包级验收全部通过。
反向验证:
* 篡改包内 contracts/VERSION 的 rest-api=0.4.0 →
✗ ["rest-api: pck=0.5.0 快照=0.4.0"],退出码 1;
* 期望版本写 0.2.1 对 v0.2.2 的包 → 三处版本断言全失败,退出码 1。
顺带修掉脚本自身一个缺陷:SHA256SUMS 用相对路径 + cd 到包目录后被二次拼接成
dist-release/dist-release/SHA256SUMS,导致 sha256 校验假失败(其余检查仍全绿,
很容易被忽略)。首跑时正是它把「全部通过」变成了失败。
verify.sh 仍全绿(754 通过 / 0 失败 / 5 跳过)。
|
2026-09-17 04:50:40 +08:00 |
|
zijunchiang
|
ba988f6ca6
|
fix(packaging): 发布包 exe 的 PE 版本资源必须等于发布版本(v0.2.2)
缺陷(v0.2.1 实测):把 opendance-windows-v0.2.1.zip 解开、在 Windows 上
「右键 → 属性 → 详细信息」,显示的是
文件版本 0.1.0
产品版本 0.1.0
而产物名、Git tag、README 全是 v0.2.1 —— 正是文档 §11.2 要禁止的
「不可核对的发布」。
根因:export_presets.cfg 的 application/file_version /
application/product_version 被硬编码在 0.1.0,而它们**不会**跟随
project.godot 的 application/config/version 自动更新;check-version.sh 当时
只比 project.godot ↔ project.yaml(两处一致),所以静态校验全绿、产物却是错的。
漂移发生在没人看的第三个位置 —— 校验位置少一个,等于没校验。
证据:
strings -a -el OpenDance.exe -> FileVersion 0.1.0 / ProductVersion 0.1.0
VS_FIXEDFILEINFO @0x67b976c -> FileVersion 0.1.0.0 / ProductVersion 0.0.0.0
对照组:官方模板 windows_release_x86_64.exe 的 FileVersion = 4.7.2,
说明本机 rcedit 改写是生效的,问题纯粹在预设的值本身。
修复:
* build/sync-export-version.sh(新增):导出前把版本号写进导出预设,
幂等、拒绝非 X.Y.Z;
* build/check-pe-version.sh(新增):直接读 exe 的 PE 版本资源 ——
VS_FIXEDFILEINFO(签名 0xFEEF04BD 后 16 字节,grep -abo 取偏移约 40 ms)
与 StringFileInfo(strings -el,资源管理器属性页显示的就是它)。
两处都查:实测固定信息里的 ProductVersion 恒为 0.0.0.0;
* build/check-version.sh:新增导出预设两个键的校验;
* build/export-windows.sh:导出前同步、导出后直接复核产物;
* build/package-release.sh:打包前校验源 exe、解包后再校验包内 exe;
* build/verify.sh:新增步骤 1/5「产物 PE 版本资源」(未导出则跳过);
* tests/test_about.gd:新增 test_export_preset_version_matches_project_version。
反向验证:把预设 file_version 改回 0.1.0 →
check-version.sh 退出码 1(报出两处不一致);
无头测试 753 通过 / 1 失败,失败项正是
「导出预设的 file_version 与版本号唯一来源一致」。
实测 v0.2.2 产物:PE FileVersion 0.2.2.0 / StringFileInfo 0.2.2,
版本号在 project.godot / project.yaml / export_presets.cfg / exe 元数据 /
产物名 / tag 六处一致。verify.sh 全绿(754 通过 / 0 失败 / 5 跳过)。
同时把 LZMA 压缩的取舍写清楚:deflate 包 38,248,818 B 仍超 IIS
30,000,000 B 上限,故 Release 资产仍是 LZMA 整包(26,628,529 B,不拆分卷);
若上限放开或改由 Web /static 分发,用 ZIP_METHOD=deflate 重打包即可得到
资源管理器可直接双击解压的包。
|
2026-09-17 04:22:57 +08:00 |
|
zijunchiang
|
14ee5f7ec9
|
fix(scene): 包围盒沿树累乘变换(修掉入树前的 ERROR 与范围偏位)+ 21 项"真机等价"测试
缺陷:SceneRuntime._compute_aabb() 用 MeshInstance3D.global_transform 计算包围盒,
而它在 _assemble() 里调用 —— 那时运行时节点**还没入树**。两个后果:
- Godot 打一行 `ERROR: Condition "!is_inside_tree()" is true. Returning: Transform3D()`
(真机每次载入场景都刷一条,与"日志里不应有 ERROR"的排查约定冲突);
- 树外的 global_transform 返回单位变换,算出来的只是各网格的**局部** AABB,
于是重力/风 Area3D 的碰撞盒与兜底地面会偏小或偏位 —— 对把网格摆在非原点位置的
场景(导出 GLB 的常见情况)**风力可能作用不到物体上**。
修复:改为从根往下累乘 `Node3D.transform`,不依赖是否入树;挂在单位变换父节点下时
结果与旧语义一致。
新增 21 项无头"真机等价"测试(不需要设备):
- tests/test_scene_config.gd:在代码里手写合法 GLB(三角形 + BIN chunk)落盘后
**真实加载**:warnings 为空、按 colliders 三种取值分别核对碰撞体(mesh / auto 命中
*_col / auto 未命中的兜底地面 / none)、重力 Area3D 的 gravity 与方向、
SPACE_OVERRIDE_REPLACE、风力与 WIND_FORCE_SCALE 对齐,以及**带位移网格**
(translation [10,0,0])时重力区域与兜底地面都跟着位移(该缺陷的回归测试);
- tests/test_pose_server.gd:**时间维度**验证保活 —— 只发应用层 ping、不发关键点帧的
客户端在 17 秒(> 15 秒静默超时)后仍保持连接("画面里没人就掉线、疯狂重连"的根因
对照),而完全不发数据的客户端被 close(1001) 断开。
新增 build/check-version.sh:强制 project.godot 的 application/config/version
(「关于」清单与 .pck 的来源)与 project.yaml、发布版本号三者一致;已接入
build/package-release.sh(打包前)与 build/verify.sh(第 0/4 步),并做过反向验证。
此前没有任何机制保证这两个文件同步 —— 界面显示的版本可能和产物名不一致。
版本升至 0.2.1(project.godot / project.yaml / README / 产物名 / tag 全部一致),
About 清单与 .pck 内版本号均为 0.2.1(已用 strings 核对)。
验证:build/verify.sh 四步全通过(版本一致性 → 编译 → 751 项断言 → 180 帧启动无错误);
产物 opendance-windows-v0.2.1.zip(LZMA,26,627,191 B)
sha256 d49885327047f5a52de6c732431ea080cd1a030ae1c581a8bc0ec0d99bfb792c。
|
2026-09-17 03:59:29 +08:00 |
|
zijunchiang
|
86e5decd10
|
build(release): 增加 ZIP_METHOD=lzma 选项(37 MB 整包被 IIS 30 MB 上限挡住时用)
Windows v0.2.0 发布时,Gitea 站点 IIS 的 maxAllowedContentLength 仍是默认的
30,000,000 字节:16.4 MB 通过,而 38,245,602 B 的整包用**正确**的 multipart 写法
(字段名 attachment)复测依旧 HTTP 413(仅发出 917 KB 即被拒)。为不阻塞发布,
本次给打包脚本增加压缩方式开关:
ZIP_METHOD=deflate (默认)约 38 MB,Windows 资源管理器双击即可解压
ZIP_METHOD=lzma 约 26 MB,可绕过 30 MB 闸;**代价**是 Windows 10 资源管理器
不支持 LZMA(method 14) 的 zip,必须用 7-Zip / WinRAR / PeaZip
脚本细节:
- lzma 走 `7z a -tzip -mm=LZMA -mx=9`(缺 7z 时报错并给出安装提示);
- lzma 包额外做 `7z t` 完整性测试,并改用 `7z x` 做解压自检(unzip 对 method 14
支持不一致,不能用来验证);
- 超限告警里补上「可用 ZIP_METHOD=lzma 绕过」的提示。
README.md / README.txt 同时写明:v0.2.0 的 zip 是 LZMA,需要 7-Zip/WinRAR 解压;
若 IIS 上限已放开,请改用默认 deflate 包。
|
2026-09-17 03:42:05 +08:00 |
|
zijunchiang
|
5581cbe05d
|
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 全部通过
|
2026-09-17 00:45:23 +08:00 |
|
zijunchiang
|
315cf79708
|
feat(win): T-WIN-011 Windows 打包与一键验证脚本
|
2026-09-14 20:04:54 +08:00 |
|