switch-pico/tests/switch2_native_gamepad_bridge_test.cpp
Joey Yakimowich-Payne 80b788099b 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.
2026-09-19 17:03:46 -06:00

1055 lines
49 KiB
C++

#include <assert.h>
#include <math.h>
#include <stdint.h>
#include <string.h>
#include "controller_input.h"
#include "input/bluepad32_input_backend.h"
#include "model.h"
#include "pico/stdlib.h"
#include "platform/pico/bootsel_pairing_button.h"
#include "platform/pico/system_clock.h"
#include "profile/controller_profile_runtime.h"
#include "profile/profile_service.h"
namespace {
uint64_t now_us = 1000000;
uint32_t stage;
Bluepad32NativeGamepadSnapshot sources[BLUEPAD32_NATIVE_PAIR_COUNT];
ControllerProfile profiles[BLUEPAD32_NATIVE_PAIR_COUNT];
// Existing single-pair scenarios exercise PairA in both executable configurations.
Bluepad32NativeGamepadSnapshot& source = sources[0];
ControllerProfile& profile = profiles[0];
bool selected[BLUEPAD32_NATIVE_PAIR_COUNT];
uint64_t cue_tokens[PROBE_CONTROLLER_COUNT];
uint64_t next_cue_token;
uint32_t profile_generation = 1;
bool alternating_shortcuts[BLUEPAD32_NATIVE_PAIR_COUNT];
bool shortcut_phases[BLUEPAD32_NATIVE_PAIR_COUNT];
bool& alternating_shortcut = alternating_shortcuts[0];
bool latching_shortcuts[BLUEPAD32_NATIVE_PAIR_COUNT];
struct SlotShortcut {
bool active = false;
bool latched = false;
uint32_t connection_generation = 0;
};
SlotShortcut slot_shortcuts[BLUEPAD32_INPUT_BACKEND_SLOT_COUNT];
probe_controller_input controls[PROBE_CONTROLLER_COUNT];
uint8_t reports[PROBE_CONTROLLER_COUNT][63];
uint8_t source_pair(uint8_t slot) {
for (uint8_t pair_index = 0; pair_index < BLUEPAD32_NATIVE_PAIR_COUNT; ++pair_index)
if (sources[pair_index].controller.active && sources[pair_index].slot == slot) return pair_index;
assert(false);
return 0;
}
} // namespace
uint32_t time_us_32() { return static_cast<uint32_t>(now_us); }
absolute_time_t get_absolute_time() { return now_us; }
uint32_t to_ms_since_boot(absolute_time_t time) { return static_cast<uint32_t>(time / 1000); }
void system_clock_initialize() {}
extern "C" int probe_debug_printf(const char*, ...) { return 0; }
BootselPairingButtonEvent bootsel_pairing_button_task() { return BootselPairingButtonEvent::kNone; }
void bluepad32_input_backend_init() { stage = 1; }
void bluepad32_input_backend_start() { stage = 2; }
void bluepad32_input_backend_poll() {}
void bluepad32_input_backend_diagnostics(Bluepad32BackendDiagnostics* out) { *out = {}; out->initialization_stage = stage; }
void bluepad32_input_backend_open_pairing_window() {}
void bluepad32_input_backend_select_native_source(uint8_t pair_index, const uint8_t*) {
assert(pair_index < BLUEPAD32_NATIVE_PAIR_COUNT);
selected[pair_index] = true;
}
void bluepad32_input_backend_native_snapshot(uint8_t pair_index, Bluepad32NativeGamepadSnapshot* out) {
assert(pair_index < BLUEPAD32_NATIVE_PAIR_COUNT);
*out = selected[pair_index] ? sources[pair_index] : Bluepad32NativeGamepadSnapshot{};
}
bool bluepad32_input_backend_native_sample_request(uint8_t instance, uint8_t, uint64_t* token) {
if (instance >= PROBE_CONTROLLER_COUNT || !sources[instance / 2].controller.active || !token) return false;
*token = cue_tokens[instance] = ++next_cue_token;
return true;
}
int bluepad32_input_backend_native_sample_result(uint8_t instance, uint64_t token) {
return instance < PROBE_CONTROLLER_COUNT && token && cue_tokens[instance] == token ? 1 : -1;
}
void bluepad32_input_backend_native_sample_cancel(uint8_t instance) {
assert(instance < PROBE_CONTROLLER_COUNT);
cue_tokens[instance] = 0;
}
bool bluepad32_input_backend_native_rumble_submit(uint8_t, const NativeHapticsActuatorFrame*) {
assert(false && "gameplay motor dispatch belongs to the native backend fixture");
return false;
}
void bluepad32_input_backend_native_rumble_cancel(uint8_t) {
assert(false && "gameplay motor cancellation belongs to the native backend fixture");
}
void bluepad32_input_backend_queue_profile_feedback(uint8_t, uint32_t, uint8_t, ControllerProfileConfirmationPolicy) {}
void controller_profile_runtime_reset() {
for (ControllerProfile& value : profiles)
value = controller_profile_default(controller_identity_global(), 0);
}
uint32_t profile_service_database_generation() { return profile_generation; }
bool controller_profile_runtime_take_initial_profile_indication(uint8_t, ControllerProfileRuntimeProfileChangeEvent*) { return false; }
bool controller_profile_runtime_take_profile_change(uint8_t, ControllerProfileRuntimeProfileChangeEvent*) { return false; }
ControllerProfileTransformResult controller_profile_runtime_transform(
uint8_t slot, const Bluepad32SlotSnapshot& input, uint32_t, AdapterUsbMode) {
if (!input.active) {
slot_shortcuts[slot] = {};
return {};
}
const uint8_t pair_index = source_pair(slot);
auto result = controller_profile_transform(input.state, profiles[pair_index]);
if (alternating_shortcuts[pair_index]) {
// Model a runtime synthetic transition spanning the two halves. Two
// evaluations for one paired report would expose contradictory states.
shortcut_phases[pair_index] = !shortcut_phases[pair_index];
result.state.button_system = result.state.button_capture = shortcut_phases[pair_index];
}
if (latching_shortcuts[pair_index]) {
// Model a macro/Shift latch owned by a runtime SLOT, not a USB pair.
auto& shortcut = slot_shortcuts[slot];
if (!shortcut.active || shortcut.connection_generation != input.connection_generation) {
shortcut = {};
shortcut.active = true;
shortcut.connection_generation = input.connection_generation;
}
if (input.state.button_select) shortcut.latched = true;
result.state.button_system = result.state.button_capture = shortcut.latched;
}
return result;
}
namespace {
uint32_t now_ms() { return to_ms_since_boot(now_us); }
void put_pair(uint8_t* out, uint16_t x, uint16_t y) {
out[0] = static_cast<uint8_t>(x);
out[1] = static_cast<uint8_t>((x >> 8) | (y << 4));
out[2] = static_cast<uint8_t>(y >> 4);
}
void calibrate(uint8_t instance, uint16_t x, uint16_t y, uint16_t px, uint16_t py, uint16_t nx, uint16_t ny) {
uint8_t record[9];
put_pair(record, x, y);
put_pair(record + 3, px, py);
put_pair(record + 6, nx, ny);
probe_controller_input_set_full_stick_calibration(instance, record);
}
uint16_t stick_x(uint8_t instance) { return reports[instance][5] | ((reports[instance][6] & 15u) << 8); }
uint16_t stick_y(uint8_t instance) { return (reports[instance][6] >> 4) | (reports[instance][7] << 4); }
uint8_t imu_length(uint8_t instance) { return reports[instance][probe_model_imu_length_offset(instance)]; }
uint32_t bits(const uint8_t* bytes, unsigned offset, unsigned count) {
uint32_t value = 0;
for (unsigned i = 0; i < count; ++i) value |= uint32_t((bytes[(offset + i) / 8] >> ((offset + i) % 8)) & 1) << i;
return value;
}
void quaternion(uint8_t instance, double out[4]) {
const uint8_t* imu = reports[instance] + probe_model_imu_data_offset(instance);
assert(imu_length(instance) == 30);
const unsigned largest = bits(imu, 32, 3);
assert(largest < 4);
double ratios[3], norm = 1;
for (unsigned i = 0; i < 3; ++i) {
ratios[i] = bits(imu, 35 + 31 * i, 31) / 1073741824.0 - 1;
norm += ratios[i] * ratios[i];
}
out[largest] = 1 / sqrt(norm);
for (unsigned i = 0; i < 3; ++i) out[(largest + i + 1) & 3] = ratios[i] * out[largest];
}
void publish_at_current_time(uint8_t pair_index, bool motion) {
Bluepad32NativeGamepadSnapshot& snapshot = sources[pair_index];
snapshot.received_us = time_us_32();
++snapshot.state_generation;
if (motion) {
snapshot.accel_received_us = snapshot.gyro_received_us = time_us_32();
++snapshot.accel_sequence;
++snapshot.gyro_sequence;
}
}
void publish(bool motion = true, uint8_t pair_index = 0) {
now_us += 4000;
publish_at_current_time(pair_index, motion);
}
uint32_t peek(uint8_t instance) {
probe_controller_input_poll(instance, now_ms(), &controls[instance]);
return probe_controller_input_peek_native_report(instance, now_ms(), reports[instance]);
}
void consume(uint8_t instance) {
const uint32_t token = peek(instance);
assert(token && probe_controller_input_commit_native_report(instance, token));
}
void pair(uint8_t pair_index = 0) { consume(pair_index * 2); consume(pair_index * 2 + 1); }
void no_mouse_or_rails() {
for (unsigned i = 0; i < 2; ++i) {
assert((reports[i][3] & 0xc0) == 0);
assert(reports[i][9] == 0 && reports[i][10] == 0 && reports[i][11] == 0 && reports[i][12] == 0);
assert(reports[i][13] == 0xff);
}
}
void mapped_halves_and_calibration() {
source.slot = 2;
source.controller.active = true;
source.controller.connection_generation = 7;
source.controller.identity = controller_identity_global();
source.battery = 128;
publish();
// Neither an absent source nor an uncalibrated child masquerades as active.
assert(!peek(0) && !controls[0].active);
calibrate(0, 2000, 2100, 1500, 1400, 1600, 1700);
assert(peek(0));
assert(!peek(1) && !controls[1].active);
calibrate(1, 1800, 1900, 1700, 1800, 1400, 1500);
pair();
assert(stick_x(0) == 2000 && stick_y(0) == 2100);
assert(stick_x(1) == 1800 && stick_y(1) == 1900);
assert(reports[0][1] == 0x15 && reports[1][1] == 0x15); // Measured half battery, USB powered, not charging.
// Actual profile transforms can move controls across native children.
profile.button_map[static_cast<unsigned>(ControllerProfileLogicalButton::kSouth)] =
static_cast<uint8_t>(ControllerProfileLogicalButton::kDpadRight);
profile.button_map[static_cast<unsigned>(ControllerProfileLogicalButton::kDpadLeft)] =
static_cast<uint8_t>(ControllerProfileLogicalButton::kEast);
profile.triggers[0].digital_threshold = 20000;
profile.triggers[1].digital_threshold = 30000;
ControllerState& state = source.controller.state;
state.button_south = state.dpad_left = true;
state.button_left_shoulder = state.button_right_shoulder = true;
state.button_select = state.button_start = true;
state.button_left_stick = state.button_right_stick = true;
state.button_system = state.button_capture = true;
state.left_trigger = 19999;
state.right_trigger = 30000;
state.right_stick_x = INT16_MAX;
state.left_stick_y = INT16_MIN;
publish(); pair();
assert(reports[0][2] == 0xf2 && reports[1][2] == 0xd2);
assert(reports[0][3] == 1 && reports[1][3] == 1);
assert(stick_x(0) == 3500 && stick_y(0) == 2100);
assert(stick_x(1) == 1800 && stick_y(1) == 3700);
no_mouse_or_rails();
state = {};
state.button_west = state.button_north = true;
state.dpad_up = state.dpad_down = true;
state.left_trigger = 20000;
state.right_stick_x = INT16_MIN;
state.left_stick_y = INT16_MAX;
publish(); pair();
assert(reports[0][2] == 0x0c && reports[1][2] == 0x29);
assert(stick_x(0) == 400 && stick_y(1) == 400);
// A malformed calibration may not spill a 12-bit axis into its neighbor.
const uint32_t left_pending = peek(1);
calibrate(0, 2000, 2100, 3000, 1400, 1600, 1700);
assert(!peek(0));
assert(probe_controller_input_commit_native_report(1, left_pending));
calibrate(0, 2000, 2100, 1500, 1400, 1600, 1700);
state = {};
profile = controller_profile_default(controller_identity_global(), 0);
alternating_shortcut = true;
for (unsigned i = 0; i < 4; ++i) {
publish(); pair();
assert(reports[0][3] == reports[1][3]);
}
alternating_shortcut = false;
}
void independent_backpressure_and_resets() {
publish();
const uint32_t blocked_left = peek(1);
const uint32_t right = peek(0);
uint8_t saved[63]; memcpy(saved, reports[0], sizeof(saved));
assert(peek(0) == right && memcmp(saved, reports[0], sizeof(saved)) == 0);
assert(!probe_controller_input_commit_native_report(1, right));
assert(probe_controller_input_commit_native_report(0, right));
assert(!probe_controller_input_commit_native_report(0, right));
assert(probe_controller_input_commit_native_report(1, blocked_left));
publish();
const uint32_t obsolete = peek(1);
for (unsigned i = 0; i < 40; ++i) {
source.controller.state.dpad_down = (i & 1) != 0;
source.controller.state.button_east = (i & 1) != 0;
publish(); consume(0);
}
const uint32_t latest = peek(1);
assert(latest != obsolete && reports[1][2] == 1);
assert(!probe_controller_input_commit_native_report(1, obsolete));
probe_controller_input_set_native_stream(0, false);
assert(!peek(0));
assert(probe_controller_input_commit_native_report(1, latest));
probe_controller_input_set_native_stream(0, true);
const uint32_t right_pending = peek(0);
probe_controller_input_set_native_stream(1, false);
assert(probe_controller_input_commit_native_report(0, right_pending));
probe_controller_input_set_native_stream(1, true);
source.controller.state = {};
}
void real_motion_admission_and_loss() {
source.accel_valid = source.gyro_valid = true;
source.accel_q13[1] = 8192; // SDL face-up gravity -> native +Z, no mouse mounting.
// These values have already passed the DS5 factory-calibration path.
// Even a controller rotating at connection must not wait for stationary bias estimation.
source.gyro_q10[0] = 0;
source.gyro_q10[1] = 90 * 1024;
source.gyro_q10[2] = 0;
publish(); pair();
assert(imu_length(0) == 30 && imu_length(1) == 30);
source.gyro_q10[1] = 0;
publish(); pair();
// Polling and fresh button packets cannot create additional IMU samples.
for (unsigned i = 0; i < 420; ++i) {
publish(false); pair();
assert(controls[0].active && controls[1].active);
assert(imu_length(0) == 0 && imu_length(1) == 0);
}
publish(); pair(); // Fresh factory-calibrated data recovers without another settling delay.
assert(imu_length(0) == 30 && imu_length(1) == 30);
const uint8_t* right_imu = reports[0] + probe_model_imu_data_offset(0);
const uint8_t* left_imu = reports[1] + probe_model_imu_data_offset(1);
assert(memcmp(right_imu, left_imu, 30) == 0);
assert(bits(right_imu, 128, 32) == 0 && bits(right_imu, 160, 32) == 0);
assert(bits(right_imu, 192, 32) == (1u << 28));
double initial[4]; quaternion(0, initial);
// A new controls packet with no new IMU cannot emit the old sample again.
source.controller.state.button_east = true;
publish(false); pair();
assert(reports[0][2] == 2 && imu_length(0) == 0 && imu_length(1) == 0);
// A blocked child's motion is not consumed by the other child's endpoint.
publish(); consume(0);
const uint32_t left_pending = peek(1);
assert(imu_length(1) == 30);
consume(0); assert(imu_length(0) == 0);
probe_controller_input_set_native_stream(0, false);
assert(probe_controller_input_commit_native_report(1, left_pending));
probe_controller_input_set_native_stream(0, true);
publish(); pair();
assert(imu_length(0) == 30 && imu_length(1) == 30); // No shared recalibration on USB reset.
// One second of genuine 90dps yaw advances the same rigid orientation once,
// not twice because two virtual endpoints happen to consume it.
source.gyro_q10[1] += 90 * 1024;
for (unsigned i = 0; i < 250; ++i) { publish(); pair(); }
double turned[4]; quaternion(0, turned);
double dot = 0;
for (unsigned i = 0; i < 4; ++i) dot += initial[i] * turned[i];
assert(fabs(fabs(dot) - sqrt(.5)) < .015);
quaternion(1, initial);
for (unsigned i = 0; i < 4; ++i) assert(fabs(initial[i] - turned[i]) < 1e-8);
source.gyro_q10[1] -= 90 * 1024;
publish();
const uint32_t obsolete = peek(0);
source.accel_valid = source.gyro_valid = false;
assert(!probe_controller_input_commit_native_report(0, obsolete));
publish(false); pair();
assert(controls[0].active && reports[0][2] == 2 && imu_length(0) == 0 && imu_length(1) == 0);
source.accel_valid = source.gyro_valid = true;
publish(); pair();
assert(imu_length(0) == 30);
for (unsigned i = 0; i < 38; ++i) { publish(false); pair(); }
assert(controls[0].active && reports[0][2] == 2 && imu_length(0) == 0 && imu_length(1) == 0);
publish();
const uint32_t old_right = peek(0), old_left = peek(1);
// Even a reconnect whose teardown was missed retires both USB identities.
++source.controller.connection_generation;
source.accel_valid = source.gyro_valid = false; // New epochs require fresh reports.
assert(!probe_controller_input_commit_native_report(0, old_right));
assert(!probe_controller_input_commit_native_report(1, old_left));
pair();
assert(controls[0].active && imu_length(0) == 0 && imu_length(1) == 0);
source.controller.active = false;
memset(reports[0], 0x5a, 63);
assert(!peek(0) && !controls[0].active);
for (uint8_t byte : reports[0]) assert(byte == 0x5a);
assert(!peek(1) && !controls[1].active);
source.controller.active = true;
++source.controller.connection_generation;
publish(); pair();
now_us += 500000;
assert(!peek(0) && !peek(1));
assert(!controls[0].active && !controls[1].active);
}
void selected_motion_target_keeps_both_control_halves() {
source = {};
source.slot = 0;
source.controller.active = true;
source.controller.connection_generation = 99;
source.controller.state.button_south = true;
source.controller.state.dpad_up = true;
source.accel_valid = source.gyro_valid = true;
source.accel_q13[1] = 8192;
profile = controller_profile_default(controller_identity_global(), 0);
calibrate(0, 2048, 2048, 2047, 2047, 2048, 2048);
calibrate(1, 2048, 2048, 2047, 2047, 2048, 2048);
publish(); pair();
for (uint8_t instance = 0; instance < 2; ++instance) {
assert(controls[instance].active);
const bool enabled = (SWITCH2_BRIDGE_IMU_TARGET_MASK & (1u << instance)) != 0;
assert(imu_length(instance) == (enabled ? 30 : 0));
}
assert(reports[0][2] == 0x01 && reports[1][2] == 0x08);
source.gyro_q10[1] = 90 * 1024;
publish(); pair();
assert(imu_length(0) == ((SWITCH2_BRIDGE_IMU_TARGET_MASK & 1) ? 30 : 0));
assert(imu_length(1) == ((SWITCH2_BRIDGE_IMU_TARGET_MASK & 2) ? 30 : 0));
no_mouse_or_rails();
}
void wii_bias_and_independent_sensor_freshness() {
++source.controller.connection_generation;
source.track_stationary_bias = true;
source.gyro_q10[1] = 2 * 1024;
publish(); pair();
assert(controls[0].active && controls[1].active);
assert(reports[0][2] == 0x01 && reports[1][2] == 0x08);
for (uint8_t instance = 0; instance < 2; ++instance)
assert(imu_length(instance) == ((SWITCH2_BRIDGE_IMU_TARGET_MASK & (1u << instance)) ? 30 : 0));
for (unsigned i = 0; i < 400; ++i) { publish(); pair(); }
for (uint8_t instance = 0; instance < 2; ++instance) {
assert(imu_length(instance) == ((SWITCH2_BRIDGE_IMU_TARGET_MASK & (1u << instance)) ? 30 : 0));
}
// Accelerometer-only reports must not refresh a stalled MotionPlus stream.
for (unsigned i = 0; i < 38; ++i) {
publish(false);
source.accel_received_us = time_us_32();
++source.accel_sequence;
pair();
}
assert(controls[0].active && controls[1].active);
assert(imu_length(0) == 0 && imu_length(1) == 0);
source.gyro_valid = false;
publish(); pair();
assert(reports[0][2] == 0x01 && reports[1][2] == 0x08);
assert(imu_length(0) == 0 && imu_length(1) == 0);
// Fresh Wii sensors recover immediately, without borrowing the old bias.
source.gyro_valid = true;
publish(); pair();
for (uint8_t instance = 0; instance < 2; ++instance)
assert(imu_length(instance) == ((SWITCH2_BRIDGE_IMU_TARGET_MASK & (1u << instance)) ? 30 : 0));
// A factory-calibrated source switching policy must initialize immediately.
source.track_stationary_bias = false;
publish(); pair();
for (uint8_t instance = 0; instance < 2; ++instance) {
assert(imu_length(instance) == ((SWITCH2_BRIDGE_IMU_TARGET_MASK & (1u << instance)) ? 30 : 0));
}
}
void nunchuk_buttons_map_to_native_left_shoulders() {
++source.controller.connection_generation;
source.controller.state = {};
source.accel_valid = source.gyro_valid = false;
source.battery = 0;
profile = controller_profile_default(controller_identity_global(), 0);
// The real Wii parser maps Nunchuk C to west and Z to north. These are
// ordinary profile inputs, not the unrelated Switch2 extra "C" control.
profile.button_map[static_cast<unsigned>(ControllerProfileLogicalButton::kWest)] =
CONTROLLER_PROFILE_LEFT_TRIGGER_CONTROL;
profile.button_map[static_cast<unsigned>(ControllerProfileLogicalButton::kNorth)] =
static_cast<uint8_t>(ControllerProfileLogicalButton::kLeftShoulder);
source.controller.state.button_west = true; // C -> ZL.
publish(false); pair();
assert(reports[0][2] == 0 && reports[1][2] == 0x20);
assert(reports[0][1] == 0x01 && reports[1][1] == 0x01);
source.controller.state.button_west = false;
source.controller.state.button_north = true; // Z -> L.
publish(false); pair();
assert(reports[0][2] == 0 && reports[1][2] == 0x10);
source.controller.state.button_right_shoulder = true; // Remote 2 -> R.
publish(false); pair();
assert(reports[0][2] == 0x10 && reports[1][2] == 0x10); // Real L+R across the pair.
source.controller.state.button_west = true;
publish(false); pair();
assert(reports[0][2] == 0x10 && reports[1][2] == 0x30);
source.controller.state = {};
publish(false); pair();
assert(reports[0][2] == 0 && reports[1][2] == 0); // No sticky synthetic chord.
no_mouse_or_rails();
}
void inactive_child(uint8_t instance) {
memset(reports[instance], 0x5a, sizeof(reports[instance]));
assert(!peek(instance) && !controls[instance].active);
assert(controls[instance].buttons[0] == 0 && controls[instance].buttons[1] == 0);
for (uint8_t byte : controls[instance].stick) assert(byte == 0);
for (uint8_t byte : reports[instance]) assert(byte == 0x5a);
}
void solo_controls_and_explicit_rails() {
source.accel_valid = source.gyro_valid = false;
calibrate(0, 2000, 2100, 1500, 1400, 1600, 1700);
calibrate(1, 1800, 1900, 1700, 1800, 1400, 1500);
bool ControllerState::* const faces[] = {
&ControllerState::button_south, &ControllerState::button_east,
&ControllerState::button_west, &ControllerState::button_north};
const uint8_t face_bits[2][4] = {{0x02, 0x08, 0x01, 0x04}, {0x04, 0x01, 0x08, 0x02}};
ControllerState& state = source.controller.state;
for (uint8_t instance = 0; instance < 2; ++instance) {
const bool left = instance == 1;
profile = controller_profile_default(controller_identity_global(), 0);
profile.native_joycon_layout = left ? ControllerProfileNativeJoyconLayout::kLeftSolo :
ControllerProfileNativeJoyconLayout::kRightSolo;
++profile_generation;
for (unsigned face = 0; face < 4; ++face) {
state = {};
state.*faces[face] = true;
publish(false); consume(instance);
assert(reports[instance][2] == face_bits[instance][face]);
inactive_child(instance ^ 1);
}
// Dpad is not silently merged into the four solo face actions.
state = {};
state.dpad_up = state.dpad_down = state.dpad_left = state.dpad_right = true;
publish(false); consume(instance);
assert(reports[instance][2] == 0);
profile.button_map[static_cast<unsigned>(ControllerProfileLogicalButton::kDpadRight)] =
static_cast<uint8_t>(ControllerProfileLogicalButton::kSouth);
++profile_generation;
consume(instance);
assert(reports[instance][2] == face_bits[instance][0]);
state = {};
state.button_select = state.button_start = state.button_system = state.button_capture = true;
state.button_left_stick = true;
publish(false); consume(instance);
assert(reports[instance][2] == 0xc0 && reports[instance][3] == 1);
state = {};
state.button_right_stick = true;
state.right_stick_x = INT16_MIN;
publish(false); consume(instance);
assert(reports[instance][2] == 0);
assert(stick_x(instance) == (left ? 1800 : 2000));
assert(stick_y(instance) == (left ? 1900 : 2100));
// A live swap selects the physical right stick AND click, without a
// physical publication or a second swap in the native routing layer.
profile.swap_sticks = true;
++profile_generation;
consume(instance);
assert(reports[instance][2] == 0x80);
assert(stick_x(instance) == (left ? 1800 : 2000));
assert(stick_y(instance) == (left ? 3700 : 400));
state.button_right_stick = false;
state.button_left_stick = true;
state.right_stick_x = 0;
state.right_stick_y = INT16_MIN;
publish(false); consume(instance);
assert(reports[instance][2] == 0);
assert(stick_x(instance) == (left ? 3500 : 400));
assert(stick_y(instance) == (left ? 1900 : 2100));
state = {};
state.button_left_shoulder = true;
state.left_trigger = UINT16_MAX;
publish(false); consume(instance);
assert(reports[instance][2] == (left ? 0x30 : 0));
assert(reports[instance][3] == 0);
state = {};
state.button_right_shoulder = true;
state.right_trigger = UINT16_MAX;
publish(false); consume(instance);
assert(reports[instance][2] == (left ? 0 : 0x30));
assert(reports[instance][3] == 0);
const uint8_t sl = left ? CONTROLLER_PROFILE_LEFT_SL_OUTPUT : CONTROLLER_PROFILE_RIGHT_SL_OUTPUT;
const unsigned shoulder_l = static_cast<unsigned>(ControllerProfileLogicalButton::kLeftShoulder);
const unsigned shoulder_r = static_cast<unsigned>(ControllerProfileLogicalButton::kRightShoulder);
profile.button_map[shoulder_l] = sl;
profile.button_map[shoulder_r] = sl + 1;
++profile_generation;
state = {};
state.button_left_shoulder = true;
publish(false); consume(instance);
assert(reports[instance][2] == 0 && reports[instance][3] == 0x80);
state.button_right_shoulder = true;
publish(false); consume(instance);
assert(reports[instance][2] == 0 && reports[instance][3] == 0xc0);
state.button_left_shoulder = false;
publish(false); consume(instance);
assert(reports[instance][2] == 0 && reports[instance][3] == 0x40);
// Mapping to the other child's rails never creates a selected-side chord.
profile.button_map[shoulder_l] = left ? CONTROLLER_PROFILE_RIGHT_SL_OUTPUT : CONTROLLER_PROFILE_LEFT_SL_OUTPUT;
profile.button_map[shoulder_r] = left ? CONTROLLER_PROFILE_RIGHT_SR_OUTPUT : CONTROLLER_PROFILE_LEFT_SR_OUTPUT;
++profile_generation;
state.button_left_shoulder = true;
publish(false); consume(instance);
assert(reports[instance][2] == 0 && reports[instance][3] == 0);
inactive_child(instance ^ 1);
// A mapped analog source reaches the same rail wire bit only at its
// transformed digital threshold; unmapped physical extras cannot leak.
profile.triggers[0].output = sl;
profile.triggers[0].digital_threshold = 20000;
++profile_generation;
state = {};
state.extra_buttons = 0x7f;
state.left_trigger = 19999;
publish(false); consume(instance);
assert(reports[instance][2] == 0 && reports[instance][3] == 0);
state.left_trigger = 20000;
publish(false); consume(instance);
assert(reports[instance][2] == 0 && reports[instance][3] == 0x80);
}
}
void profile_changes_retire_tokens_without_source_publication() {
profile = controller_profile_default(controller_identity_global(), 0);
++profile_generation;
source.controller.state = {};
source.controller.state.button_south = source.controller.state.dpad_left = true;
source.accel_valid = source.gyro_valid = false;
publish(false);
const uint32_t paired_right = peek(0), paired_left = peek(1);
profile.native_joycon_layout = ControllerProfileNativeJoyconLayout::kRightSolo;
++profile_generation;
assert(!probe_controller_input_commit_native_report(0, paired_right));
assert(!probe_controller_input_commit_native_report(1, paired_left));
consume(0);
assert(reports[0][2] == 0x02);
inactive_child(1);
const uint32_t solo_right = peek(0);
profile.native_joycon_layout = ControllerProfileNativeJoyconLayout::kLeftSolo;
++profile_generation;
assert(!probe_controller_input_commit_native_report(0, solo_right));
consume(1);
assert(reports[1][2] == 0x04);
inactive_child(0);
const uint32_t solo_left = peek(1);
// Even an ordinary mapping edit in the same layout retires old reports.
profile.button_map[static_cast<unsigned>(ControllerProfileLogicalButton::kSouth)] =
static_cast<uint8_t>(ControllerProfileLogicalButton::kNorth);
++profile_generation;
assert(!probe_controller_input_commit_native_report(1, solo_left));
consume(1);
assert(reports[1][2] == 0x02);
profile.native_joycon_layout = ControllerProfileNativeJoyconLayout::kPaired;
++profile_generation;
pair();
assert(reports[0][2] == 0x08 && reports[1][2] == 0x04);
}
void digital_dpad_reaches_the_mapped_left_stick() {
source.accel_valid = source.gyro_valid = false;
source.controller.active = true;
calibrate(0, 2048, 2048, 1000, 1000, 1000, 1000);
calibrate(1, 2048, 2048, 1000, 1000, 1000, 1000);
profile = controller_profile_default(controller_identity_global(), 0);
profile.button_map[12] = CONTROLLER_PROFILE_LEFT_STICK_UP_OUTPUT;
profile.button_map[13] = CONTROLLER_PROFILE_LEFT_STICK_DOWN_OUTPUT;
profile.button_map[14] = CONTROLLER_PROFILE_LEFT_STICK_LEFT_OUTPUT;
profile.button_map[15] = CONTROLLER_PROFILE_LEFT_STICK_RIGHT_OUTPUT;
++profile_generation;
ControllerState& state = source.controller.state;
state = {};
state.dpad_up = true;
publish(false); pair();
assert(stick_x(1) == 2048 && stick_y(1) == 3048);
assert(stick_x(0) == 2048 && stick_y(0) == 2048);
assert(reports[0][2] == 0 && reports[1][2] == 0);
profile.native_joycon_layout = ControllerProfileNativeJoyconLayout::kRightSolo;
++profile_generation;
consume(0);
assert(stick_x(0) == 1048 && stick_y(0) == 2048);
inactive_child(1);
state.dpad_right = true;
publish(false); consume(0);
assert(stick_x(0) == 1341 && stick_y(0) == 2755);
profile.native_joycon_layout = ControllerProfileNativeJoyconLayout::kLeftSolo;
++profile_generation;
consume(1);
assert(stick_x(1) == 2755 && stick_y(1) == 1341);
inactive_child(0);
// Digital directions still target mapped LEFT after swapping. Physical
// left movement now belongs to mapped right and must not block them.
profile.swap_sticks = true;
++profile_generation;
state.dpad_up = false;
state.left_stick_x = INT16_MAX;
publish(false); consume(1);
assert(stick_x(1) == 2048 && stick_y(1) == 1048);
state.right_stick_y = INT16_MAX;
publish(false); consume(1);
assert(stick_x(1) == 1048 && stick_y(1) == 2048);
// Releasing all inputs cannot leave a generated stick or click held.
state = {};
publish(false); consume(1);
assert(stick_x(1) == 2048 && stick_y(1) == 2048 && reports[1][2] == 0);
}
void solo_motion_rotates_coherently_and_resets_frame() {
profile = controller_profile_default(controller_identity_global(), 0);
source.controller.state = {};
++source.controller.connection_generation;
source.track_stationary_bias = false;
source.accel_valid = source.gyro_valid = true;
source.accel_q13[0] = 2048;
source.accel_q13[1] = 4096;
source.accel_q13[2] = -4096;
// Parallel acceleration/rate vectors turn about reference gravity. A
// one-sided or sign-inconsistent rotation cannot preserve this motion.
source.gyro_q10[0] = 30 * 1024;
source.gyro_q10[1] = 60 * 1024;
source.gyro_q10[2] = -60 * 1024;
const ControllerProfileNativeJoyconLayout layouts[] = {
ControllerProfileNativeJoyconLayout::kPaired,
ControllerProfileNativeJoyconLayout::kLeftSolo,
ControllerProfileNativeJoyconLayout::kRightSolo,
ControllerProfileNativeJoyconLayout::kPaired};
const int32_t body_accel[4][3] = {
{2048, 4096, 4096}, {4096, 4096, -2048},
{-4096, 4096, 2048}, {2048, 4096, 4096}};
uint32_t previous_token = 0;
uint8_t previous_instance = 0;
publish();
for (unsigned layout = 0; layout < 4; ++layout) {
profile.native_joycon_layout = layouts[layout];
++profile_generation;
if (previous_token)
assert(!probe_controller_input_commit_native_report(previous_instance, previous_token));
const uint8_t instance = layout == 1 ? 1 : layout == 2 ? 0 :
(SWITCH2_BRIDGE_IMU_TARGET_MASK & 1) ? 0 : 1;
consume(instance);
if (layout == 1 || layout == 2) inactive_child(instance ^ 1);
if (!(SWITCH2_BRIDGE_IMU_TARGET_MASK & (1u << instance))) {
assert(imu_length(instance) == 0);
continue;
}
assert(imu_length(instance) == 30);
const uint8_t* imu = reports[instance] + probe_model_imu_data_offset(instance);
assert(bits(imu, 12, 12) == 1); // No committed timestamp from the old frame.
for (unsigned axis = 0; axis < 3; ++axis)
assert(bits(imu, 128 + axis * 32, 32) == static_cast<uint32_t>(body_accel[layout][axis] * 32768));
double initial[4]; quaternion(instance, initial);
const double w = sqrt((1.0 + body_accel[layout][2] / 6144.0) / 2.0);
const double expected[4] = {
w, body_accel[layout][1] / (12288.0 * w),
-body_accel[layout][0] / (12288.0 * w), 0};
double dot = 0;
for (unsigned i = 0; i < 4; ++i) dot += initial[i] * expected[i];
assert(fabs(fabs(dot) - 1.0) < 1e-6);
for (unsigned sample = 0; sample < 250; ++sample) { publish(); consume(instance); }
double turned[4]; quaternion(instance, turned);
const double half = sqrt(.5);
const double expected_turn[4] = {
half * initial[0], half * (initial[1] - initial[2]),
half * (initial[1] + initial[2]), half * initial[0]};
dot = 0;
for (unsigned i = 0; i < 4; ++i) dot += turned[i] * expected_turn[i];
assert(fabs(fabs(dot) - 1.0) < 1e-5);
consume(instance);
assert(imu_length(instance) == 0); // No repeated sensor provenance.
publish();
previous_token = peek(instance);
previous_instance = instance;
assert(previous_token && imu_length(instance) == 30);
// Next layout uses this exact fresh source sample, not a new publication.
}
}
#if PROBE_CONTROLLER_COUNT == 4
void publish_both(bool motion = true) {
now_us += 4000;
publish_at_current_time(0, motion);
publish_at_current_time(1, motion);
}
void prepare_two_sources(bool motion) {
for (uint8_t pair_index = 0; pair_index < BLUEPAD32_NATIVE_PAIR_COUNT; ++pair_index) {
auto& snapshot = sources[pair_index];
const uint32_t connection_generation = snapshot.controller.connection_generation + 1;
snapshot = {};
snapshot.slot = pair_index;
snapshot.controller.active = true;
snapshot.controller.connection_generation = connection_generation;
snapshot.controller.identity = controller_identity_global();
snapshot.accel_valid = snapshot.gyro_valid = motion;
snapshot.accel_q13[1] = 8192;
profiles[pair_index] = controller_profile_default(controller_identity_global(), 0);
alternating_shortcuts[pair_index] = false;
}
++profile_generation;
for (uint8_t instance = 0; instance < PROBE_CONTROLLER_COUNT; ++instance) {
probe_controller_input_set_native_stream(instance, true);
calibrate(instance, 2048, 2048, 1000, 1000, 1000, 1000);
}
publish_both(motion);
pair(0);
pair(1);
}
void two_pair_controls_and_profile_coherence() {
prepare_two_sources(false);
auto& a = sources[0].controller.state;
auto& b = sources[1].controller.state;
a.button_south = a.dpad_up = true;
a.button_left_shoulder = a.button_right_shoulder = true;
b.button_east = b.dpad_down = true;
sources[0].battery = 255;
sources[1].battery = 0;
publish_both(false); pair(0); pair(1);
assert(reports[0][2] == 0x11 && reports[1][2] == 0x18);
assert(reports[2][2] == 0x02 && reports[3][2] == 0x01);
assert(reports[0][1] == 0x25 && reports[1][1] == 0x25);
assert(reports[2][1] == 0x01 && reports[3][1] == 0x01);
a.button_left_shoulder = a.button_right_shoulder = false;
b.button_left_shoulder = b.button_right_shoulder = true;
publish_both(false); pair(1); pair(0);
assert(reports[0][2] == 0x01 && reports[1][2] == 0x08);
assert(reports[2][2] == 0x12 && reports[3][2] == 0x11); // Real L+R only on PairB.
// Digital mapped-left movement after swapping feeds only A's solo frame.
// B independently inverts its physical left stick, then swaps it to right.
a = {}; b = {};
a.dpad_up = a.button_south = true;
a.left_stick_x = INT16_MAX;
profiles[0].button_map[12] = CONTROLLER_PROFILE_LEFT_STICK_UP_OUTPUT;
profiles[0].native_joycon_layout = ControllerProfileNativeJoyconLayout::kRightSolo;
profiles[0].swap_sticks = true;
b.dpad_down = true;
b.left_stick_y = INT16_MAX;
profiles[1].sticks[0].invert_y = true;
profiles[1].swap_sticks = true;
++profile_generation;
publish_both(false);
consume(0); pair(1); inactive_child(1);
assert(reports[0][2] == 0x02 && stick_x(0) == 1048 && stick_y(0) == 2048);
assert(reports[2][2] == 0 && stick_x(2) == 2048 && stick_y(2) == 3048);
assert(reports[3][2] == 0x01 && stick_x(3) == 2048 && stick_y(3) == 2048);
profiles[0].native_joycon_layout = ControllerProfileNativeJoyconLayout::kLeftSolo;
++profile_generation;
pair(1); consume(1); inactive_child(0);
assert(reports[1][2] == 0x04 && stick_x(1) == 3048 && stick_y(1) == 2048);
assert(reports[2][2] == 0 && stick_y(2) == 3048 && reports[3][2] == 0x01);
// A and B may select different solo sides without neutralizing each other.
profiles[1].native_joycon_layout = ControllerProfileNativeJoyconLayout::kRightSolo;
b.right_stick_x = INT16_MAX;
++profile_generation;
publish_both(false);
consume(2); consume(1); inactive_child(0); inactive_child(3);
assert(stick_x(1) == 3048 && stick_y(1) == 2048);
assert(stick_x(2) == 2048 && stick_y(2) == 3048);
a = {}; b = {};
profiles[0] = profiles[1] = controller_profile_default(controller_identity_global(), 0);
++profile_generation;
alternating_shortcuts[0] = alternating_shortcuts[1] = true;
shortcut_phases[0] = false;
shortcut_phases[1] = true;
for (unsigned round = 0; round < 4; ++round) {
publish_both(false);
consume(0); consume(2); consume(1); consume(3);
assert(reports[0][3] == reports[1][3] && reports[2][3] == reports[3][3]);
assert(reports[0][3] != reports[2][3]);
}
const uint8_t a_before = reports[0][3], b_before = reports[2][3];
// A's same-millisecond publication must re-evaluate A, not B; alternating
// slot-local transitions make both duplicate and missing evaluations visible.
publish_at_current_time(0, false);
consume(0); consume(2); consume(1); consume(3);
assert(reports[0][3] != a_before && reports[0][3] == reports[1][3]);
assert(reports[2][3] == b_before && reports[2][3] == reports[3][3]);
alternating_shortcuts[0] = alternating_shortcuts[1] = false;
}
void two_pair_transport_and_disconnect_isolation() {
prepare_two_sources(true);
sources[0].controller.state.button_south = true;
sources[1].controller.state.button_north = true;
publish_both();
uint32_t pending[PROBE_CONTROLLER_COUNT];
uint64_t cues[PROBE_CONTROLLER_COUNT];
for (uint8_t instance = 0; instance < PROBE_CONTROLLER_COUNT; ++instance) {
pending[instance] = peek(instance);
assert(pending[instance]);
assert(bluepad32_input_backend_native_sample_request(instance, 1, &cues[instance]));
}
assert(!probe_controller_input_commit_native_report(0, pending[2]));
uint8_t saved_b[2][63];
memcpy(saved_b, reports + 2, sizeof(saved_b));
sources[0].controller.active = false;
// Recheck the actual owning source, even before any poll sees its loss.
assert(!probe_controller_input_commit_native_report(0, pending[0]));
assert(!probe_controller_input_commit_native_report(1, pending[1]));
inactive_child(0); inactive_child(1);
for (uint8_t instance = 0; instance < 2; ++instance)
assert(bluepad32_input_backend_native_sample_result(instance, cues[instance]) == -1);
for (uint8_t instance = 2; instance < 4; ++instance) {
assert(bluepad32_input_backend_native_sample_result(instance, cues[instance]) == 1);
assert(peek(instance) == pending[instance]);
assert(memcmp(saved_b[instance - 2], reports[instance], 63) == 0);
assert(probe_controller_input_commit_native_report(instance, pending[instance]));
}
const uint32_t b_pending = peek(2);
sources[0].controller.active = true;
++sources[0].controller.connection_generation;
publish(true, 0); pair(0);
assert(reports[0][2] == 0x01 && reports[2][2] == 0x08);
assert(!probe_controller_input_commit_native_report(0, pending[0]));
assert(probe_controller_input_commit_native_report(2, b_pending));
// Repeated updates on three endpoints must neither consume a blocked
// endpoint's counter nor starve the other source's two endpoints.
publish_both();
const uint32_t blocked_left = peek(1);
const uint8_t left_counter = reports[1][0];
const uint32_t blocked_b = peek(2);
const uint8_t b_counter = reports[2][0];
for (unsigned update = 0; update < 40; ++update) {
sources[1].controller.state.button_east = (update & 1u) != 0;
publish_both(); consume(0); pair(1);
}
assert(!probe_controller_input_commit_native_report(1, blocked_left));
assert(!probe_controller_input_commit_native_report(2, blocked_b));
consume(1);
assert(reports[1][0] == left_counter);
assert(reports[2][0] == static_cast<uint8_t>(b_counter + 39));
assert(reports[2][2] == 0x0a);
probe_controller_input_set_native_stream(0, false);
assert(!peek(0));
publish_both();
const uint32_t left_pending = peek(1);
pair(1);
assert(probe_controller_input_commit_native_report(1, left_pending));
probe_controller_input_set_native_stream(0, true);
consume(0);
assert(reports[0][2] == 0x01);
// USB suspension also remains child-local on PairB.
publish_both();
const uint32_t a_pending = peek(0), b_left_pending = peek(3);
probe_controller_input_set_native_stream(2, false);
assert(!peek(2));
assert(probe_controller_input_commit_native_report(0, a_pending));
assert(probe_controller_input_commit_native_report(3, b_left_pending));
assert(bluepad32_input_backend_native_sample_result(2, cues[2]) == -1);
assert(bluepad32_input_backend_native_sample_result(3, cues[3]) == 1);
probe_controller_input_set_native_stream(2, true);
const uint32_t expires = peek(0);
// B stays live while A's queued report expires, then A's source times out.
for (unsigned update = 0; update < 26; ++update) { publish(true, 1); pair(1); }
assert(!probe_controller_input_commit_native_report(0, expires));
for (unsigned update = 0; update < 100; ++update) { publish(true, 1); pair(1); }
const uint32_t surviving_b = peek(2);
inactive_child(0); inactive_child(1);
assert(probe_controller_input_commit_native_report(2, surviving_b));
assert(controls[2].active && controls[3].active && reports[2][2] == 0x0a);
}
void two_pair_motion_provenance_and_resets() {
prepare_two_sources(true);
const uint8_t side = (SWITCH2_BRIDGE_IMU_TARGET_MASK & 1) ? 0 : 1;
const uint8_t a_imu = side, b_imu = 2 + side;
sources[0].gyro_q10[1] = 90 * 1024;
sources[1].gyro_q10[1] = -45 * 1024;
for (unsigned sample = 0; sample < 250; ++sample) {
publish_both();
consume(0); consume(2); consume(1); consume(3);
}
for (uint8_t instance = 0; instance < PROBE_CONTROLLER_COUNT; ++instance) {
const bool enabled = (SWITCH2_BRIDGE_IMU_TARGET_MASK & (1u << (instance & 1u))) != 0;
assert(imu_length(instance) == (enabled ? 30 : 0));
}
double a[4], b[4];
quaternion(a_imu, a); quaternion(b_imu, b);
assert(fabs(fabs(a[0]) - sqrt(.5)) < .015);
assert(fabs(fabs(b[0]) - cos(3.141592653589793 / 8)) < .015);
assert(a[3] * b[3] < 0); // Opposite physical yaw cannot share one integrator.
if (SWITCH2_BRIDGE_IMU_TARGET_MASK == 3) {
assert(memcmp(reports[0] + probe_model_imu_data_offset(0),
reports[1] + probe_model_imu_data_offset(1), 30) == 0);
assert(memcmp(reports[2] + probe_model_imu_data_offset(2),
reports[3] + probe_model_imu_data_offset(3), 30) == 0);
}
sources[0].gyro_q10[1] = sources[1].gyro_q10[1] = 0;
publish_both(); pair(0); pair(1);
quaternion(b_imu, b);
const uint8_t* b_block = reports[b_imu] + probe_model_imu_data_offset(b_imu);
const uint32_t b_ticks = bits(b_block, 0, 12);
publish(false, 1); pair(1);
publish(true, 0); pair(0);
pair(1);
assert(imu_length(2) == 0 && imu_length(3) == 0); // A cannot manufacture a B sample.
const uint32_t pending_a = peek(a_imu);
sources[0].controller.active = false;
inactive_child(0); inactive_child(1);
sources[0].controller.active = true;
++sources[0].controller.connection_generation;
publish(true, 0); pair(0);
assert(!probe_controller_input_commit_native_report(a_imu, pending_a));
quaternion(a_imu, a);
assert(fabs(fabs(a[0]) - 1) < 1e-6); // Only A reconnects at identity heading.
publish(true, 1); pair(1);
double after[4]; quaternion(b_imu, after);
for (unsigned axis = 0; axis < 4; ++axis) assert(fabs(after[axis] - b[axis]) < 1e-6);
b_block = reports[b_imu] + probe_model_imu_data_offset(b_imu);
assert(bits(b_block, 12, 12) == ((bits(b_block, 0, 12) - b_ticks) & 0xfffu));
// Reframing A to solo must not reset B's heading or in-flight motion.
profiles[0].native_joycon_layout = ControllerProfileNativeJoyconLayout::kLeftSolo;
++profile_generation;
publish_both();
const uint32_t b_pending = peek(b_imu);
uint8_t saved[63]; memcpy(saved, reports[b_imu], sizeof(saved));
consume(1); inactive_child(0);
assert(peek(b_imu) == b_pending && memcmp(saved, reports[b_imu], sizeof(saved)) == 0);
assert(probe_controller_input_commit_native_report(b_imu, b_pending));
quaternion(b_imu, after);
for (unsigned axis = 0; axis < 4; ++axis) assert(fabs(after[axis] - b[axis]) < 1e-6);
}
void recycled_slot_preserves_the_new_pairs_runtime() {
prepare_two_sources(false);
const uint32_t old_a = peek(0);
// A disconnects without another poll. B reconnects into A's recycled
// physical slot and starts a held synthetic action before A sees its loss.
sources[0].controller.active = false;
sources[1].slot = sources[0].slot;
++sources[1].controller.connection_generation;
sources[1].controller.state.button_select = true;
latching_shortcuts[1] = true;
publish(false, 1); pair(1);
assert(reports[2][3] == 1 && reports[3][3] == 1);
sources[1].controller.state.button_select = false;
publish(false, 1); pair(1);
const uint32_t pending_b = peek(2);
inactive_child(0); inactive_child(1);
assert(!probe_controller_input_commit_native_report(0, old_a));
assert(probe_controller_input_commit_native_report(2, pending_b));
// The next evaluation exposes accidental inactive-transform retirement;
// checking only the already-cached report would miss that runtime reset.
publish(false, 1); pair(1);
assert(reports[2][3] == 1 && reports[3][3] == 1);
sources[0].slot = 1;
sources[0].controller.active = true;
++sources[0].controller.connection_generation;
publish(false, 0); pair(0);
publish(false, 1); pair(1);
assert(reports[2][3] == 1 && reports[3][3] == 1);
latching_shortcuts[1] = false;
}
#endif
} // namespace
int main() {
assert(!probe_controller_input_peek_native_report(0, now_ms(), reports[0]));
probe_controller_input_init();
assert(probe_controller_input_start());
for (uint8_t instance = 0; instance < PROBE_CONTROLLER_COUNT; ++instance) {
probe_controller_input_set_native_stream(instance, true);
assert(!peek(instance));
}
mapped_halves_and_calibration();
independent_backpressure_and_resets();
if (SWITCH2_BRIDGE_IMU_TARGET_MASK == 3) real_motion_admission_and_loss();
selected_motion_target_keeps_both_control_halves();
wii_bias_and_independent_sensor_freshness();
nunchuk_buttons_map_to_native_left_shoulders();
solo_controls_and_explicit_rails();
profile_changes_retire_tokens_without_source_publication();
digital_dpad_reaches_the_mapped_left_stick();
solo_motion_rotates_coherently_and_resets_frame();
#if PROBE_CONTROLLER_COUNT == 4
two_pair_controls_and_profile_coherence();
two_pair_transport_and_disconnect_isolation();
two_pair_motion_provenance_and_resets();
recycled_slot_preserves_the_new_pairs_runtime();
#endif
return 0;
}