fix(lcxl3): the driver had never connected anything to SuperCollider
Resume point 1 was "run lcxl3-driver against a live SuperCollider, because it
never has been". It has now, and the run found two defects that no amount of
reading could have found — both invisible precisely because the code reported
success.
DEFECT 1 — the aconnect direction trap. reconcile() resolved its destination
with `_clients("-i")`:
dst = next((p for p, n in _clients("-i") if "SuperCollider" in n), None)
`aconnect -i` lists ports you can read FROM. SuperCollider appears in both
directions: in0..in4 are destinations, out0..out6 are sources. So `-i` returned
SC's *output* port — 130:5 on this rig — and the driver ran
aconnect ParVagues LCXL3:0 130:5
which writes into an output-only port and fails. The CompletedProcess was
discarded, so the failure had no way to be seen, and the next line printed
"reconcile: src -> dst" as though the link existed. Every previous reasoning
step about this driver rested on a connection that was never made.
Fixed by resolving destinations from `-o` via a new _sc_input_ports(), and by
checking the return code and PRINTING the failure. A reconciler that cannot say
"I failed" is decoration — the rig has been bitten by this shape before
(feedback_verify_the_plumbing, reference_check_the_instrument_first).
DEFECT 2 — reconcile only ever ADDED links, so it could not be correct here.
`MIDIIn.connectAll` subscribes SC to every source present at boot. Measured on
the live rig:
14:0 Midi Through -> 130:2
20:0 LCXL3 1 MIDI -> 130:3
20:1 LCXL3 1 DAW -> 130:4
So the moment the driver published a correct, translated stream, SC was hearing
it ALONGSIDE two raw ones. This is worse than duplication: v3's DAW map
collides with the corpus's v2 numbers on 16 indices with different meanings, so
row C knob 4 would drive ^52 (right) and ^32 (row B's cell) at once — two
orbits moving when one knob turns. And a doubled CC is fatal on a latch: the
toggle fires twice and cancels, which reads from the stage as "the button does
nothing".
Added prune(), which cuts every path into SC that is not our virtual port,
re-asserted on the same timer as the connect (the binding must be re-asserted,
never assumed — feedback_stale_binding_pattern). Midi Through is cut by default
because midi-autoconnect's rule 1 pushes the surface into it, i.e. it is a
second copy of the same stream; --keep-thru opts out.
Added `--graph`, which prints what actually feeds SC and what prune would do.
That is the diagnostic whose absence let defect 1 hide: the driver's own view
of the graph was never printable, so it could never be checked against reality.
Verified on the live rig (sclang 845344 / scsynth 845718, LCXL3 on client 20):
--graph now resolves 130:0-130:4 and correctly classifies 3 WOULD CUT paths and
PipeWire-RT-Event's monitor bridge as left alone. Hardware also confirms the
translation table's row D: PLN reports fader 1 = CC5, which is V3_ROWS["D"][0].
Showing