Skip to content

feat(lighting): enable Lightpad rendering and LUMI key colors - #7

Merged
hyperb1iss merged 10 commits into
mainfrom
nova/lumi-lighting
Sep 8, 2026
Merged

feat(lighting): enable Lightpad rendering and LUMI key colors#7
hyperb1iss merged 10 commits into
mainfrom
nova/lumi-lighting

Conversation

@hyperb1iss

@hyperb1iss hyperb1iss commented Sep 7, 2026

Copy link
Copy Markdown
Owner

Lightpad frames could be accepted without producing the requested colors, and LUMI had no per-key lighting API. This change restores device-side rendering for Lightpad grids and adds individual colors for all 24 LUMI keys.

The assembler relocates jump targets to their position in the complete program. Both renderers disable the firmware status overlay. The daemon uploads code separately from pixels, waits for the complete upload acknowledgement, then challenges the running renderer with a fresh nonce. Heap-state loss restarts that handshake, including when identical program bytes survive without another initialise callback.

LUMI exposes JSON key_frame (72 RGB888 bytes), discovery key_count = 24, and blocksd led keys [rainbow|HEX]. Firmware 1.3.0 or newer is required. The renderer enables default musical key handling while replacing key lighting. Factory programs remain active until a lighting request arrives; the existing Lightpad binary API stays compatible.

🛠️ Complete animation frames

Continuous input previously replaced unfinished heap targets, repeatedly updating the prefix while starving lower Lightpad rows. Each multipart transfer now finishes an immutable snapshot while newer desired frames coalesce separately. The encoder also reserves capacity for skip commands and its end marker.

Lightpad uses two 450-byte pixel banks. The host uploads into the inactive bank, then requests presentation after its heap packets are acknowledged. The renderer latches one bank for the entire repaint and acknowledges only after all 225 pixel writes. Session, sequence, and bank checks prevent stale replies from releasing a bank; retries and identical-code reconnects preserve ownership. Startup and recovery may blank the display during resynchronisation.

MIDI reception is independent of the 200 ms lifecycle timer. Incoming acknowledgements are processed immediately, while absolute timer deadlines and cooperative yielding preserve keepalives and other tasks during continuous traffic.

🧪 Validation

  • Full just check passes on the final head: 625 Python tests, Ruff, ty, dashboard checks, docs build, packaging, and fresh installed-wheel validation.
  • Independent decoded-wire tests verify complete snapshots, final convergence, resets, and cumulative acknowledgements, including 2,194 randomized packets.
  • An independently rebuilt vendor VM passes 92 repaints / 20,700 pixel writes, including partial inactive-bank uploads, stale messages, retries, identical-code recovery, and presentation requests injected halfway through repaint.
  • Independent native-thread callbacks through the real MIDI connection abstraction arrive between lifecycle ticks in under 2 ms. The original loop reproduced a 180 ms queue delay.
  • On LUMI 1.3.9 over USB and Lightpad M 1.1.0 over DNA, a 15-second native Hypercolor sweep accepted 376 frames per device. Lightpad acknowledged 113 complete repaints, approximately 7.53 repaints/sec between the first and last acknowledgement, up from 1.46/sec before the receive-loop fix. These are logical repaint measurements, not physical LED scan timing.

The hardware owner confirmed whole-pad animation after the snapshot fix. Final visual feedback on buffered alignment remains pending. Physical MIDI/MPE preservation, cable/power-loss recovery, and hardware operation on macOS remain unverified. The vendor VM verifies the musical-handler arguments; a live MIDI/MPE exercise is still needed.

The matching Rust bridge is in Hypercolor #263. Python retains ownership of MIDI and the device protocol; Hypercolor supplies effects and spatial mapping.

The LUMI renderer now claims hardware brightness at 100% during initialization and validated readiness challenges, including reconnects with unchanged code. Host RGB/API brightness still controls dimming, and gamma is unchanged. Hardware readback confirmed brightness 100 in its 0–100 range. Config requests also include the SDK-required 32-bit reserved address field; three upstream C++ wire vectors cover the correction. Independent native-VM checks verified brightness calls, readiness ordering, unchanged musical-handler arguments, and black/white/patterned key frames.

hyperb1iss and others added 3 commits September 7, 2026 12:43
The VM resolves jumps and calls from the complete program binary, but
label fixups omitted the header and function table. The bitmap renderer
therefore jumped into unrelated bytes and failed after drawing one pixel
in the upstream runner.

Include the code base when resolving labels. Cover forward and backward
branches, calls, multiple functions, and repeated builds. Independent
upstream VM replay renders all 225 pixels correctly with the fix; physical
hardware compatibility remains to be verified.

Co-Authored-By: Nova (Codex) <noreply@openai.com>
Firmware clears initial pixel data while starting a program, and its
status overlay can hide the requested colors. Upload code separately,
disable the overlay, and require a nonce reply before publishing pixels.
Restart that handshake when heap state is lost, including when identical
code survives a reconnect.

Add a 24-key renderer with default musical handling enabled, a key_frame
API command, and a keys CLI pattern that waits for firmware discovery.
Keep factory programs active until a lighting request arrives.

Verified RGB on both devices, execution and host-state recovery on
Lightpad M 1.1.0 and LUMI 1.3.9, 540 tests, native vendor VM vectors,
independent review, and an installed wheel check. Physical MIDI/MPE and
cable or power-loss exercises remain unverified.

Co-Authored-By: Nova (Codex) <noreply@openai.com>
Replace the unsupported-opcode diagnosis with the reproduced jump,
startup heap, and overlay failures. Document the execution challenge,
LUMI key API and CLI, firmware requirements, and observed device results.

Separate visible RGB and simulated host-state recovery evidence from
pending physical reconnect, musical input, timing, and calibration work.
Documentation builds and scoped formatting checks pass.

Co-Authored-By: Nova (Codex) <noreply@openai.com>
hyperb1iss and others added 7 commits September 7, 2026 13:26
New animation frames repeatedly dirtied the heap prefix, starving lower
Lightpad rows while the API continued accepting frames. Pin each
multipart transfer and coalesce newer targets separately so every
surface completes without lowering the requested frame cadence.

Reserve packet capacity for skip commands and the end marker. Decoded
wire regressions cover continuous updates, slow ACKs, resets and final
convergence rather than trusting optimistic host state alone.
The bitmap renderer could repaint while heap packets replaced its
pixels, mixing upper and lower rows from different animation frames.
Upload into an inactive bank and release the old bank only after the
renderer acknowledges a complete repaint of its successor.

Correlate presentations by session, sequence and bank. Recover ownership
through readiness after heap loss, including unchanged program uploads,
and retry lost presentation replies without rewriting a visible bank.

Validate the assembled program in the vendor VM and cover host bank
ownership, coalescing, stale replies and reset recovery in regressions.
Document the two-bank presentation handshake and distinguish queued
frames, completed logical repaints and physical LED scanout. Explain
snapshot starvation and tearing symptoms so successful API replies are
not mistaken for complete display delivery.
Sleeping between receive batches delayed queued acknowledgements by up
to 200 ms and limited complete Lightpad frame delivery. Wait for input
until the next lifecycle deadline instead, processing each packet as
it arrives while retaining the existing timer cadence.

Yield cooperatively during preloaded bursts and check deadlines during
continuous traffic so neither peer tasks nor keepalives are starved.
Cover arrival between ticks, timer alignment and transport cleanup.
Match the SDK request layout by writing its 32-bit zero address before
 the requested item. The shorter packets prevented LUMI configuration
readback, obscuring the actual hardware brightness and gamma values.

Add C++ SDK packing vectors for the lowest, brightness, and highest
config IDs. Corrected live requests returned brightness 100 and gamma 0.

Co-Authored-By: Nova (Codex) <noreply@openai.com>
Firmware brightness can dim frames that already carry software scaling.
Set the LUMI hardware brightness to its SDK maximum when the renderer
initialises and answers a matching readiness challenge, including when
unchanged bytecode reconnects without running initialise again.

Keep brightness configuration knowledge inside the key renderer. Native
VM verification covers both setup paths, rejected queries, unchanged
pixel values, and preservation of the default musical key handler.

Co-Authored-By: Nova (Codex) <noreply@openai.com>
Describe the renderer's full-scale hardware brightness setting and its
reconnect behavior so clients keep dimming in their host RGB pipeline.
Clarify that the renderer leaves gamma correction unchanged.
@hyperb1iss
hyperb1iss merged commit e290e87 into main Sep 8, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant