Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
186 lines
7.7 KiB
C++
186 lines
7.7 KiB
C++
#include "test_support.h"
|
|
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
|
|
#include "../../switch2_reports.h"
|
|
|
|
namespace {
|
|
|
|
SwitchInputState neutral_state() {
|
|
SwitchInputState state{};
|
|
state.lx = SWITCH_PRO_JOYSTICK_MID;
|
|
state.ly = SWITCH_PRO_JOYSTICK_MID;
|
|
state.rx = SWITCH_PRO_JOYSTICK_MID;
|
|
state.ry = SWITCH_PRO_JOYSTICK_MID;
|
|
return state;
|
|
}
|
|
|
|
bool bytes_are_zero(
|
|
const std::array<uint8_t, 63>& payload,
|
|
std::size_t begin,
|
|
std::size_t end) {
|
|
for (std::size_t index = begin; index < end; ++index) {
|
|
if (payload[index] != 0) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool switch2_neutral_reports_pack_documented_constants() {
|
|
// Given: neutral normalized input and distinct counters.
|
|
const SwitchInputState state = neutral_state();
|
|
|
|
// When: common and Pro reports are built.
|
|
const Switch2InputReport common = switch2_build_input_report(
|
|
Switch2InputReportId::Common, state, 0x78563412);
|
|
const Switch2InputReport pro = switch2_build_input_report(
|
|
Switch2InputReportId::Pro, state, 0x1234);
|
|
|
|
// Then: IDs, counters, neutral sticks, and documented constants are exact.
|
|
CHECK(static_cast<uint8_t>(common.id) == 0x05);
|
|
CHECK(static_cast<uint8_t>(pro.id) == 0x09);
|
|
CHECK(common.payload.size() + 1 == 64 && pro.payload.size() + 1 == 64);
|
|
CHECK(common.payload[0] == 0x12 && common.payload[1] == 0x34);
|
|
CHECK(common.payload[2] == 0x56 && common.payload[3] == 0x78);
|
|
CHECK(common.payload[10] == 0xFF && common.payload[11] == 0xF7 && common.payload[12] == 0x7F);
|
|
CHECK(common.payload[13] == 0xFF && common.payload[14] == 0xF7 && common.payload[15] == 0x7F);
|
|
CHECK(common.payload[0x29] == 0x01);
|
|
CHECK(pro.payload[0] == 0x34);
|
|
CHECK(pro.payload[1] == 0x01);
|
|
CHECK(pro.payload[5] == 0xFF && pro.payload[6] == 0xF7 && pro.payload[7] == 0x7F);
|
|
CHECK(pro.payload[8] == 0xFF && pro.payload[9] == 0xF7 && pro.payload[10] == 0x7F);
|
|
CHECK(pro.payload[11] == 0x30);
|
|
return true;
|
|
}
|
|
|
|
bool switch2_report_counters_roll_over_deterministically() {
|
|
// Given: counters at and beyond the Pro report's eight-bit boundary.
|
|
const SwitchInputState state = neutral_state();
|
|
|
|
// When: reports are built at 255 and 256.
|
|
const Switch2InputReport common = switch2_build_input_report(
|
|
Switch2InputReportId::Common, state, 0xFFFFFFFF);
|
|
const Switch2InputReport pro_255 = switch2_build_input_report(
|
|
Switch2InputReportId::Pro, state, 255);
|
|
const Switch2InputReport pro_256 = switch2_build_input_report(
|
|
Switch2InputReportId::Pro, state, 256);
|
|
|
|
// Then: common remains LE32 while Pro uses the low eight bits.
|
|
CHECK(common.payload[0] == 0xFF && common.payload[1] == 0xFF);
|
|
CHECK(common.payload[2] == 0xFF && common.payload[3] == 0xFF);
|
|
CHECK(pro_255.payload[0] == 0xFF);
|
|
CHECK(pro_256.payload[0] == 0x00);
|
|
return true;
|
|
}
|
|
|
|
bool switch2_reports_map_every_shared_button() {
|
|
struct ButtonCase {
|
|
bool SwitchInputState::*field;
|
|
uint8_t common_offset;
|
|
uint8_t common_mask;
|
|
uint8_t pro_offset;
|
|
uint8_t pro_mask;
|
|
};
|
|
static constexpr ButtonCase cases[] = {
|
|
{&SwitchInputState::button_y, 4, 0x01, 2, 0x04},
|
|
{&SwitchInputState::button_b, 4, 0x04, 2, 0x01},
|
|
{&SwitchInputState::button_a, 4, 0x08, 2, 0x02},
|
|
{&SwitchInputState::button_x, 4, 0x02, 2, 0x08},
|
|
{&SwitchInputState::button_r, 4, 0x40, 2, 0x10},
|
|
{&SwitchInputState::button_zr, 4, 0x80, 2, 0x20},
|
|
{&SwitchInputState::button_minus, 5, 0x01, 3, 0x40},
|
|
{&SwitchInputState::button_plus, 5, 0x02, 2, 0x40},
|
|
{&SwitchInputState::button_r3, 5, 0x04, 2, 0x80},
|
|
{&SwitchInputState::button_l3, 5, 0x08, 3, 0x80},
|
|
{&SwitchInputState::button_home, 5, 0x10, 4, 0x01},
|
|
{&SwitchInputState::button_capture, 5, 0x20, 4, 0x02},
|
|
{&SwitchInputState::dpad_down, 6, 0x01, 3, 0x01},
|
|
{&SwitchInputState::dpad_up, 6, 0x02, 3, 0x08},
|
|
{&SwitchInputState::dpad_right, 6, 0x04, 3, 0x02},
|
|
{&SwitchInputState::dpad_left, 6, 0x08, 3, 0x04},
|
|
{&SwitchInputState::button_l, 6, 0x40, 3, 0x10},
|
|
{&SwitchInputState::button_zl, 6, 0x80, 3, 0x20},
|
|
};
|
|
|
|
// Given/When: every shared button is built independently in both reports.
|
|
for (const ButtonCase& button : cases) {
|
|
SwitchInputState state = neutral_state();
|
|
state.*(button.field) = true;
|
|
const Switch2InputReport common = switch2_build_input_report(
|
|
Switch2InputReportId::Common, state, 0);
|
|
const Switch2InputReport pro = switch2_build_input_report(
|
|
Switch2InputReportId::Pro, state, 0);
|
|
|
|
// Then: only the captured byte and bit for that button is set.
|
|
for (uint8_t offset = 4; offset <= 7; ++offset) {
|
|
CHECK(common.payload[offset] ==
|
|
(offset == button.common_offset ? button.common_mask : 0));
|
|
}
|
|
for (uint8_t offset = 2; offset <= 4; ++offset) {
|
|
CHECK(pro.payload[offset] ==
|
|
(offset == button.pro_offset ? button.pro_mask : 0));
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool switch2_reports_pack_twelve_bit_stick_extremes() {
|
|
// Given: four distinct normalized axis values.
|
|
SwitchInputState state{};
|
|
state.lx = 0x0000;
|
|
state.ly = 0xFFFF;
|
|
state.rx = 0x1234;
|
|
state.ry = 0xABCD;
|
|
|
|
// When: both report formats are built.
|
|
const Switch2InputReport common = switch2_build_input_report(
|
|
Switch2InputReportId::Common, state, 0);
|
|
const Switch2InputReport pro = switch2_build_input_report(
|
|
Switch2InputReportId::Pro, state, 0);
|
|
|
|
// Then: axes are reduced and packed in captured 12-bit little-endian form.
|
|
const uint8_t expected_left[] = {0x00, 0xF0, 0xFF};
|
|
const uint8_t expected_right[] = {0x23, 0xC1, 0xAB};
|
|
for (uint8_t index = 0; index < 3; ++index) {
|
|
CHECK(common.payload[10 + index] == expected_left[index]);
|
|
CHECK(common.payload[13 + index] == expected_right[index]);
|
|
CHECK(pro.payload[5 + index] == expected_left[index]);
|
|
CHECK(pro.payload[8 + index] == expected_right[index]);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool switch2_reports_zero_unknowns_and_ignore_imu() {
|
|
// Given: populated IMU input that has no known Switch 2 packing.
|
|
SwitchInputState state = neutral_state();
|
|
state.imu_sample_count = 3;
|
|
state.imu_samples[0] = {1, 2, 3, 4, 5, 6};
|
|
state.imu_samples[1] = {7, 8, 9, 10, 11, 12};
|
|
state.imu_samples[2] = {13, 14, 15, 16, 17, 18};
|
|
|
|
// When: both reports are built twice from identical input.
|
|
const Switch2InputReport common = switch2_build_input_report(
|
|
Switch2InputReportId::Common, state, 7);
|
|
const Switch2InputReport common_again = switch2_build_input_report(
|
|
Switch2InputReportId::Common, state, 7);
|
|
const Switch2InputReport pro = switch2_build_input_report(
|
|
Switch2InputReportId::Pro, state, 7);
|
|
|
|
// Then: unknown power/sensor/motion fields stay deterministic and zero.
|
|
CHECK(common.payload == common_again.payload);
|
|
CHECK(bytes_are_zero(common.payload, 8, 10));
|
|
CHECK(bytes_are_zero(common.payload, 16, 0x29));
|
|
CHECK(bytes_are_zero(common.payload, 0x2A, 63));
|
|
CHECK(bytes_are_zero(pro.payload, 12, 63));
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void run_switch2_report_tests(TestRunner& runner) {
|
|
runner.run("Switch 2 neutral report constants", switch2_neutral_reports_pack_documented_constants);
|
|
runner.run("Switch 2 report counter rollover", switch2_report_counters_roll_over_deterministically);
|
|
runner.run("Switch 2 report button mapping", switch2_reports_map_every_shared_button);
|
|
runner.run("Switch 2 report stick packing", switch2_reports_pack_twelve_bit_stick_extremes);
|
|
runner.run("Switch 2 report unknown fields", switch2_reports_zero_unknowns_and_ignore_imu);
|
|
}
|