feat(native-usb): add 32-frame HD gameplay rumble and USB flight diagnostics

Preserve native frequency/amplitude timelines for DualSense PCM output,
remove the unsupported 64-frame path, and retain compatibility rumble for
other controllers. Use the validated 300 MHz sampling phase and restrict
non-bondable Classic discovery autoconnect to previously paired peers.

Buffer native-hub UART stdout and release USB IRQs around port-reset
callbacks. Add observer liveness and pre-SETUP root-response observations
without changing recovery behavior. Cover transport and haptics boundaries.

Record the user-accepted 0.108 trial: controls remained responsive and
rumble felt fine. Instrumentation changes timing; the disconnect root
cause and long-term reliability remain unqualified.

Validation: 624 tests, 11 affected firmware/probe builds, and on-device
concurrent USB and HD-auto-start checks. Private captures, generated
images, and unrelated working-tree files are intentionally excluded.
This commit is contained in:
Joey Yakimowich-Payne 2026-09-19 17:03:46 -06:00
commit 80b788099b
46 changed files with 2873 additions and 412 deletions

View file

@ -409,7 +409,7 @@ void probe_protocol_gate_native_report(const probe_protocol_state* state,
}
bool probe_protocol_decode_rumble(uint8_t report_id, const uint8_t* data,
size_t length, probe_rumble_frame* output) {
size_t length, NativeHapticsActuatorFrame* output) {
if (!data || !output) return false;
if (report_id == 0) {
if (length < 17 || length > 64 || data[0] != 0x01) return false;
@ -424,16 +424,14 @@ bool probe_protocol_decode_rumble(uint8_t report_id, const uint8_t* data,
// packs frequency/amplitude/frequency/amplitude as four 10-bit LE fields.
// Header: format 01 [7:6], sample count [5:4], sequence [3:0].
// Sequence is informational; unused sample bytes and USB padding may be stale.
probe_rumble_frame decoded = {.count = (data[0] >> 4) & 3u};
for (unsigned i = 0; i < decoded.count; ++i) {
NativeHapticsActuatorFrame decoded = {.sample_count = (data[0] >> 4) & 3u};
for (unsigned i = 0; i < decoded.sample_count; ++i) {
const uint8_t* sample = data + 1u + 5u * i;
const unsigned first = (sample[1] >> 2) | ((sample[2] & 0x0fu) << 6);
const unsigned second = (sample[3] >> 6) | ((unsigned)sample[4] << 2);
const unsigned amplitude = first > second ? first : second;
// ERM compatibility, not HD waveform reproduction: ignore frequencies
// and round max(amplitudes) across the full 10-bit range to 0..255.
// SDL's conservative outbound clamp is not an inbound validity limit.
decoded.magnitude[i] = (uint8_t)((amplitude * 255u + 511u) / 1023u);
NativeHapticsSample* out = &decoded.samples[i];
out->low_frequency_code = sample[0] | ((uint16_t)(sample[1] & 3u) << 8);
out->low_amplitude = (sample[1] >> 2) | ((uint16_t)(sample[2] & 15u) << 6);
out->high_frequency_code = (sample[2] >> 4) | ((uint16_t)(sample[3] & 63u) << 4);
out->high_amplitude = (sample[3] >> 6) | ((uint16_t)sample[4] << 2);
}
*output = decoded;
return true;