# `BB.Robot`
[🔗](https://github.com/beam-bots/bb/blob/main/lib/bb/robot.ex#L5)

An optimised robot representation for kinematic computations.

This struct is built from the Spark DSL at compile-time and contains:
- All physical values converted to SI base units (floats)
- Flat maps for O(1) lookup of links, joints, sensors, and actuators by name
- Pre-computed topology metadata for efficient traversal
- Bidirectional parent/child references

## Structure

The robot is organised as flat maps indexed by name:

- `links` - all links in the robot, keyed by atom name
- `joints` - all joints in the robot, keyed by atom name
- `sensors` - all sensors (at any level), keyed by atom name
- `actuators` - all actuators, keyed by atom name

## Unit Conventions

All physical quantities are stored as native floats in SI base units:

- Length: meters
- Angle: radians
- Mass: kilograms
- Moment of inertia: kg·m²
- Force: newtons
- Torque: newton-meters
- Linear velocity: m/s
- Angular velocity: rad/s

# `actuator_info`

```elixir
@type actuator_info() :: %{
  name: atom(),
  joint: atom(),
  transmission: transmission() | nil
}
```

# `param_location`

```elixir
@type param_location() ::
  {:joint, atom(), [atom()]}
  | {:actuator, atom(), [atom()]}
  | {:sensor, atom(), [atom()]}
```

# `sensor_info`

```elixir
@type sensor_info() :: %{
  name: atom(),
  attached_to: {:link, atom()} | {:joint, atom()} | :robot,
  transmission: transmission() | nil
}
```

# `t`

```elixir
@type t() :: %BB.Robot{
  actuators: %{required(atom()) =&gt; actuator_info()},
  joints: %{required(atom()) =&gt; BB.Robot.Joint.t()},
  links: %{required(atom()) =&gt; BB.Robot.Link.t()},
  name: atom(),
  param_subscriptions: %{required([atom()]) =&gt; [param_location()]},
  root_link: atom(),
  sensors: %{required(atom()) =&gt; sensor_info()},
  topology: BB.Robot.Topology.t()
}
```

# `transmission`

```elixir
@type transmission() :: %{
  reduction: float() | nil,
  offset: float() | nil,
  reversed?: boolean() | nil
}
```

# `actuator_path`

```elixir
@spec actuator_path(t(), atom()) ::
  {:ok, [atom()]} | {:error, BB.Error.Kinematics.UnknownActuator.t()}
```

Get the full path from root to an actuator.

Actuator names are unique per robot, so the path is derivable: an actuator
always hangs off a joint, and the joint's own path gives the links and joints
above it. The result matches the `:path` the framework injects into the
actuator's `:bb` option, and therefore the topic its commands are published
to — `[:actuator | actuator_path(robot, name)]`.

    BB.Robot.actuator_path(robot, :pan_servo)
    #=> {:ok, [:base, :pan, :pan_servo]}

# `child_joints`

```elixir
@spec child_joints(t(), atom()) ::
  {:ok, [BB.Robot.Joint.t()]} | {:error, BB.Error.Kinematics.UnknownLink.t()}
```

Get the child joints of a link.

A link with no children returns `{:ok, []}`, which is distinct from naming a
link that doesn't exist.

# `get_joint`

```elixir
@spec get_joint(t(), atom()) ::
  {:ok, BB.Robot.Joint.t()} | {:error, BB.Error.Kinematics.UnknownJoint.t()}
```

Get a joint by name.

# `get_link`

```elixir
@spec get_link(t(), atom()) ::
  {:ok, BB.Robot.Link.t()} | {:error, BB.Error.Kinematics.UnknownLink.t()}
```

Get a link by name.

# `joints_in_order`

```elixir
@spec joints_in_order(t()) :: [BB.Robot.Joint.t()]
```

Get all joints in traversal order.

# `links_in_order`

```elixir
@spec links_in_order(t()) :: [BB.Robot.Link.t()]
```

Get all links in topological order (root first).

# `parent_joint`

```elixir
@spec parent_joint(t(), atom()) ::
  {:ok, BB.Robot.Joint.t()}
  | {:error,
     BB.Error.Kinematics.UnknownLink.t()
     | BB.Error.Kinematics.UnknownJoint.t()
     | BB.Error.Kinematics.NoParentJoint.t()}
```

Get the parent joint of a link.

The root link has no parent joint, which is reported as
`{:error, %BB.Error.Kinematics.NoParentJoint{}}` — a distinct type from
`UnknownLink` so a caller walking up the tree can match on it as a termination
signal rather than being told a valid root link doesn't exist.

# `path_between`

```elixir
@spec path_between(t(), atom(), atom()) ::
  {:ok, [atom()]}
  | {:error,
     BB.Error.Kinematics.UnknownLink.t() | BB.Error.Kinematics.NotAnAncestor.t()}
```

Get the path from a source link down to a target link.

Restricted to the case where `source_link` is an ancestor of `target_link`,
which is a prefix drop on the precomputed root-relative paths. The result
starts at `source_link` and ends at `target_link`, interleaving the joints and
links between them, so `path_to/2` is the special case of a source at the root.

A source that isn't above the target reports
`BB.Error.Kinematics.NotAnAncestor`, carrying the nearest common ancestor so
the message names the link the caller should have passed.

    BB.Robot.path_between(robot, :chassis, :sensor_head)
    #=> {:ok, [:chassis, :mast, :sensor_head]}

# `path_to`

```elixir
@spec path_to(t(), atom()) ::
  {:ok, [atom()]} | {:error, BB.Error.Kinematics.UnknownLink.t()}
```

Get the path from root to a given link or joint.

Equivalent to `path_between/3` from the root link, and delegates to
`BB.Robot.Topology.path_to/2`.

# `root_link`

```elixir
@spec root_link(t()) :: atom()
```

Get the link at the root of the kinematic tree.

Returns a bare atom rather than a result tuple: unlike every other lookup here
it cannot fail, because `BB.Dsl.TopologyTransformer` guarantees exactly one
root link exists.

Useful when a caller genuinely wants a whole-tree chain and has to say so —
`BB.Motion` requires `:source_link` with no default, precisely so that
root-to-target is recorded as a decision rather than assumed.

    BB.Motion.move_to(robot, :gripper, target,
      source_link: BB.Robot.root_link(robot),
      solver: BB.IK.DLS
    )

---

*Consult [api-reference.md](api-reference.md) for complete listing*
