# `Statifier.Interpreter.Selection`
[🔗](https://github.com/riddler/statifier-ex/blob/v2.0.0/lib/statifier/interpreter/selection.ex#L1)

Appendix D transition selection, ported function for function (ADR-0002).

Every function here that reads a global takes `machine_state` (or, when it
needs only topology, `machine`) as its first argument - the mechanical
deviation `docs/observability.md` constraint 1 sanctions for reifying the
pseudocode's `configuration` and `historyValue` globals onto
`%Statifier.MachineState{}`. Each function's own `@doc`
says which global its first argument stands in for; this paragraph states
the rule once so they do not each re-argue it.

`compute_exit_set/2` returns a `MapSet` of indexes, not an `OrderedSet`:
`remove_conflicting_transitions` wants set intersection and gets it directly,
and a caller that wants exit *order*
pipes the result through `Statifier.Machine.exit_order/2`, which already
exists for exactly that. No function in this module orders an exit set
itself.

Every function is a pure query - plain values in, plain values out, no
hidden context, callable standalone in `iex` (`docs/observability.md`
constraint 5). The block divides in two. The domain half - `find_lcca/2`
(delegated), `get_effective_target_states/2`, `get_transition_domain/2`,
and `compute_exit_set/2` - answers "which states does this transition
leave". The selection half - `condition_match/2`, `select_transitions/2`,
`select_eventless_transitions/1`, and `remove_conflicting_transitions/2` -
answers "which transitions fire", and calls into the domain half to do it.

`select_transitions/2` and `select_eventless_transitions/1` are the two
functions in this module that thread `machine_state` through their
*return* value as well as their first argument: both return
`{MachineState.t(), [Transition.t()]}` rather than a bare list, so that a
failed `cond` discovered mid-walk has a machine_state to enqueue
`error.execution` onto. The machine_state comes back **unchanged** when no
`cond` fails during that round, and carries one `error.execution` event per
failed `cond` - in walk order - when one or more do. This is the
deliberate, signature-preserving edit the paragraph above used to
anticipate; it has landed. Every other query in this module, including
`remove_conflicting_transitions/2` itself, still returns a plain value with
no machine_state, because none of them can raise.

The two walks and the conflict filter are Appendix D's two nested loops
with labelled breaks, decomposed into named private helpers to stay under
Credo's cyclomatic-complexity and nesting limits - each
helper's doc names the pseudocode lines it stands in for, so "diff against
the pseudocode" still works one level down. The enabled set is deduplicated
by `t_index`, keeping the first occurrence, in place of the pseudocode's
`OrderedSet`.

# `compute_exit_set`

```elixir
@spec compute_exit_set(
  machine_state :: Statifier.MachineState.t(),
  transitions :: [Statifier.Machine.Transition.t()]
) :: MapSet.t(non_neg_integer())
```

`computeExitSet` (Appendix D) - every index in `machine_state.configuration`
that a proper descendant of any transition in `transitions`'s domain, unioned
across `transitions`.

Two literal-port details that must survive review:

- The guard is `if t.target` - written targets (`transition.targets == []`),
  not the effective ones `get_transition_domain/2` resolves. A transition
  with written targets whose effective targets resolve empty still enters
  the loop; its domain is then `nil` and it is handled explicitly below,
  contributing nothing rather than being filtered out earlier by a merged
  test.
- `Machine.descendant?/3` is **proper**, so a transition's domain is never a
  member of its own exit set - the whole reason that predicate's strictness
  exists (`Machine.descendant?/3`'s own `@doc`).

# `condition_match`

```elixir
@spec condition_match(
  machine_state :: Statifier.MachineState.t(),
  transition :: Statifier.Machine.Transition.t()
) :: {:ok, boolean()} | {:error, term()}
```

`conditionMatch` (Appendix D) - the one `cond` seam. `nil` `cond` always
passes; a *written* `cond` is evaluated through `Statifier.Evaluator`
against a `Predicator.Context.t()` built from `machine_state` - once per
call here, and once per selection round in the private walk below (the
"once per evaluation site" contract `Statifier.Evaluator`'s own moduledoc
states). `{:ok, true}` enables the transition; `{:ok, false}` and
`{:error, _}` both do not.

A non-boolean `{:ok, value}` - anything other than `true` or `false` - is
treated as an `{:error, {:non_boolean_cond, value}}` rather than as a falsy
value, because **spec 5.9.1 makes it the same case as an evaluation
error**: "If a conditional expression cannot be evaluated as a boolean
value ('true' or 'false') or if its evaluation causes an error, the SCXML
Processor MUST treat the expression as if it evaluated to 'false' and MUST
place the error 'error.execution' in the internal event queue." The spec
joins the two with one `or` and gives them one consequence, so collapsing a
non-boolean quietly to `false` would satisfy half of that MUST and drop the
other half. This is not a deviation from Appendix D's `conditionMatch`; it
is the normative clause `conditionMatch` evaluates under.

The `{:error, _}` spelling is how a *pure query* carries both halves at
once: `docs/architecture.md` principle 3 forbids this leaf from raising or
rescuing, so it reports the failure and the two entry points below turn it
into "not enabled" plus the enqueue. ADR-0004 is why there is no third
option - predicator is the datamodel and has no ECMAScript truthiness to
borrow, so "cannot be evaluated as a boolean" is decidable here rather
than being a matter of taste.

This function never enqueues anything itself - it is a pure query, plain
values in and out, per this module's own moduledoc. The `{:error, _}`
path's `error.execution` enqueue lives in the two entry points below, not
here.

Unreachable from the corpus today: `FeatureDetector` marks
`conditional_transitions` `:unsupported`, so no compiled document can carry
a `cond`-bearing transition through selection yet. Reachable, and tested,
from a machine_state and transition built by hand.

# `find_lcca`

`findLCCA` (Appendix D) - see `Statifier.Machine.lcca/2` for the body.

One implementation, two names: `Machine.lcca/2` is the
port under `Statifier.Machine`'s own convention of naming its query
helpers after the spec operation they serve rather than after the spec
function itself; this `defdelegate` puts the spec's own name at the
interpreter's port surface, where `mix adr.check` looks and where
ADR-0002 expects to find it.

# `get_effective_target_states`

```elixir
@spec get_effective_target_states(
  machine_state :: Statifier.MachineState.t(),
  transition :: Statifier.Machine.Transition.t()
) :: [non_neg_integer()]
```

`getEffectiveTargetStates` (Appendix D) - `transition`'s targets, with every
`:history` target resolved to a concrete set of indexes.

`machine_state` stands in for two pseudocode globals: `historyValue`
(`machine_state.history_values`) and, indirectly through
`machine_state.machine`, the topology `Machine.at/2` reads. A non-history
target passes through unchanged. A `:history` target with a recorded entry
in `history_values` contributes that entry's members. An unrecorded
`:history` target recurses through its own `history_default` transition's
targets - the pseudocode's `getEffectiveTargetStates(s.transition)` - which
is itself ported as a call back into this function, since a history
default's targets are ordinary transition targets that may (pathologically,
but representably) include another history state.

Returns a plain list where the pseudocode accumulates into an
`OrderedSet`, so the result is **not** deduplicated: `target="hs b1a"`,
where `hs` resolves to `b1a`, yields that index twice. Every consumer is
insensitive to it - `get_transition_domain/2` feeds the list to
`Enum.all?/2` and to `find_lcca/2`, neither of which changes answer on a
repeat, and `Statifier.Interpreter.ExitEntry`'s entry-set construction
absorbs repeats into the set it is building - so the dedupe would be dead
work at every call site that exists. Deduplicate at the consumer that
needs it, if one ever does, rather than here.

# `get_transition_domain`

```elixir
@spec get_transition_domain(
  machine_state :: Statifier.MachineState.t(),
  transition :: Statifier.Machine.Transition.t()
) :: non_neg_integer() | nil
```

`getTransitionDomain` (Appendix D) - the single state whose descendants (in
the configuration) `transition` exits, or `nil` for a transition with no
effective targets.

`machine_state` stands in for the same globals as
`get_effective_target_states/2`, which this function calls first. Two
literal-port details:

- The guard here is on *effective* targets (an empty resolution, e.g. an
  unrecorded history with no default reachable, yields `nil`), distinct
  from `compute_exit_set/2`'s guard on *written* targets - the pseudocode
  keeps the two tests separate and so does this port.
- `type == :internal` only ever narrows the domain to `source` when
  `source` is compound (`Machine.compound?/2`) and every effective target
  is a **proper** descendant of it (`Machine.descendant?/3`); every other
  case, including every `:external` transition, falls through to
  `find_lcca/2` on `[source | effective_targets]`.

# `remove_conflicting_transitions`

```elixir
@spec remove_conflicting_transitions(
  machine_state :: Statifier.MachineState.t(),
  enabled_transitions :: [Statifier.Machine.Transition.t()]
) :: [Statifier.Machine.Transition.t()]
```

`removeConflictingTransitions` (Appendix D) - `enabled_transitions`,
filtered down to a conflict-free set, ported exactly for its filter order:
two transitions conflict iff their exit sets intersect;
on conflict, a `t1` sourced in a descendant of `t2`'s source preempts
`t2` (`t2` is marked for removal, `t1` keeps checking the rest of the
filtered set); otherwise `t1` itself is preempted and its inner loop stops
immediately - this is *not* "the earlier transition always wins", it is
"the earlier transition wins unless the later one is the more specific
(descendant) source". A surviving `t1` has every transition it marked for
removal dropped from the filtered set and is appended to it.

This is the function ADR-0002 was partly adopted to fix: v1's conflict
resolution reduced every transition's exit set to a boolean "does it leave
the nearest parallel ancestor" and collapsed an entire microstep to one
transition whenever both a leaving and a non-leaving transition were
enabled, discarding unrelated, genuinely non-conflicting transitions from
other parallel regions. This port computes a real exit set per transition
(`compute_exit_set/2`) and only ever removes a transition that actually
intersects another's exit set.

The insertion order `enabled_transitions` arrives in is
`select_transitions/2`/`select_eventless_transitions/1`'s document-order,
dedup-by-`t_index` walk - the pseudocode's own comment about
`toList`'s ordering "the order of the states that selected them" is exactly
that walk, not anything this function itself orders.

# `select_eventless_transitions`

```elixir
@spec select_eventless_transitions(machine_state :: Statifier.MachineState.t()) ::
  {Statifier.MachineState.t(), [Statifier.Machine.Transition.t()]}
```

`selectEventlessTransitions` (Appendix D) - `select_transitions/2`'s
sibling for transitions with no `event` attribute (`events == []`). Same
walk, same dedupe, same trailing `remove_conflicting_transitions/2` call;
the pseudocode writes these as two functions rather than one parameterized
by "is there an event" and this port keeps them that way
(`Credo.Check.Design.DuplicatedCode` is disabled in `.credo.exs` for
exactly this reason).

Returns `{machine_state, transitions}`, matching `select_transitions/2`:
unchanged when no `cond` fails, carrying one `error.execution` per failed
`cond` (in walk order) when one does.

# `select_transitions`

```elixir
@spec select_transitions(
  machine_state :: Statifier.MachineState.t(),
  event :: Statifier.Event.t()
) ::
  {Statifier.MachineState.t(), [Statifier.Machine.Transition.t()]}
```

`selectTransitions` (Appendix D) - the transitions `event` enables, one per
atomic state in `machine_state.configuration` at most (child preempts
ancestor), deduplicated by `t_index`, and filtered through
`remove_conflicting_transitions/2` per the pseudocode's own last line.

`event.name` is tokenized once via `NameMatch.tokenize/1` before the walk
and threaded down to every atomic state's search - a hoist out of the
per-transition matcher - rather than re-split per transition.

Returns `{machine_state, transitions}`: the machine_state comes back
unchanged when no `cond` fails during this round, and carries one
`error.execution` per failed `cond` (in walk order) when one does - see the
moduledoc.

---

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