Add a comment-injection mechanism to roundtrip.py (COMMENTS / PATCH_COMMENTS
address-keyed dicts) so annotations survive regeneration -- they live in the
script, not the generated file.
Annotated in firmware.asm:
* The router at 0xA57B: register-bank switch, event-buffer save, the
CV-source-locked early-out, the cable-number switch (cable 0 -> 0x70,
cable 1 -> 0xc3, cable 2 -> 0x82 = CV ring), the descriptor write via
0x551e, and the single common routing pass at 0xA5C0.
* The dispatcher at 0xDF05 / dispatch call at 0xDF28.
* The usb1_cv_stub at 0x8126 (each instruction) and the retargeted call.
The router's structure clarifies why re-injection is used rather than a
router patch: 0x551e only writes a 3-byte destination descriptor, and the
actual routing pass (0xA5C0+) runs once per invocation, wrapped by a
PSW push/pop -- so two destinations require two full router calls.
roundtrip.py converts the radare2 disassembly (qunexus_v2.2.1.asm) into a
single sdas8051 assembler source (firmware.asm) and verifies it re-assembles
byte-identical to the 54,279-byte stock image (base 0x2400).
31,051 of 31,346 items (99.06%) re-assemble from mnemonics; 295 are
pinned to .db where r2's display syntax doesn't round-trip through
sdas8051 (256 ajmp/acall that sdas8051 mis-encodes, 39 data-in-code).
Converges in 3 iterations.
Also adds patch_usb1_to_cv.asm (the USB-1->CV patch as a standalone
sdas8051 source) and verify_patch_asm.py (proves the assembled patch
matches the bytes written to the device over C2).
Reproduce: python3 roundtrip.py