一、缺陷:config 被当成心跳
_maybe_push_config() 原来每 CONFIG_PUSH_INTERVAL_MS(2 秒)无条件广播一次 config。
契约 §3.3 的语义是「运行中**调整**时下发」,不是心跳 —— 一次 30 分钟推流就是 900 条
没有任何信息量的消息;更糟的是它让「收到 config」无法被当作「配置变了」的信号。
实测 v0.2.4 产物:17 秒内收到 9 条。
修复:
* 抽出 config_payload() 作为唯一载荷来源(同时用作「是否变化」的指纹);
* hello 应答顺序固定为「先 welcome、再 config」(若靠周期 tick 补发,
新对端第一条消息可能变成 config,只认握手应答的实现会困惑);
* _maybe_push_config() 仅在载荷真的变化时广播,并用 CONFIG_PUSH_INTERVAL_MS 去抖;
* 显式 broadcast_config()(UI 改设置)同步更新指纹,避免下一 tick 重发。
实测 v0.2.5:17 秒内 1 条。
二、契约「可选/兜底」语义的定向验收(沿用 Android 侧的教训:
契约里可选的字段必须用**不提供**它的对端来测,否则等于没测)
新增 3 个场景(客户端断言 30 → 40 项):
* v0.1.0 风格对端:只发二进制帧(每 500ms)、从不 ping/stats、hello 不带 deviceId,
持续 17 秒(> 静默超时 15 秒)→ 连接必须保持(契约 §1.1 的向后兼容承诺);
* 两条都不带 deviceId 的连接同时在线 → 不做去重、按连接计数(§5),
device_replaced 只应有那一次真正重复 deviceId 的事件;
* 完全不发 hello、只发 5 帧的对端 → 不断开、不崩、帧仍计入。
三、验收脚本自身修掉两处误判
* 「跨连接的 frame_id 回退」被当成缺陷:frame_id 是**每条连接各自从 0 递增**,
新连接重新开始完全合规 —— 指标用错了。已改名并标注「仅参考」,
真正相关的判据是逐连接的 frames_dropped == 3;
* 「静默超时次数」被当成固定值:套件里本来就有"完全不发数据"的对端,
空闲的替换连接也会被正常超时。已改为**定向断言**:由 hello 记录建立
client_id → device 映射(探针新增 hellos[]),只检查 v0.1.0 风格对端没被误踢。
四、测试客户端改为顺序无关
固定「welcome 之后紧跟 config」后,"第一条消息就是 welcome"的假设立刻暴露。
契约没规定消息顺序,客户端必须顺序无关 —— 已加 nextOfType(type) 按类型取消息
并跳过其它类型(同时计入 counts)。
实测(v0.2.5 发布包,四条验收全绿):
* accept-release.sh:pck 无开发残留 / PE 版本资源 0.2.5 / pck 启动 180 帧 / 运行时探针
* accept-ws.sh:客户端 40 通过 / 0 失败 + 服务端侧全部事实
(10 次连接、111 帧、frames_dropped=3、malformed=1、device_replaced 仅 1 次、
v0.1.0 风格对端未被静默超时踢掉)
* accept-frame-vectors.sh:解码 12/12、396 点 0 差异、编码器 0 差异
* verify.sh:785 通过 / 0 失败
56 lines
1.5 KiB
Plaintext
56 lines
1.5 KiB
Plaintext
; OpenDance Studio —— Windows 客户端(Godot 4)
|
||
;
|
||
; 契约版本:见 contracts/VERSION(rest-api 0.5.0 / data-models 0.7.0 / ...,与 opendance-web 冻结版本对齐)
|
||
; 渲染方式选 gl_compatibility:兼容性最好,便于在 Linux CI 上无 GPU 导出。
|
||
|
||
config_version=5
|
||
|
||
[application]
|
||
|
||
config/name="OpenDance Studio"
|
||
config/description="摄像头跳舞评分系统 —— Windows 客户端。接收 Android 辅助 App 的 33 点骨骼关键点,驱动 VRM 角色并 DTW 实时评分。"
|
||
config/version="0.2.5"
|
||
run/main_scene="res://scenes/main.tscn"
|
||
config/features=PackedStringArray("4.7", "GL Compatibility")
|
||
config/icon="res://icon.svg"
|
||
boot_splash/show_image=false
|
||
|
||
[audio]
|
||
|
||
buses/default_bus_layout=""
|
||
|
||
[autoload]
|
||
|
||
AppConfig="*res://src/autoload/app_config.gd"
|
||
CatalogStore="*res://src/autoload/catalog_store.gd"
|
||
PoseReceiver="*res://src/autoload/pose_receiver.gd"
|
||
ScoreService="*res://src/autoload/score_service.gd"
|
||
|
||
[display]
|
||
|
||
window/size/viewport_width=1280
|
||
window/size/viewport_height=720
|
||
window/size/window_width_override=1280
|
||
window/size/window_height_override=720
|
||
window/stretch/mode="canvas_items"
|
||
window/stretch/aspect="expand"
|
||
|
||
[file_customization]
|
||
|
||
folder_colors={
|
||
"res://src/": "blue",
|
||
"res://scenes/": "green",
|
||
"res://tests/": "purple"
|
||
}
|
||
|
||
[input_devices]
|
||
|
||
pointing/emulate_touch_from_mouse=false
|
||
|
||
[rendering]
|
||
|
||
renderer/rendering_method="gl_compatibility"
|
||
renderer/rendering_method.mobile="gl_compatibility"
|
||
textures/canvas_textures/default_texture_filter=1
|
||
anti_aliasing/quality/msaa_3d=1
|