From 29b1fd624639d7eb984fb4cadfdef28944f3a5d1 Mon Sep 17 00:00:00 2001 From: Matthew Stanley <1379tech@gmail.com> Date: Fri, 10 Jul 2026 11:09:33 -0700 Subject: [PATCH] input: first-class L3/R3 (stick click) binds across the keybind ecosystem MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The controller map had no l3/r3 rows at all, so pad bits 1/2 could never be produced by a gamepad — DualShock-only games (Ape Escape) were unplayable. Keyboard slots existed but shipped unbound. - main.cpp: PAD_L3/PAD_R3 bits; controller_map 14->16 entries; defaults l3=leftstick, r3=rightstick; input.ini template + doc comment updated. - psx_keybinds.c: keyboard defaults L3=T, R3=Y (continue the Q/W/E/R shoulder row). Launcher Controls page picks them up automatically. - beetle_main.cpp: same T/Y keys on the oracle so psx-beetle stays drivable for L3/R3 games. - tools/package_release.ps1: shipped input.ini template gains l3/r3. - README: keyboard + controller map tables. Wire is unchanged: sio.c transmits the button word verbatim, matching Beetle's dualshock.cpp. Validated in-game on Ape Escape (user-confirmed R3/L3 inputs on a real pad). Co-Authored-By: Claude Fable 5 --- README.md | 2 ++ runtime/src/beetle_main.cpp | 4 ++++ runtime/src/main.cpp | 25 ++++++++++++++++++------- runtime/src/psx_keybinds.c | 5 +++-- tools/package_release.ps1 | 5 ++++- 5 files changed, 31 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 5bf6963f..be784b4d 100644 --- a/README.md +++ b/README.md @@ -187,6 +187,7 @@ generated C is optimized. Game projects generate their own | Triangle | A | | L1 / R1 | Q / W | | L2 / R2 | E / R | +| L3 / R3 (stick clicks) | T / Y | | Start | Enter | | Select | Right Shift | | Turbo | Tab (hold) | @@ -205,6 +206,7 @@ Xbox-style controller defaults are enabled when a controller is connected: | Triangle | Y | | L1 / R1 | LB / RB | | L2 / R2 | LT / RT | +| L3 / R3 | Left / right stick click | | Start | Menu | | Select | View / Back | diff --git a/runtime/src/beetle_main.cpp b/runtime/src/beetle_main.cpp index a6132356..5ccb8708 100644 --- a/runtime/src/beetle_main.cpp +++ b/runtime/src/beetle_main.cpp @@ -26,6 +26,8 @@ int beetle_debug_server_get_input_override(void); } #define PAD_SELECT (1 << 0) +#define PAD_L3 (1 << 1) +#define PAD_R3 (1 << 2) #define PAD_START (1 << 3) #define PAD_UP (1 << 4) #define PAD_RIGHT (1 << 5) @@ -57,6 +59,8 @@ static uint16_t pad_from_keyboard(void) { if (keys[SDL_SCANCODE_W]) b &= ~PAD_R1; if (keys[SDL_SCANCODE_E]) b &= ~PAD_L2; if (keys[SDL_SCANCODE_R]) b &= ~PAD_R2; + if (keys[SDL_SCANCODE_T]) b &= ~PAD_L3; /* stick clicks: same keys */ + if (keys[SDL_SCANCODE_Y]) b &= ~PAD_R3; /* as psx-runtime defaults */ return b; } diff --git a/runtime/src/main.cpp b/runtime/src/main.cpp index 13ac3fd8..89749270 100644 --- a/runtime/src/main.cpp +++ b/runtime/src/main.cpp @@ -903,11 +903,15 @@ static void sdl_audio_update(int turbo_active) { } /* PS1 digital pad button bits (active-low: 0=pressed, 1=released). - * Bit 0 = SELECT, Bit 3 = START, Bit 4 = UP, Bit 5 = RIGHT, - * Bit 6 = DOWN, Bit 7 = LEFT, Bit 8 = L2, Bit 9 = R2, - * Bit 10 = L1, Bit 11 = R1, Bit 12 = TRIANGLE, Bit 13 = CIRCLE, - * Bit 14 = CROSS, Bit 15 = SQUARE */ + * Bit 0 = SELECT, Bit 1 = L3, Bit 2 = R3, Bit 3 = START, + * Bit 4 = UP, Bit 5 = RIGHT, Bit 6 = DOWN, Bit 7 = LEFT, + * Bit 8 = L2, Bit 9 = R2, Bit 10 = L1, Bit 11 = R1, + * Bit 12 = TRIANGLE, Bit 13 = CIRCLE, Bit 14 = CROSS, Bit 15 = SQUARE. + * L3/R3 (stick clicks) exist on a DualShock only; the wire reports them like + * Beetle's dualshock.cpp does — straight from the button word, no mode mask. */ #define PAD_SELECT (1 << 0) +#define PAD_L3 (1 << 1) +#define PAD_R3 (1 << 2) #define PAD_START (1 << 3) #define PAD_UP (1 << 4) #define PAD_RIGHT (1 << 5) @@ -942,7 +946,7 @@ struct PsxButtonMap { static int controller_device_index = 0; static int controller_deadzone = 12000; -static std::array controller_map = {{ +static std::array controller_map = {{ { PAD_UP, "up", {} }, { PAD_DOWN, "down", {} }, { PAD_LEFT, "left", {} }, @@ -955,6 +959,8 @@ static std::array controller_map = {{ { PAD_R1, "r1", {} }, { PAD_L2, "l2", {} }, { PAD_R2, "r2", {} }, + { PAD_L3, "l3", {} }, + { PAD_R3, "r3", {} }, { PAD_START, "start", {} }, { PAD_SELECT, "select", {} }, }}; @@ -1099,6 +1105,8 @@ static void set_default_controller_mapping(void) { set_sources("r1", "rightshoulder"); set_sources("l2", "lefttrigger"); set_sources("r2", "righttrigger"); + set_sources("l3", "leftstick"); + set_sources("r3", "rightstick"); set_sources("start", "start"); set_sources("select", "back"); } @@ -1107,7 +1115,8 @@ static std::string default_input_ini_text(void) { return "; PSXRecomp input mapping. PSX buttons are active when any listed source is pressed.\n" "; Sources use SDL/Xbox names: a,b,x,y,back,start,leftshoulder,rightshoulder,\n" - "; lefttrigger,righttrigger,dpup,dpdown,dpleft,dpright,leftx-/leftx+/lefty-/lefty+.\n" + "; lefttrigger,righttrigger,leftstick,rightstick (stick clicks -> L3/R3),\n" + "; dpup,dpdown,dpleft,dpright,leftx-/leftx+/lefty-/lefty+.\n" "\n" "[controller]\n" "enabled = true\n" @@ -1127,6 +1136,8 @@ static std::string default_input_ini_text(void) { "r1 = rightshoulder\n" "l2 = lefttrigger\n" "r2 = righttrigger\n" + "l3 = leftstick\n" + "r3 = rightstick\n" "start = start\n" "select = back\n"; } @@ -1311,7 +1322,7 @@ static bool controller_source_pressed_h(SDL_GameController* h, const ControllerS /* Keyboard -> PSX button word for `player` (1 or 2), via the configurable * keybinds.ini map (psx_keybinds). Defaults reproduce the old hardcoded layout * (arrows=d-pad, X/S/Z/A=Cross/Circle/Square/Triangle, Q/W/E/R=L1/R1/L2/R2, - * Return=Start, RShift=Select), so out-of-the-box behaviour is unchanged. */ + * Return=Start, RShift=Select) plus T/Y=L3/R3 stick clicks. */ static uint16_t pad_from_keyboard(int player) { const Uint8* keys = SDL_GetKeyboardState(NULL); return psx_keybinds_pad_word(keys, player); diff --git a/runtime/src/psx_keybinds.c b/runtime/src/psx_keybinds.c index 6124fe2a..5e19efa9 100644 --- a/runtime/src/psx_keybinds.c +++ b/runtime/src/psx_keybinds.c @@ -39,7 +39,8 @@ * with defaults changes nothing until the user edits it: * D-pad: Arrow keys Start: Return Select: Right Shift * Cross: X Circle: S Square: Z Triangle: A - * L1: Q R1: W L2: E R2: R L3/R3: unbound + * L1: Q R1: W L2: E R2: R L3: T R3: Y (stick clicks continue the + * shoulder row; DualShock-only games like Ape Escape require L3/R3) * Left analog stick: Arrow keys (matches the old keyboard analog path) * Right analog stick: unbound (the keyboard never drove it before) * Player 2 is fully unbound (add binds to enable a 2nd keyboard player). @@ -52,7 +53,7 @@ .square = SDL_SCANCODE_Z, .triangle = SDL_SCANCODE_A, \ .l1 = SDL_SCANCODE_Q, .r1 = SDL_SCANCODE_W, \ .l2 = SDL_SCANCODE_E, .r2 = SDL_SCANCODE_R, \ - .l3 = SDL_SCANCODE_UNKNOWN, .r3 = SDL_SCANCODE_UNKNOWN, \ + .l3 = SDL_SCANCODE_T, .r3 = SDL_SCANCODE_Y, \ .start = SDL_SCANCODE_RETURN, .select = SDL_SCANCODE_RSHIFT, \ .ls_up = SDL_SCANCODE_UP, .ls_down = SDL_SCANCODE_DOWN, \ .ls_left = SDL_SCANCODE_LEFT, .ls_right = SDL_SCANCODE_RIGHT, \ diff --git a/tools/package_release.ps1 b/tools/package_release.ps1 index 0998afc2..218bb03b 100644 --- a/tools/package_release.ps1 +++ b/tools/package_release.ps1 @@ -54,7 +54,8 @@ Write-Host "Verified self-contained: imports only system DLLs ($($imports.Count) @" ; PSXRecomp input mapping. PSX buttons are active when any listed source is pressed. ; Sources use SDL/Xbox names: a,b,x,y,back,start,leftshoulder,rightshoulder, -; lefttrigger,righttrigger,dpup,dpdown,dpleft,dpright,leftx-/leftx+/lefty-/lefty+. +; lefttrigger,righttrigger,leftstick,rightstick (stick clicks -> L3/R3), +; dpup,dpdown,dpleft,dpright,leftx-/leftx+/lefty-/lefty+. [controller] enabled = true @@ -74,6 +75,8 @@ l1 = leftshoulder r1 = rightshoulder l2 = lefttrigger r2 = righttrigger +l3 = leftstick +r3 = rightstick start = start select = back "@ | Set-Content -Encoding ASCII (Join-Path $Stage "input.ini")