mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-25 18:05:12 -04:00
Test JogCon steering, hotplug and live input
This commit is contained in:
@@ -72,7 +72,7 @@ Columns: **N** = native, **D** = DuckStation oracle.
|
||||
| `cdrom_sector_history_clear` | ✓ | | — | Reset the CD-ROM sector history ring |
|
||||
| `watch` | ✓ | ✓ | `addr` | Set byte-level memory watchpoint (fires per-frame on change) |
|
||||
| `unwatch` | ✓ | ✓ | `addr` | Remove memory watchpoint |
|
||||
| `set_input` | ✓ | ✓ | `buttons`, optional `frames`, optional `lx`, `ly`, `rx`, `ry` | Override pad1 buttons and optional analog axes (PS1 inverted bitmask, 0 = pressed; axes 0-255). Holds until `clear_input` on both backends; pass `frames=N` (beetle) to auto-release after N frames |
|
||||
| `set_input` | ✓ | ✓ | `buttons`, optional `frames`, optional `lx`, `ly`, `rx`, `ry`, `pad_type` | Override pad1 buttons and optional analog axes (PS1 inverted bitmask, 0 = pressed; axes 0-255). Debug builds also accept `pad_type` (0 digital, 1 DualShock, 2 JogCon) to test an emulated device identity. Holds until `clear_input` on both backends; pass `frames=N` (beetle) to auto-release after N frames |
|
||||
| `clear_input` | ✓ | ✓ | — | Remove input and analog axis overrides |
|
||||
| `turbo` | ✓ | | `enabled` | Enable/disable TCP-controlled frontend turbo for fast-forward validation |
|
||||
| `turbo_state` | ✓ | | — | Query TCP-controlled turbo state |
|
||||
|
||||
@@ -392,6 +392,9 @@ if(BUILD_TESTING)
|
||||
add_executable(controller_type_test tests/test_controller_type.cpp)
|
||||
target_include_directories(controller_type_test PRIVATE include)
|
||||
add_test(NAME controller_type_test COMMAND controller_type_test)
|
||||
add_executable(psx_stick_test tests/test_psx_stick.cpp src/psx_stick.c)
|
||||
target_include_directories(psx_stick_test PRIVATE include)
|
||||
add_test(NAME psx_stick_test COMMAND psx_stick_test)
|
||||
|
||||
add_executable(gte_register_access_test
|
||||
tests/test_gte_register_access.cpp
|
||||
|
||||
@@ -204,6 +204,11 @@ int debug_server_get_input_override(void);
|
||||
* 0 when the injection is buttons-only. */
|
||||
int debug_server_get_axis_override(unsigned char st[4]);
|
||||
|
||||
/* Optional debug-only emulated controller identity set by set_input/press:
|
||||
* -1 follows normal device selection; otherwise 0=digital, 1=DualShock,
|
||||
* 2=JogCon. */
|
||||
int debug_server_get_pad_type_override(void);
|
||||
|
||||
/* TCP-controlled turbo mode. When enabled the frontend skips presentation and
|
||||
* wall-clock pacing at vblank, matching the keyboard TAB turbo path. */
|
||||
int debug_server_turbo_enabled(void);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
/* SDL2/3 does not expose a consistently useful wheel class for every mapping.
|
||||
@@ -23,4 +24,18 @@ inline bool psx_controller_name_is_wheel(const char* name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Pure reopen policy so unplug and launcher-selected device changes can be
|
||||
* regression-tested without a physical SDL controller. */
|
||||
inline bool psx_controller_handle_needs_close(bool controller_selected,
|
||||
bool handle_open,
|
||||
bool handle_attached,
|
||||
const char* selected_guid,
|
||||
const char* active_guid) {
|
||||
if (!controller_selected) return true;
|
||||
if (!handle_open) return false;
|
||||
if (!handle_attached) return true;
|
||||
return selected_guid && selected_guid[0] && active_guid && active_guid[0] &&
|
||||
std::strcmp(selected_guid, active_guid) != 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -282,6 +282,9 @@ static int s_input_frames = 0;
|
||||
* pad sampler alongside the button word. */
|
||||
static int s_axis_override = 0;
|
||||
static uint8_t s_axis_st[4] = { 0x80, 0x80, 0x80, 0x80 };
|
||||
/* Test-only controller identity override for set_input/press. -1 follows the
|
||||
* selected host device/config; 0/1/2 force digital/DualShock/JogCon. */
|
||||
static int s_pad_type_override = -1;
|
||||
|
||||
/*
|
||||
* Exact guest-VBlank input route. A client queues run-length encoded digital
|
||||
@@ -7763,9 +7766,14 @@ static void handle_unwatch(int id, const char *json)
|
||||
static void handle_set_input(int id, const char *json)
|
||||
{
|
||||
char val_str[32];
|
||||
const int pad_type = json_get_int(json, "pad_type", -1);
|
||||
if (!json_get_str(json, "buttons", val_str, sizeof(val_str))) {
|
||||
send_err(id, "missing buttons"); return;
|
||||
}
|
||||
if (pad_type < -1 || pad_type > 2) {
|
||||
send_err(id, "pad_type must be -1 (automatic), 0, 1, or 2"); return;
|
||||
}
|
||||
s_pad_type_override = pad_type;
|
||||
s_input_override = (int)hex_to_u32(val_str);
|
||||
s_input_frames = 0;
|
||||
/* Optional stick override: any of lx/ly/rx/ry (0..255) arms it; omitted
|
||||
@@ -7784,7 +7792,12 @@ static void handle_press(int id, const char *json)
|
||||
{
|
||||
int buttons = json_get_int(json, "buttons", -1);
|
||||
int frames = json_get_int(json, "frames", 2);
|
||||
const int pad_type = json_get_int(json, "pad_type", -1);
|
||||
if (buttons < 0) { send_err(id, "missing buttons"); return; }
|
||||
if (pad_type < -1 || pad_type > 2) {
|
||||
send_err(id, "pad_type must be -1 (automatic), 0, 1, or 2"); return;
|
||||
}
|
||||
s_pad_type_override = pad_type;
|
||||
s_input_override = buttons;
|
||||
s_input_frames = frames;
|
||||
int ax[4] = { json_get_int(json, "lx", -1), json_get_int(json, "ly", -1),
|
||||
@@ -7813,16 +7826,18 @@ static void handle_pad_status(int id, const char *json)
|
||||
sio_get_pad_sticks(0, sticks0);
|
||||
sio_get_pad_sticks(1, sticks1);
|
||||
send_fmt("{\"id\":%d,\"ok\":true,\"pad\":\"0x%04X\","
|
||||
"\"slot0\":{\"buttons\":\"0x%04X\",\"connected\":%s,\"analog\":%s,\"sticks\":[%u,%u,%u,%u]},"
|
||||
"\"slot1\":{\"buttons\":\"0x%04X\",\"connected\":%s,\"analog\":%s,\"sticks\":[%u,%u,%u,%u]},"
|
||||
"\"override\":%d,\"override_frames\":%d,"
|
||||
"\"slot0\":{\"buttons\":\"0x%04X\",\"connected\":%s,\"analog\":%s,\"type\":%d,\"sticks\":[%u,%u,%u,%u]},"
|
||||
"\"slot1\":{\"buttons\":\"0x%04X\",\"connected\":%s,\"analog\":%s,\"type\":%d,\"sticks\":[%u,%u,%u,%u]},"
|
||||
"\"override\":%d,\"override_frames\":%d,\"override_pad_type\":%d,"
|
||||
"\"override_axes\":[%u,%u,%u,%u],\"override_axes_valid\":%s}\n",
|
||||
id, pad0,
|
||||
pad0, sio_get_pad_connected(0) ? "true" : "false", sio_get_pad_analog(0) ? "true" : "false",
|
||||
sio_get_pad_analog(0),
|
||||
sticks0[0], sticks0[1], sticks0[2], sticks0[3],
|
||||
pad1, sio_get_pad_connected(1) ? "true" : "false", sio_get_pad_analog(1) ? "true" : "false",
|
||||
sio_get_pad_analog(1),
|
||||
sticks1[0], sticks1[1], sticks1[2], sticks1[3],
|
||||
s_input_override, s_input_frames,
|
||||
s_input_override, s_input_frames, s_pad_type_override,
|
||||
s_axis_st[0], s_axis_st[1], s_axis_st[2], s_axis_st[3],
|
||||
s_axis_override ? "true" : "false");
|
||||
}
|
||||
@@ -7836,6 +7851,7 @@ static void handle_clear_input(int id, const char *json)
|
||||
s_input_override = -1;
|
||||
s_input_frames = 0;
|
||||
s_axis_override = 0;
|
||||
s_pad_type_override = -1;
|
||||
s_axis_st[0] = s_axis_st[1] = s_axis_st[2] = s_axis_st[3] = 0x80;
|
||||
send_ok(id);
|
||||
}
|
||||
@@ -7882,6 +7898,7 @@ static void handle_input_route_start(int id, const char *json)
|
||||
s_input_override = -1;
|
||||
s_input_frames = 0;
|
||||
s_axis_override = 0;
|
||||
s_pad_type_override = -1;
|
||||
s_input_route_index = 0;
|
||||
s_input_route_remaining = s_input_route[0].frames;
|
||||
s_input_route_active = 1;
|
||||
@@ -15684,6 +15701,11 @@ int debug_server_get_axis_override(unsigned char st[4])
|
||||
return 1;
|
||||
}
|
||||
|
||||
int debug_server_get_pad_type_override(void)
|
||||
{
|
||||
return s_pad_type_override;
|
||||
}
|
||||
|
||||
int debug_server_turbo_enabled(void)
|
||||
{
|
||||
return s_turbo_enabled != 0;
|
||||
|
||||
+45
-22
@@ -5038,21 +5038,21 @@ static void refresh_player_devices(void) {
|
||||
const int netplay = psx_netplay_active();
|
||||
for (int s = 0; s < PSX_MAX_PLAYERS; s++) {
|
||||
PlayerInput& p = g_players[s];
|
||||
if (p.kind != 2 ||
|
||||
(p.handle && !SDL_GameControllerGetAttached(p.handle)))
|
||||
close_player(p); /* kind change or unplug */
|
||||
else if (p.handle && p.guid[0]) {
|
||||
/* The launcher may have assigned a different GUID while the old
|
||||
* handle is still attached. Release it so open_player can honor
|
||||
* the new selection and choose the matching emulated type. */
|
||||
char active_guid[40] = {0};
|
||||
const bool attached = p.handle &&
|
||||
SDL_GameControllerGetAttached(p.handle);
|
||||
if (p.handle) {
|
||||
SDL_Joystick* joy = SDL_GameControllerGetJoystick(p.handle);
|
||||
SDL_JoystickGUID active_guid = joy ? SDL_JoystickGetGUID(joy)
|
||||
: SDL_JoystickGUID{};
|
||||
char active[40] = {0};
|
||||
if (joy) SDL_JoystickGetGUIDString(active_guid, active,
|
||||
(int)sizeof(active));
|
||||
if (joy && std::strcmp(active, p.guid) != 0) close_player(p);
|
||||
if (joy) SDL_JoystickGetGUIDString(SDL_JoystickGetGUID(joy),
|
||||
active_guid,
|
||||
(int)sizeof(active_guid));
|
||||
}
|
||||
/* The launcher may change GUID while the old handle remains attached;
|
||||
* pure policy covers that and unplug paths with mockable inputs. */
|
||||
if (psx_controller_handle_needs_close(
|
||||
p.kind == 2, p.handle != nullptr, attached,
|
||||
p.guid, active_guid))
|
||||
close_player(p);
|
||||
if (p.kind == 2) open_player(p, s);
|
||||
if (netplay) continue;
|
||||
const int mode = effective_player_mode_for_sio(p, s);
|
||||
@@ -5648,8 +5648,13 @@ static void apply_input_override_to_sio(int override_word) {
|
||||
|
||||
const int effective_mode = controller_policy_resolve_override_mode(
|
||||
0, 1, mode, w, st, stick_live, dpad_live);
|
||||
const int eff_analog =
|
||||
int eff_analog =
|
||||
effective_mode == (int)PSXRecompV4::PAD_MODE_ANALOG ? 1 : 0;
|
||||
#ifndef PSX_NO_DEBUG_TOOLS
|
||||
const int debug_type = debug_server_get_pad_type_override();
|
||||
if (debug_type == SIO_PAD_DUALSHOCK || debug_type == SIO_PAD_JOGCON)
|
||||
eff_analog = 1;
|
||||
#endif
|
||||
/* Injected input only (set_input / dev routing): fold the injected D-pad
|
||||
* word onto the left stick so stick-only menu/move paths respond to a
|
||||
* button-bit injection that has no physical stick behind it.
|
||||
@@ -5669,9 +5674,13 @@ static void apply_input_override_to_sio(int override_word) {
|
||||
}
|
||||
if (!eff_analog) { st[0] = st[1] = st[2] = st[3] = 0x80; }
|
||||
sio_set_pad_sticks(0, st[0], st[1], st[2], st[3]);
|
||||
const int injected_type = p.steering_wheel ? SIO_PAD_JOGCON
|
||||
: (eff_analog ? SIO_PAD_DUALSHOCK
|
||||
: SIO_PAD_DIGITAL);
|
||||
int injected_type = p.steering_wheel ? SIO_PAD_JOGCON
|
||||
: (eff_analog ? SIO_PAD_DUALSHOCK
|
||||
: SIO_PAD_DIGITAL);
|
||||
#ifndef PSX_NO_DEBUG_TOOLS
|
||||
if (debug_type >= SIO_PAD_DIGITAL && debug_type <= SIO_PAD_JOGCON)
|
||||
injected_type = debug_type;
|
||||
#endif
|
||||
sio_request_pad_type(0, injected_type);
|
||||
psx_selfcheck_note_pad(0, w, st[0], st[1], st[2], st[3],
|
||||
(uint8_t)injected_type);
|
||||
@@ -5716,8 +5725,13 @@ static int capture_pad_slot(int s, PsxNetPad* out) {
|
||||
s, player, mode, src, p, policy_buttons, st);
|
||||
const int eff_analog =
|
||||
effective_mode == PSXRecompV4::PAD_MODE_ANALOG ? 1 : 0;
|
||||
const int frame_type = p.steering_wheel ? SIO_PAD_JOGCON
|
||||
: (eff_analog ? SIO_PAD_DUALSHOCK : SIO_PAD_DIGITAL);
|
||||
int frame_type = p.steering_wheel ? SIO_PAD_JOGCON
|
||||
: (eff_analog ? SIO_PAD_DUALSHOCK : SIO_PAD_DIGITAL);
|
||||
#ifndef PSX_NO_DEBUG_TOOLS
|
||||
const int debug_type = debug_server_get_pad_type_override();
|
||||
if (debug_type >= SIO_PAD_DIGITAL && debug_type <= SIO_PAD_JOGCON)
|
||||
frame_type = debug_type;
|
||||
#endif
|
||||
if (savestate_input_guard_active()) {
|
||||
out->buttons = 0xFFFFu;
|
||||
out->lx = out->ly = out->rx = out->ry = 0x80u;
|
||||
@@ -5937,10 +5951,19 @@ static void capture_override_pad(int override_word, PsxNetPad* out) {
|
||||
|
||||
const int effective_mode = controller_policy_resolve_override_mode(
|
||||
0, 1, mode, w, st, stick_live, dpad_live);
|
||||
const int eff_analog =
|
||||
int eff_analog =
|
||||
effective_mode == (int)PSXRecompV4::PAD_MODE_ANALOG ? 1 : 0;
|
||||
const int frame_type = p.steering_wheel ? SIO_PAD_JOGCON
|
||||
: (eff_analog ? SIO_PAD_DUALSHOCK : SIO_PAD_DIGITAL);
|
||||
#ifndef PSX_NO_DEBUG_TOOLS
|
||||
const int debug_type = debug_server_get_pad_type_override();
|
||||
if (debug_type == SIO_PAD_DUALSHOCK || debug_type == SIO_PAD_JOGCON)
|
||||
eff_analog = 1;
|
||||
#endif
|
||||
int frame_type = p.steering_wheel ? SIO_PAD_JOGCON
|
||||
: (eff_analog ? SIO_PAD_DUALSHOCK : SIO_PAD_DIGITAL);
|
||||
#ifndef PSX_NO_DEBUG_TOOLS
|
||||
if (debug_type >= SIO_PAD_DIGITAL && debug_type <= SIO_PAD_JOGCON)
|
||||
frame_type = debug_type;
|
||||
#endif
|
||||
/* Injected input only; see the note on the sibling fold above. Not
|
||||
* hardware behaviour, retained solely so injection can steer stick-only
|
||||
* games. */
|
||||
|
||||
@@ -22,6 +22,26 @@ int main() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
struct HotplugCase {
|
||||
bool selected, open, attached;
|
||||
const char* selected_guid;
|
||||
const char* active_guid;
|
||||
bool close;
|
||||
} hotplug[] = {
|
||||
{true, false, false, "wheel-a", nullptr, false},
|
||||
{true, true, true, "wheel-a", "wheel-a", false},
|
||||
{true, true, false, "wheel-a", "wheel-a", true},
|
||||
{true, true, true, "wheel-b", "wheel-a", true},
|
||||
{false, true, true, "wheel-a", "wheel-a", true},
|
||||
{true, true, true, nullptr, "wheel-a", false},
|
||||
};
|
||||
for (const auto& test : hotplug) {
|
||||
if (psx_controller_handle_needs_close(test.selected, test.open,
|
||||
test.attached, test.selected_guid, test.active_guid) != test.close) {
|
||||
std::fprintf(stderr, "hotplug policy failed\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
std::puts("controller type classification: passed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -87,9 +87,20 @@ int main(void)
|
||||
netplay_ih_pad_to_frame(&pad, 9, 0, &dig);
|
||||
CHECK(dig.analog == 0u, "digital frame");
|
||||
netplay_ih_frame_to_pad(&dig, &pad2);
|
||||
CHECK(pad2.analog == 0u, "digital roundtrip");
|
||||
CHECK(pad2.analog == 0u, "digital roundtrip");
|
||||
}
|
||||
|
||||
/* Restore the original DualShock sample for the ring-buffer assertions
|
||||
* below; the preceding type/steering checks intentionally used later
|
||||
* ticks and different pad modes. */
|
||||
memset(&pad, 0, sizeof(pad));
|
||||
pad.buttons = 0xFDFFu;
|
||||
pad.lx = (uint8_t)(0x80 + 40);
|
||||
pad.ly = pad.rx = pad.ry = 0x80;
|
||||
pad.analog = SIO_PAD_DUALSHOCK;
|
||||
pad.connected = 1;
|
||||
netplay_ih_pad_to_frame(&pad, 10, 0, &f);
|
||||
|
||||
CHECK(netplay_ih_put(&h, 0, &f), "put local");
|
||||
CHECK(netplay_ih_get(&h, 0, 10, &got), "get local");
|
||||
CHECK(got.stick_x == 40, "get stick");
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
#include "psx_stick.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
int main() {
|
||||
uint8_t x = 0, y = 0;
|
||||
psx_stick_to_dualshock(0, 0, 4096, 0, &x, &y);
|
||||
if (x != 0x80 || y != 0x80) return 1;
|
||||
|
||||
psx_stick_to_dualshock(4095, 0, 4096, 0, &x, &y);
|
||||
if (x != 0x80 || y != 0x80) return 2;
|
||||
|
||||
psx_stick_to_dualshock(-32768, 0, 4096, 0, &x, &y);
|
||||
if (x != 0x00 || y != 0x80) return 3;
|
||||
|
||||
psx_stick_to_dualshock(32767, 0, 4096, 0, &x, &y);
|
||||
if (x != 0xFF || y != 0x80) return 4;
|
||||
|
||||
psx_stick_to_dualshock(16384, 0, 4096, 0, &x, &y);
|
||||
if (x <= 0x80 || x >= 0xFF || y != 0x80) return 5;
|
||||
std::printf("stick mapping: center/left/right/partial pass (partial x=%u)\n",
|
||||
(unsigned)x);
|
||||
return 0;
|
||||
}
|
||||
@@ -177,6 +177,16 @@ int main(void) {
|
||||
EXPECT("right.direction", 0x01, xchg(0, 0x00));
|
||||
(void)xchg(0, 0x00);
|
||||
|
||||
/* Partial position stays proportional and reports movement toward center. */
|
||||
sio_set_pad_sticks(0, 0xC0, 0x80, 0x80, 0x80);
|
||||
EXPECT("partial.prefix", 0xFF, xchg(0, 0x01));
|
||||
EXPECT("partial.id", 0xE3, xchg(0, 0x42));
|
||||
(void)xchg(0, 0x00); (void)xchg(0, 0x00); (void)xchg(0, 0x00);
|
||||
EXPECT("partial.position", 0x40, xchg(0, 0x00));
|
||||
EXPECT("partial.sign", 0x00, xchg(0, 0x00));
|
||||
EXPECT("partial.direction", 0x02, xchg(0, 0x00));
|
||||
(void)xchg(0, 0x00);
|
||||
|
||||
EXPECT("rollback.restore", 1, sio_snapshot_read(snapshot, snapshot_len));
|
||||
EXPECT("rollback.type", SIO_PAD_JOGCON, sio_get_pad_analog(0));
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user