From fbc8b55044db1423093edb4d7aedcd7629c65bb1 Mon Sep 17 00:00:00 2001 From: clement Date: Tue, 30 Jun 2026 21:58:42 +0200 Subject: [PATCH] feat(iphone): python topology codec --- data_only_viz/arkit_topology.py | 43 ++++++++++++++++++++++ data_only_viz/tests/test_arkit_topology.py | 28 ++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 data_only_viz/arkit_topology.py create mode 100644 data_only_viz/tests/test_arkit_topology.py diff --git a/data_only_viz/arkit_topology.py b/data_only_viz/arkit_topology.py new file mode 100644 index 0000000..3fa2dc3 --- /dev/null +++ b/data_only_viz/arkit_topology.py @@ -0,0 +1,43 @@ +"""AVLiveWire topology payload codec. + +Mirror of the Swift `TopologyPayload` (shared/AVLiveWire). Layout +(big-endian): u16 jointCount; per joint [u8 nameLen + utf8 name]; +then per joint i16 parent (-1 = root). +""" +from __future__ import annotations + +import struct + + +def encode_topology(names: list[str], parents: list[int]) -> bytes: + out = bytearray() + out += struct.pack(">H", len(names)) + for nm in names: + b = nm.encode("utf-8")[:255] + out.append(len(b)) + out += b + for p in parents: + out += struct.pack(">h", p) + return bytes(out) + + +def decode_topology(payload: bytes): + """Return (joint_names, parents) or None if malformed.""" + if len(payload) < 2: + return None + n = struct.unpack(">H", payload[:2])[0] + o = 2 + names: list[str] = [] + for _ in range(n): + if o >= len(payload): + return None + ln = payload[o] + o += 1 + if o + ln > len(payload): + return None + names.append(payload[o:o + ln].decode("utf-8", "replace")) + o += ln + if o + n * 2 > len(payload): + return None + parents = list(struct.unpack(">" + "h" * n, payload[o:o + n * 2])) + return names, parents diff --git a/data_only_viz/tests/test_arkit_topology.py b/data_only_viz/tests/test_arkit_topology.py new file mode 100644 index 0000000..8f30e19 --- /dev/null +++ b/data_only_viz/tests/test_arkit_topology.py @@ -0,0 +1,28 @@ +"""AVLiveWire topology payload round-trip (mirror of Swift TopologyPayload).""" +from data_only_viz.arkit_topology import decode_topology, encode_topology + + +def test_round_trip(): + names = ["root_joint", "hips_joint", "left_arm_joint"] + parents = [-1, 0, 1] + payload = encode_topology(names, parents) + out = decode_topology(payload) + assert out is not None + got_names, got_parents = out + assert got_names == names + assert got_parents == parents + + +def test_empty(): + payload = encode_topology([], []) + out = decode_topology(payload) + assert out == ([], []) + + +def test_rejects_truncated(): + # Claims 3 joints, provides no body. + assert decode_topology(b"\x00\x03") is None + + +def test_rejects_too_short(): + assert decode_topology(b"\x00") is None