# `Monty.Frameworks.Models.MotorPolicies.SurfacePolicy`
[🔗](https://github.com/d-led/ex.tbp.monty/blob/v0.1.0/packages/ex_tbp_monty_core/lib/monty/frameworks/models/motor_policies/surface_policy.ex#L1)

A policy that walks the surface of an object, feeling along it.

Mirrors `tbp/monty/frameworks/models/motor_policies.py::SurfacePolicy`, the
policy a surface agent runs: it keeps the sensor a fixed distance from the
surface and moves *along* it, so the evidence it collects comes from a band
that crosses the object rather than from wherever a random walk lands.

## The cycle

Four actions, in order, forever:

    MoveForward -> OrientHorizontal -> OrientVertical -> MoveTangentially

Moving forward puts the sensor at `desired_object_distance` from what it sees;
orienting horizontally turns the sensor to face the surface normal's sideways
lean, orienting vertically its up-and-down lean, and moving tangentially takes
a step along the surface in a direction that is mostly the last one and
partly new (`alpha`, Monty's "0 is the same as before, 1 is a random walk").
Only the vertical orienting step is worth showing the learning modules — the
other three are bookkeeping to stay on the object — which is what
`motor_only_step` says.

## When it cannot see the object

`object_coverage` below a tenth, or a search already under way, means the
sensor has left the object; then the policy searches: back off to a circle
four times the desired distance in front of it and step round it thirty
degrees at a time, horizontally for a full turn, then vertically for a
second, then at random angles until something comes into view.

## What it shares with `InformedPolicy`

Monty's `SurfacePolicy` is an `InformedPolicy`: it inherits the jump machinery
and overrides the step. Here the jump step is
`InformedPolicy.goal_driven_actions/4`, taken by any policy carrying the jump
fields, and Monty's `_handle_successful_jump` hook is `handle_successful_jump/1`
dispatched to the policy's own module (`docs/monty-differences.md` §2.9). A jump that
lands with the object in view restarts the cycle as if the agent had just
moved tangentially, which is how a goal-driven step hands back to walking.

# `surface_state`

```elixir
@type surface_state() :: %{
  :agent_id =&gt; atom() | String.t(),
  :rng =&gt; :rand.state() | nil,
  :alpha =&gt; float(),
  :desired_object_distance =&gt; float(),
  :use_goal_driven_actions =&gt; boolean(),
  :is_jumping =&gt; boolean(),
  :is_undoing_jump =&gt; boolean(),
  :pre_jump_state =&gt; map() | nil,
  :undo_jump_actions =&gt; [Monty.Frameworks.Actions.Action.t()],
  :attempting_to_find_object =&gt; boolean(),
  :tangential_angle =&gt; float() | nil,
  :touch_search_amount =&gt; float(),
  :last_surface_policy_action =&gt; Monty.Frameworks.Actions.Action.t() | nil,
  :telemetry =&gt; term(),
  optional(atom()) =&gt; term()
}
```

A policy that can run the surface cycle: the fields the step reads.

`SurfacePolicy` and `SurfacePolicyCurvatureInformed` both carry them, which is
how Monty's inheritance is expressed here (`docs/monty-differences.md` §2.9).

# `t`

```elixir
@type t() :: %Monty.Frameworks.Models.MotorPolicies.SurfacePolicy{
  action_sampler: Monty.Frameworks.Actions.ActionSamplers.ActionSampler.t(),
  agent_id: atom() | String.t(),
  alpha: float(),
  attempting_to_find_object: boolean(),
  desired_object_distance: float(),
  is_jumping: boolean(),
  is_undoing_jump: boolean(),
  last_surface_policy_action: Monty.Frameworks.Actions.Action.t() | nil,
  pre_jump_state: Monty.Frameworks.Models.MotorSystemState.AgentState.t() | nil,
  rng: :rand.state() | nil,
  tangential_angle: float(),
  telemetry:
    Monty.Frameworks.Models.MotorPolicies.SurfacePolicyTelemetry.t() | nil,
  touch_search_amount: float(),
  undo_action: Monty.Frameworks.Actions.Action.t() | nil,
  undo_jump_actions: [Monty.Frameworks.Actions.Action.t()],
  use_goal_driven_actions: boolean()
}
```

# `call`

```elixir
@spec call(
  t(),
  Monty.Context.t(),
  map() | nil,
  Monty.Frameworks.Models.MotorSystemState.t(),
  Monty.Cmp.Message.t() | nil,
  Monty.Cmp.Goal.t() | nil
) :: {Monty.Frameworks.Models.MotorPolicies.MotorPolicyResult.t(), t()}
```

One step: a jump if one is in flight or asked for, otherwise the cycle.

The search state is reset on every step that goes on to the cycle, so falling
off the object later starts a search from the beginning.

# `handle_successful_jump`

```elixir
@spec handle_successful_jump(surface_state()) :: surface_state()
```

A jump has landed and the object is in view: carry on as if we had just moved.

Monty overrides its `_handle_successful_jump` here so that the corrective part
of the cycle runs next, and records that the step where the jump landed is
telemetry rather than a curvature choice.

# `new`

```elixir
@spec new(keyword()) :: t()
```

A surface policy for `:agent_id`, with `:alpha` weighting new directions.

`:desired_object_distance` is how far the sensor should sit from the surface,
Monty's 2.5 cm unless told otherwise.

# `reset`

```elixir
@spec reset(surface_state()) :: surface_state()
```

Starts the next episode: no jump in flight, and the search forgotten.

# `state_dict`

```elixir
@spec state_dict(t()) :: map()
```

Nothing to report: what it knows is the action it would undo.

# `surface_step`

```elixir
@spec surface_step(
  surface_state(),
  Monty.Context.t(),
  map() | nil,
  map(),
  Monty.Cmp.Message.t() | nil,
  Monty.Cmp.Goal.t() | nil
) ::
  {Monty.Frameworks.Models.MotorPolicies.MotorPolicyResult.t(), surface_state()}
```

One step of the surface cycle, for any policy that carries its fields.

Monty's `SurfacePolicy.__call__`, reached here through `call/6` and directly by
`SurfacePolicyCurvatureInformed`, which is a subclass there and a second struct
here (`docs/monty-differences.md` §2.9). The tangential direction is the one part it
overrides, so that is asked of the policy's own module.

# `tangential_direction`

```elixir
@spec tangential_direction(
  surface_state(),
  Monty.Context.t(),
  map(),
  Monty.Cmp.Message.t() | nil
) ::
  {Monty.Geometry.Rotation.vector(), surface_state()}
```

A direction that is mostly the last one and partly new, in the agent's frame.

Monty's `SurfacePolicy.tangential_direction`, and the one method
`SurfacePolicyCurvatureInformed` overrides — hence public, and reached by the
policy's own module (`docs/monty-differences.md` §2.9).

---

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