Module reference · Robot

Vehicle / Robot Model

The robot-side module. It applies a released command through a configured vehicle behavior and updates the authoritative remote state — the pivot where the uplink ends and the downlink begins.

Layer Unite.Kernel
Direction Robot · uplink terminus, downlink source
Base type VehicleRobotModel<TCommand, TVehicleState>
Selection Required — exactly one per study
Demo implementation TurtleBot3WafflePiEnhanced
Requirement DR4 · Configurable vehicle behavior
Paper §5.6

What

The Vehicle / Robot Model applies a received command through a configured vehicle behavior and updates the authoritative remote state. It sits at the robot end of the uplink: it consumes whatever Remote-side Assistance publishes, if a study assigns one, or whatever Uplink Communication releases directly, if not — and it is the only stage in the loop that turns a command into vehicle motion.

Concretely, it is VehicleRobotModelModule, an abstract class in Unite.Kernel (Assets/UNITE/Kernel/Uplink/VehicleRobotModel.cs). A study implements it by subclassing the generic VehicleRobotModel<TCommand, TVehicleState>, declaring its accepted command contract and the vehicle state contract it publishes as generic parameters — the same shape Command Mapping & Encoding uses for TInputContract/TCommandContract.

Command Mapping determines what the operator asks the robot to do; the Vehicle Model determines what the robot actually does. Everything between those two stages — assistance, delay — can shape or delay the command, but none of it decides how the vehicle responds. That decision belongs here, and only here.

Remote-side Assistance may preserve, modify, replace, or suppress the command Optional
Empty field: Uplink Communication feeds the Vehicle / Robot Model directly instead.
Vehicle / Robot Model command → vehicle motion
Authoritative remote state the complete model-owned vehicle snapshot
Remote Observation & State Capture samples this state for the downlink
Remote-side Assistance is optional, not the arrow into the Vehicle / Robot Model itself: with no assistance selected, the path is simply Uplink Communication → Vehicle / Robot Model. Either way, exactly one command source feeds this module.

Accepting a command and applying it is only half the job. The other half is publishing what happened: this module owns the robot's authoritative state — not a prediction, not a reconstruction, the actual simulated position, orientation, and velocity the rest of the loop treats as ground truth. Every downlink stage that eventually shows the operator something — and Remote-side Assistance, reading local state before the next command — ultimately traces back to a state this module published.

Why

This module implements DR4, configurable vehicle behavior. The review found that the same operator command can produce different motion depending on the platform, steering geometry, dynamic model, actuator response, and wheel–terrain interaction. Some studies specified these in detail — a simulated large UGV with a 14-DoF model, Pacejka tires, a diesel engine, and an automatic transmission; soft-terrain slippage modeled as part of the vehicle; a deliberate distinction between the controller-internal model and the simulated plant. Others described the apparatus without specifying its motion behavior at all: a three-wheel robot with two motor-driven wheels and a caster, with no statement of the steering geometry, or game physics characterized as "realistic" without a model type or parameters. In both of the latter cases the vehicle behavior cannot be reconstructed from the description.

DR4 does not ask for one canonical vehicle model. It asks that steering geometry, kinematic or dynamic behavior, actuator response, and wheel–terrain interaction each be an explicit, reportable configuration choice — and that steering geometry in particular be a field a study sets, not a property implied by how a GameObject happens to be built. A study that reports which Vehicle Model it used and which of these fields it changed has said enough for someone else to rebuild the apparatus; a study that reports only "differential drive" or "realistic physics" has not.

What the reconstruction revealed. The study UNITE reconstructs (§6) describes its disturbance model in structural detail — naming six sources and identifying which use Perlin noise — but reports no parameter values, describing them only as "iteratively calibrated." Reconstruction needs concrete numbers, so the reconstruction chose its own to produce comparable trajectory variation, reproducing the model's behavior but not its exact calibration. Those six sources are exactly the six disturbance fields on TurtleBot3-WafflePi-Moon, covered under Examples below — what the original paper left as "iteratively calibrated" becomes six explicit, versioned numbers here, each one a value the reconstruction had to pick and could instead have reported.

How

A released command's path through this module, and what happens on every tick regardless of whether one arrived:

  1. A command package arrives. TeleroboticsAgent wires exactly one upstream source to this module's ReceivePackage, decided once at startup: Remote-side Assistance's output if a study assigned one, otherwise Uplink Communication's release, directly.
  2. ReceivePackage only enqueues. The public, package-receiving entry point does no work of its own — it queues the package for this tick's Step.
  3. Each vehicle step applies queued commands. The model checks each payload against its declared command type. A mismatched payload is rejected; a valid command is passed to the vehicle-specific implementation.
  4. UpdateVehicleState always runs, once, last. After every queued command this tick has been applied — even if the queue was empty — Step calls protected abstract void UpdateVehicleState(double timestampSeconds). State therefore refreshes on every tick at the study's configured update rate, not only on ticks that happened to receive a command.
  5. Inside those two methods, the concrete model does whatever its vehicle behavior needs. The base class prescribes nothing here: interpreting steering geometry, integrating a kinematic or dynamic model, applying actuator response, applying wheel–terrain effects are all entirely the implementation's own — see Examples below for what the shipped rover actually does.
  6. UpdateVehicleState finishes by publishing. protected void PublishState(TVehicleState state) checks that the state's own timestamp matches the tick's current timestamp, then sets LatestState and HasState, and fires event Action<TVehicleState> StateUpdated.
  7. Downstream, anything holding a reference reads LatestState. Remote-side Assistance on the next tick, and Remote Observation & State Capture's sources, each pattern-match LatestState down to the concrete state type they expect — exactly like a shipped observation source does for TurtleBotState.

That last point is the distinction worth keeping straight: operator-side state may be reconstructed or predicted — that is what Operator-side State Reconstruction does, downlink, after its own delay — but the Vehicle / Robot Model owns the actual authoritative remote state. Nothing else in the loop simulates the robot; everything else either commands it or observes what it already did.

The concrete state is the model-owned snapshot for that tick. It can contain pose, realized velocities, actuator values, sensor readings, or other vehicle facts. Observation sources project the fields needed by each consumer; they do not create a second authoritative vehicle state.

Boundary

Owns

  • Accepting only its declared command contract, and logging an error and dropping anything else.
  • Its steering geometry, kinematic or dynamic model, actuator response, and wheel–terrain interaction — however ApplyCommand and UpdateVehicleState choose to implement them.
  • Publishing the single authoritative remote vehicle state for the tick, exactly once, through PublishState.

Does not own

  • Reading an input device or mapping it into a command — Command Mapping & Encoding's job.
  • Communication delay or transport — Uplink Communication's job.
  • Deciding whether to preserve, modify, replace, or suppress a command before it arrives — Remote-side Assistance's job, if a study assigns one.
  • Sampling or packaging its own state for the downlink — Remote Observation & State Capture's job; this module publishes state, it doesn't decide what a study observes from it.

Contract

A concrete model declares two things as generic parameters: the command contract it accepts — a subclass of EncodedCommandContract, documented in full on the Command Mapping & Encoding page — and the state contract it publishes, a subclass of VehicleStateContract:

C#
public abstract class VehicleRobotModelModule : TeleroboticsModule, IPackageConsumer
{
    public abstract void ReceivePackage(Package package);

    /// <summary>Whether a state has been published yet.</summary>
    public bool HasState { get; protected set; }

    /// <summary>
    /// The most recently published state. A module that only holds a reference to
    /// this Kernel base type (rather than the concrete vehicle model) reads state
    /// through here and pattern-matches down to the concrete state type it expects.
    /// </summary>
    public VehicleStateContract LatestState { get; protected set; }
}

public abstract class VehicleRobotModel<TCommand, TVehicleState>
    : VehicleRobotModelModule
    where TCommand : EncodedCommandContract
    where TVehicleState : VehicleStateContract
{
    public event Action<TVehicleState> StateUpdated;

    protected abstract void ApplyCommand(TCommand command, double timestampSeconds);
    protected abstract void UpdateVehicleState(double timestampSeconds);

    protected void PublishState(TVehicleState state)
    {
        // ...rejects null, rejects a timestamp that doesn't match the current
        // tick, then sets LatestState/HasState and fires StateUpdated...
    }
}

VehicleStateContract (Unite.Core) is the published-state counterpart of EncodedCommandContract — one timestamp, validated the same way:

C#
public abstract class VehicleStateContract
{
    public double TimestampSeconds { get; }

    protected VehicleStateContract(double timestampSeconds)
    {
        // ...validates it is finite and non-negative...
        TimestampSeconds = timestampSeconds;
    }
}

A study implements the two abstract methods and constructs its own TVehicleState in UpdateVehicleState, whose TimestampSeconds must equal the tick's own timestamp before PublishState will accept it — Examples, below, shows the shipped rover doing exactly this.

C#
public sealed class TurtleBotSnapshot
{
    public VehicleKinematics Kinematics { get; }
    public VehicleActuation Actuation { get; }
    public VehicleSensors Sensors { get; }
}

public sealed class VehicleActuation
{
    public float LeftVelocity { get; }
    public float RightVelocity { get; }
    public float SteeringAngle { get; }
}

public sealed class VehicleSensors
{
    public CameraSensorSnapshot Camera { get; }
    public IReadOnlyList<DistanceSensorSnapshot> DistanceSensors { get; }
}

public sealed class CameraSensorSnapshot
{
    public Vector3 Position { get; }
    public Quaternion Orientation { get; }
    public CameraFrameSnapshot CapturedFrame { get; }
}

public sealed class DistanceSensorSnapshot
{
    public string SensorId { get; }
    public Vector3 Origin { get; }
    public Vector3 Direction { get; }
    public float Distance { get; }
    public float MaximumDistance { get; }
    public bool HasHit { get; }
}

public sealed class TurtleBotState : VehicleStateContract
{
    public TurtleBotSnapshot Snapshot { get; }
}

Complete state. PublishState publishes the full TurtleBotSnapshot; Remote Observation & State Capture selects the fields that become robot-pose, robot-state, or robot-view.

Vehicle-specific shape. Another vehicle defines the state facets it needs, applies its command-to-motion rule, updates realized actuation, reads mounted sensors, and publishes one complete snapshot without changing the Kernel module.

Kinematics and actuation. Kinematics describes motion such as position, orientation, and velocity. Actuation describes the mechanism that produced it, such as realized wheel velocities or a steering angle; those values may differ from the requested command.

Mounted camera. CapturedFrame is the camera exposure handle associated with the snapshot. RobotViewSource uses it to create robot-view while the Vehicle Model remains the camera owner.

The command type a study picks in Command Mapping & Encoding determines which Vehicle Model can consume the resulting package. That page's own three examples — differential drive, skid-steer, and position delta — are three different TCommand shapes, and each needs a Vehicle Model whose generic parameter matches. The shipped study scene uses differential drive: TurtleBot3WafflePiEnhanced declares WheelVelocityCommand. Selecting a skid-steer or position-delta mapping without a Vehicle Model built for its command leaves every package failing the check below, from the very first tick.

Type-checked on one side only. Like every stage downstream of Uplink Communication, this module never sees TCommand guaranteed by the type system — packages travel as an untyped payload, so Step pattern-matches package.Payload against TCommand itself and logs an error on a mismatch rather than crashing. A study that changes its command contract upstream — swapping Command Mapping & Encoding's output type — has to pick a Vehicle Model built for the new type, or every command silently fails this check for the rest of the trial.

Steering geometry is a configuration field, not an inferred property. DR4 asks specifically that steering geometry be something a study sets explicitly — a serialized field or a chosen implementation class — rather than a property implied by how a GameObject happens to be constructed. Two implementations that both accept WheelVelocityCommand can integrate it completely differently; nothing about the command contract itself says which one a study is using.

Examples

The demo follows one command all the way from the existing differential-drive mapping to the rover's authoritative state. The mapping creates a WheelVelocityCommand; the Vehicle Model turns those requested wheel velocities into motion, then publishes the resulting pose and wheel state.

Input Provider
Differential-drive Command Mapping ArrowToWheelVelocityMapping
WheelVelocityCommand the published command
Uplink Communication
TurtleBot3WafflePiEnhanced the Vehicle Model
TurtleBotState the authoritative vehicle state
Remote-side Assistance — the demo's shipped SlopeBoundaryGuard — actually sits between Uplink Communication and this module too; see What, above. The chain here starts from the command's origin and skips to what this section is about: how the Vehicle Model interprets it.

Differential drive: command to motion. ArrowToWheelVelocityMapping has already converted the operator's input into left and right wheel velocities. After the uplink delay, TurtleBot3WafflePiEnhanced consumes that command.

C#
public sealed partial class TurtleBot3WafflePiEnhanced
    : VehicleRobotModel<WheelVelocityCommand, TurtleBotState>
{
    [SerializeField] private EveryMoveVehicleConfiguration configuration;
    [SerializeField] private Transform robot;
    [SerializeField] private Transform robotCamera;
    [SerializeField] private Camera cameraSensor;
    [SerializeField] private MeshCollider terrain;
    private float x, z, theta, vL, vR;

    protected override void ApplyCommand(WheelVelocityCommand command, double timestamp)
    {
        float dt = command.DeltaTime;
        vL = Mathf.MoveTowards(vL, command.RequestedLeft, configuration.maxLinearAcceleration * dt);
        vR = Mathf.MoveTowards(vR, command.RequestedRight, configuration.maxLinearAcceleration * dt);
        ApplyDisturbances(ref vL, ref vR, dt);

        float difference = Mathf.Clamp(vR - vL,
            -configuration.maxWheelVelocityDifference, configuration.maxWheelVelocityDifference);
        float velocity = (vL + vR) * 0.5f;
        x += velocity * Mathf.Cos(theta) * dt;
        z -= velocity * Mathf.Sin(theta) * dt;
        theta += difference / configuration.wheelbase * dt;
    }

    protected override void UpdateVehicleState(double timestamp)
    {
        Vector3 position = new Vector3(x, robot.position.y, z);
        robot.position = position;
        robot.rotation = Quaternion.Euler(0f, theta * Mathf.Rad2Deg, 0f);
        Vector3 velocity = new Vector3(
            (vL + vR) * 0.5f * Mathf.Cos(theta),
            0f,
            -(vL + vR) * 0.5f * Mathf.Sin(theta));
        // The vehicle-owned camera sensor supplies the latest frame handle.
        PublishState(new TurtleBotState(timestamp, new TurtleBotSnapshot(
            new VehicleKinematics(position,
                Quaternion.Euler(0f, theta * Mathf.Rad2Deg, 0f),
                velocity, theta),
            new VehicleActuation(vL, vR, latestInput),
            new VehicleSensors(robotCamera
                ? new CameraSensorSnapshot(
                    robotCamera.position,
                    robotCamera.rotation,
                    latestCameraFrame)
                : null)));
    }
}

The model uses differential-drive kinematics: the average of the two realized wheel velocities advances the rover, while their difference updates its heading using the configured wheelbase. There is no Rigidbody, WheelCollider, or PhysX drive component in this model. Mathf.MoveTowards models actuator response and ApplyDisturbances applies the configured wheel slip and terrain-sampled vibration from TurtleBot3-WafflePi-Moon.

The model applies the computed position and orientation to the robot transform before publishing the state. PublishState then commits that same tick's result for other modules to read. The snapshot also records the mounted camera pose and the latest vehicle-owned camera frame handle; Remote Observation & State Capture only selects and packages that sensor data into streams.

Study example. Compare operator-side assistance or presentation techniques while keeping the vehicle model, terrain, task, and communication delay fixed. This is exactly what the demo's four scenes — condition-baseline, condition-network, condition-path, and condition-envelope — already do: they share the same TurtleBot3-WafflePi-Moon vehicle, delay, and task configuration, and differ only in the downlink presentation module under test.

Config

Vehicle / Robot Model is a Unity component, configured in the Inspector like the rest of a study.

  1. Add a concrete vehicle model to the agent. Add a component such as TurtleBot3WafflePiEnhanced to the same GameObject as TeleroboticsAgent. Every selected module must live on that one GameObject.
  2. Assign it to the agent's Vehicle Robot Model field. It's required — the agent logs "TeleroboticsAgent requires one Input Provider and one Command Mapping and Encoding module, one Uplink Communication module, and one Vehicle/Robot Model" and disables itself if this field, Input Provider, Command Mapping & Encoding, or Uplink Communication is left empty.
  3. Select and assign its vehicle implementation asset. TurtleBot3WafflePiEnhanced reads its own EveryMoveVehicleConfiguration ScriptableObject (TurtleBot3-WafflePi-Moon.asset in the demo) — steering geometry, actuator limits, and wheel–terrain disturbance all live on that one shareable, versionable asset, not hardcoded in the component.
  4. Confirm the upstream command contract matches. Whatever Command Mapping & Encoding — or Remote-side Assistance, if it replaces the command — produces has to be the exact TCommand this model's generic parameter declares, or every package fails the type check in How and is silently dropped for the rest of the trial.
  5. Confirm downstream observation modules read this model's state. A source such as PoseSource holds its own [SerializeField] VehicleRobotModelModule vehicle reference and reads vehicle.LatestState; point it at the same GameObject's Vehicle / Robot Model, or Remote Observation & State Capture has nothing to publish.
  6. Keep every other module fixed when comparing vehicle models. The demo's own four scenes hold input, mapping, delay, terrain, and task identical and vary only the module under test — apply the same discipline when the module under test is this one.

Same GameObject, one instance. The agent's Awake validates that every selected module — including the Vehicle / Robot Model — is attached to its own GameObject; a reference to a component elsewhere in the scene fails this check and disables the agent.