# `Monty.Frameworks.Models.SensorModules.FeatureChangeFilter`
[🔗](https://github.com/d-led/ex.tbp.monty/blob/v0.1.0/packages/ex_tbp_monty_core/lib/monty/frameworks/models/sensor_modules/feature_change_filter.ex#L1)

Sends a point to the learning modules only when something about it has changed
enough to be worth learning.

Mirrors `tbp/monty/frameworks/models/sensor_modules.py::FeatureChangeFilter`.
Without it a surface agent would hand its learning module a new point on every
timestep even when it had barely moved, which is a different experiment: the
agent would fill a graph with near-identical points instead of sampling the
surface.

The tolerances are the config's `delta_thresholds`, by feature name, and three
names are not features at all:

  * `n_steps` — send anyway after this many steps without sending;
  * `distance` — send if the point moved further than this from the last one
    sent;
  * `hsv` — hue is a circle, so the change is the shorter way round, and the
    tolerance is one number per channel.

A percept already flagged as uninteresting — off the object, or with a surface
the fit could not define — is passed on as it is, as Monty passes it: the filter
decides between features and a location only, not whether to send.

# `memory`

```elixir
@type memory() :: %{
  last_percept: Monty.Cmp.Message.t() | nil,
  steps_since_sent: non_neg_integer()
}
```

What the filter remembers: the last percept it sent on, and how long ago.

# `t`

```elixir
@type t() :: %Monty.Frameworks.Models.SensorModules.FeatureChangeFilter{
  delta_thresholds: map()
}
```

# `new`

```elixir
@spec new(map()) :: {t(), memory()}
```

The filter, and the memory a new episode starts with.

---

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