feat: preserve native HD rumble on Switch 2 controllers
This commit is contained in:
parent
a87917bf0b
commit
006b573ee8
25 changed files with 1722 additions and 60 deletions
|
|
@ -220,6 +220,9 @@ static uint8_t capture_write(hci_con_handle_t handle, uint16_t value_handle, uin
|
|||
assert(value_handle == RUMBLE_HANDLE && length <= sizeof(peer->rumble));
|
||||
memcpy(peer->rumble, value, length);
|
||||
peer->rumble_length = length;
|
||||
unsigned slot = peer->rumbles % FIXTURE_RUMBLE_HISTORY;
|
||||
memcpy(peer->rumble_history[slot], value, length);
|
||||
peer->rumble_times[slot] = now_ms;
|
||||
++peer->rumbles;
|
||||
}
|
||||
return 0;
|
||||
|
|
@ -235,6 +238,7 @@ uint8_t gatt_client_write_value_of_characteristic(btstack_packet_handler_t callb
|
|||
struct fixture_peer* peer = peer_for_handle(handle);
|
||||
peer->pending_write = value;
|
||||
peer->pending_length = length;
|
||||
memcpy(peer->pending_snapshot, value, length);
|
||||
return begin_query(callback, handle, QUERY_WRITE);
|
||||
}
|
||||
|
||||
|
|
@ -269,6 +273,8 @@ static void query_done(struct fixture_peer* peer, uint8_t status) {
|
|||
uint8_t data[9] = {GATT_EVENT_QUERY_COMPLETE};
|
||||
if (peer->query == QUERY_CCCD)
|
||||
assert(peer->pending_length == 2 && peer->pending_write[0] == 1 && peer->pending_write[1] == 0);
|
||||
if (peer->query == QUERY_WRITE)
|
||||
assert(memcmp(peer->pending_write, peer->pending_snapshot, peer->pending_length) == 0);
|
||||
peer->query = QUERY_NONE;
|
||||
data[8] = status;
|
||||
event(peer, data, sizeof(data));
|
||||
|
|
@ -617,7 +623,7 @@ static void test_rumble_delay_expiry_retry_and_teardown(void) {
|
|||
advance(24);
|
||||
assert(!rumble_active(peer));
|
||||
uni_hid_parser_switch2_play_dual_rumble(&peer->device, 0, UINT16_MAX, 12, 34);
|
||||
advance(70000); // Stateful host rumble does not expire at 65.535 seconds.
|
||||
advance(70000); // Held local feedback does not expire at65.535 seconds.
|
||||
assert(((rumble_frame(peer) >> 10) & 1023) == 136 && ((rumble_frame(peer) >> 30) & 1023) == 48);
|
||||
uni_hid_parser_switch2_play_dual_rumble(&peer->device, 0, 100, 255, 255);
|
||||
advance(1);
|
||||
|
|
@ -664,6 +670,360 @@ static void test_write_request_buffers_and_failed_completion(void) {
|
|||
assert(peer->rumbles == writes);
|
||||
}
|
||||
|
||||
static struct fixture_peer* ready_peer(uint16_t pid, bool requests) {
|
||||
reset();
|
||||
request_writes = requests;
|
||||
struct fixture_peer* peer = connect_peer(pid, false);
|
||||
discover(peer);
|
||||
subscribe_response(peer);
|
||||
finish_setup(peer);
|
||||
return peer;
|
||||
}
|
||||
|
||||
static uni_switch2_haptics_frame_t native_frame(unsigned left, unsigned right, unsigned seed) {
|
||||
uni_switch2_haptics_frame_t frame = {0};
|
||||
frame.sides[0].count = left;
|
||||
frame.sides[1].count = right;
|
||||
for (unsigned side = 0; side < 2; ++side) {
|
||||
for (unsigned i = 0; i < frame.sides[side].count; ++i) {
|
||||
uni_switch2_haptics_encode_sample(frame.sides[side].samples[i],
|
||||
10 + seed + 5 * i + side, 80 + seed + i + side,
|
||||
1000 + seed * 100 + i * 200 + side * 3000,
|
||||
5000 + seed * 100 + i * 700 + side * 500);
|
||||
}
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
|
||||
static uni_switch2_haptics_side_t final_hold(const uni_switch2_haptics_side_t* source) {
|
||||
uni_switch2_haptics_side_t side = {.count = 1};
|
||||
memcpy(side.samples[0], source->samples[source->count - 1], 5);
|
||||
return side;
|
||||
}
|
||||
|
||||
static void assert_block(const struct fixture_peer* peer, unsigned side,
|
||||
const uni_switch2_haptics_side_t* expected) {
|
||||
assert(peer->rumble_length >= 17 + side * 16);
|
||||
const uint8_t* block = peer->rumble + 1 + side * 16;
|
||||
assert((block[0] & 0xf0) == (0x40 | (expected->count << 4)));
|
||||
assert(memcmp(block + 1, expected->samples, 5 * expected->count) == 0);
|
||||
for (unsigned i = 1 + 5 * expected->count; i < 16; ++i)
|
||||
assert(block[i] == 0);
|
||||
}
|
||||
|
||||
static void assert_silent(const struct fixture_peer* peer, unsigned side) {
|
||||
const uint8_t* block = peer->rumble + 1 + side * 16;
|
||||
assert((block[0] & 0xf0) == 0x50);
|
||||
assert(!(block[2] & 0xfc) && !(block[3] & 0x0f) && !(block[4] & 0xc0) && !block[5]);
|
||||
for (unsigned i = 6; i < 16; ++i)
|
||||
assert(block[i] == 0);
|
||||
}
|
||||
|
||||
static void test_native_fifo_stereo_counts_and_retry(void) {
|
||||
struct fixture_peer* peer = ready_peer(UNI_SW2_PRO_PID, false);
|
||||
uni_switch2_haptics_frame_t a = native_frame(3, 2, 1);
|
||||
uni_switch2_haptics_frame_t b = native_frame(2, 0, 2);
|
||||
uni_switch2_haptics_frame_t c = native_frame(1, 3, 3);
|
||||
uint32_t drops = uni_hid_parser_switch2_haptics_dropped();
|
||||
assert(uni_hid_parser_switch2_queue_haptics(&peer->device, &a, now_ms));
|
||||
assert(uni_hid_parser_switch2_queue_haptics(&peer->device, &b, now_ms));
|
||||
next_write_error = BTSTACK_ACL_BUFFERS_FULL;
|
||||
advance(1);
|
||||
assert(peer->rumbles == 0);
|
||||
advance(13);
|
||||
assert(peer->rumbles == 1 && peer->rumble_length == 33);
|
||||
assert(peer->rumble[1] == 0x70 && peer->rumble[17] == 0x60);
|
||||
assert_block(peer, 0, &a.sides[0]);
|
||||
assert_block(peer, 1, &a.sides[1]);
|
||||
// C arrives while A plays, with enough source lifetime for all three frames.
|
||||
assert(uni_hid_parser_switch2_queue_haptics(&peer->device, &c, now_ms));
|
||||
advance(15);
|
||||
assert(peer->rumbles == 1); // Three samples cannot be interrupted at13ms.
|
||||
advance(1);
|
||||
assert(peer->rumbles == 2 && peer->rumble[1] == 0x61);
|
||||
assert_block(peer, 0, &b.sides[0]);
|
||||
uni_switch2_haptics_side_t right = final_hold(&a.sides[1]);
|
||||
assert_block(peer, 1, &right);
|
||||
advance(10);
|
||||
assert(peer->rumbles == 2);
|
||||
advance(1);
|
||||
assert(peer->rumbles == 3 && peer->rumble[1] == 0x52 && peer->rumble[17] == 0x72);
|
||||
assert_block(peer, 0, &c.sides[0]);
|
||||
assert_block(peer, 1, &c.sides[1]);
|
||||
assert(peer->rumble_times[1] - peer->rumble_times[0] == 16);
|
||||
assert(peer->rumble_times[2] - peer->rumble_times[1] == 11);
|
||||
assert(uni_hid_parser_switch2_haptics_dropped() == drops);
|
||||
advance(23); // C expires at receipt14 +50, not transmission41 +50.
|
||||
assert_silent(peer, 0);
|
||||
assert_silent(peer, 1);
|
||||
}
|
||||
|
||||
static void test_native_hold_and_absent_side_watchdogs(void) {
|
||||
struct fixture_peer* peer = ready_peer(UNI_SW2_PRO_PID, false);
|
||||
uni_switch2_haptics_frame_t a = native_frame(3, 2, 4);
|
||||
assert(uni_hid_parser_switch2_queue_haptics(&peer->device, &a, now_ms));
|
||||
advance(1);
|
||||
assert_block(peer, 0, &a.sides[0]);
|
||||
advance(15);
|
||||
assert(peer->rumbles == 1);
|
||||
advance(1);
|
||||
uni_switch2_haptics_side_t left = final_hold(&a.sides[0]);
|
||||
uni_switch2_haptics_side_t right = final_hold(&a.sides[1]);
|
||||
assert(peer->rumbles == 2);
|
||||
assert_block(peer, 0, &left);
|
||||
assert_block(peer, 1, &right);
|
||||
advance(3); // t20; only the left actuator receives a fresh source update.
|
||||
uni_switch2_haptics_frame_t b = native_frame(1, 0, 5);
|
||||
assert(uni_hid_parser_switch2_queue_haptics(&peer->device, &b, now_ms));
|
||||
advance(3); // Last count1 hold guarded until t23.
|
||||
assert_block(peer, 0, &b.sides[0]);
|
||||
assert_block(peer, 1, &right);
|
||||
advance(27);
|
||||
assert_block(peer, 0, &b.sides[0]);
|
||||
assert_silent(peer, 1); // Absent side must not gain a new50ms watchdog.
|
||||
advance(20);
|
||||
assert_silent(peer, 0);
|
||||
assert_silent(peer, 1);
|
||||
}
|
||||
|
||||
static void test_feedback_advances_host_and_resumes_only_final_samples(void) {
|
||||
struct fixture_peer* peer = ready_peer(UNI_SW2_PRO_PID, false);
|
||||
uint32_t drops = uni_hid_parser_switch2_haptics_dropped();
|
||||
uni_switch2_haptics_frame_t a = native_frame(2, 3, 6);
|
||||
uni_hid_parser_switch2_play_dual_rumble(&peer->device, 0, 40, 20, 30);
|
||||
assert(uni_hid_parser_switch2_queue_haptics(&peer->device, &a, now_ms));
|
||||
advance(1);
|
||||
assert(((rumble_frame(peer) >> 10) & 1023) == 120);
|
||||
assert((peer->rumble[1] & 0xf0) == 0x50);
|
||||
advance(9);
|
||||
uni_switch2_haptics_frame_t b = native_frame(3, 0, 7);
|
||||
assert(uni_hid_parser_switch2_queue_haptics(&peer->device, &b, now_ms));
|
||||
advance(1);
|
||||
assert(((rumble_frame(peer) >> 10) & 1023) == 120);
|
||||
uni_hid_parser_switch2_play_dual_rumble(&peer->device, 0, 0, 0, 0);
|
||||
advance(1);
|
||||
uni_switch2_haptics_side_t left = final_hold(&b.sides[0]);
|
||||
uni_switch2_haptics_side_t right = final_hold(&a.sides[1]);
|
||||
assert_block(peer, 0, &left);
|
||||
assert_block(peer, 1, &right);
|
||||
for (unsigned i = 0; i < peer->rumbles; ++i) {
|
||||
assert((peer->rumble_history[i][1] & 0xf0) == 0x50);
|
||||
assert((peer->rumble_history[i][17] & 0xf0) == 0x50);
|
||||
}
|
||||
uni_hid_parser_switch2_play_dual_rumble(&peer->device, 0, 100, 50, 60);
|
||||
advance(48); // Both original host watchdogs elapse under feedback.
|
||||
uni_hid_parser_switch2_play_dual_rumble(&peer->device, 0, 0, 0, 0);
|
||||
advance(1);
|
||||
assert_silent(peer, 0);
|
||||
assert_silent(peer, 1);
|
||||
assert(uni_hid_parser_switch2_haptics_dropped() == drops);
|
||||
}
|
||||
|
||||
static void test_full_fifo_stop_barrier_and_async_completion(void) {
|
||||
struct fixture_peer* peer = ready_peer(UNI_SW2_JOYCON_R_PID, true);
|
||||
uni_switch2_haptics_frame_t a = native_frame(3, 0, 8);
|
||||
uni_switch2_haptics_frame_t b = native_frame(2, 0, 9);
|
||||
uint32_t drops = uni_hid_parser_switch2_haptics_dropped();
|
||||
assert(uni_hid_parser_switch2_queue_haptics(&peer->device, &a, now_ms));
|
||||
advance(1);
|
||||
assert(peer->query == QUERY_WRITE && peer->rumble_length == 17);
|
||||
const uint8_t* borrowed = peer->pending_write;
|
||||
uint8_t snapshot[17];
|
||||
memcpy(snapshot, borrowed, sizeof(snapshot));
|
||||
for (unsigned i = 1; i < 16; ++i)
|
||||
assert(uni_hid_parser_switch2_queue_haptics(&peer->device, &a, now_ms));
|
||||
assert(!uni_hid_parser_switch2_queue_haptics(&peer->device, &a, now_ms));
|
||||
assert(uni_hid_parser_switch2_haptics_dropped() == drops);
|
||||
uni_switch2_haptics_frame_t stop;
|
||||
uni_switch2_haptics_silence(&stop);
|
||||
stop.sides[1].count = 0; // Physical Joy-Con stop is not a logical stereo stop.
|
||||
assert(uni_hid_parser_switch2_queue_haptics(&peer->device, &stop, now_ms - 100));
|
||||
assert(uni_hid_parser_switch2_haptics_dropped() == drops + 16);
|
||||
assert(uni_hid_parser_switch2_queue_haptics(&peer->device, &b, now_ms));
|
||||
advance(1);
|
||||
assert(memcmp(snapshot, borrowed, sizeof(snapshot)) == 0);
|
||||
query_done(peer, 0); // Completion of old epoch must not consume b/the stop.
|
||||
advance(1);
|
||||
assert(peer->rumbles == 2 && peer->rumble[1] == 0x51);
|
||||
assert_silent(peer, 0);
|
||||
query_done(peer, 0);
|
||||
advance(6);
|
||||
assert(peer->rumbles == 3 && peer->rumble[1] == 0x62);
|
||||
assert_block(peer, 0, &b.sides[0]);
|
||||
query_done(peer, 0);
|
||||
advance(11);
|
||||
uni_switch2_haptics_side_t hold = final_hold(&b.sides[0]);
|
||||
assert_block(peer, 0, &hold);
|
||||
query_done(peer, 0);
|
||||
}
|
||||
|
||||
static void test_async_expiry_and_topology_reset_do_not_revive_state(void) {
|
||||
struct fixture_peer* peer = ready_peer(UNI_SW2_PRO_PID, true);
|
||||
uni_switch2_haptics_frame_t a = native_frame(3, 2, 10);
|
||||
uint32_t drops = uni_hid_parser_switch2_haptics_dropped();
|
||||
assert(uni_hid_parser_switch2_queue_haptics(&peer->device, &a, now_ms));
|
||||
advance(1);
|
||||
advance(49); // Write request is still borrowing the original batch.
|
||||
assert(peer->rumbles == 1 && uni_hid_parser_switch2_haptics_dropped() == drops + 1);
|
||||
query_done(peer, 0);
|
||||
advance(1);
|
||||
assert_silent(peer, 0);
|
||||
assert_silent(peer, 1);
|
||||
query_done(peer, 0);
|
||||
uni_hid_parser_switch2_play_dual_rumble(&peer->device, 0, UINT16_MAX, 40, 50);
|
||||
advance(6);
|
||||
assert(rumble_active(peer) && peer->query == QUERY_WRITE);
|
||||
uni_switch2_haptics_frame_t b = native_frame(2, 1, 11);
|
||||
assert(uni_hid_parser_switch2_queue_haptics(&peer->device, &a, now_ms));
|
||||
uni_hid_parser_switch2_reset_haptics(&peer->device);
|
||||
assert(uni_hid_parser_switch2_queue_haptics(&peer->device, &b, now_ms));
|
||||
advance(1);
|
||||
query_done(peer, 0); // Old local overlay must not clear the reset neutral.
|
||||
advance(1);
|
||||
assert_silent(peer, 0);
|
||||
assert_silent(peer, 1);
|
||||
query_done(peer, 0);
|
||||
advance(6);
|
||||
assert_block(peer, 0, &b.sides[0]);
|
||||
assert_block(peer, 1, &b.sides[1]);
|
||||
query_done(peer, 0);
|
||||
}
|
||||
|
||||
static void test_host_stop_does_not_cancel_local_feedback(void) {
|
||||
struct fixture_peer* peer = ready_peer(UNI_SW2_PRO_PID, false);
|
||||
uni_hid_parser_switch2_play_dual_rumble(&peer->device, 0, UINT16_MAX, 100, 200);
|
||||
uni_switch2_haptics_frame_t a = native_frame(3, 3, 12);
|
||||
assert(uni_hid_parser_switch2_queue_haptics(&peer->device, &a, now_ms));
|
||||
advance(1);
|
||||
assert(uni_hid_parser_switch2_queue_rumble(&peer->device, 0, 0, 0, now_ms));
|
||||
advance(1);
|
||||
assert(((rumble_frame(peer) >> 10) & 1023) == 800);
|
||||
uni_hid_parser_switch2_play_dual_rumble(&peer->device, 0, 0, 0, 0);
|
||||
advance(1);
|
||||
assert_silent(peer, 0);
|
||||
assert_silent(peer, 1);
|
||||
}
|
||||
|
||||
static void test_conventional_host_lifetimes_and_invalid_native_input(void) {
|
||||
struct fixture_peer* peer = ready_peer(UNI_SW2_JOYCON_L_PID, false);
|
||||
assert(uni_hid_parser_switch2_queue_rumble(&peer->device, 255, 200, UINT16_MAX, now_ms));
|
||||
advance(70000);
|
||||
assert((rumble_frame(peer) & 1023) == 0xe1);
|
||||
assert(((rumble_frame(peer) >> 20) & 1023) == 0x1e1);
|
||||
assert(((rumble_frame(peer) >> 10) & 1023) == 800);
|
||||
assert(((rumble_frame(peer) >> 30) & 1023) == 1020);
|
||||
assert((peer->rumble[1] & 0xf0) == 0x50);
|
||||
for (unsigned i = 7; i < 17; ++i)
|
||||
assert(peer->rumble[i] == 0);
|
||||
assert(uni_hid_parser_switch2_queue_rumble(&peer->device, 1, 2, 20, now_ms - 10));
|
||||
advance(9);
|
||||
assert(((rumble_frame(peer) >> 10) & 1023) == 8);
|
||||
advance(1);
|
||||
assert_silent(peer, 0);
|
||||
uint32_t drops = uni_hid_parser_switch2_haptics_dropped();
|
||||
uni_switch2_haptics_frame_t invalid = native_frame(1, 0, 13);
|
||||
invalid.sides[0].count = 4;
|
||||
assert(uni_hid_parser_switch2_queue_haptics(&peer->device, &invalid, now_ms));
|
||||
uni_switch2_haptics_frame_t stale = native_frame(1, 0, 14);
|
||||
assert(uni_hid_parser_switch2_queue_haptics(&peer->device, &stale, now_ms - 50));
|
||||
assert(uni_hid_parser_switch2_queue_rumble(&peer->device, 10, 20, 100, now_ms - 50));
|
||||
assert(uni_hid_parser_switch2_haptics_dropped() == drops + 3);
|
||||
advance(13);
|
||||
assert_silent(peer, 0);
|
||||
}
|
||||
|
||||
static void test_native_watchdog_clock_wrap(void) {
|
||||
reset();
|
||||
now_ms = UINT32_MAX - 20;
|
||||
struct fixture_peer* peer = connect_peer(UNI_SW2_JOYCON_L_PID, false);
|
||||
discover(peer);
|
||||
subscribe_response(peer);
|
||||
finish_setup(peer);
|
||||
uni_switch2_haptics_frame_t frame = native_frame(1, 0, 15);
|
||||
assert(uni_hid_parser_switch2_queue_haptics(&peer->device, &frame, now_ms));
|
||||
advance(49);
|
||||
assert_block(peer, 0, &frame.sides[0]);
|
||||
advance(1);
|
||||
assert_silent(peer, 0);
|
||||
}
|
||||
|
||||
static void test_identical_hold_coalescing_refreshes_borrowed_head(void) {
|
||||
struct fixture_peer* peer = ready_peer(UNI_SW2_JOYCON_L_PID, true);
|
||||
uni_switch2_haptics_frame_t frame = native_frame(1, 0, 16);
|
||||
uint32_t drops = uni_hid_parser_switch2_haptics_dropped();
|
||||
assert(uni_hid_parser_switch2_queue_haptics(&peer->device, &frame, now_ms));
|
||||
advance(1);
|
||||
const uint8_t* borrowed = peer->pending_write;
|
||||
uint8_t snapshot[17];
|
||||
memcpy(snapshot, borrowed, sizeof(snapshot));
|
||||
advance(39);
|
||||
for (unsigned i = 0; i < 32; ++i)
|
||||
assert(uni_hid_parser_switch2_queue_haptics(&peer->device, &frame, now_ms));
|
||||
assert(memcmp(snapshot, borrowed, sizeof(snapshot)) == 0);
|
||||
advance(10); // The original t0 hold would expire now without source refresh.
|
||||
assert(uni_hid_parser_switch2_haptics_dropped() == drops);
|
||||
query_done(peer, 0);
|
||||
advance(39);
|
||||
assert_block(peer, 0, &frame.sides[0]);
|
||||
query_done(peer, 0);
|
||||
advance(1); // Refreshed deadline remains t40+50, not ATT completion+50.
|
||||
assert_silent(peer, 0);
|
||||
assert(uni_hid_parser_switch2_haptics_dropped() == drops);
|
||||
query_done(peer, 0);
|
||||
}
|
||||
|
||||
static void test_hold_coalescing_requires_identical_samples_and_side_masks(void) {
|
||||
struct fixture_peer* peer = ready_peer(UNI_SW2_PRO_PID, true);
|
||||
uni_switch2_haptics_frame_t frame;
|
||||
for (unsigned i = 0; i < 16; ++i) {
|
||||
frame = native_frame(1, 1, i + 1);
|
||||
assert(uni_hid_parser_switch2_queue_haptics(&peer->device, &frame, now_ms));
|
||||
}
|
||||
// An identical tail hold can refresh even at capacity. A partial-side update
|
||||
// is a different command, as is a changed sample; neither may erase history.
|
||||
assert(uni_hid_parser_switch2_queue_haptics(&peer->device, &frame, now_ms));
|
||||
frame.sides[1].count = 0;
|
||||
assert(!uni_hid_parser_switch2_queue_haptics(&peer->device, &frame, now_ms));
|
||||
frame = native_frame(1, 1, 17);
|
||||
assert(!uni_hid_parser_switch2_queue_haptics(&peer->device, &frame, now_ms));
|
||||
advance(1);
|
||||
uni_switch2_haptics_frame_t first = native_frame(1, 1, 1);
|
||||
assert_block(peer, 0, &first.sides[0]);
|
||||
assert_block(peer, 1, &first.sides[1]);
|
||||
}
|
||||
|
||||
static void test_expired_history_does_not_starve_fresh_sequences(void) {
|
||||
struct fixture_peer* peer = ready_peer(UNI_SW2_PRO_PID, false);
|
||||
uni_switch2_haptics_frame_t a = native_frame(3, 3, 20);
|
||||
uni_switch2_haptics_frame_t stale = native_frame(3, 3, 21);
|
||||
uni_switch2_haptics_frame_t fresh = native_frame(3, 3, 22);
|
||||
assert(uni_hid_parser_switch2_queue_haptics(&peer->device, &a, now_ms));
|
||||
advance(1);
|
||||
unsigned sent = peer->rumbles;
|
||||
assert(uni_hid_parser_switch2_queue_haptics(&peer->device, &stale, now_ms - 40));
|
||||
assert(uni_hid_parser_switch2_queue_haptics(&peer->device, &fresh, now_ms));
|
||||
advance(10);
|
||||
assert(peer->rumbles == sent); // Unplayed stale history must not interrupt A.
|
||||
advance(6);
|
||||
assert_block(peer, 0, &fresh.sides[0]);
|
||||
assert_block(peer, 1, &fresh.sides[1]);
|
||||
|
||||
peer = ready_peer(UNI_SW2_PRO_PID, false);
|
||||
a = native_frame(1, 1, 23);
|
||||
assert(uni_hid_parser_switch2_queue_haptics(&peer->device, &a, now_ms - 43));
|
||||
advance(1);
|
||||
assert(uni_hid_parser_switch2_queue_haptics(&peer->device, &fresh, now_ms));
|
||||
advance(6); // A's source watchdog expires as its playback guard ends.
|
||||
assert_block(peer, 0, &fresh.sides[0]); // No unnecessary silent/HOLD packet.
|
||||
|
||||
peer = ready_peer(UNI_SW2_PRO_PID, false);
|
||||
uint32_t drops = uni_hid_parser_switch2_haptics_dropped();
|
||||
assert(uni_hid_parser_switch2_queue_haptics(&peer->device, &fresh, now_ms - 40));
|
||||
advance(1);
|
||||
assert_silent(peer, 0); // Nine ms cannot contain a complete three-frame batch.
|
||||
assert(uni_hid_parser_switch2_haptics_dropped() == drops + 1);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
test_connected_callback_rejection();
|
||||
test_advertisement_bounds_and_admission();
|
||||
|
|
@ -672,6 +1032,17 @@ int main(void) {
|
|||
test_calibration_physical_inputs_and_sensor_units();
|
||||
test_rumble_delay_expiry_retry_and_teardown();
|
||||
test_write_request_buffers_and_failed_completion();
|
||||
test_native_fifo_stereo_counts_and_retry();
|
||||
test_native_hold_and_absent_side_watchdogs();
|
||||
test_feedback_advances_host_and_resumes_only_final_samples();
|
||||
test_full_fifo_stop_barrier_and_async_completion();
|
||||
test_async_expiry_and_topology_reset_do_not_revive_state();
|
||||
test_host_stop_does_not_cancel_local_feedback();
|
||||
test_conventional_host_lifetimes_and_invalid_native_input();
|
||||
test_native_watchdog_clock_wrap();
|
||||
test_identical_hold_coalescing_refreshes_borrowed_head();
|
||||
test_hold_coalescing_requires_identical_samples_and_side_masks();
|
||||
test_expired_history_does_not_starve_fresh_sequences();
|
||||
reset();
|
||||
puts("Switch2 protocol boundaries, setup failure, pairing, calibration, physical input, motion and rumble passed");
|
||||
return 0;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue