WIP: checkpoint Wii native IMU and upstream IR bridge

Pause Wii pointing work with firmware 0.33-wii-trace installed. Preserve the pinned libogc IR pipeline, calibrated native IMU, software BOOTSEL, and standard camera sensitivity trial.

Tracking instability, tracking loss, and ineffective vertical movement remain unresolved. Level-2 camera filtering is not hardware-qualified. Nine targeted regression tests pass; firmware and persistent storage were verified after flashing.
This commit is contained in:
Joey Yakimowich-Payne 2026-09-11 17:05:54 -06:00
commit 9f6dddb790
41 changed files with 7990 additions and 150 deletions

View file

@ -40,6 +40,10 @@ struct WiiAccelFixture {
};
WiiAccelFixture remote_accel_fixture;
WiiAccelFixture nunchuk_accel_fixture;
#ifdef SWITCH2_BRIDGE_WII_INPUT
WiiAccelFixture gyro_fixture;
bool wii_rumble_ready = true;
#endif
bool wii_accel_fixture_snapshot(const WiiAccelFixture& fixture,
uni_hid_device_t* controller,
@ -749,6 +753,17 @@ extern "C" bool uni_hid_parser_wii_nunchuk_accel_snapshot(
return wii_accel_fixture_snapshot(nunchuk_accel_fixture, controller, acceleration, sequence);
}
#ifdef SWITCH2_BRIDGE_WII_INPUT
extern "C" bool uni_hid_parser_wii_gyro_snapshot(
uni_hid_device_t* controller, int32_t gyro[3], uint32_t* sequence) {
return wii_accel_fixture_snapshot(gyro_fixture, controller, gyro, sequence);
}
extern "C" bool uni_hid_parser_wii_rumble_ready(uni_hid_device_t*) {
require(state_lock_depth == 0, "Wii dispatch readiness must be checked on Core 1 outside the state lock");
return wii_rumble_ready;
}
#endif
#ifdef SWITCH_PICO_HAPTICS_EXPERIMENT
namespace {
std::vector<btstack_timer_source_t*> native_timers;
@ -5605,6 +5620,186 @@ void dispatch_wii_requests() {
wii_dispatch_active = false;
}
#ifdef SWITCH2_BRIDGE_WII_INPUT
void test_wii_bridge_sensors() {
auto selected = wii_device(1);
auto other = wii_device(0);
bluepad32_input_backend_init();
bluepad32_input_backend_select_wii_source(selected.conn.btaddr);
start_pairing_backend();
require(platform_on_device_ready(&other) == UNI_ERROR_SUCCESS, "unselected Wii must remain available normally");
uni_controller_t input{};
input.klass = UNI_CONTROLLER_CLASS_GAMEPAD;
input.gamepad.buttons = BUTTON_A;
remote_accel_fixture = {&other, {8193, -42, 91}, 7, true};
gyro_fixture = {&other, {123456, -45678, 91011}, 11, true};
platform_on_controller_data(&other, &input);
Bluepad32WiiBridgeSnapshot snapshot{};
bluepad32_input_backend_wii_snapshot(&snapshot);
require(!snapshot.controller.active && snapshot.slot == 0xff,
"an unrelated Wii cannot claim the selected native source");
selected.controller_type = CONTROLLER_TYPE_UnknownController;
require(platform_on_device_ready(&selected) == UNI_ERROR_SUCCESS, "non-Wii address match must connect normally");
platform_on_controller_data(&selected, &input);
bluepad32_input_backend_wii_snapshot(&snapshot);
require(!snapshot.controller.active, "matching address alone must not identify a Wii");
platform_on_device_disconnected(&selected);
auto replacement = wii_device(1);
require(platform_on_device_ready(&replacement) == UNI_ERROR_SUCCESS, "selected physical Wii must become ready");
const uint32_t generation = slot_snapshot(1).connection_generation;
if (kDefaultMotionEnabled) {
require(bluepad32_input_backend_toggle_motion(1, generation), "legacy motion toggle must be independent");
}
remote_accel_fixture = {&replacement, {8193, -42, 91}, 7, true};
gyro_fixture = {&replacement, {123456, -45678, 91011}, 11, true};
now_ms = 100;
platform_on_controller_data(&replacement, &input);
bluepad32_input_backend_wii_snapshot(&snapshot);
require(snapshot.controller.active && snapshot.slot == 1 &&
snapshot.controller.state.button_south &&
snapshot.controller.state.motion_sample_count == 0 &&
snapshot.accel_valid && snapshot.gyro_valid &&
snapshot.accel_q13[0] == 8193 && snapshot.gyro_q10[0] == 123456,
"native raw precision and ordinary buttons must survive legacy motion disable");
require(snapshot.accel_received_us == 100000 && snapshot.gyro_received_us == 100000,
"both first genuine sensor samples need ingress timestamps");
now_ms = 250;
++remote_accel_fixture.sequence;
platform_on_controller_data(&replacement, &input);
now_ms = 300; // Status/ACK callback has no new sensor sequence.
platform_on_controller_data(&replacement, &input);
bluepad32_input_backend_report_sent(1);
bluepad32_input_backend_wii_snapshot(&snapshot);
require(snapshot.received_us == 300000 && snapshot.accel_received_us == 250000 &&
snapshot.gyro_received_us == 100000 && snapshot.gyro_sequence == 11,
"accel interleaves, status and USB consumption cannot rejuvenate gyro");
require(bluepad32_input_backend_set_wii_orientation(
snapshot.controller.identity, generation, true), "orientation change must queue");
dispatch_wii_requests();
platform_on_controller_data(&replacement, &input);
bluepad32_input_backend_wii_snapshot(&snapshot);
require(snapshot.controller.connection_generation != generation &&
!snapshot.accel_valid && !snapshot.gyro_valid &&
!snapshot.controller.accelerometer.valid,
"a logical epoch must not republish cached parser samples as fresh");
++remote_accel_fixture.sequence;
++gyro_fixture.sequence;
now_ms = 320;
platform_on_controller_data(&replacement, &input);
bluepad32_input_backend_wii_snapshot(&snapshot);
require(snapshot.accel_valid && snapshot.gyro_valid && snapshot.gyro_received_us == 320000,
"distinct samples must recover the new logical epoch");
platform_on_device_disconnected(&replacement);
bluepad32_input_backend_wii_snapshot(&snapshot);
require(!snapshot.controller.active && !snapshot.accel_valid && !snapshot.gyro_valid &&
snapshot.slot == 0xff, "disconnect must retire source identity and samples together");
}
void (*during_wii_rumble)() = nullptr;
void observe_wii_rumble(uni_hid_device_t* target, uint16_t delay,
uint16_t duration, uint8_t weak, uint8_t strong) {
require(state_lock_depth == 0, "Wii transport must run without the backend lock");
play_rumble(target, delay, duration, weak, strong);
if (during_wii_rumble != nullptr) during_wii_rumble();
}
void test_wii_bridge_cues() {
auto selected = wii_device(1);
selected.report_parser.play_dual_rumble = observe_wii_rumble;
auto other = wii_device(0);
bluepad32_input_backend_init();
bluepad32_input_backend_select_wii_source(selected.conn.btaddr);
start_pairing_backend();
require(platform_on_device_ready(&other) == UNI_ERROR_SUCCESS &&
platform_on_device_ready(&selected) == UNI_ERROR_SUCCESS, "cue fixtures must connect");
uint64_t token = 0;
require(bluepad32_input_backend_wii_sample_request(3, &token) &&
bluepad32_input_backend_wii_sample_result(token) == 0 &&
selected.rumble_calls == 0, "Core 0 queue acceptance is not cue completion");
process_rumble_timer(&g_rumble_timer);
require(bluepad32_input_backend_wii_sample_result(token) == 1 &&
bluepad32_input_backend_wii_sample_result(token) == -1 &&
selected.rumble_calls == 1 && selected.last_rumble_duration_ms == 25,
"double-click completion must be consumable once after its first driver dispatch");
now_ms = 60;
process_rumble_timer(&g_rumble_timer);
require(selected.rumble_calls == 1, "double-click gap must not vibrate");
now_ms = 115;
process_rumble_timer(&g_rumble_timer);
require(selected.rumble_calls == 2 && selected.last_rumble_duration_ms == 25 &&
other.rumble_calls == 0, "double-click must dispatch exactly its second selected-Wii pulse");
now_ms = 2000;
process_rumble_timer(&g_rumble_timer);
uint64_t newer;
require(bluepad32_input_backend_wii_sample_request(1, &newer) && newer != token,
"finite patterns must release the cue ledger without reusing tokens");
process_rumble_timer(&g_rumble_timer);
require(selected.last_rumble_duration_ms == 1000, "buzz must have a finite one-second driver timer");
require(bluepad32_input_backend_wii_sample_request(0, &token), "stop must replace a running pattern");
process_rumble_timer(&g_rumble_timer);
require(selected.last_rumble_duration_ms == 0 &&
bluepad32_input_backend_wii_sample_result(token) == 1 &&
bluepad32_input_backend_wii_sample_result(newer) == -1,
"stop must retire the old ledger and complete only after dispatch");
const int stopped_calls = selected.rumble_calls;
now_ms += 3000;
process_rumble_timer(&g_rumble_timer);
require(selected.rumble_calls == stopped_calls, "stopped patterns must never replay");
}
void test_wii_bridge_cue_races() {
auto selected = wii_device(0);
selected.report_parser.play_dual_rumble = observe_wii_rumble;
bluepad32_input_backend_init();
bluepad32_input_backend_select_wii_source(selected.conn.btaddr);
start_pairing_backend();
require(platform_on_device_ready(&selected) == UNI_ERROR_SUCCESS, "selected Wii must connect");
const auto connection = slot_snapshot(0);
bluepad32_input_backend_queue_profile_feedback(
0, connection.connection_generation, 1, ControllerProfileConfirmationPolicy::kRumble);
uint64_t token;
require(bluepad32_input_backend_wii_sample_request(3, &token), "cue must wait behind profile feedback");
process_rumble_timer(&g_rumble_timer);
require(selected.last_rumble_duration_ms == 75 &&
bluepad32_input_backend_wii_sample_result(token) == 0,
"profile confirmation has priority and cannot count as cue dispatch");
now_ms = 150;
during_wii_rumble = [] { bluepad32_input_backend_wii_sample_cancel(); };
process_rumble_timer(&g_rumble_timer);
during_wii_rumble = nullptr;
require(bluepad32_input_backend_wii_sample_result(token) == -1,
"cancellation during driver dispatch must defeat its late completion");
process_rumble_timer(&g_rumble_timer);
require(selected.last_rumble_duration_ms == 0, "canceled in-flight cue must receive a finite stop");
wii_rumble_ready = false;
require(bluepad32_input_backend_wii_sample_request(1, &token), "topology setup may defer cue dispatch");
const int calls = selected.rumble_calls;
process_rumble_timer(&g_rumble_timer);
require(bluepad32_input_backend_wii_sample_result(token) == 0 && selected.rumble_calls == calls,
"a Wii void-hook early return must not masquerade as dispatch");
now_ms += 2000;
require(bluepad32_input_backend_wii_sample_result(token) == -1, "undispatched cue deadline must expire");
wii_rumble_ready = true;
process_rumble_timer(&g_rumble_timer);
require(selected.rumble_calls == calls, "expired cue must not dispatch when setup finally completes");
uint64_t replacement_token;
require(bluepad32_input_backend_wii_sample_request(2, &replacement_token) && replacement_token != token,
"expiration must permit a uniquely identified new request");
during_wii_rumble = [] { platform_on_device_disconnected(g_slots[0].device); };
process_rumble_timer(&g_rumble_timer);
during_wii_rumble = nullptr;
auto replacement = wii_device(0);
require(platform_on_device_ready(&replacement) == UNI_ERROR_SUCCESS, "replacement must connect");
process_rumble_timer(&g_rumble_timer);
require(bluepad32_input_backend_wii_sample_result(replacement_token) == -1 &&
replacement.rumble_calls == 0,
"late driver completion must never cross a physical generation");
replacement.report_parser.play_dual_rumble = nullptr;
require(!bluepad32_input_backend_wii_sample_request(1, &token),
"a missing Wii rumble driver cannot accept a cue");
}
#endif
void test_wii_orientation() {
start_pairing_backend();
initialize_runtime_profile_storage();
@ -6021,6 +6216,20 @@ void test_transport_background_scan() {
int main(int argc, char** argv) {
require(argc == 2, "scenario argument required");
const std::string scenario = argv[1];
#ifdef SWITCH2_BRIDGE_WII_INPUT
if (scenario == "wii-bridge-sensors") {
test_wii_bridge_sensors();
return 0;
}
if (scenario == "wii-bridge-cues") {
test_wii_bridge_cues();
return 0;
}
if (scenario == "wii-bridge-cue-races") {
test_wii_bridge_cue_races();
return 0;
}
#endif
if (scenario == "transport-policy") {
test_transport_mode_policy();
return 0;