Python-WebXR Communication Scheme
The Virtual Field runtime uses a websocket-based JSON protocol between:
the Python runtime server in
src/virtual_field/server/app.pythe WebXR browser client in
VR/client/app.js
This page describes the message envelope and the main message types currently used by the Python and WebXR sides.
Transport
transport: websocket
encoding: JSON text messages
protocol version:
1
Every message uses the same top-level envelope:
{
"version": 1,
"type": "message_type",
"payload": {}
}
The Python runtime validates:
versionexists and equals1typeis a stringpayloadis a JSON object
Connection roles
The runtime currently supports three roles:
vr_client: interactive WebXR or desktop client controlling armsspectator: read-only client receiving scene statepublisher: external client that publishes meshes and overlay data into the scene
The browser client usually connects as vr_client or spectator.
Startup handshake
Client to Python: hello
The first message must be hello.
Example for a VR client:
{
"version": 1,
"type": "hello",
"payload": {
"client": "bsr-webxr",
"role": "vr_client",
"requested_arm_count": 2,
"character_mode": "spirobs"
}
}
Common payload fields:
client: optional client identifier stringrole:vr_client,spectator, orpublisherrequested_arm_count: requested arm count forvr_clientcharacter_mode: requested simulation mode forvr_clientuser_id: optional preferred user id forvr_clientowner_id: optional owner id forpublisher
Python to client: hello_ack
The Python runtime responds with hello_ack.
Example for vr_client:
{
"version": 1,
"type": "hello_ack",
"payload": {
"protocol": 1,
"server_time": 1712345678.12,
"role": "vr_client",
"character_mode": "spirobs",
"user_id": "user_1",
"arm_ids": ["user_1_arm_0", "user_1_arm_1"],
"controlled_arm_ids": ["user_1_arm_0", "user_1_arm_1"]
}
}
Example for spectator:
{
"version": 1,
"type": "hello_ack",
"payload": {
"protocol": 1,
"server_time": 1712345678.12,
"role": "spectator",
"user_id": "spectator_1",
"arm_ids": [],
"controlled_arm_ids": []
}
}
Python to client: asset_manifest
After hello_ack, Python sends asset_manifest.
Example:
{
"version": 1,
"type": "asset_manifest",
"payload": {
"user_id": "user_1",
"arms": {
"user_1_arm_0": {"color": "#ff6b6b"},
"user_1_arm_1": {"color": "#74c0fc"}
},
"scenery": {}
}
}
This is used by the client for arm color assignment and basic scene setup.
WebXR input sent to Python
Client to Python: xr_input
The browser sends controller and head tracking data as xr_input.
Example:
{
"version": 1,
"type": "xr_input",
"payload": {
"timestamp": 1234.56,
"head_pose": {
"translation": [0.0, 1.6, 0.0],
"rotation_xyzw": [0.0, 0.0, 0.0, 1.0]
},
"controllers": {
"left": {
"pose": {
"translation": [-0.2, 1.3, -0.4],
"rotation_xyzw": [0.0, 0.0, 0.0, 1.0]
},
"velocity": {
"linear": [0.0, 0.0, 0.0],
"angular": [0.0, 0.0, 0.0]
},
"grip": 1.0,
"trigger": 0.0,
"joystick": [0.0, 0.0],
"buttons": {
"trigger_click": false,
"grip_click": true,
"primary": false,
"secondary": false
}
},
"right": {
"pose": {
"translation": [0.2, 1.3, -0.4],
"rotation_xyzw": [0.0, 0.0, 0.0, 1.0]
},
"velocity": {
"linear": [0.0, 0.0, 0.0],
"angular": [0.0, 0.0, 0.0]
},
"grip": 0.8,
"trigger": 0.3,
"joystick": [0.1, -0.2],
"buttons": {
"trigger_click": false,
"grip_click": false,
"primary": false,
"secondary": true
}
}
}
}
}
This payload maps directly onto XRInputSample and ControllerSample in
src/virtual_field/core/commands.py.
Relevant controller fields
Each controller may contain:
pose.translation: controller position in scene coordinatespose.rotation_xyzw: controller orientation quaternionvelocity.linearvelocity.angulargrip: analog grip valuetrigger: analog trigger valuejoystick: 2D thumbstick valuebuttons: boolean click/button state
The runtime converts this to arm commands internally through
SessionArmControlMapper.
Keepalive and reset
Client to Python: heartbeat
Clients may send:
{
"version": 1,
"type": "heartbeat",
"payload": {}
}
This updates the server-side session timeout.
Client to Python: reset
vr_client may request arm reallocation and mode reset:
{
"version": 1,
"type": "reset",
"payload": {}
}
Python responds with a fresh hello_ack payload containing reset: true.
Scene updates sent from Python
Python to client: scene_state
Python publishes the current simulation scene continuously with scene_state.
Top-level payload shape:
{
"timestamp": 1.23,
"arms": {},
"scenery": {},
"user_arms": {},
"meshes": {},
"overlay_points": {},
"spheres": {}
}
arms
arms is a mapping from arm_id to arm state.
Each arm state contains:
arm_idowner_user_idbasetipcenterlineradiielement_lengthsdirectorscontact_points
The WebXR renderer primarily uses:
tipcenterlineradiioptionally
element_lengthsoptionally
directorsoptionally
contact_points
user_arms
user_arms maps each user id to the list of arm ids owned by that user.
This is especially important for spectator mode because the client uses it to decide which arms to render as the currently observed pair.
meshes
meshes is a mapping from mesh_id to dynamic mesh entity data.
Each mesh contains:
mesh_idowner_idasset_uritranslationrotation_xyzwscalevisible
overlay_points
overlay_points is a mapping from overlay_id to point-cloud-like overlay data.
Each overlay contains:
overlay_idowner_idpointspoint_sizevisible
spheres
spheres is a mapping from sphere_id to simple sphere entities.
Each sphere contains:
sphere_idowner_idtranslationradiuscolor_rgbvisible
Publisher messages
The publisher role is used by Python-side or external tools that inject scene
content without controlling arms.
Supported messages currently include:
add_meshremove_meshupdate_mesh_transformclear_meshesupdate_overlay_pointsremove_overlay_pointsclear_overlay_pointsheartbeat
These messages are acknowledged with:
mesh_ackoverlay_ackor
error
Error handling
Python to client: error
If a request is invalid or unsupported, Python responds with:
{
"version": 1,
"type": "error",
"payload": {
"reason": "human-readable explanation"
}
}
Examples include:
missing
hellounsupported protocol version
unsupported message type
invalid publisher mesh update
role mismatch, such as sending
xr_inputfrom a non-vr_client
Summary
The Python-WebXR communication scheme is intentionally simple:
JSON envelope with
version,type, andpayloadhello/hello_ack/asset_manifestfor setupxr_inputandheartbeatfrom WebXR client to Pythonscene_statefrom Python to WebXR clientpublisher-only mesh and overlay messages for auxiliary scene content
This makes it straightforward to implement either side independently as long as the payload shapes stay aligned with the shared runtime schema.