From 694bec4ead8826ee8817a95ec6a9319c38b24605 Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Wed, 15 Jul 2026 17:05:56 +0200 Subject: [PATCH] =?UTF-8?q?fix(android):=20SC2=20sticks=20=E2=80=94=20SETT?= =?UTF-8?q?ING=5FENABLE=5FRAW=5FJOYSTICK=3D0=20for=20calibrated=20i16?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A Steam Controller 2 opened in raw mode (our capture claims the HID interface) reports ADC joystick coordinates ~0..3200, which Steam/SDL read as only a few percent of full travel — the sticks barely move in Steam's controller test even though menu navigation still crosses its lower threshold. Steam sends SETTING_ENABLE_RAW_JOYSTICK (0x2e) = 0 during native init to force firmware-calibrated signed i16; replicate it (NORMALIZE_JOYSTICKS) alongside lizard-off at claim time and on the 3 s watchdog refresh (the refresh also repairs a host/driver that re-enabled ADC mode after capture started). Co-Authored-By: Claude Opus 4.8 --- .../kotlin/io/unom/punktfunk/kit/Sc2Device.kt | 14 ++++++++++++++ .../kotlin/io/unom/punktfunk/kit/Sc2UsbLink.kt | 16 +++++++++++----- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/clients/android/kit/src/main/kotlin/io/unom/punktfunk/kit/Sc2Device.kt b/clients/android/kit/src/main/kotlin/io/unom/punktfunk/kit/Sc2Device.kt index 7a202d0b..50b03ca6 100644 --- a/clients/android/kit/src/main/kotlin/io/unom/punktfunk/kit/Sc2Device.kt +++ b/clients/android/kit/src/main/kotlin/io/unom/punktfunk/kit/Sc2Device.kt @@ -79,6 +79,20 @@ object Sc2Device { // [4..6] = LIZARD_MODE_OFF (0) — already zero } + /** + * Force firmware-calibrated signed i16 stick coordinates. Steam sends this during physical + * controller initialization (`SETTING_ENABLE_RAW_JOYSTICK` = 0x2e, value 0); without it a + * controller previously opened in raw mode reports ADC coordinates around 0..3200, which a + * Triton consumer interprets as only a few percent of full travel. + */ + val NORMALIZE_JOYSTICKS: ByteArray = ByteArray(64).also { + it[0] = 0x01 // feature report id + it[1] = 0x87.toByte() // ID_SET_SETTINGS_VALUES + it[2] = 3 // one ControllerSetting {u8 num, u16 value} + it[3] = 0x2E // SETTING_ENABLE_RAW_JOYSTICK + // [4..6] = disabled (0) — firmware emits calibrated signed i16 values + } + const val LIZARD_REFRESH_MS = 3000L /** Wire mapping: SC2 button bit → punktfunk `Gamepad.BTN_*`, the inverse of the host's diff --git a/clients/android/kit/src/main/kotlin/io/unom/punktfunk/kit/Sc2UsbLink.kt b/clients/android/kit/src/main/kotlin/io/unom/punktfunk/kit/Sc2UsbLink.kt index baf74cc5..040d5d29 100644 --- a/clients/android/kit/src/main/kotlin/io/unom/punktfunk/kit/Sc2UsbLink.kt +++ b/clients/android/kit/src/main/kotlin/io/unom/punktfunk/kit/Sc2UsbLink.kt @@ -135,7 +135,7 @@ class Sc2UsbLink( @Suppress("UnspecifiedRegisterReceiverFlag") context.registerReceiver(receiver, filter) } - claimed.forEach { sendFeature(conn, it.iface.id, Sc2Device.DISABLE_LIZARD) } + claimed.forEach { configureInputMode(conn, it.iface.id) } reader = Thread({ readLoop(conn, claimed) }, "pf-sc2-usb").apply { isDaemon = true start() @@ -215,11 +215,12 @@ class Sc2UsbLink( while (running) { val now = android.os.SystemClock.elapsedRealtime() if (now - lastLizard >= Sc2Device.LIZARD_REFRESH_MS) { - // Refresh on every claimed interface until one is known-active (the dongle - // relays it per-slot); afterwards the active one suffices. + // Refresh both required firmware modes. The raw-joystick setting is normally + // persistent, but replaying it also repairs a host/driver that enabled ADC + // coordinates after capture started. val target = activeClaim - if (target != null) sendFeature(conn, target.iface.id, Sc2Device.DISABLE_LIZARD) - else live.forEach { sendFeature(conn, it.iface.id, Sc2Device.DISABLE_LIZARD) } + if (target != null) configureInputMode(conn, target.iface.id) + else live.forEach { configureInputMode(conn, it.iface.id) } lastLizard = now } // Submit the next pending OUT report on the active (else first) interface. @@ -321,6 +322,11 @@ class Sc2UsbLink( sendReport(conn, ifId, type, data) } + private fun configureInputMode(conn: UsbDeviceConnection, ifaceId: Int) { + sendFeature(conn, ifaceId, Sc2Device.DISABLE_LIZARD) + sendFeature(conn, ifaceId, Sc2Device.NORMALIZE_JOYSTICKS) + } + private fun sendFeature(conn: UsbDeviceConnection, ifaceId: Int, data: ByteArray) { sendReport(conn, ifaceId, REPORT_TYPE_FEATURE, data) }