Skip to content

Sophia Unity runtime

Role, source and build

sophia_unity is a current platform component. Its master branch builds the Sophia WebGL scene, consuming animation packets from the ROS WebSocket streamer. It renders the virtual character; loading its assets does not establish that animation streaming or robot actuation is available.

The inspected project pins Unity 6000.3.22f1. SophiaUnity.Editor.SophiaWebGLBuild.Build builds only Assets/Scenes/sophia.unity, defaults to Builds/WebGL, and accepts UNITY_BUILD_PATH. CI runs on master pushes or manual dispatch, checks out LFS assets, builds WebGL, and publishes an immutable asset image. Consult the current workflow for artifact tags and the stack manifest for the activated digest.

The WebUI exposes installed assets only when the activation environment and directory are valid. /api/virtual-project returns the chosen loader/assets; clients should use that response rather than construct filenames. Serve .wasm as application/wasm and compressed files with their actual content encoding. A wrong MIME/encoding combination can break the player before any socket connection occurs.

Animation connection

The streamer listens on 0.0.0.0:9099 by default and subscribes to PAU data. Its default rate is 50 Hz, with --fps, --port, --topic, --config, --projection_matrix, and --world2camera_matrix controls. It translates robot animation into the Unity packet format and applies configured limits/mappings.

For a WebGL page on a nonloopback origin, ConnectionUI derives ws[s]://<page-authority>/unity_wss. For loopback pages it selects ws://localhost:9099. Outside that WebGL resolution path it uses the configured queue address. A remote proxy must upgrade /unity_wss to the stream listener; serving static assets alone is insufficient.

The current documentation host intentionally serves only static documentation. It is not a Unity animation proxy.

Packet contract

Packets are JSON objects with at least one recognized animation channel: joints, blendshapes, head_rotation, neck_rotation, eyes, or jaw. The model also accepts timestamp and optional camera matrices.

Field Structure / handling
head_rotation, neck_rotation Objects with float x, y, z; downstream accessors reorder axes
eyes leftPitch, leftYaw, rightPitch, rightYaw
joints Named body/arm/finger float channels
blendshapes Named facial float channels
jaw Float passed to head animator
timestamp Float used for interpolation interval
projection_matrix, world2camera_matrix Optional matrix model fields

AnimationPacket.TryParse rejects empty input, payloads longer than 1,048,576 UTF-16 code units, nonobject text, packets with no recognized animation channel, invalid JSON, and nonfinite public float values. Partial packets overwrite a copy of the previous model; missing major sections are initialized. A partial update therefore retains previous values rather than resetting every other channel.

Minimal visual-only injection example:

{"head_rotation":{"x":0,"y":0,"z":0},"jaw":0,"timestamp":1}

This is a structural example, not a claim about calibrated joint units or desired robot pose. Axis/unit conversions belong to ws_streamer mappings and the animator implementation. VectorModel.GetYawPitchRoll() returns (x,z,y) and GetRollPitchYaw() returns (z,x,y); eye accessors reorder yaw/pitch. Do not interpret packet axes as a direct physical robot coordinate system.

Queueing, reconnect and interpolation

PackageQueue validates ws/wss addresses, uses connection generations to discard obsolete connection attempts, and resets prior packets/interpolation state on disconnect. Each render iteration consumes at most 64 messages, merges partial updates, and uses the latest resulting packet. Intermediate frames can be dropped intentionally to keep rendering responsive.

Timestamp deltas above zero and no greater than 0.25 seconds inform the interpolation interval, clamped to 1/120–0.1 seconds; other deltas fall back to 1/30 second. The queue reports packet receive rate. Automatic reconnect is scheduled after one second by ConnectionUI when enabled. Choosing Disconnect disables automatic reconnection.

Browser/HTML integration methods

Methods are Unity component entry points, not HTTP routes. Host pages may invoke them using Unity's component-message mechanism when the scene contains the intended object.

Component method Effect
ConnectionUI.SetUIvisibility(int) Set overlay visibility to value == 1
ConnectionUI.SetUIvisible(int) Toggle overlay; argument is not used as an absolute state
ConnectionUI.SetVerboseMessage(int) Enable verbose logging for value 1
ConnectionUI.SetKeyInputAllowed(int) Set WebGL keyboard capture
ConnectionUI.SetWSurn(string) Set address; prepend ws:// if string does not start with ws
ConnectionUI.ForceConnectWS(int) Enable auto-connect and connect
ConnectionUI.InjectJSONframe(string) Parse and apply an immediate frame through the queue
ConnectionUI.SetRangesBlendshapes(int) Set head blendshape range
ConnectionUI.SetRangesJaw(int) Set jaw range
AnimationDriver.SetMatrixUpdateAllowed(int) Set projection-update flag
AnimationDriver.SetW2cMatrixUpdateAllowed(int) Set world-to-camera-update flag

The inspected AnimationDriver has camera-matrix application code commented out. Setting its flags does not establish that camera matrices are applied. The optional OnAppReady browser callback is guarded so its absence does not abort startup.

UI and camera controls

  • Press H to toggle the connection overlay when keyboard input reaches Unity.
  • The overlay shows server address, connection state and receive rate; it offers reconnect behavior and explicit connection/disconnection.
  • Camera scripts include head-only/full-body view presets and B for background switching when those scene components are active.
  • Movement uses configured Unity input axes (Fire1, Vertical, Horizontal); actual bindings follow project Input settings.
  • WebGL starts with global keyboard capture disabled, allowing surrounding operator controls to work.

Diagnosis and acceptance

Check asset fetches and WebGL startup first, /unity_wss handshake second, packet rate third, and channel/mapping/rig behavior last. A connected socket with zero packets points upstream; packets with no motion point to parser/channel/rig issues. If the player repeatedly reconnects, inspect proxy upgrade settings, origin scheme and the stream process.

Inspect CI/build output and scene validators before publishing new character assets. Validate face, head, eye, jaw, arm and finger channels against the selected rig, then compare behavior with the intended physical profile. These visual/hardware checks are not established by source inspection.

Evidence: sophia_unity/ProjectSettings/ProjectVersion.txt, .github/workflows/webgl-deploy.yml, Assets/Editor/SophiaWebGLBuild.cs, Assets/AnimReplicator/{ConnectionUI.cs,AnimationDriver.cs,Util/PackageQueue.cs,Models/*}, camera scripts, and head-ros-nodes/ros/ros_misc/nodes/ws_streamer.py.