Explorar el Código

Add linting for trailing comments and fix existing issues

Jariullah Safi hace 4 meses
padre
commit
8a8903377d

+ 5 - 3
.github/workflows/format.yml

@@ -19,7 +19,9 @@ jobs:
           sudo apt-get update
           sudo apt-get update
           sudo apt-get install -y clang-format-18
           sudo apt-get install -y clang-format-18
 
 
-      - name: Check formatting
+      - name: Check formatting and style
         # Fails the job (and blocks the PR) the moment any file differs from
         # Fails the job (and blocks the PR) the moment any file differs from
-        # the Linux kernel .clang-format style. Run `make format` to fix.
-        run: make format-check
+        # the Linux kernel .clang-format style, or a long trailing // comment
+        # is found. Run `make format` to fix formatting; move long trailing
+        # comments above their statement to satisfy the lint.
+        run: make check

+ 45 - 0
AGENTS.md

@@ -0,0 +1,45 @@
+# AGENTS.md
+
+## Code style
+
+- **Linux kernel style**, enforced by `.clang-format` (8-wide tabs, 80 columns).
+  Pinned to **clang-format 18** — other versions reformat differently and CI
+  will reject them.
+- **No long trailing comments.** clang-format can't relocate a trailing `//`
+  comment, so a long one either splits the statement or runs past 80 columns.
+  Instead put the comment on its own line(s) **above** the statement, with a
+  blank line before it for separation:
+
+  ```c
+  // why this value matters, at whatever length you need
+  static int g_foo = 0;
+  ```
+
+  not
+
+  ```c
+  static int g_foo = 0; // why this value matters, at whatever length ...
+  ```
+
+  Short trailing comments that fit within 80 columns are fine.
+
+## Before pushing
+
+```sh
+make format   # auto-format (kernel style)
+make check    # what CI gates on: format-check + trailing-comment lint
+```
+
+CI rejects a PR immediately if either fails. The lint lives in
+`tools/check-trailing-comments.py`.
+
+## Build / compile-check (no hardware needed)
+
+```sh
+arduino-cli compile -b adafruit:nrf52:feather52840 \
+  --build-property "build.extra_flags=-DNRF52840_XXAA {build.flags.usb} -DCFG_TUD_HID=4" \
+  OpenPuck
+```
+
+`OpenPuck/git_version.h` is generated by `gen_version.sh` and gitignored —
+never commit it.

+ 10 - 1
Makefile

@@ -16,7 +16,7 @@ FORMAT_FILES := $(shell find OpenPuck puck_sniffer pairtui \
 	\( -name '*.c' -o -name '*.cpp' -o -name '*.h' -o -name '*.hpp' -o -name '*.ino' \) \
 	\( -name '*.c' -o -name '*.cpp' -o -name '*.h' -o -name '*.hpp' -o -name '*.ino' \) \
 	-not -name 'git_version.h')
 	-not -name 'git_version.h')
 
 
-.PHONY: format format-check
+.PHONY: format format-check lint check
 
 
 ## Reformat all C/C++ sources in place using the Linux kernel style.
 ## Reformat all C/C++ sources in place using the Linux kernel style.
 format:
 format:
@@ -26,3 +26,12 @@ format:
 ## Used by CI to reject unformatted code.
 ## Used by CI to reject unformatted code.
 format-check:
 format-check:
 	$(CLANG_FORMAT) --dry-run --Werror $(FORMAT_FILES)
 	$(CLANG_FORMAT) --dry-run --Werror $(FORMAT_FILES)
+
+## Style rules clang-format can't enforce. Currently: a long trailing //
+## comment must be moved onto its own line above the statement (clang-format
+## won't relocate it, so it would otherwise split the code or run past 80).
+lint:
+	python3 tools/check-trailing-comments.py $(FORMAT_FILES)
+
+## Everything CI gates on.
+check: format-check lint

+ 36 - 24
OpenPuck/OpenPuck.ino

@@ -20,7 +20,9 @@
 using namespace Adafruit_LittleFS_Namespace;
 using namespace Adafruit_LittleFS_Namespace;
 
 
 #include "config.h"
 #include "config.h"
-#include "build_info.h" // OPK_GIT_HASH (used to tag the one-time factory-reset build)
+
+// OPK_GIT_HASH (used to tag the one-time factory-reset build)
+#include "build_info.h"
 #include "identity.h"
 #include "identity.h"
 #include "bonds.h"
 #include "bonds.h"
 #include "radio.h"
 #include "radio.h"
@@ -37,14 +39,16 @@ using namespace Adafruit_LittleFS_Namespace;
 #error "build with -DCFG_TUD_HID=4 (extra_flags): up to 4 HID interfaces per mode"
 #error "build with -DCFG_TUD_HID=4 (extra_flags): up to 4 HID interfaces per mode"
 #endif
 #endif
 
 
-static uint8_t g_usbCfgDesc
-	[512]; // puck composite (4 HID + WebUSB) exceeds the default 256 B config buffer
+// puck composite (4 HID + WebUSB) exceeds the default 256 B config buffer
+static uint8_t g_usbCfgDesc[512];
 
 
 void setup()
 void setup()
 {
 {
 	genSerial();
 	genSerial();
 	ledInit();
 	ledInit();
-	rfGenSessionAddr(); // per-device unique RF session address (advertised in the host frame; isolates pucks)
+
+	// per-device unique RF session address (advertised in the host frame; isolates pucks)
+	rfGenSessionAddr();
 	InternalFS.begin();
 	InternalFS.begin();
 #if OPK_FACTORY_RESET
 #if OPK_FACTORY_RESET
 	// Recovery build (-DOPK_FACTORY_RESET=1): wipe ALL persistent storage ONCE on the first boot after flashing,
 	// Recovery build (-DOPK_FACTORY_RESET=1): wipe ALL persistent storage ONCE on the first boot after flashing,
@@ -52,8 +56,9 @@ void setup()
 	factoryResetOnce(OPK_GIT_HASH);
 	factoryResetOnce(OPK_GIT_HASH);
 #endif
 #endif
 	loadCfg();
 	loadCfg();
-	g_xbox = !modeIsPuck(
-		g_usbMode); // load persisted config + decide USB presentation BEFORE registering interfaces
+
+	// load persisted config + decide USB presentation BEFORE registering interfaces
+	g_xbox = !modeIsPuck(g_usbMode);
 	g_active = controllerFor(g_usbMode);
 	g_active = controllerFor(g_usbMode);
 
 
 	// ---- USB descriptor rebuild ----
 	// ---- USB descriptor rebuild ----
@@ -74,14 +79,15 @@ void setup()
 	USBDevice.detach();
 	USBDevice.detach();
 	delay(30);
 	delay(30);
 	if (keepCdc) {
 	if (keepCdc) {
-		USBDevice.setConfigurationBuffer(
-			g_usbCfgDesc,
-			sizeof g_usbCfgDesc); // keep boot CDC composite (debug boot)
+		// keep boot CDC composite (debug boot)
+		USBDevice.setConfigurationBuffer(g_usbCfgDesc,
+						 sizeof g_usbCfgDesc);
 	} else {
 	} else {
 		USBDevice.clearConfiguration();
 		USBDevice.clearConfiguration();
-		USBDevice.setConfigurationBuffer(
-			g_usbCfgDesc,
-			sizeof g_usbCfgDesc); // headroom over the default 256 B cap
+
+		// headroom over the default 256 B cap
+		USBDevice.setConfigurationBuffer(g_usbCfgDesc,
+						 sizeof g_usbCfgDesc);
 	}
 	}
 
 
 	// Distinct USB serial PER MODE (must be set AFTER clearConfiguration, which nulls it). Hosts cache USB
 	// Distinct USB serial PER MODE (must be set AFTER clearConfiguration, which nulls it). Hosts cache USB
@@ -105,7 +111,8 @@ void setup()
 	if (puckMode && !keepCdc)
 	if (puckMode && !keepCdc)
 		wakeHidBegin();
 		wakeHidBegin();
 
 
-	g_active->begin(); // register this mode's USB interface(s) + set VID/PID/strings
+	// register this mode's USB interface(s) + set VID/PID/strings
+	g_active->begin();
 
 
 	// Boot-mouse wake interface for clean (non-puck) modes, and for puck on the one-shot debug boot (CDC on,
 	// Boot-mouse wake interface for clean (non-puck) modes, and for puck on the one-shot debug boot (CDC on,
 	// no endpoint room for wake mouse on a normal puck boot -- wake is registered above instead). Skipped for PS
 	// no endpoint room for wake mouse on a normal puck boot -- wake is registered above instead). Skipped for PS
@@ -118,10 +125,11 @@ void setup()
 	if (!puckMode && !psClean)
 	if (!puckMode && !psClean)
 		usb_web.begin();
 		usb_web.begin();
 	// Enable USB Remote Wakeup (bit 5) so the host lets us signal wake-from-sleep. Bit 7 is always required.
 	// Enable USB Remote Wakeup (bit 5) so the host lets us signal wake-from-sleep. Bit 7 is always required.
-	USBDevice.setConfigurationAttribute(
-		0x80 |
-		0x20); // bmAttributes: required(0x80) | remote_wakeup(0x20)
-	USBDevice.attach(); // re-connect with the final descriptor (host re-reads it fresh -> deterministic enumeration)
+	// bmAttributes: required(0x80) | remote_wakeup(0x20)
+	USBDevice.setConfigurationAttribute(0x80 | 0x20);
+
+	// re-connect with the final descriptor (host re-reads it fresh -> deterministic enumeration)
+	USBDevice.attach();
 	Serial.begin(115200);
 	Serial.begin(115200);
 	for (int i = 0; i < 300 && !USBDevice.mounted(); i++)
 	for (int i = 0; i < 300 && !USBDevice.mounted(); i++)
 		delay(10); // wait up to 3s for USB mount, but NEVER hang
 		delay(10); // wait up to 3s for USB mount, but NEVER hang
@@ -130,7 +138,9 @@ void setup()
 		ledWakePulse();
 		ledWakePulse();
 	} // wake host if bus was sleeping when we (re-)attached
 	} // wake host if bus was sleeping when we (re-)attached
 	loadBonds();
 	loadBonds();
-	hapticInit(); // clear relay/active flags + arm the reconnect block & initial stop burst
+
+	// clear relay/active flags + arm the reconnect block & initial stop burst
+	hapticInit();
 	static const char *MODE_NAME[] = {
 	static const char *MODE_NAME[] = {
 		"STEAM(puck)",	       "XBOX(xinput+mouse)",
 		"STEAM(puck)",	       "XBOX(xinput+mouse)",
 		"SWITCH(horipad)",     "LIZARD(puck kb/mouse)",
 		"SWITCH(horipad)",     "LIZARD(puck kb/mouse)",
@@ -164,15 +174,17 @@ void setup()
 // loop-timing diagnostics: the poll rate is capped by the loop ITERATION time (the pacing wants 4000us but a
 // loop-timing diagnostics: the poll rate is capped by the loop ITERATION time (the pacing wants 4000us but a
 // slow loop fires the poll only as often as it comes around). Per-section us is accumulated and, each second,
 // slow loop fires the poll only as often as it comes around). Per-section us is accumulated and, each second,
 // the average loop period + the slowest section are published for the WebUSB panel to pinpoint the culprit.
 // the average loop period + the slowest section are published for the WebUSB panel to pinpoint the culprit.
-uint16_t g_loopPeriodUs =
-	0; // avg loop iteration time last second (1e6/iterations)
-uint8_t g_loopWorst =
-	0; // index of the slowest section: 0=webusb 1=ctrl.task 2=serial 3=rfdiag 4=rflink 5=haptic 6=led
+
+// avg loop iteration time last second (1e6/iterations)
+uint16_t g_loopPeriodUs = 0;
+
+// index of the slowest section: 0=webusb 1=ctrl.task 2=serial 3=rfdiag 4=rflink 5=haptic 6=led
+uint8_t g_loopWorst = 0;
 uint16_t g_loopWorstUs = 0; // that section's avg us per iteration
 uint16_t g_loopWorstUs = 0; // that section's avg us per iteration
 void loop()
 void loop()
 {
 {
-	NRF_WDT->RR[0] =
-		WDT_RR_RR_Reload; // feed the watchdog each loop; if we ever stop, the ~8s WDT auto-resets us
+	// feed the watchdog each loop; if we ever stop, the ~8s WDT auto-resets us
+	NRF_WDT->RR[0] = WDT_RR_RR_Reload;
 	if (g_dirty) {
 	if (g_dirty) {
 		g_dirty = false;
 		g_dirty = false;
 		saveBonds();
 		saveBonds();

+ 2 - 2
OpenPuck/bonds.h

@@ -19,8 +19,8 @@ struct Slot {
 };
 };
 extern Slot g_slot[NSLOT];
 extern Slot g_slot[NSLOT];
 
 
-extern int
-	g_connSlot; // bonded slot being polled (== the USB-connected slot); -1 = none
+// bonded slot being polled (== the USB-connected slot); -1 = none
+extern int g_connSlot;
 extern unsigned long
 extern unsigned long
 	g_connReplyMs; // millis of last RF reply (link-alive timestamp)
 	g_connReplyMs; // millis of last RF reply (link-alive timestamp)
 extern volatile bool g_dirty; // bonds changed -> flush to flash from loop()
 extern volatile bool g_dirty; // bonds changed -> flush to flash from loop()

+ 16 - 13
OpenPuck/config.cpp

@@ -13,26 +13,27 @@ bool g_persistMode = false;
 uint8_t g_bootMode = 0xFF;
 uint8_t g_bootMode = 0xFF;
 
 
 bool g_debugCdcThisBoot = false;
 bool g_debugCdcThisBoot = false;
-static uint8_t g_debugCdc =
-	0; // persisted one-shot arm, stored in Cfg.rsvd0 (1 = keep CDC for the next boot)
+
+// persisted one-shot arm, stored in Cfg.rsvd0 (1 = keep CDC for the next boot)
+static uint8_t g_debugCdc = 0;
 
 
 int g_mDiv = 64, g_mFric = 94;
 int g_mDiv = 64, g_mFric = 94;
 uint8_t g_abSwap = 0;
 uint8_t g_abSwap = 0;
 uint8_t g_back[4] = {
 uint8_t g_back[4] = {
 	5, 6, 7, 8
 	5, 6, 7, 8
 }; // L4->LB R4->RB L5->L3 R5->R3 (0..11 buttons, 12..15 D-pad U/D/L/R)
 }; // L4->LB R4->RB L5->L3 R5->R3 (0..11 buttons, 12..15 D-pad U/D/L/R)
-uint8_t g_qamMap =
-	0; // 0 = default (unmapped, uses hardcoded behavior per mode)
-uint8_t g_rumbleScale =
-	200; // rumble strength % (200 = double); adjustable from the WebUSB panel
+// 0 = default (unmapped, uses hardcoded behavior per mode)
+uint8_t g_qamMap = 0;
+// rumble strength % (200 = double); adjustable from the WebUSB panel
+uint8_t g_rumbleScale = 200;
 
 
 // poll rate is fixed. Faster than the controller can refresh wastes airtime; slower adds latency. Any rate
 // poll rate is fixed. Faster than the controller can refresh wastes airtime; slower adds latency. Any rate
 // persisted by an older build is ignored and overwritten with the default on boot (see loadCfg).
 // persisted by an older build is ignored and overwritten with the default on boot (see loadCfg).
 const uint32_t g_pollUs = POLL_US_DEFAULT;
 const uint32_t g_pollUs = POLL_US_DEFAULT;
 
 
 #define CFG_FILE "/cfg.bin"
 #define CFG_FILE "/cfg.bin"
-#define CFG_MAGIC \
-	0xC9 // bumped (rumbleScale): old cfg ignored -> clean defaults on first boot
+// bumped (rumbleScale): old cfg ignored -> clean defaults on first boot
+#define CFG_MAGIC 0xC9
 struct Cfg {
 struct Cfg {
 	uint8_t magic, mode, mDiv, mFric, rsvd0, abSwap, back[4], pollU100,
 	uint8_t magic, mode, mDiv, mFric, rsvd0, abSwap, back[4], pollU100,
 		persistMode, bootMode, chordBtn[3], qamMap, rumbleScale;
 		persistMode, bootMode, chordBtn[3], qamMap, rumbleScale;
@@ -104,15 +105,17 @@ void loadCfg()
 							c.chordBtn[i] :
 							c.chordBtn[i] :
 							CHORD_DEF[i];
 							CHORD_DEF[i];
 			g_qamMap = c.qamMap;
 			g_qamMap = c.qamMap;
-			g_rumbleScale =
-				c.rumbleScale; // 0 is a valid setting (rumble off)
+
+			// 0 is a valid setting (rumble off)
+			g_rumbleScale = c.rumbleScale;
 		}
 		}
 		f.close();
 		f.close();
 	}
 	}
+	// clear the one-shot so the NEXT cold boot reverts to the default/persist policy
 	if (consume) {
 	if (consume) {
 		g_bootMode = 0xFF;
 		g_bootMode = 0xFF;
 		saveCfg();
 		saveCfg();
-	} // clear the one-shot so the NEXT cold boot reverts to the default/persist policy
+	}
 }
 }
 
 
 void saveMode(uint8_t m)
 void saveMode(uint8_t m)
@@ -140,8 +143,8 @@ void armDebugCdcNextBoot()
 // every call site (serial "ERASE-ALL", WebUSB op 0x0A magic, both with on-screen warnings).
 // every call site (serial "ERASE-ALL", WebUSB op 0x0A magic, both with on-screen warnings).
 void factoryErase()
 void factoryErase()
 {
 {
-	InternalFS
-		.begin(); // ensure mounted before we reformat (no-op if already up)
+	// ensure mounted before we reformat (no-op if already up)
+	InternalFS.begin();
 	InternalFS.format(); // wipes cfg.bin + bonds.bin + the entire FS
 	InternalFS.format(); // wipes cfg.bin + bonds.bin + the entire FS
 }
 }
 
 

+ 37 - 33
OpenPuck/config.h

@@ -35,14 +35,14 @@
 #define MODE_LIZARD \
 #define MODE_LIZARD \
 	3 // Puck HID; always keyboard+mouse (ignores Steam heartbeat)
 	3 // Puck HID; always keyboard+mouse (ignores Steam heartbeat)
 #define MODE_SW_PRO 4 // Nintendo Switch Pro Controller (057E:2009) + gyro
 #define MODE_SW_PRO 4 // Nintendo Switch Pro Controller (057E:2009) + gyro
-#define MODE_PS5 \
-	5 // Sony DualSense (054C:0CE6) + gyro + split trackpad (+ wake mouse + WebUSB panel)
-#define MODE_HIDGYRO \
-	6 // DS4-layout generic HID gamepad + gyro (+ wake mouse + WebUSB panel)
-#define MODE_PS5_GAME \
-	7 // DualSense, CLEAN single-HID (no wake/WebUSB) so PC games classify it as PlayStation (Fortnite)
-#define MODE_DS4_GAME \
-	8 // DS4, CLEAN single-HID (no wake/WebUSB) for game PlayStation classification
+// Sony DualSense (054C:0CE6) + gyro + split trackpad (+ wake mouse + WebUSB panel)
+#define MODE_PS5 5
+// DS4-layout generic HID gamepad + gyro (+ wake mouse + WebUSB panel)
+#define MODE_HIDGYRO 6
+// DualSense, CLEAN single-HID (no wake/WebUSB) so PC games classify it as PlayStation (Fortnite)
+#define MODE_PS5_GAME 7
+// DS4, CLEAN single-HID (no wake/WebUSB) for game PlayStation classification
+#define MODE_DS4_GAME 8
 #define MODE_MAX 8
 #define MODE_MAX 8
 
 
 // The two "game" personalities drop the wake-mouse + WebUSB interfaces so the device is a genuine single-HID PS
 // The two "game" personalities drop the wake-mouse + WebUSB interfaces so the device is a genuine single-HID PS
@@ -63,50 +63,54 @@ static inline bool modeValid(uint8_t m)
 }
 }
 
 
 extern uint8_t g_usbMode; // loaded from flash at boot
 extern uint8_t g_usbMode; // loaded from flash at boot
-extern bool
-	g_xbox; // true for all non-puck presentations (cached !modeIsPuck(g_usbMode))
+
+// true for all non-puck presentations (cached !modeIsPuck(g_usbMode))
+extern bool g_xbox;
 extern uint8_t g_chordBtn[3]; // back4+B/X/Y -> these modes (A always STEAM)
 extern uint8_t g_chordBtn[3]; // back4+B/X/Y -> these modes (A always STEAM)
 
 
 // Mode persistence policy: by DEFAULT every fresh power-on/reconnect lands in STEAM mode (0). An explicit
 // Mode persistence policy: by DEFAULT every fresh power-on/reconnect lands in STEAM mode (0). An explicit
 // mode switch still works for the session via a ONE-SHOT bootMode (honored once, then cleared, so the next
 // mode switch still works for the session via a ONE-SHOT bootMode (honored once, then cleared, so the next
 // cold boot reverts to Steam). The WebUI "persist last mode" toggle (g_persistMode) instead remembers the
 // cold boot reverts to Steam). The WebUI "persist last mode" toggle (g_persistMode) instead remembers the
 // last selected mode across reboots.
 // last selected mode across reboots.
-extern bool
-	g_persistMode; // false (default) = always boot Steam; true = boot into last mode
-extern uint8_t
-	g_bootMode; // one-shot: boot into this mode once then clear (!persistMode + explicit switch)
+// false (default) = always boot Steam; true = boot into last mode
+extern bool g_persistMode;
+// one-shot: boot into this mode once then clear (!persistMode + explicit switch)
+extern uint8_t g_bootMode;
 
 
 // One-shot debug CDC. Puck mode normally DROPS the CDC serial console to free the USB endpoint its wake-mouse
 // One-shot debug CDC. Puck mode normally DROPS the CDC serial console to free the USB endpoint its wake-mouse
 // interface needs (so puck can wake a sleeping Windows host). Arming this keeps CDC for the NEXT boot only --
 // interface needs (so puck can wake a sleeping Windows host). Arming this keeps CDC for the NEXT boot only --
 // dropping the wake mouse that boot -- so someone can attach the serial debugger; the boot after reverts to
 // dropping the wake mouse that boot -- so someone can attach the serial debugger; the boot after reverts to
 // normal automatically. Mirrors the g_bootMode one-shot. Armed from the WebUSB panel or CDC 'D' command.
 // normal automatically. Mirrors the g_bootMode one-shot. Armed from the WebUSB panel or CDC 'D' command.
-extern bool
-	g_debugCdcThisBoot; // decision for THIS boot: true => keep CDC, skip the wake interface
+// decision for THIS boot: true => keep CDC, skip the wake interface
+extern bool g_debugCdcThisBoot;
 void armDebugCdcNextBoot(); // persist the one-shot (caller reboots)
 void armDebugCdcNextBoot(); // persist the one-shot (caller reboots)
 
 
 // persisted, runtime-tunable config:
 // persisted, runtime-tunable config:
 extern int g_mDiv, g_mFric; // xbox/lizard mouse sensitivity divisor / friction%
 extern int g_mDiv, g_mFric; // xbox/lizard mouse sensitivity divisor / friction%
 extern uint8_t g_abSwap; // 1 = swap A/B and X/Y (Nintendo face-button layout)
 extern uint8_t g_abSwap; // 1 = swap A/B and X/Y (Nintendo face-button layout)
-extern uint8_t g_back
-	[4]; // back paddles L4,R4,L5,R5 -> button codes (0..15 standard, 16=PS Touch Click, 17=PS5 Mute)
-extern uint8_t
-	g_qamMap; // QAM (3 dots) physical button -> same code space (0 = default/unmapped)
-extern uint8_t
-	g_rumbleScale; // rumble strength, percent of decoded amplitude (100 = 1x, 200 = 2x default), all modes
+
+// back paddles L4,R4,L5,R5 -> button codes (0..15 standard, 16=PS Touch Click, 17=PS5 Mute)
+extern uint8_t g_back[4];
+// QAM (3 dots) physical button -> same code space (0 = default/unmapped)
+extern uint8_t g_qamMap;
+// rumble strength, percent of decoded amplitude (100 = 1x, 200 = 2x default), all modes
+extern uint8_t g_rumbleScale;
 // Switch Pro motion settings. Persisted in their OWN flash file (mode_switch_pro.cpp), NOT in Cfg -- so changing
 // Switch Pro motion settings. Persisted in their OWN flash file (mode_switch_pro.cpp), NOT in Cfg -- so changing
 // them never resets the rest of the config. Set from the WebUSB panel.
 // them never resets the rest of the config. Set from the WebUSB panel.
-extern uint8_t
-	g_swProRate; // Switch Pro report cadence: 0 = 66Hz (15ms, compat), 1 = 120Hz (8ms, DEFAULT), 2 = full (~250Hz)
-extern uint8_t
-	g_swGyroScale10; // Switch Pro gyro sensitivity x10 (10 = 1.0x default; 5/15/20/25/30 = 0.5..3.0x)
-void swProSaveCfg(); // persist g_swProRate + g_swGyroScale10 to their flash file
+// Switch Pro report cadence: 0 = 66Hz (15ms, compat), 1 = 120Hz (8ms, DEFAULT), 2 = full (~250Hz)
+extern uint8_t g_swProRate;
+// Switch Pro gyro sensitivity x10 (10 = 1.0x default; 5/15/20/25/30 = 0.5..3.0x)
+extern uint8_t g_swGyroScale10;
+
+// persist g_swProRate + g_swGyroScale10 to their flash file
+void swProSaveCfg();
 
 
-#define POLL_US_DEFAULT \
-	4000u // 250 Hz -- matches SC2 input report rate (1000000/250 = 4000 us)
-#define USB_STREAM_MS \
-	4u // host-side HID stream cadence for translated modes (~250 Hz)
-extern const uint32_t
-	g_pollUs; // RF poll cadence (us). FIXED -- not configurable (see loadCfg).
+// 250 Hz -- matches SC2 input report rate (1000000/250 = 4000 us)
+#define POLL_US_DEFAULT 4000u
+// host-side HID stream cadence for translated modes (~250 Hz)
+#define USB_STREAM_MS 4u
+// RF poll cadence (us). FIXED -- not configurable (see loadCfg).
+extern const uint32_t g_pollUs;
 
 
 // loop-timing diagnostics (defined in OpenPuck.ino) -- surfaced in the WebUSB status blob to find what caps
 // loop-timing diagnostics (defined in OpenPuck.ino) -- surfaced in the WebUSB status blob to find what caps
 // the poll rate: avg loop period, slowest section index, and that section's avg us/iteration.
 // the poll rate: avg loop period, slowest section index, and that section's avg us/iteration.

+ 6 - 2
OpenPuck/controllers.cpp

@@ -30,9 +30,13 @@ IController *controllerFor(uint8_t mode)
 	case MODE_HIDGYRO:
 	case MODE_HIDGYRO:
 		return &g_hidGyroCtl;
 		return &g_hidGyroCtl;
 	case MODE_PS5_GAME:
 	case MODE_PS5_GAME:
-		return &g_ps5Ctl; // same DualSense controller; setup() drops wake/WebUSB for clean enum
+
+		// same DualSense controller; setup() drops wake/WebUSB for clean enum
+		return &g_ps5Ctl;
 	case MODE_DS4_GAME:
 	case MODE_DS4_GAME:
-		return &g_hidGyroCtl; // same DS4 controller; setup() drops wake/WebUSB for clean enum
+
+		// same DS4 controller; setup() drops wake/WebUSB for clean enum
+		return &g_hidGyroCtl;
 	default:
 	default:
 		return &g_steamPuck;
 		return &g_steamPuck;
 	}
 	}

+ 3 - 1
OpenPuck/controllers.h

@@ -42,9 +42,11 @@ class IController {
 	virtual void task()
 	virtual void task()
 	{
 	{
 	}
 	}
+
+	// a wake gesture fired while suspended; queue any post-resume input the host needs to actually wake
 	virtual void wakeEvent()
 	virtual void wakeEvent()
 	{
 	{
-	} // a wake gesture fired while suspended; queue any post-resume input the host needs to actually wake
+	}
 	virtual bool isPuck() const
 	virtual bool isPuck() const
 	{
 	{
 		return false;
 		return false;

+ 72 - 55
OpenPuck/haptics.cpp

@@ -2,7 +2,9 @@
 #include "bonds.h"
 #include "bonds.h"
 #include "config.h"
 #include "config.h"
 #include "rf_link.h"
 #include "rf_link.h"
-#include <Adafruit_TinyUSB.h> // USBDevice.suspended() -> autonomous controller power-off on host sleep
+
+// USBDevice.suspended() -> autonomous controller power-off on host sleep
+#include <Adafruit_TinyUSB.h>
 #include <Arduino.h>
 #include <Arduino.h>
 #include <string.h>
 #include <string.h>
 
 
@@ -29,18 +31,24 @@ void hapticSendShutdown()
 		relayEnqueue(0x9F, OFF, sizeof OFF);
 		relayEnqueue(0x9F, OFF, sizeof OFF);
 }
 }
 
 
-static unsigned long g_haptic82Ms =
-	0; // millis of last 0x82 haptic OUTPUT relayed (Steam mode)
-static bool g_haptic82On =
-	false; // a non-zero 0x82 haptic is currently active (awaiting host stop)
-static unsigned long g_rumble80Ms =
-	0; // millis of last translated host rumble (0x80)
-static bool g_rumble80On =
-	false; // Steam/Triton rumble is latched on until an explicit zero report
-static unsigned long g_reinitAt =
-	0; // when to fire the next post-reconnect haptic re-init (0 = none scheduled)
-static uint8_t g_reinitLeft =
-	0; // how many re-init shots remain in this connect window
+// millis of last 0x82 haptic OUTPUT relayed (Steam mode)
+static unsigned long g_haptic82Ms = 0;
+
+// a non-zero 0x82 haptic is currently active (awaiting host stop)
+static bool g_haptic82On = false;
+
+// millis of last translated host rumble (0x80)
+static unsigned long g_rumble80Ms = 0;
+
+// Steam/Triton rumble is latched on until an explicit zero report
+static bool g_rumble80On = false;
+
+// when to fire the next post-reconnect haptic re-init (0 = none scheduled)
+static unsigned long g_reinitAt = 0;
+
+// how many re-init shots remain in this connect window
+static uint8_t g_reinitLeft = 0;
+
 // haptic activity happened -> arm a clear once it goes idle (catches a
 // haptic activity happened -> arm a clear once it goes idle (catches a
 // latch that engaged during/after use, even seconds after connect)
 // latch that engaged during/after use, even seconds after connect)
 static bool g_hapClearArmed = false;
 static bool g_hapClearArmed = false;
@@ -52,8 +60,8 @@ struct RelayMsg {
 	uint8_t rid, len;
 	uint8_t rid, len;
 	uint8_t data[RELAY_MAXP];
 	uint8_t data[RELAY_MAXP];
 };
 };
-#define RELAY_QLEN \
-	32 // deep enough to hold a full Steam settings/LED transaction burst without loss
+// deep enough to hold a full Steam settings/LED transaction burst without loss
+#define RELAY_QLEN 32
 static RelayMsg g_rq[RELAY_QLEN];
 static RelayMsg g_rq[RELAY_QLEN];
 static volatile uint8_t
 static volatile uint8_t
 	g_rqHead = 0,
 	g_rqHead = 0,
@@ -69,8 +77,9 @@ bool relayPending()
 }
 }
 bool relayEnqueue(uint8_t rid, const uint8_t *payload, uint8_t plen)
 bool relayEnqueue(uint8_t rid, const uint8_t *payload, uint8_t plen)
 {
 {
+	// 60B is the RF frame ceiling; longer can't be relayed
 	if (plen > RELAY_MAXP)
 	if (plen > RELAY_MAXP)
-		plen = RELAY_MAXP; // 60B is the RF frame ceiling; longer can't be relayed
+		plen = RELAY_MAXP;
 	uint32_t pm = __get_PRIMASK();
 	uint32_t pm = __get_PRIMASK();
 	__disable_irq();
 	__disable_irq();
 	uint8_t h = g_rqHead, nx = rqNext(h);
 	uint8_t h = g_rqHead, nx = rqNext(h);
@@ -131,7 +140,8 @@ void hapLogAdd(uint8_t slot, uint8_t rid, const uint8_t *b, uint16_t n)
 	__set_PRIMASK(pm);
 	__set_PRIMASK(pm);
 }
 }
 void hapticDumpLog()
 void hapticDumpLog()
-{ // dump the captured history (oldest->newest) over CDC for the haptic-whine hunt
+{
+	// dump the captured history (oldest->newest) over CDC for the haptic-whine hunt
 	const uint16_t N = HAPLOG_N;
 	const uint16_t N = HAPLOG_N;
 	uint32_t now = millis();
 	uint32_t now = millis();
 	Serial.printf("# --- capture history (now=%lu, connSlot=%d) ---\n",
 	Serial.printf("# --- capture history (now=%lu, connSlot=%d) ---\n",
@@ -152,12 +162,10 @@ void hapticDumpLog()
 //      resetDrain(true)=from the OLDEST entry (dump the whole ring from boot). pull skips empty slots. ----
 //      resetDrain(true)=from the OLDEST entry (dump the whole ring from boot). pull skips empty slots. ----
 void hapLogResetDrain(bool fromBoot)
 void hapLogResetDrain(bool fromBoot)
 {
 {
-	g_hapTail =
-		fromBoot ?
-			(uint16_t)((g_hapHead + 1) %
-				   HAPLOG_N) // oldest slot (the next-to-overwrite holds it)
-			:
-			g_hapHead; // "now"
+	// oldest slot (the next-to-overwrite holds it)
+	g_hapTail = fromBoot ? (uint16_t)((g_hapHead + 1) % HAPLOG_N) :
+			       // "now"
+			       g_hapHead;
 }
 }
 bool hapLogPull(uint32_t *logMs, uint8_t *slot, uint8_t *rid, uint8_t *n,
 bool hapLogPull(uint32_t *logMs, uint8_t *slot, uint8_t *rid, uint8_t *n,
 		uint8_t bytes16[16])
 		uint8_t bytes16[16])
@@ -200,11 +208,13 @@ static bool haptic82PayloadOn(const uint8_t *p, uint16_t n)
 		return false;
 		return false;
 	for (uint16_t i = 2; i < n; i++)
 	for (uint16_t i = 2; i < n; i++)
 		if (p[i])
 		if (p[i])
-			return true; // observed form is [01 01 gain], but treat any trailing non-zero as active
+			// observed form is [01 01 gain], but treat any trailing non-zero as active
+			return true;
 	return false;
 	return false;
 }
 }
 static void hapticCancelPendingOn()
 static void hapticCancelPendingOn()
-{ // void queued ON entries (stale Steam haptics / translated rumble across a reconnect)
+{
+	// void queued ON entries (stale Steam haptics / translated rumble across a reconnect)
 	uint32_t pm = __get_PRIMASK();
 	uint32_t pm = __get_PRIMASK();
 	__disable_irq();
 	__disable_irq();
 	for (uint8_t i = g_rqTail; i != g_rqHead; i = rqNext(i)) {
 	for (uint8_t i = g_rqTail; i != g_rqHead; i = rqNext(i)) {
@@ -237,8 +247,9 @@ void haptic82HostReport(const uint8_t *p, uint16_t n)
 	if (n < 3)
 	if (n < 3)
 		return;
 		return;
 	g_haptic82Ms = millis();
 	g_haptic82Ms = millis();
-	g_hapClearArmed =
-		true; // any haptic activity arms a clear when it next goes idle (kills a latch from this use)
+
+	// any haptic activity arms a clear when it next goes idle (kills a latch from this use)
+	g_hapClearArmed = true;
 	// Track on/off only. Do NOT synthesize a stop burst when Steam's own stop arrives: that stop is already
 	// Track on/off only. Do NOT synthesize a stop burst when Steam's own stop arrives: that stop is already
 	// forwarded verbatim, so adding 0x82-zero frames on top just makes the controller see the stop several times
 	// forwarded verbatim, so adding 0x82-zero frames on top just makes the controller see the stop several times
 	// over. Each 0x82 is a discrete pad click, so the extra frames are exactly the spurious end-of-movement
 	// over. Each 0x82 is a discrete pad click, so the extra frames are exactly the spurious end-of-movement
@@ -247,8 +258,8 @@ void haptic82HostReport(const uint8_t *p, uint16_t n)
 }
 }
 bool hapticSteamRumble(uint16_t lowFreq, uint16_t highFreq)
 bool hapticSteamRumble(uint16_t lowFreq, uint16_t highFreq)
 {
 {
-	if (g_rumbleScale !=
-	    100) { // user rumble-strength scale (percent; 200 = double). Clamp to 16-bit.
+	// user rumble-strength scale (percent; 200 = double). Clamp to 16-bit.
+	if (g_rumbleScale != 100) {
 		uint32_t l = (uint32_t)lowFreq * g_rumbleScale / 100,
 		uint32_t l = (uint32_t)lowFreq * g_rumbleScale / 100,
 			 h = (uint32_t)highFreq * g_rumbleScale / 100;
 			 h = (uint32_t)highFreq * g_rumbleScale / 100;
 		lowFreq = (l > 0xFFFF) ? 0xFFFF : (uint16_t)l;
 		lowFreq = (l > 0xFFFF) ? 0xFFFF : (uint16_t)l;
@@ -265,8 +276,9 @@ bool hapticSteamRumble(uint16_t lowFreq, uint16_t highFreq)
 	// We map conventional gamepad low/high-frequency motors to left/right speeds and use max as intensity.
 	// We map conventional gamepad low/high-frequency motors to left/right speeds and use max as intensity.
 	uint16_t intensity = lowFreq > highFreq ? lowFreq : highFreq;
 	uint16_t intensity = lowFreq > highFreq ? lowFreq : highFreq;
 	uint8_t p[9];
 	uint8_t p[9];
-	p[0] = on ? 0x04 :
-		    0x00; // haptic_type_t::HAPTIC_TYPE_RUMBLE; 0 is the off/zero report
+
+	// haptic_type_t::HAPTIC_TYPE_RUMBLE; 0 is the off/zero report
+	p[0] = on ? 0x04 : 0x00;
 	p[1] = (uint8_t)(intensity & 0xFF);
 	p[1] = (uint8_t)(intensity & 0xFF);
 	p[2] = (uint8_t)(intensity >> 8);
 	p[2] = (uint8_t)(intensity >> 8);
 	p[3] = (uint8_t)(lowFreq & 0xFF);
 	p[3] = (uint8_t)(lowFreq & 0xFF);
@@ -300,7 +312,9 @@ void rfConnFlushRelay(uint8_t ch, uint8_t s1)
 {
 {
 	while (g_rqTail != g_rqHead) {
 	while (g_rqTail != g_rqHead) {
 		RelayMsg &m = g_rq[g_rqTail];
 		RelayMsg &m = g_rq[g_rqTail];
-		if (m.rid) { // rid 0 = entry voided by hapticCancelPendingOn -> skip, take the next
+
+		// rid 0 = entry voided by hapticCancelPendingOn -> skip, take the next
+		if (m.rid) {
 			uint8_t rl = m.len;
 			uint8_t rl = m.len;
 			if (rl > RELAY_MAXP)
 			if (rl > RELAY_MAXP)
 				rl = RELAY_MAXP;
 				rl = RELAY_MAXP;
@@ -337,17 +351,19 @@ void rfConnFlushRelay(uint8_t ch, uint8_t s1)
 				memcpy(p + 4, m.data, rl);
 				memcpy(p + 4, m.data, rl);
 				plen = (uint8_t)(4 + rl);
 				plen = (uint8_t)(4 + rl);
 			}
 			}
-			hapLogAdd(
-				0xFE, m.rid, m.data,
-				rl); // log what we actually TX to the controller (slot 0xFE) for the buzz hunt
-			g_rqTail = rqNext(
-				g_rqTail); // copied out -> release the slot before the TX
+			// log what we actually TX to the controller (slot 0xFE) for the buzz hunt
+			hapLogAdd(0xFE, m.rid, m.data, rl);
+
+			// copied out -> release the slot before the TX
+			g_rqTail = rqNext(g_rqTail);
 			// s1 carries a PID distinct from the GET poll that follows (caller cycles it), so the controller's ESB
 			// s1 carries a PID distinct from the GET poll that follows (caller cycles it), so the controller's ESB
 			// dedup never mistakes the GET for a retransmit of this relay. 80us RX window: the relay is NO-ACK, the
 			// dedup never mistakes the GET for a retransmit of this relay. 80us RX window: the relay is NO-ACK, the
 			// controller sends nothing back, so don't burn the full ~1.2ms reply window (that was halving the poll
 			// controller sends nothing back, so don't burn the full ~1.2ms reply window (that was halving the poll
 			// rate during haptics).
 			// rate during haptics).
 			rfConnTx(ch, s1, p, plen, 80);
 			rfConnTx(ch, s1, p, plen, 80);
-			return; // ONE relay per poll cycle (matches the real puck's pacing)
+
+			// ONE relay per poll cycle (matches the real puck's pacing)
+			return;
 		}
 		}
 		g_rqTail = rqNext(g_rqTail);
 		g_rqTail = rqNext(g_rqTail);
 	}
 	}
@@ -378,13 +394,12 @@ void hapticReinit()
 	static const uint8_t T81B[] = {
 	static const uint8_t T81B[] = {
 		0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
 		0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
 	};
 	};
-	relayEnqueue(
-		0x81, nullptr,
-		0); // reset action (FUN_0001f554) -- Steam sends this first
+	// reset action (FUN_0001f554) -- Steam sends this first
+	relayEnqueue(0x81, nullptr, 0);
 	relayEnqueue(0x87, H30, sizeof H30);
 	relayEnqueue(0x87, H30, sizeof H30);
-	relayEnqueue(
-		0x87, H18,
-		sizeof H18); // haptic config (enabled/amplifier/gain): the part that clears a latch
+
+	// haptic config (enabled/amplifier/gain): the part that clears a latch
+	relayEnqueue(0x87, H18, sizeof H18);
 	relayEnqueue(0x87, H35, sizeof H35);
 	relayEnqueue(0x87, H35, sizeof H35);
 	relayEnqueue(0x81, T81A, sizeof T81A);
 	relayEnqueue(0x81, T81A, sizeof T81A);
 	relayEnqueue(0x81, T81B, sizeof T81B);
 	relayEnqueue(0x81, T81B, sizeof T81B);
@@ -394,9 +409,8 @@ void hapticInit()
 	g_rqHead = g_rqTail = 0;
 	g_rqHead = g_rqTail = 0;
 	g_haptic82On = false;
 	g_haptic82On = false;
 	g_rumble80On = false;
 	g_rumble80On = false;
-	g_hapticBlockUntil =
-		millis() +
-		HAPTIC_RECONNECT_BLOCK_MS; // boot: block stale Steam 0x82 until link stable
+	// boot: block stale Steam 0x82 until link stable
+	g_hapticBlockUntil = millis() + HAPTIC_RECONNECT_BLOCK_MS;
 	// NO fabricated stop burst. USB capture proves Steam only ever sends 0x82 [01 01 f7] pulses -- never a
 	// NO fabricated stop burst. USB capture proves Steam only ever sends 0x82 [01 01 f7] pulses -- never a
 	// zero-gain [01 01 00] "stop". Injecting our invented stop frame (which the real puck never sends) at
 	// zero-gain [01 01 00] "stop". Injecting our invented stop frame (which the real puck never sends) at
 	// boot/connect is unfaithful and, given the controller's "haptic script already active -> ignore" behavior,
 	// boot/connect is unfaithful and, given the controller's "haptic script already active -> ignore" behavior,
@@ -409,23 +423,26 @@ void hapticInit()
 // link-up edge. Idempotent -- safe to call repeatedly.
 // link-up edge. Idempotent -- safe to call repeatedly.
 void hapticOnReconnect()
 void hapticOnReconnect()
 {
 {
-	g_hapticBlockUntil =
-		millis() +
-		HAPTIC_RECONNECT_BLOCK_MS; // no haptics relayed for the next 3s
+	// no haptics relayed for the next 3s
+	g_hapticBlockUntil = millis() + HAPTIC_RECONNECT_BLOCK_MS;
 	g_haptic82On = false;
 	g_haptic82On = false;
 	g_rumble80On = false;
 	g_rumble80On = false;
-	hapticCancelPendingOn(); // drop any haptic ON queued before the link came up
+
+	// drop any haptic ON queued before the link came up
+	hapticCancelPendingOn();
 	// Be PROACTIVE: start re-initing the haptic engine right after the link is up and repeat across the whole
 	// Be PROACTIVE: start re-initing the haptic engine right after the link is up and repeat across the whole
 	// settle window, instead of waiting for the block to end. The brief connect buzz engages early (during the
 	// settle window, instead of waiting for the block to end. The brief connect buzz engages early (during the
 	// block, controller-internal), so a single late shot misses it -- frequent early resets cut it short or
 	// block, controller-internal), so a single late shot misses it -- frequent early resets cut it short or
 	// preempt it. The re-init is settings only (no haptic play), and Steam haptics are blocked the whole time,
 	// preempt it. The re-init is settings only (no haptic play), and Steam haptics are blocked the whole time,
 	// so repeating it can't itself buzz.
 	// so repeating it can't itself buzz.
 	g_reinitAt = millis() + 200u; // first reset ~200ms after (re)connect
 	g_reinitAt = millis() + 200u; // first reset ~200ms after (re)connect
-	g_reinitLeft =
-		HAPTIC_REINIT_SHOTS; // then every HAPTIC_REINIT_GAP_MS across the window
+
+	// then every HAPTIC_REINIT_GAP_MS across the window
+	g_reinitLeft = HAPTIC_REINIT_SHOTS;
 	uint8_t mk = 2;
 	uint8_t mk = 2;
-	hapLogAdd(0xFD, 0xEE, &mk,
-		  1); // capture marker: RECONNECT detected (block+reinit armed)
+
+	// capture marker: RECONNECT detected (block+reinit armed)
+	hapLogAdd(0xFD, 0xEE, &mk, 1);
 }
 }
 void hapticTask()
 void hapticTask()
 {
 {

+ 16 - 7
OpenPuck/haptics.h

@@ -47,15 +47,19 @@
 // Enqueue one host->controller report: rid = report/command id, payload = the bytes AFTER [cmd][len] (what
 // Enqueue one host->controller report: rid = report/command id, payload = the bytes AFTER [cmd][len] (what
 // goes on the air). ISR-safe (brief PRIMASK critical section). Returns false (dropped) when the ring is full.
 // goes on the air). ISR-safe (brief PRIMASK critical section). Returns false (dropped) when the ring is full.
 bool relayEnqueue(uint8_t rid, const uint8_t *payload, uint8_t plen);
 bool relayEnqueue(uint8_t rid, const uint8_t *payload, uint8_t plen);
-bool relayPending(); // anything still queued (xinput uses it to pace rumble re-queues)
+
+// anything still queued (xinput uses it to pace rumble re-queues)
+bool relayPending();
 extern uint8_t g_relayOp; // relay frame opcode (E3 poll)
 extern uint8_t g_relayOp; // relay frame opcode (E3 poll)
 extern uint8_t g_relaySub; // relay sub-TLV type byte = SET
 extern uint8_t g_relaySub; // relay sub-TLV type byte = SET
 extern volatile uint8_t
 extern volatile uint8_t
 	g_testHaptic; // 't<n>' injects n test haptics for the buzz hunt
 	g_testHaptic; // 't<n>' injects n test haptics for the buzz hunt
-extern volatile uint8_t
-	g_hapticStop; // pending haptic-STOP frames to relay (kill a latched whine)
+// pending haptic-STOP frames to relay (kill a latched whine)
+extern volatile uint8_t g_hapticStop;
 extern unsigned long g_hapticBlockUntil;
 extern unsigned long g_hapticBlockUntil;
-void hapticSendShutdown(); // relay the controller power-off (0x9F "off!"), burst x3 (Steam 0x9F / host-suspend / test button)
+
+// relay the controller power-off (0x9F "off!"), burst x3 (Steam 0x9F / host-suspend / test button)
+void hapticSendShutdown();
 
 
 // ---- diagnostic capture (compiled in only when OPK_LOG): a ring of recent host->controller commands +
 // ---- diagnostic capture (compiled in only when OPK_LOG): a ring of recent host->controller commands +
 //      link/TX markers, dumped over WebUSB. No-ops in a production build so call sites vanish. ----
 //      link/TX markers, dumped over WebUSB. No-ops in a production build so call sites vanish. ----
@@ -98,9 +102,14 @@ bool hapticSteamRumble(
 void rfConnQueueHapticRelay();
 void rfConnQueueHapticRelay();
 void rfConnFlushRelay(uint8_t ch, uint8_t s1);
 void rfConnFlushRelay(uint8_t ch, uint8_t s1);
 
 
-void hapticInit(); // boot reset: clear relay/active flags, arm the reconnect block
-void hapticTask(); // per-loop upkeep: link-edge markers + steam 0x82 quiet timeout + fires the scheduled re-init
-void hapticReinit(); // replay Steam's haptic-subsystem re-init to the controller -> clears a latched/stuck buzz
+// boot reset: clear relay/active flags, arm the reconnect block
+void hapticInit();
+
+// per-loop upkeep: link-edge markers + steam 0x82 quiet timeout + fires the scheduled re-init
+void hapticTask();
+
+// replay Steam's haptic-subsystem re-init to the controller -> clears a latched/stuck buzz
+void hapticReinit();
 // Called from rf_link the instant a controller (re)connect is detected (an F-reply after a gap): blocks haptic
 // Called from rf_link the instant a controller (re)connect is detected (an F-reply after a gap): blocks haptic
 // relays for HAPTIC_RECONNECT_BLOCK_MS and schedules a re-init just after, to keep the freshly-booted
 // relays for HAPTIC_RECONNECT_BLOCK_MS and schedules a re-init just after, to keep the freshly-booted
 // controller out of the degraded/latched haptic state. Reliable -- independent of hapticTask's link heuristic.
 // controller out of the degraded/latched haptic state. Reliable -- independent of hapticTask's link heuristic.

+ 6 - 4
OpenPuck/mode_hidgyro.cpp

@@ -101,15 +101,17 @@ static void hidGyroBuild(uint8_t out[63])
 void HidGyroController::begin()
 void HidGyroController::begin()
 {
 {
 	USBDevice.setID(0x054C, 0x05C4);
 	USBDevice.setID(0x054C, 0x05C4);
-	USBDevice.setDeviceVersion(
-		0x0104); // bumped: clean-DS4 is now a separate mode (DS4_GAME); normal HIDGYRO keeps wake+WebUSB. Host re-reads config by VID:PID:serial (per-mode suffix) -- bump invalidates any cached 0x0103.
+
+	// bumped: clean-DS4 is now a separate mode (DS4_GAME); normal HIDGYRO keeps wake+WebUSB. Host re-reads config by VID:PID:serial (per-mode suffix) -- bump invalidates any cached 0x0103.
+	USBDevice.setDeviceVersion(0x0104);
 	USBDevice.setManufacturerDescriptor("Sony Computer Entertainment");
 	USBDevice.setManufacturerDescriptor("Sony Computer Entertainment");
 	USBDevice.setProductDescriptor("Wireless Controller");
 	USBDevice.setProductDescriptor("Wireless Controller");
 	g_hidGyro.enableOutEndpoint(true);
 	g_hidGyro.enableOutEndpoint(true);
 	g_hidGyro.setReportCallback(NULL, hidGyroSet);
 	g_hidGyro.setReportCallback(NULL, hidGyroSet);
 	g_hidGyro.setReportDescriptor(GYRO_HID_DESC, sizeof GYRO_HID_DESC);
 	g_hidGyro.setReportDescriptor(GYRO_HID_DESC, sizeof GYRO_HID_DESC);
-	g_hidGyro.setPollInterval(
-		1); // 1ms bInterval so the RF rate is the only latency limit (matches Xbox)
+
+	// 1ms bInterval so the RF rate is the only latency limit (matches Xbox)
+	g_hidGyro.setPollInterval(1);
 	g_hidGyro.begin();
 	g_hidGyro.begin();
 }
 }
 void HidGyroController::task()
 void HidGyroController::task()

+ 6 - 6
OpenPuck/mode_lizard.cpp

@@ -22,9 +22,10 @@ void rfLizard(const uint8_t *r, Adafruit_USBD_HID *mdev,
 		pry = ry;
 		pry = ry;
 	}
 	}
 	prt = rtouch;
 	prt = rtouch;
+
+	// Y inverted; *10 = desktop-cursor sensitivity (g_mDiv 64 -> eff 640). Lower g_mDiv via WebUI slider = faster.
 	float mxf = vx / (float)(g_mDiv * 10) + rmx,
 	float mxf = vx / (float)(g_mDiv * 10) + rmx,
-	      myf = -(vy / (float)(g_mDiv * 10)) +
-		    rmy; // Y inverted; *10 = desktop-cursor sensitivity (g_mDiv 64 -> eff 640). Lower g_mDiv via WebUI slider = faster.
+	      myf = -(vy / (float)(g_mDiv * 10)) + rmy;
 	int dx = (int)mxf, dy = (int)myf;
 	int dx = (int)mxf, dy = (int)myf;
 	rmx = mxf - dx;
 	rmx = mxf - dx;
 	rmy = myf - dy; // sub-pixel carry
 	rmy = myf - dy; // sub-pixel carry
@@ -105,10 +106,9 @@ void rfLizard(const uint8_t *r, Adafruit_USBD_HID *mdev,
 	    (g_usbMode == MODE_LIZARD) ? HID_KEY_ESCAPE : HID_KEY_TAB);
 	    (g_usbMode == MODE_LIZARD) ? HID_KEY_ESCAPE : HID_KEY_TAB);
 	LZK(b & TB_MENU,
 	LZK(b & TB_MENU,
 	    (g_usbMode == MODE_LIZARD) ? HID_KEY_TAB : HID_KEY_ESCAPE);
 	    (g_usbMode == MODE_LIZARD) ? HID_KEY_TAB : HID_KEY_ESCAPE);
-	int sx = s16off(r, 8),
-	    sy = s16off(
-		    r,
-		    10); // left stick (XInput sign: +Y = up); deflect ~37% acts as a d-pad
+
+	// left stick (XInput sign: +Y = up); deflect ~37% acts as a d-pad
+	int sx = s16off(r, 8), sy = s16off(r, 10);
 	LZK((b & TB_DUP) || sy > 12000, HID_KEY_ARROW_UP);
 	LZK((b & TB_DUP) || sy > 12000, HID_KEY_ARROW_UP);
 	LZK((b & TB_DDN) || sy < -12000, HID_KEY_ARROW_DOWN);
 	LZK((b & TB_DDN) || sy < -12000, HID_KEY_ARROW_DOWN);
 	LZK((b & TB_DLF) || sx < -12000, HID_KEY_ARROW_LEFT);
 	LZK((b & TB_DLF) || sx < -12000, HID_KEY_ARROW_LEFT);

+ 11 - 6
OpenPuck/mode_ps5.cpp

@@ -46,7 +46,8 @@ static uint16_t ps5Get(uint8_t rid, hid_report_type_t type, uint8_t *buf,
 		return 0;
 		return 0;
 	memset(buf, 0, reqlen);
 	memset(buf, 0, reqlen);
 	switch (rid) {
 	switch (rid) {
-	case 0x03: { // capabilities: identify as DualSense-capable (SDL checks byte 2 == 0x28)
+	// capabilities: identify as DualSense-capable (SDL checks byte 2 == 0x28)
+	case 0x03: {
 		if (reqlen < 47)
 		if (reqlen < 47)
 			return 0;
 			return 0;
 		buf[0] = 0x00;
 		buf[0] = 0x00;
@@ -56,7 +57,9 @@ static uint16_t ps5Get(uint8_t rid, hid_report_type_t type, uint8_t *buf,
 		buf[4] = 0x0E; // sensors + lightbar + vibration capability bits
 		buf[4] = 0x0E; // sensors + lightbar + vibration capability bits
 		return 47;
 		return 47;
 	}
 	}
-	case 0x05: { // calibration: neutral-ish non-zero block so SDL's read succeeds
+
+	// calibration: neutral-ish non-zero block so SDL's read succeeds
+	case 0x05: {
 		uint16_t n = reqlen < 40 ? reqlen : 40;
 		uint16_t n = reqlen < 40 ? reqlen : 40;
 		for (uint16_t i = 0; i < n; i++)
 		for (uint16_t i = 0; i < n; i++)
 			buf[i] = 0;
 			buf[i] = 0;
@@ -148,15 +151,17 @@ static void ps5Build(uint8_t out[63])
 void Ps5Controller::begin()
 void Ps5Controller::begin()
 {
 {
 	USBDevice.setID(0x054C, 0x0CE6);
 	USBDevice.setID(0x054C, 0x0CE6);
-	USBDevice.setDeviceVersion(
-		0x0104); // bumped: clean-PS is now a separate mode (PS5_GAME); normal PS5 keeps wake+WebUSB. Host re-reads config by VID:PID:serial (per-mode suffix) -- bump invalidates any cached 0x0103.
+
+	// bumped: clean-PS is now a separate mode (PS5_GAME); normal PS5 keeps wake+WebUSB. Host re-reads config by VID:PID:serial (per-mode suffix) -- bump invalidates any cached 0x0103.
+	USBDevice.setDeviceVersion(0x0104);
 	USBDevice.setManufacturerDescriptor("Sony Interactive Entertainment");
 	USBDevice.setManufacturerDescriptor("Sony Interactive Entertainment");
 	USBDevice.setProductDescriptor("DualSense Wireless Controller");
 	USBDevice.setProductDescriptor("DualSense Wireless Controller");
 	g_ps5.enableOutEndpoint(true);
 	g_ps5.enableOutEndpoint(true);
 	g_ps5.setReportCallback(ps5Get, ps5Set);
 	g_ps5.setReportCallback(ps5Get, ps5Set);
 	g_ps5.setReportDescriptor(PS5_HID_DESC, sizeof PS5_HID_DESC);
 	g_ps5.setReportDescriptor(PS5_HID_DESC, sizeof PS5_HID_DESC);
-	g_ps5.setPollInterval(
-		1); // 1ms bInterval so the RF rate is the only latency limit (matches Xbox)
+
+	// 1ms bInterval so the RF rate is the only latency limit (matches Xbox)
+	g_ps5.setPollInterval(1);
 	g_ps5.begin();
 	g_ps5.begin();
 }
 }
 void Ps5Controller::task()
 void Ps5Controller::task()

+ 21 - 15
OpenPuck/mode_switch_hori.cpp

@@ -9,18 +9,22 @@ SwitchHoriController g_switchHori;
 
 
 // 8-byte input report: [btn_lo][btn_hi][hat][LX][LY][RX][RY][vendor], sticks uint8 center 0x80.
 // 8-byte input report: [btn_lo][btn_hi][hat][LX][LY][RX][RY][vendor], sticks uint8 center 0x80.
 static const uint8_t SWITCH_HID_DESC[] = {
 static const uint8_t SWITCH_HID_DESC[] = {
-	0x05, 0x01, 0x09, 0x05, 0xA1, 0x01, // Usage Page (Generic Desktop), Usage (Game Pad), Collection (App)
-	0x15, 0x00, 0x25, 0x01, 0x35, 0x00, 0x45, 0x01, 0x75, 0x01,
-	0x95, 0x10, // 16 x 1-bit buttons
-	0x05, 0x09, 0x19, 0x01, 0x29, 0x10, 0x81, 0x02, 0x05, 0x01,
-	0x25, 0x07, 0x46, 0x3B, 0x01, 0x75, 0x04, 0x95, 0x01, 0x65,
-	0x14, 0x09, 0x39, 0x81, 0x42, // hat switch (4 bits)
+
+	// Usage Page (Generic Desktop), Usage (Game Pad), Collection (App)
+	0x05, 0x01, 0x09, 0x05, 0xA1, 0x01, 0x15, 0x00, 0x25, 0x01, 0x35, 0x00,
+	0x45, 0x01, 0x75, 0x01, 0x95, 0x10, // 16 x 1-bit buttons
+	0x05, 0x09, 0x19, 0x01, 0x29, 0x10, 0x81, 0x02, 0x05, 0x01, 0x25, 0x07,
+	0x46, 0x3B, 0x01, 0x75, 0x04, 0x95, 0x01, 0x65, 0x14, 0x09, 0x39, 0x81,
+	0x42, // hat switch (4 bits)
 	0x65, 0x00, 0x95, 0x01, 0x81, 0x01, // 4-bit padding to a byte
 	0x65, 0x00, 0x95, 0x01, 0x81, 0x01, // 4-bit padding to a byte
-	0x26, 0xFF, 0x00, 0x46, 0xFF, 0x00, 0x09, 0x30, 0x09, 0x31,
-	0x09, 0x32, 0x09, 0x35, 0x75, 0x08, 0x95, 0x04, 0x81, 0x02, // X,Y,Z,Rz
-	0x06, 0x00, 0xFF, 0x09, 0x20, 0x95, 0x01, 0x81, 0x02, // vendor byte (input)
-	0x0A, 0x21, 0x26, 0x95, 0x08, 0x91, 0x02, // vendor (output, 8 bytes; unused)
-	0xC0
+	0x26, 0xFF, 0x00, 0x46, 0xFF, 0x00, 0x09, 0x30, 0x09, 0x31, 0x09, 0x32,
+	0x09, 0x35, 0x75, 0x08, 0x95, 0x04, 0x81, 0x02, // X,Y,Z,Rz
+
+	// vendor byte (input)
+	0x06, 0x00, 0xFF, 0x09, 0x20, 0x95, 0x01, 0x81, 0x02,
+
+	// vendor (output, 8 bytes; unused)
+	0x0A, 0x21, 0x26, 0x95, 0x08, 0x91, 0x02, 0xC0
 };
 };
 static Adafruit_USBD_HID g_switch; // HORIPAD gamepad HID interface
 static Adafruit_USBD_HID g_switch; // HORIPAD gamepad HID interface
 static unsigned long g_swLastMs = 0;
 static unsigned long g_swLastMs = 0;
@@ -160,14 +164,16 @@ static void switchBuildHoripad(uint8_t out[8])
 void SwitchHoriController::begin()
 void SwitchHoriController::begin()
 {
 {
 	USBDevice.setID(0x0F0D, 0x0092);
 	USBDevice.setID(0x0F0D, 0x0092);
-	USBDevice.setDeviceVersion(
-		0x0202); // bumped from 0x0201 for 1ms poll interval (Windows caches config by VID:PID:bcdDevice)
+
+	// bumped from 0x0201 for 1ms poll interval (Windows caches config by VID:PID:bcdDevice)
+	USBDevice.setDeviceVersion(0x0202);
 	USBDevice.setManufacturerDescriptor("HORI CO.,LTD.");
 	USBDevice.setManufacturerDescriptor("HORI CO.,LTD.");
 	USBDevice.setProductDescriptor("POKKEN CONTROLLER");
 	USBDevice.setProductDescriptor("POKKEN CONTROLLER");
 	g_switch.enableOutEndpoint(true);
 	g_switch.enableOutEndpoint(true);
 	g_switch.setReportDescriptor(SWITCH_HID_DESC, sizeof SWITCH_HID_DESC);
 	g_switch.setReportDescriptor(SWITCH_HID_DESC, sizeof SWITCH_HID_DESC);
-	g_switch.setPollInterval(
-		1); // 1ms bInterval so the RF rate is the only latency limit (matches Xbox)
+
+	// 1ms bInterval so the RF rate is the only latency limit (matches Xbox)
+	g_switch.setPollInterval(1);
 	g_switch.begin();
 	g_switch.begin();
 }
 }
 void SwitchHoriController::task()
 void SwitchHoriController::task()

+ 68 - 55
OpenPuck/mode_switch_pro.cpp

@@ -18,8 +18,8 @@ SwitchProController g_switchPro;
 // 2wiCC-matching compat fallback; "full" is the 4 ms PC rate for those who want lowest latency and tolerate it.
 // 2wiCC-matching compat fallback; "full" is the 4 ms PC rate for those who want lowest latency and tolerate it.
 #define SW_PRO_REPORT_MS 15u // 66 Hz
 #define SW_PRO_REPORT_MS 15u // 66 Hz
 #define SW_PRO_REPORT_MS_120 8u // ~120 Hz
 #define SW_PRO_REPORT_MS_120 8u // ~120 Hz
-#define SW_ACCEL_DIV \
-	4 // SC2 accel is +/-2g (16384/g); /4 -> the genuine Pro +/-8g (4096/g) the Switch cal expects
+// SC2 accel is +/-2g (16384/g); /4 -> the genuine Pro +/-8g (4096/g) the Switch cal expects
+#define SW_ACCEL_DIV 4
 
 
 uint8_t g_swProRate =
 uint8_t g_swProRate =
 	1; // 0 = 66Hz, 1 = 120Hz (default), 2 = full (~250Hz / USB_STREAM_MS)
 	1; // 0 = 66Hz, 1 = 120Hz (default), 2 = full (~250Hz / USB_STREAM_MS)
@@ -44,10 +44,8 @@ static void swProLoadCfg()
 	uint8_t b[3];
 	uint8_t b[3];
 	if (f.open(SWPRO_CFG_FILE, FILE_O_READ)) {
 	if (f.open(SWPRO_CFG_FILE, FILE_O_READ)) {
 		if (f.read(b, 3) == 3 && b[0] == 0x01) {
 		if (f.read(b, 3) == 3 && b[0] == 0x01) {
-			g_swProRate =
-				(b[1] <= 2) ?
-					b[1] :
-					1; // 0=66Hz, 1=120Hz, 2=full; bad value -> default 120Hz
+			// 0=66Hz, 1=120Hz, 2=full; bad value -> default 120Hz
+			g_swProRate = (b[1] <= 2) ? b[1] : 1;
 			g_swGyroScale10 =
 			g_swGyroScale10 =
 				(b[2] >= 5 && b[2] <= 30) ?
 				(b[2] >= 5 && b[2] <= 30) ?
 					b[2] :
 					b[2] :
@@ -145,8 +143,8 @@ static uint32_t codeToJc(uint8_t c, uint32_t fA, uint32_t fB, uint32_t fX,
 }
 }
 static unsigned long g_swProLastMs = 0;
 static unsigned long g_swProLastMs = 0;
 static Adafruit_USBD_HID g_swPro;
 static Adafruit_USBD_HID g_swPro;
-static uint8_t g_swProReportMode =
-	0; // 0 until the host's subcommand 0x03 selects 0x30 -> THEN we stream input
+// 0 until the host's subcommand 0x03 selects 0x30 -> THEN we stream input
+static uint8_t g_swProReportMode = 0;
 static const uint8_t JC_MAC[6] = {
 static const uint8_t JC_MAC[6] = {
 	0x7C, 0xBB, 0x8A, 0x00, 0x00, 0x01
 	0x7C, 0xBB, 0x8A, 0x00, 0x00, 0x01
 }; // synthetic but stable controller MAC
 }; // synthetic but stable controller MAC
@@ -167,11 +165,13 @@ static void jcRumble(const uint8_t *p, uint16_t pn)
 		return; // [timer][left rumble x4][right rumble x4]
 		return; // [timer][left rumble x4][right rumble x4]
 	uint16_t lo = jcRumbleAmp(p + 1), hi = jcRumbleAmp(p + 5);
 	uint16_t lo = jcRumbleAmp(p + 1), hi = jcRumbleAmp(p + 5);
 	static uint16_t lastLo = 0, lastHi = 0;
 	static uint16_t lastLo = 0, lastHi = 0;
+	// only relay on change: the Switch streams rumble every frame, so
 	if (lo == lastLo && hi == lastHi)
 	if (lo == lastLo && hi == lastHi)
-		return; // only relay on change: the Switch streams rumble every frame, so
+		return;
 	lastLo = lo;
 	lastLo = lo;
-	lastHi =
-		hi; // re-sending unchanged values would flood the RF relay and loop the motor
+
+	// re-sending unchanged values would flood the RF relay and loop the motor
+	lastHi = hi;
 	hapticSteamRumble(lo, hi);
 	hapticSteamRumble(lo, hi);
 }
 }
 static int jcStick12(int16_t v, bool inv)
 static int jcStick12(int16_t v, bool inv)
@@ -179,8 +179,9 @@ static int jcStick12(int16_t v, bool inv)
 	int a = 2048 + (inv ? -((int)v >> 4) : ((int)v >> 4));
 	int a = 2048 + (inv ? -((int)v >> 4) : ((int)v >> 4));
 	return a < 0 ? 0 : (a > 4095 ? 4095 : a);
 	return a < 0 ? 0 : (a > 4095 ? 4095 : a);
 }
 }
+// pack two 12-bit axes into 3 bytes (both normal; Switch Y polarity is opposite DS4)
 static void jcPackStick(uint8_t s[3], int16_t x, int16_t y)
 static void jcPackStick(uint8_t s[3], int16_t x, int16_t y)
-{ // pack two 12-bit axes into 3 bytes (both normal; Switch Y polarity is opposite DS4)
+{
 	int X = jcStick12(x, false), Y = jcStick12(y, false);
 	int X = jcStick12(x, false), Y = jcStick12(y, false);
 	s[0] = (uint8_t)(X & 0xFF);
 	s[0] = (uint8_t)(X & 0xFF);
 	s[1] = (uint8_t)(((Y & 0x0F) << 4) | ((X >> 8) & 0x0F));
 	s[1] = (uint8_t)(((Y & 0x0F) << 4) | ((X >> 8) & 0x0F));
@@ -246,19 +247,21 @@ static void jcInputPrefix(uint8_t *out)
 	if (b & TB_R5)
 	if (b & TB_R5)
 		jc |= codeToJc(g_back[3], fA, fB, fX, fY);
 		jc |= codeToJc(g_back[3], fA, fB, fX, fY);
 	out[0] = g_jcTimer++;
 	out[0] = g_jcTimer++;
+
 	// battery full+charging (hi nibble 0x9), connection_info=1 (lo nibble): a wired/charging Pro Controller.
 	// battery full+charging (hi nibble 0x9), connection_info=1 (lo nibble): a wired/charging Pro Controller.
 	// A real Switch reads this to show the pad as connected; Steam/hid-nintendo accept it too. (2wiCC parity)
 	// A real Switch reads this to show the pad as connected; Steam/hid-nintendo accept it too. (2wiCC parity)
 	out[1] = 0x91;
 	out[1] = 0x91;
 	out[2] = (uint8_t)(jc);
 	out[2] = (uint8_t)(jc);
 	out[3] = (uint8_t)(jc >> 8);
 	out[3] = (uint8_t)(jc >> 8);
 	out[4] = (uint8_t)(jc >> 16);
 	out[4] = (uint8_t)(jc >> 16);
+
 	// charging_grip bit (button "common" byte, bit7): genuine Pro Controller always sets it on USB; real
 	// charging_grip bit (button "common" byte, bit7): genuine Pro Controller always sets it on USB; real
 	// Switch uses it to recognise a wired controller. Not a button, so hid-nintendo ignores it.
 	// Switch uses it to recognise a wired controller. Not a button, so hid-nintendo ignores it.
 	out[3] |= 0x80;
 	out[3] |= 0x80;
 	jcPackStick(out + 5, g_in.lx, g_in.ly);
 	jcPackStick(out + 5, g_in.lx, g_in.ly);
 	jcPackStick(out + 8, g_in.rx, g_in.ry);
 	jcPackStick(out + 8, g_in.rx, g_in.ry);
-	out[11] =
-		0x09; // rumble_input_report echo: genuine pad emits 0x09..0x0C; some Switch firmware expects this nonzero.
+	// rumble_input_report echo: genuine pad emits 0x09..0x0C; some Switch firmware expects this nonzero.
+	out[11] = 0x09;
 }
 }
 static void switchProBuild(uint8_t out[63])
 static void switchProBuild(uint8_t out[63])
 {
 {
@@ -278,18 +281,17 @@ static void switchProBuild(uint8_t out[63])
 	// by SW_ACCEL_DIV=4 to report ~1g of gravity. Without this the console reads gravity as ~4g, REJECTS the accel for
 	// by SW_ACCEL_DIV=4 to report ~1g of gravity. Without this the console reads gravity as ~4g, REJECTS the accel for
 	// drift correction (it must be linear accel, not gravity), and the gyro's roll error accumulates into the slow
 	// drift correction (it must be linear accel, not gravity), and the gyro's roll error accumulates into the slow
 	// ~45deg lean. Gyro is left at native scale (aiming sensitivity is right); only the accel magnitude was wrong.
 	// ~45deg lean. Gyro is left at native scale (aiming sensitivity is right); only the accel magnitude was wrong.
-	int16_t aX =
-		(int16_t)(g_in.ay /
-			  SW_ACCEL_DIV); // accel X <- +g_in.ay /4   (matches gyro +gy)
-	int16_t aY =
-		(int16_t)((-(int16_t)g_in.ax) /
-			  SW_ACCEL_DIV); // accel Y <- -g_in.ax /4   (matches gyro -gx)
-	int16_t aZ =
-		(int16_t)(g_in.az /
-			  SW_ACCEL_DIV); // accel Z <- +g_in.az /4   (matches gyro +gz)
-	int16_t groll = gscale(
-		(int16_t)g_in
-			.gy); // gyro outputs scaled by the user sensitivity factor
+	// accel X <- +g_in.ay /4   (matches gyro +gy)
+	int16_t aX = (int16_t)(g_in.ay / SW_ACCEL_DIV);
+
+	// accel Y <- -g_in.ax /4   (matches gyro -gx)
+	int16_t aY = (int16_t)((-(int16_t)g_in.ax) / SW_ACCEL_DIV);
+
+	// accel Z <- +g_in.az /4   (matches gyro +gz)
+	int16_t aZ = (int16_t)(g_in.az / SW_ACCEL_DIV);
+
+	// gyro outputs scaled by the user sensitivity factor
+	int16_t groll = gscale((int16_t)g_in.gy);
 	int16_t gpitch = gscale((int16_t)(-(int16_t)g_in.gx));
 	int16_t gpitch = gscale((int16_t)(-(int16_t)g_in.gx));
 	int16_t gyaw = gscale((int16_t)g_in.gz);
 	int16_t gyaw = gscale((int16_t)g_in.gz);
 	for (int k = 0; k < 3; k++) {
 	for (int k = 0; k < 3; k++) {
@@ -326,8 +328,8 @@ static const uint8_t SPI_PARAMS2[18] = { 0x0F, 0x30, 0x61, 0x96, 0x30, 0xF3,
 					 0xC7, 0x79, 0x9C, 0x33, 0x36, 0x63 };
 					 0xC7, 0x79, 0x9C, 0x33, 0x36, 0x63 };
 static const uint8_t SPI_COLOR[13] = { 0x32, 0x32, 0x32, 0xE6, 0xE6, 0xE6, 0x32,
 static const uint8_t SPI_COLOR[13] = { 0x32, 0x32, 0x32, 0xE6, 0xE6, 0xE6, 0x32,
 				       0x32, 0x32, 0x32, 0x32, 0x32, 0xFF };
 				       0x32, 0x32, 0x32, 0x32, 0x32, 0xFF };
-static uint8_t g_spiStickCal
-	[18]; // built at boot: left[9]+right[9] factory stick calibration (packed 12-bit)
+// built at boot: left[9]+right[9] factory stick calibration (packed 12-bit)
+static uint8_t g_spiStickCal[18];
 static void jcPack12(uint8_t *o9, const uint16_t v[6])
 static void jcPack12(uint8_t *o9, const uint16_t v[6])
 { // pack 6 12-bit values into 9 bytes (Switch stick-cal format)
 { // pack 6 12-bit values into 9 bytes (Switch stick-cal format)
 	o9[0] = v[0] & 0xFF;
 	o9[0] = v[0] & 0xFF;
@@ -356,16 +358,14 @@ static void jcBuildStickCal()
 // can register the pad (Steam/hid-nintendo never do, which is why this was previously unneeded). The data is the
 // can register the pad (Steam/hid-nintendo never do, which is why this was previously unneeded). The data is the
 // canonical hid handshake blob; the Switch only validates the shape, not the key contents. Each is the 31-byte
 // canonical hid handshake blob; the Switch only validates the shape, not the key contents. Each is the 31-byte
 // reply body that follows the 0x21 input prefix + ack(0x81) + echoed-subcommand(0x01). (adapted from 2wiCC)
 // reply body that follows the 0x21 input prefix + ack(0x81) + echoed-subcommand(0x01). (adapted from 2wiCC)
-static const uint8_t BT_PAIR_1
-	[31] = { // type 1: echo type + controller BT addr + "Pro Controller" name
-		0x01,	   JC_MAC[0], JC_MAC[1], JC_MAC[2], JC_MAC[3],
-		JC_MAC[4], JC_MAC[5], 0x00,	 0x25,	    0x08,
-		0x50,	   0x72,      0x6F,	 0x20,	    0x43,
-		0x6F,	   0x6E,      0x74,	 0x72,	    0x6F,
-		0x6C,	   0x6C,      0x65,	 0x72, // "Pro Controller"
-		0x00,	   0x00,      0x00,	 0x00,	    0x00,
-		0x68
-	};
+// type 1: echo type + controller BT addr + "Pro Controller" name
+static const uint8_t BT_PAIR_1[31] = {
+	0x01, JC_MAC[0], JC_MAC[1], JC_MAC[2], JC_MAC[3], JC_MAC[4], JC_MAC[5],
+	0x00, 0x25,	 0x08,	    0x50,      0x72,	  0x6F,	     0x20,
+	0x43, 0x6F,	 0x6E,	    0x74,      0x72,	  0x6F,	     0x6C,
+	0x6C, 0x65,	 0x72, // "Pro Controller"
+	0x00, 0x00,	 0x00,	    0x00,      0x00,	  0x68
+};
 static const uint8_t BT_PAIR_2[31] = { // type 2: LTK exchange
 static const uint8_t BT_PAIR_2[31] = { // type 2: LTK exchange
 	0x02, 0xE5, 0xC8, 0xE4, 0x92, 0x05, 0xFF, 0xC9, 0x8A, 0x7D, 0xEA,
 	0x02, 0xE5, 0xC8, 0xE4, 0x92, 0x05, 0xFF, 0xC9, 0x8A, 0x7D, 0xEA,
 	0x15, 0xF6, 0x19, 0xBA, 0x82, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00,
 	0x15, 0xF6, 0x19, 0xBA, 0x82, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -440,8 +440,8 @@ static void spiRead(uint32_t addr, uint8_t len, uint8_t *dst)
 		else if (a >= 0x6098 && a < 0x6098 + 18)
 		else if (a >= 0x6098 && a < 0x6098 + 18)
 			v = SPI_PARAMS2[a - 0x6098];
 			v = SPI_PARAMS2[a - 0x6098];
 		else if (a >= 0x8000 && a < 0x8100)
 		else if (a >= 0x8000 && a < 0x8100)
-			v = g_userCal[a -
-				      0x8000]; // persisted user cal (0xFF blank -> factory fallback)
+			// persisted user cal (0xFF blank -> factory fallback)
+			v = g_userCal[a - 0x8000];
 		dst[i] = v;
 		dst[i] = v;
 	}
 	}
 }
 }
@@ -477,7 +477,8 @@ static void jcSubcmd(uint8_t sub, const uint8_t *args, uint16_t alen)
 	jcInputPrefix(p);
 	jcInputPrefix(p);
 	p[13] = sub;
 	p[13] = sub;
 	switch (sub) {
 	switch (sub) {
-	case 0x01: { // manual BT pairing: 3-stage key exchange a real Switch runs over USB
+	// manual BT pairing: 3-stage key exchange a real Switch runs over USB
+	case 0x01: {
 		const uint8_t *d = BT_PAIR_3;
 		const uint8_t *d = BT_PAIR_3;
 		uint8_t t = (alen >= 1) ? args[0] : 3;
 		uint8_t t = (alen >= 1) ? args[0] : 3;
 		if (t == 1)
 		if (t == 1)
@@ -491,7 +492,9 @@ static void jcSubcmd(uint8_t sub, const uint8_t *args, uint16_t alen)
 	case 0x02: // request device info
 	case 0x02: // request device info
 		p[12] = 0x82;
 		p[12] = 0x82;
 		p[14] = 0x03;
 		p[14] = 0x03;
-		p[15] = 0x48; // firmware version 3.72 (genuine Pro Controller value)
+
+		// firmware version 3.72 (genuine Pro Controller value)
+		p[15] = 0x48;
 		p[16] = 0x03; // controller type: Pro Controller
 		p[16] = 0x03; // controller type: Pro Controller
 		p[17] = 0x02; // fixed
 		p[17] = 0x02; // fixed
 		memcpy(&p[18], JC_MAC, 6);
 		memcpy(&p[18], JC_MAC, 6);
@@ -518,7 +521,8 @@ static void jcSubcmd(uint8_t sub, const uint8_t *args, uint16_t alen)
 		spiRead(a, rl, &p[19]);
 		spiRead(a, rl, &p[19]);
 		break;
 		break;
 	}
 	}
-	case 0x11: // SPI flash write -> persist user calibration so Switch motion-cal sticks
+	// SPI flash write -> persist user calibration so Switch motion-cal sticks
+	case 0x11:
 		if (alen >= 5) {
 		if (alen >= 5) {
 			uint32_t a = (uint32_t)args[0] |
 			uint32_t a = (uint32_t)args[0] |
 				     ((uint32_t)args[1] << 8) |
 				     ((uint32_t)args[1] << 8) |
@@ -528,13 +532,18 @@ static void jcSubcmd(uint8_t sub, const uint8_t *args, uint16_t alen)
 				   (alen > 5) ? (uint16_t)(alen - 5) : 0);
 				   (alen > 5) ? (uint16_t)(alen - 5) : 0);
 		}
 		}
 		p[12] = 0x80;
 		p[12] = 0x80;
-		break; // write ACK
-	case 0x03: // set input report mode (0x30 = standard full) -> begin streaming
+
+		// write ACK
+		break;
+
+	// set input report mode (0x30 = standard full) -> begin streaming
+	case 0x03:
 		if (alen >= 1 && args[0] == 0x30)
 		if (alen >= 1 && args[0] == 0x30)
 			g_swProReportMode = 0x30;
 			g_swProReportMode = 0x30;
 		p[12] = 0x80;
 		p[12] = 0x80;
 		break;
 		break;
-	case 0x04: // trigger buttons elapsed time -> canned reply (genuine pad returns data)
+	// trigger buttons elapsed time -> canned reply (genuine pad returns data)
+	case 0x04:
 		p[12] = 0x83;
 		p[12] = 0x83;
 		p[14] = 0x00;
 		p[14] = 0x00;
 		p[15] = 0xCC;
 		p[15] = 0xCC;
@@ -548,7 +557,9 @@ static void jcSubcmd(uint8_t sub, const uint8_t *args, uint16_t alen)
 		break; // set NFC/IR config
 		break; // set NFC/IR config
 	default:
 	default:
 		p[12] = 0x80;
 		p[12] = 0x80;
-		break; // 0x06/0x08/0x30/0x38/0x40/0x41/0x48/... generic positive ACK
+
+		// 0x06/0x08/0x30/0x38/0x40/0x41/0x48/... generic positive ACK
+		break;
 	}
 	}
 	jcEnq(0x21, p, JC_REPLEN);
 	jcEnq(0x21, p, JC_REPLEN);
 }
 }
@@ -610,20 +621,22 @@ static void jcSet(uint8_t rid, hid_report_type_t type, uint8_t const *b,
 void SwitchProController::begin()
 void SwitchProController::begin()
 {
 {
 	USBDevice.setID(0x057E, 0x2009);
 	USBDevice.setID(0x057E, 0x2009);
-	USBDevice.setDeviceVersion(
-		0x0212); // bumped from 0x0211 for 1ms poll interval (Windows caches config by VID:PID:bcdDevice)
+	// bumped from 0x0211 for 1ms poll interval (Windows caches config by VID:PID:bcdDevice)
+	USBDevice.setDeviceVersion(0x0212);
 	USBDevice.setManufacturerDescriptor("Nintendo Co., Ltd.");
 	USBDevice.setManufacturerDescriptor("Nintendo Co., Ltd.");
 	USBDevice.setProductDescriptor("Pro Controller");
 	USBDevice.setProductDescriptor("Pro Controller");
 	jcBuildStickCal();
 	jcBuildStickCal();
-	loadUserCal(); // restore any persisted Switch motion calibration (per-unit IMU bias correction)
+
+	// restore any persisted Switch motion calibration (per-unit IMU bias correction)
+	loadUserCal();
 	swProLoadCfg(); // restore report-rate + gyro-scale settings
 	swProLoadCfg(); // restore report-rate + gyro-scale settings
 	g_swPro.enableOutEndpoint(true);
 	g_swPro.enableOutEndpoint(true);
-	g_swPro.setReportCallback(
-		NULL,
-		jcSet); // answer the Nintendo USB handshake + subcommands (else Steam never binds it)
+	// answer the Nintendo USB handshake + subcommands (else Steam never binds it)
+	g_swPro.setReportCallback(NULL, jcSet);
 	g_swPro.setReportDescriptor(SWPRO_HID_DESC, sizeof SWPRO_HID_DESC);
 	g_swPro.setReportDescriptor(SWPRO_HID_DESC, sizeof SWPRO_HID_DESC);
-	g_swPro.setPollInterval(
-		1); // 1ms bInterval so the RF rate is the only latency limit (matches Xbox)
+
+	// 1ms bInterval so the RF rate is the only latency limit (matches Xbox)
+	g_swPro.setPollInterval(1);
 	g_swPro.begin();
 	g_swPro.begin();
 }
 }
 void SwitchProController::task()
 void SwitchProController::task()

+ 14 - 42
OpenPuck/mode_xinput.cpp

@@ -45,8 +45,9 @@ static uint8_t g_xiInBuf[32], g_xiOutBuf[32];
 static volatile uint16_t g_rumbleLow = 0, g_rumbleHigh = 0;
 static volatile uint16_t g_rumbleLow = 0, g_rumbleHigh = 0;
 static volatile unsigned long g_rumbleMs =
 static volatile unsigned long g_rumbleMs =
 	0; // millis of last rumble OUT packet (stuck-rumble watchdog)
 	0; // millis of last rumble OUT packet (stuck-rumble watchdog)
-#define RUMBLE_STUCK_MS \
-	2500u // release a held rumble if no OUT packet refreshes it for this long (covers a lost stop without cutting normal short rumbles)
+
+// release a held rumble if no OUT packet refreshes it for this long (covers a lost stop without cutting normal short rumbles)
+#define RUMBLE_STUCK_MS 2500u
 
 
 static void xi_init(void)
 static void xi_init(void)
 {
 {
@@ -148,44 +149,15 @@ class Adafruit_USBD_XInput : public Adafruit_USBD_Interface {
 		uint8_t epin = TinyUSBDevice.allocEndpoint(TUSB_DIR_IN),
 		uint8_t epin = TinyUSBDevice.allocEndpoint(TUSB_DIR_IN),
 			epout = TinyUSBDevice.allocEndpoint(TUSB_DIR_OUT);
 			epout = TinyUSBDevice.allocEndpoint(TUSB_DIR_OUT);
 		const uint8_t t[XINPUT_DESC_LEN] = {
 		const uint8_t t[XINPUT_DESC_LEN] = {
-			9,
-			TUSB_DESC_INTERFACE,
-			itfnum,
-			0x00,
-			0x02,
-			0xFF,
-			0x5D,
-			0x01,
-			_strid,
-			0x11,
-			0x21,
-			0x00,
-			0x01,
-			0x01,
-			0x25,
-			epin,
-			0x14,
-			0x00,
-			0x00,
-			0x00,
-			0x00,
-			0x13,
-			epout,
-			0x08,
-			0x00,
-			0x00,
-			7,
-			TUSB_DESC_ENDPOINT,
-			epin,
-			TUSB_XFER_INTERRUPT,
-			U16_TO_U8S_LE(0x20),
-			1, // bInterval 1ms (1000Hz) so the RF rate is the only limit
-			7,
-			TUSB_DESC_ENDPOINT,
-			epout,
-			TUSB_XFER_INTERRUPT,
+			9, TUSB_DESC_INTERFACE, itfnum, 0x00, 0x02, 0xFF, 0x5D,
+			0x01, _strid, 0x11, 0x21, 0x00, 0x01, 0x01, 0x25, epin,
+			0x14, 0x00, 0x00, 0x00, 0x00, 0x13, epout, 0x08, 0x00,
+			0x00, 7, TUSB_DESC_ENDPOINT, epin, TUSB_XFER_INTERRUPT,
 			U16_TO_U8S_LE(0x20),
 			U16_TO_U8S_LE(0x20),
-			8
+
+			// bInterval 1ms (1000Hz) so the RF rate is the only limit
+			1, 7, TUSB_DESC_ENDPOINT, epout, TUSB_XFER_INTERRUPT,
+			U16_TO_U8S_LE(0x20), 8
 		};
 		};
 		memcpy(buf, t, XINPUT_DESC_LEN);
 		memcpy(buf, t, XINPUT_DESC_LEN);
 		return XINPUT_DESC_LEN;
 		return XINPUT_DESC_LEN;
@@ -387,9 +359,9 @@ static void rfXboxMouse(const uint8_t *r)
 void XboxController::begin()
 void XboxController::begin()
 {
 {
 	g_rumbleLow = g_rumbleHigh = 0;
 	g_rumbleLow = g_rumbleHigh = 0;
-	USBDevice.setID(
-		0x045E,
-		0x028E); // device-level 045E:028E match -> Windows xusb / SDL / Linux xpad all bind it
+
+	// device-level 045E:028E match -> Windows xusb / SDL / Linux xpad all bind it
+	USBDevice.setID(0x045E, 0x028E);
 	// bcdDevice 0x0115 (was 0x0114): Windows caches the config descriptor by VID:PID:bcdDevice, so any change to
 	// bcdDevice 0x0115 (was 0x0114): Windows caches the config descriptor by VID:PID:bcdDevice, so any change to
 	// this mode's interfaces (here: the wake-mouse interface) MUST bump bcdDevice or Windows serves a stale
 	// this mode's interfaces (here: the wake-mouse interface) MUST bump bcdDevice or Windows serves a stale
 	// descriptor and the change is invisible (no manual Device-Manager cache-clear should ever be needed).
 	// descriptor and the change is invisible (no manual Device-Manager cache-clear should ever be needed).

+ 3 - 2
OpenPuck/mode_xinput.h

@@ -14,7 +14,8 @@ class XboxController : public IController {
 	void begin() override;
 	void begin() override;
 	void onReport45(const uint8_t *rep, bool fresh,
 	void onReport45(const uint8_t *rep, bool fresh,
 			uint8_t bodyTlen) override;
 			uint8_t bodyTlen) override;
-	void
-	task() override; // legacy XInput rumble -> haptic relay + stuck-rumble watchdog
+
+	// legacy XInput rumble -> haptic relay + stuck-rumble watchdog
+	void task() override;
 };
 };
 extern XboxController g_xboxCtl;
 extern XboxController g_xboxCtl;

+ 55 - 41
OpenPuck/puck_hid.cpp

@@ -94,8 +94,9 @@ static Adafruit_USBD_HID hid[NSLOT];
 // Steam, while running, re-sends settings report 0x87 (lizard-off) every ~3s as a heartbeat (captured on HW),
 // Steam, while running, re-sends settings report 0x87 (lizard-off) every ~3s as a heartbeat (captured on HW),
 // and ANY OUTPUT report likewise stamps g_steamAliveMs. When the heartbeat stops we fall back to lizard, so the
 // and ANY OUTPUT report likewise stamps g_steamAliveMs. When the heartbeat stops we fall back to lizard, so the
 // controller drives desktop keyboard+mouse whenever Steam isn't running. MODE_LIZARD forces lizard always.
 // controller drives desktop keyboard+mouse whenever Steam isn't running. MODE_LIZARD forces lizard always.
-static unsigned long g_steamAliveMs =
-	0; // millis of last Steam OUTPUT/settings write; 0 at boot => lizard until Steam appears
+
+// millis of last Steam OUTPUT/settings write; 0 at boot => lizard until Steam appears
+static unsigned long g_steamAliveMs = 0;
 // Fall back to lizard this long after Steam's ~3s settings heartbeat stops. Keep >2x the cadence (7s): the
 // Fall back to lizard this long after Steam's ~3s settings heartbeat stops. Keep >2x the cadence (7s): the
 // haptic relay is gated by !lizardActive(), so a shorter window lets a single jittered/delayed heartbeat flip
 // haptic relay is gated by !lizardActive(), so a shorter window lets a single jittered/delayed heartbeat flip
 // lizard mid-session while Steam is still running -> a haptic that arrives in that window gets gated out
 // lizard mid-session while Steam is still running -> a haptic that arrives in that window gets gated out
@@ -132,21 +133,22 @@ static unsigned long g_resumeMs = 0;
 static void handleSet(int slot, uint8_t rid, hid_report_type_t type,
 static void handleSet(int slot, uint8_t rid, hid_report_type_t type,
 		      uint8_t const *b, uint16_t n)
 		      uint8_t const *b, uint16_t n)
 {
 {
-	if (type ==
-	    HID_REPORT_TYPE_OUTPUT) { // Steam OUTPUT reports 0x80-0x89. The haptic/actuator reports (0x80-0x86)
-		// are relayed to the controller, and ONLY when they arrive on the CONNECTED slot's interface. We have one
-		// controller but expose 4 puck slots, and a report aimed at a DIFFERENT slot made the controller buzz at
-		// random -> that is what the slot gate below fixes. (This used to also clamp to 0x82-ONLY, which silently
-		// dropped the ping / grip / test haptics: those ride other report IDs such as 0x85/0x86, so they never
-		// reached the controller.) The 63-byte settings/config reports 0x87/0x88/0x89 are NOT haptics and are not
-		// pushed here -- 0x87 (lizard-off/settings) reaches the controller through the feature-0x01 passthrough path.
+	// Steam OUTPUT reports 0x80-0x89. The haptic/actuator reports (0x80-0x86)
+	// are relayed to the controller, and ONLY when they arrive on the CONNECTED slot's interface. We have one
+	// controller but expose 4 puck slots, and a report aimed at a DIFFERENT slot made the controller buzz at
+	// random -> that is what the slot gate below fixes. (This used to also clamp to 0x82-ONLY, which silently
+	// dropped the ping / grip / test haptics: those ride other report IDs such as 0x85/0x86, so they never
+	// reached the controller.) The 63-byte settings/config reports 0x87/0x88/0x89 are NOT haptics and are not
+	// pushed here -- 0x87 (lizard-off/settings) reaches the controller through the feature-0x01 passthrough path.
+	if (type == HID_REPORT_TYPE_OUTPUT) {
 		if (rid >= 0x80 && rid <= 0x89) {
 		if (rid >= 0x80 && rid <= 0x89) {
-			hapLogAdd(
-				(uint8_t)slot, rid, b,
-				n); // capture ALL OUTPUT reports (even un-relayed) for the 'H' dump
-			hostStampAlive(); // ANY Steam OUTPUT report (not just the 0x87 heartbeat) means Steam is present and
-				// driving -> leave lizard for gamepad NOW, so a haptic that arrives before the
-				// first 0x87 doesn't get relayed while we're still presenting lizard (-> buzz loop).
+			// capture ALL OUTPUT reports (even un-relayed) for the 'H' dump
+			hapLogAdd((uint8_t)slot, rid, b, n);
+
+			// ANY Steam OUTPUT report (not just the 0x87 heartbeat) means Steam is present and
+			// driving -> leave lizard for gamepad NOW, so a haptic that arrives before the
+			// first 0x87 doesn't get relayed while we're still presenting lizard (-> buzz loop).
+			hostStampAlive();
 		}
 		}
 		// Post-resume mute also gates haptics: while onReport45 is muted Steam reads NO 0x45 back, which is the
 		// Post-resume mute also gates haptics: while onReport45 is muted Steam reads NO 0x45 back, which is the
 		// exact condition under which Steam loops the same haptic command (-> connect/wake buzz loop). Same logic
 		// exact condition under which Steam loops the same haptic command (-> connect/wake buzz loop). Same logic
@@ -168,8 +170,9 @@ static void handleSet(int slot, uint8_t rid, hid_report_type_t type,
 					haptic82HostReport(b, n);
 					haptic82HostReport(b, n);
 			}
 			}
 		}
 		}
-		if (Serial.availableForWrite() >
-		    80) { // log so we can see what Steam actually sends (e.g. glide haptics)
+
+		// log so we can see what Steam actually sends (e.g. glide haptics)
+		if (Serial.availableForWrite() > 80) {
 			Serial.printf("# OUT if%d rid=%02X n=%u:", slot, rid,
 			Serial.printf("# OUT if%d rid=%02X n=%u:", slot, rid,
 				      n);
 				      n);
 			for (uint16_t i = 0; i < n && i < 14; i++)
 			for (uint16_t i = 0; i < n && i < 14; i++)
@@ -184,30 +187,32 @@ static void handleSet(int slot, uint8_t rid, hid_report_type_t type,
 	uint8_t cmd = b[0], len = (n > 1) ? b[1] : 0;
 	uint8_t cmd = b[0], len = (n > 1) ? b[1] : 0;
 	const uint8_t *pl = b + 2;
 	const uint8_t *pl = b + 2;
 	uint16_t pln = (n >= 2) ? n - 2 : 0;
 	uint16_t pln = (n >= 2) ? n - 2 : 0;
+
+	// settings/haptic/LED report (incl. 0x87 lizard-off heartbeat, SDL Triton lizard-disable)
 	if (cmd >= 0x80 && cmd <= 0x89)
 	if (cmd >= 0x80 && cmd <= 0x89)
-		hostStampAlive(); // settings/haptic/LED report (incl. 0x87 lizard-off heartbeat, SDL Triton lizard-disable)
+		hostStampAlive();
 	// Controller power-off: Steam's "turn off controller" is feature-0x01 frame 9F 04 6F 66 66 21 ("off!"),
 	// Controller power-off: Steam's "turn off controller" is feature-0x01 frame 9F 04 6F 66 66 21 ("off!"),
 	// confirmed from a real puck capture. The feature-0x01 relay below forwards it once; hapticSendShutdown()
 	// confirmed from a real puck capture. The feature-0x01 relay below forwards it once; hapticSendShutdown()
 	// bursts it for NO-ACK reliability (the single hook the test button + host-suspend also drive).
 	// bursts it for NO-ACK reliability (the single hook the test button + host-suspend also drive).
 	if (rid == 1 && cmd == 0x9F)
 	if (rid == 1 && cmd == 0x9F)
 		hapticSendShutdown();
 		hapticSendShutdown();
-	if (rid == 1 &&
-	    n >= 2) { // report 0x01 = raw passthrough -> queue for RF relay to the controller
+
+	// report 0x01 = raw passthrough -> queue for RF relay to the controller
+	if (rid == 1 && n >= 2) {
 		// capture EVERY relayed feature-1 command for the WebUSB capture view
 		// capture EVERY relayed feature-1 command for the WebUSB capture view
 		// (haptics, LED SET_LED_COLOR, 0x87 settings, 0x9F power-off). The log
 		// (haptics, LED SET_LED_COLOR, 0x87 settings, 0x9F power-off). The log
 		// shows cmd as "rid"; bytes start [cmd][len]...  Was gated to 0x80-0x89,
 		// shows cmd as "rid"; bytes start [cmd][len]...  Was gated to 0x80-0x89,
 		// which hid exactly the LED/power-off frames we now need to see.
 		// which hid exactly the LED/power-off frames we now need to see.
 		hapLogAdd((uint8_t)slot, cmd, b, n);
 		hapLogAdd((uint8_t)slot, cmd, b, n);
 		bool haptic82 = (cmd == 0x82 && len <= pln);
 		bool haptic82 = (cmd == 0x82 && len <= pln);
-		bool muted =
-			g_resumeMs &&
-			millis() - g_resumeMs <
-				POST_RESUME_MUTE_MS; // see the OUTPUT path: no haptics while Steam can't read 0x45 back
-		bool relayOk =
-			hapticRelaySlotOk(slot) &&
-			!(haptic82 &&
-			  (lizardActive() ||
-			   muted)); // never push haptics to the controller while presenting lizard (Steam isn't reading 0x45 -> would buzz-loop)
+
+		// see the OUTPUT path: no haptics while Steam can't read 0x45 back
+		bool muted = g_resumeMs &&
+			     millis() - g_resumeMs < POST_RESUME_MUTE_MS;
+
+		// never push haptics to the controller while presenting lizard (Steam isn't reading 0x45 -> would buzz-loop)
+		bool relayOk = hapticRelaySlotOk(slot) &&
+			       !(haptic82 && (lizardActive() || muted));
 		if (relayOk && (!haptic82 || !haptic82Blocked())) {
 		if (relayOk && (!haptic82 || !haptic82Blocked())) {
 			// Relay the DECLARED length (up to the 60B RF frame ceiling), not a truncation: Steam's multi-register
 			// Relay the DECLARED length (up to the 60B RF frame ceiling), not a truncation: Steam's multi-register
 			// 0x87 settings blocks (LED brightness) and calibration writes are longer than the old 18B cap, and the
 			// 0x87 settings blocks (LED brightness) and calibration writes are longer than the old 18B cap, and the
@@ -218,10 +223,10 @@ static void handleSet(int slot, uint8_t rid, hid_report_type_t type,
 					"# RELAY TRUNC cmd=%02X len=%u>%u\n",
 					"# RELAY TRUNC cmd=%02X len=%u>%u\n",
 					cmd, len, (unsigned)RELAY_MAXP);
 					cmd, len, (unsigned)RELAY_MAXP);
 			relayEnqueue(cmd, pl, rl);
 			relayEnqueue(cmd, pl, rl);
+
+			// track from RELAYED frames only (see the OUTPUT path)
 			if (haptic82)
 			if (haptic82)
-				haptic82HostReport(
-					pl,
-					len); // track from RELAYED frames only (see the OUTPUT path)
+				haptic82HostReport(pl, len);
 		}
 		}
 	}
 	}
 	if (Serial.availableForWrite() >
 	if (Serial.availableForWrite() >
@@ -254,8 +259,11 @@ static void handleSet(int slot, uint8_t rid, hid_report_type_t type,
 		S.resp_len = 63;
 		S.resp_len = 63;
 		break;
 		break;
 	}
 	}
-	case 0xB4: // connection/version state per slot: value 0x02 = controller connected, 0x01 = not
-		hostStampAlive(); // SDL Triton polls this on init; treat like Steam contact so we forward 0x45
+
+	// connection/version state per slot: value 0x02 = controller connected, 0x01 = not
+	case 0xB4:
+		// SDL Triton polls this on init; treat like Steam contact so we forward 0x45
+		hostStampAlive();
 		S.resp[0] = 0xB4;
 		S.resp[0] = 0xB4;
 		S.resp[1] = 0x01;
 		S.resp[1] = 0x01;
 		S.resp[2] = (slot == g_connSlot && !g_xbox &&
 		S.resp[2] = (slot == g_connSlot && !g_xbox &&
@@ -311,8 +319,10 @@ static uint16_t handleGet(int slot, uint8_t rid, hid_report_type_t type,
 	(void)rid;
 	(void)rid;
 	if (type != HID_REPORT_TYPE_FEATURE)
 	if (type != HID_REPORT_TYPE_FEATURE)
 		return 0;
 		return 0;
+
+	// host reading feature data => gamepad consumer active (Steam/SDL)
 	if (g_usbMode == MODE_STEAM)
 	if (g_usbMode == MODE_STEAM)
-		hostStampAlive(); // host reading feature data => gamepad consumer active (Steam/SDL)
+		hostStampAlive();
 	Slot &S = g_slot[slot];
 	Slot &S = g_slot[slot];
 	uint16_t n = S.resp_len ? S.resp_len : 63;
 	uint16_t n = S.resp_len ? S.resp_len : 63;
 	if (n > reqlen)
 	if (n > reqlen)
@@ -368,8 +378,9 @@ void SteamPuckController::begin()
 	for (int i = 0; i < NSLOT; i++) {
 	for (int i = 0; i < NSLOT; i++) {
 		hid[i].setReportDescriptor(desc, descLen);
 		hid[i].setReportDescriptor(desc, descLen);
 		hid[i].setReportCallback(GETCB[i], SETCB[i]);
 		hid[i].setReportCallback(GETCB[i], SETCB[i]);
-		hid[i].setPollInterval(
-			1); // 1ms USB poll (was default 10ms = 100/s cap -> choppy)
+
+		// 1ms USB poll (was default 10ms = 100/s cap -> choppy)
+		hid[i].setPollInterval(1);
 		hid[i].begin();
 		hid[i].begin();
 	}
 	}
 }
 }
@@ -475,14 +486,17 @@ void SteamPuckController::task()
 	wakeNudgeTask();
 	wakeNudgeTask();
 	{
 	{
 		static bool wasSusp = false;
 		static bool wasSusp = false;
-		bool susp =
-			USBDevice.suspended(); // stamp the suspended->active edge for the post-resume mute
+
+		// stamp the suspended->active edge for the post-resume mute
+		bool susp = USBDevice.suspended();
 		if (wasSusp && !susp)
 		if (wasSusp && !susp)
 			g_resumeMs = millis();
 			g_resumeMs = millis();
 		wasSusp = susp;
 		wasSusp = susp;
 	}
 	}
+
+	// no periodic 0x79/0x7B while the host sleeps -- those sends can wake it too
 	if (USBDevice.suspended())
 	if (USBDevice.suspended())
-		return; // no periodic 0x79/0x7B while the host sleeps -- those sends can wake it too
+		return;
 	static bool usbConn = false;
 	static bool usbConn = false;
 	static unsigned long last79 = 0, last7B = 0, connEdgeMs = 0;
 	static unsigned long last79 = 0, last7B = 0, connEdgeMs = 0;
 	bool conn = (g_connSlot >= 0 && millis() - g_connReplyMs < 300);
 	bool conn = (g_connSlot >= 0 && millis() - g_connReplyMs < 300);

+ 3 - 2
OpenPuck/puck_hid.h

@@ -26,8 +26,9 @@ class SteamPuckController : public IController {
 	void onAuxReport(uint8_t rid, const uint8_t *data, uint8_t n)
 	void onAuxReport(uint8_t rid, const uint8_t *data, uint8_t n)
 		override; // forward controller 0x43/0x44 status to Steam
 		override; // forward controller 0x43/0x44 status to Steam
 	void task() override;
 	void task() override;
-	void wakeEvent()
-		override; // queue the harmless post-resume wake nudge (mouse jiggle + Ctrl tap)
+
+	// queue the harmless post-resume wake nudge (mouse jiggle + Ctrl tap)
+	void wakeEvent() override;
 	bool isPuck() const override
 	bool isPuck() const override
 	{
 	{
 		return true;
 		return true;

+ 16 - 14
OpenPuck/radio.cpp

@@ -46,11 +46,13 @@ uint32_t g_rfRxCount = 0;
 // ENDIAN=Big (MSB-first), NO whitening, addr "ibex", CRC16 0x11021/0xFFFF; frame = FUN_00027e9a (static length).
 // ENDIAN=Big (MSB-first), NO whitening, addr "ibex", CRC16 0x11021/0xFFFF; frame = FUN_00027e9a (static length).
 uint32_t g_crcinit = 0xFFFF;
 uint32_t g_crcinit = 0xFFFF;
 uint8_t g_whiteiv = 37;
 uint8_t g_whiteiv = 37;
-uint8_t g_mode =
-	RADIO_MODE_MODE_Ble_2Mbit; // <-- was the bug (1Mbit). Real puck = 2Mbit.
-uint32_t g_pcnf0 =
-	0x00030008UL; // S0LEN0, LFLEN8, S1LEN3 (ESB DPL) - CRC-VALIDATED
+// <-- was the bug (1Mbit). Real puck = 2Mbit.
+uint8_t g_mode = RADIO_MODE_MODE_Ble_2Mbit;
+
+// S0LEN0, LFLEN8, S1LEN3 (ESB DPL) - CRC-VALIDATED
+uint32_t g_pcnf0 = 0x00030008UL;
 uint8_t g_statlen = 0x20;
 uint8_t g_statlen = 0x20;
+
 // ENDIAN=Big, BALEN4, MAXLEN=96 (was 64 -> truncated the controller's
 // ENDIAN=Big, BALEN4, MAXLEN=96 (was 64 -> truncated the controller's
 // 66-byte 0x43-battery-augmented F1 -> CRC-fail -> battery never arrived)
 // 66-byte 0x43-battery-augmented F1 -> CRC-fail -> battery never arrived)
 uint32_t g_pcnf1 = 0x01040060;
 uint32_t g_pcnf1 = 0x01040060;
@@ -95,17 +97,17 @@ void rfConfig(uint8_t ch)
 #if defined(RADIO_MODECNF0_RU_Fast)
 #if defined(RADIO_MODECNF0_RU_Fast)
 	NRF_RADIO->MODECNF0 = (RADIO_MODECNF0_RU_Fast << RADIO_MODECNF0_RU_Pos);
 	NRF_RADIO->MODECNF0 = (RADIO_MODECNF0_RU_Fast << RADIO_MODECNF0_RU_Pos);
 #endif
 #endif
-	NRF_RADIO->PCNF0 =
-		g_pcnf0; // static length (tunable '0'); g_pcnf1=0 -> build from g_statlen/g_balen
+	// static length (tunable '0'); g_pcnf1=0 -> build from g_statlen/g_balen
+	NRF_RADIO->PCNF0 = g_pcnf0;
+
+	// ENDIAN=Big (puck transmits MSB-first)
+	// WHITEEN=0
 	NRF_RADIO->PCNF1 =
 	NRF_RADIO->PCNF1 =
-		g_pcnf1 ?
-			g_pcnf1 :
-			((1u
-			  << RADIO_PCNF1_ENDIAN_Pos) // ENDIAN=Big (puck transmits MSB-first)
-			 | ((uint32_t)g_balen << RADIO_PCNF1_BALEN_Pos) |
-			 ((uint32_t)g_statlen << RADIO_PCNF1_STATLEN_Pos) |
-			 ((uint32_t)g_statlen
-			  << RADIO_PCNF1_MAXLEN_Pos)); // WHITEEN=0
+		g_pcnf1 ? g_pcnf1 :
+			  ((1u << RADIO_PCNF1_ENDIAN_Pos) |
+			   ((uint32_t)g_balen << RADIO_PCNF1_BALEN_Pos) |
+			   ((uint32_t)g_statlen << RADIO_PCNF1_STATLEN_Pos) |
+			   ((uint32_t)g_statlen << RADIO_PCNF1_MAXLEN_Pos));
 	NRF_RADIO->CRCCNF = g_crccnf; // CRC16, address included
 	NRF_RADIO->CRCCNF = g_crccnf; // CRC16, address included
 	NRF_RADIO->CRCPOLY = g_crcpoly;
 	NRF_RADIO->CRCPOLY = g_crcpoly;
 	NRF_RADIO->CRCINIT = g_crcinit;
 	NRF_RADIO->CRCINIT = g_crcinit;

+ 6 - 6
OpenPuck/radio.h

@@ -28,8 +28,8 @@ extern const uint8_t PAIR_BASE[4]; // "ibex"
 extern uint8_t g_rfPrefix; // discovery prefix (rodata byte @0x56f98)
 extern uint8_t g_rfPrefix; // discovery prefix (rodata byte @0x56f98)
 extern uint8_t g_rfCh; // current TX/RX channel (hopped during beacon/poll)
 extern uint8_t g_rfCh; // current TX/RX channel (hopped during beacon/poll)
 extern uint8_t g_rfBase[4]; // "ibex"
 extern uint8_t g_rfBase[4]; // "ibex"
-extern uint8_t
-	g_sessCh; // connected-session channel: a CLEAN data channel off the congested adv ch2
+// connected-session channel: a CLEAN data channel off the congested adv ch2
+extern uint8_t g_sessCh;
 
 
 // Per-device SESSION address (base+prefix). Discovery/rendezvous stays on the SHARED "ibex" address (g_rfBase)
 // Per-device SESSION address (base+prefix). Discovery/rendezvous stays on the SHARED "ibex" address (g_rfBase)
 // so any controller can find us; the host frame then advertises THIS unique address and the controller adopts
 // so any controller can find us; the host frame then advertises THIS unique address and the controller adopts
@@ -39,8 +39,8 @@ extern uint8_t g_sessBase[4];
 extern uint8_t g_sessPrefix;
 extern uint8_t g_sessPrefix;
 void rfGenSessionAddr();
 void rfGenSessionAddr();
 
 
-extern uint8_t rfrx[100],
-	rftx[100]; // RADIO DMA buffers (>= MAXLEN+2; the controller's 0x43-augmented F1 is ~66B)
+// RADIO DMA buffers (>= MAXLEN+2; the controller's 0x43-augmented F1 is ~66B)
+extern uint8_t rfrx[100], rftx[100];
 extern uint32_t g_rfRxCount;
 extern uint32_t g_rfRxCount;
 
 
 // ---- tunable radio parameters (CDC console M/0/2/i/w/I/P/N/T/A) ----
 // ---- tunable radio parameters (CDC console M/0/2/i/w/I/P/N/T/A) ----
@@ -54,8 +54,8 @@ extern uint32_t g_crcpoly;
 extern uint16_t g_crccnf; // CRC16, address included
 extern uint16_t g_crccnf; // CRC16, address included
 extern uint8_t g_pid;
 extern uint8_t g_pid;
 extern uint8_t g_balen; // ESB 5-byte addr
 extern uint8_t g_balen; // ESB 5-byte addr
-extern bool
-	g_prefixRaw; // false = bitrev8 prefix (ESB addr_conv); true = raw prefix byte
+// false = bitrev8 prefix (ESB addr_conv); true = raw prefix byte
+extern bool g_prefixRaw;
 
 
 uint8_t rfBitrev8(uint8_t x);
 uint8_t rfBitrev8(uint8_t x);
 // Access address (IBEX FUN_00037530): BASE0 = bitrev8 each base byte packed big-endian; PREFIX0 = raw or
 // Access address (IBEX FUN_00037530): BASE0 = bitrev8 each base byte packed big-endian; PREFIX0 = raw or

+ 11 - 16
OpenPuck/rf_diag.cpp

@@ -414,16 +414,14 @@ static void rfRespondPoll()
 // RX that session address on the primary channel to capture the live connected exchange (E3 poll + F1 reply).
 // RX that session address on the primary channel to capture the live connected exchange (E3 poll + F1 reply).
 void rfSniffStart()
 void rfSniffStart()
 {
 {
+	// PARK (no hop): primary carries the traffic
 	uint8_t ch =
 	uint8_t ch =
-		(g_sniffPh == 0) ?
-			2 :
-			(g_sniffPark ?
-				 g_sniffPark :
-				 g_schan[0]); // PARK (no hop): primary carries the traffic
+		(g_sniffPh == 0) ? 2 : (g_sniffPark ? g_sniffPark : g_schan[0]);
 	const uint8_t *base = (g_sniffPh == 0) ? g_rfBase : g_sbase;
 	const uint8_t *base = (g_sniffPh == 0) ? g_rfBase : g_sbase;
 	uint8_t pfx = (g_sniffPh == 0) ? g_rfPrefix : g_sprefix;
 	uint8_t pfx = (g_sniffPh == 0) ? g_rfPrefix : g_sprefix;
-	rfConfig(
-		ch); // Ble_2Mbit, PCNF0=0x30008, ENDIAN big, BALEN4, CRC16 0x11021
+
+	// Ble_2Mbit, PCNF0=0x30008, ENDIAN big, BALEN4, CRC16 0x11021
+	rfConfig(ch);
 	uint8_t b[4];
 	uint8_t b[4];
 	for (int i = 0; i < 4; i++)
 	for (int i = 0; i < 4; i++)
 		b[i] = rfBitrev8(base[i]);
 		b[i] = rfBitrev8(base[i]);
@@ -449,9 +447,8 @@ static void rfSniffPoll()
 		uint8_t len = rfrx[0];
 		uint8_t len = rfrx[0];
 		if (crcok && len && len < 40) {
 		if (crcok && len && len < 40) {
 			g_sniffN++;
 			g_sniffN++;
-			if (g_sniffPh == 0 &&
-			    rfrx[2] ==
-				    0xE1) { // host frame -> learn session params + DUMP FULL (app-data may ride here)
+			// host frame -> learn session params + DUMP FULL (app-data may ride here)
+			if (g_sniffPh == 0 && rfrx[2] == 0xE1) {
 				memcpy(g_sbase, rfrx + 15, 4);
 				memcpy(g_sbase, rfrx + 15, 4);
 				g_sprefix = rfrx[19];
 				g_sprefix = rfrx[19];
 				g_schan[0] = rfrx[11];
 				g_schan[0] = rfrx[11];
@@ -470,8 +467,8 @@ static void rfSniffPoll()
 				g_schi = 0;
 				g_schi = 0;
 				rfSniffStart();
 				rfSniffStart();
 			} else if (g_sniffPh == 1) {
 			} else if (g_sniffPh == 1) {
-				uint8_t ty =
-					rfrx[2]; // 0xF1=input, 0x12/0xE1/0xE4=puck host-frame w/ app-data
+				// 0xF1=input, 0x12/0xE1/0xE4=puck host-frame w/ app-data
+				uint8_t ty = rfrx[2];
 				const char *tag = (ty == 0xF1) ? "<<<INPUT" :
 				const char *tag = (ty == 0xF1) ? "<<<INPUT" :
 						  (ty == 0x12 || ty == 0xE1 ||
 						  (ty == 0x12 || ty == 0xE1 ||
 						   ty == 0xE4) ?
 						   ty == 0xE4) ?
@@ -480,10 +477,8 @@ static void rfSniffPoll()
 				Serial.printf("%s SNIFF#%lu ch%u len%u: ", tag,
 				Serial.printf("%s SNIFF#%lu ch%u len%u: ", tag,
 					      (unsigned long)g_sniffN,
 					      (unsigned long)g_sniffN,
 					      g_schan[g_schi], len);
 					      g_schan[g_schi], len);
-				uint8_t dn =
-					(len + 2 < 64) ?
-						len + 2 :
-						64; // FULL frame (app-data TLVs sit near the end)
+				// FULL frame (app-data TLVs sit near the end)
+				uint8_t dn = (len + 2 < 64) ? len + 2 : 64;
 				for (uint8_t i = 0; i < dn; i++)
 				for (uint8_t i = 0; i < dn; i++)
 					Serial.printf("%02X", rfrx[i]);
 					Serial.printf("%02X", rfrx[i]);
 				Serial.println();
 				Serial.println();

+ 2 - 1
OpenPuck/rf_diag.h

@@ -31,7 +31,8 @@ void rfReplayOnce(); // bit-exact re-transmit of a captured real-puck frame
 void rfBeaconOnce(); // dongle beacon experiment ([01][seq][..][E2@5])
 void rfBeaconOnce(); // dongle beacon experiment ([01][seq][..][E2@5])
 void applyCfg(uint8_t i); // apply autosweep candidate radio config i
 void applyCfg(uint8_t i); // apply autosweep candidate radio config i
 void rfRespondStart(); // scan-then-respond (RX ch2 adv, reply host frame)
 void rfRespondStart(); // scan-then-respond (RX ch2 adv, reply host frame)
-void rfSniffStart(); // session sniffer (learn session params, then capture the live exchange)
+// session sniffer (learn session params, then capture the live exchange)
+void rfSniffStart();
 
 
 // Per-loop: service whichever diag mode is active.
 // Per-loop: service whichever diag mode is active.
 void rfDiagTask();
 void rfDiagTask();

+ 114 - 104
OpenPuck/rf_link.cpp

@@ -13,16 +13,16 @@
 bool g_rfHost = true;
 bool g_rfHost = true;
 bool g_connOn = true;
 bool g_connOn = true;
 uint8_t g_connType = 0xE7; // start with protocol-version handshake, then 0xE3
 uint8_t g_connType = 0xE7; // start with protocol-version handshake, then 0xE3
-uint8_t g_e7b =
-	0; // 0=current(slow/awake), 1=test protocol-version-1. 'V<n>' to toggle.
+// 0=current(slow/awake), 1=test protocol-version-1. 'V<n>' to toggle.
+uint8_t g_e7b = 0;
 uint8_t g_connLen = 0x08;
 uint8_t g_connLen = 0x08;
-uint8_t g_getParam =
-	0x00; // GET report 0x45 param byte (try 0x00, fall back 0x2D) - 'q' cmd
-uint8_t g_e3mode =
-	1; // DEFAULT 1: cycling the ESB PID drains the controller's report queue (~400 new/s vs ~60 with a fixed PID) -- THE rate fix. 'e<n>' to A/B.
+// GET report 0x45 param byte (try 0x00, fall back 0x2D) - 'q' cmd
+uint8_t g_getParam = 0x00;
+// DEFAULT 1: cycling the ESB PID drains the controller's report queue (~400 new/s vs ~60 with a fixed PID) -- THE rate fix. 'e<n>' to A/B.
+uint8_t g_e3mode = 1;
 bool g_connVerbose = false;
 bool g_connVerbose = false;
-uint32_t g_rxWin =
-	1200; // poll RX-window (us); shorter=more polls/s but may miss DELAYED replies. Tunable 'r'.
+// poll RX-window (us); shorter=more polls/s but may miss DELAYED replies. Tunable 'r'.
+uint32_t g_rxWin = 1200;
 unsigned long g_connCooldown = 0;
 unsigned long g_connCooldown = 0;
 
 
 uint8_t g_connSt = 0; // 0=announce awake, 1=poll loop
 uint8_t g_connSt = 0; // 0=announce awake, 1=poll loop
@@ -40,16 +40,16 @@ unsigned long g_qosCheckMs = 0, g_qosLastHopMs = 0;
 
 
 uint16_t g_f1ps = 0;
 uint16_t g_f1ps = 0;
 uint16_t g_newps = 0;
 uint16_t g_newps = 0;
-uint16_t g_pollsps =
-	0; // polls/s (GET+relay TXs) last second -- distinguishes loop-starvation from reply-loss
-uint16_t g_pollPeriodUs =
-	0; // MEASURED avg us between GET-poll fires (vs intended g_pollUs) -- ground truth
+// polls/s (GET+relay TXs) last second -- distinguishes loop-starvation from reply-loss
+uint16_t g_pollsps = 0;
+// MEASURED avg us between GET-poll fires (vs intended g_pollUs) -- ground truth
+uint16_t g_pollPeriodUs = 0;
 static uint32_t g_pollDtSum = 0;
 static uint32_t g_pollDtSum = 0;
 static uint16_t g_pollDtCnt = 0;
 static uint16_t g_pollDtCnt = 0;
-volatile uint8_t g_linkRssi =
-	0; // smoothed |dBm| of the controller's replies (0 = none yet)
-volatile uint8_t g_battery =
-	0; // battery % from the controller's report 0x43 (body[1]); 0 = none yet
+// smoothed |dBm| of the controller's replies (0 = none yet)
+volatile uint8_t g_linkRssi = 0;
+// battery % from the controller's report 0x43 (body[1]); 0 = none yet
+volatile uint8_t g_battery = 0;
 
 
 // ---- internal counters / timers ----
 // ---- internal counters / timers ----
 static uint8_t g_e3pid = 0;
 static uint8_t g_e3pid = 0;
@@ -75,26 +75,26 @@ static void rfHostFrameOnce(int slot, bool discovery)
 {
 {
 	if (slot < 0 || slot >= NSLOT || !g_slot[slot].used)
 	if (slot < 0 || slot >= NSLOT || !g_slot[slot].used)
 		return;
 		return;
-	uint8_t *rec =
-		g_slot[slot].rec; // [proteus_uuid 4][ibex_uuid 4][serial 16]
+	// [proteus_uuid 4][ibex_uuid 4][serial 16]
+	uint8_t *rec = g_slot[slot].rec;
 	// CRC-VALIDATED frame (decoded from real puck): ESB-DPL RAM = [LENGTH][S1=PID][payload(18)]. payload:
 	// CRC-VALIDATED frame (decoded from real puck): ESB-DPL RAM = [LENGTH][S1=PID][payload(18)]. payload:
 	// [0]=0xE1, [1..5]=proteus_uuid LE, [5..9]=ibex_uuid LE, [9]=session channel, [10..13]=0, [13..17]=session
 	// [0]=0xE1, [1..5]=proteus_uuid LE, [5..9]=ibex_uuid LE, [9]=session channel, [10..13]=0, [13..17]=session
 	// base, [17]=session prefix. Radio auto-appends CRC16 0x11021.
 	// base, [17]=session prefix. Radio auto-appends CRC16 0x11021.
 	memset(rftx, 0, sizeof rftx);
 	memset(rftx, 0, sizeof rftx);
-	rftx[0] =
-		0x12; // LENGTH = 18 (controller's buf[0]==0x12 check validates this)
-	rftx[1] =
-		(uint8_t)((g_pid++ & 3)
-			  << 1); // S1 = PID<<1 | noack0  (matches real puck 00/02/04/06)
+	// LENGTH = 18 (controller's buf[0]==0x12 check validates this)
+	rftx[0] = 0x12;
+	// S1 = PID<<1 | noack0  (matches real puck 00/02/04/06)
+	rftx[1] = (uint8_t)((g_pid++ & 3) << 1);
 	rftx[2] = 0xE1; // payload[0] marker
 	rftx[2] = 0xE1; // payload[0] marker
-	memcpy(rftx + 3, rec + 0,
-	       4); // payload[1..5] proteus_uuid (LE, as bonded)
+	// payload[1..5] proteus_uuid (LE, as bonded)
+	memcpy(rftx + 3, rec + 0, 4);
 	memcpy(rftx + 7, rec + 4, 4); // payload[5..9] ibex_uuid
 	memcpy(rftx + 7, rec + 4, 4); // payload[5..9] ibex_uuid
+
 	// payload[9] session channel: tell the controller to run the session on
 	// payload[9] session channel: tell the controller to run the session on
 	// the clean channel (it adopts buf[0xe]); discovery beacon still TXes on ch2
 	// the clean channel (it adopts buf[0xe]); discovery beacon still TXes on ch2
 	rftx[11] = g_sessCh;
 	rftx[11] = g_sessCh;
-	memcpy(rftx + 15, g_sessBase,
-	       4); // payload[13..17] session base  (the per-device UNIQUE address)
+	// payload[13..17] session base  (the per-device UNIQUE address)
+	memcpy(rftx + 15, g_sessBase, 4);
 	rftx[19] = g_sessPrefix; // payload[17] session prefix
 	rftx[19] = g_sessPrefix; // payload[17] session prefix
 	// TX address: discovery uses the shared "ibex" rendezvous; the session keepalive uses our unique address
 	// TX address: discovery uses the shared "ibex" rendezvous; the session keepalive uses our unique address
 	// (where the controller now listens). The advertised session params (above) are identical either way.
 	// (where the controller now listens). The advertised session params (above) are identical either way.
@@ -122,13 +122,13 @@ static void rfHostFrameOnce(int slot, bool discovery)
 	while (!NRF_RADIO->EVENTS_END && (micros() - t0) < bwin) {
 	while (!NRF_RADIO->EVENTS_END && (micros() - t0) < bwin) {
 	}
 	}
 	if (NRF_RADIO->EVENTS_END) {
 	if (NRF_RADIO->EVENTS_END) {
-		NRF_RADIO->EVENTS_END =
-			0; // ANY reception = controller answered our frame
+		// ANY reception = controller answered our frame
+		NRF_RADIO->EVENTS_END = 0;
 		g_rfRxCount++;
 		g_rfRxCount++;
 		bool crcok = NRF_RADIO->CRCSTATUS & 1;
 		bool crcok = NRF_RADIO->CRCSTATUS & 1;
 		uint8_t len = rfrx[0];
 		uint8_t len = rfrx[0];
-		if (Serial.availableForWrite() >
-		    90) { // non-blocking: don't stall the loop on CDC backpressure
+		// non-blocking: don't stall the loop on CDC backpressure
+		if (Serial.availableForWrite() > 90) {
 			Serial.printf(
 			Serial.printf(
 				"*** RESP#%lu ch%u crc%d rxmatch%lu len%u: ",
 				"*** RESP#%lu ch%u crc%d rxmatch%lu len%u: ",
 				(unsigned long)g_rfRxCount, g_rfCh, crcok,
 				(unsigned long)g_rfRxCount, g_rfCh, crcok,
@@ -149,8 +149,8 @@ void rfHopTo(uint8_t newCh)
 		return;
 		return;
 	uint8_t cur = g_sessCh, savedRfCh = g_rfCh;
 	uint8_t cur = g_sessCh, savedRfCh = g_rfCh;
 	g_sessCh = newCh;
 	g_sessCh = newCh;
-	g_rfCh =
-		cur; // host frame now advertises newCh but is TXed on cur (session addr)
+	// host frame now advertises newCh but is TXed on cur (session addr)
+	g_rfCh = cur;
 	for (int k = 0; k < 6; k++) {
 	for (int k = 0; k < 6; k++) {
 		rfHostFrameOnce(g_connSlot, false);
 		rfHostFrameOnce(g_connSlot, false);
 		delayMicroseconds(700);
 		delayMicroseconds(700);
@@ -161,18 +161,15 @@ void rfHopTo(uint8_t newCh)
 uint8_t rfConnTx(uint8_t ch, uint8_t s1, const uint8_t *payload, uint8_t plen,
 uint8_t rfConnTx(uint8_t ch, uint8_t s1, const uint8_t *payload, uint8_t plen,
 		 uint16_t rxWinUs)
 		 uint16_t rxWinUs)
 {
 {
-	uint16_t win =
-		rxWinUs ?
-			rxWinUs :
-			g_rxWin; // relays pass a tiny window (no reply expected); polls use g_rxWin
+	// relays pass a tiny window (no reply expected); polls use g_rxWin
+	uint16_t win = rxWinUs ? rxWinUs : g_rxWin;
 	memset(rftx, 0, sizeof rftx);
 	memset(rftx, 0, sizeof rftx);
 	rftx[0] = plen; // LENGTH = payload byte count
 	rftx[0] = plen; // LENGTH = payload byte count
 	rftx[1] = s1; // S1 (type-specific)
 	rftx[1] = s1; // S1 (type-specific)
 	memcpy(rftx + 2, payload, plen); // payload[0]=type byte, then data/TLVs
 	memcpy(rftx + 2, payload, plen); // payload[0]=type byte, then data/TLVs
 	rfConfig(ch);
 	rfConfig(ch);
-	rfSetAddr(
-		g_sessBase,
-		g_sessPrefix); // connected poll runs on this puck's UNIQUE session addr
+	// connected poll runs on this puck's UNIQUE session addr
+	rfSetAddr(g_sessBase, g_sessPrefix);
 	NRF_RADIO->PACKETPTR = (uint32_t)rftx;
 	NRF_RADIO->PACKETPTR = (uint32_t)rftx;
 	NRF_RADIO->SHORTS = RADIO_SHORTS_READY_START_Msk |
 	NRF_RADIO->SHORTS = RADIO_SHORTS_READY_START_Msk |
 			    RADIO_SHORTS_END_DISABLE_Msk;
 			    RADIO_SHORTS_END_DISABLE_Msk;
@@ -198,22 +195,22 @@ uint8_t rfConnTx(uint8_t ch, uint8_t s1, const uint8_t *payload, uint8_t plen,
 		NRF_RADIO->EVENTS_END = 0;
 		NRF_RADIO->EVENTS_END = 0;
 		bool crcok = NRF_RADIO->CRCSTATUS & 1;
 		bool crcok = NRF_RADIO->CRCSTATUS & 1;
 		rxlen = rfrx[0];
 		rxlen = rfrx[0];
+		// reply arrived but CRC failed -> RF quality (channel/interference)
 		if (!crcok) {
 		if (!crcok) {
 			g_stCrc++;
 			g_stCrc++;
 			g_qosBad++;
 			g_qosBad++;
-		} // reply arrived but CRC failed -> RF quality (channel/interference)
-		if (crcok && rxlen &&
-		    rxlen <=
-			    96) { // F1 input ~46B; 0x43-augmented ~66B -> allow up to MAXLEN(96)
-			uint8_t rtype =
-				rfrx[2]; // reply type byte (proven offset from captures)
+		}
+		// F1 input ~46B; 0x43-augmented ~66B -> allow up to MAXLEN(96)
+		if (crcok && rxlen && rxlen <= 96) {
+			// reply type byte (proven offset from captures)
+			uint8_t rtype = rfrx[2];
 			// Only OUR controller's replies (F-type: 0xF1 input / 0xF2 disconnect / 0xF3 status) mark the link
 			// Only OUR controller's replies (F-type: 0xF1 input / 0xF2 disconnect / 0xF3 status) mark the link
 			// alive. Every OpenPuck shares the same RF address "ibex" + CRC config, and a puck transmits host-frame
 			// alive. Every OpenPuck shares the same RF address "ibex" + CRC config, and a puck transmits host-frame
 			// beacons (0xE1) + polls (0xE2/E3/E7) -- all E-type. Without this gate, puck A receives a SECOND puck's
 			// beacons (0xE1) + polls (0xE2/E3/E7) -- all E-type. Without this gate, puck A receives a SECOND puck's
 			// 0xE1 beacon (e.g. one just plugged into another computer), bumps g_connReplyMs, and the "new RF
 			// 0xE1 beacon (e.g. one just plugged into another computer), bumps g_connReplyMs, and the "new RF
 			// connection" wake in rfLinkTask() fires -> the second puck spuriously wakes this sleeping host.
 			// connection" wake in rfLinkTask() fires -> the second puck spuriously wakes this sleeping host.
-			if (rtype >=
-			    0xF0) { // F-type reply (F1/F2/F3) -> our controller is alive
+			// F-type reply (F1/F2/F3) -> our controller is alive
+			if (rtype >= 0xF0) {
 				// A reply after a long gap (or the first ever) = a (re)connect. Arm the haptic block + re-init HERE,
 				// A reply after a long gap (or the first ever) = a (re)connect. Arm the haptic block + re-init HERE,
 				// directly off the reply stream -- reliable even when hapticTask's 300ms link-up edge doesn't fire
 				// directly off the reply stream -- reliable even when hapticTask's 300ms link-up edge doesn't fire
 				// (e.g. a power-cycled controller that reconnects without us cleanly seeing the link drop).
 				// (e.g. a power-cycled controller that reconnects without us cleanly seeing the link drop).
@@ -222,11 +219,12 @@ uint8_t rfConnTx(uint8_t ch, uint8_t s1, const uint8_t *payload, uint8_t plen,
 					    1500u)
 					    1500u)
 					hapticOnReconnect();
 					hapticOnReconnect();
 				g_connRx++;
 				g_connRx++;
-				g_connReplyMs =
-					millis(); // link alive -> loop() suppresses the redundant E1 beacon
+				// link alive -> loop() suppresses the redundant E1 beacon
+				g_connReplyMs = millis();
+				// |dBm| of this reply (started by the ADDRESS short)
 				uint8_t rs =
 				uint8_t rs =
-					(uint8_t)(NRF_RADIO->RSSISAMPLE &
-						  0x7F); // |dBm| of this reply (started by the ADDRESS short)
+					(uint8_t)(NRF_RADIO->RSSISAMPLE & 0x7F);
+				// EWMA, ~8-sample horizon
 				if (rs)
 				if (rs)
 					g_linkRssi =
 					g_linkRssi =
 						g_linkRssi ?
 						g_linkRssi ?
@@ -234,15 +232,15 @@ uint8_t rfConnTx(uint8_t ch, uint8_t s1, const uint8_t *payload, uint8_t plen,
 									   7u +
 									   7u +
 								   rs + 4u) /
 								   rs + 4u) /
 								  8u) :
 								  8u) :
-							rs; // EWMA, ~8-sample horizon
+							rs;
 			}
 			}
 			if (rtype == 0xF1)
 			if (rtype == 0xF1)
 				g_stF1++;
 				g_stF1++;
+			// controller disconnecting/powering off -> back off 2.5s
 			if (rtype == 0xF2)
 			if (rtype == 0xF2)
-				g_connCooldown =
-					millis(); // controller disconnecting/powering off -> back off 2.5s
-			if (rtype ==
-			    0xF3) { // F3 = controller status/version reply (reply to E7 handshake, byte[6]=version)
+				g_connCooldown = millis();
+			// F3 = controller status/version reply (reply to E7 handshake, byte[6]=version)
+			if (rtype == 0xF3) {
 				g_stF3++;
 				g_stF3++;
 				g_connF3v = rfrx[6];
 				g_connF3v = rfrx[6];
 				if (g_connVerbose &&
 				if (g_connVerbose &&
@@ -259,17 +257,16 @@ uint8_t rfConnTx(uint8_t ch, uint8_t s1, const uint8_t *payload, uint8_t plen,
 			bool isF1 = (rtype == 0xF1);
 			bool isF1 = (rtype == 0xF1);
 			if (isF1) {
 			if (isF1) {
 				g_connF1++;
 				g_connF1++;
-				int idx = 3,
-				    end = rxlen +
-					  2; // walk ALL type6 TLVs (= HID report 0x45). idx is INT, not
+				// walk ALL type6 TLVs (= HID report 0x45). idx is INT, not
+				int idx = 3, end = rxlen + 2;
 				const uint8_t *lastRep = nullptr;
 				const uint8_t *lastRep = nullptr;
-				uint8_t lastTlen =
-					0; // uint8_t: a tlen of 0xFE would make idx+=tlen+2 wrap
-				while (idx + 1 <
-				       end) { // mod-256 back to itself -> infinite loop -> USB hang/"crash".
+				// uint8_t: a tlen of 0xFE would make idx+=tlen+2 wrap
+				uint8_t lastTlen = 0;
+				// mod-256 back to itself -> infinite loop -> USB hang/"crash".
+				while (idx + 1 < end) {
+					// pace with the real puck -- taking only [0] halved our rate.
 					uint8_t tlen = rfrx[idx],
 					uint8_t tlen = rfrx[idx],
-						ttype = rfrx[idx +
-							     1]; // pace with the real puck -- taking only [0] halved our rate.
+						ttype = rfrx[idx + 1];
 					if (tlen == 0)
 					if (tlen == 0)
 						break;
 						break;
 					// Only a FULL 0x45 report that fits entirely in rfrx: a short or late/garbled TLV must not let the
 					// Only a FULL 0x45 report that fits entirely in rfrx: a short or late/garbled TLV must not let the
@@ -278,15 +275,16 @@ uint8_t rfConnTx(uint8_t ch, uint8_t s1, const uint8_t *payload, uint8_t plen,
 					    (size_t)(idx + 2) + tlen <=
 					    (size_t)(idx + 2) + tlen <=
 						    sizeof(rfrx) &&
 						    sizeof(rfrx) &&
 					    rfrx[idx + 2] == 0x45) {
 					    rfrx[idx + 2] == 0x45) {
+						// report 0x45: [0x45][seq][buttons u32]...
 						const uint8_t *rep =
 						const uint8_t *rep =
-							&rfrx[idx +
-							      2]; // report 0x45: [0x45][seq][buttons u32]...
+							&rfrx[idx + 2];
 						bool fresh =
 						bool fresh =
 							(rep[1] != g_lastSeq);
 							(rep[1] != g_lastSeq);
+						// genuine new report vs stale poll-repeat
 						if (fresh) {
 						if (fresh) {
 							g_stNew++;
 							g_stNew++;
 							g_lastSeq = rep[1];
 							g_lastSeq = rep[1];
-						} // genuine new report vs stale poll-repeat
+						}
 						uint32_t bb = btnsOf(rep);
 						uint32_t bb = btnsOf(rep);
 						// USB remote wakeup on Steam button short press (down + up within 1 s). A long press likely means
 						// USB remote wakeup on Steam button short press (down + up within 1 s). A long press likely means
 						// the user is powering off the controller, so we ignore it.
 						// the user is powering off the controller, so we ignore it.
@@ -300,21 +298,24 @@ uint8_t rfConnTx(uint8_t ch, uint8_t s1, const uint8_t *payload, uint8_t plen,
 									(bb &
 									(bb &
 									 TB_STEAM) !=
 									 TB_STEAM) !=
 									0;
 									0;
+								// rising edge: record press time
 								if (steamNow &&
 								if (steamNow &&
 								    !steamWasDown)
 								    !steamWasDown)
 									steamDownMs =
 									steamDownMs =
-										millis(); // rising edge: record press time
+										millis();
+								// falling edge within 1 s -> short press -> wake
 								if (!steamNow &&
 								if (!steamNow &&
 								    steamWasDown &&
 								    steamWasDown &&
 								    millis() - steamDownMs <
 								    millis() - steamDownMs <
 									    1000u &&
 									    1000u &&
 								    USBDevice
 								    USBDevice
-									    .suspended()) { // falling edge within 1 s -> short press -> wake
+									    .suspended()) {
 									USBDevice
 									USBDevice
 										.remoteWakeup();
 										.remoteWakeup();
 									ledWakePulse();
 									ledWakePulse();
+									// post-resume nudge (host needs real input to actually wake)
 									if (g_active)
 									if (g_active)
-										g_active->wakeEvent(); // post-resume nudge (host needs real input to actually wake)
+										g_active->wakeEvent();
 								}
 								}
 								steamWasDown =
 								steamWasDown =
 									steamNow;
 									steamNow;
@@ -350,19 +351,22 @@ uint8_t rfConnTx(uint8_t ch, uint8_t s1, const uint8_t *payload, uint8_t plen,
 								}
 								}
 								if (g_usbMode !=
 								if (g_usbMode !=
 								    MODE_STEAM) {
 								    MODE_STEAM) {
-									g_in.buttons &= ~(
-										uint32_t)(TB_STEAM |
-											  TB_Y); // stream modes read g_in
+									// stream modes read g_in
+									g_in.buttons &=
+										~(uint32_t)(TB_STEAM |
+											    TB_Y);
+									// push modes read btnsOf(rep): TB_Y in rep[2],
 									((uint8_t *)
 									((uint8_t *)
 										 rep)
 										 rep)
 										[2] &=
 										[2] &=
 										~(uint8_t)
 										~(uint8_t)
-											TB_Y; // push modes read btnsOf(rep): TB_Y in rep[2],
+											TB_Y;
+									// TB_STEAM in rep[4]
 									((uint8_t *)
 									((uint8_t *)
 										 rep)
 										 rep)
 										[4] &=
 										[4] &=
 										~(uint8_t)(TB_STEAM >>
 										~(uint8_t)(TB_STEAM >>
-											   16); // TB_STEAM in rep[4]
+											   16);
 								}
 								}
 							} else {
 							} else {
 								offHoldMs = 0;
 								offHoldMs = 0;
@@ -380,9 +384,9 @@ uint8_t rfConnTx(uint8_t ch, uint8_t s1, const uint8_t *payload, uint8_t plen,
 									  14);
 									  14);
 						g_in.lt =
 						g_in.lt =
 							trigU8(u16off(rep, 4));
 							trigU8(u16off(rep, 4));
-						g_in.rt = trigU8(u16off(
-							rep,
-							6)); // for the Switch digital-trigger threshold
+						// for the Switch digital-trigger threshold
+						g_in.rt =
+							trigU8(u16off(rep, 6));
 						g_in.lpx = (int16_t)s16off(rep,
 						g_in.lpx = (int16_t)s16off(rep,
 									   16);
 									   16);
 						g_in.lpy = (int16_t)s16off(rep,
 						g_in.lpy = (int16_t)s16off(rep,
@@ -431,12 +435,12 @@ uint8_t rfConnTx(uint8_t ch, uint8_t s1, const uint8_t *payload, uint8_t plen,
 						// Controller STATUS reports (0x43 = periodic power/battery, ~every 2s; 0x44 = status event). The real
 						// Controller STATUS reports (0x43 = periodic power/battery, ~every 2s; 0x44 = status event). The real
 						// puck forwards these to the host verbatim -- that's how Steam reads battery -- but OpenPuck used to
 						// puck forwards these to the host verbatim -- that's how Steam reads battery -- but OpenPuck used to
 						// drop everything but 0x45. Forward them (onAuxReport), and snapshot the battery % for the WebUSB panel.
 						// drop everything but 0x45. Forward them (onAuxReport), and snapshot the battery % for the WebUSB panel.
+						// [rid][body...]
 						const uint8_t *rep =
 						const uint8_t *rep =
-							&rfrx[idx +
-							      2]; // [rid][body...]
+							&rfrx[idx + 2];
+						// 0x43 body[1] (~0x5e=94) reads as battery % (sniff-derived)
 						if (rep[0] == 0x43 && tlen >= 3)
 						if (rep[0] == 0x43 && tlen >= 3)
-							g_battery =
-								rep[2]; // 0x43 body[1] (~0x5e=94) reads as battery % (sniff-derived)
+							g_battery = rep[2];
 						if (g_active)
 						if (g_active)
 							g_active->onAuxReport(
 							g_active->onAuxReport(
 								rep[0], rep + 1,
 								rep[0], rep + 1,
@@ -503,10 +507,11 @@ uint8_t rfConnTx(uint8_t ch, uint8_t s1, const uint8_t *payload, uint8_t plen,
 			}
 			}
 		} else
 		} else
 			rxlen = 0;
 			rxlen = 0;
+		// RX window expired with no packet at all
 	} else {
 	} else {
 		g_stNoRx++;
 		g_stNoRx++;
 		g_qosBad++;
 		g_qosBad++;
-	} // RX window expired with no packet at all
+	}
 	NRF_RADIO->TASKS_DISABLE = 1;
 	NRF_RADIO->TASKS_DISABLE = 1;
 	RWAIT_DISABLED();
 	RWAIT_DISABLED();
 	NRF_RADIO->EVENTS_DISABLED = 0;
 	NRF_RADIO->EVENTS_DISABLED = 0;
@@ -523,10 +528,12 @@ static void rfConnStep()
 			g_connSlot = s;
 			g_connSlot = s;
 			break;
 			break;
 		}
 		}
+	// need a bonded slot (session established by host frame)
 	if (g_connSlot < 0)
 	if (g_connSlot < 0)
-		return; // need a bonded slot (session established by host frame)
+		return;
 	uint8_t ch = g_sessCh;
 	uint8_t ch = g_sessCh;
-	if (g_connSt == 0) { // announce HOST AWAKE: E7 00 00, a few times
+	// announce HOST AWAKE: E7 00 00, a few times
+	if (g_connSt == 0) {
 		uint8_t p[3] = { 0xE7, 0x00, g_e7b };
 		uint8_t p[3] = { 0xE7, 0x00, g_e7b };
 		rfConnTx(ch, 0x01, p, 3);
 		rfConnTx(ch, 0x01, p, 3);
 		if (++g_connStep >= 4) {
 		if (++g_connStep >= 4) {
@@ -535,9 +542,11 @@ static void rfConnStep()
 			Serial.println(
 			Serial.println(
 				"# CONN: awake announced -> polling GET report 0x45");
 				"# CONN: awake announced -> polling GET report 0x45");
 		}
 		}
-	} else { // poll loop: E3 + GET report 0x45 every poll; re-assert awake periodically
+		// poll loop: E3 + GET report 0x45 every poll; re-assert awake periodically
+	} else {
+		// CONTROLLED CADENCE: poll ~every g_pollUs
 		if ((uint32_t)(micros() - g_lastPollUs) < g_pollUs)
 		if ((uint32_t)(micros() - g_lastPollUs) < g_pollUs)
-			return; // CONTROLLED CADENCE: poll ~every g_pollUs
+			return;
 		// FIXED-RATE schedule: advance the deadline by exactly one interval. The old `g_lastPollUs = micros()`
 		// FIXED-RATE schedule: advance the deadline by exactly one interval. The old `g_lastPollUs = micros()`
 		// reset the reference to AFTER the gate passed, so each cycle's ~1ms of poll work + loop jitter was added
 		// reset the reference to AFTER the gate passed, so each cycle's ~1ms of poll work + loop jitter was added
 		// to the next interval and never reclaimed -- the period drifted to ~5000us (200/s) instead of 4000us
 		// to the next interval and never reclaimed -- the period drifted to ~5000us (200/s) instead of 4000us
@@ -546,15 +555,16 @@ static void rfConnStep()
 		{
 		{
 			uint32_t now = micros();
 			uint32_t now = micros();
 			static uint32_t lastFire = 0;
 			static uint32_t lastFire = 0;
+			// MEASURED actual period
 			if (lastFire) {
 			if (lastFire) {
 				g_pollDtSum += (uint32_t)(now - lastFire);
 				g_pollDtSum += (uint32_t)(now - lastFire);
 				g_pollDtCnt++;
 				g_pollDtCnt++;
-			} // MEASURED actual period
+			}
 			lastFire = now;
 			lastFire = now;
 			g_lastPollUs += g_pollUs;
 			g_lastPollUs += g_pollUs;
+			// fell >1 interval behind -> resync
 			if ((uint32_t)(now - g_lastPollUs) >= g_pollUs)
 			if ((uint32_t)(now - g_lastPollUs) >= g_pollUs)
-				g_lastPollUs =
-					now; // fell >1 interval behind -> resync
+				g_lastPollUs = now;
 		}
 		}
 		if ((g_connPoll & 0x1F) == 0) {
 		if ((g_connPoll & 0x1F) == 0) {
 			uint8_t pa[3] = { 0xE7, 0x00, g_e7b };
 			uint8_t pa[3] = { 0xE7, 0x00, g_e7b };
@@ -569,19 +579,18 @@ static void rfConnStep()
 			rfConnFlushRelay(ch, rs1);
 			rfConnFlushRelay(ch, rs1);
 		}
 		}
 		{
 		{
-			uint8_t p[5] = {
-				0xE3, 0x02, 0x01, 0x45, g_getParam
-			}; // E3 + TLV [len=02][subtype=01 GET][id=0x45][param]
+			// E3 + TLV [len=02][subtype=01 GET][id=0x45][param]
+			uint8_t p[5] = { 0xE3, 0x02, 0x01, 0x45, g_getParam };
+			// cycle PID (S1 1,3,5,7), NO_ACK=1
+			// cycle PID (S1 0,2,4,6), NO_ACK=0
+			// fixed (matches captured puck poll)
 			uint8_t s1 =
 			uint8_t s1 =
 				(g_e3mode == 1) ?
 				(g_e3mode == 1) ?
 					(uint8_t)((((g_e3pid++) & 3) << 1) |
 					(uint8_t)((((g_e3pid++) & 3) << 1) |
-						  1) // cycle PID (S1 1,3,5,7), NO_ACK=1
-				:
+						  1) :
 				(g_e3mode == 2) ?
 				(g_e3mode == 2) ?
-					(uint8_t)(((g_e3pid++) & 3)
-						  << 1) // cycle PID (S1 0,2,4,6), NO_ACK=0
-					:
-					0x07; // fixed (matches captured puck poll)
+					(uint8_t)(((g_e3pid++) & 3) << 1) :
+					0x07;
 			uint8_t rx = rfConnTx(ch, s1, p, 5);
 			uint8_t rx = rfConnTx(ch, s1, p, 5);
 			if (rx)
 			if (rx)
 				g_chF1[0]++;
 				g_chF1[0]++;
@@ -619,15 +628,16 @@ void rfLinkTask()
 		rfConnStep();
 		rfConnStep();
 	} // connected-mode: poll controller, read input
 	} // connected-mode: poll controller, read input
 	{
 	{
-		static bool wasRfConn =
-			false; // remote wakeup on new RF controller connection
+		// remote wakeup on new RF controller connection
+		static bool wasRfConn = false;
 		bool nowRfConn =
 		bool nowRfConn =
 			(g_connSlot >= 0 && millis() - g_connReplyMs < 300);
 			(g_connSlot >= 0 && millis() - g_connReplyMs < 300);
 		if (nowRfConn && !wasRfConn && USBDevice.suspended()) {
 		if (nowRfConn && !wasRfConn && USBDevice.suspended()) {
 			USBDevice.remoteWakeup();
 			USBDevice.remoteWakeup();
 			ledWakePulse();
 			ledWakePulse();
+			// post-resume nudge (host needs real input to actually wake)
 			if (g_active)
 			if (g_active)
-				g_active->wakeEvent(); // post-resume nudge (host needs real input to actually wake)
+				g_active->wakeEvent();
 		}
 		}
 		wasRfConn = nowRfConn;
 		wasRfConn = nowRfConn;
 	}
 	}

+ 25 - 17
OpenPuck/rf_link.h

@@ -14,28 +14,33 @@
 #include <stdint.h>
 #include <stdint.h>
 
 
 // ---- operational toggles + tunables (set from the CDC console / WebUSB; read here + by webusb status) ----
 // ---- operational toggles + tunables (set from the CDC console / WebUSB; read here + by webusb status) ----
-extern bool
-	g_rfHost; // auto-start host beacon on boot (resumes puck role after a USB replug)
+// auto-start host beacon on boot (resumes puck role after a USB replug)
+extern bool g_rfHost;
 extern bool g_connOn; // auto-start connected-mode poll on boot
 extern bool g_connOn; // auto-start connected-mode poll on boot
 extern uint8_t g_connType; // start packet type (E7 handshake, then E3)
 extern uint8_t g_connType; // start packet type (E7 handshake, then E3)
-extern uint8_t
-	g_e7b; // E7 payload B-byte: 0=current(slow/awake), 1=test protocol-version-1
+
+// E7 payload B-byte: 0=current(slow/awake), 1=test protocol-version-1
+extern uint8_t g_e7b;
 extern uint8_t g_connLen;
 extern uint8_t g_connLen;
 extern uint8_t g_getParam; // GET report 0x45 param byte
 extern uint8_t g_getParam; // GET report 0x45 param byte
-extern uint8_t
-	g_e3mode; // E3-poll PID/S1 mode (0=fixed07, 1=cyclePID+noack1, 2=cyclePID+noack0)
-extern bool
-	g_connVerbose; // full multi-line debug dump vs compact "I45 <hex>" stream
+
+// E3-poll PID/S1 mode (0=fixed07, 1=cyclePID+noack1, 2=cyclePID+noack0)
+extern uint8_t g_e3mode;
+
+// full multi-line debug dump vs compact "I45 <hex>" stream
+extern bool g_connVerbose;
 extern uint32_t g_rxWin; // poll RX-window (us): caps poll rate (~1e6/rxWin)
 extern uint32_t g_rxWin; // poll RX-window (us): caps poll rate (~1e6/rxWin)
-extern unsigned long
-	g_connCooldown; // set on 0xF2 disconnect; pauses beacon+poll so a powering-off controller can sleep
+
+// set on 0xF2 disconnect; pauses beacon+poll so a powering-off controller can sleep
+extern unsigned long g_connCooldown;
 
 
 // connected-mode state (reset by the 'k' console toggle)
 // connected-mode state (reset by the 'k' console toggle)
 extern uint8_t g_connSt, g_connStep;
 extern uint8_t g_connSt, g_connStep;
 extern uint16_t g_connPoll;
 extern uint16_t g_connPoll;
 extern uint32_t g_connF1; // count of 0xF1 input reports seen
 extern uint32_t g_connF1; // count of 0xF1 input reports seen
-extern uint8_t
-	g_connF3v; // last protocol version the controller reported in an F3 reply (0xFF=none)
+
+// last protocol version the controller reported in an F3 reply (0xFF=none)
+extern uint8_t g_connF3v;
 
 
 // QoS adaptive channel hopping
 // QoS adaptive channel hopping
 extern uint8_t g_qos; // 0=off (static g_sessCh), 1=auto-hop on degradation
 extern uint8_t g_qos; // 0=off (static g_sessCh), 1=auto-hop on degradation
@@ -45,11 +50,14 @@ extern unsigned long g_qosCheckMs, g_qosLastHopMs;
 
 
 // per-second rate readouts for the WebUSB status blob
 // per-second rate readouts for the WebUSB status blob
 extern uint16_t g_f1ps; // last completed second's F1 rate
 extern uint16_t g_f1ps; // last completed second's F1 rate
-extern uint16_t g_newps; // genuine new-report rate (report 0x45 seq byte changes)
-extern uint16_t
-	g_pollsps; // last second's poll TX count (GET+relay) -- vs F1 tells starvation from reply-loss
-extern uint16_t
-	g_pollPeriodUs; // MEASURED avg us between GET-poll fires (compare to the intended g_pollUs=4000)
+// genuine new-report rate (report 0x45 seq byte changes)
+extern uint16_t g_newps;
+
+// last second's poll TX count (GET+relay) -- vs F1 tells starvation from reply-loss
+extern uint16_t g_pollsps;
+
+// MEASURED avg us between GET-poll fires (compare to the intended g_pollUs=4000)
+extern uint16_t g_pollPeriodUs;
 
 
 // Smoothed controller->puck signal strength, sampled by the radio (RSSISAMPLE) on each CRC-good controller
 // Smoothed controller->puck signal strength, sampled by the radio (RSSISAMPLE) on each CRC-good controller
 // reply during the poll. Stored as the dBm MAGNITUDE (35 = -35dBm); 0 = no sample yet. puck_hid reports it
 // reply during the poll. Stored as the dBm MAGNITUDE (35 = -35dBm); 0 = no sample yet. puck_hid reports it

+ 16 - 13
OpenPuck/serial_console.cpp

@@ -89,7 +89,10 @@ void serialConsolePoll()
 				g_rfRespond = false;
 				g_rfRespond = false;
 				NRF_RADIO->TASKS_DISABLE = 1;
 				NRF_RADIO->TASKS_DISABLE = 1;
 				Serial.println("# RF off");
 				Serial.println("# RF off");
-			} else if (line[0] == 'x') {
+			}
+
+			// switch USB mode: 0=steam 1=xbox 2=hori 3=lizard 4=swpro 5=ps5 6=hidgyro 7=ps5-game/clean 8=ds4-game/clean
+			else if (line[0] == 'x') {
 				uint8_t m = strtoul(line + 1, 0, 10);
 				uint8_t m = strtoul(line + 1, 0, 10);
 				if (modeValid(m)) {
 				if (modeValid(m)) {
 					if (USBDevice.suspended()) {
 					if (USBDevice.suspended()) {
@@ -105,8 +108,7 @@ void serialConsolePoll()
 						NVIC_SystemReset();
 						NVIC_SystemReset();
 					}
 					}
 				}
 				}
-			} // switch USB mode: 0=steam 1=xbox 2=hori 3=lizard 4=swpro 5=ps5 6=hidgyro 7=ps5-game/clean 8=ds4-game/clean
-			else if (line[0] == 'c') {
+			} else if (line[0] == 'c') {
 				g_rfCh = atoi(line + 1);
 				g_rfCh = atoi(line + 1);
 				Serial.printf("# ch=%u\n", g_rfCh);
 				Serial.printf("# ch=%u\n", g_rfCh);
 				if (g_rfListen)
 				if (g_rfListen)
@@ -240,9 +242,9 @@ void serialConsolePoll()
 			} else if (line[0] == 'J') {
 			} else if (line[0] == 'J') {
 				char *sp = 0;
 				char *sp = 0;
 				uint8_t id = strtoul(line + 1, &sp, 0);
 				uint8_t id = strtoul(line + 1, &sp, 0);
-				uint16_t val =
-					sp ? strtoul(sp, 0, 0) :
-					     0; // inject SET-SETTINGS to controller: report 0x87 [id][val u16 LE]
+
+				// inject SET-SETTINGS to controller: report 0x87 [id][val u16 LE]
+				uint16_t val = sp ? strtoul(sp, 0, 0) : 0;
 				uint8_t pl[3] = { id, (uint8_t)(val & 0xFF),
 				uint8_t pl[3] = { id, (uint8_t)(val & 0xFF),
 						  (uint8_t)(val >> 8) };
 						  (uint8_t)(val >> 8) };
 				relayEnqueue(0x87, pl, 3);
 				relayEnqueue(0x87, pl, 3);
@@ -308,18 +310,19 @@ void serialConsolePoll()
 					"# E3 poll PID mode=%u (0=fixed07, 1=cyclePID+noack1, 2=cyclePID+noack0) - watch new=/s\n",
 					"# E3 poll PID mode=%u (0=fixed07, 1=cyclePID+noack1, 2=cyclePID+noack0) - watch new=/s\n",
 					g_e3mode);
 					g_e3mode);
 			} else if (line[0] == 't') {
 			} else if (line[0] == 't') {
-				uint8_t n =
-					line[1] ?
-						strtoul(line + 1, 0, 10) :
-						40; // inject n test haptics (output 0x82 [01 01 F7]) over the relay
+				// inject n test haptics (output 0x82 [01 01 F7]) over the relay
+				uint8_t n = line[1] ? strtoul(line + 1, 0, 10) :
+						      40;
 				g_testHaptic = n;
 				g_testHaptic = n;
 				Serial.printf(
 				Serial.printf(
 					"# test-haptic burst x%u queued (relay 0x82 01 01 F7 via op=%02X sub=%02X)\n",
 					"# test-haptic burst x%u queued (relay 0x82 01 01 F7 via op=%02X sub=%02X)\n",
 					n, g_relayOp, g_relaySub);
 					n, g_relayOp, g_relaySub);
-			} else if (line[0] == 'H') {
+			}
+
+			// dump the captured OUTPUT-report history (oldest->newest)
+			else if (line[0] == 'H') {
 				hapticDumpLog();
 				hapticDumpLog();
-			} // dump the captured OUTPUT-report history (oldest->newest)
-			else if (line[0] == 'j') {
+			} else if (line[0] == 'j') {
 				g_connType = strtoul(line + 1, 0, 16);
 				g_connType = strtoul(line + 1, 0, 16);
 				Serial.printf("# connType=%02X\n", g_connType);
 				Serial.printf("# connType=%02X\n", g_connType);
 			} else if (line[0] == 'Q') {
 			} else if (line[0] == 'Q') {

+ 3 - 2
OpenPuck/status_led.cpp

@@ -24,8 +24,9 @@ void ledWakePulse()
 {
 {
 	g_pulseMs = millis();
 	g_pulseMs = millis();
 	g_lit = true;
 	g_lit = true;
-	ledWrite(
-		WAKE_LED_ON); // light immediately at the remoteWakeup() call site, not on the next loop
+
+	// light immediately at the remoteWakeup() call site, not on the next loop
+	ledWrite(WAKE_LED_ON);
 }
 }
 
 
 void ledTask()
 void ledTask()

+ 9 - 5
OpenPuck/status_led.h

@@ -14,17 +14,21 @@
 #pragma once
 #pragma once
 
 
 #ifndef WAKE_LED_PIN_A
 #ifndef WAKE_LED_PIN_A
-#define WAKE_LED_PIN_A \
-	LED_BUILTIN // Feather: P1.15 user LED (harmless unconnected pad on SuperMini clones)
+
+// Feather: P1.15 user LED (harmless unconnected pad on SuperMini clones)
+#define WAKE_LED_PIN_A LED_BUILTIN
 #endif
 #endif
 #ifndef WAKE_LED_PIN_B
 #ifndef WAKE_LED_PIN_B
-#define WAKE_LED_PIN_B \
-	24 // SuperMini "Pro Micro" clone: P0.15 blue user LED (D24 in the Feather map)
+
+// SuperMini "Pro Micro" clone: P0.15 blue user LED (D24 in the Feather map)
+#define WAKE_LED_PIN_B 24
 #endif
 #endif
 #ifndef WAKE_LED_ON
 #ifndef WAKE_LED_ON
 #define WAKE_LED_ON HIGH // set LOW if your board's LED is wired active-low
 #define WAKE_LED_ON HIGH // set LOW if your board's LED is wired active-low
 #endif
 #endif
 
 
 void ledInit(); // call once from setup(): pins to output, LED off
 void ledInit(); // call once from setup(): pins to output, LED off
-void ledWakePulse(); // call at each USBDevice.remoteWakeup() site: LED on now, off after 500ms
+
+// call at each USBDevice.remoteWakeup() site: LED on now, off after 500ms
+void ledWakePulse();
 void ledTask(); // call every loop(): times out the pulse
 void ledTask(); // call every loop(): times out the pulse

+ 11 - 7
OpenPuck/triton.h

@@ -30,17 +30,20 @@
 #define TB_LB 0x80000u
 #define TB_LB 0x80000u
 #define TB_RPADT 0x200000u
 #define TB_RPADT 0x200000u
 #define TB_RPADC 0x400000u
 #define TB_RPADC 0x400000u
-#define TB_TOUCH \
-	0x100000u // virtual: PS Touch Click (back-paddle/QAM target, not a real Triton button)
-#define TB_MUTE \
-	0x1000000u // virtual: PS5 Mute button (back-paddle/QAM target, not a real Triton button)
+
+// virtual: PS Touch Click (back-paddle/QAM target, not a real Triton button)
+#define TB_TOUCH 0x100000u
+
+// virtual: PS5 Mute button (back-paddle/QAM target, not a real Triton button)
+#define TB_MUTE 0x1000000u
 #define TB_LPADT 0x2000000u
 #define TB_LPADT 0x2000000u
 #define TB_LPADC 0x4000000u
 #define TB_LPADC 0x4000000u
 
 
 // all four back paddles held -> mode-switch chord guard
 // all four back paddles held -> mode-switch chord guard
 #define CHORD_BACK4 (TB_R4 | TB_L4 | TB_R5 | TB_L5)
 #define CHORD_BACK4 (TB_R4 | TB_L4 | TB_R5 | TB_L5)
-#define SW_TRIG_ON \
-	40 // analog-trigger fraction (of 0xFF) at which digital ZL/ZR (Switch) etc. trip
+
+// analog-trigger fraction (of 0xFF) at which digital ZL/ZR (Switch) etc. trip
+#define SW_TRIG_ON 40
 
 
 // ---- report 0x45 field decoders (offsets relative to rep[2], the buttons low byte) ----
 // ---- report 0x45 field decoders (offsets relative to rep[2], the buttons low byte) ----
 static inline int s16off(const uint8_t *r, int off)
 static inline int s16off(const uint8_t *r, int off)
@@ -73,7 +76,8 @@ void imuFrom45(const uint8_t *r, int16_t *ax, int16_t *ay, int16_t *az,
 // This single struct replaces the per-mode duplicates the firmware used to carry (the IMU/stick/button state
 // This single struct replaces the per-mode duplicates the firmware used to carry (the IMU/stick/button state
 // was copied verbatim into g_swPro*, g_ps5*, and g_gyro*). One source, many consumers.
 // was copied verbatim into g_swPro*, g_ps5*, and g_gyro*). One source, many consumers.
 struct PuckInput {
 struct PuckInput {
-	uint32_t buttons; // raw Triton buttons (TB_*); per-mode builders apply their own chord masking
+	// raw Triton buttons (TB_*); per-mode builders apply their own chord masking
+	uint32_t buttons;
 	int16_t lx, ly, rx, ry; // sticks (int16, center 0)
 	int16_t lx, ly, rx, ry; // sticks (int16, center 0)
 	uint8_t lt, rt; // triggers scaled 0..255 (trigU8)
 	uint8_t lt, rt; // triggers scaled 0..255 (trigU8)
 	int16_t lpx, lpy, rpx, rpy; // left / right trackpad coords (int16)
 	int16_t lpx, lpy, rpx, rpy; // left / right trackpad coords (int16)

+ 2 - 2
OpenPuck/wake_hid.cpp

@@ -10,8 +10,8 @@ static Adafruit_USBD_HID g_wakeHid;
 
 
 void wakeHidBegin()
 void wakeHidBegin()
 {
 {
-	g_wakeHid.setBootProtocol(
-		HID_ITF_PROTOCOL_MOUSE); // boot mouse = a wake device class honored by Windows + Linux
+	// boot mouse = a wake device class honored by Windows + Linux
+	g_wakeHid.setBootProtocol(HID_ITF_PROTOCOL_MOUSE);
 	g_wakeHid.setStringDescriptor("OpenPuck Wake");
 	g_wakeHid.setStringDescriptor("OpenPuck Wake");
 	g_wakeHid.setReportDescriptor(WAKE_HID_DESC, sizeof WAKE_HID_DESC);
 	g_wakeHid.setReportDescriptor(WAKE_HID_DESC, sizeof WAKE_HID_DESC);
 	g_wakeHid.setPollInterval(10);
 	g_wakeHid.setPollInterval(10);

+ 2 - 1
OpenPuck/wake_hid.h

@@ -16,4 +16,5 @@
 // (g_debugCdcThisBoot) and ARCHITECTURE.md.
 // (g_debugCdcThisBoot) and ARCHITECTURE.md.
 #pragma once
 #pragma once
 
 
-void wakeHidBegin(); // register the boot-keyboard wake interface (call from setup() for clean modes)
+// register the boot-keyboard wake interface (call from setup() for clean modes)
+void wakeHidBegin();

+ 67 - 35
OpenPuck/webusb_config.cpp

@@ -17,8 +17,8 @@ Adafruit_USBD_WebUSB usb_web;
 //                [loopPeriod_lo][loopPeriod_hi][loopWorstIdx][loopWorstUs_lo][loopWorstUs_hi]
 //                [loopPeriod_lo][loopPeriod_hi][loopWorstIdx][loopWorstUs_lo][loopWorstUs_hi]
 //                [pollPeriod_lo][pollPeriod_hi][logEnabled][battery%][rssi|dBm|]
 //                [pollPeriod_lo][pollPeriod_hi][logEnabled][battery%][rssi|dBm|]
 //                [gitDirty][gitHash 12B ASCII, NUL-padded][rumbleScale][swPro120][swGyroScale10][raw accel ax ay az 3x s16 LE]
 //                [gitDirty][gitHash 12B ASCII, NUL-padded][rumbleScale][swPro120][swGyroScale10][raw accel ax ay az 3x s16 LE]
-#define WB_PAYLEN \
-	60 // keep the blob under 64 bytes total (readBlob reads one 64-byte packet)
+// keep the blob under 64 bytes total (readBlob reads one 64-byte packet)
+#define WB_PAYLEN 60
 static void webusbSendBlob()
 static void webusbSendBlob()
 {
 {
 	if (!usb_web.connected())
 	if (!usb_web.connected())
@@ -27,7 +27,9 @@ static void webusbSendBlob()
 	uint8_t p[2 + WB_PAYLEN];
 	uint8_t p[2 + WB_PAYLEN];
 	p[0] = 0xA5;
 	p[0] = 0xA5;
 	p[1] = WB_PAYLEN;
 	p[1] = WB_PAYLEN;
-	p[2] = 7; // protocol version (7 = +raw accel; 6 = +swPro120/gyroScale; 5 = +rumbleScale)
+
+	// protocol version (7 = +raw accel; 6 = +swPro120/gyroScale; 5 = +rumbleScale)
+	p[2] = 7;
 	p[3] = g_usbMode;
 	p[3] = g_usbMode;
 	p[4] = (uint8_t)g_mDiv;
 	p[4] = (uint8_t)g_mDiv;
 	p[5] = (uint8_t)g_mFric;
 	p[5] = (uint8_t)g_mFric;
@@ -54,8 +56,9 @@ static void webusbSendBlob()
 	p[26] = g_chordBtn[1];
 	p[26] = g_chordBtn[1];
 	p[27] = g_chordBtn[2];
 	p[27] = g_chordBtn[2];
 	p[28] = (uint8_t)g_pollsps;
 	p[28] = (uint8_t)g_pollsps;
-	p[29] = (uint8_t)(g_pollsps >>
-			  8); // poll TX rate (vs delivered/new -> starvation vs reply-loss)
+
+	// poll TX rate (vs delivered/new -> starvation vs reply-loss)
+	p[29] = (uint8_t)(g_pollsps >> 8);
 	p[30] = (uint8_t)g_loopPeriodUs;
 	p[30] = (uint8_t)g_loopPeriodUs;
 	p[31] = (uint8_t)(g_loopPeriodUs >>
 	p[31] = (uint8_t)(g_loopPeriodUs >>
 			  8); // loop diag: avg iteration time + slowest section
 			  8); // loop diag: avg iteration time + slowest section
@@ -79,8 +82,12 @@ static void webusbSendBlob()
 			p[41 + i] = (uint8_t)h[i];
 			p[41 + i] = (uint8_t)h[i];
 	}
 	}
 	p[53] = g_rumbleScale; // rumble strength % (protocol v5)
 	p[53] = g_rumbleScale; // rumble strength % (protocol v5)
-	p[54] = g_swProRate; // Switch Pro report rate 0=66/1=120/2=full (protocol v6)
-	p[55] = g_swGyroScale10; // Switch Pro gyro sensitivity x10 (protocol v6)
+
+	// Switch Pro report rate 0=66/1=120/2=full (protocol v6)
+	p[54] = g_swProRate;
+
+	// Switch Pro gyro sensitivity x10 (protocol v6)
+	p[55] = g_swGyroScale10;
 	{
 	{
 		int16_t a[3] = { g_in.ax, g_in.ay, g_in.az };
 		int16_t a[3] = { g_in.ax, g_in.ay, g_in.az };
 		memcpy(&p[56], a, 6);
 		memcpy(&p[56], a, 6);
@@ -150,14 +157,12 @@ void webusbPoll()
 			if (n == 0)
 			if (n == 0)
 				break;
 				break;
 			uint8_t op = buf[0];
 			uint8_t op = buf[0];
-			uint8_t need =
-				(op == 0x02) ?
-					3 :
-				(op == 0x03 || op == 0x05) ?
-					2 :
-				(op == 0x0A) ?
-					4 :
-					1; // 0x05 carries a value byte; 0x0A carries a 3-byte magic
+
+			// 0x05 carries a value byte; 0x0A carries a 3-byte magic
+			uint8_t need = (op == 0x02)		  ? 3 :
+				       (op == 0x03 || op == 0x05) ? 2 :
+				       (op == 0x0A)		  ? 4 :
+								    1;
 			if (op < 0x01 || op > 0x0A) { // resync: drop one byte
 			if (op < 0x01 || op > 0x0A) { // resync: drop one byte
 				memmove(buf, buf + 1, --n);
 				memmove(buf, buf + 1, --n);
 				continue;
 				continue;
@@ -168,23 +173,29 @@ void webusbPoll()
 				webusbSendBlob();
 				webusbSendBlob();
 			}
 			}
 #if OPK_LOG
 #if OPK_LOG
+
+			// rewind drain: buf[1]=1 from boot (whole ring), 0 from now
 			else if (op == 0x05) {
 			else if (op == 0x05) {
 				hapLogResetDrain(buf[1] != 0);
 				hapLogResetDrain(buf[1] != 0);
-			} // rewind drain: buf[1]=1 from boot (whole ring), 0 from now
-			else if (op == 0x06) {
+			} else if (op == 0x06) {
 				webusbDrainCapture();
 				webusbDrainCapture();
 			} // drain entries since the rewind (panel polls this)
 			} // drain entries since the rewind (panel polls this)
 #endif
 #endif
+
+			// clear a stuck/latched haptic buzz on the controller (functional)
 			else if (op == 0x07) {
 			else if (op == 0x07) {
 				hapticReinit();
 				hapticReinit();
-			} // clear a stuck/latched haptic buzz on the controller (functional)
+			}
+
+			// TEST: trigger the controller power-off (same path Steam 0x9F / host-suspend use)
 			else if (op == 0x08) {
 			else if (op == 0x08) {
 				hapticSendShutdown();
 				hapticSendShutdown();
-			} // TEST: trigger the controller power-off (same path Steam 0x9F / host-suspend use)
-			else if (op ==
-				 0x0A) { // FULL factory wipe: erase cfg.bin + bonds.bin, reboot to clean defaults.
-				// Guarded by a 3-byte magic ("ERS") so a stray/corrupt byte can never trigger it; the panel double-
-				// confirms before sending. Irreversible -- the controller must be re-paired afterwards.
+			}
+
+			// FULL factory wipe: erase cfg.bin + bonds.bin, reboot to clean defaults.
+			// Guarded by a 3-byte magic ("ERS") so a stray/corrupt byte can never trigger it; the panel double-
+			// confirms before sending. Irreversible -- the controller must be re-paired afterwards.
+			else if (op == 0x0A) {
 				if (buf[1] == 0x45 && buf[2] == 0x52 &&
 				if (buf[1] == 0x45 && buf[2] == 0x52 &&
 				    buf[3] == 0x53) {
 				    buf[3] == 0x53) {
 					usb_web.flush();
 					usb_web.flush();
@@ -194,8 +205,9 @@ void webusbPoll()
 				}
 				}
 			} else if (op == 0x02) {
 			} else if (op == 0x02) {
 				uint8_t f = buf[1], v = buf[2];
 				uint8_t f = buf[1], v = buf[2];
-				bool persist =
-					true; // every settable field persists (poll rate is no longer settable)
+
+				// every settable field persists (poll rate is no longer settable)
+				bool persist = true;
 				switch (f) {
 				switch (f) {
 				case 1:
 				case 1:
 					g_mDiv = v < 4 ? 4 : v;
 					g_mDiv = v < 4 ? 4 : v;
@@ -214,53 +226,73 @@ void webusbPoll()
 					g_back[f - 5] = v;
 					g_back[f - 5] = v;
 					break;
 					break;
 				// case 9 (pollU100) removed -- poll rate is fixed at POLL_US_DEFAULT and no longer configurable.
 				// case 9 (pollU100) removed -- poll rate is fixed at POLL_US_DEFAULT and no longer configurable.
+
+				// E7 protocol-version B-byte (experimental v1 fast)
 				case 10:
 				case 10:
 					g_e7b = v ? 1 : 0;
 					g_e7b = v ? 1 : 0;
-					break; // E7 protocol-version B-byte (experimental v1 fast)
+					break;
+
+				// haptic-relay opcode (buzz hunt)
 				case 11:
 				case 11:
 					g_relayOp = v;
 					g_relayOp = v;
-					break; // haptic-relay opcode (buzz hunt)
+					break;
+
+				// haptic-relay sub-type (buzz hunt)
 				case 12:
 				case 12:
 					g_relaySub = v;
 					g_relaySub = v;
-					break; // haptic-relay sub-type (buzz hunt)
+					break;
 				case 13:
 				case 13:
 					g_testHaptic = v ? v : 40;
 					g_testHaptic = v ? v : 40;
 					break; // inject v test haptics (0->40)
 					break; // inject v test haptics (0->40)
+
+				// Steam: forward only fresh reports (dedupe)
 				case 14:
 				case 14:
 					g_fwdNewOnly = v ? 1 : 0;
 					g_fwdNewOnly = v ? 1 : 0;
-					break; // Steam: forward only fresh reports (dedupe)
+					break;
 				case 15:
 				case 15:
 					g_qos = v ? 1 : 0;
 					g_qos = v ? 1 : 0;
 					g_hopIdx = 0;
 					g_hopIdx = 0;
 					g_qosBad = 0;
 					g_qosBad = 0;
 					g_qosCheckMs = millis();
 					g_qosCheckMs = millis();
 					break; // QoS adaptive channel hopping
 					break; // QoS adaptive channel hopping
+
+				// persist last mode across reboots (else always boot Steam)
 				case 16:
 				case 16:
 					g_persistMode = v ? true : false;
 					g_persistMode = v ? true : false;
-					break; // persist last mode across reboots (else always boot Steam)
+					break;
+
+				// back4+B/X/Y mode assignments
 				case 17:
 				case 17:
 				case 18:
 				case 18:
 				case 19:
 				case 19:
 					if (modeValid(v))
 					if (modeValid(v))
 						g_chordBtn[f - 17] = v;
 						g_chordBtn[f - 17] = v;
-					break; // back4+B/X/Y mode assignments
+					break;
+
+				// reboot once WITH the CDC serial console (puck mode), then auto-revert
 				case 20:
 				case 20:
 					armDebugCdcNextBoot();
 					armDebugCdcNextBoot();
 					usb_web.flush();
 					usb_web.flush();
 					delay(40);
 					delay(40);
 					NVIC_SystemReset();
 					NVIC_SystemReset();
-					break; // reboot once WITH the CDC serial console (puck mode), then auto-revert
+					break;
+
+				// QAM physical button remap code (0=default/unmapped)
 				case 21:
 				case 21:
 					g_qamMap = v;
 					g_qamMap = v;
-					break; // QAM physical button remap code (0=default/unmapped)
+					break;
+
+				// rumble strength % (0=off, 100=1x, 200=double)
 				case 22:
 				case 22:
 					g_rumbleScale = v;
 					g_rumbleScale = v;
-					break; // rumble strength % (0=off, 100=1x, 200=double)
+					break;
+
+				// Switch Pro report rate (0=66Hz,1=120Hz,2=full)
 				case 23:
 				case 23:
 					g_swProRate = (v <= 2) ? v : 1;
 					g_swProRate = (v <= 2) ? v : 1;
 					swProSaveCfg();
 					swProSaveCfg();
 					persist = false;
 					persist = false;
-					break; // Switch Pro report rate (0=66Hz,1=120Hz,2=full)
+					break;
 				case 24:
 				case 24:
 					g_swGyroScale10 =
 					g_swGyroScale10 =
 						(v >= 5 && v <= 30) ? v : 10;
 						(v >= 5 && v <= 30) ? v : 10;

+ 3 - 3
pairtui/scmd.c

@@ -873,9 +873,9 @@ int main(int argc, char **argv)
 		IOHIDDeviceClose(dev, kIOHIDOptionsTypeNone);
 		IOHIDDeviceClose(dev, kIOHIDOptionsTypeNone);
 		CFRelease(dev);
 		CFRelease(dev);
 		return rc;
 		return rc;
-	} else if (arg < argc &&
-		   strcmp(argv[arg], "find") ==
-			   0) { // probe all non-destructive opcodes for bond/serial data
+	}
+	// probe all non-destructive opcodes for bond/serial data
+	else if (arg < argc && strcmp(argv[arg], "find") == 0) {
 		// BLOCKLIST = known state-changing/destructive opcodes; never sent here.
 		// BLOCKLIST = known state-changing/destructive opcodes; never sent here.
 		static const int block[] = { 0x80, 0x81, 0x85, 0x86, 0x87, 0x88,
 		static const int block[] = { 0x80, 0x81, 0x85, 0x86, 0x87, 0x88,
 					     0x8D, 0x8E, 0x8F, 0x9F, 0xAD, 0xAF,
 					     0x8D, 0x8E, 0x8F, 0x9F, 0xAD, 0xAF,

+ 23 - 17
puck_sniffer/puck_sniffer.ino

@@ -59,6 +59,7 @@ static uint8_t g_forceCh =
 	0; // !=0 -> user pinned a channel (skip auto-acquire retune)
 	0; // !=0 -> user pinned a channel (skip auto-acquire retune)
 static bool g_pinSession =
 static bool g_pinSession =
 	false; // user pinned a FULL session (cmd 05): never auto-retune addr/ch
 	false; // user pinned a FULL session (cmd 05): never auto-retune addr/ch
+
 // cmd 06: stay on ibex/ch2 and stream EVERY E1 beacon, never auto-lock
 // cmd 06: stay on ibex/ch2 and stream EVERY E1 beacon, never auto-lock
 // (so you can see how many pucks/slots are advertising + their bases)
 // (so you can see how many pucks/slots are advertising + their bases)
 static bool g_survey = false;
 static bool g_survey = false;
@@ -149,9 +150,9 @@ static void rxPump()
 		NRF_RADIO->EVENTS_END = 0;
 		NRF_RADIO->EVENTS_END = 0;
 		bool crcok = NRF_RADIO->CRCSTATUS & 1;
 		bool crcok = NRF_RADIO->CRCSTATUS & 1;
 		uint8_t rssi = (uint8_t)(NRF_RADIO->RSSISAMPLE & 0x7F);
 		uint8_t rssi = (uint8_t)(NRF_RADIO->RSSISAMPLE & 0x7F);
-		uint8_t match =
-			(uint8_t)(NRF_RADIO->RXMATCH &
-				  0x7); // which logical address (pipe) the frame hit
+
+		// which logical address (pipe) the frame hit
+		uint8_t match = (uint8_t)(NRF_RADIO->RXMATCH & 0x7);
 		g_lastMatch = match;
 		g_lastMatch = match;
 		uint8_t len = rfrx[0];
 		uint8_t len = rfrx[0];
 		uint16_t n = (uint16_t)(2 + len);
 		uint16_t n = (uint16_t)(2 + len);
@@ -161,11 +162,11 @@ static void rxPump()
 			 (uint8_t)n);
 			 (uint8_t)n);
 		g_lastRx = millis();
 		g_lastRx = millis();
 		if (g_state == 1)
 		if (g_state == 1)
-			g_sweepDry =
-				0; // any frame on the session resets the dry-sweep count
+			// any frame on the session resets the dry-sweep count
+			g_sweepDry = 0;
 		if (crcok && len >= 1 && (rfrx[2] & 0xF0) == 0xF0)
 		if (crcok && len >= 1 && (rfrx[2] & 0xF0) == 0xF0)
-			g_lastFrx =
-				millis(); // controller reply = ACTIVE session here -> stop hunting, park
+			// controller reply = ACTIVE session here -> stop hunting, park
+			g_lastFrx = millis();
 		// Parse the session params advertised in an E1 host-frame (payload[0]=0xE1) by the ASSUMED offsets, and
 		// Parse the session params advertised in an E1 host-frame (payload[0]=0xE1) by the ASSUMED offsets, and
 		// always record them for the status report -- so the panel shows what auto-acquire would lock onto even
 		// always record them for the status report -- so the panel shows what auto-acquire would lock onto even
 		// when we choose not to (lets us eyeball whether the real puck's offsets match ours).
 		// when we choose not to (lets us eyeball whether the real puck's offsets match ours).
@@ -177,7 +178,8 @@ static void rxPump()
 			g_advBase[3] = rfrx[18];
 			g_advBase[3] = rfrx[18];
 			g_advPfx = rfrx[19];
 			g_advPfx = rfrx[19];
 			if (g_pinSession || g_survey)
 			if (g_pinSession || g_survey)
-				continue; // pinned / survey -> never auto-retune (just record adv)
+				// pinned / survey -> never auto-retune (just record adv)
+				continue;
 			// Target a specific bonded slot by its ibex_uuid (E1 bytes rfrx[7..10]): the puck beacons one E1 per slot,
 			// Target a specific bonded slot by its ibex_uuid (E1 bytes rfrx[7..10]): the puck beacons one E1 per slot,
 			// so skip any E1 that isn't the CONNECTED controller's -- otherwise we lock an idle slot's keepalive.
 			// so skip any E1 that isn't the CONNECTED controller's -- otherwise we lock an idle slot's keepalive.
 			if (g_haveTarget &&
 			if (g_haveTarget &&
@@ -248,8 +250,9 @@ static void drainToHost()
 {
 {
 	if (!usb_web.connected())
 	if (!usb_web.connected())
 		return;
 		return;
-	uint16_t budget =
-		64; // bound per-call so RX stays responsive; the ring covers bursts
+
+	// bound per-call so RX stays responsive; the ring covers bursts
+	uint16_t budget = 64;
 	while (budget-- && g_tail != g_head) {
 	while (budget-- && g_tail != g_head) {
 		if (g_streaming)
 		if (g_streaming)
 			emitPacket(g_ring[g_tail]);
 			emitPacket(g_ring[g_tail]);
@@ -283,8 +286,8 @@ static void handleCmd()
 			g_pinSession = false;
 			g_pinSession = false;
 			tuneDiscovery();
 			tuneDiscovery();
 		} // survey: list all E1 beacons on ch2, no lock
 		} // survey: list all E1 beacons on ch2, no lock
-		else if (c ==
-			 0x07) { // target ibex_uuid: lock only the matching slot's E1
+		// target ibex_uuid: lock only the matching slot's E1
+		else if (c == 0x07) {
 			int u0 = usb_web.read(), u1 = usb_web.read(),
 			int u0 = usb_web.read(), u1 = usb_web.read(),
 			    u2 = usb_web.read(), u3 = usb_web.read();
 			    u2 = usb_web.read(), u3 = usb_web.read();
 			if (u0 >= 0 && u1 >= 0 && u2 >= 0 && u3 >= 0) {
 			if (u0 >= 0 && u1 >= 0 && u2 >= 0 && u3 >= 0) {
@@ -331,9 +334,9 @@ void setup()
 	// Standard Adafruit WebUSB setup (keep the default CDC composite; just add the WebUSB vendor interface). No
 	// Standard Adafruit WebUSB setup (keep the default CDC composite; just add the WebUSB vendor interface). No
 	// landing page -> no Chrome "open <url>" notification. No detach/clearConfiguration: that's OpenPuck-specific
 	// landing page -> no Chrome "open <url>" notification. No detach/clearConfiguration: that's OpenPuck-specific
 	// endpoint juggling and can leave the WebUSB stream un-enabled.
 	// endpoint juggling and can leave the WebUSB stream un-enabled.
-	USBDevice.setID(
-		0x28DE,
-		0x534E); // 'SN' -- distinct PID so the host doesn't confuse it with a puck
+
+	// 'SN' -- distinct PID so the host doesn't confuse it with a puck
+	USBDevice.setID(0x28DE, 0x534E);
 	USBDevice.setManufacturerDescriptor("Valve Software");
 	USBDevice.setManufacturerDescriptor("Valve Software");
 	USBDevice.setProductDescriptor("OpenPuck Sniffer");
 	USBDevice.setProductDescriptor("OpenPuck Sniffer");
 	usb_web.begin();
 	usb_web.begin();
@@ -341,7 +344,8 @@ void setup()
 	for (int i = 0; i < 200 && !USBDevice.mounted(); i++)
 	for (int i = 0; i < 200 && !USBDevice.mounted(); i++)
 		delay(10);
 		delay(10);
 
 
-	rfGenSessionAddr(); // harmless (we never TX); leaves the radio tunables at their validated defaults
+	// harmless (we never TX); leaves the radio tunables at their validated defaults
+	rfGenSessionAddr();
 	tuneDiscovery();
 	tuneDiscovery();
 	g_lastRx = millis();
 	g_lastRx = millis();
 }
 }
@@ -350,7 +354,9 @@ void loop()
 {
 {
 	rxPump();
 	rxPump();
 	handleCmd();
 	handleCmd();
-	rxPump(); // pump again around the (fast, buffered) USB work to catch tight poll->reply pairs
+
+	// pump again around the (fast, buffered) USB work to catch tight poll->reply pairs
+	rxPump();
 	drainToHost();
 	drainToHost();
 	// Session HUNT (Gazell). A pinned session (cmd 04/05) is never disturbed. Otherwise: while we're NOT seeing the
 	// Session HUNT (Gazell). A pinned session (cmd 04/05) is never disturbed. Otherwise: while we're NOT seeing the
 	// controller's 0xF* replies (the ACTIVE session), the keepalive trickles in on the primary but the real E3/F1
 	// controller's 0xF* replies (the ACTIVE session), the keepalive trickles in on the primary but the real E3/F1

+ 17 - 14
puck_sniffer/radio.cpp

@@ -46,13 +46,15 @@ uint32_t g_rfRxCount = 0;
 // ENDIAN=Big (MSB-first), NO whitening, addr "ibex", CRC16 0x11021/0xFFFF; frame = FUN_00027e9a (static length).
 // ENDIAN=Big (MSB-first), NO whitening, addr "ibex", CRC16 0x11021/0xFFFF; frame = FUN_00027e9a (static length).
 uint32_t g_crcinit = 0xFFFF;
 uint32_t g_crcinit = 0xFFFF;
 uint8_t g_whiteiv = 37;
 uint8_t g_whiteiv = 37;
-uint8_t g_mode =
-	RADIO_MODE_MODE_Ble_2Mbit; // <-- was the bug (1Mbit). Real puck = 2Mbit.
+
+// <-- was the bug (1Mbit). Real puck = 2Mbit.
+uint8_t g_mode = RADIO_MODE_MODE_Ble_2Mbit;
 uint32_t g_pcnf0 =
 uint32_t g_pcnf0 =
 	0x00030008UL; // S0LEN0, LFLEN8, S1LEN3 (ESB DPL) - CRC-VALIDATED
 	0x00030008UL; // S0LEN0, LFLEN8, S1LEN3 (ESB DPL) - CRC-VALIDATED
 uint8_t g_statlen = 0x20;
 uint8_t g_statlen = 0x20;
-uint32_t g_pcnf1 =
-	0x01040060; // ENDIAN=Big, BALEN4, MAXLEN=96 (was 64; longer F1 frames e.g. 0x43-augmented)
+
+// ENDIAN=Big, BALEN4, MAXLEN=96 (was 64; longer F1 frames e.g. 0x43-augmented)
+uint32_t g_pcnf1 = 0x01040060;
 uint32_t g_crcpoly = 0x11021UL;
 uint32_t g_crcpoly = 0x11021UL;
 uint16_t g_crccnf = 0x2; // CRC16, address included
 uint16_t g_crccnf = 0x2; // CRC16, address included
 uint8_t g_pid = 0;
 uint8_t g_pid = 0;
@@ -129,17 +131,18 @@ void rfConfig(uint8_t ch)
 #if defined(RADIO_MODECNF0_RU_Fast)
 #if defined(RADIO_MODECNF0_RU_Fast)
 	NRF_RADIO->MODECNF0 = (RADIO_MODECNF0_RU_Fast << RADIO_MODECNF0_RU_Pos);
 	NRF_RADIO->MODECNF0 = (RADIO_MODECNF0_RU_Fast << RADIO_MODECNF0_RU_Pos);
 #endif
 #endif
-	NRF_RADIO->PCNF0 =
-		g_pcnf0; // static length (tunable '0'); g_pcnf1=0 -> build from g_statlen/g_balen
+
+	// static length (tunable '0'); g_pcnf1=0 -> build from g_statlen/g_balen
+	NRF_RADIO->PCNF0 = g_pcnf0;
+
+	// ENDIAN=Big (puck transmits MSB-first)
 	NRF_RADIO->PCNF1 =
 	NRF_RADIO->PCNF1 =
-		g_pcnf1 ?
-			g_pcnf1 :
-			((1u
-			  << RADIO_PCNF1_ENDIAN_Pos) // ENDIAN=Big (puck transmits MSB-first)
-			 | ((uint32_t)g_balen << RADIO_PCNF1_BALEN_Pos) |
-			 ((uint32_t)g_statlen << RADIO_PCNF1_STATLEN_Pos) |
-			 ((uint32_t)g_statlen
-			  << RADIO_PCNF1_MAXLEN_Pos)); // WHITEEN=0
+		g_pcnf1 ? g_pcnf1 :
+			  ((1u << RADIO_PCNF1_ENDIAN_Pos) |
+			   ((uint32_t)g_balen << RADIO_PCNF1_BALEN_Pos) |
+			   ((uint32_t)g_statlen << RADIO_PCNF1_STATLEN_Pos) |
+			   ((uint32_t)g_statlen
+			    << RADIO_PCNF1_MAXLEN_Pos)); // WHITEEN=0
 	NRF_RADIO->CRCCNF = g_crccnf; // CRC16, address included
 	NRF_RADIO->CRCCNF = g_crccnf; // CRC16, address included
 	NRF_RADIO->CRCPOLY = g_crcpoly;
 	NRF_RADIO->CRCPOLY = g_crcpoly;
 	NRF_RADIO->CRCINIT = g_crcinit;
 	NRF_RADIO->CRCINIT = g_crcinit;

+ 6 - 4
puck_sniffer/radio.h

@@ -28,8 +28,9 @@ extern const uint8_t PAIR_BASE[4]; // "ibex"
 extern uint8_t g_rfPrefix; // discovery prefix (rodata byte @0x56f98)
 extern uint8_t g_rfPrefix; // discovery prefix (rodata byte @0x56f98)
 extern uint8_t g_rfCh; // current TX/RX channel (hopped during beacon/poll)
 extern uint8_t g_rfCh; // current TX/RX channel (hopped during beacon/poll)
 extern uint8_t g_rfBase[4]; // "ibex"
 extern uint8_t g_rfBase[4]; // "ibex"
-extern uint8_t
-	g_sessCh; // connected-session channel: a CLEAN data channel off the congested adv ch2
+
+// connected-session channel: a CLEAN data channel off the congested adv ch2
+extern uint8_t g_sessCh;
 
 
 // Per-device SESSION address (base+prefix). Discovery/rendezvous stays on the SHARED "ibex" address (g_rfBase)
 // Per-device SESSION address (base+prefix). Discovery/rendezvous stays on the SHARED "ibex" address (g_rfBase)
 // so any controller can find us; the host frame then advertises THIS unique address and the controller adopts
 // so any controller can find us; the host frame then advertises THIS unique address and the controller adopts
@@ -53,8 +54,9 @@ extern uint32_t g_crcpoly;
 extern uint16_t g_crccnf; // CRC16, address included
 extern uint16_t g_crccnf; // CRC16, address included
 extern uint8_t g_pid;
 extern uint8_t g_pid;
 extern uint8_t g_balen; // ESB 5-byte addr
 extern uint8_t g_balen; // ESB 5-byte addr
-extern bool
-	g_prefixRaw; // false = bitrev8 prefix (ESB addr_conv); true = raw prefix byte
+
+// false = bitrev8 prefix (ESB addr_conv); true = raw prefix byte
+extern bool g_prefixRaw;
 
 
 uint8_t rfBitrev8(uint8_t x);
 uint8_t rfBitrev8(uint8_t x);
 // Access address (IBEX FUN_00037530): BASE0 = bitrev8 each base byte packed big-endian; PREFIX0 = raw or
 // Access address (IBEX FUN_00037530): BASE0 = bitrev8 each base byte packed big-endian; PREFIX0 = raw or

+ 92 - 0
tools/check-trailing-comments.py

@@ -0,0 +1,92 @@
+#!/usr/bin/env python3
+"""Reject trailing // comments that push a line past the 80-column limit.
+
+clang-format (kernel style, ReflowComments: false) cannot move a trailing
+comment onto its own line, so when a short statement carries a long trailing
+comment it either splits the statement or leaves the line over-length. Our
+convention is to instead put the comment on its own line(s) ABOVE the
+statement, separated by a blank line. This check enforces that: a line wider
+than the limit whose overflow comes from a trailing // comment is an error.
+
+Standalone comment lines (// in column 0..) are left alone -- the kernel style
+does not reflow comments, and those are not what triggers the bad split.
+"""
+import sys
+
+LIMIT = 80
+TAB = 8
+
+
+def visual_width(s):
+    """Width of a line with tabs expanded to the next multiple of TAB."""
+    col = 0
+    for ch in s:
+        if ch == "\t":
+            col += TAB - (col % TAB)
+        else:
+            col += 1
+    return col
+
+
+def trailing_comment_index(line):
+    """Char index of the first real `//` comment, or None.
+
+    Skips `//` that appears inside a string or char literal so that e.g.
+    "http://example" is not mistaken for a comment.
+    """
+    quote = None
+    i, n = 0, len(line)
+    while i < n:
+        ch = line[i]
+        if quote:
+            if ch == "\\":
+                i += 2
+                continue
+            if ch == quote:
+                quote = None
+            i += 1
+            continue
+        if ch in "\"'":
+            quote = ch
+            i += 1
+            continue
+        if ch == "/" and i + 1 < n and line[i + 1] == "/":
+            return i
+        i += 1
+    return None
+
+
+def violations(path):
+    out = []
+    with open(path, errors="replace") as f:
+        for lineno, raw in enumerate(f, 1):
+            line = raw.rstrip("\n")
+            if visual_width(line) <= LIMIT:
+                continue
+            ci = trailing_comment_index(line)
+            if ci is None:
+                continue  # over-length, but not because of a comment
+            if line[:ci].strip() == "":
+                continue  # standalone comment line -> allowed
+            out.append((lineno, visual_width(line)))
+    return out
+
+
+def main(argv):
+    files = argv[1:]
+    bad = 0
+    for path in files:
+        for lineno, width in violations(path):
+            bad += 1
+            print(f"{path}:{lineno}: trailing comment exceeds "
+                  f"{LIMIT} columns (line is {width}); "
+                  f"move the comment onto its own line above the statement")
+    if bad:
+        print(f"\n{bad} trailing-comment violation(s). "
+              f"See tools/check-trailing-comments.py.", file=sys.stderr)
+        return 1
+    return 0
+
+
+if __name__ == "__main__":
+    sys.exit(main(sys.argv))