# `Monty.Frameworks.Models.MotorPolicies.SurfacePolicyCurvatureInformed`
[🔗](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_curvature_informed.ex#L1)

A surface policy that follows the principal curvature directions when it can.

Mirrors `tbp/monty/frameworks/models/motor_policies.py::SurfacePolicyCurvatureInformed`.
`SurfacePolicy` walks the surface in a direction that is mostly the last one;
this policy does that only when the surface tells it nothing, and otherwise
heads along the object's own lines — the rim of a cup, the handle of a mug — by
following a principal curvature direction.

## Which line it follows, and for how long

It starts on the **smallest** principal curvature (in absolute value) and
follows it for `max_pc_bias_steps`, then the largest, then back, alternating
while the directions stay defined. When they stop being defined it falls back
to the standard momentum step, and it will not consider curvature again until
it has taken `min_general_steps` of those.

## Two reasons to ignore a curvature direction

A direction that points mostly along the agent's **z** axis is ignored: on the
rim of a cup the curvature direction comes out pointing away from the surface,
which is an artefact of the agent re-orienting against a noisy estimate. And a
direction that would take the sensor where it **has already been** is turned
away from it by a search over headings — the first attempt flips a curvature
direction that has just started, later ones rotate the heading at random, the
search looks less strictly as it fails, and if nothing is free after a hundred
attempts the original heading is kept.

## What it shares with `SurfacePolicy`

In Monty this is a subclass and overrides `__call__` (to record where a
tangential step came from), `tangential_direction` and `reset`. Here it is a
second struct carrying the same fields, calling `SurfacePolicy.surface_step/6`
and overriding the one method, which that step asks of the policy's own module
(`docs/monty-differences.md` §2.9).

# `t`

```elixir
@type t() :: %Monty.Frameworks.Models.MotorPolicies.SurfacePolicyCurvatureInformed{
  action_sampler: Monty.Frameworks.Actions.ActionSamplers.ActionSampler.t(),
  agent_id: atom() | String.t(),
  alpha: float(),
  attempting_to_find_object: boolean(),
  continuous_pc_steps: non_neg_integer(),
  desired_object_distance: float(),
  following_heading_counter: non_neg_integer(),
  following_pc_counter: non_neg_integer(),
  ignoring_pc_counter: non_neg_integer(),
  is_jumping: boolean(),
  is_undoing_jump: boolean(),
  last_surface_policy_action: Monty.Frameworks.Actions.Action.t() | nil,
  max_pc_bias_steps: pos_integer(),
  min_dir_pref: boolean(),
  min_general_steps: non_neg_integer(),
  min_heading_steps: non_neg_integer(),
  pc_alpha: float(),
  pc_is_z_defined: boolean(),
  pre_jump_state: Monty.Frameworks.Models.MotorSystemState.AgentState.t() | nil,
  prev_angle: float() | nil,
  rng: :rand.state() | nil,
  setting_new_heading: boolean(),
  tangent_locs: [Monty.Geometry.Rotation.vector()],
  tangent_norms: [Monty.Geometry.Rotation.vector() | nil],
  tangential_angle: float() | nil,
  tangential_vec: Monty.Geometry.Rotation.vector() | nil,
  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(),
  using_pc_guide: 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, with the place a tangential move came from remembered first.

Monty records the sensor's location and surface normal whenever the previous
action was the vertical orienting, because the tangential step follows it
immediately: those are the places the sensor has walked to, as opposed to the
corrective movements that put it there.

# `new`

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

A curvature-following surface policy.

`:alpha` weights a standard step towards the previous one, `:pc_alpha` the
same for the moving average of a curvature direction. `:max_pc_bias_steps`,
`:min_general_steps` and `:min_heading_steps` are Monty's three counters, and
they are required because their right values are a property of the experiment.

# `reset`

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

Starts the next episode: the surface's own state goes with it.

# `state_dict`

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

Nothing beyond what a surface policy reports.

# `tangential_direction`

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

The direction of a tangential move: along a curvature line, or by momentum.

Monty's override of `SurfacePolicy.tangential_direction`. Curvature is only
considered when the pose is fully defined *and* enough standard steps have been
taken since the last time curvature was followed.

---

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