cmake_minimum_required(VERSION 3.10)
project(beyond_synaptics C CXX ASM)

set(CMAKE_EXECUTABLE_SUFFIX ".elf")
set(CMAKE_C_COMPILER_TARGET thumbv7m-unknown-none-eabi)
set(CMAKE_CXX_COMPILER_TARGET thumbv7m-unknown-none-eabi)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)

set(CPU_FLAGS -mthumb -mcpu=cortex-m4 -mfloat-abi=softfp -mfpu=fpv4-sp-d16)
# set(COMPILER_FLAGS -ffreestanding -ffunction-sections -fdata-sections -fsigned-char -fmessage-length=0 -fshort-enums)
set(COMPILER_FLAGS -pipe -fno-strict-aliasing -Wall -Wstrict-prototypes -Wmissing-prototypes
 -Werror-implicit-function-declaration -Wpointer-arith -std=gnu99
 -ffunction-sections -fdata-sections -Wchar-subscripts -Wcomment
 -Wformat=2 -Wimplicit-int -Wmain -Wparentheses -Wsequence-point
 -Wreturn-type -Wswitch -Wtrigraphs -Wunused -Wuninitialized -Wunknown-pragmas -Wno-expansion-to-defined
 -Wfloat-equal -Wundef -Wshadow -Wbad-function-cast -Wwrite-strings -Wsign-compare
 -Waggregate-return -Wmissing-declarations -Wformat -Wmissing-format-attribute
 -Wno-deprecated-declarations -Wpacked -Wredundant-decls -Wnested-externs -Wlong-long
 -Wunreachable-code -Wcast-align --param max-inline-insns-single=500)
# NOTE: -mlong-calls REMOVED (0.4.4) — it wedged the MCU under SteamVR addon
# load (BeyondEyetracking/BeyondSteamVR co-driving 0101); hardware-isolated as
# the sole cause. -ffast-math also removed (unsafe FP / -ffinite-math-only kills
# NaN guards; its crtfastmath FPSCR init was gc-sections'd out anyway). The
# official Studio .cproj uses neither flag.

set(LINKER_FILE "${CMAKE_CURRENT_LIST_DIR}/src/ASF/sam/utils/linker_scripts/samg/samg55j19/gcc/flash_usb_bootloader.ld")

add_compile_options(${CPU_FLAGS} ${COMPILER_FLAGS})
add_link_options(${CPU_FLAGS})
add_definitions(-D__SAMG55G19__ -DARM_MATH_CM4=true -Dprintf=iprintf -D__FREERTOS__ -Dscanf=iscanf -DBOARD=USER_EXT_BOARD -DUDD_ENABLE -DNOTBOARD=SAMG55_XPLAINED_PRO -DUSER_BOARD_REVISION=4)

include(utils.cmake)

if("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang")
    include(../../../clang_utils.cmake)
    clang_utils_get_arm_gcc_sysroot(${ARM_GCC_C_COMPILER} ${CPU_FLAGS})
    clang_utils_get_arm_gcc_multilib_dir(${ARM_GCC_C_COMPILER} ${CPU_FLAGS})
    clang_utils_get_arm_gcc_lib_dir(${ARM_GCC_C_COMPILER} ${CPU_FLAGS})
    clang_utils_get_arm_gcc_crt(${ARM_GCC_SYSROOT} ${ARM_GCC_MULTIDIR} ${ARM_GCC_LIBGCC_DIR})
    string(APPEND CMAKE_EXE_LINKER_FLAGS
        " -nostdlib -L${ARM_GCC_SYSROOT}/lib/${ARM_GCC_MULTIDIR} -L${ARM_GCC_LIBGCC_DIR}")
    string(APPEND CMAKE_EXE_LINKER_FLAGS " -lgcc -lc_nano -lc")
elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
    string(REGEX MATCH ".*\.specs.*" has_specs "${CMAKE_EXE_LINKER_FLAGS}")

    if(NOT has_specs)
        string(APPEND CMAKE_EXE_LINKER_FLAGS " --specs=nano.specs")
    endif()
endif()

string(REPLACE ";" " " _LINKER_CPU_FLAGS "${CPU_FLAGS}")

# subdirlist(include_dir_list ${CMAKE_CURRENT_LIST_DIR}/src)

add_executable(beyond_synaptics src/main.c)
target_include_directories(beyond_synaptics PRIVATE
src/
# Project source dirs must precede the ASF/CMSIS dirs: the samg55 CMSIS tree
# ships its own component/instance "adc.h" which otherwise shadows the
# project's src/Drivers/adc.h on a bare #include "adc.h".
src/Devices/
src/Drivers/
src/config/
src/crash_handler/
src/ASF/
src/ASF/common/
src/ASF/common/boards/
src/ASF/common/services/
src/ASF/common/services/clock/
src/ASF/common/services/clock/samg/
src/ASF/common/services/ioport/
src/ASF/common/services/ioport/sam/
src/ASF/common/services/serial/
src/ASF/common/services/serial/sam_uart/
src/ASF/common/services/sleepmgr/
src/ASF/common/services/sleepmgr/sam/
src/ASF/common/services/usb/
src/ASF/common/services/usb/class/
src/ASF/common/services/usb/class/audio/device
src/ASF/common/services/usb/class/audio
src/ASF/common/services/usb/class/hid
src/ASF/common/services/usb/class/hid/device
src/ASF/common/services/usb/class/composite
src/ASF/common/services/usb/class/hid/device/generic
src/ASF/common/services/usb/udc/
src/ASF/common/utils/
src/ASF/common/utils/interrupt/
src/ASF/common/utils/stdio/
src/ASF/common/utils/stdio/stdio_serial/
src/ASF/sam/
src/ASF/sam/boards/
src/ASF/sam/boards/displayboard
src/ASF/sam/boards/samg55_xplained_pro/
src/ASF/sam/drivers/
src/ASF/sam/drivers/adc/
src/ASF/sam/drivers/efc/
src/ASF/sam/drivers/flexcom/
src/ASF/sam/drivers/matrix/
src/ASF/sam/drivers/pdc/
src/ASF/sam/drivers/pdc/pdc_uart_example/
src/ASF/sam/drivers/pdm/
src/ASF/sam/drivers/pio/
src/ASF/sam/drivers/pmc/
src/ASF/sam/drivers/supc/
src/ASF/sam/drivers/udp/
src/ASF/sam/drivers/usart/
src/ASF/sam/utils/
src/ASF/sam/utils/cmsis/
src/ASF/sam/utils/cmsis/samg/
src/ASF/sam/utils/cmsis/samg/samg55/
src/ASF/sam/utils/cmsis/samg/samg55/include/
src/ASF/sam/utils/cmsis/samg/samg55/include/component/
src/ASF/sam/utils/cmsis/samg/samg55/include/instance/
src/ASF/sam/utils/cmsis/samg/samg55/include/pio/
src/ASF/sam/utils/cmsis/samg/samg55/source/
src/ASF/sam/utils/cmsis/samg/samg55/source/templates/
src/ASF/sam/utils/cmsis/samg/samg55/source/templates/gcc/
src/ASF/sam/utils/fpu/
src/ASF/sam/utils/header_files/
src/ASF/sam/utils/linker_scripts/
src/ASF/sam/utils/linker_scripts/samg/
src/ASF/sam/utils/linker_scripts/samg/samg55j19/
src/ASF/sam/utils/linker_scripts/samg/samg55j19/gcc/
src/ASF/sam/utils/make/
src/ASF/sam/utils/preprocessor/
src/ASF/sam/utils/syscalls/
src/ASF/sam/utils/syscalls/gcc/
src/ASF/thirdparty/
src/ASF/thirdparty/CMSIS/
src/ASF/thirdparty/CMSIS/Include/
src/ASF/thirdparty/CMSIS/Lib/
src/ASF/thirdparty/CMSIS/Lib/GCC/
src/ASF/thirdparty/freertos/
src/ASF/thirdparty/freertos/freertos-10.0.0/
src/ASF/thirdparty/freertos/freertos-10.0.0/Source/
src/ASF/thirdparty/freertos/freertos-10.0.0/Source/include/
src/ASF/thirdparty/freertos/freertos-10.0.0/Source/portable/
src/ASF/thirdparty/freertos/freertos-10.0.0/Source/portable/GCC/
src/ASF/thirdparty/freertos/freertos-10.0.0/Source/portable/GCC/ARM_CM4F/
src/ASF/thirdparty/freertos/freertos-10.0.0/Source/portable/MemMang/
src/config/
src/Devices
src/Drivers
src/crash_handler
)

target_sources(beyond_synaptics PRIVATE
src/ASF/common/services/usb/class/audio/device/udi_audio.c
src/ASF/common/services/usb/class/hid/device/udi_hid_desc.c
src/ASF/sam/boards/displayboard/displayboard_init.c
src/ASF/sam/drivers/adc/adc2.c
src/crash_handler/crash_handler.c
src/Devices/eyetrack_fpga.c
src/Devices/fan_motor_control.c
src/Devices/linkbox_hpd.c
src/Devices/oled_control.c
src/Devices/prox_control.c
src/Devices/prox_tmd2635.c
src/Devices/rgb_led.c
src/Devices/usb2517.c
src/Devices/usb3803.c
src/Devices/usbc_mux.c
src/Devices/vxr_interface.c
src/Devices/tundra_uart.c
src/Drivers/adc.c
src/Drivers/flash_mutex.c
src/Drivers/i2c.c
src/Drivers/pdm_mics.c
src/Drivers/power_control.c
src/Drivers/rtt_util.c
src/Drivers/signature.c
src/Drivers/temp_sense.c
src/eeprom_emulation.c
src/hw_test.c
src/sw_ver.c
src/ui_interface.c
src/usart_interface.c
src/usbhid_interface.c
src/video_proc.c
src/test_and_debug.c
src/ASF/common/services/usb/class/composite/udi_composite_desc.c
src/ASF/common/services/usb/class/hid/device/generic/udi_hid_generic.c
src/ASF/common/services/usb/class/hid/device/udi_hid.c
src/ASF/common/services/usb/udc/udc.c
src/ASF/sam/drivers/pio/pio_handler.c
src/ASF/sam/drivers/udp/udp_device.c
src/ASF/sam/drivers/matrix/matrix.c
src/ASF/sam/drivers/pdc/pdc.c
src/ASF/sam/drivers/pdm/pdm.c
src/ASF/sam/drivers/pio/pio.c
src/ASF/common/services/clock/samg/sysclk.c
src/ASF/common/services/serial/usart_serial.c
src/ASF/common/services/sleepmgr/sam/sleepmgr.c
src/ASF/common/utils/interrupt/interrupt_sam_nvic.c
src/ASF/common/utils/stdio/read.c
src/ASF/common/utils/stdio/write.c
src/ASF/sam/drivers/efc/efc.c
src/ASF/sam/drivers/flexcom/flexcom.c
src/ASF/sam/drivers/pmc/pmc.c
src/ASF/sam/drivers/pmc/sleep.c
src/ASF/sam/drivers/supc/supc.c
src/ASF/sam/drivers/usart/usart.c
src/ASF/sam/utils/cmsis/samg/samg55/source/templates/gcc/startup_samg55.c
src/ASF/sam/utils/cmsis/samg/samg55/source/templates/system_samg55.c
src/ASF/sam/utils/syscalls/gcc/syscalls.c
src/ASF/thirdparty/freertos/freertos-10.0.0/Source/croutine.c
src/ASF/thirdparty/freertos/freertos-10.0.0/Source/event_groups.c
src/ASF/thirdparty/freertos/freertos-10.0.0/Source/list.c
src/ASF/thirdparty/freertos/freertos-10.0.0/Source/portable/GCC/ARM_CM4F/port.c
src/ASF/thirdparty/freertos/freertos-10.0.0/Source/portable/MemMang/heap_3.c
src/ASF/thirdparty/freertos/freertos-10.0.0/Source/queue.c
src/ASF/thirdparty/freertos/freertos-10.0.0/Source/stream_buffer.c
src/ASF/thirdparty/freertos/freertos-10.0.0/Source/tasks.c
src/ASF/thirdparty/freertos/freertos-10.0.0/Source/timers.c
)

target_link_directories(beyond_synaptics PRIVATE src/ASF/thirdparty/CMSIS/Lib/GCC)
target_link_libraries(beyond_synaptics PRIVATE arm_cortexM4lf_math m)

target_link_options(beyond_synaptics PRIVATE -Wl,--gc-sections -Wl,-T ${LINKER_FILE})

firmware_size(beyond_synaptics sysv)
generate_object(beyond_synaptics .bin binary)

if ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
     generate_object(beyond_synaptics .hex ihex)
endif()

-add_definitions(-D__SAMG55G19__ -DARM_MATH_CM4=true -Dprintf=iprintf -D__FREERTOS__ -Dscanf=iscanf -DBOARD=USER_EXT_BOARD -DUDD_ENABLE -DNOTBOARD=SAMG55_XPLAINED_PRO -DUSER_BOARD_REVISION=4)

+add_definitions(-D__SAMG55J19__ -DARM_MATH_CM4=true -Dprintf=iprintf -D__FREERTOS__ -Dscanf=iscanf -DBOARD=USER_EXT_BOARD -DUDD_ENABLE -DNOTBOARD=SAMG55_XPLAINED_PRO -DUSER_BOARD_REVISION=4)


1	#ifndef SW_VER_H_
2	#define SW_VER_H_
3
4
5	#define MAJOR_VERSION		0
6	#define MINOR_VERSION		4
7	#define PATCH_VERSION		3
8	// 0.4.0: doze warm-shallow-sleep (ADR-0005) - display-sleep/wake HID pair
9	// ('H'/'h'), doze-latch, fan kickstart. Host capability-gates doze on >= 0.4.0.
10	// 0.4.1: gamma-eased doze brightness sweep (perceptually even fade/wake).
11	// 0.4.2: born-doze bringup skips fan/LED-on (gated on !doze_active) - kills the
12	// brief boot fan-spin/LED-flash when launching not-worn straight into doze.
13	// 0.4.3: born-doze wake race fix - an 'h' arriving before the bringup settles
14	// into DOZE_ASLEEP is remembered (doze_wake_pending) and the settle comes up
15	// straight into the wake fade, instead of disarming (lit, no fade-in). Fixes
16	// donned-startup + first-don-after-undonned having no brightness sweep.
17
18	#define macrostr(s) #s
19	#define VERSION_STRING_BUILDER(major, minor, patch)		macrostr(major) "." macrostr(minor) "." macrostr(patch)
20	#define VERSION_STRING		VERSION_STRING_BUILDER(MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION)
21
22	const uint8_t* get_sw_ver(void);
23	uint8_t get_sw_ver_len(void);
24
25	#endif /* SW_VER_H_ */

#ifndef SW_VER_H_
#define SW_VER_H_

#define MAJOR_VERSION		0
#define MINOR_VERSION		4
#define PATCH_VERSION		3
// 0.4.0: doze warm-shallow-sleep (ADR-0005) - display-sleep/wake HID pair
// ('H'/'h'), doze-latch, fan kickstart. Host capability-gates doze on >= 0.4.0.
// 0.4.1: gamma-eased doze brightness sweep (perceptually even fade/wake).
// 0.4.2: born-doze bringup skips fan/LED-on (gated on !doze_active) - kills the
// brief boot fan-spin/LED-flash when launching not-worn straight into doze.
// 0.4.3: born-doze wake race fix - an 'h' arriving before the bringup settles
// into DOZE_ASLEEP is remembered (doze_wake_pending) and the settle comes up
// straight into the wake fade, instead of disarming (lit, no fade-in). Fixes
// donned-startup + first-don-after-undonned having no brightness sweep.

#define macrostr(s) #s
#define VERSION_STRING_BUILDER(major, minor, patch)		macrostr(major) "." macrostr(minor) "." macrostr(patch)
#define VERSION_STRING		VERSION_STRING_BUILDER(MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION)

const uint8_t* get_sw_ver(void);
uint8_t get_sw_ver_len(void);

#endif /* SW_VER_H_ */


Edit

#define PATCH_VERSION		3
// 0.4.0: doze warm-shallow-sleep (ADR-0005) - display-sleep/wake HID pair

#define PATCH_VERSION		4
// 0.4.4: build fix — drop -mlong-calls (wedged the MCU under SteamVR addon load,
// hardware-isolated as sole cause) + drop -ffast-math + fix __SAMG55G19__ typo
// to __SAMG55J19__. CMakeLists/toolchain only; no firmware logic change.
// 0.4.0: doze warm-shallow-sleep (ADR-0005) - display-sleep/wake HID pair


The file C:\Users\decid\Documents\projects\beyond_synaptics\src\sw_ver.h has been updated successfully. (file state is current in your context — no need to Read it back)

#define PATCH_VERSION		4
// 0.4.4: build fix — drop -mlong-calls (wedged the MCU under SteamVR addon load,
// hardware-isolated as sole cause) + drop -ffast-math + fix __SAMG55G19__ typo
// to __SAMG55J19__. CMakeLists/toolchain only; no firmware logic change.
// 0.4.0: doze warm-shallow-sleep (ADR-0005) - display-sleep/wake HID pair

#ifndef SW_VER_H_
#define SW_VER_H_

#define MAJOR_VERSION		0
#define MINOR_VERSION		4
#define PATCH_VERSION		3
// 0.4.0: doze warm-shallow-sleep (ADR-0005) - display-sleep/wake HID pair
// ('H'/'h'), doze-latch, fan kickstart. Host capability-gates doze on >= 0.4.0.
// 0.4.1: gamma-eased doze brightness sweep (perceptually even fade/wake).
// 0.4.2: born-doze bringup skips fan/LED-on (gated on !doze_active) - kills the
// brief boot fan-spin/LED-flash when launching not-worn straight into doze.
// 0.4.3: born-doze wake race fix - an 'h' arriving before the bringup settles
// into DOZE_ASLEEP is remembered (doze_wake_pending) and the settle comes up
// straight into the wake fade, instead of disarming (lit, no fade-in). Fixes
// donned-startup + first-don-after-undonned having no brightness sweep.

#define macrostr(s) #s
#define VERSION_STRING_BUILDER(major, minor, patch)		macrostr(major) "." macrostr(minor) "." macrostr(patch)
#define VERSION_STRING		VERSION_STRING_BUILDER(MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION)

const uint8_t* get_sw_ver(void);
uint8_t get_sw_ver_len(void);

#endif /* SW_VER_H_ */

+// 0.4.4: build fix — drop -mlong-calls (wedged the MCU under SteamVR addon load,

+// hardware-isolated as sole cause) + drop -ffast-math + fix __SAMG55G19__ typo

+// to __SAMG55J19__. CMakeLists/toolchain only; no firmware logic change.


Source fix applied to main repo (CMakeLists: dropped `-mlong-calls` + `-ffast-math`, fixed J19; sw_ver → 0.4.4). Building the real 0.4.4 release from the main tree + flashing it as the HMD's final good state.


Bash

cd C:/Users/decid/Documents/projects/beyond_synaptics && export PATH="/c/Program Files (x86)/Arm GNU Toolchain arm-none-eabi/12.2 mpacbti-rel1/bin:$LOCALAPPDATA/Microsoft/WinGet/Links:$PATH" && rm -rf out/build && cmake --preset arm-gcc-toolchain.cmake -DCMAKE_BUILD_TYPE=Release >/tmp/cfg044.log 2>&1 && cmake --build out/build/arm-gcc-toolchain.cmake >/tmp/build044.log 2>&1 && echo "BUILD OK" && ls -la out/build/arm-gcc-toolchain.cmake/beyond_synaptics.hex && echo "---verify -mlong-calls GONE from build---" && (grep -c "mlong-calls" out/build/arm-gcc-toolchain.cmake/build.ninja || echo "0 (none)") || (echo "BUILD FAILED"; tail -20 /tmp/build044.log /tmp/cfg044.log)

Build real 0.4.4 release


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


<system-reminder>Warning: the file exists but is shorter than the provided offset (1). The file has 1 lines.</system-reminder>


<task-notification>
<task-id>b9503bkxq</task-id>
<tool-use-id>toolu_01Dv68Dkvkrt6MPqh4enuwhq</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-sauna\d3fb9be1-d61d-4f88-b77c-2a2a282b11fd\tasks\b9503bkxq.output</output-file>
<status>completed</status>
<summary>Background command "Build real 0.4.4 release" completed (exit code 0)</summary>
</task-notification>


Build running. Waiting.


<owl_reachability_notice>
You have an active owl perch. When spawning subagents or doing work, use run_in_background: true so you stay reachable for incoming messages.
</owl_reachability_notice>


CAVEMAN MODE ACTIVE (full). Drop articles/filler/pleasantries/hedging. Fragments OK. Code/commits/security: write normal.


1	BUILD OK
2	-rw-r--r-- 1 decid 197609 205744 Jun 15 04:16 out/build/arm-gcc-toolchain.cmake/beyond_synaptics.hex
3	---verify -mlong-calls GONE from build---
4	0
5	0 (none)
6

BUILD OK
-rw-r--r-- 1 decid 197609 205744 Jun 15 04:16 out/build/arm-gcc-toolchain.cmake/beyond_synaptics.hex
---verify -mlong-calls GONE from build---
0
0 (none)


Real 0.4.4 built, `-mlong-calls` confirmed absent from build. Packaging + flashing as final HMD state.


cd C:/Users/decid/Documents/projects/beyond_synaptics && export PATH="/c/Program Files (x86)/Arm GNU Toolchain arm-none-eabi/12.2 mpacbti-rel1/bin:$LOCALAPPDATA/Microsoft/WinGet/Links:$PATH" && SC=$(ls scripts/usb_fw_packager_cli.py 2>/dev/null && echo scripts || echo .) && echo "packager dir: $SC" && cd $SC && py usb_fw_packager_cli.py -o "C:/Users/decid/Documents/projects/beyond_synaptics/dist/Beyond_AppImage_v044_release.hex" "C:/Users/decid/Documents/projects/beyond_synaptics/out/build/arm-gcc-toolchain.cmake/beyond_synaptics.hex" 2>&1 | tail -3 && py utility_fw_packager_cli.py -v 0.4.4 "C:/Users/decid/Documents/projects/beyond_synaptics/dist/Beyond_AppImage_v044_release.hex" "C:/Users/decid/Documents/projects/beyond_synaptics/dist/latest_0.4.4_release.beyondfw" 2>&1 | tail -3 && echo "---pkg version---" && py -c "import sys; sys.path.insert(0,r'C:\Users\decid\Documents\projects\quick-beyond-updater'); import bs_hmd_tools as t; print(t.get_file_fw_version(r'C:/Users/decid/Documents/projects/beyond_synaptics/dist/latest_0.4.4_release.beyondfw'))"

Package 0.4.4 release


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


<task-notification>
<task-id>bastaifil</task-id>
<tool-use-id>toolu_018FLKHb4sANVqgBjTYPVq9K</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-sauna\d3fb9be1-d61d-4f88-b77c-2a2a282b11fd\tasks\bastaifil.output</output-file>
<status>failed</status>
<summary>Background command "Package 0.4.4 release" failed with exit code 1</summary>
</task-notification>


1	packager dir: scripts/usb_fw_packager_cli.py
2	scripts
3	/usr/bin/bash: line 4: cd: too many arguments
4

packager dir: scripts/usb_fw_packager_cli.py
scripts
/usr/bin/bash: line 4: cd: too many arguments


Prompt:
<task-notification>
<task-id>bastaifil</task-id>
<tool-use-id>toolu_018FLKHb4sANVqgBjTYPVq9K</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-sauna\d3fb9be1-d61d-4f88-b77c-2a2a282b11fd\tasks\bastaifil.output</output-file>
<status>failed</status>
<summary>Background command "Package 0.4.4 release" failed with exit code 1</summary>
</task-notification>


Packager is in `scripts/`. My `cd` got two args — rerunning cleanly.


cd C:/Users/decid/Documents/projects/beyond_synaptics/scripts && export PATH="/c/Program Files (x86)/Arm GNU Toolchain arm-none-eabi/12.2 mpacbti-rel1/bin:$LOCALAPPDATA/Microsoft/WinGet/Links:$PATH" && BS=C:/Users/decid/Documents/projects/beyond_synaptics && py usb_fw_packager_cli.py -o "$BS/dist/Beyond_AppImage_v044_release.hex" "$BS/out/build/arm-gcc-toolchain.cmake/beyond_synaptics.hex" 2>&1 | tail -2 && py utility_fw_packager_cli.py -v 0.4.4 "$BS/dist/Beyond_AppImage_v044_release.hex" "$BS/dist/latest_0.4.4_release.beyondfw" 2>&1 | tail -2 && echo "---pkg version check---" && py -c "import sys; sys.path.insert(0,r'C:\Users\decid\Documents\projects\quick-beyond-updater'); import bs_hmd_tools as t; print('file ver:', t.get_file_fw_version(r'$BS/dist/latest_0.4.4_release.beyondfw'))"

Package 0.4.4 release (fixed cd)


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


<task-notification>
<task-id>bhj0je3gh</task-id>
<tool-use-id>toolu_01Q22xCyukvcwTbBELpyBgvs</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-sauna\d3fb9be1-d61d-4f88-b77c-2a2a282b11fd\tasks\bhj0je3gh.output</output-file>
<status>completed</status>
<summary>Background command "Package 0.4.4 release (fixed cd)" completed (exit code 0)</summary>
</task-notification>


1	Firmware file created! C:/Users/decid/Documents/projects/beyond_synaptics/dist\Beyond_AppImage_v044_release.hex
2	Firmware file created! C:/Users/decid/Documents/projects/beyond_synaptics/dist\Beyond_AppImage_v044_release.bin
3	---pkg version check---
4	file ver: 0.4.4
5

Firmware file created! C:/Users/decid/Documents/projects/beyond_synaptics/dist\Beyond_AppImage_v044_release.hex
Firmware file created! C:/Users/decid/Documents/projects/beyond_synaptics/dist\Beyond_AppImage_v044_release.bin
---pkg version check---
file ver: 0.4.4


Prompt:
<task-notification>
<task-id>bhj0je3gh</task-id>
<tool-use-id>toolu_01Q22xCyukvcwTbBELpyBgvs</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-sauna\d3fb9be1-d61d-4f88-b77c-2a2a282b11fd\tasks\bhj0je3gh.output</output-file>
<status>completed</status>
<summary>Background command "Package 0.4.4 release (fixed cd)" completed (exit code 0)</summary>
</task-notification>


0.4.4 release packaged (`latest_0.4.4_release.beyondfw`, verified 0.4.4). Flashing as final HMD state.


cd C:/Users/decid/Documents/projects/beyond_synaptics/dist && py flash_variant.py "C:/Users/decid/Documents/projects/beyond_synaptics/dist/latest_0.4.4_release.beyondfw" "0.4.4" 2>&1 | tail -15

Flash 0.4.4 release to HMD


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


Flash running. Waiting for verify.


<task-notification>
<task-id>b6d8mx7jr</task-id>
<tool-use-id>toolu_01R4EHLHq9gS8JBWVVbEdzgX</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-sauna\d3fb9be1-d61d-4f88-b77c-2a2a282b11fd\tasks\b6d8mx7jr.output</output-file>
<status>completed</status>
<summary>Background command "Flash 0.4.4 release to HMD" completed (exit code 0)</summary>
</task-notification>


1	flashing latest_0.4.4_release.beyondfw -> 73152 bytes @ 0x00404000, expect 0.4.4
2	pre-flash version: 0.4.7
3	attempt 1: app->bootloader
4	attempt 1: programming via \\?\HID#VID_35BD&PID_4004#c&2dbb630a&2&0000#{4
5	Programming firmware...
6	attempt 1: Write error at address 0x00404F80, code: 46 (code-46 transient expected on 1st)
7	attempt 2: programming via \\?\HID#VID_35BD&PID_4004#c&2dbb630a&2&0000#{4
8	Programming firmware...
9	attempt 2: programming OK
10	=== verify ===
11	post-flash version: 0.4.4
12	RESULT: OK
13

flashing latest_0.4.4_release.beyondfw -> 73152 bytes @ 0x00404000, expect 0.4.4
pre-flash version: 0.4.7
attempt 1: app->bootloader
attempt 1: programming via \\?\HID#VID_35BD&PID_4004#c&2dbb630a&2&0000#{4
Programming firmware...
attempt 1: Write error at address 0x00404F80, code: 46 (code-46 transient expected on 1st)
attempt 2: programming via \\?\HID#VID_35BD&PID_4004#c&2dbb630a&2&0000#{4
Programming firmware...
attempt 2: programming OK
=== verify ===
post-flash version: 0.4.4
RESULT: OK


0.4.4 release flashed + verified (73,152 B — identical config to validated `fullfix`). One final session test on the *exact* shipped build:


cd C:/Users/decid/Documents/projects/beyond_synaptics/dist && py session_test.py 2>&1 | tail -14

Final session test on 0.4.4 release


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


Final validation running. Checking the staged fix diff while it runs.


cd C:/Users/decid/Documents/projects/beyond_synaptics && echo "===branch + status===" && git rev-parse --abbrev-ref HEAD && git status --short -- CMakeLists.txt src/sw_ver.h && echo "" && echo "===diff (the fix)===" && git diff -- CMakeLists.txt src/sw_ver.h

Show firmware fix diff


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


<task-notification>
<task-id>bfmxzypr9</task-id>
<tool-use-id>toolu_01FMqwmn67u7fjeC2RPjgcNZ</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-sauna\d3fb9be1-d61d-4f88-b77c-2a2a282b11fd\tasks\bfmxzypr9.output</output-file>
<status>completed</status>
<summary>Background command "Show firmware fix diff" completed (exit code 0)</summary>
</task-notification>


1	===branch + status===
2	doze-warm-sleep
3	 M CMakeLists.txt
4	 M src/sw_ver.h
5
6	===diff (the fix)===
7	diff --git a/CMakeLists.txt b/CMakeLists.txt
8	index 653459b..8c306f9 100644
9	--- a/CMakeLists.txt
10	+++ b/CMakeLists.txt
11	@@ -17,15 +17,19 @@ set(COMPILER_FLAGS -pipe -fno-strict-aliasing -Wall -Wstrict-prototypes -Wmissin
12	  -Wfloat-equal -Wundef -Wshadow -Wbad-function-cast -Wwrite-strings -Wsign-compare
13	  -Waggregate-return -Wmissing-declarations -Wformat -Wmissing-format-attribute
14	  -Wno-deprecated-declarations -Wpacked -Wredundant-decls -Wnested-externs -Wlong-long
15	- -Wunreachable-code -Wcast-align --param max-inline-insns-single=500
16	- -ffast-math -mlong-calls)
17	+ -Wunreachable-code -Wcast-align --param max-inline-insns-single=500)
18	+# NOTE: -mlong-calls REMOVED (0.4.4) — it wedged the MCU under SteamVR addon
19	+# load (BeyondEyetracking/BeyondSteamVR co-driving 0101); hardware-isolated as
20	+# the sole cause. -ffast-math also removed (unsafe FP / -ffinite-math-only kills
21	+# NaN guards; its crtfastmath FPSCR init was gc-sections'd out anyway). The
22	+# official Studio .cproj uses neither flag.
23
24
25	 set(LINKER_FILE "${CMAKE_CURRENT_LIST_DIR}/src/ASF/sam/utils/linker_scripts/samg/samg55j19/gcc/flash_usb_bootloader.ld")
26
27	 add_compile_options(${CPU_FLAGS} ${COMPILER_FLAGS})
28	 add_link_options(${CPU_FLAGS})
29	-add_definitions(-D__SAMG55G19__ -DARM_MATH_CM4=true -Dprintf=iprintf -D__FREERTOS__ -Dscanf=iscanf -DBOARD=USER_EXT_BOARD -DUDD_ENABLE -DNOTBOARD=SAMG55_XPLAINED_PRO -DUSER_BOARD_REVISION=4)
30	+add_definitions(-D__SAMG55J19__ -DARM_MATH_CM4=true -Dprintf=iprintf -D__FREERTOS__ -Dscanf=iscanf -DBOARD=USER_EXT_BOARD -DUDD_ENABLE -DNOTBOARD=SAMG55_XPLAINED_PRO -DUSER_BOARD_REVISION=4)
31
32	 include(utils.cmake)
33
34	diff --git a/src/sw_ver.h b/src/sw_ver.h
35	index 7878e62..00855a1 100644
36	--- a/src/sw_ver.h
37	+++ b/src/sw_ver.h
38	@@ -4,7 +4,10 @@
39
40	 #define MAJOR_VERSION		0
41	 #define MINOR_VERSION		4
42	-#define PATCH_VERSION		3
43	+#define PATCH_VERSION		4
44	+// 0.4.4: build fix — drop -mlong-calls (wedged the MCU under SteamVR addon load,
45	+// hardware-isolated as sole cause) + drop -ffast-math + fix __SAMG55G19__ typo
46	+// to __SAMG55J19__. CMakeLists/toolchain only; no firmware logic change.
47	 // 0.4.0: doze warm-shallow-sleep (ADR-0005) - display-sleep/wake HID pair
48	 // ('H'/'h'), doze-latch, fan kickstart. Host capability-gates doze on >= 0.4.0.
49	 // 0.4.1: gamma-eased doze brightness sweep (perceptually even fade/wake).
50

===branch + status===
doze-warm-sleep
 M CMakeLists.txt
 M src/sw_ver.h

===diff (the fix)===
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 653459b..8c306f9 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -17,15 +17,19 @@ set(COMPILER_FLAGS -pipe -fno-strict-aliasing -Wall -Wstrict-prototypes -Wmissin
  -Wfloat-equal -Wundef -Wshadow -Wbad-function-cast -Wwrite-strings -Wsign-compare
  -Waggregate-return -Wmissing-declarations -Wformat -Wmissing-format-attribute
  -Wno-deprecated-declarations -Wpacked -Wredundant-decls -Wnested-externs -Wlong-long
- -Wunreachable-code -Wcast-align --param max-inline-insns-single=500
- -ffast-math -mlong-calls)
+ -Wunreachable-code -Wcast-align --param max-inline-insns-single=500)
+# NOTE: -mlong-calls REMOVED (0.4.4) — it wedged the MCU under SteamVR addon
+# load (BeyondEyetracking/BeyondSteamVR co-driving 0101); hardware-isolated as
+# the sole cause. -ffast-math also removed (unsafe FP / -ffinite-math-only kills
+# NaN guards; its crtfastmath FPSCR init was gc-sections'd out anyway). The
+# official Studio .cproj uses neither flag.

 set(LINKER_FILE "${CMAKE_CURRENT_LIST_DIR}/src/ASF/sam/utils/linker_scripts/samg/samg55j19/gcc/flash_usb_bootloader.ld")

 add_compile_options(${CPU_FLAGS} ${COMPILER_FLAGS})
 add_link_options(${CPU_FLAGS})
-add_definitions(-D__SAMG55G19__ -DARM_MATH_CM4=true -Dprintf=iprintf -D__FREERTOS__ -Dscanf=iscanf -DBOARD=USER_EXT_BOARD -DUDD_ENABLE -DNOTBOARD=SAMG55_XPLAINED_PRO -DUSER_BOARD_REVISION=4)
+add_definitions(-D__SAMG55J19__ -DARM_MATH_CM4=true -Dprintf=iprintf -D__FREERTOS__ -Dscanf=iscanf -DBOARD=USER_EXT_BOARD -DUDD_ENABLE -DNOTBOARD=SAMG55_XPLAINED_PRO -DUSER_BOARD_REVISION=4)

 include(utils.cmake)

diff --git a/src/sw_ver.h b/src/sw_ver.h
index 7878e62..00855a1 100644
--- a/src/sw_ver.h
+++ b/src/sw_ver.h
@@ -4,7 +4,10 @@

 #define MAJOR_VERSION		0
 #define MINOR_VERSION		4
-#define PATCH_VERSION		3
+#define PATCH_VERSION		4
+// 0.4.4: build fix — drop -mlong-calls (wedged the MCU under SteamVR addon load,
+// hardware-isolated as sole cause) + drop -ffast-math + fix __SAMG55G19__ typo
+// to __SAMG55J19__. CMakeLists/toolchain only; no firmware logic change.
 // 0.4.0: doze warm-shallow-sleep (ADR-0005) - display-sleep/wake HID pair
 // ('H'/'h'), doze-latch, fan kickstart. Host capability-gates doze on >= 0.4.0.
 // 0.4.1: gamma-eased doze brightness sweep (perceptually even fade/wake).


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


Fix diff clean on `doze-warm-sleep`. Waiting on the final shipped-build validation verdict.


<system-reminder>Warning: the file exists but is shorter than the provided offset (1). The file has 1 lines.</system-reminder>


Still running. Awaiting completion.


<task-notification>
<task-id>bc6ubc0a7</task-id>
<tool-use-id>toolu_01B7aiorTUQCwdUXHELyEprv</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-sauna\d3fb9be1-d61d-4f88-b77c-2a2a282b11fd\tasks\bc6ubc0a7.output</output-file>
<status>completed</status>
<summary>Background command "Final session test on 0.4.4 release" completed (exit code 0)</summary>
</task-notification>


<owl_reachability_notice>
You have an active owl perch. When spawning subagents or doing work, use run_in_background: true so you stay reachable for incoming messages.
</owl_reachability_notice>


CAVEMAN MODE ACTIVE (full). Drop articles/filler/pleasantries/hedging. Fragments OK. Code/commits/security: write normal.


1	PRE-SESSION MCU: 0.4.4
2	launching SteamVR (appid 250820)...
3	  SteamVR up after ~2s: ['vrserver.exe']
4	session running 45s (addons co-driving MCU)...
5	MID-SESSION MCU: 0.4.4
6	session running another 30s...
7	quitting SteamVR...
8	  force-killing remaining: ['vrserver.exe', 'vrmonitor.exe']
9	  post-quit procs: []
10	waiting 6s for addons to release 0101...
11	POST-EXIT MCU (attempt 1): 0.4.4
12	POST-EXIT MCU (attempt 2): 0.4.4
13	=== VERDICT: ALIVE - NO WEDGE ===
14

PRE-SESSION MCU: 0.4.4
launching SteamVR (appid 250820)...
  SteamVR up after ~2s: ['vrserver.exe']
session running 45s (addons co-driving MCU)...
MID-SESSION MCU: 0.4.4
session running another 30s...
quitting SteamVR...
  force-killing remaining: ['vrserver.exe', 'vrmonitor.exe']
  post-quit procs: []
waiting 6s for addons to release 0101...
POST-EXIT MCU (attempt 1): 0.4.4
POST-EXIT MCU (attempt 2): 0.4.4
=== VERDICT: ALIVE - NO WEDGE ===


**0.4.4 release SURVIVES — final validation passed.** HMD now runs official 0.4.4, alive through full SteamVR + addon session + exit.

🎯 **Bug solved end-to-end.** Marking resolved + cleaning up.


Edit

description: 0.4.x firmware wedges the Beyond when any SteamVR Beyond-addon runs — prox dies, brightness sticks ~7, power-cycle to recover

description: RESOLVED — 0.4.x SteamVR-addon MCU wedge was the CMake flag -mlong-calls; fixed in 0.4.4 (removed -mlong-calls + -ffast-math, fixed J19 define), hardware-validated


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)

---
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 FOUND & HARDWARE-CONFIRMED 2026-06-15: the CMake build flag `-mlong-calls`** (in `beyond_synaptics/CMakeLists.txt` COMPILER_FLAGS; the official Studio `.cproj` has NO such flag). Under SteamVR-addon load it wedges the MCU. NOT a firmware-source bug — the doze source is inert without the doze HID opcodes (addons never send 0x48/0x68). Isolated by building 7 single-variable variants + on-HMD A/B flash/SteamVR-session testing (harness validated against official 0.3.18 which survives): perfect correlation — every variant WITH `-mlong-calls` wedges (nonano, nofastmath), every variant WITHOUT it survives (matchofficial, nomlong, official 0.3.18). `-ffast-math` (also CMake-added, crtfastmath FPSCR-set gc'd out) and `__SAMG55G19__` typo and newlib-nano were each ruled out (nomlong keeps nano+G19, only drops -mlong-calls → survives). FIX: remove `-mlong-calls` from CMakeLists (also remove `-ffast-math` = FP footgun, fix `__SAMG55G19__`→`__SAMG55J19__` = correctness). Validated ship config = "fullfix". Test harness: `beyond_synaptics/dist/{flash_variant.py,session_test.py}`; wedge detector = `get_hmd_fw_version`→None post-SteamVR-exit (also dies MID-session — addon does NOT exclusively hold 0101, a live MCU ACKs mid-session). Recovery = physical power-cycle only.

ORIGINAL SYMPTOM (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.

**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).

Also ELIMINATED: v0.3.18→v0.3.19 source delta (baseline was v0.3.18; HMD jumped straight 0.3.18→0.4.3). That delta is ONLY an RGB-LED-animation feature (commit b2c696a: `report[4]` mode byte on the 'L'/0x4c cmd + LED blinking task). LED-task only, can't gate prox/HID. Not the wedge.

**LEADING HYPOTHESIS = BUILD/TOOLCHAIN (source is inert).** The 0.4.3 flashed is the headless CMake/arm-gcc-12.2 build; known-good v0.3.18 was an official build. Compared `CMakeLists.txt` vs `Beyond_Synaptics.cproj`, CMake-introduced deviations: **`-ffast-math`** (Studio has none — sets FPU FZ/default-NaN globally at startup via crtfastmath, changes ALL float behavior → PRIME suspect), `-mlong-calls`, `--specs=nano.specs`/`-lc_nano` (newlib-nano: smaller reentrancy/heap vs full newlib), and `-D__SAMG55G19__` typo (chip is J19; selects samg55g19.h CMSIS header — compile-benign since no J19-only pin macros referenced, but WRONG, fix to `__SAMG55J19__`). NOTE: `.cproj` is NOT the true production config — it has `BOARD=SAMG55_XPLAINED_PRO` but real Beyond HW init is gated `#if BOARD==USER_EXT_BOARD` (main.c 149/168/326, usart_interface.c 158), and CMake correctly uses `USER_EXT_BOARD`. So `.cproj` board/linker(`flash_usb_bootloader.ld` for AppImage) deltas are likely intentional-correct; trust the prebuilt AppImage hex (official Studio artifacts, in-repo up to `build/v0.3.9/Beyond_AppImage_v039_250328.hex`) as the real baseline, NOT `.cproj`. No prebuilt 0.3.10–0.3.19 committed.

**EXPERIMENTS DONE 2026-06-14 (build-isolation, no flash yet):**
- Track B reproduced v0.3.18 source via CMake toolchain, binary-diffed vs official 0.3.18 (`HMDUtility/src/latest.beyondfw`, the real prebuilt). Source list byte-identical to v0.3.18 `.cproj` (68 files) → reproduction faithful, delta is purely build-system. ~96% bytes differ (Studio-gcc vs arm-gcc-12.2/-O3).
- **`-ffast-math` FPSCR-flush theory DISPROVEN:** crtfastmath `set_fast_math` ctor is dropped by `-Wl,--gc-sections` (SAMG linker script doesn't KEEP its `.init_array` entry) — no `vmsr fpscr` in image, FPU stays at reset default. `-ffast-math` only changes per-TU codegen now (weaker suspect).
- **newlib-nano CONFIRMED the CMake-added deviation:** CMakeLists appends `--specs=nano.specs`→`-lc_nano`, `_impure_data`=76B; official `.cproj` has NO `--specs` → official=full newlib (`_impure_data`=288B). Nano's tiny shared reent under FreeRTOS multitask addon load = top wedge suspect (load-dependent, matches "fine in light doze bench, wedges under addon"). Linker `flash_usb_bootloader.ld` is CORRECT (official 0.3.18 also starts 0x404000).

**4 FLASHABLE VARIANTS BUILT in worktree `../bs_trackA/dist/` (all v0.4.4, start 0x404000, NOT flashed — human must flash):** `latest_nonano.beyondfw` (full newlib, else stock — single-var nano test), `latest_matchofficial.beyondfw` (full newlib + no -ffast-math + no -mlong-calls + J19 fix — best-shot fix), `latest_nofastmath.beyondfw` (nano kept, -ffast-math removed), `latest_fullfix.beyondfw` (nano kept, -ffast-math+mlong-calls removed, J19). Worktree CMakeLists.txt currently holds the matchofficial edits (uncommitted).

**FLASH DECISION TREE (each = flash + SteamVR+BeyondEyetracking+BeyondSteamVR session, NO sauna; watch prox alive / brightness not latched / no exit-wedge):** Flash `nonano` FIRST (single-var, top suspect). GONE ⇒ newlib-nano sole cause → ship 0.4.4 = drop `--specs=nano.specs` only (minimal). Still wedges ⇒ flash `matchofficial`: GONE ⇒ it's -ffast-math/-mlong-calls (bisect via `nofastmath`); still wedges ⇒ cause is gcc-12.2/-O3 codegen itself (match official gcc ver or drop to -O2). Flash via `reflash_doze.py` pattern, code-46 retry (re-target 35BD:4004), verify `get_hmd_fw_version`→"0.4.4". Don't power past a wedge — power-cycle first. Cleanup when done: `git worktree remove ../bs_trackA ../bs_v0318`. Related: [[beyond-synaptics-firmware-build]], [[hmd-bench-rig]].

-description: 0.4.x firmware wedges the Beyond when any SteamVR Beyond-addon runs — prox dies, brightness sticks ~7, power-cycle to recover

+description: RESOLVED — 0.4.x SteamVR-addon MCU wedge was the CMake flag -mlong-calls; fixed in 0.4.4 (removed -mlong-calls + -ffast-math, fixed J19 define), hardware-validated


**ROOT CAUSE FOUND & HARDWARE-CONFIRMED 2026-06-15: the CMake build flag `-mlong-calls`**

**SHIPPED 2026-06-15:** fix committed-ready on `beyond_synaptics` branch `doze-warm-sleep` (CMakeLists + sw_ver.h → 0.4.4; logic unchanged). Real 0.4.4 built, packaged (`beyond_synaptics/dist/latest_0.4.4_release.beyondfw`), flashed to HFENDULEAM, and final-validated (full SteamVR+addon session → ALIVE, no wedge). HMD now runs 0.4.4. (As of session end: source edits were uncommitted pending Brandon's go; test worktrees bs_trackA/bs_v0318 may need `git worktree remove --force`.)

**ROOT CAUSE FOUND & HARDWARE-CONFIRMED 2026-06-15: the CMake build flag `-mlong-calls`**


**SHIPPED 2026-06-15:** fix committed-ready on `beyond_synaptics` branch `doze-warm-sleep` (CMakeLists + sw_ver.h → 0.4.4; logic unchanged). Real 0.4.4 built, packaged (`beyond_synaptics/dist/latest_0.4.4_release.beyondfw`), flashed to HFENDULEAM, and final-validated (full SteamVR+addon session → ALIVE, no wedge). HMD now runs 0.4.4. (As of session end: source edits were uncommitted pending Brandon's go; test worktrees bs_trackA/bs_v0318 may need `git worktree remove --force`.)

**ROOT CAUSE FOUND & HARDWARE-CONFIRMED 2026-06-15: the CMake build flag `-mlong-calls`**

---
name: steamvr-forcekill-mcu-hang
description: RESOLVED — 0.4.x SteamVR-addon MCU wedge was the CMake flag -mlong-calls; fixed in 0.4.4 (removed -mlong-calls + -ffast-math, fixed J19 define), hardware-validated
metadata:
  node_type: memory
  type: project
  originSessionId: 92a9c1d9-a8d0-47c8-b932-02f7be39d701
---

**ROOT CAUSE FOUND & HARDWARE-CONFIRMED 2026-06-15: the CMake build flag `-mlong-calls`** (in `beyond_synaptics/CMakeLists.txt` COMPILER_FLAGS; the official Studio `.cproj` has NO such flag). Under SteamVR-addon load it wedges the MCU. NOT a firmware-source bug — the doze source is inert without the doze HID opcodes (addons never send 0x48/0x68). Isolated by building 7 single-variable variants + on-HMD A/B flash/SteamVR-session testing (harness validated against official 0.3.18 which survives): perfect correlation — every variant WITH `-mlong-calls` wedges (nonano, nofastmath), every variant WITHOUT it survives (matchofficial, nomlong, official 0.3.18). `-ffast-math` (also CMake-added, crtfastmath FPSCR-set gc'd out) and `__SAMG55G19__` typo and newlib-nano were each ruled out (nomlong keeps nano+G19, only drops -mlong-calls → survives). FIX: remove `-mlong-calls` from CMakeLists (also remove `-ffast-math` = FP footgun, fix `__SAMG55G19__`→`__SAMG55J19__` = correctness). Validated ship config = "fullfix". Test harness: `beyond_synaptics/dist/{flash_variant.py,session_test.py}`; wedge detector = `get_hmd_fw_version`→None post-SteamVR-exit (also dies MID-session — addon does NOT exclusively hold 0101, a live MCU ACKs mid-session). Recovery = physical power-cycle only.

ORIGINAL SYMPTOM (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.

**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).

Also ELIMINATED: v0.3.18→v0.3.19 source delta (baseline was v0.3.18; HMD jumped straight 0.3.18→0.4.3). That delta is ONLY an RGB-LED-animation feature (commit b2c696a: `report[4]` mode byte on the 'L'/0x4c cmd + LED blinking task). LED-task only, can't gate prox/HID. Not the wedge.

**LEADING HYPOTHESIS = BUILD/TOOLCHAIN (source is inert).** The 0.4.3 flashed is the headless CMake/arm-gcc-12.2 build; known-good v0.3.18 was an official build. Compared `CMakeLists.txt` vs `Beyond_Synaptics.cproj`, CMake-introduced deviations: **`-ffast-math`** (Studio has none — sets FPU FZ/default-NaN globally at startup via crtfastmath, changes ALL float behavior → PRIME suspect), `-mlong-calls`, `--specs=nano.specs`/`-lc_nano` (newlib-nano: smaller reentrancy/heap vs full newlib), and `-D__SAMG55G19__` typo (chip is J19; selects samg55g19.h CMSIS header — compile-benign since no J19-only pin macros referenced, but WRONG, fix to `__SAMG55J19__`). NOTE: `.cproj` is NOT the true production config — it has `BOARD=SAMG55_XPLAINED_PRO` but real Beyond HW init is gated `#if BOARD==USER_EXT_BOARD` (main.c 149/168/326, usart_interface.c 158), and CMake correctly uses `USER_EXT_BOARD`. So `.cproj` board/linker(`flash_usb_bootloader.ld` for AppImage) deltas are likely intentional-correct; trust the prebuilt AppImage hex (official Studio artifacts, in-repo up to `build/v0.3.9/Beyond_AppImage_v039_250328.hex`) as the real baseline, NOT `.cproj`. No prebuilt 0.3.10–0.3.19 committed.

**EXPERIMENTS DONE 2026-06-14 (build-isolation, no flash yet):**
- Track B reproduced v0.3.18 source via CMake toolchain, binary-diffed vs official 0.3.18 (`HMDUtility/src/latest.beyondfw`, the real prebuilt). Source list byte-identical to v0.3.18 `.cproj` (68 files) → reproduction faithful, delta is purely build-system. ~96% bytes differ (Studio-gcc vs arm-gcc-12.2/-O3).
- **`-ffast-math` FPSCR-flush theory DISPROVEN:** crtfastmath `set_fast_math` ctor is dropped by `-Wl,--gc-sections` (SAMG linker script doesn't KEEP its `.init_array` entry) — no `vmsr fpscr` in image, FPU stays at reset default. `-ffast-math` only changes per-TU codegen now (weaker suspect).
- **newlib-nano CONFIRMED the CMake-added deviation:** CMakeLists appends `--specs=nano.specs`→`-lc_nano`, `_impure_data`=76B; official `.cproj` has NO `--specs` → official=full newlib (`_impure_data`=288B). Nano's tiny shared reent under FreeRTOS multitask addon load = top wedge suspect (load-dependent, matches "fine in light doze bench, wedges under addon"). Linker `flash_usb_bootloader.ld` is CORRECT (official 0.3.18 also starts 0x404000).

**4 FLASHABLE VARIANTS BUILT in worktree `../bs_trackA/dist/` (all v0.4.4, start 0x404000, NOT flashed — human must flash):** `latest_nonano.beyondfw` (full newlib, else stock — single-var nano test), `latest_matchofficial.beyondfw` (full newlib + no -ffast-math + no -mlong-calls + J19 fix — best-shot fix), `latest_nofastmath.beyondfw` (nano kept, -ffast-math removed), `latest_fullfix.beyondfw` (nano kept, -ffast-math+mlong-calls removed, J19). Worktree CMakeLists.txt currently holds the matchofficial edits (uncommitted).

**FLASH DECISION TREE (each = flash + SteamVR+BeyondEyetracking+BeyondSteamVR session, NO sauna; watch prox alive / brightness not latched / no exit-wedge):** Flash `nonano` FIRST (single-var, top suspect). GONE ⇒ newlib-nano sole cause → ship 0.4.4 = drop `--specs=nano.specs` only (minimal). Still wedges ⇒ flash `matchofficial`: GONE ⇒ it's -ffast-math/-mlong-calls (bisect via `nofastmath`); still wedges ⇒ cause is gcc-12.2/-O3 codegen itself (match official gcc ver or drop to -O2). Flash via `reflash_doze.py` pattern, code-46 retry (re-target 35BD:4004), verify `get_hmd_fw_version`→"0.4.4". Don't power past a wedge — power-cycle first. Cleanup when done: `git worktree remove ../bs_trackA ../bs_v0318`. Related: [[beyond-synaptics-firmware-build]], [[hmd-bench-rig]].

+**SHIPPED 2026-06-15:** fix committed-ready on `beyond_synaptics` branch `doze-warm-sleep` (CMakeLists + sw_ver.h → 0.4.4; logic unchanged). Real 0.4.4 built, packaged (`beyond_synaptics/dist/latest_0.4.4_release.beyondfw`), flashed to HFENDULEAM, and final-validated (full SteamVR+addon session → ALIVE, no wedge). HMD now runs 0.4.4. (As of session end: source edits were uncommitted pending Brandon's go; test worktrees bs_trackA/bs_v0318 may need `git worktree remove --force`.)


Bash

cd C:/Users/decid/Documents/projects/beyond_synaptics && git worktree remove --force ../bs_trackA 2>&1; git worktree remove --force ../bs_v0318 2>&1; echo "---remaining worktrees---" && git worktree list

Remove test worktrees


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


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


1	---remaining worktrees---
2	C:/Users/decid/Documents/projects/beyond_synaptics  67bcafd [doze-warm-sleep]
3

---remaining worktrees---
C:/Users/decid/Documents/projects/beyond_synaptics  67bcafd [doze-warm-sleep]


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


Worktrees cleaned. **Done.**

## Summary — SteamVR-addon MCU wedge SOLVED

**Root cause:** `-mlong-calls` in `beyond_synaptics/CMakeLists.txt` — a CMake-build flag never in the official Studio build. Wedges the MCU under SteamVR-addon (`BeyondEyetracking`/`BeyondSteamVR`) load. **Not a firmware-logic bug** — doze source is inert without the doze HID opcodes the addons never send.

**How pinned** (7 variants, on-HMD A/B, harness validated vs known-good 0.3.18):

| build | `-mlong-calls` | result |
|---|---|---|
| official 0.3.18 (control) | no | survive |
| nonano / nofastmath | **yes** | **wedge** |
| matchofficial / nomlong / fullfix / **0.4.4** | no | survive |

5/5 perfect correlation. `-ffast-math`, `__SAMG55G19__` typo, newlib-nano all ruled out.

**Fix shipped (0.4.4):** dropped `-mlong-calls` (the fix) + `-ffast-math` (FP footgun) + fixed `__SAMG55G19__`→`J19` (typo). Logic untouched. Built, packaged (`dist/latest_0.4.4_release.beyondfw`), flashed → **HMD now on 0.4.4, validated alive through a full addon session.**

**Wrap's theory was wrong** — it wasn't a firmware HID-dispatch/doze regression, and sauna was indeed blameless. The uncommitted sauna `suspend/resume` MCU code attacked a non-bug; lean toward reverting it (separate decision).

**Open:** the firmware fix is **uncommitted** on `doze-warm-sleep` (`CMakeLists.txt` + `src/sw_ver.h`). Per your call:


AskUserQuestion

Question: Commit the 0.4.4 firmware fix?

Choices:
Commit CMakeLists.txt + sw_ver.h (0.4.4) on the current branch with the root-cause explanation. No push.
git commit -m '0.4.4: drop -mlong-calls\n(SteamVR-addon MCU wedge fix)\n+ -ffast-math, J19 typo'
branch: doze-warm-sleep
Commit and push doze-warm-sleep to origin.
git commit + git push
origin/doze-warm-sleep
Leave working-tree changes for you to review/commit yourself.

Question: Sauna uncommitted suspend/resume MCU code (mcu_prox + spatial_light)?

Choices:
It targeted the 0101 double-open non-bug; real cause was firmware -mlong-calls. Discard the uncommitted sauna changes.
git restore src/device/mcu_prox.* \
  src/app/spatial_light.cpp
(sauna repo)
Leave the sauna suspend/resume changes in place for now; revisit separately.


Your questions have been answered: "Commit the 0.4.4 firmware fix?"="Commit + push" selected preview:
git commit + git push
origin/doze-warm-sleep, "Sauna uncommitted suspend/resume MCU code (mcu_prox + spatial_light)?"=(no option selected) notes: keep and commit. we can revert later if it proves problematic in some way.. You can now continue with these answers in mind.

Question: Commit the 0.4.4 firmware fix?

Choices:
Commit CMakeLists.txt + sw_ver.h (0.4.4) on the current branch with the root-cause explanation. No push.
git commit -m '0.4.4: drop -mlong-calls\n(SteamVR-addon MCU wedge fix)\n+ -ffast-math, J19 typo'
branch: doze-warm-sleep
Commit and push doze-warm-sleep to origin.
git commit + git push
origin/doze-warm-sleep
Leave working-tree changes for you to review/commit yourself.

Question: Sauna uncommitted suspend/resume MCU code (mcu_prox + spatial_light)?

Choices:
It targeted the 0101 double-open non-bug; real cause was firmware -mlong-calls. Discard the uncommitted sauna changes.
git restore src/device/mcu_prox.* \
  src/app/spatial_light.cpp
(sauna repo)
Leave the sauna suspend/resume changes in place for now; revisit separately.


Committing + pushing firmware fix; committing sauna coexistence code (no push). Firmware first:


Bash

cd C:/Users/decid/Documents/projects/beyond_synaptics && git add CMakeLists.txt src/sw_ver.h && git commit -F - <<'EOF'
0.4.4: drop -mlong-calls — fixes SteamVR-addon MCU wedge

-mlong-calls (CMake-only; never in the official Studio .cproj) wedged the MCU
under SteamVR-addon load (BeyondEyetracking/BeyondSteamVR co-driving 0101):
MCU stops ACKing mid-session, displays stuck dark, power-cycle only recovery.
Hardware-isolated as the sole cause via 7-variant on-HMD A/B testing (validated
against known-good 0.3.18). Not a firmware-logic regression — the doze source
is inert without the doze HID opcodes the addons never send.

Also dropped -ffast-math (unsafe FP / -ffinite-math-only; its crtfastmath FPSCR
init was gc-sections'd out anyway) and fixed the __SAMG55G19__ typo ->
__SAMG55J19__. CMakeLists/toolchain only; no firmware logic change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
EOF
echo "---push---" && git push -u origin doze-warm-sleep 2>&1 | tail -8

Commit + push firmware 0.4.4 fix


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


<task-notification>
<task-id>bnd79t336</task-id>
<tool-use-id>toolu_011Vq7BtJm1Ji7BbXa29ufa4</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-sauna\d3fb9be1-d61d-4f88-b77c-2a2a282b11fd\tasks\bnd79t336.output</output-file>
<status>completed</status>
<summary>Background command "Commit + push firmware 0.4.4 fix" completed (exit code 0)</summary>
</task-notification>


1	[doze-warm-sleep f805163] 0.4.4: drop -mlong-calls — fixes SteamVR-addon MCU wedge
2	 2 files changed, 11 insertions(+), 4 deletions(-)
3	---push---
4	remote:
5	remote: Create a pull request for 'doze-warm-sleep' on GitHub by visiting:
6	remote:      https://github.com/BigscreenVR/beyond_synaptics/pull/new/doze-warm-sleep
7	remote:
8	branch 'doze-warm-sleep' set up to track 'origin/doze-warm-sleep'.
9	To https://github.com/BigscreenVR/beyond_synaptics.git
10	 * [new branch]      doze-warm-sleep -> doze-warm-sleep
11

[doze-warm-sleep f805163] 0.4.4: drop -mlong-calls — fixes SteamVR-addon MCU wedge
 2 files changed, 11 insertions(+), 4 deletions(-)
---push---
remote:
remote: Create a pull request for 'doze-warm-sleep' on GitHub by visiting:
remote:      https://github.com/BigscreenVR/beyond_synaptics/pull/new/doze-warm-sleep
remote:
branch 'doze-warm-sleep' set up to track 'origin/doze-warm-sleep'.
To https://github.com/BigscreenVR/beyond_synaptics.git
 * [new branch]      doze-warm-sleep -> doze-warm-sleep


Prompt:
<task-notification>
<task-id>bnd79t336</task-id>
<tool-use-id>toolu_011Vq7BtJm1Ji7BbXa29ufa4</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-sauna\d3fb9be1-d61d-4f88-b77c-2a2a282b11fd\tasks\bnd79t336.output</output-file>
<status>completed</status>
<summary>Background command "Commit + push firmware 0.4.4 fix" completed (exit code 0)</summary>
</task-notification>


Firmware committed (`f805163`) + pushed → `origin/doze-warm-sleep`. Now committing sauna coexistence code (3 source files only, not the stray PNGs/timelapse; no push):


cd C:/Users/decid/Documents/projects/sauna && git add src/app/spatial_light.cpp src/device/mcu_prox.cpp src/device/mcu_prox.h && git commit -F - <<'EOF'
mcu_prox: suspend/resume 0101 for SteamVR coexistence

Release the 0101 MCU handle while SteamVR owns the headset and reopen + settle
after it exits, so sauna and SteamVR's BeyondProximity driver don't both hold
the interface. reclaim re-reads worn() only after resume() so a suspended-prox
stale not-worn doesn't mis-route the reclaim.

Note: the 0.4.x SteamVR-addon MCU wedge turned out to be a separate firmware
build bug (-mlong-calls, fixed in fw 0.4.4), NOT this double-open path. Keeping
this as defensive coexistence; revert if it proves problematic.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
EOF
echo "---status---" && git log --oneline -1 && git status --short

Commit sauna coexistence code (no push)


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


<task-notification>
<task-id>b6bthd3ac</task-id>
<tool-use-id>toolu_016br3e5zYboWkufnQ8uzUfR</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-sauna\d3fb9be1-d61d-4f88-b77c-2a2a282b11fd\tasks\b6bthd3ac.output</output-file>
<status>completed</status>
<summary>Background command "Commit sauna coexistence code (no push)" completed (exit code 0)</summary>
</task-notification>


1	warning: in the working copy of 'src/app/spatial_light.cpp', LF will be replaced by CRLF the next time Git touches it
2	warning: in the working copy of 'src/device/mcu_prox.cpp', LF will be replaced by CRLF the next time Git touches it
3	warning: in the working copy of 'src/device/mcu_prox.h', LF will be replaced by CRLF the next time Git touches it
4	[main 0c3c77d] mcu_prox: suspend/resume 0101 for SteamVR coexistence
5	 3 files changed, 60 insertions(+), 5 deletions(-)
6	---status---
7	0c3c77d mcu_prox: suspend/resume 0101 for SteamVR coexistence
8	?? .screen-timelapse/
9	?? dist_doze_optic_strip.png
10	?? dist_doze_ramp_compare.png
11

warning: in the working copy of 'src/app/spatial_light.cpp', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'src/device/mcu_prox.cpp', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'src/device/mcu_prox.h', LF will be replaced by CRLF the next time Git touches it
[main 0c3c77d] mcu_prox: suspend/resume 0101 for SteamVR coexistence
 3 files changed, 60 insertions(+), 5 deletions(-)
---status---
0c3c77d mcu_prox: suspend/resume 0101 for SteamVR coexistence
?? .screen-timelapse/
?? dist_doze_optic_strip.png
?? dist_doze_ramp_compare.png


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


Both committed. **Complete.**

## Shipped
- **Firmware** `f805163` on `doze-warm-sleep` → **pushed** to `origin` (PR link: `github.com/BigscreenVR/beyond_synaptics/pull/new/doze-warm-sleep`). 0.4.4: dropped `-mlong-calls` + `-ffast-math`, fixed J19.
- **Sauna** `0c3c77d` on `main` (committed, **not pushed** per your call) — suspend/resume 0101 coexistence code, with a note it's defensive (not the wedge fix), revertable later.
- Stray `dist_doze_*.png` + `.screen-timelapse/` left untracked (not part of either fix).

## HMD state
Running official **0.4.4**, validated alive through a full SteamVR + addon session. No power-cycle needed going forward.

## The whole arc
Wrap said "firmware HID-dispatch/doze regression, sauna blameless." Half right: sauna *was* blameless, but it wasn't doze logic or HID dispatch — it was a **toolchain build flag** (`-mlong-calls`) the headless CMake build added that the official Studio build never had. Inert source, broken build. Found by elimination + 7-variant on-hardware A/B against a known-good 0.3.18 control.

Memory updated → RESOLVED. Test harness (`flash_variant.py`, `session_test.py`) left in `beyond_synaptics/dist/` if you want it again.


<command-message>spt:commune</command-message>
<command-name>/spt:commune</command-name>
<command-args>with immediate next steps and broad summary of the project's status + end goal. we'll resume in the next session</command-args>


Base directory for this skill: C:\Users\decid\.ccs\instances\bigscreen\plugins\cache\cplugs\spt\1.11.25\skills\commune

# /spt:commune

All commands use `$LIVE` env var, auto-injected by the plugin's SessionStart hook. If commands fail with "command not found", restart the Claude Code session so the SessionStart hook re-runs.

> **Identity auto-detection:** Your identity is auto-detected from your session. Pass your ID explicitly only if auto-detection fails.

## Flow

1. **Download Psyche's *current* context** — it shifts during the session as background echo communes fire. Re-download right before composing; the gen-N snapshot you read at session start is already stale:
   ```bash
   $LIVE psyche-download <your-id>
   ```

   > **Output truncation.** If the Bash tool reports `Output too large (N KB)` and saves the full result to a `tool-results/<id>.txt` file, use the `Read` tool on that saved path to access the full content. Do NOT re-run `psyche-download` to grep/awk out sections — repeated invocations re-download the same context, waste tokens, and may duplicate `## Pending Commune` sections in `live_context.md`.

2. **Diff your knowledge against this fresh download** — only what's missing from *it* is the real delta (new work, decisions, intentions). Anything already absorbed is not. If the download includes a `<memformat>` section, use it as a guide for what topics to cover. A `## Pending Commune (uncommitted)` section means a previous commune is still queued — do NOT re-send it.

3. **Commune only the delta** -- use the **Write tool** to create
   `.claude/{your-id}-commune.md` with the two-slice body as its contents
   (see `## Two-slice body shape (Phase 25 D-10/D-11)` below for the required
   envelope wrapping). Do not use Bash/heredoc -- Write is the canonical path:
   no shell escaping, no `EOF` collision risk, and Write auto-creates the
   `.claude/` parent directory.

   Your Self listener detects the file on its next poll iteration, notifies
   your Psyche wrapper, and the wrapper ingests the content via the existing
   Psyche session. On success (subprocess exit code 0) the wrapper deletes the
   file. On error (token rate limit, etc) the file is retained and retried on
   the next consume cycle. If you write again before the wrapper consumes, the
   latest write wins (last-write-wins overwrite semantics).

If Psyche's context is already current, no commune is needed. Say so and move on.

Read `commune.md` (in this skill directory) for the full commune protocol -- when to send, what to include, and diligence triggers.

**Agent-type guard:** Only works on live agents. If target is not live, refuses with: "Communes require a live agent with Psyche."

## Two-slice body shape (Phase 25 D-10/D-11)

The body you write in `.claude/{your-id}-commune.md` MUST itself be wrapped in two-slice envelopes per Phase 25 D-10/D-11. The taxonomy — what belongs in `<live-context>` vs `<project-context>` — is defined canonically in `psyche.md` §`<output_envelope>` (lines 282–323). Read that section as the source of truth; this skill doc teaches only when and how to apply it.

**In-project detection rule (D-25.1-04).** Before composing your commune body, run `$LIVE psyche-download <your-id>` (Step 1 of `## Flow` above) and inspect the `<current ... />` tag at the top of the output. The `<current/>` tag carries a `project="..."` attribute whenever any payload is produced — its presence means you are inside a tracked project (inside a git repo, `project` is the repo name; outside a git repo per D-11, `project` is the cwd-basename fallback, which still counts as a project context for routing purposes). A `psyche-download` invocation that returns no payload at all (NO-CONTEXT exit) means no project resolved. So: `project="..."` populated on `<current/>` → emit BOTH envelopes (apply rule 1 or 2 below); no payload at all → emit ONLY `<live-context>` (rule 3). Secondary signal: the `<project-context-resolved name="..."/>` sentinel further down the output indicates the project file already has prior content — useful for spotting first-time-in-project pairings, but NOT the routing rule.

**Three rules (D-25.1-01..03):**

- **In-project, both slices populated** (marker present, you have project-specific content this cycle): emit `<live-context>...</live-context>` AND `<project-context>...</project-context>`, both with non-empty bodies.
- **In-project, no project-specific update this cycle** (marker present, but nothing project-bound to report): emit `<live-context>...</live-context>` AND `<project-context></project-context>` (empty body — the deliberate "in-project but quiet" signal per D-25.1-02). Distinguishable from "no project resolved".
- **Outside any tracked project** (marker absent): emit ONLY `<live-context>...</live-context>`. Do NOT emit an empty `<project-context>` envelope (missing tag means "no project resolved" per D-25.1-03).

**Nested envelopes.** The Phase 23 `<EVENT type="commune">` envelope (see `## Envelope shape (Phase 23 v1.8)` below) is composed by `$LIVE commune` automatically — you NEVER write `<EVENT>` tags. You write ONLY the inner two-slice body. The two envelope layers nest: outer EVENT wraps inner two-slice body.

(See `psyche.md` §`<output_envelope>` for the canonical taxonomy and per-slice content rules.)

## Envelope shape (Phase 23 v1.8)

As of v1.8 Phase 23, commune deliveries are wrapped in a typed EVENT envelope carrying a 5-field stamp (`machine`, `project`, `branch`, `head_sha`, `head_subject`); the prose `COMMUNE (timestamp): body` form is gone.

```xml
<EVENT type="commune" timestamp="2026-05-20T06:00:00-07:00" machine="hostname" project="claude_skill_owl" branch="main" head_sha="abc123..." head_subject="subject line capped at 72 chars">body</EVENT>
```

Outside a git repo (D-11), `branch`, `head_sha`, and `head_subject` are omitted entirely (not empty-string-set):

```xml
<EVENT type="commune" timestamp="2026-05-20T06:00:00-07:00" machine="hostname" project="cwd-basename">body</EVENT>
```

`$LIVE commune` composes this envelope automatically — Self does NOT manually construct EVENT XML. The `body` placeholder in the example above is the two-slice body (see `## Two-slice body shape (Phase 25 D-10/D-11)` above); the two envelope layers nest. The wrapper-side parser ignores unknown attrs, so the stamp fields are forward-compatible with older receivers. For drift detection on resume, see the `live` skill.

ARGUMENTS: with immediate next steps and broad summary of the project's status + end goal. we'll resume in the next session


<owl_reachability_notice>
You have an active owl perch. When spawning subagents or doing work, use run_in_background: true so you stay reachable for incoming messages.
</owl_reachability_notice>


CAVEMAN MODE ACTIVE (full). Drop articles/filler/pleasantries/hedging. Fragments OK. Code/commits/security: write normal.


/spt:commune with immediate next steps and broad summary of the project's status + end goal. we'll resume in the next session


$LIVE psyche-download solar 2>&1 | tail -60

Download current Psyche context


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


<task-notification>
<task-id>bi2fmakxu</task-id>
<tool-use-id>toolu_01AaRDywp9MkVVAbiw55kj4S</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-sauna\d3fb9be1-d61d-4f88-b77c-2a2a282b11fd\tasks\bi2fmakxu.output</output-file>
<status>completed</status>
<summary>Background command "Download current Psyche context" completed (exit code 0)</summary>
</task-notification>


1	<current machine="HFENDULEAM" project="sauna" branch="main" head_sha="0c3c77d3cffdedffd39af897382c4b98c8a10fe8" head_subject="mcu_prox: suspend/resume 0101 for SteamVR coexistence" commits_since="0" commits_unpulled="0"/>
2	## Completed This Cycle
3	- **nofastmath (0.4.6) test** — PASSED. Survived identical harness → **`-ffast-math` RULED OUT.**
4	- **nomlong (0.4.8) test** — PASSED. Only diff from nofastmath was `-mlong-calls` removed. **`-mlong-calls` CONFIRMED ROOT CAUSE** (5/5 perfect correlation: 0.3.18 control, nonano, matchofficial, nofastmath, nomlong).
5	- **fullfix (0.4.7, ship config)** — ALIVE, NO WEDGE. Validated end-to-end.
6	- **Real 0.4.4 release** — Built from main repo (source fix applied: removed `-mlong-calls` + `-ffast-math`, fixed J19). Packaged (`latest_0.4.4_release.beyondfw`, 73,152 B). Flashed + verified.
7	- **Final validation on shipped 0.4.4** — PASSED. HMD alive through full SteamVR + addon session + exit.
8
9	## Status
10	**BUG SOLVED.** `-mlong-calls` flag added to CMake build wedges MCU under SteamVR-addon load. Removed. HMD shipped with 0.4.4 (no `-mlong-calls`, no `-ffast-math`, J19 define fixed). Cleanup in progress.
11
12	## Blockers
13	None.<project-context-resolved name="sauna"/>
14	## Resolution Summary
15	Root cause = `-mlong-calls` in `beyond_synaptics/CMakeLists.txt` (CMake-added; official Studio build never had it). Hypothesis narrowing via controlled bisection isolated flag perfectly across 5 build variants.
16
17	## Fix Applied
18	- CMakeLists.txt: `-mlong-calls` removed, `-ffast-math` removed
19	- define: J19 (was G19)
20	- Result: 0.4.4 release, validated end-to-end on hardware
21
22	## Cleanup
23	bs_trackA worktree + intermediate builds (nofastmath, nomlong, fullfix) to be removed.

<current machine="HFENDULEAM" project="sauna" branch="main" head_sha="0c3c77d3cffdedffd39af897382c4b98c8a10fe8" head_subject="mcu_prox: suspend/resume 0101 for SteamVR coexistence" commits_since="0" commits_unpulled="0"/>
## Completed This Cycle
- **nofastmath (0.4.6) test** — PASSED. Survived identical harness → **`-ffast-math` RULED OUT.**
- **nomlong (0.4.8) test** — PASSED. Only diff from nofastmath was `-mlong-calls` removed. **`-mlong-calls` CONFIRMED ROOT CAUSE** (5/5 perfect correlation: 0.3.18 control, nonano, matchofficial, nofastmath, nomlong).
- **fullfix (0.4.7, ship config)** — ALIVE, NO WEDGE. Validated end-to-end.
- **Real 0.4.4 release** — Built from main repo (source fix applied: removed `-mlong-calls` + `-ffast-math`, fixed J19). Packaged (`latest_0.4.4_release.beyondfw`, 73,152 B). Flashed + verified.
- **Final validation on shipped 0.4.4** — PASSED. HMD alive through full SteamVR + addon session + exit.

## Status
**BUG SOLVED.** `-mlong-calls` flag added to CMake build wedges MCU under SteamVR-addon load. Removed. HMD shipped with 0.4.4 (no `-mlong-calls`, no `-ffast-math`, J19 define fixed). Cleanup in progress.

## Blockers
None.<project-context-resolved name="sauna"/>
## Resolution Summary
Root cause = `-mlong-calls` in `beyond_synaptics/CMakeLists.txt` (CMake-added; official Studio build never had it). Hypothesis narrowing via controlled bisection isolated flag perfectly across 5 build variants.

## Fix Applied
- CMakeLists.txt: `-mlong-calls` removed, `-ffast-math` removed
- define: J19 (was G19)
- Result: 0.4.4 release, validated end-to-end on hardware

## Cleanup
bs_trackA worktree + intermediate builds (nofastmath, nomlong, fullfix) to be removed.


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


Psyche has the technical resolution already. Delta = commits landed (firmware pushed, sauna committed), cleanup done, plus the next-steps + end-goal framing the user requested. In-project (`project="sauna"`) → both envelopes.


Write

<live-context>
SESSION WRAP — firmware bug fully closed + both commits landed. Resuming next session.

## Delta since last echo (commits + cleanup now DONE — was "in progress")
- **Firmware committed + PUSHED**: `f805163` on `beyond_synaptics` branch `doze-warm-sleep` → pushed to `origin`. PR not yet opened; GitHub offered: https://github.com/BigscreenVR/beyond_synaptics/pull/new/doze-warm-sleep
- **Sauna committed (NOT pushed)**: `0c3c77d` on `main` — the suspend/resume 0101 SteamVR-coexistence code (mcu_prox.cpp/.h + spatial_light.cpp). Brandon's call: KEEP as defensive coexistence, revert later if problematic. It was NOT the wedge fix (firmware -mlong-calls was).
- **Worktrees removed**: bs_trackA + bs_v0318 gone (`git worktree remove --force`). Only main worktree remains.
- HMD HFENDULEAM running official **0.4.4**, validated alive through full SteamVR+addon session. No power-cycle pending.

## IMMEDIATE NEXT STEPS (next session)
1. **beyond_synaptics PR/merge decision** — doze-warm-sleep is pushed but not merged to main. Decide: open PR + merge 0.4.4 to main, or hold. (doze-warm-sleep also carries the whole M5 doze feature, not just the 0.4.4 build fix — scope the merge accordingly.)
2. **Sauna `0c3c77d` push-or-hold** — committed to main locally, not pushed. Decide push vs leave local vs revert (defensive code, non-essential now firmware is fixed).
3. **Triage sauna untracked files** — `dist_doze_optic_strip.png`, `dist_doze_ramp_compare.png`, `.screen-timelapse/` still untracked. Gitignore or remove.
4. **(Optional) propagate 0.4.4** to other rigs running doze fw + SteamVR addons (enlyzeam etc.) — only if they hit the same wedge.
5. **Resume M5/M6 sauna roadmap** — the firmware bug was a detour; main thread is the VRAM-reclaim / doze warm-sleep milestone.

## Resume aids
- Test harness kept: `beyond_synaptics/dist/{flash_variant.py, session_test.py}` (flash any .beyondfw + verify; SteamVR-session wedge detector via get_hmd_fw_version→None post-exit).
- Memory `steamvr-forcekill-mcu-hang` marked RESOLVED with full root-cause + isolation method.
</live-context>
<project-context>
## sauna — end goal & where the firmware bug fits
sauna is the Bigscreen Beyond desktop/HMD presenter built to **coexist with SteamVR** — share the Beyond HMD, reclaim VRAM, and use firmware "doze" warm-sleep for fast (~95ms) wake instead of cold 3-5s reacquire. M5 milestone = VRAM reclaim + doze HOST integration (done, committed earlier b017148). The 0.4.x SteamVR-addon MCU wedge was the last blocker to that coexistence goal on current firmware.

## Firmware wedge — RESOLVED (project milestone)
Root cause was NOT sauna and NOT doze logic: it was the build flag **`-mlong-calls`** in `beyond_synaptics/CMakeLists.txt` (added by the headless CMake build; the official Microchip Studio build never had it). It wedged the ATSAMG55 MCU under SteamVR-addon load (BeyondEyetracking/BeyondSteamVR co-driving the 0101 HID interface) — MCU stops ACKing, displays stuck dark, power-cycle-only recovery. Hardware-isolated as sole cause via 7 single-variable build variants + on-HMD A/B against a known-good 0.3.18 control (5/5 correlation with the flag). Fixed in **fw 0.4.4** (removed -mlong-calls + -ffast-math, fixed __SAMG55G19__→__SAMG55J19__ typo); zero firmware-logic change. The earlier theories (doze HID-dispatch regression, sauna/SteamVR 0101 double-open) were both wrong — sauna confirmed blameless.

## Key facts for future work
- HMDUtility (`../HMDUtility`) holds the BeyondEyetracking + BeyondSteamVR addon source; its 0101 command set is in `src/hid_handler.cpp` (no 0x48/0x68 — doze opcodes don't collide).
- Build/flash runbook: memory `beyond-synaptics-firmware-build`; code-46 first-flash retry quirk is normal.
- sauna's committed suspend/resume 0101 code (0c3c77d) is defensive only, kept pending real-world need.
</project-context>


File created successfully at: C:\Users\decid\Documents\projects\sauna\.claude\solar-commune.md (file state is current in your context — no need to Read it back)

<live-context>
SESSION WRAP — firmware bug fully closed + both commits landed. Resuming next session.

## Delta since last echo (commits + cleanup now DONE — was "in progress")
- **Firmware committed + PUSHED**: `f805163` on `beyond_synaptics` branch `doze-warm-sleep` → pushed to `origin`. PR not yet opened; GitHub offered: https://github.com/BigscreenVR/beyond_synaptics/pull/new/doze-warm-sleep
- **Sauna committed (NOT pushed)**: `0c3c77d` on `main` — the suspend/resume 0101 SteamVR-coexistence code (mcu_prox.cpp/.h + spatial_light.cpp). Brandon's call: KEEP as defensive coexistence, revert later if problematic. It was NOT the wedge fix (firmware -mlong-calls was).
- **Worktrees removed**: bs_trackA + bs_v0318 gone (`git worktree remove --force`). Only main worktree remains.
- HMD HFENDULEAM running official **0.4.4**, validated alive through full SteamVR+addon session. No power-cycle pending.

## IMMEDIATE NEXT STEPS (next session)
1. **beyond_synaptics PR/merge decision** — doze-warm-sleep is pushed but not merged to main. Decide: open PR + merge 0.4.4 to main, or hold. (doze-warm-sleep also carries the whole M5 doze feature, not just the 0.4.4 build fix — scope the merge accordingly.)
2. **Sauna `0c3c77d` push-or-hold** — committed to main locally, not pushed. Decide push vs leave local vs revert (defensive code, non-essential now firmware is fixed).
3. **Triage sauna untracked files** — `dist_doze_optic_strip.png`, `dist_doze_ramp_compare.png`, `.screen-timelapse/` still untracked. Gitignore or remove.
4. **(Optional) propagate 0.4.4** to other rigs running doze fw + SteamVR addons (enlyzeam etc.) — only if they hit the same wedge.
5. **Resume M5/M6 sauna roadmap** — the firmware bug was a detour; main thread is the VRAM-reclaim / doze warm-sleep milestone.

## Resume aids
- Test harness kept: `beyond_synaptics/dist/{flash_variant.py, session_test.py}` (flash any .beyondfw + verify; SteamVR-session wedge detector via get_hmd_fw_version→None post-exit).
- Memory `steamvr-forcekill-mcu-hang` marked RESOLVED with full root-cause + isolation method.
</live-context>
<project-context>
## sauna — end goal & where the firmware bug fits
sauna is the Bigscreen Beyond desktop/HMD presenter built to **coexist with SteamVR** — share the Beyond HMD, reclaim VRAM, and use firmware "doze" warm-sleep for fast (~95ms) wake instead of cold 3-5s reacquire. M5 milestone = VRAM reclaim + doze HOST integration (done, committed earlier b017148). The 0.4.x SteamVR-addon MCU wedge was the last blocker to that coexistence goal on current firmware.

## Firmware wedge — RESOLVED (project milestone)
Root cause was NOT sauna and NOT doze logic: it was the build flag **`-mlong-calls`** in `beyond_synaptics/CMakeLists.txt` (added by the headless CMake build; the official Microchip Studio build never had it). It wedged the ATSAMG55 MCU under SteamVR-addon load (BeyondEyetracking/BeyondSteamVR co-driving the 0101 HID interface) — MCU stops ACKing, displays stuck dark, power-cycle-only recovery. Hardware-isolated as sole cause via 7 single-variable build variants + on-HMD A/B against a known-good 0.3.18 control (5/5 correlation with the flag). Fixed in **fw 0.4.4** (removed -mlong-calls + -ffast-math, fixed __SAMG55G19__→__SAMG55J19__ typo); zero firmware-logic change. The earlier theories (doze HID-dispatch regression, sauna/SteamVR 0101 double-open) were both wrong — sauna confirmed blameless.

## Key facts for future work
- HMDUtility (`../HMDUtility`) holds the BeyondEyetracking + BeyondSteamVR addon source; its 0101 command set is in `src/hid_handler.cpp` (no 0x48/0x68 — doze opcodes don't collide).
- Build/flash runbook: memory `beyond-synaptics-firmware-build`; code-46 first-flash retry quirk is normal.
- sauna's committed suspend/resume 0101 code (0c3c77d) is defensive only, kept pending real-world need.
</project-context>


Commune written → `.claude/solar-commune.md`. Self listener will ingest + delete on next poll.

**Delta communed** (Psyche already had the technical resolution):
- Both commits landed: firmware `f805163` pushed to `origin/doze-warm-sleep`; sauna `0c3c77d` committed to main (not pushed, kept defensive).
- Worktrees cleaned.
- **Next-session steps**: (1) beyond_synaptics PR/merge decision, (2) sauna commit push-or-hold, (3) triage untracked PNGs/timelapse, (4) optional 0.4.4 propagation to other rigs, (5) resume M5/M6 roadmap.
- End-goal framing: sauna = SteamVR-coexistent Beyond presenter; firmware wedge was the last blocker, now resolved.

Both envelopes emitted (in-project: `sauna`). Ready to resume next session.


what is the absolute path of v0.4.4 `latest.beyondfw`? or has it not been packaged that way yet? please do so if not, and share the path with me


Not packaged as canonical `latest.beyondfw` yet — only `latest_0.4.4_release.beyondfw`. Creating the canonical name + verifying.


Bash

cd C:/Users/decid/Documents/projects/beyond_synaptics/dist && echo "===existing latest.beyondfw (if any)===" && (py -c "import sys; sys.path.insert(0,r'C:\Users\decid\Documents\projects\quick-beyond-updater'); import bs_hmd_tools as t,os; print('old latest.beyondfw ver:', t.get_file_fw_version('latest.beyondfw')) if os.path.exists('latest.beyondfw') else print('no existing latest.beyondfw')") && cp latest_0.4.4_release.beyondfw latest.beyondfw && echo "===copied -> verify===" && py -c "import sys; sys.path.insert(0,r'C:\Users\decid\Documents\projects\quick-beyond-updater'); import bs_hmd_tools as t,os; print('latest.beyondfw ver:', t.get_file_fw_version('latest.beyondfw'), '| size', os.path.getsize('latest.beyondfw'),'B')" && echo "ABS PATH: C:\\Users\\decid\\Documents\\projects\\beyond_synaptics\\dist\\latest.beyondfw"

Package canonical latest.beyondfw for 0.4.4


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


<task-notification>
<task-id>b1n5zq3h1</task-id>
<tool-use-id>toolu_018QyynMMWWWbRhcPboZVwza</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-sauna\d3fb9be1-d61d-4f88-b77c-2a2a282b11fd\tasks\b1n5zq3h1.output</output-file>
<status>completed</status>
<summary>Background command "Package canonical latest.beyondfw for 0.4.4" completed (exit code 0)</summary>
</task-notification>


1	===existing latest.beyondfw (if any)===
2	old latest.beyondfw ver: 0.4.3
3	===copied -> verify===
4	latest.beyondfw ver: 0.4.4 | size 73230 B
5	ABS PATH: C:\Users\decid\Documents\projects\beyond_synaptics\dist\latest.beyondfw
6

===existing latest.beyondfw (if any)===
old latest.beyondfw ver: 0.4.3
===copied -> verify===
latest.beyondfw ver: 0.4.4 | size 73230 B
ABS PATH: C:\Users\decid\Documents\projects\beyond_synaptics\dist\latest.beyondfw


Prompt:
<task-notification>
<task-id>b1n5zq3h1</task-id>
<tool-use-id>toolu_018QyynMMWWWbRhcPboZVwza</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-sauna\d3fb9be1-d61d-4f88-b77c-2a2a282b11fd\tasks\b1n5zq3h1.output</output-file>
<status>completed</status>
<summary>Background command "Package canonical latest.beyondfw for 0.4.4" completed (exit code 0)</summary>
</task-notification>


Done. Canonical `latest.beyondfw` now = 0.4.4 (was 0.4.3, overwritten):

```
C:\Users\decid\Documents\projects\beyond_synaptics\dist\latest.beyondfw
```

Verified: version **0.4.4**, 73,230 B (the validated ship config — no `-mlong-calls`, no `-ffast-math`, J19). Identical payload to `latest_0.4.4_release.beyondfw`.


<command-message>grill-with-docs</command-message>
<command-name>/grill-with-docs</command-name>
<command-args>experimental concept proposal: can sauna create a virtual display driver that steamvr targets, such that sauna can keep ownership of the physical video stream/display interface at all times, but steamvr can hook into the virtual one anytime and sauna would instantly pipe steamvr's output to the physical interface when detected.</command-args>


Base directory for this skill: C:\Users\decid\.ccs\instances\bigscreen\skills\grill-with-docs

<what-to-do>

Interview me relentlessly about every aspect of this plan until we reach a shared understanding. Walk down each branch of the design tree, resolving dependencies between decisions one-by-one. For each question, provide your recommended answer.

Ask the questions one at a time, waiting for feedback on each question before continuing.

If a question can be answered by exploring the codebase, explore the codebase instead.

</what-to-do>

<supporting-info>

## Domain awareness

During codebase exploration, also look for existing documentation:

### File structure

Most repos have a single context:

```
/
├── CONTEXT.md
├── docs/
│   └── adr/
│       ├── 0001-event-sourced-orders.md
│       └── 0002-postgres-for-write-model.md
└── src/
```

If a `CONTEXT-MAP.md` exists at the root, the repo has multiple contexts. The map points to where each one lives:

```
/
├── CONTEXT-MAP.md
├── docs/
│   └── adr/                          ← system-wide decisions
├── src/
│   ├── ordering/
│   │   ├── CONTEXT.md
│   │   └── docs/adr/                 ← context-specific decisions
│   └── billing/
│       ├── CONTEXT.md
│       └── docs/adr/
```

Create files lazily — only when you have something to write. If no `CONTEXT.md` exists, create one when the first term is resolved. If no `docs/adr/` exists, create it when the first ADR is needed.

## During the session

### Challenge against the glossary

When the user uses a term that conflicts with the existing language in `CONTEXT.md`, call it out immediately. "Your glossary defines 'cancellation' as X, but you seem to mean Y — which is it?"

### Sharpen fuzzy language

When the user uses vague or overloaded terms, propose a precise canonical term. "You're saying 'account' — do you mean the Customer or the User? Those are different things."

### Discuss concrete scenarios

When domain relationships are being discussed, stress-test them with specific scenarios. Invent scenarios that probe edge cases and force the user to be precise about the boundaries between concepts.

### Cross-reference with code

When the user states how something works, check whether the code agrees. If you find a contradiction, surface it: "Your code cancels entire Orders, but you just said partial cancellation is possible — which is right?"

### Update CONTEXT.md inline

When a term is resolved, update `CONTEXT.md` right there. Don't batch these up — capture them as they happen. Use the format in [CONTEXT-FORMAT.md](./CONTEXT-FORMAT.md).

`CONTEXT.md` should be totally devoid of implementation details. Do not treat `CONTEXT.md` as a spec, a scratch pad, or a repository for implementation decisions. It is a glossary and nothing else.

### Offer ADRs sparingly

Only offer to create an ADR when all three are true:

1. **Hard to reverse** — the cost of changing your mind later is meaningful
2. **Surprising without context** — a future reader will wonder "why did they do it this way?"
3. **The result of a real trade-off** — there were genuine alternatives and you picked one for specific reasons

If any of the three is missing, skip the ADR. Use the format in [ADR-FORMAT.md](./ADR-FORMAT.md).

</supporting-info>

ARGUMENTS: experimental concept proposal: can sauna create a virtual display driver that steamvr targets, such that sauna can keep ownership of the physical video stream/display interface at all times, but steamvr can hook into the virtual one anytime and sauna would instantly pipe steamvr's output to the physical interface when detected.


Before grilling — mapping how sauna currently owns the physical Beyond display + the SteamVR handoff, so questions are grounded. Checking for existing docs too.


Agent

Map sauna display ownership architecture

Prompt:
Explore the sauna codebase at C:\Users\decid\Documents\projects\sauna to answer these specific questions. Be thorough — check multiple files/naming conventions. Return concise findings with file:line refs.

1. HOW does sauna acquire and present to the PHYSICAL Bigscreen Beyond display? Is it a Windows monitor (DXGI/DirectMode), a direct-mode HMD via a vendor/OpenVR API, a Vulkan/D3D swapchain to a specific adapter+output, or something else? Find the presenter/swapchain/output-acquisition code. Look in src/app/spatial_light.cpp and any src/*/presenter*, *display*, *output*, *swapchain*, *vrlink*, *directmode* files.

2. What exactly is "requestHmdOwnership", "hmdReleasedNow", and the SteamVR ownership handoff state machine? How does sauna RELEASE the physical display to SteamVR and RECLAIM it? What Windows/GPU mechanism actually transfers the display (does sauna release a DXGI output / direct-mode acquire, stop presenting, etc.)? Summarize the Own::k* states and transitions.

3. How does sauna DETECT SteamVR taking over / exiting? (process polling vrserver/vrcompositor? OpenVR events? a driver hook?) Find the detection code.

4. Is there ANY existing virtual-display, indirect-display-driver (Windows IDD), staging/intermediate-surface, or "pipe another app's output" code already in sauna? Search for IDD, "indirect", "virtual display", desktop duplication, shared texture / shared handle, NT handle, KeyedMutex, capture of another process.

5. What does sauna capture/composite as its normal content (desktop duplication? specific windows? a capture ring)? Find the capture source abstraction (the "screens"/"src" with stop()/resume() seen in spatial_light.cpp).

6. Does sauna talk to SteamVR/OpenVR at all as a driver or client? Any openvr.h / vrclient / driver_*.cpp / .vrdrivermanifest / IVRDriver in the repo?

7. Existing docs: is there a CONTEXT.md or CONTEXT-MAP.md at repo root, and a docs/adr/ directory? List ADR filenames + titles if present. Also note any ADR-0005 (doze) or architecture docs describing the display/ownership model.

Return a structured summary per question with file:line evidence. Do not modify anything.


/grill-with-docs experimental concept proposal: can sauna create a virtual display driver that steamvr targets, such that sauna can keep ownership of the physical video stream/display interface at all…


<owl_reachability_notice>
You have an active owl perch. Launch this Agent with run_in_background: true so you stay reachable for incoming messages while it works.
</owl_reachability_notice>