# 032 — The surface that spoke a different language

**Landed 2026-08-28/29.** The LCXL3 arrived. PLN, unpacking it: *"we have now infinite
knobs so always at 0 init ✨ We have colors and brightness, true colors all 3 leds
combinations, under each button and knob! we have a OLED screen with true pixels we could
display ANYTHING there! OMG so exciting wdyt?"*

Mostly yes. But the device that can paint anything could not, at first, play a single one
of his 169 tracks.

## The board's biggest unknown, answered

`reference_lcxl_v3_ordered` had carried a flagged question for twelve days: *can
third-party SysEx paint arbitrary text to the OLED?* It mattered because HUD v2's alarms
are DERIVED state, not control values — if the OLED only rendered parameter values, the
alarms would stay stuck on screen.

The official programmer's reference settles it: `F0 00 20 29 02 15 09 <target> <1216
bytes> 7F`, nineteen bytes per row, sixty-four rows, seven pixels per byte. And the
firmware **ACKs every frame** *"suitable for timing fluid animations"* — so animation is
a designed-in feature, not a hack. DESIGN.md's "alarms need motion, not numbers" is
therefore satisfiable on the device itself.

Two more open items closed the same way. LED brightness is CC 111 and non-volatile, so
open taste call #11 — should a bypassed filter be bright or dim — stops being an argument
and becomes a knob. And endless encoders send relative deltas, so pot-pickup does not
exist to solve.

## The keystone: three steps, PLN's eyes, ten minutes

The guide files "Colouring the surface" and "Controlling the screen" inside the DAW mode
chapter, prefaced *"only available once DAW mode is enabled"*. That is a document claim,
and this project has scar tissue about believing those. So it got tested.

Magenta on the MIDI port in standalone: nothing. Same paint on the DAW port with DAW mode
off: nothing. `daw on`, same bytes: all 24 rings and 16 buttons lit. PLN: *"ok now i see
purple ABC blue EF"*.

Hardware fact, not documentation artefact. Which created the real problem.

## Sixteen collisions, and one of them is the kick

DAW mode's CC map is fixed, and it was read off the hardware rather than off a
low-resolution diagram — one control per row, watching what arrived. It overlaps v2's map
with **different meanings**:

    row  v2 (lcxl_grid)   v3 DAW
    A    13-20            13-20     identical
    B    29-36            21-28
    C    49-56            29-36     <- v2's row B numbers
    D    77-84            5-12
    E    41-44, 57-60     37-44
    F    73-76, 89-92     45-52

The decisive one: v3 `#41` is button **E5**, while v2 `#41` is button **E1** — the kick
gate that log 031's 169-file migration had just finished aligning. Renumbering would slide
the kick gate four buttons to the right, silently, with nothing erroring. That is the
exact failure class the migration existed to end.

So: software translates, the corpus does not move again. `tools/lcxl3-driver.py` reads the
v3 DAW port and emits v2 numbers on a virtual port, deriving the mapping from
`lcxl_grid.ROW_CCS` positionally rather than retyping it. Three facts make it cheap —
SuperCollider's `MIDIFunc.cc` binds with no cc, channel or src filter so only the number
matters; every encoder row is still in absolute mode, so a v3 encoder already behaves like
a v2 pot; and endless mode would need an integrator anyway, which is where paint-by-value
belongs later.

## Two silent breakages found on the way

**Both rig reconcilers had been dead since 23 August.** `203/EXEC`: when log 030 rescued
them from `$HOME` they were committed as **644**. The recurrence has a repo-level cause —
`core.fileMode = false` here, so git ignores exec-bit changes from the working tree
entirely, and `chmod` can never fix the index. `git update-index --chmod=+x` is the tool.
Worse, an auto-restarting unit reports `activating`, which reads as busy rather than
broken, so `rig_units --status` showed a yellow that nobody chased for five days.

**`midi-autoconnect` hardcoded "Launch Control XL"**, which matches neither v3 port — and
it routes `aconnect`'s complaints to `/dev/null`, so Ardour silently stopped hearing the
surface with every check green. It now resolves the name, preferring the driver's
translated port, and logs its choice once per change rather than every two seconds.

## Instruments that lied, and one that lied about the instrument

`aseqsend -s "B0 25 05"` treats its argument as a **filename**: it printed `cannot open
B0 25 05` and exited **0**. Thirty-two paint attempts, silent no-op, exit success. That is
how a transport bug can look like a hardware answer. `lcxl-leds.py` shells out that way
today.

And `pgrep -c -f sclang` returned 2 while `ps -C sclang` returned nothing — it was
matching its own command line. Reading that as "SC is running" produced a confidently
wrong picture of the rig for several minutes. `pgrep -x` says the truth.

## Firmware, and a decode worth keeping

The device inquiry version field is **7-bit packed**: `01 00 0A 54` = 1.0.(10<<7|84) =
**1.0.1364**, which PLN confirmed off the bootloader screen. Updated to **1.1.1465.0** via
web Components in Chromium — WebMIDI works on Linux, despite Novation documenting only the
Mac/Windows app. v1.1 adds two extra USB→DIN output ports, Mode-as-momentary for fast
custom-mode switching, and encoder acceleration (which CC 121 had refused to answer on
1.0, independently confirming the version).

## A claim retracted with evidence

The board twice asserted that the v3's DIN path mitigates the USB-hub single point of
failure. It does not: `aconnect -l` shows only Midi Through and the controller. **This
machine has no MIDI DIN input at all** — the UMC202HD has no MIDI jacks. The controller
could speak DIN all night and nothing host-side would hear it. So DAW mode disabling DIN
out costs nothing today, and #154 needs a different answer.

## Left open, honestly

The driver has never run against a live SuperCollider, because sclang, scsynth and Ardour
were all at zero when it was written. The whole "PLN can play his tracks on the new
surface" claim rests on that one untested step.
