The stand's state machine, read from the same tables the DAQ runs on.
The DAQ does not have a state machine written in code; it has two CSVs, and the
firmware and the GUI both read them. So does this. Copying the *behaviour* into
Python would have created a second source of truth that drifts silently the
first time somebody adds a state -- and the whole point of the twin is that it
does what the stand does.
``state_machine_actuators.csv``
A matrix. One row per actuator, one column per state, ``OPEN`` or ``CLOSE``
in each cell. This is what a state *is* on this stand: a column.
``state_transitions.csv``
Which moves are legal, as a square 0/1 matrix. Idle cannot go straight to
Fire; it goes through Armed and the press states. Enforcing that here means
the twin refuses the same sequences the stand refuses, which is most of what
makes it useful for training somebody.
Matching actuators to a drawing
-------------------------------
The tables name actuators the way the crew does -- "LOX Main", "Fuel Vent". A
P&ID names them however the person drawing it typed them -- "MV-OX", "MVO",
"SV-FUEL-VENT". So the join is fuzzy and, more importantly, *reported*: a run
says which actuators matched a symbol and which did not, because an unmatched
"LOX Main" means the main valve is not being commanded at all and the trace
would look like a very smooth start.