Ver Fonte

Add linting for trailing comments and fix existing issues

Jariullah Safi há 4 meses atrás
pai
commit
8a8903377d

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

@@ -19,7 +19,9 @@ jobs:
           sudo apt-get update
           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
-        # 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' \) \
 	-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.
 format:
@@ -26,3 +26,12 @@ format:
 ## Used by CI to reject unformatted code.
 format-check:
 	$(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;
 
 #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 "bonds.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"
 #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()
 {
 	genSerial();
 	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();
 #if OPK_FACTORY_RESET
 	// 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);
 #endif
 	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);
 
 	// ---- USB descriptor rebuild ----
@@ -74,14 +79,15 @@ void setup()
 	USBDevice.detach();
 	delay(30);
 	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 {
 		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
@@ -105,7 +111,8 @@ void setup()
 	if (puckMode && !keepCdc)
 		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,
 	// 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)
 		usb_web.begin();
 	// 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);
 	for (int i = 0; i < 300 && !USBDevice.mounted(); i++)
 		delay(10); // wait up to 3s for USB mount, but NEVER hang
@@ -130,7 +138,9 @@ void setup()
 		ledWakePulse();
 	} // wake host if bus was sleeping when we (re-)attached
 	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[] = {
 		"STEAM(puck)",	       "XBOX(xinput+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
 // 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.
-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
 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) {
 		g_dirty = false;
 		saveBonds();

+ 2 - 2
OpenPuck/bonds.h

@@ -19,8 +19,8 @@ struct Slot {
 };
 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
 	g_connReplyMs; // millis of last RF reply (link-alive timestamp)
 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;
 
 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;
 uint8_t g_abSwap = 0;
 uint8_t g_back[4] = {
 	5, 6, 7, 8
 }; // 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
 // persisted by an older build is ignored and overwritten with the default on boot (see loadCfg).
 const uint32_t g_pollUs = POLL_US_DEFAULT;
 
 #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 {
 	uint8_t magic, mode, mDiv, mFric, rsvd0, abSwap, back[4], pollU100,
 		persistMode, bootMode, chordBtn[3], qamMap, rumbleScale;
@@ -104,15 +105,17 @@ void loadCfg()
 							c.chordBtn[i] :
 							CHORD_DEF[i];
 			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();
 	}
+	// clear the one-shot so the NEXT cold boot reverts to the default/persist policy
 	if (consume) {
 		g_bootMode = 0xFF;
 		saveCfg();
-	} // clear the one-shot so the NEXT cold boot reverts to the default/persist policy
+	}
 }
 
 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).
 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
 }
 

+ 37 - 33
OpenPuck/config.h

@@ -35,14 +35,14 @@
 #define MODE_LIZARD \
 	3 // Puck HID; always keyboard+mouse (ignores Steam heartbeat)
 #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
 
 // 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 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)
 
 // 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
 // cold boot reverts to Steam). The WebUI "persist last mode" toggle (g_persistMode) instead remembers the
 // 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
 // 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
 // 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)
 
 // persisted, runtime-tunable config:
 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_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
 // 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
 // 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:
 		return &g_hidGyroCtl;
 	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:
-		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:
 		return &g_steamPuck;
 	}

+ 3 - 1
OpenPuck/controllers.h

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

+ 72 - 55
OpenPuck/haptics.cpp

@@ -2,7 +2,9 @@
 #include "bonds.h"
 #include "config.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 <string.h>
 
@@ -29,18 +31,24 @@ void hapticSendShutdown()
 		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
 // latch that engaged during/after use, even seconds after connect)
 static bool g_hapClearArmed = false;
@@ -52,8 +60,8 @@ struct RelayMsg {
 	uint8_t rid, len;
 	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 volatile uint8_t
 	g_rqHead = 0,
@@ -69,8 +77,9 @@ bool relayPending()
 }
 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)
-		plen = RELAY_MAXP; // 60B is the RF frame ceiling; longer can't be relayed
+		plen = RELAY_MAXP;
 	uint32_t pm = __get_PRIMASK();
 	__disable_irq();
 	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);
 }
 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;
 	uint32_t now = millis();
 	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. ----
 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,
 		uint8_t bytes16[16])
@@ -200,11 +208,13 @@ static bool haptic82PayloadOn(const uint8_t *p, uint16_t n)
 		return false;
 	for (uint16_t i = 2; i < n; 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;
 }
 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();
 	__disable_irq();
 	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)
 		return;
 	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
 	// 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
@@ -247,8 +258,8 @@ void haptic82HostReport(const uint8_t *p, uint16_t n)
 }
 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,
 			 h = (uint32_t)highFreq * g_rumbleScale / 100;
 		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.
 	uint16_t intensity = lowFreq > highFreq ? lowFreq : highFreq;
 	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[2] = (uint8_t)(intensity >> 8);
 	p[3] = (uint8_t)(lowFreq & 0xFF);
@@ -300,7 +312,9 @@ void rfConnFlushRelay(uint8_t ch, uint8_t s1)
 {
 	while (g_rqTail != g_rqHead) {
 		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;
 			if (rl > RELAY_MAXP)
 				rl = RELAY_MAXP;
@@ -337,17 +351,19 @@ void rfConnFlushRelay(uint8_t ch, uint8_t s1)
 				memcpy(p + 4, m.data, 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
 			// 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
 			// rate during haptics).
 			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);
 	}
@@ -378,13 +394,12 @@ void hapticReinit()
 	static const uint8_t T81B[] = {
 		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, 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(0x81, T81A, sizeof T81A);
 	relayEnqueue(0x81, T81B, sizeof T81B);
@@ -394,9 +409,8 @@ void hapticInit()
 	g_rqHead = g_rqTail = 0;
 	g_haptic82On = 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
 	// 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,
@@ -409,23 +423,26 @@ void hapticInit()
 // link-up edge. Idempotent -- safe to call repeatedly.
 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_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
 	// 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
 	// 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.
 	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;
-	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()
 {

+ 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
 // 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 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_relaySub; // relay sub-TLV type byte = SET
 extern volatile uint8_t
 	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;
-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 +
 //      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 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
 // 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.

+ 6 - 4
OpenPuck/mode_hidgyro.cpp

@@ -101,15 +101,17 @@ static void hidGyroBuild(uint8_t out[63])
 void HidGyroController::begin()
 {
 	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.setProductDescriptor("Wireless Controller");
 	g_hidGyro.enableOutEndpoint(true);
 	g_hidGyro.setReportCallback(NULL, hidGyroSet);
 	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();
 }
 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;
 	}
 	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,
-	      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;
 	rmx = mxf - dx;
 	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);
 	LZK(b & TB_MENU,
 	    (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_DDN) || sy < -12000, HID_KEY_ARROW_DOWN);
 	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;
 	memset(buf, 0, reqlen);
 	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)
 			return 0;
 		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
 		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;
 		for (uint16_t i = 0; i < n; i++)
 			buf[i] = 0;
@@ -148,15 +151,17 @@ static void ps5Build(uint8_t out[63])
 void Ps5Controller::begin()
 {
 	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.setProductDescriptor("DualSense Wireless Controller");
 	g_ps5.enableOutEndpoint(true);
 	g_ps5.setReportCallback(ps5Get, ps5Set);
 	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();
 }
 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.
 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
-	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 unsigned long g_swLastMs = 0;
@@ -160,14 +164,16 @@ static void switchBuildHoripad(uint8_t out[8])
 void SwitchHoriController::begin()
 {
 	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.setProductDescriptor("POKKEN CONTROLLER");
 	g_switch.enableOutEndpoint(true);
 	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();
 }
 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.
 #define SW_PRO_REPORT_MS 15u // 66 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 =
 	1; // 0 = 66Hz, 1 = 120Hz (default), 2 = full (~250Hz / USB_STREAM_MS)
@@ -44,10 +44,8 @@ static void swProLoadCfg()
 	uint8_t b[3];
 	if (f.open(SWPRO_CFG_FILE, FILE_O_READ)) {
 		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 =
 				(b[2] >= 5 && b[2] <= 30) ?
 					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 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] = {
 	0x7C, 0xBB, 0x8A, 0x00, 0x00, 0x01
 }; // 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]
 	uint16_t lo = jcRumbleAmp(p + 1), hi = jcRumbleAmp(p + 5);
 	static uint16_t lastLo = 0, lastHi = 0;
+	// only relay on change: the Switch streams rumble every frame, so
 	if (lo == lastLo && hi == lastHi)
-		return; // only relay on change: the Switch streams rumble every frame, so
+		return;
 	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);
 }
 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));
 	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)
-{ // 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);
 	s[0] = (uint8_t)(X & 0xFF);
 	s[1] = (uint8_t)(((Y & 0x0F) << 4) | ((X >> 8) & 0x0F));
@@ -246,19 +247,21 @@ static void jcInputPrefix(uint8_t *out)
 	if (b & TB_R5)
 		jc |= codeToJc(g_back[3], fA, fB, fX, fY);
 	out[0] = g_jcTimer++;
+
 	// 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)
 	out[1] = 0x91;
 	out[2] = (uint8_t)(jc);
 	out[3] = (uint8_t)(jc >> 8);
 	out[4] = (uint8_t)(jc >> 16);
+
 	// 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.
 	out[3] |= 0x80;
 	jcPackStick(out + 5, g_in.lx, g_in.ly);
 	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])
 {
@@ -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
 	// 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.
-	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 gyaw = gscale((int16_t)g_in.gz);
 	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 };
 static const uint8_t SPI_COLOR[13] = { 0x32, 0x32, 0x32, 0xE6, 0xE6, 0xE6, 0x32,
 				       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])
 { // pack 6 12-bit values into 9 bytes (Switch stick-cal format)
 	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
 // 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)
-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
 	0x02, 0xE5, 0xC8, 0xE4, 0x92, 0x05, 0xFF, 0xC9, 0x8A, 0x7D, 0xEA,
 	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)
 			v = SPI_PARAMS2[a - 0x6098];
 		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;
 	}
 }
@@ -477,7 +477,8 @@ static void jcSubcmd(uint8_t sub, const uint8_t *args, uint16_t alen)
 	jcInputPrefix(p);
 	p[13] = 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;
 		uint8_t t = (alen >= 1) ? args[0] : 3;
 		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
 		p[12] = 0x82;
 		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[17] = 0x02; // fixed
 		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]);
 		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) {
 			uint32_t a = (uint32_t)args[0] |
 				     ((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);
 		}
 		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)
 			g_swProReportMode = 0x30;
 		p[12] = 0x80;
 		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[14] = 0x00;
 		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
 	default:
 		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);
 }
@@ -610,20 +621,22 @@ static void jcSet(uint8_t rid, hid_report_type_t type, uint8_t const *b,
 void SwitchProController::begin()
 {
 	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.setProductDescriptor("Pro Controller");
 	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
 	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.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();
 }
 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 unsigned long g_rumbleMs =
 	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)
 {
@@ -148,44 +149,15 @@ class Adafruit_USBD_XInput : public Adafruit_USBD_Interface {
 		uint8_t epin = TinyUSBDevice.allocEndpoint(TUSB_DIR_IN),
 			epout = TinyUSBDevice.allocEndpoint(TUSB_DIR_OUT);
 		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),
-			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);
 		return XINPUT_DESC_LEN;
@@ -387,9 +359,9 @@ static void rfXboxMouse(const uint8_t *r)
 void XboxController::begin()
 {
 	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
 	// 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).

+ 3 - 2
OpenPuck/mode_xinput.h

@@ -14,7 +14,8 @@ class XboxController : public IController {
 	void begin() override;
 	void onReport45(const uint8_t *rep, bool fresh,
 			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;

+ 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),
 // 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.
-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
 // 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
@@ -132,21 +133,22 @@ static unsigned long g_resumeMs = 0;
 static void handleSet(int slot, uint8_t rid, hid_report_type_t type,
 		      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) {
-			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
 		// 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);
 			}
 		}
-		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,
 				      n);
 			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;
 	const uint8_t *pl = b + 2;
 	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)
-		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!"),
 	// 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).
 	if (rid == 1 && cmd == 0x9F)
 		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
 		// (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,
 		// which hid exactly the LED/power-off frames we now need to see.
 		hapLogAdd((uint8_t)slot, cmd, b, n);
 		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())) {
 			// 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
@@ -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",
 					cmd, len, (unsigned)RELAY_MAXP);
 			relayEnqueue(cmd, pl, rl);
+
+			// track from RELAYED frames only (see the OUTPUT path)
 			if (haptic82)
-				haptic82HostReport(
-					pl,
-					len); // track from RELAYED frames only (see the OUTPUT path)
+				haptic82HostReport(pl, len);
 		}
 	}
 	if (Serial.availableForWrite() >
@@ -254,8 +259,11 @@ static void handleSet(int slot, uint8_t rid, hid_report_type_t type,
 		S.resp_len = 63;
 		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[1] = 0x01;
 		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;
 	if (type != HID_REPORT_TYPE_FEATURE)
 		return 0;
+
+	// host reading feature data => gamepad consumer active (Steam/SDL)
 	if (g_usbMode == MODE_STEAM)
-		hostStampAlive(); // host reading feature data => gamepad consumer active (Steam/SDL)
+		hostStampAlive();
 	Slot &S = g_slot[slot];
 	uint16_t n = S.resp_len ? S.resp_len : 63;
 	if (n > reqlen)
@@ -368,8 +378,9 @@ void SteamPuckController::begin()
 	for (int i = 0; i < NSLOT; i++) {
 		hid[i].setReportDescriptor(desc, descLen);
 		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();
 	}
 }
@@ -475,14 +486,17 @@ void SteamPuckController::task()
 	wakeNudgeTask();
 	{
 		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)
 			g_resumeMs = millis();
 		wasSusp = susp;
 	}
+
+	// no periodic 0x79/0x7B while the host sleeps -- those sends can wake it too
 	if (USBDevice.suspended())
-		return; // no periodic 0x79/0x7B while the host sleeps -- those sends can wake it too
+		return;
 	static bool usbConn = false;
 	static unsigned long last79 = 0, last7B = 0, connEdgeMs = 0;
 	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)
 		override; // forward controller 0x43/0x44 status to Steam
 	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
 	{
 		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).
 uint32_t g_crcinit = 0xFFFF;
 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;
+
 // ENDIAN=Big, BALEN4, MAXLEN=96 (was 64 -> truncated the controller's
 // 66-byte 0x43-battery-augmented F1 -> CRC-fail -> battery never arrived)
 uint32_t g_pcnf1 = 0x01040060;
@@ -95,17 +97,17 @@ void rfConfig(uint8_t ch)
 #if defined(RADIO_MODECNF0_RU_Fast)
 	NRF_RADIO->MODECNF0 = (RADIO_MODECNF0_RU_Fast << RADIO_MODECNF0_RU_Pos);
 #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 =
-		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->CRCPOLY = g_crcpoly;
 	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_rfCh; // current TX/RX channel (hopped during beacon/poll)
 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)
 // 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;
 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;
 
 // ---- 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 uint8_t g_pid;
 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);
 // 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).
 void rfSniffStart()
 {
+	// PARK (no hop): primary carries the traffic
 	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;
 	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];
 	for (int i = 0; i < 4; i++)
 		b[i] = rfBitrev8(base[i]);
@@ -449,9 +447,8 @@ static void rfSniffPoll()
 		uint8_t len = rfrx[0];
 		if (crcok && len && len < 40) {
 			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);
 				g_sprefix = rfrx[19];
 				g_schan[0] = rfrx[11];
@@ -470,8 +467,8 @@ static void rfSniffPoll()
 				g_schi = 0;
 				rfSniffStart();
 			} 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" :
 						  (ty == 0x12 || ty == 0xE1 ||
 						   ty == 0xE4) ?
@@ -480,10 +477,8 @@ static void rfSniffPoll()
 				Serial.printf("%s SNIFF#%lu ch%u len%u: ", tag,
 					      (unsigned long)g_sniffN,
 					      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++)
 					Serial.printf("%02X", rfrx[i]);
 				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 applyCfg(uint8_t i); // apply autosweep candidate radio config i
 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.
 void rfDiagTask();

+ 114 - 104
OpenPuck/rf_link.cpp

@@ -13,16 +13,16 @@
 bool g_rfHost = true;
 bool g_connOn = true;
 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_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;
-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;
 
 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_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 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 ----
 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)
 		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:
 	// [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.
 	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
-	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
+
 	// 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
 	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
 	// 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.
@@ -122,13 +122,13 @@ static void rfHostFrameOnce(int slot, bool discovery)
 	while (!NRF_RADIO->EVENTS_END && (micros() - t0) < bwin) {
 	}
 	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++;
 		bool crcok = NRF_RADIO->CRCSTATUS & 1;
 		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(
 				"*** RESP#%lu ch%u crc%d rxmatch%lu len%u: ",
 				(unsigned long)g_rfRxCount, g_rfCh, crcok,
@@ -149,8 +149,8 @@ void rfHopTo(uint8_t newCh)
 		return;
 	uint8_t cur = g_sessCh, savedRfCh = g_rfCh;
 	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++) {
 		rfHostFrameOnce(g_connSlot, false);
 		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,
 		 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);
 	rftx[0] = plen; // LENGTH = payload byte count
 	rftx[1] = s1; // S1 (type-specific)
 	memcpy(rftx + 2, payload, plen); // payload[0]=type byte, then data/TLVs
 	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->SHORTS = RADIO_SHORTS_READY_START_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;
 		bool crcok = NRF_RADIO->CRCSTATUS & 1;
 		rxlen = rfrx[0];
+		// reply arrived but CRC failed -> RF quality (channel/interference)
 		if (!crcok) {
 			g_stCrc++;
 			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
 			// 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
 			// 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.
-			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,
 				// 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).
@@ -222,11 +219,12 @@ uint8_t rfConnTx(uint8_t ch, uint8_t s1, const uint8_t *payload, uint8_t plen,
 					    1500u)
 					hapticOnReconnect();
 				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)(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)
 					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 +
 								   rs + 4u) /
 								  8u) :
-							rs; // EWMA, ~8-sample horizon
+							rs;
 			}
 			if (rtype == 0xF1)
 				g_stF1++;
+			// controller disconnecting/powering off -> back off 2.5s
 			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_connF3v = rfrx[6];
 				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);
 			if (isF1) {
 				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;
-				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],
-						ttype = rfrx[idx +
-							     1]; // pace with the real puck -- taking only [0] halved our rate.
+						ttype = rfrx[idx + 1];
 					if (tlen == 0)
 						break;
 					// 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 <=
 						    sizeof(rfrx) &&
 					    rfrx[idx + 2] == 0x45) {
+						// report 0x45: [0x45][seq][buttons u32]...
 						const uint8_t *rep =
-							&rfrx[idx +
-							      2]; // report 0x45: [0x45][seq][buttons u32]...
+							&rfrx[idx + 2];
 						bool fresh =
 							(rep[1] != g_lastSeq);
+						// genuine new report vs stale poll-repeat
 						if (fresh) {
 							g_stNew++;
 							g_lastSeq = rep[1];
-						} // genuine new report vs stale poll-repeat
+						}
 						uint32_t bb = btnsOf(rep);
 						// 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.
@@ -300,21 +298,24 @@ uint8_t rfConnTx(uint8_t ch, uint8_t s1, const uint8_t *payload, uint8_t plen,
 									(bb &
 									 TB_STEAM) !=
 									0;
+								// rising edge: record press time
 								if (steamNow &&
 								    !steamWasDown)
 									steamDownMs =
-										millis(); // rising edge: record press time
+										millis();
+								// falling edge within 1 s -> short press -> wake
 								if (!steamNow &&
 								    steamWasDown &&
 								    millis() - steamDownMs <
 									    1000u &&
 								    USBDevice
-									    .suspended()) { // falling edge within 1 s -> short press -> wake
+									    .suspended()) {
 									USBDevice
 										.remoteWakeup();
 									ledWakePulse();
+									// post-resume nudge (host needs real input to actually wake)
 									if (g_active)
-										g_active->wakeEvent(); // post-resume nudge (host needs real input to actually wake)
+										g_active->wakeEvent();
 								}
 								steamWasDown =
 									steamNow;
@@ -350,19 +351,22 @@ uint8_t rfConnTx(uint8_t ch, uint8_t s1, const uint8_t *payload, uint8_t plen,
 								}
 								if (g_usbMode !=
 								    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 *)
 										 rep)
 										[2] &=
 										~(uint8_t)
-											TB_Y; // push modes read btnsOf(rep): TB_Y in rep[2],
+											TB_Y;
+									// TB_STEAM in rep[4]
 									((uint8_t *)
 										 rep)
 										[4] &=
 										~(uint8_t)(TB_STEAM >>
-											   16); // TB_STEAM in rep[4]
+											   16);
 								}
 							} else {
 								offHoldMs = 0;
@@ -380,9 +384,9 @@ uint8_t rfConnTx(uint8_t ch, uint8_t s1, const uint8_t *payload, uint8_t plen,
 									  14);
 						g_in.lt =
 							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,
 									   16);
 						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
 						// 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.
+						// [rid][body...]
 						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)
-							g_battery =
-								rep[2]; // 0x43 body[1] (~0x5e=94) reads as battery % (sniff-derived)
+							g_battery = rep[2];
 						if (g_active)
 							g_active->onAuxReport(
 								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
 			rxlen = 0;
+		// RX window expired with no packet at all
 	} else {
 		g_stNoRx++;
 		g_qosBad++;
-	} // RX window expired with no packet at all
+	}
 	NRF_RADIO->TASKS_DISABLE = 1;
 	RWAIT_DISABLED();
 	NRF_RADIO->EVENTS_DISABLED = 0;
@@ -523,10 +528,12 @@ static void rfConnStep()
 			g_connSlot = s;
 			break;
 		}
+	// need a bonded slot (session established by host frame)
 	if (g_connSlot < 0)
-		return; // need a bonded slot (session established by host frame)
+		return;
 	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 };
 		rfConnTx(ch, 0x01, p, 3);
 		if (++g_connStep >= 4) {
@@ -535,9 +542,11 @@ static void rfConnStep()
 			Serial.println(
 				"# 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)
-			return; // CONTROLLED CADENCE: poll ~every g_pollUs
+			return;
 		// 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
 		// 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();
 			static uint32_t lastFire = 0;
+			// MEASURED actual period
 			if (lastFire) {
 				g_pollDtSum += (uint32_t)(now - lastFire);
 				g_pollDtCnt++;
-			} // MEASURED actual period
+			}
 			lastFire = now;
 			g_lastPollUs += g_pollUs;
+			// fell >1 interval behind -> resync
 			if ((uint32_t)(now - g_lastPollUs) >= g_pollUs)
-				g_lastPollUs =
-					now; // fell >1 interval behind -> resync
+				g_lastPollUs = now;
 		}
 		if ((g_connPoll & 0x1F) == 0) {
 			uint8_t pa[3] = { 0xE7, 0x00, g_e7b };
@@ -569,19 +579,18 @@ static void rfConnStep()
 			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 =
 				(g_e3mode == 1) ?
 					(uint8_t)((((g_e3pid++) & 3) << 1) |
-						  1) // cycle PID (S1 1,3,5,7), NO_ACK=1
-				:
+						  1) :
 				(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);
 			if (rx)
 				g_chF1[0]++;
@@ -619,15 +628,16 @@ void rfLinkTask()
 		rfConnStep();
 	} // 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 =
 			(g_connSlot >= 0 && millis() - g_connReplyMs < 300);
 		if (nowRfConn && !wasRfConn && USBDevice.suspended()) {
 			USBDevice.remoteWakeup();
 			ledWakePulse();
+			// post-resume nudge (host needs real input to actually wake)
 			if (g_active)
-				g_active->wakeEvent(); // post-resume nudge (host needs real input to actually wake)
+				g_active->wakeEvent();
 		}
 		wasRfConn = nowRfConn;
 	}

+ 25 - 17
OpenPuck/rf_link.h

@@ -14,28 +14,33 @@
 #include <stdint.h>
 
 // ---- 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 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_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 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)
 extern uint8_t g_connSt, g_connStep;
 extern uint16_t g_connPoll;
 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
 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
 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
 // 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;
 				NRF_RADIO->TASKS_DISABLE = 1;
 				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);
 				if (modeValid(m)) {
 					if (USBDevice.suspended()) {
@@ -105,8 +108,7 @@ void serialConsolePoll()
 						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);
 				Serial.printf("# ch=%u\n", g_rfCh);
 				if (g_rfListen)
@@ -240,9 +242,9 @@ void serialConsolePoll()
 			} else if (line[0] == 'J') {
 				char *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)(val >> 8) };
 				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",
 					g_e3mode);
 			} 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;
 				Serial.printf(
 					"# test-haptic burst x%u queued (relay 0x82 01 01 F7 via op=%02X sub=%02X)\n",
 					n, g_relayOp, g_relaySub);
-			} else if (line[0] == 'H') {
+			}
+
+			// dump the captured OUTPUT-report history (oldest->newest)
+			else if (line[0] == 'H') {
 				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);
 				Serial.printf("# connType=%02X\n", g_connType);
 			} else if (line[0] == 'Q') {

+ 3 - 2
OpenPuck/status_led.cpp

@@ -24,8 +24,9 @@ void ledWakePulse()
 {
 	g_pulseMs = millis();
 	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()

+ 9 - 5
OpenPuck/status_led.h

@@ -14,17 +14,21 @@
 #pragma once
 
 #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
 #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
 #ifndef WAKE_LED_ON
 #define WAKE_LED_ON HIGH // set LOW if your board's LED is wired active-low
 #endif
 
 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

+ 11 - 7
OpenPuck/triton.h

@@ -30,17 +30,20 @@
 #define TB_LB 0x80000u
 #define TB_RPADT 0x200000u
 #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_LPADC 0x4000000u
 
 // all four back paddles held -> mode-switch chord guard
 #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) ----
 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
 // was copied verbatim into g_swPro*, g_ps5*, and g_gyro*). One source, many consumers.
 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)
 	uint8_t lt, rt; // triggers scaled 0..255 (trigU8)
 	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()
 {
-	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.setReportDescriptor(WAKE_HID_DESC, sizeof WAKE_HID_DESC);
 	g_wakeHid.setPollInterval(10);

+ 2 - 1
OpenPuck/wake_hid.h

@@ -16,4 +16,5 @@
 // (g_debugCdcThisBoot) and ARCHITECTURE.md.
 #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]
 //                [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]
-#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()
 {
 	if (!usb_web.connected())
@@ -27,7 +27,9 @@ static void webusbSendBlob()
 	uint8_t p[2 + WB_PAYLEN];
 	p[0] = 0xA5;
 	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[4] = (uint8_t)g_mDiv;
 	p[5] = (uint8_t)g_mFric;
@@ -54,8 +56,9 @@ static void webusbSendBlob()
 	p[26] = g_chordBtn[1];
 	p[27] = g_chordBtn[2];
 	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[31] = (uint8_t)(g_loopPeriodUs >>
 			  8); // loop diag: avg iteration time + slowest section
@@ -79,8 +82,12 @@ static void webusbSendBlob()
 			p[41 + i] = (uint8_t)h[i];
 	}
 	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 };
 		memcpy(&p[56], a, 6);
@@ -150,14 +157,12 @@ void webusbPoll()
 			if (n == 0)
 				break;
 			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
 				memmove(buf, buf + 1, --n);
 				continue;
@@ -168,23 +173,29 @@ void webusbPoll()
 				webusbSendBlob();
 			}
 #if OPK_LOG
+
+			// rewind drain: buf[1]=1 from boot (whole ring), 0 from now
 			else if (op == 0x05) {
 				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();
 			} // drain entries since the rewind (panel polls this)
 #endif
+
+			// clear a stuck/latched haptic buzz on the controller (functional)
 			else if (op == 0x07) {
 				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) {
 				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 &&
 				    buf[3] == 0x53) {
 					usb_web.flush();
@@ -194,8 +205,9 @@ void webusbPoll()
 				}
 			} else if (op == 0x02) {
 				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) {
 				case 1:
 					g_mDiv = v < 4 ? 4 : v;
@@ -214,53 +226,73 @@ void webusbPoll()
 					g_back[f - 5] = v;
 					break;
 				// 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:
 					g_e7b = v ? 1 : 0;
-					break; // E7 protocol-version B-byte (experimental v1 fast)
+					break;
+
+				// haptic-relay opcode (buzz hunt)
 				case 11:
 					g_relayOp = v;
-					break; // haptic-relay opcode (buzz hunt)
+					break;
+
+				// haptic-relay sub-type (buzz hunt)
 				case 12:
 					g_relaySub = v;
-					break; // haptic-relay sub-type (buzz hunt)
+					break;
 				case 13:
 					g_testHaptic = v ? v : 40;
 					break; // inject v test haptics (0->40)
+
+				// Steam: forward only fresh reports (dedupe)
 				case 14:
 					g_fwdNewOnly = v ? 1 : 0;
-					break; // Steam: forward only fresh reports (dedupe)
+					break;
 				case 15:
 					g_qos = v ? 1 : 0;
 					g_hopIdx = 0;
 					g_qosBad = 0;
 					g_qosCheckMs = millis();
 					break; // QoS adaptive channel hopping
+
+				// persist last mode across reboots (else always boot Steam)
 				case 16:
 					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 18:
 				case 19:
 					if (modeValid(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:
 					armDebugCdcNextBoot();
 					usb_web.flush();
 					delay(40);
 					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:
 					g_qamMap = v;
-					break; // QAM physical button remap code (0=default/unmapped)
+					break;
+
+				// rumble strength % (0=off, 100=1x, 200=double)
 				case 22:
 					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:
 					g_swProRate = (v <= 2) ? v : 1;
 					swProSaveCfg();
 					persist = false;
-					break; // Switch Pro report rate (0=66Hz,1=120Hz,2=full)
+					break;
 				case 24:
 					g_swGyroScale10 =
 						(v >= 5 && v <= 30) ? v : 10;

+ 3 - 3
pairtui/scmd.c

@@ -873,9 +873,9 @@ int main(int argc, char **argv)
 		IOHIDDeviceClose(dev, kIOHIDOptionsTypeNone);
 		CFRelease(dev);
 		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.
 		static const int block[] = { 0x80, 0x81, 0x85, 0x86, 0x87, 0x88,
 					     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)
 static bool g_pinSession =
 	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
 // (so you can see how many pucks/slots are advertising + their bases)
 static bool g_survey = false;
@@ -149,9 +150,9 @@ static void rxPump()
 		NRF_RADIO->EVENTS_END = 0;
 		bool crcok = NRF_RADIO->CRCSTATUS & 1;
 		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;
 		uint8_t len = rfrx[0];
 		uint16_t n = (uint16_t)(2 + len);
@@ -161,11 +162,11 @@ static void rxPump()
 			 (uint8_t)n);
 		g_lastRx = millis();
 		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)
-			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
 		// 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).
@@ -177,7 +178,8 @@ static void rxPump()
 			g_advBase[3] = rfrx[18];
 			g_advPfx = rfrx[19];
 			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,
 			// so skip any E1 that isn't the CONNECTED controller's -- otherwise we lock an idle slot's keepalive.
 			if (g_haveTarget &&
@@ -248,8 +250,9 @@ static void drainToHost()
 {
 	if (!usb_web.connected())
 		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) {
 		if (g_streaming)
 			emitPacket(g_ring[g_tail]);
@@ -283,8 +286,8 @@ static void handleCmd()
 			g_pinSession = false;
 			tuneDiscovery();
 		} // 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(),
 			    u2 = usb_web.read(), u3 = usb_web.read();
 			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
 	// landing page -> no Chrome "open <url>" notification. No detach/clearConfiguration: that's OpenPuck-specific
 	// 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.setProductDescriptor("OpenPuck Sniffer");
 	usb_web.begin();
@@ -341,7 +344,8 @@ void setup()
 	for (int i = 0; i < 200 && !USBDevice.mounted(); i++)
 		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();
 	g_lastRx = millis();
 }
@@ -350,7 +354,9 @@ void loop()
 {
 	rxPump();
 	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();
 	// 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

+ 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).
 uint32_t g_crcinit = 0xFFFF;
 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 =
 	0x00030008UL; // S0LEN0, LFLEN8, S1LEN3 (ESB DPL) - CRC-VALIDATED
 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;
 uint16_t g_crccnf = 0x2; // CRC16, address included
 uint8_t g_pid = 0;
@@ -129,17 +131,18 @@ void rfConfig(uint8_t ch)
 #if defined(RADIO_MODECNF0_RU_Fast)
 	NRF_RADIO->MODECNF0 = (RADIO_MODECNF0_RU_Fast << RADIO_MODECNF0_RU_Pos);
 #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 =
-		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->CRCPOLY = g_crcpoly;
 	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_rfCh; // current TX/RX channel (hopped during beacon/poll)
 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)
 // 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 uint8_t g_pid;
 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);
 // 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))