extends TestCase ## T-WIN-004:239 字节帧反序列化 —— 契约 contracts/websocket-keypoints.md §2 ## ## 测试向量与 opendance-web 的 TypeScript 参考实现 ## (web-server/src/preprocess/keypoint-codec.ts)保持一致, ## 保证两端对同一字节流的解释完全相同。 const KeypointFrame = preload("res://src/net/keypoint_frame.gd") func _sample_points() -> PackedVector3Array: var points := PackedVector3Array() points.resize(33) for i in 33: points[i] = Vector3( float(i % 33) / 32.0, 1.0 - float(i % 33) / 32.0, float((i % 7) - 3) / 10.0 ) return points func _sample_confidence() -> PackedFloat32Array: var conf := PackedFloat32Array() conf.resize(33) for i in 33: conf[i] = 1.0 if i % 2 == 0 else 0.5 return conf func test_constants_match_contract() -> void: eq(KeypointFrame.BINARY_SIZE, 239, "帧长度常量为 239") eq(KeypointFrame.KEYPOINT_COUNT, 33, "关键点数量为 33") eq(KeypointFrame.HEADER_SIZE + KeypointFrame.KEYPOINT_COUNT * KeypointFrame.POINT_BYTES, 239, "8 + 33×7 = 239") eq(KeypointFrame.MAGIC, 0x4F44, "magic 为 'OD'(0x4F44)") eq(KeypointFrame.PROTOCOL_VERSION, 1, "协议版本为 1") func test_encode_produces_239_bytes_with_correct_header() -> void: var buf := KeypointFrame.encode(42, _sample_points(), _sample_confidence(), 1) eq(buf.size(), 239, "编码结果恰好 239 字节") eq(buf.decode_u16(0), 0x4F44, "magic 正确") eq(buf.decode_u8(2), 1, "version 正确") eq(buf.decode_u8(3), 1, "flags 正确") eq(buf.decode_u32(4), 42, "frame_id 正确") func test_roundtrip_within_fixed_point_precision() -> void: var points := _sample_points() var conf := _sample_confidence() var decoded := KeypointFrame.decode(KeypointFrame.encode(7, points, conf, 0)) not_null(decoded, "解码成功") eq(decoded.frame_id, 7, "frame_id 往返正确") eq(decoded.points.size(), 33, "解码出 33 个点") var max_delta := 0.0 for i in 33: max_delta = maxf(max_delta, absf(decoded.points[i].x - points[i].x)) max_delta = maxf(max_delta, absf(decoded.points[i].y - points[i].y)) max_delta = maxf(max_delta, absf(decoded.points[i].z - points[i].z)) # 定点精度:1/32767 ≈ 3.05e-5,留一倍余量 ok(max_delta < 1e-4, "坐标往返误差小于定点精度", "最大误差 %.8f" % max_delta) var max_conf_delta := 0.0 for i in 33: max_conf_delta = maxf(max_conf_delta, absf(decoded.confidence[i] - conf[i])) ok(max_conf_delta < 1.0 / 255.0 + 1e-6, "置信度往返误差在一个量化步长内", "最大误差 %.8f" % max_conf_delta) func test_rejects_wrong_length() -> void: var short := PackedByteArray() short.resize(238) ne(KeypointFrame.validate(short), "", "238 字节被拒绝") contains(KeypointFrame.validate(short), "239", "错误信息说明期望长度") var long := PackedByteArray() long.resize(240) ne(KeypointFrame.validate(long), "", "240 字节被拒绝") is_null(KeypointFrame.decode(short), "解码失败返回 null") func test_rejects_bad_magic() -> void: var buf := PackedByteArray() buf.resize(239) ne(KeypointFrame.validate(buf), "", "全零帧因 magic 错误被拒绝") contains(KeypointFrame.validate(buf), "magic", "错误信息指出 magic") func test_rejects_unsupported_version() -> void: var buf := KeypointFrame.encode(0, _sample_points(), _sample_confidence()) buf.encode_u8(2, 2) contains(KeypointFrame.validate(buf), "版本", "版本不符被拒绝") func test_extreme_values_do_not_overflow() -> void: var points := _sample_points() points[0] = Vector3(1.0, 1.0, -1.0) points[1] = Vector3(0.0, 0.0, 1.0) var conf := _sample_confidence() conf[0] = 1.0 conf[1] = 0.0 var decoded := KeypointFrame.decode(KeypointFrame.encode(0, points, conf)) almost(decoded.points[0].x, 1.0, 1e-4, "x = 1.0 不溢出 int16") almost(decoded.points[0].z, -1.0, 1e-4, "z = -1.0 不溢出 int16") almost(decoded.points[1].z, 1.0, 1e-4, "z = 1.0 不溢出 int16") almost(decoded.confidence[1], 0.0, 1e-6, "置信度 0 往返正确") func test_flags_helpers() -> void: var frame := KeypointFrame.decode( KeypointFrame.encode(0, _sample_points(), _sample_confidence(), KeypointFrame.FLAG_MIRRORED | KeypointFrame.FLAG_UPPER_BODY_ONLY) ) is_true(frame.is_mirrored(), "镜像标志解析正确") is_true(frame.is_upper_body_only(), "上半身模式标志解析正确") is_false(frame.has_low_confidence(), "未设置低置信度标志") func test_mirrored_frame_x_is_flipped() -> void: var points := _sample_points() var frame := KeypointFrame.decode( KeypointFrame.encode(0, points, _sample_confidence(), KeypointFrame.FLAG_MIRRORED) ) var flipped := frame.unmirrored_points() almost(flipped[5].x, 1.0 - points[5].x, 1e-4, "镜像帧翻转 x") # y 未经翻转,但仍经过 int16 定点往返,容差取定点精度 almost(flipped[5].y, points[5].y, 1e-4, "翻转不影响 y") var normal := KeypointFrame.decode(KeypointFrame.encode(0, points, _sample_confidence(), 0)) almost(normal.unmirrored_points()[5].x, points[5].x, 1e-4, "非镜像帧不翻转 x") func test_confidence_threshold_marks_unreliable() -> void: var conf := _sample_confidence() conf[11] = 0.49 conf[12] = 0.5 var frame := KeypointFrame.decode(KeypointFrame.encode(0, _sample_points(), conf)) is_false(frame.is_reliable(11), "0.49 判为不可信") is_true(frame.is_reliable(12), "0.5 判为可信(阈值含端点)") is_true(KeypointFrame.compute_low_confidence(_sample_points(), conf), "存在低置信度点时置位") func test_json_debug_frame() -> void: var raw_points: Array = [] for i in 33: raw_points.append([0.25, 0.75, -0.1, 0.9]) var frame := KeypointFrame.from_json_dict({ "frameId": 9, "flags": {"mirrored": true, "upperBodyOnly": false}, "points": raw_points, }) not_null(frame, "JSON 调试帧解析成功") eq(frame.frame_id, 9, "frameId 解析正确") is_true(frame.is_mirrored(), "mirrored 标志解析正确") almost(frame.points[0].x, 0.25, 1e-6, "坐标解析正确") var bad := KeypointFrame.from_json_dict({"frameId": 1, "points": []}) is_null(bad, "点数不足的 JSON 帧被拒绝")