From 7ef29ad1124e22ee17f925d92c0a1178738577e8 Mon Sep 17 00:00:00 2001 From: Matthew Stanley <1379tech@gmail.com> Date: Sat, 24 Oct 2026 18:42:06 -0700 Subject: [PATCH] Fold the debug host layer's left stick onto the D-pad for digital pads set_input layer=host is a default-mapped gamepad; a real one folds ls_* onto the D-pad in digital mode, the layer dropped the stick. Co-Authored-By: Claude Opus 5.5 (1M context) --- runtime/CMakeLists.txt | 3 +++ runtime/include/psx_stick_fold.h | 23 ++++++++++++++++++++ runtime/src/main.cpp | 8 ++++++- runtime/tests/test_psx_stick_fold.c | 33 +++++++++++++++++++++++++++++ 4 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 runtime/include/psx_stick_fold.h create mode 100644 runtime/tests/test_psx_stick_fold.c diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index 18c18113..6e788912 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -900,6 +900,9 @@ if(BUILD_TESTING) add_executable(psx_hotkey_pad_test tests/test_psx_hotkey_pad.c) target_include_directories(psx_hotkey_pad_test PRIVATE include) add_test(NAME psx_hotkey_pad_test COMMAND psx_hotkey_pad_test) + add_executable(psx_stick_fold_test tests/test_psx_stick_fold.c) + target_include_directories(psx_stick_fold_test PRIVATE include) + add_test(NAME psx_stick_fold_test COMMAND psx_stick_fold_test) add_executable(psx_trigger_values_test tests/test_psx_trigger_values.c) target_include_directories(psx_trigger_values_test PRIVATE include) add_test(NAME psx_trigger_values_test COMMAND psx_trigger_values_test) diff --git a/runtime/include/psx_stick_fold.h b/runtime/include/psx_stick_fold.h new file mode 100644 index 00000000..a691ba3f --- /dev/null +++ b/runtime/include/psx_stick_fold.h @@ -0,0 +1,23 @@ +#pragma once +/* The default controller map's digital stick fold for a pad expressed as PSX + * stick bytes. main.cpp's default map binds ls_up/down/left/right to + * lefty-/lefty+/leftx-/leftx+ and folds them onto the D-pad (fold_bit) while + * the pad presents as DIGITAL, with the same per-axis test as + * controller_source_pressed_h (v > dz, v < -dz). The debug host input layer + * (set_input {"layer":"host"}) is a default-mapped gamepad, so its left stick + * must press the D-pad exactly as a physical controller's would. */ +#include + +/* A PSX stick byte (0x00..0xFF, centre 0x80) in SDL axis units. */ +static inline int psx_stick_byte_to_axis(uint8_t v) { return ((int)v - 0x80) * 256; } + +/* Press (clear, active-low) the D-pad bits the left stick folds onto. */ +static inline uint16_t psx_fold_left_stick_to_dpad(uint16_t word, uint8_t lx, + uint8_t ly, int deadzone_raw) { + const int x = psx_stick_byte_to_axis(lx), y = psx_stick_byte_to_axis(ly); + if (y < -deadzone_raw) word &= (uint16_t)~(1u << 4); /* Up */ + if (x > deadzone_raw) word &= (uint16_t)~(1u << 5); /* Right */ + if (y > deadzone_raw) word &= (uint16_t)~(1u << 6); /* Down */ + if (x < -deadzone_raw) word &= (uint16_t)~(1u << 7); /* Left */ + return word; +} diff --git a/runtime/src/main.cpp b/runtime/src/main.cpp index 6dfc74d6..52ac6493 100644 --- a/runtime/src/main.cpp +++ b/runtime/src/main.cpp @@ -94,6 +94,7 @@ extern "C" uint32_t g_guest_last_store_addr, g_guest_last_store_val; #include "psx_trigger.h" /* continuous SDL trigger -> 0..255 magnitude */ #include "mod_pad_transform.h" #include "psx_hotkey_pad.h" /* host shortcut bindings, direct claims */ +#include "psx_stick_fold.h" /* default-map digital stick fold (host layer) */ #include "psx_controller_type.h" /* mapped wheel-name classification */ #include "psx_netplay_rb.h" #include "psx_selfcheck.h" @@ -6795,7 +6796,9 @@ static uint16_t host_layer_release_claimed(uint16_t word, uint32_t claimed) { /* Stage 1 for P1 while the host layer is armed: buttons less any claimed * shortcut button; DualShock with the layer's sticks unless P1 is digital - * (host_view, the transform's host pad: the sticks in either mode). */ + * (host_view, the transform's host pad: the sticks in either mode). A digital + * P1 folds the left stick onto the D-pad like a default-mapped controller + * (psx_stick_fold.h), so a digital-only game moves from the stick here too. */ static int capture_host_layer_pad(PsxNetPad* out, uint16_t word, const uint8_t st[4], bool guarded, bool host_view, int present_sio_slot = 0) { @@ -6805,6 +6808,9 @@ static int capture_host_layer_pad(PsxNetPad* out, uint16_t word, const bool analog = mode != PSXRecompV4::PAD_MODE_DIGITAL; const bool sticks = analog || host_view; out->buttons = host_layer_release_claimed(word, g_p1_claimed_buttons); + if (!analog && !host_view) + out->buttons = psx_fold_left_stick_to_dpad(out->buttons, st[0], st[1], + kDefaultDeadzoneRaw); out->lx = sticks ? st[0] : 0x80; out->ly = sticks ? st[1] : 0x80; out->rx = sticks ? st[2] : 0x80; out->ry = sticks ? st[3] : 0x80; out->analog = analog ? SIO_PAD_DUALSHOCK : SIO_PAD_DIGITAL; diff --git a/runtime/tests/test_psx_stick_fold.c b/runtime/tests/test_psx_stick_fold.c new file mode 100644 index 00000000..9ce917e8 --- /dev/null +++ b/runtime/tests/test_psx_stick_fold.c @@ -0,0 +1,33 @@ +/* psx_stick_fold.h: the default map's digital left-stick fold onto the D-pad. */ +#include "psx_stick_fold.h" +#include + +static int failures = 0; +#define CHECK(c) do { if (!(c)) { fprintf(stderr, "FAIL %s:%d %s\n", __FILE__, __LINE__, #c); failures++; } } while (0) + +enum { UP = 1 << 4, RIGHT = 1 << 5, DOWN = 1 << 6, LEFT = 1 << 7, CROSS = 1 << 14 }; +static const int dz = 3277; /* main.cpp kDefaultDeadzoneRaw */ + +int main(void) { + /* Centred stick: nothing pressed, other buttons untouched. */ + CHECK(psx_fold_left_stick_to_dpad(0xFFFF, 0x80, 0x80, dz) == 0xFFFF); + CHECK(psx_fold_left_stick_to_dpad((uint16_t)~CROSS, 0x80, 0x80, dz) == (uint16_t)~CROSS); + /* Full deflections press exactly one direction each. */ + CHECK(psx_fold_left_stick_to_dpad(0xFFFF, 0xFF, 0x80, dz) == (uint16_t)~RIGHT); + CHECK(psx_fold_left_stick_to_dpad(0xFFFF, 0x00, 0x80, dz) == (uint16_t)~LEFT); + CHECK(psx_fold_left_stick_to_dpad(0xFFFF, 0x80, 0x00, dz) == (uint16_t)~UP); + CHECK(psx_fold_left_stick_to_dpad(0xFFFF, 0x80, 0xFF, dz) == (uint16_t)~DOWN); + /* Diagonal presses both axes. */ + CHECK(psx_fold_left_stick_to_dpad(0xFFFF, 0xFF, 0x00, dz) == (uint16_t)~(RIGHT | UP)); + /* Per-axis deadzone: a strong right push with small Y drift is Right only. */ + CHECK(psx_fold_left_stick_to_dpad(0xFFFF, 0xFF, 0x80 + 12, dz) == (uint16_t)~RIGHT); + /* Threshold: 13 steps (3328) passes 3277, 12 steps (3072) does not. */ + CHECK(psx_fold_left_stick_to_dpad(0xFFFF, 0x80 + 13, 0x80, dz) == (uint16_t)~RIGHT); + CHECK(psx_fold_left_stick_to_dpad(0xFFFF, 0x80 + 12, 0x80, dz) == 0xFFFF); + CHECK(psx_fold_left_stick_to_dpad(0xFFFF, 0x80 - 13, 0x80, dz) == (uint16_t)~LEFT); + /* An already-held D-pad bit stays held. */ + CHECK(psx_fold_left_stick_to_dpad((uint16_t)~UP, 0xFF, 0x80, dz) == (uint16_t)~(UP | RIGHT)); + if (failures) return 1; + puts("PASS psx_stick_fold"); + return 0; +}