number stimulus outputs in device stage order

The output index now equals the device stage, so outputs follow the order the
device measures them: master port 1 through its switch positions, then port 2.
The previous position-major numbering permuted the middle two against the
physical order, so data captured before this needs its calibration re-captured.
This commit is contained in:
Ayzen
2026-10-02 13:57:33 +03:00
parent 7329750c0c
commit 61ef8720e1
3 changed files with 97 additions and 46 deletions
+17 -5
View File
@@ -399,20 +399,32 @@ class PerPointStimulusSwitchingTest(unittest.TestCase):
def test_two_positions_give_four_outputs(self) -> None:
channels = build_output_channels((1, 2), 2)
# Outputs are switch-position-major, so outputs 0/1 stay exactly what they are
# without switching; stages are port-major, matching the bitstream taking the
# port from the stage counter's UPPER bits.
# Port-major in BOTH numbers: the output index is the device stage, so outputs
# come in the order the device measures them — port 1 through its switch
# positions, then port 2. Verified against a scope on the switch line.
self.assertEqual(
[(c.index, c.stimulus_port, c.stage_index) for c in channels],
[(0, 1, 0), (1, 2, 2), (2, 1, 1), (3, 2, 3)],
[(0, 1, 0), (1, 1, 1), (2, 2, 2), (3, 2, 3)],
)
def test_output_index_is_the_device_stage(self) -> None:
# The whole point of the 2026-09-17 renumbering: an operator hanging antennas
# sees stage order on a scope, and the output numbering must not permute it.
for substage_count in (1, 2, 4):
channels = build_output_channels((1, 2), substage_count)
self.assertEqual(
[c.index for c in channels],
[c.stage_index for c in channels],
msg=f"substages={substage_count}",
)
self.assertEqual([c.index for c in channels], list(range(len(channels))))
def test_switch_position_is_the_low_stage_bit(self) -> None:
# This is what lets the controller just invert its pin on every trigger cycle
# instead of tracking an absolute stage number.
channels = build_output_channels((1, 2), 2)
for channel in channels:
expected_position = channel.index // 2 # switch-position-major output order
expected_position = channel.index & 1
self.assertEqual(channel.stage_index & 1, expected_position)
def test_stage_budget_is_enforced(self) -> None: