Files

162 lines
6.0 KiB
GDScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 帧被拒绝")