Module reference · Uplink

Remote-side Assistance

The uplink's optional robot-side stage. Processes commands after the uplink delay, using current local observations before the vehicle applies them.

Layer Unite.Kernel
Direction Uplink · operator → robot
Base type UplinkRemoteSideAssistanceModule
Selection Optional — zero or one per study
Demo implementation SlopeBoundaryGuard
Requirement DR5 · Configurable assistance techniques
Paper §5.5

What

Remote-side Assistance is a TeleroboticsModule that sits between Uplink Communication and the Vehicle/Robot Model. It is the last stage that can inspect, transform, or drop a command before the vehicle executes it — right up to the moment of execution — and the first stage in the loop that runs on the robot side, after the uplink delay. Like Operator-side Assistance (uplink), it's optional: a study that has nothing to add here leaves the field empty and a released command goes straight to the Vehicle/Robot Model. What sets it apart is that it can also read the robot's current local observations — the latest robot-side snapshot, such as pose, terrain, or sensor state — before deciding what to do with a command.

Concretely, it is UplinkRemoteSideAssistanceModule, an abstract class in Unite.Kernel (Assets/UNITE/Kernel/Uplink/UplinkRemoteSideAssistance.cs). It uses the same package-processing shape as Operator-side Assistance, plus one addition only this side of the delay needs: a second receive path, ReceiveObservation, and a read method, TryGetLatestObservation — both covered under Contract below.

Why

DR5 does not treat assistance as one slot. The review found shared control, safeguarded execution, delay compensation, and obstacle avoidance recurring at a specific point: after a command has crossed the uplink delay, but before it reaches the vehicle. That is a different position in the loop than Operator-side Assistance (uplink), which never sees the delay or any remote state at all. Giving that position its own module turns it into an explicit, reportable configuration choice: did this study apply obstacle avoidance, shared control, or delay compensation on the robot side, and exactly what did it read to decide? Without a dedicated module, that logic would otherwise be folded into the vehicle controller.

The contrast that matters for study design: Operator-side Assistance (uplink) acts before the command crosses the delay; Remote-side Assistance acts after the delay, using the robot's current state. That capability comes from TryGetLatestObservation. Whatever it returns is the robot's current state — not the state the operator was looking at when they issued the command, and not the state at the moment the command was released either, but whatever Remote Observation & State Capture last sampled locally, without crossing any delay. That is what lets this stage react to terrain, obstacles, or vehicle state that has changed since the operator acted — something Operator-side Assistance cannot structurally do.

What the reconstruction revealed. The paper attributes course keeping to "cliffs and boundaries" without specifying whether the behavior belongs to the scene or an assistance mechanism. UNITE represents it as an explicit, configurable Remote-side Assistance choice; the slope-based guard appears in the Examples.

The module is optional; the dependency is not implied. Assigning a concrete implementation here does not, by itself, wire up any sensor or state feed — an implementation declares what it reads (a stream id, typically), and the study is responsible for making sure something actually publishes to that stream through Remote Observation & State Capture. See Config.

How

The path a released command takes, and where local observations join it:

The path a command takes after uplink release, and where local observation joins it Uplink Communication releases a command into Remote-side Assistance, which may preserve, modify, replace, or suppress it before the Vehicle/Robot Model receives the result. Below Remote-side Assistance, Remote Observation and State Capture feeds it the robot's current local observations through a separate path that never crosses communication delay. OPTIONAL · STUDY-PROVIDED Uplink Communication releases due command Remote-side Assistance preserve / modify / replace / suppress Vehicle/Robot Model applies the resulting command Remote Observation & State Capture local observation no delay
Only the middle box is study-provided, and only if a study assigns one — an empty field wires Uplink Communication straight to the Vehicle/Robot Model. The dashed feed from Remote Observation & State Capture is a separate path that never crosses communication delay: it is what TryGetLatestObservation reads.

A delayed command can therefore be checked against the robot's current situation before it is applied.

Every tick, the Kernel drains whatever released command packages arrived from Uplink Communication and calls TryProcessPackage once per package, in order, with that tick's timestamp — identical in shape to Operator-side Assistance's contract:

C#
protected abstract bool TryProcessPackage(
    Package package,
    double timestampSeconds,
    out Package output);

Return false to drop the package for this tick — no command reaches the Vehicle/Robot Model at all. Return true with a package to publish it downstream; the Kernel only checks that the returned package isn't null, exactly as it does for Operator-side Assistance. Failing that check logs an error and drops the tick.

Reading local state is a separate, event-driven path, independent of the command path above. Remote Observation & State Capture continuously maintains a snapshot of the robot's surroundings — pose, terrain, sensor readings, whatever a study configures — and makes the latest snapshot available to this stage without it ever crossing the uplink delay. Concretely, that snapshot arrives through this module's ReceiveObservation, and an implementation reads it back through TryGetLatestObservation. Remote Observation & State Capture refreshes and publishes that snapshot before this module processes commands, so whatever TryProcessPackage reads is that tick's freshly captured local state, not anything that has crossed the uplink.

Boundary

Owns

  • Receiving every command package Uplink Communication releases, queued in arrival order.
  • Reading whatever locally captured observations Remote Observation & State Capture publishes, by declared stream id.
  • Deciding, per package, whether to drop it, pass it through unchanged, or publish a transformed replacement.
  • Publishing at most one output package per package it received.

Does not own

  • Communication delay or packet delivery — already applied by Uplink Communication before this stage ever sees a package.
  • Authoritative vehicle state — the Vehicle/Robot Model updates and owns it; this stage only reads a locally captured snapshot.
  • Sensor or observation capture — Remote Observation & State Capture's job; this stage consumes what that module publishes, it doesn't sample anything itself.
  • Actuator execution — interpreting the command physically stays the Vehicle/Robot Model's job.
  • Task completion or logging — Task Goal & Termination and Data Capture & Logging's job.

Contract

This contract matters when writing a new assistance technique — configuring a study that already has one is covered under Config below. Like Operator-side Assistance (uplink), this stage has no dedicated contract type to subclass for the command path — it reads and writes the same Package envelope, documented in full on the Operator-side Assistance (uplink) page. What it adds is a second, read-only path for local state.

C#
public abstract class UplinkRemoteSideAssistanceModule
    : TeleroboticsModule, IPackageSource, IPackageConsumer
{
    public event Action<Package> PackageProduced;

    public void ReceivePackage(Package package);

    /// <summary>
    /// Receives a locally captured observation package. This is a separate
    /// input from command packages: a pre-control refresh is available for
    /// the current assistance step, while the post-control refresh becomes
    /// the observation available on the next control tick.
    /// </summary>
    public void ReceiveObservation(Package package);

    /// <summary>
    /// Reads the latest locally captured observation for a stream. The result
    /// is the observation available at the remote control tick, not a delayed
    /// downlink package.
    /// </summary>
    protected bool TryGetLatestObservation(
        string streamId, out Package package);

    /// <summary>
    /// Reads and type-checks the latest locally captured observation.
    /// </summary>
    protected bool TryGetLatestObservation<TObservation>(
        string streamId, out TObservation observation)
        where TObservation : class;

    protected abstract bool TryProcessPackage(
        Package package,
        double timestampSeconds,
        out Package output);
}

TryGetLatestObservation returns the latest available local snapshot for a given stream id — whatever Remote Observation & State Capture most recently published, or nothing if it hasn't published yet. An implementation declares which stream it depends on, typically as a serialized field, then reads back the latest snapshot for that stream by id, exactly like Uplink Communication's channels do for commands. A generic overload adds a type check on top: it returns that same snapshot only if its payload matches the requested TObservation, so an implementation doesn't have to pattern-match the payload itself. If nothing has published to that stream yet, or the study never wired a matching source at all, the call simply returns false — there is no error, because an unconfigured dependency is a valid state before the first observation arrives, not a failure.

The Vehicle/Robot Model doesn't check StreamId either. Unlike Uplink Communication, it only pattern-matches package.Payload against its declared command contract and drops the tick on a mismatch — so a StreamId error goes uncaught here, the same "type-checked on one side only" situation the Operator-side Assistance (uplink) contract describes. Preserve package.StreamId and SourceTimestampSeconds when constructing a replacement anyway — it's still on the implementation, even though nothing downstream enforces it.

Examples

The baseline passes commands through; the route guard reads local pose observations before the vehicle update.

Leave the field empty to pass every released command from Uplink Communication to the Vehicle/Robot Model unchanged.

Config

Remote-side Assistance is a Unity component, configured in the Inspector like the rest of a study.

  1. Add a concrete assistance component to the agent, if the study uses one. Add a component such as SlopeBoundaryGuard to the same GameObject as TeleroboticsAgent. Every selected module must live on that one GameObject.
  2. Assign it to the agent's Uplink Remote Side Assistance field, or leave it empty. This field isn't required — leaving it empty wires Uplink Communication straight to the Vehicle/Robot Model, exactly as described under Pass-through baseline above.
  3. If the implementation reads local observations, configure Remote Observation & State Capture to publish the stream it expects. A dependency is declared by the implementation, not by the Kernel. SlopeBoundaryGuard, for instance, depends on a stream called robot-pose — a name for "the robot's current position and heading" — and that name has to match a source configured on the agent's Remote Observation & State Capture module, or the read simply never succeeds.

An unconfigured dependency fails silently. Assigning a component here does not by itself require Remote Observation & State Capture to be assigned too — the two fields are wired independently in TeleroboticsAgent.Awake. Add a component that reads a stream nothing publishes to, and every call to TryGetLatestObservation simply returns false for the rest of the trial: no error, no warning, just an implementation whose read-side never succeeds.

Disabled is not the same as empty. An assigned-but-disabled module blocks every command rather than creating pass-through behavior — its Step never runs, so nothing reaches the Vehicle/Robot Model while it stays disabled, silently. That's because the agent wires the subscription off the field being non-null, not off whether the component is enabled. To get real pass-through, clear the field itself rather than disabling the component.

Same GameObject, one instance. The agent's Awake validates that every assigned module — including Remote-side Assistance, if the field isn't left empty — is attached to its own GameObject; a reference to a component elsewhere in the scene fails this check and disables the agent.