# `Monty.Runtime.Step.State`
[🔗](https://github.com/d-led/ex.tbp.monty/blob/v0.1.0/packages/ex_tbp_monty_core/lib/monty/runtime/step/state.ex#L1)

What a step coordinator needs to know between steps, with the wiring
normalised once at start, and the model-level bookkeeping Monty keeps on
`MontyBase`.

Monty's connection matrices (`sm_to_lm_matrix`, `lm_to_lm_matrix`,
`lm_to_lm_vote_matrix`) are lists indexed by module position in Python; here
they arrive as maps keyed by module id. This struct inverts them once — for
every learning module, which sensors feed it and which modules feed it from
above — so the step loop reads a plain map instead of defensive lookups on
every phase.

Two counts are deliberately distinct. `step` is how many steps this episode
has taken, and is what the context carries. `matching_steps` and
`exploratory_steps` count only the steps in which at least one module was
given an observation — the steps in which the episode actually learned
something — because those are the counts an experiment's termination rules
are written in terms of.

# `id`

```elixir
@type id() :: atom() | String.t()
```

# `input`

```elixir
@type input() :: %{sensor_modules: [id()], learning_modules: [id()]}
```

# `match_criterion`

```elixir
@type match_criterion() :: {module(), term()}
```

# `module_spec`

```elixir
@type module_spec() :: %{id: id(), module: module(), state: term()}
```

# `step_type`

```elixir
@type step_type() :: :matching | :exploratory
```

# `t`

```elixir
@type t() :: %Monty.Runtime.Step.State{
  attention: ([Monty.Cmp.Goal.t()], [Monty.Cmp.AttentionRegion.t()] -&gt;
                [Monty.Cmp.Goal.t()]),
  call_timeout: timeout(),
  combine_votes: (map(), t() -&gt; map()),
  done?: boolean(),
  episode_seed: non_neg_integer() | nil,
  experiment_mode:
    Monty.Frameworks.Models.AbstractMontyClasses.LearningModule.mode(),
  exploratory_steps: integer(),
  inputs: %{optional(id()) =&gt; input()},
  learning_modules: [module_spec()],
  lm_to_lm_vote: %{optional(id()) =&gt; [id()]} | nil,
  match_criterion: match_criterion(),
  matching_steps: integer(),
  min_eval_steps: non_neg_integer(),
  min_train_steps: non_neg_integer(),
  model_class: module(),
  model_id: id(),
  motor: {module(), term()} | nil,
  motor_only?: boolean(),
  motor_state: term(),
  num_exploratory_steps: non_neg_integer(),
  semantic_id_to_label: %{optional(integer()) =&gt; String.t()},
  sensor_modules: [module_spec()],
  step: non_neg_integer(),
  step_type: step_type(),
  terminal_states: %{optional(id()) =&gt; terminal_state()},
  vote_timeout: timeout()
}
```

# `terminal_state`

```elixir
@type terminal_state() ::
  Monty.Frameworks.Models.AbstractMontyClasses.LearningModule.terminal_state()
```

# `all_goals`

```elixir
@spec all_goals([Monty.Cmp.Goal.t()], [Monty.Cmp.AttentionRegion.t()]) :: [
  Monty.Cmp.Goal.t()
]
```

Monty's `NoopAttentionSystem`: every proposed goal passes.

# `combine_votes`

```elixir
@spec combine_votes(map(), t()) :: map()
```

Monty's default vote routing: a receiver gets the votes of the modules wired
to it by `lm_to_lm_vote_matrix`, mounted in the frame of its own sensor.

Two calculations, told apart by the shape of a vote exactly as Monty tells them
apart (`isinstance(votes_per_lm[0], set)` in `_combine_votes`): a graph module
names the objects it ruled out and the answer is their union
(`...GraphMatching.MontyForGraphMatching.combine_votes/2`), while an evidence
module names the hypotheses it still believes and the answer is that vote moved
into the receiver's frame
(`...EvidenceMatching.Model.MontyForEvidenceGraphMatching.combine_votes/2`).
Reading the shape rather than the config's model class is what lets one runtime
step either family — and a run of the graph-matching family used to crash on
its first vote round, because the union was never written.

# `new`

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

Builds the coordinator state from a model spec.

---

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