# `Monty.Frameworks.Models.AbstractMontyClasses.SensorModule`
[🔗](https://github.com/d-led/ex.tbp.monty/blob/v0.1.0/packages/ex_tbp_monty_core/lib/monty/frameworks/models/abstract_monty_classes/sensor_module.ex#L1)

Behaviour for a sensor module, mirroring
`tbp/monty/frameworks/models/abstract_monty_classes.py::SensorModule`.

The callbacks are pure: they take the module's state and return the next
state alongside their result. `Monty.Runtime.SensorModuleServer` is the thin
process that holds that state; a module can be tested without a process at
all.

`update_state/2` is called before `step/3` on every step, because a sensor
module needs to know where the sensor is before it can say what it observes.

# `agent_state`

```elixir
@type agent_state() :: map()
```

# `observation`

```elixir
@type observation() :: map()
```

# `state`

```elixir
@type state() :: term()
```

Whatever a module keeps between steps; opaque to the runtime.

# `clear_buffers`

```elixir
@callback clear_buffers(state()) :: state()
```

Drops the observations this sensor kept, and nothing it has learnt.

Monty's `NoResetEvidenceGraphMatching._reset_modules_buffers` sets
`sm.processed_obs = []` at an object boundary: the observations will not be
read again, but the delta filter's memory decides what counts as a change and
has to survive, or the first patch on the new object is a sight the sensor has
never had. `reset/1` is the whole-episode version and is not the same thing.

The default is a sensor that keeps nothing.

# `propose_goals`
*optional* 

```elixir
@callback propose_goals(state()) :: {[Monty.Cmp.Goal.t()], state()}
```

Goals this sensor proposes (a salience module proposes; a camera does not).

# `propose_region`
*optional* 

```elixir
@callback propose_region(state()) :: {Monty.Cmp.AttentionRegion.t(), state()}
```

The region this sensor wants attended to.

# `reset`

```elixir
@callback reset(state()) :: state()
```

Clears short-term state for a new episode.

# `step`

```elixir
@callback step(observation(), boolean(), Monty.Context.t(), state()) ::
  {Monty.Cmp.Message.t() | nil, state()}
```

Turns one raw observation into a percept, or `nil` when the sensor has
nothing to say this step.

The context is the run's — its step, its mode, and through
`Monty.Context.rng_for/2` the random stream this sensor draws from. Monty
passes one for the same reason (`CameraSM.step(ctx, ...)`): a sensor that adds
noise needs to know which run and which step it is adding it to.

# `update_state`

```elixir
@callback update_state(agent_state(), state()) :: state()
```

Records the sensor's pose for the step about to run.

# `__using__`
*macro* 

Defaults matching Monty's base `SensorModule`: a plain sensor proposes no
goals and attends to nothing.

A module `use`s this behaviour and overrides what it needs, which is how the
Python base class reads too — and why a module that implements
`propose_goals/1` does not have to fight a catch-all default clause.

---

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