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

Pre-computed topology metadata for efficient traversal and kinematic operations.

This struct contains ordering information that allows:
- Forward kinematics to process joints in the correct order
- Path lookup between any two nodes
- Depth information for tree operations

Paths are root-relative and interleave links and joints, starting and ending
with the node they name — `[:base, :shoulder, :upper_arm, :elbow, :forearm]`.

Lookups here have no robot name to attribute their errors to. `BB.Robot`
delegates to them and fills the name in.

# `t`

```elixir
@type t() :: %BB.Robot.Topology{
  depth: %{required(atom()) =&gt; non_neg_integer()},
  joint_order: [atom()],
  link_order: [atom()],
  paths: %{required(atom()) =&gt; [atom()]}
}
```

# `depth_of`

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

Get the depth of a node in the tree.

The root link has depth 0. Each joint/link pair adds 1 to the depth.

# `leaf_links`

```elixir
@spec leaf_links(t(), BB.Robot.t()) :: [atom()]
```

Get all leaf links (links with no child joints).

# `max_depth`

```elixir
@spec max_depth(t()) :: non_neg_integer()
```

Get the maximum depth of the kinematic tree.

# `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 node down to a target node.

Restricted to the case where `source` is an ancestor of `target`, which makes
this a prefix drop on the precomputed paths. The result starts at `source` and
ends at `target`; a source equal to the target gives `{:ok, [source]}`.

Fails with `BB.Error.Kinematics.NotAnAncestor` when the source sits somewhere
other than above the target, carrying their nearest common ancestor. That
ancestor always exists: `BB.Dsl.TopologyTransformer` rejects any topology
without exactly one root link and the DSL's nesting makes cycles impossible,
so the topology is a single tree in which any two nodes share at least the
root.

# `path_to`

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

Get the path from root to a node.

Returns a list of link/joint names from the root to the given node.

---

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