1	===ALL HID_CODE_FOR defines===
2	20:#define HID_CODE_FOR_SW_VER						('*') //0x2a
3	21:#define HID_CODE_FOR_SERIAL_NUM                 ('%') //0x25
4	22:#define HID_CODE_FOR_HMD_SERIAL_NUM             ('&') //0x26
5	23:#define HID_CODE_FOR_TRACKING_SERIAL    		('~') //0x7e
6	24:#define HID_CODE_FOR_USAGE_TIMER_GET    		('Z') //0x5a
7	25:#define HID_CODE_FOR_USAGE_TIMER_SET    		('z') //0x7a
8	26:#define HID_CODE_FOR_STEREO						('S') //0x53
9	27:#define HID_CODE_FOR_GAIN						('G') //0x47
10	28:#define HID_CODE_FOR_FRAC_GAIN                  ('g') //0x67
11	29:#define HID_CODE_FOR_FAN						('F') //0x46
12	30:#define HID_CODE_FOR_FAN_DEFERRED               ('f') //0x66
13	31:#define HID_CODE_FOR_RATE                    	('R') //0x52
14	32:#define HID_CODE_FOR_OLED_FLIP                  ('P') //0x50
15	33:#define HID_CODE_FOR_OLED_BRIGHNTESS   		 	('I') //0x49
16	34:#define HID_CODE_FOR_OLED_ID                    ('^') //0x5e
17	35:#define HID_CODE_FOR_BOOTLOADER                 ('B') //0x42
18	36:#define HID_CODE_FOR_RESET						('C') //0x43
19	37:#define HID_CODE_FOR_READ_SIG                   ('U') //0x55
20	38:#define HID_CODE_FOR_RGB_LED                    ('L') //0x4c
21	39:#define HID_CODE_FOR_WRITE_SIG                  ('W') //0x57
22	40:#define HID_CODE_FOR_SAVE_SIG                   ('V') //0x56
23	41:#define HID_CODE_FOR_PROX_SETTINGS              ('M') //0x4d
24	42:#define HID_CODE_FOR_CHECK_VXR_CONNECTION       ('X') //0x58
25	43:#define HID_CODE_FOR_VXR_CHECKSUM               ('K') //0x4b
26	44:#define HID_CODE_FOR_VXR_TAGS                   ('T') //0x54
27	45:#define HID_CODE_FOR_VXR_DELETE                 ('D') //0x44
28	46:#define HID_CODE_FOR_VXR_PROGRAM                ('A') //0x41
29	47:#define HID_CODE_FOR_VXR_RESET                  ('Y') //0x59
30	48:#define HID_CODE_FOR_VXR_FWNAME                 ('N') //0x4e
31	49:#define HID_CODE_FOR_HW_TEST                    ('J') //0x4a
32	50:#define HID_CODE_FOR_FATP_MODE                  ('@') //0x40
33	51:#define HID_CODE_FOR_OLED_COMMAND               ('o') //0x6f
34	52:#define HID_CODE_FOR_EDID_SWITCH                ('d') //0x64
35	53:#define HID_CODE_FOR_PROX_DISABLE               ('p') //0x70
36	54:#define HID_CODE_FOR_PROX_ENABLE                ('[') //0x5b
37	55:#define HID_CODE_FOR_COLORBARPATTERN    		('=') //0x3d
38	56:#define HID_CODE_FOR_STACK_LEVELS               ('s') //0x73
39	57:#define HID_CODE_FOR_OLED_POWER_DISABLE 		('-') //0x2d
40	58:#define HID_CODE_FOR_OLED_POWER_ENABLE  		('+') //0x2b
41	59:#define HID_CODE_FOR_FPGA_COMMANDS              ('e') //0x65
42	60:#define HID_CODE_FOR_VXR_1V_RAIL				('1') //0x31
43	61:#define HID_CODE_FOR_PROX_USER_TRIM				('t') //0x74
44	62:#define HID_CODE_FOR_DISPLAY_SLEEP				('H') //0x48 - enter doze (warm shallow sleep)
45	63:#define HID_CODE_FOR_DISPLAY_WAKE				('h') //0x68 - wake from doze
46	68: #define HID_CODE_FOR_HARDFAULT                  (',') //0x2c
47	69: #define HID_CODE_FOR_UNUSED_IRQ                 ('.') //0x2e
48	70: #define HID_CODE_FOR_STACK_OVERFLOW             ('>') //0x3e
49	76:#define HID_CODE_FOR_DATA_REPLY					('#') //0x23
50	77:#define HID_CODE_FOR_SW_VER_REPLY			HID_CODE_FOR_SW_VER
51	78:#define HID_CODE_FOR_SERIAL_NUM_REPLY		HID_CODE_FOR_SERIAL_NUM
52	79:#define HID_CODE_FOR_HMD_SERIAL_REPLY		HID_CODE_FOR_HMD_SERIAL_NUM
53	80:#define HID_CODE_FOR_TRACKING_SERIAL_REPLY	HID_CODE_FOR_TRACKING_SERIAL
54	81:#define HID_CODE_FOR_USAGE_TIMER_REPLY		HID_CODE_FOR_USAGE_TIMER_GET
55	82:#define HID_CODE_FOR_SIG_REPLY				HID_CODE_FOR_READ_SIG
56	83:#define HID_CODE_FOR_VXR_CHECKSUM_REPLY		HID_CODE_FOR_VXR_CHECKSUM
57	84:#define HID_CODE_FOR_VXR_TAGS_REPLY			HID_CODE_FOR_VXR_TAGS
58	85:#define HID_CODE_FOR_VXR_FWNAME_REPLY		HID_CODE_FOR_VXR_FWNAME
59	86:#define HID_CODE_FOR_HW_TEST_REPLY			HID_CODE_FOR_HW_TEST
60	87:#define HID_CODE_FOR_OLED_ID_REPLY			HID_CODE_FOR_OLED_ID
61	88:#define HID_CODE_FOR_STACK_LEVELS_REPLY		HID_CODE_FOR_STACK_LEVELS
62	89:#define HID_CODE_FOR_TUNDRA_UART_REPLY			(';') //0x3b
63	90:#define HID_CODE_FOR_SUCCESS_REPLY				('$') //0x24
64	91:#define HID_CODE_FOR_ERROR_REPLY				('E') //0x45
65
66	===0x48 / 0x68 / H / h references anywhere in src===
67	src/ASF/sam/utils/cmsis/samg/samg55/include/component/uhp.h:65:  __IO uint32_t UHP_HCRHDESCRIPTORA;    /**< \brief (Uhp Offset: 0x48) HC Root Hub A Register */
68	src/ASF/sam/utils/cmsis/samg/samg55/include/component/uhp.h:174:/* -------- UHP_HCRHDESCRIPTORA : (UHP Offset: 0x48) HC Root Hub A Register -------- */
69	src/Drivers/signature.h:32://		0x01, 0x08,    0x48, 0x69, 0x54, 0x68, 0x65, 0x72, 0x65, 0x21, 0x9D
70	src/edid.h:46:	0x70, 0x12, 0x79, 0x00, 0x00, 0x03, 0x00, 0x14, 0x68, 0x9F, 0x00, 0x80, 0xFF, 0x13, 0x7F, 0x00,
71	src/hw_test.h:7:	HWTest_USB_Hub = 'H',
72	src/sw_ver.h:9:// ('H'/'h'), doze-latch, fan kickstart. Host capability-gates doze on >= 0.4.0.
73	src/sw_ver.h:13:// 0.4.3: born-doze wake race fix - an 'h' arriving before the bringup settles
74	src/usbhid_interface.h:62:#define HID_CODE_FOR_DISPLAY_SLEEP				('H') //0x48 - enter doze (warm shallow sleep)
75	src/usbhid_interface.h:63:#define HID_CODE_FOR_DISPLAY_WAKE				('h') //0x68 - wake from doze
76	src/video_proc.c:60:// Born-doze wake race: the host may send 'h' (display-wake) AFTER arming the
77	src/video_proc.c:65:// wake fade, so the host need not time 'h' precisely.
78	src/video_proc.c:796:								// Host already requested wake (its 'h' raced ahead of this
79

===ALL HID_CODE_FOR defines===
20:#define HID_CODE_FOR_SW_VER						('*') //0x2a
21:#define HID_CODE_FOR_SERIAL_NUM                 ('%') //0x25
22:#define HID_CODE_FOR_HMD_SERIAL_NUM             ('&') //0x26
23:#define HID_CODE_FOR_TRACKING_SERIAL    		('~') //0x7e
24:#define HID_CODE_FOR_USAGE_TIMER_GET    		('Z') //0x5a
25:#define HID_CODE_FOR_USAGE_TIMER_SET    		('z') //0x7a
26:#define HID_CODE_FOR_STEREO						('S') //0x53
27:#define HID_CODE_FOR_GAIN						('G') //0x47
28:#define HID_CODE_FOR_FRAC_GAIN                  ('g') //0x67
29:#define HID_CODE_FOR_FAN						('F') //0x46
30:#define HID_CODE_FOR_FAN_DEFERRED               ('f') //0x66
31:#define HID_CODE_FOR_RATE                    	('R') //0x52
32:#define HID_CODE_FOR_OLED_FLIP                  ('P') //0x50
33:#define HID_CODE_FOR_OLED_BRIGHNTESS   		 	('I') //0x49
34:#define HID_CODE_FOR_OLED_ID                    ('^') //0x5e
35:#define HID_CODE_FOR_BOOTLOADER                 ('B') //0x42
36:#define HID_CODE_FOR_RESET						('C') //0x43
37:#define HID_CODE_FOR_READ_SIG                   ('U') //0x55
38:#define HID_CODE_FOR_RGB_LED                    ('L') //0x4c
39:#define HID_CODE_FOR_WRITE_SIG                  ('W') //0x57
40:#define HID_CODE_FOR_SAVE_SIG                   ('V') //0x56
41:#define HID_CODE_FOR_PROX_SETTINGS              ('M') //0x4d
42:#define HID_CODE_FOR_CHECK_VXR_CONNECTION       ('X') //0x58
43:#define HID_CODE_FOR_VXR_CHECKSUM               ('K') //0x4b
44:#define HID_CODE_FOR_VXR_TAGS                   ('T') //0x54
45:#define HID_CODE_FOR_VXR_DELETE                 ('D') //0x44
46:#define HID_CODE_FOR_VXR_PROGRAM                ('A') //0x41
47:#define HID_CODE_FOR_VXR_RESET                  ('Y') //0x59
48:#define HID_CODE_FOR_VXR_FWNAME                 ('N') //0x4e
49:#define HID_CODE_FOR_HW_TEST                    ('J') //0x4a
50:#define HID_CODE_FOR_FATP_MODE                  ('@') //0x40
51:#define HID_CODE_FOR_OLED_COMMAND               ('o') //0x6f
52:#define HID_CODE_FOR_EDID_SWITCH                ('d') //0x64
53:#define HID_CODE_FOR_PROX_DISABLE               ('p') //0x70
54:#define HID_CODE_FOR_PROX_ENABLE                ('[') //0x5b
55:#define HID_CODE_FOR_COLORBARPATTERN    		('=') //0x3d
56:#define HID_CODE_FOR_STACK_LEVELS               ('s') //0x73
57:#define HID_CODE_FOR_OLED_POWER_DISABLE 		('-') //0x2d
58:#define HID_CODE_FOR_OLED_POWER_ENABLE  		('+') //0x2b
59:#define HID_CODE_FOR_FPGA_COMMANDS              ('e') //0x65
60:#define HID_CODE_FOR_VXR_1V_RAIL				('1') //0x31
61:#define HID_CODE_FOR_PROX_USER_TRIM				('t') //0x74
62:#define HID_CODE_FOR_DISPLAY_SLEEP				('H') //0x48 - enter doze (warm shallow sleep)
63:#define HID_CODE_FOR_DISPLAY_WAKE				('h') //0x68 - wake from doze
68: #define HID_CODE_FOR_HARDFAULT                  (',') //0x2c
69: #define HID_CODE_FOR_UNUSED_IRQ                 ('.') //0x2e
70: #define HID_CODE_FOR_STACK_OVERFLOW             ('>') //0x3e
76:#define HID_CODE_FOR_DATA_REPLY					('#') //0x23
77:#define HID_CODE_FOR_SW_VER_REPLY			HID_CODE_FOR_SW_VER
78:#define HID_CODE_FOR_SERIAL_NUM_REPLY		HID_CODE_FOR_SERIAL_NUM
79:#define HID_CODE_FOR_HMD_SERIAL_REPLY		HID_CODE_FOR_HMD_SERIAL_NUM
80:#define HID_CODE_FOR_TRACKING_SERIAL_REPLY	HID_CODE_FOR_TRACKING_SERIAL
81:#define HID_CODE_FOR_USAGE_TIMER_REPLY		HID_CODE_FOR_USAGE_TIMER_GET
82:#define HID_CODE_FOR_SIG_REPLY				HID_CODE_FOR_READ_SIG
83:#define HID_CODE_FOR_VXR_CHECKSUM_REPLY		HID_CODE_FOR_VXR_CHECKSUM
84:#define HID_CODE_FOR_VXR_TAGS_REPLY			HID_CODE_FOR_VXR_TAGS
85:#define HID_CODE_FOR_VXR_FWNAME_REPLY		HID_CODE_FOR_VXR_FWNAME
86:#define HID_CODE_FOR_HW_TEST_REPLY			HID_CODE_FOR_HW_TEST
87:#define HID_CODE_FOR_OLED_ID_REPLY			HID_CODE_FOR_OLED_ID
88:#define HID_CODE_FOR_STACK_LEVELS_REPLY		HID_CODE_FOR_STACK_LEVELS
89:#define HID_CODE_FOR_TUNDRA_UART_REPLY			(';') //0x3b
90:#define HID_CODE_FOR_SUCCESS_REPLY				('$') //0x24
91:#define HID_CODE_FOR_ERROR_REPLY				('E') //0x45

===0x48 / 0x68 / H / h references anywhere in src===
src/ASF/sam/utils/cmsis/samg/samg55/include/component/uhp.h:65:  __IO uint32_t UHP_HCRHDESCRIPTORA;    /**< \brief (Uhp Offset: 0x48) HC Root Hub A Register */
src/ASF/sam/utils/cmsis/samg/samg55/include/component/uhp.h:174:/* -------- UHP_HCRHDESCRIPTORA : (UHP Offset: 0x48) HC Root Hub A Register -------- */
src/Drivers/signature.h:32://		0x01, 0x08,    0x48, 0x69, 0x54, 0x68, 0x65, 0x72, 0x65, 0x21, 0x9D
src/edid.h:46:	0x70, 0x12, 0x79, 0x00, 0x00, 0x03, 0x00, 0x14, 0x68, 0x9F, 0x00, 0x80, 0xFF, 0x13, 0x7F, 0x00,
src/hw_test.h:7:	HWTest_USB_Hub = 'H',
src/sw_ver.h:9:// ('H'/'h'), doze-latch, fan kickstart. Host capability-gates doze on >= 0.4.0.
src/sw_ver.h:13:// 0.4.3: born-doze wake race fix - an 'h' arriving before the bringup settles
src/usbhid_interface.h:62:#define HID_CODE_FOR_DISPLAY_SLEEP				('H') //0x48 - enter doze (warm shallow sleep)
src/usbhid_interface.h:63:#define HID_CODE_FOR_DISPLAY_WAKE				('h') //0x68 - wake from doze
src/video_proc.c:60:// Born-doze wake race: the host may send 'h' (display-wake) AFTER arming the
src/video_proc.c:65:// wake fade, so the host need not time 'h' precisely.
src/video_proc.c:796:								// Host already requested wake (its 'h' raced ahead of this


Prompt:
<task-notification>
<task-id>bjdo4tjwa</task-id>
<tool-use-id>toolu_01H2bbcpscYT4shBqxgTZTcA</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-sauna\d3fb9be1-d61d-4f88-b77c-2a2a282b11fd\tasks\bjdo4tjwa.output</output-file>
<status>completed</status>
<summary>Background command "HID code table + collision scan" completed (exit code 0)</summary>
</task-notification>


Decisive deduction forming. The HID code table shows **`'H'` (0x48) sat in an unused gap** between `GAIN` (`'G'`=0x47) and `OLED_BRIGHTNESS` (`'I'`=0x49); `'h'` (0x68) right after `FRAC_GAIN` (`'g'`=0x67). Pre-0.4.0 those two bytes were unhandled no-ops. 0.4.x claims them as doze sleep/wake.

Critical: `doze_active` can **only** be set via a `VIDEOPROC_DOZE_SLEEP` notify, which is sent **only** from `usbhid_set_feature` when `report[0]==0x48`. So if the addons produce doze-like symptoms (stuck dim + prox gated), they *must* be sending a feature report with `report[0]` == 0x48/0x68. There's no other path into the latch.

Two things to confirm before I commit a fix: (1) the dispatch keys purely on `report[0]` with no payload signature (so a stray byte triggers it), and (2) whether the doze stepper's `powf` could overflow the video-task stack (would explain full wedge / 0101 stops ACKing, beyond a mere dim).


Bash

cd ../beyond_synaptics && echo "===set_feature dispatch shape (report[0] handling)===" && grep -nE "report\[0\] ?== ?HID_CODE|void usbhid_set_feature|switch|if *\(report" src/usbhid_interface.c | head -50 && echo "" && echo "===video task stack + priority===" && grep -rnE "TASK_VIDEO_PROC_STACK_SIZE|TASK_VIDEO_PROC_PRIORITY|configMINIMAL_STACK|configCHECK_FOR_STACK" src/ | head

Dispatch shape + video task stack size


Command running in background with ID: b975le2p1. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-sauna\d3fb9be1-d61d-4f88-b77c-2a2a282b11fd\tasks\b975le2p1.output. You will be notified when it completes. To check interim output, use Read on that file path.


<task-notification>
<task-id>b975le2p1</task-id>
<tool-use-id>toolu_015dNzYuqZxY1LAE3A6ACCag</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-sauna\d3fb9be1-d61d-4f88-b77c-2a2a282b11fd\tasks\b975le2p1.output</output-file>
<status>completed</status>
<summary>Background command "Dispatch shape + video task stack size" completed (exit code 0)</summary>
</task-notification>


proceed with next steps. hmd power cycled.


1	===set_feature dispatch shape (report[0] handling)===
2	193:				switch((notify_value & 0xFF00) >> 8) {
3	233:				switch((notify_value & 0xFF00) >> 8) {
4	367:				switch( (HW_Test_T)((notify_value & 0xFF00u) >> 8) ) {
5	375:						_command_report_data[1] = test_usbc_switch();
6	556:void usbhid_set_feature(uint8_t* report) {
7	562:	if(report[0] == HID_CODE_FOR_SW_VER){
8	565:	if(report[0] == HID_CODE_FOR_SERIAL_NUM) {
9	568:	if(report[0] == HID_CODE_FOR_HMD_SERIAL_NUM) {
10	571:	if(report[0] == HID_CODE_FOR_TRACKING_SERIAL) {
11	574:	if(report[0] == HID_CODE_FOR_USAGE_TIMER_GET) {
12	583:	if(report[0] == HID_CODE_FOR_USAGE_TIMER_SET) {
13	594:	if(report[0] == HID_CODE_FOR_STEREO) {
14	596:		if(report[1] == 0) {
15	605:	if(report[0] == HID_CODE_FOR_GAIN) {
16	613:	if(report[0] == HID_CODE_FOR_FRAC_GAIN) {
17	620:	if(report[0] == HID_CODE_FOR_FAN) {
18	627:	if(report[0] == HID_CODE_FOR_FAN_DEFERRED) {
19	635:	if(report[0] == HID_CODE_FOR_RATE) {
20	647:	if(report[0] == HID_CODE_FOR_OLED_FLIP) {
21	653:		if(report[1] == 0) {
22	661:		} else if(report[1] == 1) {
23	676:	if(report[0] == HID_CODE_FOR_OLED_ID) {
24	681:		switch(report[1]) {
25	693:	if(report[0] == HID_CODE_FOR_OLED_BRIGHNTESS) {
26	710:	if(report[0] == HID_CODE_FOR_RESET) {
27	715:	if(report[0] == HID_CODE_FOR_BOOTLOADER) {
28	720:	if(report[0] == HID_CODE_FOR_READ_SIG) {
29	728:	if(report[0] == HID_CODE_FOR_WRITE_SIG) {
30	735:		if(report[1] <= 15) {
31	744:	if(report[0] == HID_CODE_FOR_SAVE_SIG) {
32	755:	if(report[0] == HID_CODE_FOR_RGB_LED) {
33	780:	if(report[0] == HID_CODE_FOR_PROX_SETTINGS) {
34	806:	if(report[0] == HID_CODE_FOR_VXR_CHECKSUM) {
35	815:	if(report[0] == HID_CODE_FOR_VXR_TAGS) {
36	820:	if(report[0] == HID_CODE_FOR_VXR_DELETE) {
37	839:	if(report[0] == HID_CODE_FOR_VXR_PROGRAM) {
38	855:	if(report[0] == HID_CODE_FOR_VXR_RESET) {
39	861:	if(report[0] == HID_CODE_FOR_VXR_FWNAME) {
40	866:	if(report[0] == HID_CODE_FOR_HW_TEST) {
41	873:	if(report[0] == HID_CODE_FOR_FATP_MODE) {
42	889:	if(report[0] == HID_CODE_FOR_OLED_COMMAND) {
43	908:	if(report[0] == HID_CODE_FOR_EDID_SWITCH) {
44	923:	if(report[0] == HID_CODE_FOR_PROX_DISABLE) {
45	934:	if(report[0] == HID_CODE_FOR_PROX_ENABLE) {
46	945:	if(report[0] == HID_CODE_FOR_DISPLAY_SLEEP) {
47	949:	if(report[0] == HID_CODE_FOR_DISPLAY_WAKE) {
48	954:	if(report[0] == HID_CODE_FOR_COLORBARPATTERN) {
49	960:	if(report[0] == HID_CODE_FOR_STACK_LEVELS) {
50	966:	if(report[0] == HID_CODE_FOR_HARDFAULT){
51	975:	if(report[0] == HID_CODE_FOR_UNUSED_IRQ){
52
53	===video task stack + priority===
54	src/ASF/thirdparty/freertos/freertos-10.0.0/Source/include/FreeRTOS.h:80:#ifndef configMINIMAL_STACK_SIZE
55	src/ASF/thirdparty/freertos/freertos-10.0.0/Source/include/FreeRTOS.h:81:	#error Missing definition:  configMINIMAL_STACK_SIZE must be defined in FreeRTOSConfig.h.  configMINIMAL_STACK_SIZE defines the size (in words) of the stack allocated to the idle task.  Refer to the demo project provided for your port for a suitable value.
56	src/ASF/thirdparty/freertos/freertos-10.0.0/Source/include/FreeRTOS.h:374:#ifndef configCHECK_FOR_STACK_OVERFLOW
57	src/ASF/thirdparty/freertos/freertos-10.0.0/Source/include/FreeRTOS.h:375:	#define configCHECK_FOR_STACK_OVERFLOW 0
58	src/ASF/thirdparty/freertos/freertos-10.0.0/Source/include/StackMacros.h:41: * Setting configCHECK_FOR_STACK_OVERFLOW to 1 will cause the macro to check
59	src/ASF/thirdparty/freertos/freertos-10.0.0/Source/include/StackMacros.h:43: * the stack limit.  Setting configCHECK_FOR_STACK_OVERFLOW to greater than 1
60	src/ASF/thirdparty/freertos/freertos-10.0.0/Source/include/StackMacros.h:52:#if( ( configCHECK_FOR_STACK_OVERFLOW == 1 ) && ( portSTACK_GROWTH < 0 ) )
61	src/ASF/thirdparty/freertos/freertos-10.0.0/Source/include/StackMacros.h:64:#endif /* configCHECK_FOR_STACK_OVERFLOW == 1 */
62	src/ASF/thirdparty/freertos/freertos-10.0.0/Source/include/StackMacros.h:67:#if( ( configCHECK_FOR_STACK_OVERFLOW == 1 ) && ( portSTACK_GROWTH > 0 ) )
63	src/ASF/thirdparty/freertos/freertos-10.0.0/Source/include/StackMacros.h:80:#endif /* configCHECK_FOR_STACK_OVERFLOW == 1 */
64

===set_feature dispatch shape (report[0] handling)===
193:				switch((notify_value & 0xFF00) >> 8) {
233:				switch((notify_value & 0xFF00) >> 8) {
367:				switch( (HW_Test_T)((notify_value & 0xFF00u) >> 8) ) {
375:						_command_report_data[1] = test_usbc_switch();
556:void usbhid_set_feature(uint8_t* report) {
562:	if(report[0] == HID_CODE_FOR_SW_VER){
565:	if(report[0] == HID_CODE_FOR_SERIAL_NUM) {
568:	if(report[0] == HID_CODE_FOR_HMD_SERIAL_NUM) {
571:	if(report[0] == HID_CODE_FOR_TRACKING_SERIAL) {
574:	if(report[0] == HID_CODE_FOR_USAGE_TIMER_GET) {
583:	if(report[0] == HID_CODE_FOR_USAGE_TIMER_SET) {
594:	if(report[0] == HID_CODE_FOR_STEREO) {
596:		if(report[1] == 0) {
605:	if(report[0] == HID_CODE_FOR_GAIN) {
613:	if(report[0] == HID_CODE_FOR_FRAC_GAIN) {
620:	if(report[0] == HID_CODE_FOR_FAN) {
627:	if(report[0] == HID_CODE_FOR_FAN_DEFERRED) {
635:	if(report[0] == HID_CODE_FOR_RATE) {
647:	if(report[0] == HID_CODE_FOR_OLED_FLIP) {
653:		if(report[1] == 0) {
661:		} else if(report[1] == 1) {
676:	if(report[0] == HID_CODE_FOR_OLED_ID) {
681:		switch(report[1]) {
693:	if(report[0] == HID_CODE_FOR_OLED_BRIGHNTESS) {
710:	if(report[0] == HID_CODE_FOR_RESET) {
715:	if(report[0] == HID_CODE_FOR_BOOTLOADER) {
720:	if(report[0] == HID_CODE_FOR_READ_SIG) {
728:	if(report[0] == HID_CODE_FOR_WRITE_SIG) {
735:		if(report[1] <= 15) {
744:	if(report[0] == HID_CODE_FOR_SAVE_SIG) {
755:	if(report[0] == HID_CODE_FOR_RGB_LED) {
780:	if(report[0] == HID_CODE_FOR_PROX_SETTINGS) {
806:	if(report[0] == HID_CODE_FOR_VXR_CHECKSUM) {
815:	if(report[0] == HID_CODE_FOR_VXR_TAGS) {
820:	if(report[0] == HID_CODE_FOR_VXR_DELETE) {
839:	if(report[0] == HID_CODE_FOR_VXR_PROGRAM) {
855:	if(report[0] == HID_CODE_FOR_VXR_RESET) {
861:	if(report[0] == HID_CODE_FOR_VXR_FWNAME) {
866:	if(report[0] == HID_CODE_FOR_HW_TEST) {
873:	if(report[0] == HID_CODE_FOR_FATP_MODE) {
889:	if(report[0] == HID_CODE_FOR_OLED_COMMAND) {
908:	if(report[0] == HID_CODE_FOR_EDID_SWITCH) {
923:	if(report[0] == HID_CODE_FOR_PROX_DISABLE) {
934:	if(report[0] == HID_CODE_FOR_PROX_ENABLE) {
945:	if(report[0] == HID_CODE_FOR_DISPLAY_SLEEP) {
949:	if(report[0] == HID_CODE_FOR_DISPLAY_WAKE) {
954:	if(report[0] == HID_CODE_FOR_COLORBARPATTERN) {
960:	if(report[0] == HID_CODE_FOR_STACK_LEVELS) {
966:	if(report[0] == HID_CODE_FOR_HARDFAULT){
975:	if(report[0] == HID_CODE_FOR_UNUSED_IRQ){

===video task stack + priority===
src/ASF/thirdparty/freertos/freertos-10.0.0/Source/include/FreeRTOS.h:80:#ifndef configMINIMAL_STACK_SIZE
src/ASF/thirdparty/freertos/freertos-10.0.0/Source/include/FreeRTOS.h:81:	#error Missing definition:  configMINIMAL_STACK_SIZE must be defined in FreeRTOSConfig.h.  configMINIMAL_STACK_SIZE defines the size (in words) of the stack allocated to the idle task.  Refer to the demo project provided for your port for a suitable value.
src/ASF/thirdparty/freertos/freertos-10.0.0/Source/include/FreeRTOS.h:374:#ifndef configCHECK_FOR_STACK_OVERFLOW
src/ASF/thirdparty/freertos/freertos-10.0.0/Source/include/FreeRTOS.h:375:	#define configCHECK_FOR_STACK_OVERFLOW 0
src/ASF/thirdparty/freertos/freertos-10.0.0/Source/include/StackMacros.h:41: * Setting configCHECK_FOR_STACK_OVERFLOW to 1 will cause the macro to check
src/ASF/thirdparty/freertos/freertos-10.0.0/Source/include/StackMacros.h:43: * the stack limit.  Setting configCHECK_FOR_STACK_OVERFLOW to greater than 1
src/ASF/thirdparty/freertos/freertos-10.0.0/Source/include/StackMacros.h:52:#if( ( configCHECK_FOR_STACK_OVERFLOW == 1 ) && ( portSTACK_GROWTH < 0 ) )
src/ASF/thirdparty/freertos/freertos-10.0.0/Source/include/StackMacros.h:64:#endif /* configCHECK_FOR_STACK_OVERFLOW == 1 */
src/ASF/thirdparty/freertos/freertos-10.0.0/Source/include/StackMacros.h:67:#if( ( configCHECK_FOR_STACK_OVERFLOW == 1 ) && ( portSTACK_GROWTH > 0 ) )
src/ASF/thirdparty/freertos/freertos-10.0.0/Source/include/StackMacros.h:80:#endif /* configCHECK_FOR_STACK_OVERFLOW == 1 */


Prompt:
<task-notification>
<task-id>b975le2p1</task-id>
<tool-use-id>toolu_015dNzYuqZxY1LAE3A6ACCag</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-sauna\d3fb9be1-d61d-4f88-b77c-2a2a282b11fd\tasks\b975le2p1.output</output-file>
<status>completed</status>
<summary>Background command "Dispatch shape + video task stack size" completed (exit code 0)</summary>
</task-notification>


Confirmed the dispatch shape — `usbhid_set_feature` is a flat `if(report[0] == CODE)` chain (lines 945/949), **no `switch`, no payload signature**. Any feature report whose first byte is **0x48 triggers doze-sleep, 0x68 triggers doze-wake**. Pure first-byte keying.

## Root cause (high confidence): HID opcode collision

The chain of deduction is tight:

- `doze_active` (the latch that dims to floor + gates prox) can **only** be set by a `VIDEOPROC_DOZE_SLEEP` notify → which is sent **only** from `usbhid_set_feature` on `report[0]==0x48`. No other path exists.
- The wedge reproduces with **no sauna running** and **no doze command intentionally sent**.
- ∴ the BeyondEyetracking / BeyondSteamVR addon is itself emitting feature reports with `report[0]` == 0x48 and/or 0x68.
- `'H'`=0x48 sat in the **unused gap** between `GAIN`(0x47) and `OLED_BRIGHTNESS`(0x49); `'h'`=0x68 right after `FRAC_GAIN`(0x67). Pre-0.4.0 firmware ignored those bytes (no-op). The doze branch (0.4.0) claimed them. → classic **parallel-development opcode collision**: two Bigscreen tracks (doze firmware vs the addons) grabbed the same unused opcodes.

Symptom match is exact: addon sends 0x48 → spurious doze → panel dims to floor (stuck dim) + `doze_active` gates prox (prox dies) → latch persists across SteamVR exit (video-loss clear mis-times) → 0101 wedge → power-cycle. 0.4.x-only because the opcodes didn't exist before.

This is **sauna's firmware bug**, not blameless — sauna grabbed opcodes the addons already use.

## Fix (robust, doesn't require knowing addon's exact opcode map)

Add a **payload signature** to the doze commands: firmware triggers doze only on `report[0]==0x48 && report[1]==MAGIC`; sauna sends the magic. A bare addon 0x48 (different `report[1]`) becomes a no-op again. Neutralizes the collision even if the addon reuses the byte. Needs firmware 0.4.4 + matching sauna change + flash.

One thing I can't confirm from *this* repo: that the addon actually emits 0x48/0x68 (I don't have addon source here). That's the one external fact to nail before flashing. How do you want to verify?


AskUserQuestion

Question: How to confirm the addon emits 0x48/0x68 before I build the firmware fix?

Choices:
USBPcap/Wireshark (or a HID monitor) on the 35BD:0101 interface during a BeyondEyetracking+BeyondSteamVR session; grep feature reports for report[0] in {0x48,0x68}. Direct proof, no source needed.
USBPcap on 35BD:0101
filter: SET_FEATURE
look for: data[0]==0x48 (H)
              data[0]==0x68 (h)
→ any hit = collision confirmed
You have Bigscreen addon source/protocol docs — find what HID command codes BeyondEyetracking/BeyondSteamVR send to 0101. Fastest if you have access.
grep -rE "0x48|0x68|'H'|'h'" \
  beyond_eyetracking/ beyond_steamvr/
→ find their opcode usage
Add a debug counter/log in usbhid_set_feature that records the report[0] of every feature report, flash it, run an addon session, dump the histogram. Confirms on-device but needs a flash cycle first.
if(report[0]==0x48||report[0]==0x68)
  dbg_doze_hits[report[0]]++;
// dump via stack-levels / uart
→ on-device opcode histogram
Confidence is already high; the signature fix is harmless even if the collision theory is partly wrong (sauna still sends the magic, addon bytes still ignored). Build 0.4.4 + sauna now, validate by retest.
build fw 0.4.4 (sig-gated doze)
+ sauna sends MAGIC
→ flash → retest addon session
→ wedge gone = confirmed


Your questions have been answered: "How to confirm the addon emits 0x48/0x68 before I build the firmware fix?"=(no option selected) notes: don't make educated guesses, check the source code. BeyondEyetracking and BeyondSteamVR source is over in the sibling project, HMDUtility. there's also no parallel development at play here. those projects haven't been modified or updated in weeks, and this project just started a few days ago.. You can now continue with these answers in mind.

Question: How to confirm the addon emits 0x48/0x68 before I build the firmware fix?

Choices:
USBPcap/Wireshark (or a HID monitor) on the 35BD:0101 interface during a BeyondEyetracking+BeyondSteamVR session; grep feature reports for report[0] in {0x48,0x68}. Direct proof, no source needed.
USBPcap on 35BD:0101
filter: SET_FEATURE
look for: data[0]==0x48 (H)
              data[0]==0x68 (h)
→ any hit = collision confirmed
You have Bigscreen addon source/protocol docs — find what HID command codes BeyondEyetracking/BeyondSteamVR send to 0101. Fastest if you have access.
grep -rE "0x48|0x68|'H'|'h'" \
  beyond_eyetracking/ beyond_steamvr/
→ find their opcode usage
Add a debug counter/log in usbhid_set_feature that records the report[0] of every feature report, flash it, run an addon session, dump the histogram. Confirms on-device but needs a flash cycle first.
if(report[0]==0x48||report[0]==0x68)
  dbg_doze_hits[report[0]]++;
// dump via stack-levels / uart
→ on-device opcode histogram
Confidence is already high; the signature fix is harmless even if the collision theory is partly wrong (sauna still sends the magic, addon bytes still ignored). Build 0.4.4 + sauna now, validate by retest.
build fw 0.4.4 (sig-gated doze)
+ sauna sends MAGIC
→ flash → retest addon session
→ wedge gone = confirmed


Right — let me check HMDUtility source directly instead of guessing. The collision mechanism still holds (doze, days old, grabbed opcodes the stable addon already sends) — but I need to prove the addon sends 0x48/0x68.


Bash

cd C:/Users/decid/Documents/projects && ls -d */ | head -40 && echo "===find HMDUtility===" && ls -d *HMD* *hmd* HMDUtility 2>/dev/null

Locate HMDUtility sibling project


Command running in background with ID: bdbthab4j. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-sauna\d3fb9be1-d61d-4f88-b77c-2a2a282b11fd\tasks\bdbthab4j.output. You will be notified when it completes. To check interim output, use Read on that file path.


<task-notification>
<task-id>bdbthab4j</task-id>
<tool-use-id>toolu_01KHUbythV86j1LA2fQdUkes</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-sauna\d3fb9be1-d61d-4f88-b77c-2a2a282b11fd\tasks\bdbthab4j.output</output-file>
<status>failed</status>
<summary>Background command "Locate HMDUtility sibling project" failed with exit code 2</summary>
</task-notification>


1	,claude-bigscreen/
2	AppData/
3	HMDUtility/
4	Ubers-SD-Files/
5	_3functions/
6	attractor/
7	bey-closer-t1/
8	beyond-eye-tracking-client/
9	beyond_bootloader/
10	beyond_synaptics/
11	big-eye-utils/
12	bigscreen/
13	bigscreen10/
14	bigstack/
15	biodroid-auth/
16	bs2_eyetracking/
17	calibot/
18	check-canting/
19	claude-screen-timelapse/
20	claude_skill_owl/
21	cloud/
22	cplugs-everything/
23	exput/
24	extended-mode-display/
25	eyetrack_mipi_fpga/
26	get-shit-done/
27	holohue-gui/
28	lettem-in/
29	mic-map/
30	mmi_test/
31	openvr-resilient/
32	oship/
33	own-ledger/
34	pi-agent-memory/
35	playdial/
36	qt-py-haptics/
37	quick-beyond-updater/
38	rebno/
39	rustHello/
40	sauna/
41	===find HMDUtility===
42	HMDUtility
43	HMDUtility
44

,claude-bigscreen/
AppData/
HMDUtility/
Ubers-SD-Files/
_3functions/
attractor/
bey-closer-t1/
beyond-eye-tracking-client/
beyond_bootloader/
beyond_synaptics/
big-eye-utils/
bigscreen/
bigscreen10/
bigstack/
biodroid-auth/
bs2_eyetracking/
calibot/
check-canting/
claude-screen-timelapse/
claude_skill_owl/
cloud/
cplugs-everything/
exput/
extended-mode-display/
eyetrack_mipi_fpga/
get-shit-done/
holohue-gui/
lettem-in/
mic-map/
mmi_test/
openvr-resilient/
oship/
own-ledger/
pi-agent-memory/
playdial/
qt-py-haptics/
quick-beyond-updater/
rebno/
rustHello/
sauna/
===find HMDUtility===
HMDUtility
HMDUtility


Prompt:
<task-notification>
<task-id>bdbthab4j</task-id>
<tool-use-id>toolu_01KHUbythV86j1LA2fQdUkes</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-sauna\d3fb9be1-d61d-4f88-b77c-2a2a282b11fd\tasks\bdbthab4j.output</output-file>
<status>failed</status>
<summary>Background command "Locate HMDUtility sibling project" failed with exit code 2</summary>
</task-notification>


Found `HMDUtility`. Now hunt for what command bytes it sends to `0101` — looking for 0x48/0x68/'H'/'h' and the feature-report framing.


cd C:/Users/decid/Documents/projects/HMDUtility && echo "===0x48/0x68/H/h opcode refs===" && grep -rnE "0x48|0x68|'H'|'h'|\\bH\\b" --include=*.cs --include=*.cpp --include=*.c --include=*.h --include=*.py . | grep -ivE "width|height|0x480|hash|http|hex|hub" | head -40

Grep HMDUtility for doze opcode bytes


Command running in background with ID: bj8c6prrz. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-sauna\d3fb9be1-d61d-4f88-b77c-2a2a282b11fd\tasks\bj8c6prrz.output. You will be notified when it completes. To check interim output, use Read on that file path.


<task-notification>
<task-id>bj8c6prrz</task-id>
<tool-use-id>toolu_01HpYKBq2aP9H3HmAAyjq7No</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-sauna\d3fb9be1-d61d-4f88-b77c-2a2a282b11fd\tasks\bj8c6prrz.output</output-file>
<status>completed</status>
<summary>Background command "Grep HMDUtility for doze opcode bytes" completed (exit code 0)</summary>
</task-notification>


1	===0x48/0x68/H/h opcode refs===
2	./dependencies/cpr/zlib.h:1313:   filtered data as in "wb6f", 'h' for Huffman-only compression as in "wb1h",
3	./dependencies/imgui/backends/imgui_impl_glut.cpp:11://  [ ] Platform: GLUT is unable to distinguish e.g. Backspace from CTRL+H or TAB from CTRL+I
4	./dependencies/imgui/backends/imgui_impl_glut.cpp:132:        case 'H': case 'h':             return ImGuiKey_H;
5	./dependencies/imgui/backends/imgui_impl_glut.h:11://  [ ] Platform: GLUT is unable to distinguish e.g. Backspace from CTRL+H or TAB from CTRL+I
6	./dependencies/imgui/backends/imgui_impl_win32.cpp:521:        case 'H': return ImGuiKey_H;
7	./dependencies/imgui/imgui.cpp:2350:    0xD6D6A3E8,0xA1D1937E,0x38D8C2C4,0x4FDFF252,0xD1BB67F1,0xA6BC5767,0x3FB506DD,0x48B2364B,0xD80D2BDA,0xAF0A1B4C,0x36034AF6,0x41047A60,0xDF60EFC3,0xA867DF55,0x316E8EEF,0x4669BE79,
8	./dependencies/imgui/imgui.cpp:2353:    0x86D3D2D4,0xF1D4E242,0x68DDB3F8,0x1FDA836E,0x81BE16CD,0xF6B9265B,0x6FB077E1,0x18B74777,0x88085AE6,0xFF0F6A70,0x66063BCA,0x11010B5C,0x8F659EFF,0xF862AE69,0x616BFFD3,0x166CCF45,
9	./dependencies/imgui/imgui.cpp:2359:    0x105EC76F,0xE235446C,0xF165B798,0x030E349B,0xD7C45070,0x25AFD373,0x36FF2087,0xC494A384,0x9A879FA0,0x68EC1CA3,0x7BBCEF57,0x89D76C54,0x5D1D08BF,0xAF768BBC,0xBC267848,0x4E4DFB4B,
10	./dependencies/imgui/imgui.cpp:2361:    0x30E349B1,0xC288CAB2,0xD1D83946,0x23B3BA45,0xF779DEAE,0x05125DAD,0x1642AE59,0xE4292D5A,0xBA3A117E,0x4851927D,0x5B016189,0xA96AE28A,0x7DA08661,0x8FCB0562,0x9C9BF696,0x6EF07595,
11	./dependencies/imgui/imgui.cpp:9358:    "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H",
12	./dependencies/imgui/imgui_demo.cpp:5048:            ImGui::Checkbox("H-scrollbar", &show_h_scrollbar);
13	./dependencies/imgui/imgui_widgets.cpp:3587:    const unsigned int ignored_lowercase_mask = (1 << ('h'-'a')) | (1 << ('j'-'a')) | (1 << ('l'-'a')) | (1 << ('t'-'a')) | (1 << ('w'-'a')) | (1 << ('z'-'a'));
14	./dependencies/imgui/imgui_widgets.cpp:5710:static void ColorEditRestoreH(const float* col, float* H)
15	./dependencies/imgui/imgui_widgets.cpp:5716:    *H = g.ColorEditSavedHue;
16	./dependencies/imgui/imgui_widgets.cpp:5721:static void ColorEditRestoreHS(const float* col, float* H, float* S, float* V)
17	./dependencies/imgui/imgui_widgets.cpp:5728:    // When S == 0, H is undefined.
18	./dependencies/imgui/imgui_widgets.cpp:5729:    // When H == 1 it wraps around to 0.
19	./dependencies/imgui/imgui_widgets.cpp:5730:    if (*S == 0.0f || (*H == 0.0f && g.ColorEditSavedHue == 1))
20	./dependencies/imgui/imgui_widgets.cpp:5731:        *H = g.ColorEditSavedHue;
21	./dependencies/imgui/imgui_widgets.cpp:5821:            { "H:%3d", "S:%3d", "V:%3d", "A:%3d" }  // Long display for HSVA
22	./dependencies/imgui/imgui_widgets.cpp:5827:            { "H:%0.3f", "S:%0.3f", "V:%0.3f", "A:%0.3f" }  // Long display for HSVA
23	./dependencies/imgui/imgui_widgets.cpp:6080:    float H = col[0], S = col[1], V = col[2];
24	./dependencies/imgui/imgui_widgets.cpp:6085:        ColorConvertRGBtoHSV(R, G, B, H, S, V);
25	./dependencies/imgui/imgui_widgets.cpp:6086:        ColorEditRestoreHS(col, &H, &S, &V);
26	./dependencies/imgui/imgui_widgets.cpp:6090:        ColorConvertHSVtoRGB(H, S, V, R, G, B);
27	./dependencies/imgui/imgui_widgets.cpp:6108:                H = ImAtan2(current_off.y, current_off.x) / IM_PI * 0.5f;
28	./dependencies/imgui/imgui_widgets.cpp:6109:                if (H < 0.0f)
29	./dependencies/imgui/imgui_widgets.cpp:6110:                    H += 1.0f;
30	./dependencies/imgui/imgui_widgets.cpp:6113:            float cos_hue_angle = ImCos(-H * 2.0f * IM_PI);
31	./dependencies/imgui/imgui_widgets.cpp:6114:            float sin_hue_angle = ImSin(-H * 2.0f * IM_PI);
32	./dependencies/imgui/imgui_widgets.cpp:6139:            ColorEditRestoreH(col, &H); // Greatly reduces hue jitter and reset to 0 when hue == 255 and color is rapidly modified using SV square.
33	./dependencies/imgui/imgui_widgets.cpp:6150:            H = ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1));
34	./dependencies/imgui/imgui_widgets.cpp:6213:            ColorConvertHSVtoRGB(H, S, V, col[0], col[1], col[2]);
35	./dependencies/imgui/imgui_widgets.cpp:6214:            g.ColorEditSavedHue = H;
36	./dependencies/imgui/imgui_widgets.cpp:6221:            col[0] = H;
37	./dependencies/imgui/imgui_widgets.cpp:6227:    // R,G,B and H,S,V slider color editor
38	./dependencies/imgui/imgui_widgets.cpp:6254:        if (new_H <= 0 && H > 0)
39	./dependencies/imgui/imgui_widgets.cpp:6257:                ColorConvertHSVtoRGB(H, S, new_V <= 0 ? V * 0.5f : new_V, col[0], col[1], col[2]);
40	./dependencies/imgui/imgui_widgets.cpp:6259:                ColorConvertHSVtoRGB(H, new_S <= 0 ? S * 0.5f : new_S, new_V, col[0], col[1], col[2]);
41	./dependencies/imgui/imgui_widgets.cpp:6270:            ColorConvertRGBtoHSV(R, G, B, H, S, V);
42

===0x48/0x68/H/h opcode refs===
./dependencies/cpr/zlib.h:1313:   filtered data as in "wb6f", 'h' for Huffman-only compression as in "wb1h",
./dependencies/imgui/backends/imgui_impl_glut.cpp:11://  [ ] Platform: GLUT is unable to distinguish e.g. Backspace from CTRL+H or TAB from CTRL+I
./dependencies/imgui/backends/imgui_impl_glut.cpp:132:        case 'H': case 'h':             return ImGuiKey_H;
./dependencies/imgui/backends/imgui_impl_glut.h:11://  [ ] Platform: GLUT is unable to distinguish e.g. Backspace from CTRL+H or TAB from CTRL+I
./dependencies/imgui/backends/imgui_impl_win32.cpp:521:        case 'H': return ImGuiKey_H;
./dependencies/imgui/imgui.cpp:2350:    0xD6D6A3E8,0xA1D1937E,0x38D8C2C4,0x4FDFF252,0xD1BB67F1,0xA6BC5767,0x3FB506DD,0x48B2364B,0xD80D2BDA,0xAF0A1B4C,0x36034AF6,0x41047A60,0xDF60EFC3,0xA867DF55,0x316E8EEF,0x4669BE79,
./dependencies/imgui/imgui.cpp:2353:    0x86D3D2D4,0xF1D4E242,0x68DDB3F8,0x1FDA836E,0x81BE16CD,0xF6B9265B,0x6FB077E1,0x18B74777,0x88085AE6,0xFF0F6A70,0x66063BCA,0x11010B5C,0x8F659EFF,0xF862AE69,0x616BFFD3,0x166CCF45,
./dependencies/imgui/imgui.cpp:2359:    0x105EC76F,0xE235446C,0xF165B798,0x030E349B,0xD7C45070,0x25AFD373,0x36FF2087,0xC494A384,0x9A879FA0,0x68EC1CA3,0x7BBCEF57,0x89D76C54,0x5D1D08BF,0xAF768BBC,0xBC267848,0x4E4DFB4B,
./dependencies/imgui/imgui.cpp:2361:    0x30E349B1,0xC288CAB2,0xD1D83946,0x23B3BA45,0xF779DEAE,0x05125DAD,0x1642AE59,0xE4292D5A,0xBA3A117E,0x4851927D,0x5B016189,0xA96AE28A,0x7DA08661,0x8FCB0562,0x9C9BF696,0x6EF07595,
./dependencies/imgui/imgui.cpp:9358:    "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H",
./dependencies/imgui/imgui_demo.cpp:5048:            ImGui::Checkbox("H-scrollbar", &show_h_scrollbar);
./dependencies/imgui/imgui_widgets.cpp:3587:    const unsigned int ignored_lowercase_mask = (1 << ('h'-'a')) | (1 << ('j'-'a')) | (1 << ('l'-'a')) | (1 << ('t'-'a')) | (1 << ('w'-'a')) | (1 << ('z'-'a'));
./dependencies/imgui/imgui_widgets.cpp:5710:static void ColorEditRestoreH(const float* col, float* H)
./dependencies/imgui/imgui_widgets.cpp:5716:    *H = g.ColorEditSavedHue;
./dependencies/imgui/imgui_widgets.cpp:5721:static void ColorEditRestoreHS(const float* col, float* H, float* S, float* V)
./dependencies/imgui/imgui_widgets.cpp:5728:    // When S == 0, H is undefined.
./dependencies/imgui/imgui_widgets.cpp:5729:    // When H == 1 it wraps around to 0.
./dependencies/imgui/imgui_widgets.cpp:5730:    if (*S == 0.0f || (*H == 0.0f && g.ColorEditSavedHue == 1))
./dependencies/imgui/imgui_widgets.cpp:5731:        *H = g.ColorEditSavedHue;
./dependencies/imgui/imgui_widgets.cpp:5821:            { "H:%3d", "S:%3d", "V:%3d", "A:%3d" }  // Long display for HSVA
./dependencies/imgui/imgui_widgets.cpp:5827:            { "H:%0.3f", "S:%0.3f", "V:%0.3f", "A:%0.3f" }  // Long display for HSVA
./dependencies/imgui/imgui_widgets.cpp:6080:    float H = col[0], S = col[1], V = col[2];
./dependencies/imgui/imgui_widgets.cpp:6085:        ColorConvertRGBtoHSV(R, G, B, H, S, V);
./dependencies/imgui/imgui_widgets.cpp:6086:        ColorEditRestoreHS(col, &H, &S, &V);
./dependencies/imgui/imgui_widgets.cpp:6090:        ColorConvertHSVtoRGB(H, S, V, R, G, B);
./dependencies/imgui/imgui_widgets.cpp:6108:                H = ImAtan2(current_off.y, current_off.x) / IM_PI * 0.5f;
./dependencies/imgui/imgui_widgets.cpp:6109:                if (H < 0.0f)
./dependencies/imgui/imgui_widgets.cpp:6110:                    H += 1.0f;
./dependencies/imgui/imgui_widgets.cpp:6113:            float cos_hue_angle = ImCos(-H * 2.0f * IM_PI);
./dependencies/imgui/imgui_widgets.cpp:6114:            float sin_hue_angle = ImSin(-H * 2.0f * IM_PI);
./dependencies/imgui/imgui_widgets.cpp:6139:            ColorEditRestoreH(col, &H); // Greatly reduces hue jitter and reset to 0 when hue == 255 and color is rapidly modified using SV square.
./dependencies/imgui/imgui_widgets.cpp:6150:            H = ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1));
./dependencies/imgui/imgui_widgets.cpp:6213:            ColorConvertHSVtoRGB(H, S, V, col[0], col[1], col[2]);
./dependencies/imgui/imgui_widgets.cpp:6214:            g.ColorEditSavedHue = H;
./dependencies/imgui/imgui_widgets.cpp:6221:            col[0] = H;
./dependencies/imgui/imgui_widgets.cpp:6227:    // R,G,B and H,S,V slider color editor
./dependencies/imgui/imgui_widgets.cpp:6254:        if (new_H <= 0 && H > 0)
./dependencies/imgui/imgui_widgets.cpp:6257:                ColorConvertHSVtoRGB(H, S, new_V <= 0 ? V * 0.5f : new_V, col[0], col[1], col[2]);
./dependencies/imgui/imgui_widgets.cpp:6259:                ColorConvertHSVtoRGB(H, new_S <= 0 ? S * 0.5f : new_S, new_V, col[0], col[1], col[2]);
./dependencies/imgui/imgui_widgets.cpp:6270:            ColorConvertRGBtoHSV(R, G, B, H, S, V);


Prompt:
<task-notification>
<task-id>bj8c6prrz</task-id>
<tool-use-id>toolu_01HpYKBq2aP9H3HmAAyjq7No</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-sauna\d3fb9be1-d61d-4f88-b77c-2a2a282b11fd\tasks\bj8c6prrz.output</output-file>
<status>completed</status>
<summary>Background command "Grep HMDUtility for doze opcode bytes" completed (exit code 0)</summary>
</task-notification>


<task-notification>
<task-id>bbol7ruep</task-id>
<tool-use-id>toolu_01VefHNBo7Db82nrGcu68kVd</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-sauna\d3fb9be1-d61d-4f88-b77c-2a2a282b11fd\tasks\bbol7ruep.output</output-file>
<status>completed</status>
<summary>Background command "Find addon HID send sites" completed (exit code 0)</summary>
</task-notification>


Noise from deps. Let me find the actual addon source files and how they frame HID commands to `0101`.


cd C:/Users/decid/Documents/projects/HMDUtility && echo "===layout (non-dep)===" && ls -d */ | grep -ivE "dependencies|build|out|bin|obj|.git" && echo "" && echo "===files mentioning 0101 / SetFeature / hid===" && grep -rliE "0101|SetFeature|HidD_|hid_send|SendFeature|feature report|write_report|0x35BD|35BD" --include=*.cs --include=*.cpp --include=*.c --include=*.h --include=*.py . | grep -ivE "dependencies/" | head -30

Find addon HID send sites


Command running in background with ID: bbol7ruep. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-sauna\d3fb9be1-d61d-4f88-b77c-2a2a282b11fd\tasks\bbol7ruep.output. You will be notified when it completes. To check interim output, use Read on that file path.


Prompt:
<task-notification>
<task-id>bbol7ruep</task-id>
<tool-use-id>toolu_01VefHNBo7Db82nrGcu68kVd</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-sauna\d3fb9be1-d61d-4f88-b77c-2a2a282b11fd\tasks\bbol7ruep.output</output-file>
<status>completed</status>
<summary>Background command "Find addon HID send sites" completed (exit code 0)</summary>
</task-notification>


1	===layout (non-dep)===
2	SteamVRDriver/
3	src/
4	test/
5
6	===files mentioning 0101 / SetFeature / hid===
7	./src/firmware_update.cpp
8	./src/firmware_update.h
9	./src/hid_handler.cpp
10	./src/ui.cpp
11

===layout (non-dep)===
SteamVRDriver/
src/
test/

===files mentioning 0101 / SetFeature / hid===
./src/firmware_update.cpp
./src/firmware_update.h
./src/hid_handler.cpp
./src/ui.cpp


cd C:/Users/decid/Documents/projects/HMDUtility && echo "===command bytes / enums in hid_handler.cpp===" && grep -nE "0x[0-9A-Fa-f]{2}|'[A-Za-z@%&~^]'|= *[0-9]+|command|cmd|report\[0\]|buf\[0\]|data\[0\]|SendFeature|SetFeature|HidD" src/hid_handler.cpp | head -70

Dump hid_handler command bytes


Command running in background with ID: bk8a3szli. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-sauna\d3fb9be1-d61d-4f88-b77c-2a2a282b11fd\tasks\bk8a3szli.output. You will be notified when it completes. To check interim output, use Read on that file path.


<task-notification>
<task-id>bk8a3szli</task-id>
<tool-use-id>toolu_01SZHd7JG5fzTV7WJqsBsmbs</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-sauna\d3fb9be1-d61d-4f88-b77c-2a2a282b11fd\tasks\bk8a3szli.output</output-file>
<status>completed</status>
<summary>Background command "Dump hid_handler command bytes" completed (exit code 0)</summary>
</task-notification>


1	===command bytes / enums in hid_handler.cpp===
2	12:void HidHandler::command_software_version()
3	14:    std::string command;
4	15:    command += (unsigned char)0x00; // Report ID.
5	16:    command += (unsigned char)0x2A; // Code.
6	17:    std::lock_guard<std::mutex> command_lock(command_mutex);
7	18:    pending_commands.push_back(command);
8	22:void HidHandler::command_serial_number()
9	24:    std::string command;
10	25:    command += (unsigned char)0x00; // Report ID.
11	26:    command += (unsigned char)0x25; // Code.
12	27:    std::lock_guard<std::mutex> command_lock(command_mutex);
13	28:    pending_commands.push_back(command);
14	32:void HidHandler::command_hmd_serial_number()
15	34:    std::string command;
16	35:    command += (unsigned char)0x00; // Report ID.
17	36:    command += (unsigned char)0x26; // Code.
18	37:    std::lock_guard<std::mutex> command_lock(command_mutex);
19	38:    pending_commands.push_back(command);
20	42:void HidHandler::command_tracking_flex_serial_number()
21	44:    std::string command;
22	45:    command += (unsigned char)0x00; // Report ID.
23	46:    command += (unsigned char)0x7E; // Code.
24	47:    std::lock_guard<std::mutex> command_lock(command_mutex);
25	48:    pending_commands.push_back(command);
26	52:void HidHandler::command_oled_serial_number()
27	54:    std::string command;
28	55:    command += (unsigned char)0x00; // Report ID.
29	56:    command += (unsigned char)0x5E; // Code.
30	57:    std::lock_guard<std::mutex> command_lock(command_mutex);
31	58:    pending_commands.push_back(command);
32	62:void HidHandler::command_usage_timer(unsigned char usage_type)
33	64:    std::string command;
34	65:    command += (unsigned char)0x00; // Report ID.
35	66:    command += (unsigned char)0x5A; // Code.
36	67:    command += usage_type; // Usage type requested.
37	71:    std::lock_guard<std::mutex> command_lock(command_mutex);
38	72:    pending_commands.push_back(command);
39	76:void HidHandler::command_mic_stereo(unsigned char stereo)
40	78:    std::string command;
41	79:    command += (unsigned char)0x00; // Report ID.
42	80:    command += (unsigned char)0x53; // Code.
43	81:    command += stereo; // Mono/Stereo.
44	84:    std::lock_guard<std::mutex> command_lock(command_mutex);
45	85:    pending_commands.push_back(command);
46	89:void HidHandler::command_mic_integer_gain(unsigned short integer_gain)
47	91:    std::string command;
48	92:    command += (unsigned char)0x00; // Report ID.
49	93:    command += (unsigned char)0x47; // Code.
50	94:    command += (unsigned char)((integer_gain >> 8) & 0xFF); // MSBs of gain.
51	95:    command += (unsigned char)(integer_gain & 0xFF); // LSBs of gain.
52	96:    std::lock_guard<std::mutex> command_lock(command_mutex);
53	97:    pending_commands.push_back(command);
54	101:void HidHandler::command_mic_fractional_gain(unsigned short fractional_gain)
55	103:    std::string command;
56	104:    command += (unsigned char)0x00; // Report ID.
57	105:    command += (unsigned char)0x67; // Code.
58	106:    command += (unsigned char)((fractional_gain >> 8) & 0xFF); // MSBs of gain.
59	107:    command += (unsigned char)(fractional_gain & 0xFF); // LSBs of gain.
60	109:    std::lock_guard<std::mutex> command_lock(command_mutex);
61	110:    pending_commands.push_back(command);
62	114:void HidHandler::command_fan_speed_immediate(unsigned char fan_speed)
63	116:    std::string command;
64	117:    command += (unsigned char)0x00; // Report ID.
65	118:    command += (unsigned char)0x46; // Code.
66	119:    command += fan_speed; // Speed.
67	121:    std::lock_guard<std::mutex> command_lock(command_mutex);
68	122:    pending_commands.push_back(command);
69	126:void HidHandler::command_fan_speed_deferred(unsigned char fan_speed)
70	128:    std::string command;
71	129:    command += (unsigned char)0x00; // Report ID.
72

===command bytes / enums in hid_handler.cpp===
12:void HidHandler::command_software_version()
14:    std::string command;
15:    command += (unsigned char)0x00; // Report ID.
16:    command += (unsigned char)0x2A; // Code.
17:    std::lock_guard<std::mutex> command_lock(command_mutex);
18:    pending_commands.push_back(command);
22:void HidHandler::command_serial_number()
24:    std::string command;
25:    command += (unsigned char)0x00; // Report ID.
26:    command += (unsigned char)0x25; // Code.
27:    std::lock_guard<std::mutex> command_lock(command_mutex);
28:    pending_commands.push_back(command);
32:void HidHandler::command_hmd_serial_number()
34:    std::string command;
35:    command += (unsigned char)0x00; // Report ID.
36:    command += (unsigned char)0x26; // Code.
37:    std::lock_guard<std::mutex> command_lock(command_mutex);
38:    pending_commands.push_back(command);
42:void HidHandler::command_tracking_flex_serial_number()
44:    std::string command;
45:    command += (unsigned char)0x00; // Report ID.
46:    command += (unsigned char)0x7E; // Code.
47:    std::lock_guard<std::mutex> command_lock(command_mutex);
48:    pending_commands.push_back(command);
52:void HidHandler::command_oled_serial_number()
54:    std::string command;
55:    command += (unsigned char)0x00; // Report ID.
56:    command += (unsigned char)0x5E; // Code.
57:    std::lock_guard<std::mutex> command_lock(command_mutex);
58:    pending_commands.push_back(command);
62:void HidHandler::command_usage_timer(unsigned char usage_type)
64:    std::string command;
65:    command += (unsigned char)0x00; // Report ID.
66:    command += (unsigned char)0x5A; // Code.
67:    command += usage_type; // Usage type requested.
71:    std::lock_guard<std::mutex> command_lock(command_mutex);
72:    pending_commands.push_back(command);
76:void HidHandler::command_mic_stereo(unsigned char stereo)
78:    std::string command;
79:    command += (unsigned char)0x00; // Report ID.
80:    command += (unsigned char)0x53; // Code.
81:    command += stereo; // Mono/Stereo.
84:    std::lock_guard<std::mutex> command_lock(command_mutex);
85:    pending_commands.push_back(command);
89:void HidHandler::command_mic_integer_gain(unsigned short integer_gain)
91:    std::string command;
92:    command += (unsigned char)0x00; // Report ID.
93:    command += (unsigned char)0x47; // Code.
94:    command += (unsigned char)((integer_gain >> 8) & 0xFF); // MSBs of gain.
95:    command += (unsigned char)(integer_gain & 0xFF); // LSBs of gain.
96:    std::lock_guard<std::mutex> command_lock(command_mutex);
97:    pending_commands.push_back(command);
101:void HidHandler::command_mic_fractional_gain(unsigned short fractional_gain)
103:    std::string command;
104:    command += (unsigned char)0x00; // Report ID.
105:    command += (unsigned char)0x67; // Code.
106:    command += (unsigned char)((fractional_gain >> 8) & 0xFF); // MSBs of gain.
107:    command += (unsigned char)(fractional_gain & 0xFF); // LSBs of gain.
109:    std::lock_guard<std::mutex> command_lock(command_mutex);
110:    pending_commands.push_back(command);
114:void HidHandler::command_fan_speed_immediate(unsigned char fan_speed)
116:    std::string command;
117:    command += (unsigned char)0x00; // Report ID.
118:    command += (unsigned char)0x46; // Code.
119:    command += fan_speed; // Speed.
121:    std::lock_guard<std::mutex> command_lock(command_mutex);
122:    pending_commands.push_back(command);
126:void HidHandler::command_fan_speed_deferred(unsigned char fan_speed)
128:    std::string command;
129:    command += (unsigned char)0x00; // Report ID.


Prompt:
<task-notification>
<task-id>bk8a3szli</task-id>
<tool-use-id>toolu_01SZHd7JG5fzTV7WJqsBsmbs</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-sauna\d3fb9be1-d61d-4f88-b77c-2a2a282b11fd\tasks\bk8a3szli.output</output-file>
<status>completed</status>
<summary>Background command "Dump hid_handler command bytes" completed (exit code 0)</summary>
</task-notification>


Key structural fact: HMDUtility frames every command as `[0]=0x00 ReportID, [1]=Code, [2..]=payload`. The USB layer strips the report-ID, so firmware's `report[0]` == HMDUtility's **Code** byte (verified: version cmd Code 0x2A ↔ firmware `SW_VER`=0x2a). So I need any HMDUtility command with **Code 0x48 or 0x68**. Let me extract every Code byte across hid_handler + the SteamVR driver.


cd C:/Users/decid/Documents/projects/HMDUtility && echo "===ALL Code bytes (hid_handler)===" && grep -nE "0x[0-9A-Fa-f]{2}; // Code" src/hid_handler.cpp && echo "" && echo "===0x48 / 0x68 anywhere as a sent byte (src + SteamVRDriver)===" && grep -rnE "0x48|0x68" src/hid_handler.cpp src/firmware_update.cpp SteamVRDriver/ 2>/dev/null | head -30

Extract all command code bytes


Command running in background with ID: bqosxqehc. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-sauna\d3fb9be1-d61d-4f88-b77c-2a2a282b11fd\tasks\bqosxqehc.output. You will be notified when it completes. To check interim output, use Read on that file path.


<task-notification>
<task-id>bqosxqehc</task-id>
<tool-use-id>toolu_01K9vFMqmGWtprS9F4w13o9C</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-sauna\d3fb9be1-d61d-4f88-b77c-2a2a282b11fd\tasks\bqosxqehc.output</output-file>
<status>completed</status>
<summary>Background command "Extract all command code bytes" completed (exit code 0)</summary>
</task-notification>


1	===ALL Code bytes (hid_handler)===
2	16:    command += (unsigned char)0x2A; // Code.
3	26:    command += (unsigned char)0x25; // Code.
4	36:    command += (unsigned char)0x26; // Code.
5	46:    command += (unsigned char)0x7E; // Code.
6	56:    command += (unsigned char)0x5E; // Code.
7	66:    command += (unsigned char)0x5A; // Code.
8	80:    command += (unsigned char)0x53; // Code.
9	93:    command += (unsigned char)0x47; // Code.
10	105:    command += (unsigned char)0x67; // Code.
11	118:    command += (unsigned char)0x46; // Code.
12	130:    command += (unsigned char)0x66; // Code.
13	142:    command += (unsigned char)0x52; // Code.
14	155:    command += (unsigned char)0x50; // Code.
15	167:    command += (unsigned char)0x49; // Code.
16	193:    command += (unsigned char)0x42; // Code.
17	203:    command += (unsigned char)0x65; // Code.
18	216:    command += (unsigned char)0x4C; // Code.
19	230:    command += (unsigned char)0x55; // Code.
20	243:    command += (unsigned char)0x57; // Code.
21	260:    command += (unsigned char)0x56; // Code.
22	280:    command += (unsigned char)0x4D; // Code.
23	302:    command += (unsigned char)0x4B; // Code.
24	320:    command += (unsigned char)0x54; // Code.
25	330:    command += (unsigned char)0x44; // Code.
26	344:    command += (unsigned char)0x41; // Code.
27	363:    command += (unsigned char)0x59; // Code.
28	373:    command += (unsigned char)0x4E; // Code.
29	383:    command += (unsigned char)0x4A; // Code.
30	401:    command += (unsigned char)0x40; // Code.
31	410:    command += (unsigned char)0x64; // Code for EDID switch, 'd'
32	424:    command += (unsigned char)0x6F; // Code.
33	439:    command += (unsigned char)0x42; // Code to restart into application
34	450:        command += (unsigned char)0x2A; // Code to retrieve software version
35	460:    command[0] = (unsigned char)0x44; // Code to write Flash data
36	489:    command[0] = (unsigned char)0x50; // Code to program Flash data
37	508:    command += (unsigned char)0x22; // Code to erase application region in Flash
38	516:    command[0] = (unsigned char)0x65; // Code to erase Flash by block or sector
39
40	===0x48 / 0x68 anywhere as a sent byte (src + SteamVRDriver)===
41	src/firmware_update.cpp:48:    0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
42	src/firmware_update.cpp:56:    0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
43

===ALL Code bytes (hid_handler)===
16:    command += (unsigned char)0x2A; // Code.
26:    command += (unsigned char)0x25; // Code.
36:    command += (unsigned char)0x26; // Code.
46:    command += (unsigned char)0x7E; // Code.
56:    command += (unsigned char)0x5E; // Code.
66:    command += (unsigned char)0x5A; // Code.
80:    command += (unsigned char)0x53; // Code.
93:    command += (unsigned char)0x47; // Code.
105:    command += (unsigned char)0x67; // Code.
118:    command += (unsigned char)0x46; // Code.
130:    command += (unsigned char)0x66; // Code.
142:    command += (unsigned char)0x52; // Code.
155:    command += (unsigned char)0x50; // Code.
167:    command += (unsigned char)0x49; // Code.
193:    command += (unsigned char)0x42; // Code.
203:    command += (unsigned char)0x65; // Code.
216:    command += (unsigned char)0x4C; // Code.
230:    command += (unsigned char)0x55; // Code.
243:    command += (unsigned char)0x57; // Code.
260:    command += (unsigned char)0x56; // Code.
280:    command += (unsigned char)0x4D; // Code.
302:    command += (unsigned char)0x4B; // Code.
320:    command += (unsigned char)0x54; // Code.
330:    command += (unsigned char)0x44; // Code.
344:    command += (unsigned char)0x41; // Code.
363:    command += (unsigned char)0x59; // Code.
373:    command += (unsigned char)0x4E; // Code.
383:    command += (unsigned char)0x4A; // Code.
401:    command += (unsigned char)0x40; // Code.
410:    command += (unsigned char)0x64; // Code for EDID switch, 'd'
424:    command += (unsigned char)0x6F; // Code.
439:    command += (unsigned char)0x42; // Code to restart into application
450:        command += (unsigned char)0x2A; // Code to retrieve software version
460:    command[0] = (unsigned char)0x44; // Code to write Flash data
489:    command[0] = (unsigned char)0x50; // Code to program Flash data
508:    command += (unsigned char)0x22; // Code to erase application region in Flash
516:    command[0] = (unsigned char)0x65; // Code to erase Flash by block or sector

===0x48 / 0x68 anywhere as a sent byte (src + SteamVRDriver)===
src/firmware_update.cpp:48:    0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
src/firmware_update.cpp:56:    0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,


Prompt:
<task-notification>
<task-id>bqosxqehc</task-id>
<tool-use-id>toolu_01K9vFMqmGWtprS9F4w13o9C</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-sauna\d3fb9be1-d61d-4f88-b77c-2a2a282b11fd\tasks\bqosxqehc.output</output-file>
<status>completed</status>
<summary>Background command "Extract all command code bytes" completed (exit code 0)</summary>
</task-notification>


Hypothesis falsified by source. Full HMDUtility command-code set: `0x2A 0x25 0x26 0x7E 0x5E 0x5A 0x53 0x47 0x67 0x46 0x66 0x52 0x50 0x49 0x42 0x65 0x4C 0x55 0x57 0x56 0x4D 0x4B 0x54 0x44 0x41 0x59 0x4E 0x4A 0x40 0x64 0x6F 0x22`. **No 0x48, no 0x68** (those only appear inside a CRC32 table). The addon does *not* send the doze opcodes → no collision → the symptom is **not** the doze latch. Good that I checked instead of flashing on a wrong theory.

So `doze_active` is never set in the addon scenario, which means every doze branch is inert. The wedge comes from something else in 0.4.x that's on an always-run / addon-driven path. Candidates left: the fan kickstart (addon drives fan via 0x46/0x66), the libm/`powf` linkage + build-system change (CMake/-O3/arm-gcc vs the Studio-built 0.3.x), or how an addon command behaves around SteamVR exit. Let me look at what the SteamVR driver sends — especially on shutdown — and re-check the firmware's always-run changes.


cd C:/Users/decid/Documents/projects/HMDUtility && echo "===SteamVRDriver files===" && ls SteamVRDriver/ && echo "" && echo "===how SteamVRDriver sends HID + codes===" && grep -rnE "0x[0-9A-Fa-f]{2}|Code|command_|hid|brightness|fan|prox|sleep|wake|shutdown|Deactivate|Standby|PowerOff|display" SteamVRDriver/ --include=*.cpp --include=*.h | grep -ivE "//|dependencies" | head -50

SteamVR driver HID send sites + shutdown


Command running in background with ID: bbs6z0ls8. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-sauna\d3fb9be1-d61d-4f88-b77c-2a2a282b11fd\tasks\bbs6z0ls8.output. You will be notified when it completes. To check interim output, use Read on that file path.


<task-notification>
<task-id>bbs6z0ls8</task-id>
<tool-use-id>toolu_01CAV3zUTdpxSt7BeTZvdmL5</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-sauna\d3fb9be1-d61d-4f88-b77c-2a2a282b11fd\tasks\bbs6z0ls8.output</output-file>
<status>completed</status>
<summary>Background command "SteamVR driver HID send sites + shutdown" completed (exit code 0)</summary>
</task-notification>


1	===SteamVRDriver files===
2	driver.vrdrivermanifest
3	resources
4
5	===how SteamVRDriver sends HID + codes===
6

===SteamVRDriver files===
driver.vrdrivermanifest
resources

===how SteamVRDriver sends HID + codes===


Prompt:
<task-notification>
<task-id>bbs6z0ls8</task-id>
<tool-use-id>toolu_01CAV3zUTdpxSt7BeTZvdmL5</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-sauna\d3fb9be1-d61d-4f88-b77c-2a2a282b11fd\tasks\bbs6z0ls8.output</output-file>
<status>completed</status>
<summary>Background command "SteamVR driver HID send sites + shutdown" completed (exit code 0)</summary>
</task-notification>


`SteamVRDriver/` here is just a manifest — the actual driver calls into the shared `src/hid_handler.cpp`, whose command set I already have in full. So the **addon definitively never sends 0x48/0x68**. Collision firmly disproven.

That means on the addon-only path, every doze source change is inert (verified branch-by-branch). The variable between "0.3.x works" and "0.4.x wedges" narrows to: (a) the always-run **fan kickstart**, or (b) the **build-system/toolchain change** (0.3.x was Studio-built; 0.4.3 is the headless CMake/arm-gcc build that needed documented header-shadow/source-list fixes — and prox runs on the **ADC**, exactly the subsystem the `adc.h` shadow touched).

Per your "check the source, don't guess" — I need to verify the doze branch's true base vs the known-good shipped firmware (any hidden non-doze deltas) and whether the CMake build diverges from the `.cproj` in ways that affect runtime. Let me check the base relationship and build flags.


cd C:/Users/decid/Documents/projects/beyond_synaptics && echo "===what is shipped/known-good: v0.3.19 vs doze base 78c279d===" && git log --oneline v0.3.19..78c279d 2>/dev/null | head && echo "(empty above = 78c279d == or behind v0.3.19)" && echo "" && echo "===is 78c279d ancestor of v0.3.19?===" && git merge-base --is-ancestor 78c279d v0.3.19 && echo "YES 78c279d is ancestor of v0.3.19" || echo "NO" && echo "" && echo "===commits in doze branch since v0.3.19===" && git log --oneline v0.3.19..HEAD | head && echo "" && echo "===non-doze source changes v0.3.19..HEAD (exclude docs/cmake)===" && git diff --stat v0.3.19 HEAD -- 'src/*.c' 'src/*.h' | head -40

Doze branch base vs shipped v0.3.19


Command running in background with ID: by3owxzq9. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-sauna\d3fb9be1-d61d-4f88-b77c-2a2a282b11fd\tasks\by3owxzq9.output. You will be notified when it completes. To check interim output, use Read on that file path.


<task-notification>
<task-id>by3owxzq9</task-id>
<tool-use-id>toolu_01Y6iVZ1tfHwbjkrvzJMTkxx</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-sauna\d3fb9be1-d61d-4f88-b77c-2a2a282b11fd\tasks\by3owxzq9.output</output-file>
<status>completed</status>
<summary>Background command "Doze branch base vs shipped v0.3.19" completed (exit code 0)</summary>
</task-notification>


1	===what is shipped/known-good: v0.3.19 vs doze base 78c279d===
2	78c279d Wiki
3	(empty above = 78c279d == or behind v0.3.19)
4
5	===is 78c279d ancestor of v0.3.19?===
6	NO
7
8	===commits in doze branch since v0.3.19===
9	67bcafd doze 0.4.2 + 0.4.3: boot-blip fix + born-doze wake race fix
10	33d3b5c docs: headless build/flash runbook + doze HID commands
11	189c51e doze: gamma-eased brightness sweep (perceptually even fade/wake)
12	efe87b9 doze: warm shallow-sleep firmware (ADR-0005) + CMake build
13	78c279d Wiki
14
15	===non-doze source changes v0.3.19..HEAD (exclude docs/cmake)===
16	 src/sw_ver.h           |  13 +++-
17	 src/test_and_debug.c   |  10 +++
18	 src/test_and_debug.h   |   3 +
19	 src/usbhid_interface.c |  12 +++
20	 src/usbhid_interface.h |   2 +
21	 src/video_proc.c       | 208 ++++++++++++++++++++++++++++++++++++++++++++++++-
22	 src/video_proc.h       |  24 +++++-
23	 7 files changed, 264 insertions(+), 8 deletions(-)
24

===what is shipped/known-good: v0.3.19 vs doze base 78c279d===
78c279d Wiki
(empty above = 78c279d == or behind v0.3.19)

===is 78c279d ancestor of v0.3.19?===
NO

===commits in doze branch since v0.3.19===
67bcafd doze 0.4.2 + 0.4.3: boot-blip fix + born-doze wake race fix
33d3b5c docs: headless build/flash runbook + doze HID commands
189c51e doze: gamma-eased brightness sweep (perceptually even fade/wake)
efe87b9 doze: warm shallow-sleep firmware (ADR-0005) + CMake build
78c279d Wiki

===non-doze source changes v0.3.19..HEAD (exclude docs/cmake)===
 src/sw_ver.h           |  13 +++-
 src/test_and_debug.c   |  10 +++
 src/test_and_debug.h   |   3 +
 src/usbhid_interface.c |  12 +++
 src/usbhid_interface.h |   2 +
 src/video_proc.c       | 208 ++++++++++++++++++++++++++++++++++++++++++++++++-
 src/video_proc.h       |  24 +++++-
 7 files changed, 264 insertions(+), 8 deletions(-)


Prompt:
<task-notification>
<task-id>by3owxzq9</task-id>
<tool-use-id>toolu_01Y6iVZ1tfHwbjkrvzJMTkxx</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-sauna\d3fb9be1-d61d-4f88-b77c-2a2a282b11fd\tasks\by3owxzq9.output</output-file>
<status>completed</status>
<summary>Background command "Doze branch base vs shipped v0.3.19" completed (exit code 0)</summary>
</task-notification>


Key finding: the doze branch base `78c279d "Wiki"` is **NOT an ancestor of v0.3.19** — the doze branch diverged from a different point, so the flashed 0.4.3 may be **missing commits that ARE in the shipped v0.3.19**. If any of those lost commits are addon-critical (USB/HID/eyetracking/prox), flashing 0.4.3 *regressed* them → wedge. Let me see what 0.4.3 lost relative to shipped v0.3.19.


cd C:/Users/decid/Documents/projects/beyond_synaptics && echo "===commits in v0.3.19 but MISSING from doze HEAD===" && git log --oneline HEAD..v0.3.19 && echo "" && echo "===source files those missing commits touched===" && git diff --stat HEAD v0.3.19 -- 'src/*.c' 'src/*.h' | tail -40

Commits shipped but missing from doze build


Command running in background with ID: bylejicq7. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-sauna\d3fb9be1-d61d-4f88-b77c-2a2a282b11fd\tasks\bylejicq7.output. You will be notified when it completes. To check interim output, use Read on that file path.


<task-notification>
<task-id>bylejicq7</task-id>
<tool-use-id>toolu_01X5Ecgkji86eu6kieLQd8Wk</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-sauna\d3fb9be1-d61d-4f88-b77c-2a2a282b11fd\tasks\bylejicq7.output</output-file>
<status>completed</status>
<summary>Background command "Commits shipped but missing from doze build" completed (exit code 0)</summary>
</task-notification>


1	===commits in v0.3.19 but MISSING from doze HEAD===
2
3	===source files those missing commits touched===
4	 src/sw_ver.h           |  13 +---
5	 src/test_and_debug.c   |  10 ---
6	 src/test_and_debug.h   |   3 -
7	 src/usbhid_interface.c |  12 ---
8	 src/usbhid_interface.h |   2 -
9	 src/video_proc.c       | 208 +------------------------------------------------
10	 src/video_proc.h       |  24 +-----
11	 7 files changed, 8 insertions(+), 264 deletions(-)
12

===commits in v0.3.19 but MISSING from doze HEAD===

===source files those missing commits touched===
 src/sw_ver.h           |  13 +---
 src/test_and_debug.c   |  10 ---
 src/test_and_debug.h   |   3 -
 src/usbhid_interface.c |  12 ---
 src/usbhid_interface.h |   2 -
 src/video_proc.c       | 208 +------------------------------------------------
 src/video_proc.h       |  24 +-----
 7 files changed, 8 insertions(+), 264 deletions(-)


Prompt:
<task-notification>
<task-id>bylejicq7</task-id>
<tool-use-id>toolu_01X5Ecgkji86eu6kieLQd8Wk</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-sauna\d3fb9be1-d61d-4f88-b77c-2a2a282b11fd\tasks\bylejicq7.output</output-file>
<status>completed</status>
<summary>Background command "Commits shipped but missing from doze build" completed (exit code 0)</summary>
</task-notification>


`git log HEAD..v0.3.19` is **empty** — doze 0.4.3 is a strict *superset* of v0.3.19 source. No lost commits. So that lead is dead too: the source delta from v0.3.19 → 0.4.3 is *only* the doze + fan-kickstart changes, and the doze parts are inert without 0x48/0x68.

Let me record what's now proven before proposing the decisive experiment.


1	---
2	name: steamvr-forcekill-mcu-hang
3	description: 0.4.x firmware wedges the Beyond when any SteamVR Beyond-addon runs — prox dies, brightness sticks ~7, power-cycle to recover
4	metadata:
5	  node_type: memory
6	  type: project
7	  originSessionId: 92a9c1d9-a8d0-47c8-b932-02f7be39d701
8	---
9
10	ROOT CAUSE (confirmed by Brandon 2026-06-13, SteamVR-alone repro, NO sauna): a beyond_synaptics **0.4.x firmware** regression. Whenever EITHER official SteamVR addon (BeyondEyetracking OR BeyondSteamVR) runs, the Beyond's **proximity sensor stops working for that session** (reads untriggered → displays show picture but brightness latched ~7 ≈ a doze-floor value). Then a **SteamVR shutdown leaves the Beyond WEDGED** — usually the RIGHT optic lit with a dim/black image; MCU 0101 still enumerates but stops ACKing (`get_hmd_fw_version` → None). Recovers ONLY on a physical power-cycle. NOT present on pre-0.4.0 firmware (before the M5 doze mods). So a 0.4.x doze/prox/brightness change broke it.
11
12	**Earlier wrong theories (this same session) — do NOT trust them:** (1) "force-killing SteamVR mid-session hangs the MCU" and (2) "SteamVR's BeyondProximity driver collides with sauna on 0101." Both were conjecture. Deregistering BeyondProximity (vrpathreg) did NOT fix it; the wedge reproduces with sauna not running at all and with a clean SteamVR quit. **sauna is blameless.**
13
14	**Why it matters:** blocks SteamVR coexistence on 0.4.x firmware — the whole point of sauna is to coexist with SteamVR. Suspect area: doze warm-sleep (efe87b9, ADR-0005), gamma sweep (189c51e), born-doze wake race (67bcafd), brightness-floor-to-register-0 (58d80f8). Likely a specific MCU HID command both addons send that the new doze/prox-gate/brightness-floor logic mishandles → prox disabled + brightness latched low + wedge on teardown.
15
16	**How to apply:** This is a FIRMWARE fix (beyond_synaptics), not sauna. Find the 0.4.x command-handling change vs pre-0.4.0; bump 0.4.4; headless build+flash ([[beyond-synaptics-firmware-build]], code-46 retry quirk); retest with a SteamVR addon running. Verify MCU liveness read-only via `get_hmd_fw_version` (QBU bs_hmd_tools). Don't power past a wedge with more tests — power-cycle first. Related: [[hmd-bench-rig]].
17

---
name: steamvr-forcekill-mcu-hang
description: 0.4.x firmware wedges the Beyond when any SteamVR Beyond-addon runs — prox dies, brightness sticks ~7, power-cycle to recover
metadata:
  node_type: memory
  type: project
  originSessionId: 92a9c1d9-a8d0-47c8-b932-02f7be39d701
---

ROOT CAUSE (confirmed by Brandon 2026-06-13, SteamVR-alone repro, NO sauna): a beyond_synaptics **0.4.x firmware** regression. Whenever EITHER official SteamVR addon (BeyondEyetracking OR BeyondSteamVR) runs, the Beyond's **proximity sensor stops working for that session** (reads untriggered → displays show picture but brightness latched ~7 ≈ a doze-floor value). Then a **SteamVR shutdown leaves the Beyond WEDGED** — usually the RIGHT optic lit with a dim/black image; MCU 0101 still enumerates but stops ACKing (`get_hmd_fw_version` → None). Recovers ONLY on a physical power-cycle. NOT present on pre-0.4.0 firmware (before the M5 doze mods). So a 0.4.x doze/prox/brightness change broke it.

**Earlier wrong theories (this same session) — do NOT trust them:** (1) "force-killing SteamVR mid-session hangs the MCU" and (2) "SteamVR's BeyondProximity driver collides with sauna on 0101." Both were conjecture. Deregistering BeyondProximity (vrpathreg) did NOT fix it; the wedge reproduces with sauna not running at all and with a clean SteamVR quit. **sauna is blameless.**

**Why it matters:** blocks SteamVR coexistence on 0.4.x firmware — the whole point of sauna is to coexist with SteamVR. Suspect area: doze warm-sleep (efe87b9, ADR-0005), gamma sweep (189c51e), born-doze wake race (67bcafd), brightness-floor-to-register-0 (58d80f8). Likely a specific MCU HID command both addons send that the new doze/prox-gate/brightness-floor logic mishandles → prox disabled + brightness latched low + wedge on teardown.

**How to apply:** This is a FIRMWARE fix (beyond_synaptics), not sauna. Find the 0.4.x command-handling change vs pre-0.4.0; bump 0.4.4; headless build+flash ([[beyond-synaptics-firmware-build]], code-46 retry quirk); retest with a SteamVR addon running. Verify MCU liveness read-only via `get_hmd_fw_version` (QBU bs_hmd_tools). Don't power past a wedge with more tests — power-cycle first. Related: [[hmd-bench-rig]].


Edit

**Why it matters:** blocks SteamVR coexistence on 0.4.x firmware — the whole point of sauna is to coexist with SteamVR. Suspect area: doze warm-sleep (efe87b9, ADR-0005), gamma sweep (189c51e), born-doze wake race (67bcafd), brightness-floor-to-register-0 (58d80f8). Likely a specific MCU HID command both addons send that the new doze/prox-gate/brightness-floor logic mishandles → prox disabled + brightness latched low + wedge on teardown.

**How to apply:** This is a FIRMWARE fix (beyond_synaptics), not sauna. Find the 0.4.x command-handling change vs pre-0.4.0; bump 0.4.4; headless build+flash ([[beyond-synaptics-firmware-build]], code-46 retry quirk); retest with a SteamVR addon running. Verify MCU liveness read-only via `get_hmd_fw_version` (QBU bs_hmd_tools). Don't power past a wedge with more tests — power-cycle first. Related: [[hmd-bench-rig]].

**Why it matters:** blocks SteamVR coexistence on 0.4.x firmware — the whole point of sauna is to coexist with SteamVR.

**DISPROVEN 2026-06-13 (source-checked, not guessed):** the "doze logic mishandles an addon HID command" theory is dead. (1) The doze state (`doze_active` latch) can ONLY be set by a feature report with `report[0]==0x48` ('H' sleep); wake is 0x68 ('h'). (2) The addons' full command set is in `../HMDUtility/src/hid_handler.cpp` — every code it sends to 0101: `2A 25 26 7E 5E 5A 53 47 67 46 66 52 50 49 42 65 4C 55 57 56 4D 4B 54 44 41 59 4E 4A 40 64 6F 22`. **No 0x48, no 0x68.** So doze never triggers in an addon session and every doze branch in `video_proc.c` is provably inert (all gate on `doze_active`/`doze_phase`, which stay false). Frame note: HMDUtility sends `[0]=0x00 ReportID,[1]=Code`; USB strips the report-ID so firmware `report[0]`==Code.

**Remaining real variables (v0.3.19 shipped → 0.4.3 flashed; 0.4.3 is a strict SUPERSET of v0.3.19 source, no lost commits):** only TWO things differ on always-run / addon-driven paths: (1) **fan kickstart** (`test_and_debug.c` task_fan: jumps to `FAN_MIN_START`=35 on any 0→N fan change; addon drives fan via 0x46/0x66) + `#include <math.h>`/libm linkage; (2) the **BUILD/TOOLCHAIN** — 0.4.3 is the headless CMake/arm-gcc-12.2 `-O3` build ([[beyond-synaptics-firmware-build]]) vs the official Studio build of the known-good firmware. Build had documented hazards incl. CMSIS `adc.h` shadowing `src/Drivers/adc.h` — and **prox reads via ADC**, matching "prox stops working." NOTE: still need to confirm the actual known-good baseline (official production binary, possibly NOT in this repo — v0.3.19 is just the latest tag).

**DECISIVE NEXT EXPERIMENT (isolates build vs source):** build *pure v0.3.19 source* with the SAME CMake/arm-gcc toolchain, flash, test with addons. v0.3.19-via-CMake ALSO wedges ⇒ it's the BUILD/toolchain (not doze source). v0.3.19-via-CMake works but 0.4.3 wedges ⇒ it's the 0.4.x source delta (fan-kickstart prime suspect → bisect by reverting it). Headless build+flash code-46 retry quirk applies. Verify MCU liveness read-only via `get_hmd_fw_version` (QBU bs_hmd_tools). Don't power past a wedge — power-cycle first. Related: [[hmd-bench-rig]].


The file C:\Users\decid\.ccs\instances\bigscreen\projects\C--Users-decid-Documents-projects-sauna\memory\steamvr-forcekill-mcu-hang.md has been updated successfully. (file state is current in your context — no need to Read it back)

**Why it matters:** blocks SteamVR coexistence on 0.4.x firmware — the whole point of sauna is to coexist with SteamVR.

**DISPROVEN 2026-06-13 (source-checked, not guessed):** the "doze logic mishandles an addon HID command" theory is dead. (1) The doze state (`doze_active` latch) can ONLY be set by a feature report with `report[0]==0x48` ('H' sleep); wake is 0x68 ('h'). (2) The addons' full command set is in `../HMDUtility/src/hid_handler.cpp` — every code it sends to 0101: `2A 25 26 7E 5E 5A 53 47 67 46 66 52 50 49 42 65 4C 55 57 56 4D 4B 54 44 41 59 4E 4A 40 64 6F 22`. **No 0x48, no 0x68.** So doze never triggers in an addon session and every doze branch in `video_proc.c` is provably inert (all gate on `doze_active`/`doze_phase`, which stay false). Frame note: HMDUtility sends `[0]=0x00 ReportID,[1]=Code`; USB strips the report-ID so firmware `report[0]`==Code.

**Remaining real variables (v0.3.19 shipped → 0.4.3 flashed; 0.4.3 is a strict SUPERSET of v0.3.19 source, no lost commits):** only TWO things differ on always-run / addon-driven paths: (1) **fan kickstart** (`test_and_debug.c` task_fan: jumps to `FAN_MIN_START`=35 on any 0→N fan change; addon drives fan via 0x46/0x66) + `#include <math.h>`/libm linkage; (2) the **BUILD/TOOLCHAIN** — 0.4.3 is the headless CMake/arm-gcc-12.2 `-O3` build ([[beyond-synaptics-firmware-build]]) vs the official Studio build of the known-good firmware. Build had documented hazards incl. CMSIS `adc.h` shadowing `src/Drivers/adc.h` — and **prox reads via ADC**, matching "prox stops working." NOTE: still need to confirm the actual known-good baseline (official production binary, possibly NOT in this repo — v0.3.19 is just the latest tag).

**DECISIVE NEXT EXPERIMENT (isolates build vs source):** build *pure v0.3.19 source* with the SAME CMake/arm-gcc toolchain, flash, test with addons. v0.3.19-via-CMake ALSO wedges ⇒ it's the BUILD/toolchain (not doze source). v0.3.19-via-CMake works but 0.4.3 wedges ⇒ it's the 0.4.x source delta (fan-kickstart prime suspect → bisect by reverting it). Headless build+flash code-46 retry quirk applies. Verify MCU liveness read-only via `get_hmd_fw_version` (QBU bs_hmd_tools). Don't power past a wedge — power-cycle first. Related: [[hmd-bench-rig]].

---
name: steamvr-forcekill-mcu-hang
description: 0.4.x firmware wedges the Beyond when any SteamVR Beyond-addon runs — prox dies, brightness sticks ~7, power-cycle to recover
metadata:
  node_type: memory
  type: project
  originSessionId: 92a9c1d9-a8d0-47c8-b932-02f7be39d701
---

ROOT CAUSE (confirmed by Brandon 2026-06-13, SteamVR-alone repro, NO sauna): a beyond_synaptics **0.4.x firmware** regression. Whenever EITHER official SteamVR addon (BeyondEyetracking OR BeyondSteamVR) runs, the Beyond's **proximity sensor stops working for that session** (reads untriggered → displays show picture but brightness latched ~7 ≈ a doze-floor value). Then a **SteamVR shutdown leaves the Beyond WEDGED** — usually the RIGHT optic lit with a dim/black image; MCU 0101 still enumerates but stops ACKing (`get_hmd_fw_version` → None). Recovers ONLY on a physical power-cycle. NOT present on pre-0.4.0 firmware (before the M5 doze mods). So a 0.4.x doze/prox/brightness change broke it.

**Earlier wrong theories (this same session) — do NOT trust them:** (1) "force-killing SteamVR mid-session hangs the MCU" and (2) "SteamVR's BeyondProximity driver collides with sauna on 0101." Both were conjecture. Deregistering BeyondProximity (vrpathreg) did NOT fix it; the wedge reproduces with sauna not running at all and with a clean SteamVR quit. **sauna is blameless.**

**Why it matters:** blocks SteamVR coexistence on 0.4.x firmware — the whole point of sauna is to coexist with SteamVR. Suspect area: doze warm-sleep (efe87b9, ADR-0005), gamma sweep (189c51e), born-doze wake race (67bcafd), brightness-floor-to-register-0 (58d80f8). Likely a specific MCU HID command both addons send that the new doze/prox-gate/brightness-floor logic mishandles → prox disabled + brightness latched low + wedge on teardown.

**How to apply:** This is a FIRMWARE fix (beyond_synaptics), not sauna. Find the 0.4.x command-handling change vs pre-0.4.0; bump 0.4.4; headless build+flash ([[beyond-synaptics-firmware-build]], code-46 retry quirk); retest with a SteamVR addon running. Verify MCU liveness read-only via `get_hmd_fw_version` (QBU bs_hmd_tools). Don't power past a wedge with more tests — power-cycle first. Related: [[hmd-bench-rig]].

-**Why it matters:** blocks SteamVR coexistence on 0.4.x firmware — the whole point of sauna is to coexist with SteamVR. Suspect area: doze warm-sleep (efe87b9, ADR-0005), gamma sweep (189c51e), born-doze wake race (67bcafd), brightness-floor-to-register-0 (58d80f8). Likely a specific MCU HID command both addons send that the new doze/prox-gate/brightness-floor logic mishandles → prox disabled + brightness latched low + wedge on teardown.

+**Why it matters:** blocks SteamVR coexistence on 0.4.x firmware — the whole point of sauna is to coexist with SteamVR.

-**How to apply:** This is a FIRMWARE fix (beyond_synaptics), not sauna. Find the 0.4.x command-handling change vs pre-0.4.0; bump 0.4.4; headless build+flash ([[beyond-synaptics-firmware-build]], code-46 retry quirk); retest with a SteamVR addon running. Verify MCU liveness read-only via `get_hmd_fw_version` (QBU bs_hmd_tools). Don't power past a wedge with more tests — power-cycle first. Related: [[hmd-bench-rig]].

+**DISPROVEN 2026-06-13 (source-checked, not guessed):** the "doze logic mishandles an addon HID command" theory is dead. (1) The doze state (`doze_active` latch) can ONLY be set by a feature report with `report[0]==0x48` ('H' sleep); wake is 0x68 ('h'). (2) The addons' full command set is in `../HMDUtility/src/hid_handler.cpp` — every code it sends to 0101: `2A 25 26 7E 5E 5A 53 47 67 46 66 52 50 49 42 65 4C 55 57 56 4D 4B 54 44 41 59 4E 4A 40 64 6F 22`. **No 0x48, no 0x68.** So doze never triggers in an addon session and every doze branch in `video_proc.c` is provably inert (all gate on `doze_active`/`doze_phase`, which stay false). Frame note: HMDUtility sends `[0]=0x00 ReportID,[1]=Code`; USB strips the report-ID so firmware `report[0]`==Code.

+**Remaining real variables (v0.3.19 shipped → 0.4.3 flashed; 0.4.3 is a strict SUPERSET of v0.3.19 source, no lost commits):** only TWO things differ on always-run / addon-driven paths: (1) **fan kickstart** (`test_and_debug.c` task_fan: jumps to `FAN_MIN_START`=35 on any 0→N fan change; addon drives fan via 0x46/0x66) + `#include <math.h>`/libm linkage; (2) the **BUILD/TOOLCHAIN** — 0.4.3 is the headless CMake/arm-gcc-12.2 `-O3` build ([[beyond-synaptics-firmware-build]]) vs the official Studio build of the known-good firmware. Build had documented hazards incl. CMSIS `adc.h` shadowing `src/Drivers/adc.h` — and **prox reads via ADC**, matching "prox stops working." NOTE: still need to confirm the actual known-good baseline (official production binary, possibly NOT in this repo — v0.3.19 is just the latest tag).

+**DECISIVE NEXT EXPERIMENT (isolates build vs source):** build *pure v0.3.19 source* with the SAME CMake/arm-gcc toolchain, flash, test with addons. v0.3.19-via-CMake ALSO wedges ⇒ it's the BUILD/toolchain (not doze source). v0.3.19-via-CMake works but 0.4.3 wedges ⇒ it's the 0.4.x source delta (fan-kickstart prime suspect → bisect by reverting it). Headless build+flash code-46 retry quirk applies. Verify MCU liveness read-only via `get_hmd_fw_version` (QBU bs_hmd_tools). Don't power past a wedge — power-cycle first. Related: [[hmd-bench-rig]].


Source-checking done. Here's what's now **proven** vs the wrap's theory:

## Disproven (checked, not guessed)
- **No opcode collision.** Doze latch sets *only* on `report[0]==0x48`; wake `0x68`. HMDUtility's full command set (`../HMDUtility/src/hid_handler.cpp`) sends `2A 25 26 7E 5E 5A 53 47 67 46 66 52 50 49 42 65 4C 55 57 56 4D 4B 54 44 41 59 4E 4A 40 64 6F 22` — **neither 0x48 nor 0x68**. So doze never triggers in an addon session.
- **Every doze branch is inert** without those opcodes (all gate on `doze_active`/`doze_phase`, both stay false). The wrap's "doze broke HID dispatch" is dead.
- **No lost commits.** 0.4.3 source ⊇ v0.3.19. The only source delta is doze + fan-kickstart.

## What actually differs (v0.3.19 → 0.4.3), addon-relevant
1. **Fan kickstart** — `task_fan` jumps to PWM 35 on any 0→N change; addon drives fan (0x46/0x66). Only *functional* source change on an always-run path. Plus `math.h`/libm linkage.
2. **Build/toolchain** — 0.4.3 = headless CMake/arm-gcc-12.2 `-O3`; known-good = official Studio build. Documented hazard: CMSIS `adc.h` shadowed `src/Drivers/adc.h`, and **prox reads via ADC** → matches "prox stops working."

## Decisive experiment (separates the two cleanly)
Build **pure v0.3.19 source with the same CMake toolchain**, flash, test with addons:
- v0.3.19-via-CMake **also wedges** → it's the **build/toolchain**, not doze source.
- v0.3.19-via-CMake **works** → it's the **0.4.x source delta** → revert fan-kickstart and re-test to confirm.

One gap I can't close from the repo: what was the *actual* known-good firmware? `v0.3.19` is just the latest tag here — the production binary on the HMD pre-0.4.0 may be from Bigscreen's official pipeline, not this repo.


AskUserQuestion

Question: What was the known-good 'pre-0.4.0' firmware that worked with the addons?

Choices:
The HMD shipped with Bigscreen's official firmware (built by the official pipeline, not this repo's CMake). Means build/toolchain is a strong suspect — I'd need that binary or its exact build config to compare.
shipped binary != this repo's
CMake build → build/toolchain
difference is in play
The known-good was v0.3.19 built the same way (or flashed from this repo). Means toolchain is constant → the wedge is the 0.4.x source delta (fan-kickstart).
same repo + same build
→ source delta is the cause
→ fan-kickstart prime suspect
Unclear which exact firmware was on the HMD before. I'll help you determine it (read fw version logs, QBU history, flash records).
determine baseline first
before the isolation build

Question: Run the isolation build experiment now?

Choices:
The decisive build-vs-source test. Headless CMake build of clean v0.3.19, flash (code-46 retry quirk), run an addon session, check for wedge. ~ one build+flash cycle on the just-power-cycled HMD.
git worktree v0.3.19
cmake -O3 build → .beyondfw
flash → SteamVR+addon session
→ wedge? = build  : source
Before any flash: trace set_fan_pwm / FAN_MIN_START and whether the fan path shares I2C/resources with prox/OLED that could wedge. Cheap, no hardware, may indict/clear hypothesis 1.
read set_fan_pwm()
check I2C bus sharing
fan vs prox/OLED contention
Diff compiled source list, flags, defines, linker script between the CMake build and the Studio .cproj to find build-induced runtime differences. No hardware.
diff CMakeLists sources
 vs .cproj <Compile Include>
+ flags / linker / defines


Your questions have been answered: "What was the known-good 'pre-0.4.0' firmware that worked with the addons?"=(no option selected) notes: the HMD was previously running v0.3.18. maybe we could also, in addition to building it with our toolchain and testing that, also CRC check it against the prebuilt 0.3.18 to see if there's a fundamental difference in what gets built., "Run the isolation build experiment now?"="Compare CMake build vs .cproj" selected preview:
diff CMakeLists sources
 vs .cproj <Compile Include>
+ flags / linker / defines. You can now continue with these answers in mind.

Question: What was the known-good 'pre-0.4.0' firmware that worked with the addons?

Choices:
The HMD shipped with Bigscreen's official firmware (built by the official pipeline, not this repo's CMake). Means build/toolchain is a strong suspect — I'd need that binary or its exact build config to compare.
shipped binary != this repo's
CMake build → build/toolchain
difference is in play
The known-good was v0.3.19 built the same way (or flashed from this repo). Means toolchain is constant → the wedge is the 0.4.x source delta (fan-kickstart).
same repo + same build
→ source delta is the cause
→ fan-kickstart prime suspect
Unclear which exact firmware was on the HMD before. I'll help you determine it (read fw version logs, QBU history, flash records).
determine baseline first
before the isolation build

Question: Run the isolation build experiment now?

Choices:
The decisive build-vs-source test. Headless CMake build of clean v0.3.19, flash (code-46 retry quirk), run an addon session, check for wedge. ~ one build+flash cycle on the just-power-cycled HMD.
git worktree v0.3.19
cmake -O3 build → .beyondfw
flash → SteamVR+addon session
→ wedge? = build  : source
Before any flash: trace set_fan_pwm / FAN_MIN_START and whether the fan path shares I2C/resources with prox/OLED that could wedge. Cheap, no hardware, may indict/clear hypothesis 1.
read set_fan_pwm()
check I2C bus sharing
fan vs prox/OLED contention
Diff compiled source list, flags, defines, linker script between the CMake build and the Studio .cproj to find build-induced runtime differences. No hardware.
diff CMakeLists sources
 vs .cproj <Compile Include>
+ flags / linker / defines