Skip to content

C# API

VHI_godot

VHI_godot Assembly

Two namespaces, one origin

Vhi.* is VHI's hand-written C# - what runs in the Godot scene. The one gRPC service lives here: Vhi.VhiControlService renders what an application controls and coordinates the recording session a capture pipeline drives it through.

Myogestic.Remote.* is auto-generated by Grpc.Tools at build time from proto/remote_control.proto: the typed request / reply messages, the Kind enum, and the server stub that service implements. The namespace name follows protobuf convention - the same .proto is also vendored on the MyoGestic side. Edit the .proto (not these files) to change the wire contract.

The legacy Myogestic.Vhi.V1.* namespace, the separate VhiTrainingAid service, and the ContinuousEncoding enum are all gone; a client that still speaks the old contract receives UNIMPLEMENTED.

Namespaces

Myogestic.Remote Namespace

Classes
ControlAck
ControlCapability One control this target exports, addressed by a stable dotted name. The address is **owned by the target**. A client maps its own arbitrary model-output name onto one of these; it does not invent addresses and it does not hard-code their semantics. Everything a client needs in order to send values correctly is declared right here, by the side that carries them out: - whether it is a number or a held state, and - for a number: its domain and its neutral value, and - for a state: the states it accepts and which one is neutral. That is the whole point of shipping a manifest rather than a naming convention. A convention has to be kept in sync by hand on both sides; a manifest is read.
ControlManifest
GetControlManifestRequest Empty — proto3 needs a message type even with no parameters.
GetRecordingSessionStateRequest
RecordingAck
RecordingSessionState
RemoteControl Control plane between MyoGestic and a target — a separate application that displays or actuates the values MyoGestic produces. GetControlManifest is the whole contract: it returns every control the target exports, by address (e.g. "vhi.prediction.index"), with what it can drive — the kind, the range and the states — so neither side hard-codes a stream layout or a movement table. A client calls it unconditionally, once, before sending anything; there is no per-client negotiation and nothing to declare. Continuous and discrete DOFs address separate rigs — in the Virtual Hand, the predicted hand and the operator's control hand — so the two never contend for one driver. SweepControl is the verification primitive: it confirms a DOF actually moves the way its name claims — that a control named for a joint moves that joint, in the direction its name claims — without a human watching the screen. Continuous time-series still belongs on LSL, **one stream per DOF**: a streamed control's stream is named for the control's own address and is one channel wide, so the manifest's address is also the stream name and there is nothing further to publish about the transport. This service carries discrete state, low-rate continuous commands, and verification.
RemoteControl.RemoteControlBase Base class for server-side implementations of RemoteControl
SetControlRequest
SetPresentationRequest How the target blends between the values it is given. This is the third and last of three distinct layers, and confusing them is the mistake it exists to prevent: 1. Continuous smoothing, on the MyoGestic side, inside ControlBus and before any target sees a frame. Authoritative: it decides what value is *commanded*. 2. Discrete debounce and hysteresis, also MyoGestic-side, declared on the DOF. A classifier's state must be gated for stability before it becomes a transition. A discrete control is never numerically low-pass filtered as though it were an axis — that would interpolate through states nobody selected. 3. This: purely visual interpolation in the target, so the rig does not snap jarringly between poses. Layer 3 must never be relied on as protection against classifier chatter. It changes only how a commanded value *looks* on its way to being reached; it does not change the value, and it cannot make an unstable prediction stable. A build with blending on and no debounce still jumps between states — it just does so smoothly.
SetRecordingSessionRequest
StartRecordingTrajectoryRequest
StopRecordingTrajectoryRequest Empty — proto3 needs a message type even with no parameters.
SweepControlReply
SweepControlRequest
SweepObservation What the target measured on its own rig while sweeping. Read back from the rig after the sweep, not echoed from the request — otherwise it would confirm only that the target can repeat a number back.
Enums
Kind What a control is. Mirrors the standard rather than any one target's rig: a continuous DOF is signed and normalized, +1 meaning the direction the name denotes, and rest is expressible inside the declared range.

Vhi Namespace

Classes
ControlHandSkeleton The "control" hand - the ground-truth / cued hand on the left of the scene. Follows the presence of its vhi.control.pose.* LSL streams, exactly as the predicted hand follows vhi.prediction.*: whichever is live drives the hand, checked fresh every frame rather than switched by a handshake. ... Frame-by-frame animation logic runs in _Process; the resulting pose is published to the VHI_Control LSL outlet in _PhysicsProcess. Commands originate from the gRPC control services (via the public command API on this class) and from local keyboard input - the same methods are called either way.
ControlPanelUI Runtime control panel - the collapsible UI on the right edge of the scene that lets the operator tweak the hands without restarting the app or opening the Godot editor. Hosts: ... Collapsed by default; toggled with the > chevron on the right edge. No driver-mode toggle - that is set in the Inspector or over gRPC (the gRPC control plane).
GrpcControlServer Hosts the in-process gRPC control server (Grpc.AspNetCore / Kestrel) that MyoGestic connects to. Discrete commands (set movement, freeze, speed, ...) arrive here; continuous time-series stays on LSL. Loading the ASP.NET Core shared framework inside Godot relies on SharedFrameworkAssemblyLoader (the godot#112701 workaround), which registers from a [ModuleInitializer] before this node's _Ready() runs.
HandSkeleton The 16-joint hand rig both hands drive, and the pose read-back both publish.
LSLCommunicationController All Lab Streaming Layer I/O for VHI - the only node that touches LSL directly (and even then only through LSLWrapper, never SharpLSL). One stream per DOF, in. Every control VHI exports is its own LSL stream, named by its own address and one channel wide: vhi.prediction.index, vhi.control.pose.thumb.flexion, and their siblings — the same names GetControlManifest publishes. A sample is applied to the hand the moment it arrives, and the DOFs that did not deliver hold what they were last commanded to. There is no whole-pose frame and nothing waits for one: the DOFs are independently actuated, may come from different producers and may update at different rates, so a hand whose index has moved and whose thumb has not is a real pose. Two nine-channel outlets, out, unchanged: VHI_Control and VHI_Predict publish each hand's whole pose at 60 Hz. A read-back is a recording, and a recording wants one row per instant. Stream resolution blocks ~1 s so it runs on a background Task.Run thread; results and log lines are marshalled back to Godot's main thread via CallDeferred. Sample pulls themselves are non-blocking and happen in _Process. Streams are \<i>found\</i> by name, but a found stream is only opened when it is \<b>exactly one channel wide\</b> — see Vhi.LSLCommunicationController.ResolveMissingInletsAsync.
LSLWrapper Wrapper for SharpLSL that avoids static initialization hang in Godot. Uses reflection to dynamically load and call LSL methods.
MovementConfigGenerator Utility class to generate default movements.toml from hardcoded poses
MovementConfigLoader Loads hand movement configurations from TOML files
MovementPoses Static class containing all movement pose definitions Data structure: [16 joints][2 states (max/min)][3 axes (x,y,z)]
MovementUI The on-screen text overlay in the top-left of the scene - a Godot Godot.Label that reports the control hand's currently selected movement, its animation state, and the keyboard-control hint. Refreshed every frame from ControlHandSkeleton; has no inputs of its own.
PredictedHandSkeleton The "predicted" hand - the model-output hand on the right of the scene. Renders whatever its DOFs are commanded to, whenever they are commanded: each arrives on a stream of its own (vhi.prediction.index and its eight siblings, via LSLCommunicationController) and is applied on arrival, so a producer driving one finger moves one finger and the rest hold. When EnableSmoothing is on, the rig is spherically interpolated (Slerp) toward the commanded pose at SmoothingSpeed instead of snapping - smoother on the eye, but with a small latency cost. The "right hand" appearance is achieved by a mirrored root transform on the node; both this node and the control hand instance the same left-hand FBX. The current pose is published to the VHI_Predict LSL outlet at 60 Hz.
StandardPose The one place that knows what a standard value means in rig degrees.
VhiControlService The one gRPC service VHI serves: a manifest of every control it exports, per-frame commands for the predicted and control hands, plus the recording-session coordination a capture pipeline drives them through.
Enums
Movements Hand movement definitions from Unity VirtualHandClassifier Contains all movement types and their corresponding joint poses