styly-netsync-server 0.11.0__tar.gz → 0.12.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/PKG-INFO +9 -7
- {styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/README.md +8 -6
- {styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/pyproject.toml +1 -1
- {styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/src/styly_netsync/binary_serializer.py +84 -28
- {styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/src/styly_netsync/server.py +2 -2
- {styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/src/styly_netsync_server.egg-info/PKG-INFO +9 -7
- {styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/tests/test_binary_serializer.py +182 -12
- {styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/LICENSE +0 -0
- {styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/setup.cfg +0 -0
- {styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/src/styly_netsync/__init__.py +0 -0
- {styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/src/styly_netsync/__main__.py +0 -0
- {styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/src/styly_netsync/adapters.py +0 -0
- {styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/src/styly_netsync/cli.py +0 -0
- {styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/src/styly_netsync/client.py +0 -0
- {styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/src/styly_netsync/client_simulator.py +0 -0
- {styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/src/styly_netsync/config.py +0 -0
- {styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/src/styly_netsync/default.toml +0 -0
- {styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/src/styly_netsync/events.py +0 -0
- {styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/src/styly_netsync/logging_utils.py +0 -0
- {styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/src/styly_netsync/network_utils.py +0 -0
- {styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/src/styly_netsync/nv_sync.py +0 -0
- {styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/src/styly_netsync/rest_bridge.py +0 -0
- {styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/src/styly_netsync/types.py +0 -0
- {styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/src/styly_netsync_server.egg-info/SOURCES.txt +0 -0
- {styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/src/styly_netsync_server.egg-info/dependency_links.txt +0 -0
- {styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/src/styly_netsync_server.egg-info/entry_points.txt +0 -0
- {styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/src/styly_netsync_server.egg-info/requires.txt +0 -0
- {styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/src/styly_netsync_server.egg-info/top_level.txt +0 -0
- {styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/tests/test_all_run_methods.py +0 -0
- {styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/tests/test_config.py +0 -0
- {styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/tests/test_discovery_probe.py +0 -0
- {styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/tests/test_logging_cli.py +0 -0
- {styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/tests/test_multi_nic.py +0 -0
- {styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/tests/test_nv_protocol.py +0 -0
- {styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/tests/test_object_sync.py +0 -0
- {styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/tests/test_port_error_message.py +0 -0
- {styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/tests/test_python_client.py +0 -0
- {styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/tests/test_reconnect_identity.py +0 -0
- {styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/tests/test_rest_bridge.py +0 -0
- {styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/tests/test_room_expiry.py +0 -0
- {styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/tests/test_stealth_heartbeat.py +0 -0
- {styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/tests/test_timing_monotonic.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: styly-netsync-server
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.12.0
|
|
4
4
|
Summary: STYLY NetSync Server - Multiplayer framework for Location-Based Entertainment VR/MR experiences
|
|
5
5
|
Author-email: "STYLY, Inc." <info@styly.inc>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -81,14 +81,16 @@ styly-netsync-simulator --server tcp://localhost --room my_room --clients 50
|
|
|
81
81
|
|
|
82
82
|
## Wire protocol compatibility
|
|
83
83
|
|
|
84
|
-
- Current transform wire protocol is `protocolVersion =
|
|
85
|
-
- Transform messages use `MSG_CLIENT_POSE` (11) and `MSG_ROOM_POSE` (12) with the compact
|
|
86
|
-
-
|
|
84
|
+
- Current transform wire protocol is `protocolVersion = 5`.
|
|
85
|
+
- Transform messages use `MSG_CLIENT_POSE` (11) and `MSG_ROOM_POSE` (12) with the compact V5 pose body.
|
|
86
|
+
- v5 adds an optional `MovingFloorLocal` pose flag. Bound avatars send head, hands, and virtual transforms in the registered moving floor's local coordinates, and reuse the existing 8-byte physical slot as direct physical position/yaw.
|
|
87
|
+
- Unbound v5 poses keep the v4 `xrOriginDelta` semantics: `xrOriginDelta` carries a Y component as a 4th `int16` (`dx, dy, dz, dyaw` = 8 bytes vs. v3's 6), so receivers can reconstruct the sender's rig-Y motion.
|
|
87
88
|
- Legacy transform protocols (v2/v3) and JSON transform fallback are not supported.
|
|
88
89
|
- Deploy Unity and Python updates together when changing transform protocol behavior.
|
|
89
|
-
- Protocol
|
|
90
|
+
- Protocol v5 position quantization ranges:
|
|
90
91
|
- Absolute (`headPosAbs` only): signed `int24` at `0.01 m` per unit, per-axis range `[-83,886.08 m, 83,886.07 m]`.
|
|
91
|
-
- XROrigin locomotion delta (`xrOriginDelta`, 4×`int16`: `dx, dy, dz, dyaw`): `0.01 m` per unit for translation, `0.1°` for yaw. Receivers reconstruct `physicalPos = invDeltaRot * (headPos − deltaPos)`; it is not on the wire as a separate absolute field.
|
|
92
|
+
- XROrigin locomotion delta for unbound poses (`xrOriginDelta`, 4×`int16`: `dx, dy, dz, dyaw`): `0.01 m` per unit for translation, `0.1°` for yaw. Receivers reconstruct `physicalPos = invDeltaRot * (headPos − deltaPos)`; it is not on the wire as a separate absolute field.
|
|
93
|
+
- Direct physical payload for moving-floor-local poses (`physical`, 4×`int16`: `x, y, z, yaw`): `0.01 m` per unit for translation, `0.1°` for yaw.
|
|
92
94
|
- Head-relative (`right/left/virtual`): signed `int16` at `0.005 m` per unit, per-axis range `[-163.84 m, 163.835 m]`.
|
|
93
95
|
- These are encoding limits, not a hard world-size cap. Worlds can be larger, but encoded axis values are clamped if they exceed the representable range.
|
|
94
96
|
|
|
@@ -96,7 +98,7 @@ styly-netsync-simulator --server tcp://localhost --room my_room --clients 50
|
|
|
96
98
|
|
|
97
99
|
The following options summarize trade-offs when expanding absolute-position range.
|
|
98
100
|
|
|
99
|
-
Assumed baseline (`protocolVersion=
|
|
101
|
+
Assumed unbound baseline (`protocolVersion=5`, `MovingFloorLocal` off):
|
|
100
102
|
- Client pose body with `Physical+Head+Right+Left` valid and `virtualCount=0`: `46 bytes` (matches `test_client_body_size_with_full_pose_no_virtuals`).
|
|
101
103
|
- Room per-client entry (`clientNo + poseTime + clientBody`): `56 bytes`.
|
|
102
104
|
|
|
@@ -42,14 +42,16 @@ styly-netsync-simulator --server tcp://localhost --room my_room --clients 50
|
|
|
42
42
|
|
|
43
43
|
## Wire protocol compatibility
|
|
44
44
|
|
|
45
|
-
- Current transform wire protocol is `protocolVersion =
|
|
46
|
-
- Transform messages use `MSG_CLIENT_POSE` (11) and `MSG_ROOM_POSE` (12) with the compact
|
|
47
|
-
-
|
|
45
|
+
- Current transform wire protocol is `protocolVersion = 5`.
|
|
46
|
+
- Transform messages use `MSG_CLIENT_POSE` (11) and `MSG_ROOM_POSE` (12) with the compact V5 pose body.
|
|
47
|
+
- v5 adds an optional `MovingFloorLocal` pose flag. Bound avatars send head, hands, and virtual transforms in the registered moving floor's local coordinates, and reuse the existing 8-byte physical slot as direct physical position/yaw.
|
|
48
|
+
- Unbound v5 poses keep the v4 `xrOriginDelta` semantics: `xrOriginDelta` carries a Y component as a 4th `int16` (`dx, dy, dz, dyaw` = 8 bytes vs. v3's 6), so receivers can reconstruct the sender's rig-Y motion.
|
|
48
49
|
- Legacy transform protocols (v2/v3) and JSON transform fallback are not supported.
|
|
49
50
|
- Deploy Unity and Python updates together when changing transform protocol behavior.
|
|
50
|
-
- Protocol
|
|
51
|
+
- Protocol v5 position quantization ranges:
|
|
51
52
|
- Absolute (`headPosAbs` only): signed `int24` at `0.01 m` per unit, per-axis range `[-83,886.08 m, 83,886.07 m]`.
|
|
52
|
-
- XROrigin locomotion delta (`xrOriginDelta`, 4×`int16`: `dx, dy, dz, dyaw`): `0.01 m` per unit for translation, `0.1°` for yaw. Receivers reconstruct `physicalPos = invDeltaRot * (headPos − deltaPos)`; it is not on the wire as a separate absolute field.
|
|
53
|
+
- XROrigin locomotion delta for unbound poses (`xrOriginDelta`, 4×`int16`: `dx, dy, dz, dyaw`): `0.01 m` per unit for translation, `0.1°` for yaw. Receivers reconstruct `physicalPos = invDeltaRot * (headPos − deltaPos)`; it is not on the wire as a separate absolute field.
|
|
54
|
+
- Direct physical payload for moving-floor-local poses (`physical`, 4×`int16`: `x, y, z, yaw`): `0.01 m` per unit for translation, `0.1°` for yaw.
|
|
53
55
|
- Head-relative (`right/left/virtual`): signed `int16` at `0.005 m` per unit, per-axis range `[-163.84 m, 163.835 m]`.
|
|
54
56
|
- These are encoding limits, not a hard world-size cap. Worlds can be larger, but encoded axis values are clamped if they exceed the representable range.
|
|
55
57
|
|
|
@@ -57,7 +59,7 @@ styly-netsync-simulator --server tcp://localhost --room my_room --clients 50
|
|
|
57
59
|
|
|
58
60
|
The following options summarize trade-offs when expanding absolute-position range.
|
|
59
61
|
|
|
60
|
-
Assumed baseline (`protocolVersion=
|
|
62
|
+
Assumed unbound baseline (`protocolVersion=5`, `MovingFloorLocal` off):
|
|
61
63
|
- Client pose body with `Physical+Head+Right+Left` valid and `virtualCount=0`: `46 bytes` (matches `test_client_body_size_with_full_pose_no_virtuals`).
|
|
62
64
|
- Room per-client entry (`clientNo + poseTime + clientBody`): `56 bytes`.
|
|
63
65
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "styly-netsync-server"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.12.0"
|
|
8
8
|
description = "STYLY NetSync Server - Multiplayer framework for Location-Based Entertainment VR/MR experiences"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
{styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/src/styly_netsync/binary_serializer.py
RENAMED
|
@@ -6,7 +6,7 @@ from typing import Any
|
|
|
6
6
|
logger = logging.getLogger(__name__)
|
|
7
7
|
|
|
8
8
|
# Message type identifiers
|
|
9
|
-
PROTOCOL_VERSION =
|
|
9
|
+
PROTOCOL_VERSION = 5
|
|
10
10
|
MSG_CLIENT_TRANSFORM = 1
|
|
11
11
|
MSG_ROOM_TRANSFORM = 2 # Legacy room transform with short IDs only
|
|
12
12
|
MSG_RPC = 3 # Remote procedure call
|
|
@@ -32,7 +32,7 @@ MSG_OBJECT_OWNERSHIP_REJECTED = 17 # Server → Client (ROUTER): request reject
|
|
|
32
32
|
_max_virtual_transforms = 50
|
|
33
33
|
MAX_VIRTUAL_TRANSFORMS = _max_virtual_transforms # Legacy alias for backward compat
|
|
34
34
|
|
|
35
|
-
# Protocol
|
|
35
|
+
# Protocol v5 transform encoding constants
|
|
36
36
|
ABS_POS_SCALE = 0.01
|
|
37
37
|
LOCO_POS_SCALE = 0.01
|
|
38
38
|
REL_POS_SCALE = 0.005
|
|
@@ -69,6 +69,15 @@ POSE_FLAG_HEAD_VALID = 1 << 2
|
|
|
69
69
|
POSE_FLAG_RIGHT_VALID = 1 << 3
|
|
70
70
|
POSE_FLAG_LEFT_VALID = 1 << 4
|
|
71
71
|
POSE_FLAG_VIRTUALS_VALID = 1 << 5
|
|
72
|
+
POSE_FLAG_MOVING_FLOOR_LOCAL = 1 << 6
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _compute_encoding_flags(flags: int) -> int:
|
|
76
|
+
"""Return pose encoding flags for the sanitized pose flags."""
|
|
77
|
+
encoding_flags = ENCODING_FLAGS_DEFAULT
|
|
78
|
+
if flags & POSE_FLAG_MOVING_FLOOR_LOCAL:
|
|
79
|
+
encoding_flags &= ~ENCODING_PHYSICAL_IS_XRORIGIN_DELTA
|
|
80
|
+
return encoding_flags & 0xFF
|
|
72
81
|
|
|
73
82
|
|
|
74
83
|
def get_max_virtual_transforms() -> int:
|
|
@@ -372,7 +381,7 @@ def _create_transform_dict(
|
|
|
372
381
|
|
|
373
382
|
|
|
374
383
|
def _serialize_client_body(buffer: bytearray, client: dict[str, Any]) -> None:
|
|
375
|
-
"""Serialize a client body in protocol
|
|
384
|
+
"""Serialize a client body in protocol v5 compact format."""
|
|
376
385
|
pose_seq = int(client.get("poseSeq", 0)) & 0xFFFF
|
|
377
386
|
head = client.get("head", {}) or {}
|
|
378
387
|
right = client.get("rightHand", {}) or {}
|
|
@@ -411,34 +420,51 @@ def _serialize_client_body(buffer: bytearray, client: dict[str, Any]) -> None:
|
|
|
411
420
|
POSE_FLAG_RIGHT_VALID | POSE_FLAG_LEFT_VALID | POSE_FLAG_VIRTUALS_VALID
|
|
412
421
|
)
|
|
413
422
|
|
|
423
|
+
encoding_flags = _compute_encoding_flags(flags)
|
|
414
424
|
buffer.extend(struct.pack("<H", pose_seq))
|
|
415
425
|
buffer.append(flags)
|
|
416
|
-
buffer.append(
|
|
426
|
+
buffer.append(encoding_flags)
|
|
417
427
|
|
|
418
428
|
physical_valid = bool(flags & POSE_FLAG_PHYSICAL_VALID)
|
|
419
429
|
head_valid = bool(flags & POSE_FLAG_HEAD_VALID)
|
|
420
430
|
right_valid = head_valid and bool(flags & POSE_FLAG_RIGHT_VALID)
|
|
421
431
|
left_valid = head_valid and bool(flags & POSE_FLAG_LEFT_VALID)
|
|
422
432
|
virtual_valid = head_valid and bool(flags & POSE_FLAG_VIRTUALS_VALID)
|
|
433
|
+
moving_floor_local = bool(flags & POSE_FLAG_MOVING_FLOOR_LOCAL)
|
|
423
434
|
|
|
424
435
|
xr_origin_delta_x = float(client.get("xrOriginDeltaX", 0.0))
|
|
425
436
|
xr_origin_delta_y = float(client.get("xrOriginDeltaY", 0.0))
|
|
426
437
|
xr_origin_delta_z = float(client.get("xrOriginDeltaZ", 0.0))
|
|
427
438
|
xr_origin_delta_yaw = float(client.get("xrOriginDeltaYaw", 0.0))
|
|
439
|
+
physical = client.get("physical", {}) or {}
|
|
428
440
|
head_pos = _transform_get_position(head)
|
|
429
441
|
head_rot = _transform_get_quaternion(head)
|
|
430
442
|
head_rot_n = _normalize_quaternion(*head_rot)
|
|
431
443
|
|
|
432
444
|
if physical_valid:
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
445
|
+
if moving_floor_local:
|
|
446
|
+
physical_pos = _transform_get_position(physical)
|
|
447
|
+
physical_rot = _transform_get_quaternion(physical)
|
|
448
|
+
physical_yaw = _quaternion_to_yaw_degrees(*physical_rot)
|
|
449
|
+
buffer.extend(
|
|
450
|
+
struct.pack(
|
|
451
|
+
"<hhhh",
|
|
452
|
+
_quantize_signed(physical_pos[0], LOCO_POS_SCALE),
|
|
453
|
+
_quantize_signed(physical_pos[1], LOCO_POS_SCALE),
|
|
454
|
+
_quantize_signed(physical_pos[2], LOCO_POS_SCALE),
|
|
455
|
+
_quantize_signed(physical_yaw, PHYSICAL_YAW_SCALE),
|
|
456
|
+
)
|
|
457
|
+
)
|
|
458
|
+
else:
|
|
459
|
+
buffer.extend(
|
|
460
|
+
struct.pack(
|
|
461
|
+
"<hhhh",
|
|
462
|
+
_quantize_signed(xr_origin_delta_x, LOCO_POS_SCALE),
|
|
463
|
+
_quantize_signed(xr_origin_delta_y, LOCO_POS_SCALE),
|
|
464
|
+
_quantize_signed(xr_origin_delta_z, LOCO_POS_SCALE),
|
|
465
|
+
_quantize_signed(xr_origin_delta_yaw, PHYSICAL_YAW_SCALE),
|
|
466
|
+
)
|
|
440
467
|
)
|
|
441
|
-
)
|
|
442
468
|
|
|
443
469
|
if head_valid:
|
|
444
470
|
_pack_int24_le(buffer, _quantize_signed_int24(head_pos[0], ABS_POS_SCALE))
|
|
@@ -852,7 +878,7 @@ def deserialize(data: bytes) -> tuple[int, dict[str, Any] | None, bytes]:
|
|
|
852
878
|
|
|
853
879
|
|
|
854
880
|
def _deserialize_client_body(data: bytes, offset: int) -> tuple[dict[str, Any], int]:
|
|
855
|
-
"""Deserialize protocol
|
|
881
|
+
"""Deserialize protocol v5 compact pose body."""
|
|
856
882
|
result: dict[str, Any] = {}
|
|
857
883
|
result["poseSeq"] = struct.unpack("<H", data[offset : offset + 2])[0]
|
|
858
884
|
offset += 2
|
|
@@ -868,11 +894,18 @@ def _deserialize_client_body(data: bytes, offset: int) -> tuple[dict[str, Any],
|
|
|
868
894
|
right_valid = head_valid and bool(flags & POSE_FLAG_RIGHT_VALID)
|
|
869
895
|
left_valid = head_valid and bool(flags & POSE_FLAG_LEFT_VALID)
|
|
870
896
|
virtual_valid = head_valid and bool(flags & POSE_FLAG_VIRTUALS_VALID)
|
|
897
|
+
moving_floor_local = bool(flags & POSE_FLAG_MOVING_FLOOR_LOCAL)
|
|
871
898
|
|
|
872
899
|
physical = _create_transform_dict(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, True)
|
|
873
|
-
head = _create_transform_dict(
|
|
874
|
-
|
|
875
|
-
|
|
900
|
+
head = _create_transform_dict(
|
|
901
|
+
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, moving_floor_local
|
|
902
|
+
)
|
|
903
|
+
right = _create_transform_dict(
|
|
904
|
+
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, moving_floor_local
|
|
905
|
+
)
|
|
906
|
+
left = _create_transform_dict(
|
|
907
|
+
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, moving_floor_local
|
|
908
|
+
)
|
|
876
909
|
|
|
877
910
|
head_pos = (0.0, 0.0, 0.0)
|
|
878
911
|
head_rot = (0.0, 0.0, 0.0, 1.0)
|
|
@@ -882,15 +915,19 @@ def _deserialize_client_body(data: bytes, offset: int) -> tuple[dict[str, Any],
|
|
|
882
915
|
xr_origin_delta_yaw = 0.0
|
|
883
916
|
|
|
884
917
|
if physical_valid:
|
|
885
|
-
if (
|
|
918
|
+
if (
|
|
919
|
+
not moving_floor_local
|
|
920
|
+
and (encoding_flags & ENCODING_PHYSICAL_IS_XRORIGIN_DELTA) == 0
|
|
921
|
+
):
|
|
886
922
|
raise ValueError(
|
|
887
923
|
"PhysicalValid set but XROrigin delta encoding flag is missing"
|
|
888
924
|
)
|
|
889
925
|
dx_q, dy_q, dz_q, dyaw_q = struct.unpack("<hhhh", data[offset : offset + 8])
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
926
|
+
if not moving_floor_local:
|
|
927
|
+
xr_origin_delta_x = _dequantize_signed(dx_q, LOCO_POS_SCALE)
|
|
928
|
+
xr_origin_delta_y = _dequantize_signed(dy_q, LOCO_POS_SCALE)
|
|
929
|
+
xr_origin_delta_z = _dequantize_signed(dz_q, LOCO_POS_SCALE)
|
|
930
|
+
xr_origin_delta_yaw = _dequantize_signed(dyaw_q, PHYSICAL_YAW_SCALE)
|
|
894
931
|
offset += 8
|
|
895
932
|
|
|
896
933
|
if head_valid:
|
|
@@ -913,10 +950,29 @@ def _deserialize_client_body(data: bytes, offset: int) -> tuple[dict[str, Any],
|
|
|
913
950
|
head_rot[1],
|
|
914
951
|
head_rot[2],
|
|
915
952
|
head_rot[3],
|
|
916
|
-
|
|
953
|
+
moving_floor_local,
|
|
917
954
|
)
|
|
918
955
|
|
|
919
|
-
if physical_valid and
|
|
956
|
+
if physical_valid and moving_floor_local:
|
|
957
|
+
physical_pos = (
|
|
958
|
+
_dequantize_signed(dx_q, LOCO_POS_SCALE),
|
|
959
|
+
_dequantize_signed(dy_q, LOCO_POS_SCALE),
|
|
960
|
+
_dequantize_signed(dz_q, LOCO_POS_SCALE),
|
|
961
|
+
)
|
|
962
|
+
physical_rot = _yaw_degrees_to_quaternion(
|
|
963
|
+
_dequantize_signed(dyaw_q, PHYSICAL_YAW_SCALE)
|
|
964
|
+
)
|
|
965
|
+
physical = _create_transform_dict(
|
|
966
|
+
physical_pos[0],
|
|
967
|
+
physical_pos[1],
|
|
968
|
+
physical_pos[2],
|
|
969
|
+
physical_rot[0],
|
|
970
|
+
physical_rot[1],
|
|
971
|
+
physical_rot[2],
|
|
972
|
+
physical_rot[3],
|
|
973
|
+
True,
|
|
974
|
+
)
|
|
975
|
+
elif physical_valid and head_valid:
|
|
920
976
|
translated_x = head_pos[0] - xr_origin_delta_x
|
|
921
977
|
translated_y = head_pos[1] - xr_origin_delta_y
|
|
922
978
|
translated_z = head_pos[2] - xr_origin_delta_z
|
|
@@ -970,7 +1026,7 @@ def _deserialize_client_body(data: bytes, offset: int) -> tuple[dict[str, Any],
|
|
|
970
1026
|
abs_rot[1],
|
|
971
1027
|
abs_rot[2],
|
|
972
1028
|
abs_rot[3],
|
|
973
|
-
|
|
1029
|
+
moving_floor_local,
|
|
974
1030
|
)
|
|
975
1031
|
|
|
976
1032
|
if left_valid:
|
|
@@ -999,7 +1055,7 @@ def _deserialize_client_body(data: bytes, offset: int) -> tuple[dict[str, Any],
|
|
|
999
1055
|
abs_rot[1],
|
|
1000
1056
|
abs_rot[2],
|
|
1001
1057
|
abs_rot[3],
|
|
1002
|
-
|
|
1058
|
+
moving_floor_local,
|
|
1003
1059
|
)
|
|
1004
1060
|
|
|
1005
1061
|
virtual_count = data[offset]
|
|
@@ -1041,7 +1097,7 @@ def _deserialize_client_body(data: bytes, offset: int) -> tuple[dict[str, Any],
|
|
|
1041
1097
|
abs_rot[1],
|
|
1042
1098
|
abs_rot[2],
|
|
1043
1099
|
abs_rot[3],
|
|
1044
|
-
|
|
1100
|
+
moving_floor_local,
|
|
1045
1101
|
)
|
|
1046
1102
|
)
|
|
1047
1103
|
|
|
@@ -1058,7 +1114,7 @@ def _deserialize_client_body(data: bytes, offset: int) -> tuple[dict[str, Any],
|
|
|
1058
1114
|
|
|
1059
1115
|
|
|
1060
1116
|
def _deserialize_client_transform(data: bytes, offset: int) -> dict[str, Any]:
|
|
1061
|
-
"""Deserialize client pose (
|
|
1117
|
+
"""Deserialize client pose (v5) from binary data."""
|
|
1062
1118
|
result: dict[str, Any] = {}
|
|
1063
1119
|
|
|
1064
1120
|
protocol_version = data[offset]
|
|
@@ -1096,7 +1152,7 @@ def _deserialize_rpc_message(data: bytes, offset: int) -> dict[str, Any]:
|
|
|
1096
1152
|
|
|
1097
1153
|
|
|
1098
1154
|
def _deserialize_room_transform(data: bytes, offset: int) -> dict[str, Any]:
|
|
1099
|
-
"""Deserialize room pose (
|
|
1155
|
+
"""Deserialize room pose (v5) with client numbers only."""
|
|
1100
1156
|
result: dict[str, Any] = {}
|
|
1101
1157
|
|
|
1102
1158
|
protocol_version = data[offset]
|
|
@@ -1056,7 +1056,7 @@ class NetSyncServer:
|
|
|
1056
1056
|
except UnicodeDecodeError as e:
|
|
1057
1057
|
logger.error(f"Failed to decode room ID: {e}")
|
|
1058
1058
|
continue
|
|
1059
|
-
# Protocol
|
|
1059
|
+
# Protocol v5 binary-only handling (no JSON fallback)
|
|
1060
1060
|
try:
|
|
1061
1061
|
msg_type, data, raw_payload = binary_serializer.deserialize(
|
|
1062
1062
|
message_bytes
|
|
@@ -1127,7 +1127,7 @@ class NetSyncServer:
|
|
|
1127
1127
|
logger.warning(f"Unknown binary msg_type: {msg_type}")
|
|
1128
1128
|
except Exception as e:
|
|
1129
1129
|
logger.warning(
|
|
1130
|
-
"Failed to decode protocol
|
|
1130
|
+
"Failed to decode protocol v5 message from room %s: %s",
|
|
1131
1131
|
room_id,
|
|
1132
1132
|
e,
|
|
1133
1133
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: styly-netsync-server
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.12.0
|
|
4
4
|
Summary: STYLY NetSync Server - Multiplayer framework for Location-Based Entertainment VR/MR experiences
|
|
5
5
|
Author-email: "STYLY, Inc." <info@styly.inc>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -81,14 +81,16 @@ styly-netsync-simulator --server tcp://localhost --room my_room --clients 50
|
|
|
81
81
|
|
|
82
82
|
## Wire protocol compatibility
|
|
83
83
|
|
|
84
|
-
- Current transform wire protocol is `protocolVersion =
|
|
85
|
-
- Transform messages use `MSG_CLIENT_POSE` (11) and `MSG_ROOM_POSE` (12) with the compact
|
|
86
|
-
-
|
|
84
|
+
- Current transform wire protocol is `protocolVersion = 5`.
|
|
85
|
+
- Transform messages use `MSG_CLIENT_POSE` (11) and `MSG_ROOM_POSE` (12) with the compact V5 pose body.
|
|
86
|
+
- v5 adds an optional `MovingFloorLocal` pose flag. Bound avatars send head, hands, and virtual transforms in the registered moving floor's local coordinates, and reuse the existing 8-byte physical slot as direct physical position/yaw.
|
|
87
|
+
- Unbound v5 poses keep the v4 `xrOriginDelta` semantics: `xrOriginDelta` carries a Y component as a 4th `int16` (`dx, dy, dz, dyaw` = 8 bytes vs. v3's 6), so receivers can reconstruct the sender's rig-Y motion.
|
|
87
88
|
- Legacy transform protocols (v2/v3) and JSON transform fallback are not supported.
|
|
88
89
|
- Deploy Unity and Python updates together when changing transform protocol behavior.
|
|
89
|
-
- Protocol
|
|
90
|
+
- Protocol v5 position quantization ranges:
|
|
90
91
|
- Absolute (`headPosAbs` only): signed `int24` at `0.01 m` per unit, per-axis range `[-83,886.08 m, 83,886.07 m]`.
|
|
91
|
-
- XROrigin locomotion delta (`xrOriginDelta`, 4×`int16`: `dx, dy, dz, dyaw`): `0.01 m` per unit for translation, `0.1°` for yaw. Receivers reconstruct `physicalPos = invDeltaRot * (headPos − deltaPos)`; it is not on the wire as a separate absolute field.
|
|
92
|
+
- XROrigin locomotion delta for unbound poses (`xrOriginDelta`, 4×`int16`: `dx, dy, dz, dyaw`): `0.01 m` per unit for translation, `0.1°` for yaw. Receivers reconstruct `physicalPos = invDeltaRot * (headPos − deltaPos)`; it is not on the wire as a separate absolute field.
|
|
93
|
+
- Direct physical payload for moving-floor-local poses (`physical`, 4×`int16`: `x, y, z, yaw`): `0.01 m` per unit for translation, `0.1°` for yaw.
|
|
92
94
|
- Head-relative (`right/left/virtual`): signed `int16` at `0.005 m` per unit, per-axis range `[-163.84 m, 163.835 m]`.
|
|
93
95
|
- These are encoding limits, not a hard world-size cap. Worlds can be larger, but encoded axis values are clamped if they exceed the representable range.
|
|
94
96
|
|
|
@@ -96,7 +98,7 @@ styly-netsync-simulator --server tcp://localhost --room my_room --clients 50
|
|
|
96
98
|
|
|
97
99
|
The following options summarize trade-offs when expanding absolute-position range.
|
|
98
100
|
|
|
99
|
-
Assumed baseline (`protocolVersion=
|
|
101
|
+
Assumed unbound baseline (`protocolVersion=5`, `MovingFloorLocal` off):
|
|
100
102
|
- Client pose body with `Physical+Head+Right+Left` valid and `virtualCount=0`: `46 bytes` (matches `test_client_body_size_with_full_pose_no_virtuals`).
|
|
101
103
|
- Room per-client entry (`clientNo + poseTime + clientBody`): `56 bytes`.
|
|
102
104
|
|
|
@@ -288,12 +288,12 @@ def _reconstruct_physical_from_head_and_delta(
|
|
|
288
288
|
return (px, ty, pz), physical_rot
|
|
289
289
|
|
|
290
290
|
|
|
291
|
-
class
|
|
292
|
-
"""Tests for protocol
|
|
291
|
+
class TestTransformSerializationV5:
|
|
292
|
+
"""Tests for protocol v5 transform compact serialization."""
|
|
293
293
|
|
|
294
|
-
def
|
|
295
|
-
"""Protocol version constant should be at
|
|
296
|
-
assert binary_serializer.PROTOCOL_VERSION ==
|
|
294
|
+
def test_protocol_version_is_v5(self) -> None:
|
|
295
|
+
"""Protocol version constant should be at v5."""
|
|
296
|
+
assert binary_serializer.PROTOCOL_VERSION == 5
|
|
297
297
|
|
|
298
298
|
def test_client_roundtrip_without_flags_infers_valid_bits(self) -> None:
|
|
299
299
|
"""Serializer should infer valid bits when flags are omitted."""
|
|
@@ -418,7 +418,7 @@ class TestTransformSerializationV4:
|
|
|
418
418
|
|
|
419
419
|
assert msg_type == binary_serializer.MSG_CLIENT_POSE
|
|
420
420
|
assert decoded is not None
|
|
421
|
-
assert decoded["protocolVersion"] ==
|
|
421
|
+
assert decoded["protocolVersion"] == 5
|
|
422
422
|
assert len(raw) > 0
|
|
423
423
|
|
|
424
424
|
o_head = original["head"]
|
|
@@ -615,7 +615,7 @@ class TestTransformSerializationV4:
|
|
|
615
615
|
assert abs(decoded["xrOriginDeltaYaw"] - 179.9) <= 0.1
|
|
616
616
|
|
|
617
617
|
def test_client_body_size_with_full_pose_no_virtuals(self) -> None:
|
|
618
|
-
"""Full pose body (no virtuals) should match current protocol
|
|
618
|
+
"""Full pose body (no virtuals) should match current protocol byte size."""
|
|
619
619
|
payload = {
|
|
620
620
|
"deviceId": "size-check",
|
|
621
621
|
"poseSeq": 1,
|
|
@@ -638,11 +638,142 @@ class TestTransformSerializationV4:
|
|
|
638
638
|
_, _, raw = binary_serializer.deserialize(
|
|
639
639
|
binary_serializer.serialize_client_transform(payload)
|
|
640
640
|
)
|
|
641
|
-
# v4 added a Y component to xrOriginDelta (+2 bytes vs. v3's 44).
|
|
642
641
|
assert len(raw) == 46
|
|
643
642
|
|
|
643
|
+
def test_moving_floor_local_body_size_matches_unbound(self) -> None:
|
|
644
|
+
"""Moving-floor-local pose should not add bytes to the pose body."""
|
|
645
|
+
rng = random.Random(4242)
|
|
646
|
+
unbound = _build_random_client_pose(rng, virtual_count=0)
|
|
647
|
+
unbound["flags"] = (
|
|
648
|
+
binary_serializer.POSE_FLAG_PHYSICAL_VALID
|
|
649
|
+
| binary_serializer.POSE_FLAG_HEAD_VALID
|
|
650
|
+
| binary_serializer.POSE_FLAG_RIGHT_VALID
|
|
651
|
+
| binary_serializer.POSE_FLAG_LEFT_VALID
|
|
652
|
+
)
|
|
653
|
+
bound = dict(unbound)
|
|
654
|
+
bound["flags"] = (
|
|
655
|
+
unbound["flags"] | binary_serializer.POSE_FLAG_MOVING_FLOOR_LOCAL
|
|
656
|
+
)
|
|
657
|
+
bound["physical"] = _build_transform(
|
|
658
|
+
(0.15, 1.62, -0.08),
|
|
659
|
+
(
|
|
660
|
+
0.0,
|
|
661
|
+
math.sin(math.radians(35.0) * 0.5),
|
|
662
|
+
0.0,
|
|
663
|
+
math.cos(math.radians(35.0) * 0.5),
|
|
664
|
+
),
|
|
665
|
+
)
|
|
666
|
+
|
|
667
|
+
_, _, unbound_raw = binary_serializer.deserialize(
|
|
668
|
+
binary_serializer.serialize_client_transform(unbound)
|
|
669
|
+
)
|
|
670
|
+
_, _, bound_raw = binary_serializer.deserialize(
|
|
671
|
+
binary_serializer.serialize_client_transform(bound)
|
|
672
|
+
)
|
|
673
|
+
|
|
674
|
+
assert len(unbound_raw) == 46
|
|
675
|
+
assert len(bound_raw) == len(unbound_raw)
|
|
676
|
+
|
|
677
|
+
def test_moving_floor_local_roundtrip_uses_direct_physical(self) -> None:
|
|
678
|
+
"""Bound poses use direct physical pose and keep head-relative channels intact."""
|
|
679
|
+
physical_yaw = 42.0
|
|
680
|
+
physical_rot = (
|
|
681
|
+
0.0,
|
|
682
|
+
math.sin(math.radians(physical_yaw) * 0.5),
|
|
683
|
+
0.0,
|
|
684
|
+
math.cos(math.radians(physical_yaw) * 0.5),
|
|
685
|
+
)
|
|
686
|
+
payload = {
|
|
687
|
+
"deviceId": "floor-local",
|
|
688
|
+
"poseSeq": 313,
|
|
689
|
+
"flags": (
|
|
690
|
+
binary_serializer.POSE_FLAG_PHYSICAL_VALID
|
|
691
|
+
| binary_serializer.POSE_FLAG_HEAD_VALID
|
|
692
|
+
| binary_serializer.POSE_FLAG_RIGHT_VALID
|
|
693
|
+
| binary_serializer.POSE_FLAG_LEFT_VALID
|
|
694
|
+
| binary_serializer.POSE_FLAG_VIRTUALS_VALID
|
|
695
|
+
| binary_serializer.POSE_FLAG_MOVING_FLOOR_LOCAL
|
|
696
|
+
),
|
|
697
|
+
"xrOriginDeltaX": 99.0,
|
|
698
|
+
"xrOriginDeltaY": 99.0,
|
|
699
|
+
"xrOriginDeltaZ": 99.0,
|
|
700
|
+
"xrOriginDeltaYaw": 99.0,
|
|
701
|
+
"physical": _build_transform((0.25, 1.7, -0.1), physical_rot),
|
|
702
|
+
"head": _build_transform(
|
|
703
|
+
(2.0, 3.0, 4.0), _random_unit_quaternion(random.Random(501))
|
|
704
|
+
),
|
|
705
|
+
"rightHand": _build_transform(
|
|
706
|
+
(2.35, 2.8, 4.1), _random_unit_quaternion(random.Random(502))
|
|
707
|
+
),
|
|
708
|
+
"leftHand": _build_transform(
|
|
709
|
+
(1.65, 2.8, 3.9), _random_unit_quaternion(random.Random(503))
|
|
710
|
+
),
|
|
711
|
+
"virtuals": [
|
|
712
|
+
_build_transform(
|
|
713
|
+
(2.0, 3.3, 4.7), _random_unit_quaternion(random.Random(504))
|
|
714
|
+
)
|
|
715
|
+
],
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
_, decoded, _ = binary_serializer.deserialize(
|
|
719
|
+
binary_serializer.serialize_client_transform(payload)
|
|
720
|
+
)
|
|
721
|
+
assert decoded is not None
|
|
722
|
+
assert decoded["flags"] & binary_serializer.POSE_FLAG_MOVING_FLOOR_LOCAL
|
|
723
|
+
assert (
|
|
724
|
+
decoded["encodingFlags"]
|
|
725
|
+
& binary_serializer.ENCODING_PHYSICAL_IS_XRORIGIN_DELTA
|
|
726
|
+
) == 0
|
|
727
|
+
assert decoded["xrOriginDeltaX"] == 0.0
|
|
728
|
+
assert decoded["xrOriginDeltaY"] == 0.0
|
|
729
|
+
assert decoded["xrOriginDeltaZ"] == 0.0
|
|
730
|
+
assert decoded["xrOriginDeltaYaw"] == 0.0
|
|
731
|
+
|
|
732
|
+
assert abs(decoded["physical"]["posX"] - 0.25) <= 0.01
|
|
733
|
+
assert abs(decoded["physical"]["posY"] - 1.7) <= 0.01
|
|
734
|
+
assert abs(decoded["physical"]["posZ"] + 0.1) <= 0.01
|
|
735
|
+
decoded_physical_yaw = _yaw_deg_from_quaternion(
|
|
736
|
+
(
|
|
737
|
+
decoded["physical"]["rotX"],
|
|
738
|
+
decoded["physical"]["rotY"],
|
|
739
|
+
decoded["physical"]["rotZ"],
|
|
740
|
+
decoded["physical"]["rotW"],
|
|
741
|
+
)
|
|
742
|
+
)
|
|
743
|
+
assert abs(decoded_physical_yaw - physical_yaw) <= 0.1
|
|
744
|
+
|
|
745
|
+
src_head = payload["head"]
|
|
746
|
+
dst_head = decoded["head"]
|
|
747
|
+
assert abs(dst_head["posX"] - src_head["posX"]) <= 0.01
|
|
748
|
+
assert abs(dst_head["posY"] - src_head["posY"]) <= 0.01
|
|
749
|
+
assert abs(dst_head["posZ"] - src_head["posZ"]) <= 0.01
|
|
750
|
+
|
|
751
|
+
src_right = payload["rightHand"]
|
|
752
|
+
dst_right = decoded["rightHand"]
|
|
753
|
+
assert (
|
|
754
|
+
abs(
|
|
755
|
+
(dst_right["posX"] - dst_head["posX"])
|
|
756
|
+
- (src_right["posX"] - src_head["posX"])
|
|
757
|
+
)
|
|
758
|
+
<= 0.005
|
|
759
|
+
)
|
|
760
|
+
assert (
|
|
761
|
+
abs(
|
|
762
|
+
(dst_right["posY"] - dst_head["posY"])
|
|
763
|
+
- (src_right["posY"] - src_head["posY"])
|
|
764
|
+
)
|
|
765
|
+
<= 0.005
|
|
766
|
+
)
|
|
767
|
+
assert (
|
|
768
|
+
abs(
|
|
769
|
+
(dst_right["posZ"] - dst_head["posZ"])
|
|
770
|
+
- (src_right["posZ"] - src_head["posZ"])
|
|
771
|
+
)
|
|
772
|
+
<= 0.005
|
|
773
|
+
)
|
|
774
|
+
|
|
644
775
|
def test_physical_requires_delta_encoding_flag(self) -> None:
|
|
645
|
-
"""PhysicalValid frames must carry the XROrigin-delta encoding bit."""
|
|
776
|
+
"""Unbound PhysicalValid frames must carry the XROrigin-delta encoding bit."""
|
|
646
777
|
payload = {
|
|
647
778
|
"deviceId": "missing-delta-flag",
|
|
648
779
|
"poseSeq": 7,
|
|
@@ -682,7 +813,7 @@ class TestTransformSerializationV4:
|
|
|
682
813
|
c2["poseTime"] = 223.456
|
|
683
814
|
|
|
684
815
|
room_payload = {
|
|
685
|
-
"roomId": "room-
|
|
816
|
+
"roomId": "room-v5",
|
|
686
817
|
"broadcastTime": 999.123,
|
|
687
818
|
"clients": [c1, c2],
|
|
688
819
|
}
|
|
@@ -691,8 +822,8 @@ class TestTransformSerializationV4:
|
|
|
691
822
|
|
|
692
823
|
assert msg_type == binary_serializer.MSG_ROOM_POSE
|
|
693
824
|
assert decoded is not None
|
|
694
|
-
assert decoded["protocolVersion"] ==
|
|
695
|
-
assert decoded["roomId"] == "room-
|
|
825
|
+
assert decoded["protocolVersion"] == 5
|
|
826
|
+
assert decoded["roomId"] == "room-v5"
|
|
696
827
|
assert len(decoded["clients"]) == 2
|
|
697
828
|
|
|
698
829
|
for src, dst in zip(room_payload["clients"], decoded["clients"], strict=True):
|
|
@@ -705,6 +836,45 @@ class TestTransformSerializationV4:
|
|
|
705
836
|
assert abs(src_head["posY"] - dst_head["posY"]) <= 0.01
|
|
706
837
|
assert abs(src_head["posZ"] - dst_head["posZ"]) <= 0.01
|
|
707
838
|
|
|
839
|
+
def test_room_relay_preserves_moving_floor_local_flag(self) -> None:
|
|
840
|
+
"""Room serialization should preserve moving-floor-local pose bodies."""
|
|
841
|
+
rng = random.Random(2027)
|
|
842
|
+
bound = _build_random_client_pose(rng, virtual_count=1)
|
|
843
|
+
bound["clientNo"] = 303
|
|
844
|
+
bound["poseTime"] = 333.456
|
|
845
|
+
bound["flags"] = (
|
|
846
|
+
int(bound["flags"]) | binary_serializer.POSE_FLAG_MOVING_FLOOR_LOCAL
|
|
847
|
+
)
|
|
848
|
+
bound["physical"] = _build_transform(
|
|
849
|
+
(0.2, 1.65, 0.05),
|
|
850
|
+
(
|
|
851
|
+
0.0,
|
|
852
|
+
math.sin(math.radians(-15.0) * 0.5),
|
|
853
|
+
0.0,
|
|
854
|
+
math.cos(math.radians(-15.0) * 0.5),
|
|
855
|
+
),
|
|
856
|
+
)
|
|
857
|
+
|
|
858
|
+
encoded = binary_serializer.serialize_room_transform(
|
|
859
|
+
{
|
|
860
|
+
"roomId": "room-floor-local",
|
|
861
|
+
"broadcastTime": 1000.0,
|
|
862
|
+
"clients": [bound],
|
|
863
|
+
}
|
|
864
|
+
)
|
|
865
|
+
msg_type, decoded, _ = binary_serializer.deserialize(encoded)
|
|
866
|
+
|
|
867
|
+
assert msg_type == binary_serializer.MSG_ROOM_POSE
|
|
868
|
+
assert decoded is not None
|
|
869
|
+
assert len(decoded["clients"]) == 1
|
|
870
|
+
client = decoded["clients"][0]
|
|
871
|
+
assert client["flags"] & binary_serializer.POSE_FLAG_MOVING_FLOOR_LOCAL
|
|
872
|
+
assert (
|
|
873
|
+
client["encodingFlags"]
|
|
874
|
+
& binary_serializer.ENCODING_PHYSICAL_IS_XRORIGIN_DELTA
|
|
875
|
+
) == 0
|
|
876
|
+
assert abs(client["physical"]["posY"] - 1.65) <= 0.01
|
|
877
|
+
|
|
708
878
|
def test_foot_invariant_head_minus_physical_equals_delta_y(self) -> None:
|
|
709
879
|
"""head.y - reconstructed physical.y must equal xrOriginDeltaY.
|
|
710
880
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/src/styly_netsync/client_simulator.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/src/styly_netsync/logging_utils.py
RENAMED
|
File without changes
|
{styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/src/styly_netsync/network_utils.py
RENAMED
|
File without changes
|
|
File without changes
|
{styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/src/styly_netsync/rest_bridge.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/tests/test_port_error_message.py
RENAMED
|
File without changes
|
|
File without changes
|
{styly_netsync_server-0.11.0 → styly_netsync_server-0.12.0}/tests/test_reconnect_identity.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|