mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 08:21:02 +02:00
fix(input): Nintendo A/B face map on NX
SDL labels south as a and east as b; on Switch remap so physical A confirms and physical B cancels in launcher and NamingScreen. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -217,14 +217,16 @@ Measured on Switch OLED (`feat/switch-nx`, love-nx `11.5-nx1`, 1280×720). Both
|
||||
|
||||
| Path | Control | Mapping |
|
||||
| ---- | ------- | ------- |
|
||||
| `gamepadpressed` | D-pad / left stick | move (via `GamepadMap.DEFAULT_GAMEPAD_BINDINGS`) |
|
||||
| `gamepadpressed` | `a` / `b` (SDL) | GB A / B — physical **B** (south) confirms, physical **A** (east) cancels |
|
||||
| `gamepadpressed` | D-pad / left stick | move |
|
||||
| `gamepadpressed` | SDL `a` / `b` on **NX** | swapped via `NX_GAMEPAD_BINDINGS`: physical **A** (east) = GB A confirm, physical **B** (south) = GB B cancel |
|
||||
| `gamepadpressed` | SDL `a` / `b` on desktop | identity (SDL south = GB A) |
|
||||
| `gamepadpressed` | `start` / `back` | Start / Select |
|
||||
| `joystickpressed` (raw) | only if **not** `isGamepad()` | face/menu fallback |
|
||||
| `joystickpressed` (raw) | `#1` / `#2` | GB A / B |
|
||||
| `joystickpressed` (raw) | `#3` Y / `#4` X | GB A / B (OLED naming diagnosis) |
|
||||
| `joystickpressed` (raw) | `#1` / `#2` on NX | Nintendo B / A → GB B / A |
|
||||
| `joystickpressed` (raw) | `#9` / `#10` | Select / Start (− / +) |
|
||||
|
||||
**Nintendo UX on Switch:** physical A confirms, physical B cancels (explicit NX remap of SDL face labels).
|
||||
|
||||
**Dual-path rule:** love-nx emits both `gamepadpressed` and `joystickpressed` for Joy-Con. When `joystick:isGamepad()` is true, Input and RomImporter **ignore raw** face/menu so NamingScreen does not see A+B in one frame. `NamingScreen` also prefers A over B if both edges still fire.
|
||||
|
||||
Implementation: `src/core/GamepadMap.lua` (`NX_RAW_*`, `ignoreRawForJoystick`). Launcher and gameplay share the same converter.
|
||||
|
||||
+23
-12
@@ -3,42 +3,48 @@
|
||||
|
||||
local GamepadMap = {}
|
||||
|
||||
-- LÖVE SDL game-controller mapping (D-pad / face / menu).
|
||||
-- LÖVE SDL game-controller mapping (D-pad / face / menu) — desktop/mobile.
|
||||
GamepadMap.DEFAULT_GAMEPAD_BINDINGS = {
|
||||
dpup = "up", dpdown = "down", dpleft = "left", dpright = "right",
|
||||
a = "a", b = "b",
|
||||
start = "start", back = "select",
|
||||
}
|
||||
|
||||
-- Switch: LÖVE/SDL labels south as "a" and east as "b", but Nintendo UX is
|
||||
-- physical A (east) = confirm (GB A), physical B (south) = cancel (GB B).
|
||||
GamepadMap.NX_GAMEPAD_BINDINGS = {
|
||||
dpup = "up", dpdown = "down", dpleft = "left", dpright = "right",
|
||||
a = "b", -- SDL south = Nintendo B → GB B
|
||||
b = "a", -- SDL east = Nintendo A → GB A
|
||||
start = "start", back = "select",
|
||||
}
|
||||
|
||||
-- Generic SDL joysticks without a game-controller DB entry (Linux handhelds).
|
||||
GamepadMap.RAW_BUTTON_BINDINGS = {
|
||||
[1] = "a", [2] = "b",
|
||||
[7] = "select", [8] = "start", [9] = "select", [10] = "start",
|
||||
}
|
||||
|
||||
-- Switch OLED: love.joystickpressed indices (1-based). Used only when the
|
||||
-- device is NOT a gamepad — love-nx also emits gamepadpressed for Joy-Con,
|
||||
-- and applying both face paths in one frame breaks NamingScreen (a+b).
|
||||
-- Y→a / X→b matches operator OLED naming diagnosis (2026-08-01).
|
||||
-- Switch OLED raw indices (1-based). Only when NOT isGamepad() — love-nx
|
||||
-- also emits gamepadpressed; dual-path face presses break NamingScreen.
|
||||
-- #1 = Nintendo B, #2 = Nintendo A (probe); Y/X left unmapped for naming.
|
||||
GamepadMap.NX_RAW_BUTTON_BINDINGS = {
|
||||
[1] = "a", [2] = "b",
|
||||
[3] = "a", [4] = "b",
|
||||
[1] = "b", [2] = "a",
|
||||
[9] = "select", [10] = "start",
|
||||
}
|
||||
|
||||
-- Raw index -> gamepad button name for RomImporter routing.
|
||||
-- Raw index -> gamepad button *name* for RomImporter (then NX face swap applies).
|
||||
GamepadMap.RAW_TO_GAMEPAD_BUTTON = {
|
||||
[1] = "a", [2] = "b",
|
||||
[7] = "back", [8] = "start", [9] = "back", [10] = "start",
|
||||
}
|
||||
|
||||
GamepadMap.NX_RAW_TO_GAMEPAD_BUTTON = {
|
||||
[1] = "a", [2] = "b",
|
||||
[3] = "a", [4] = "b",
|
||||
[1] = "a", [2] = "b", -- SDL south/east names; NX_GAMEPAD_BINDINGS swaps to GB
|
||||
[9] = "back", [10] = "start",
|
||||
}
|
||||
|
||||
-- Test hook: force NX raw tables without stubbing love.
|
||||
-- Test hook: force NX tables without stubbing love.
|
||||
GamepadMap._forceNXForTests = false
|
||||
|
||||
function GamepadMap._setForceNXForTests(v)
|
||||
@@ -52,8 +58,13 @@ local function nxActive()
|
||||
return false
|
||||
end
|
||||
|
||||
function GamepadMap.gamepadBindings()
|
||||
if nxActive() then return GamepadMap.NX_GAMEPAD_BINDINGS end
|
||||
return GamepadMap.DEFAULT_GAMEPAD_BINDINGS
|
||||
end
|
||||
|
||||
function GamepadMap.mapGamepadButton(button)
|
||||
return GamepadMap.DEFAULT_GAMEPAD_BINDINGS[button]
|
||||
return GamepadMap.gamepadBindings()[button]
|
||||
end
|
||||
|
||||
-- love-nx / SDL: when isGamepad(), face+menu already arrive via gamepad*.
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@ end
|
||||
function Input:applyBindings(overlay)
|
||||
local keys, pads = {}, {}
|
||||
for key, action in pairs(DEFAULT_BINDINGS) do keys[key] = action end
|
||||
for button, action in pairs(GamepadMap.DEFAULT_GAMEPAD_BINDINGS) do
|
||||
for button, action in pairs(GamepadMap.gamepadBindings()) do
|
||||
pads[button] = action
|
||||
end
|
||||
for actionId, binding in pairs(overlay or {}) do
|
||||
|
||||
@@ -1513,7 +1513,9 @@ end
|
||||
|
||||
function RomImporter:gamepadpressed(_, button)
|
||||
self:_activatePadCursor()
|
||||
if button == "a" then
|
||||
-- Map through GamepadMap so NX swaps SDL face labels to Nintendo A/B.
|
||||
local action = GamepadMap.mapGamepadButton(button)
|
||||
if action == "a" then
|
||||
-- Instant click at the virtual pointer (same path as a mouse/touch tap).
|
||||
self:mousepressed(self._padCursor.x, self._padCursor.y, 1)
|
||||
elseif button == "leftshoulder" then
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
-- When isGamepad(), raw face presses must not stack on gamepad* (NamingScreen a+b).
|
||||
-- On NX, SDL face labels are swapped so physical A confirms / B cancels.
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
if not _G.love then _G.love = require("tests.love_stub") end
|
||||
|
||||
@@ -20,23 +21,40 @@ check(GamepadMap.ignoreRawForJoystick(gamepadJoy), "ignore raw when isGamepad")
|
||||
check(not GamepadMap.ignoreRawForJoystick(rawJoy), "allow raw when not gamepad")
|
||||
check(not GamepadMap.ignoreRawForJoystick(nil), "nil joystick does not ignore raw")
|
||||
|
||||
-- Desktop: SDL a → GB A (unchanged).
|
||||
eq(GamepadMap.mapGamepadButton("a"), "a", "desktop SDL a -> GB A")
|
||||
eq(GamepadMap.mapGamepadButton("b"), "b", "desktop SDL b -> GB B")
|
||||
|
||||
GamepadMap._setForceNXForTests(true)
|
||||
eq(GamepadMap.mapRawButton(3), "a", "NX raw Y (#3) -> GB A")
|
||||
eq(GamepadMap.mapRawButton(4), "b", "NX raw X (#4) -> GB B")
|
||||
eq(GamepadMap.mapGamepadButton("a"), "b", "NX SDL south (a) -> GB B (Nintendo B)")
|
||||
eq(GamepadMap.mapGamepadButton("b"), "a", "NX SDL east (b) -> GB A (Nintendo A)")
|
||||
eq(GamepadMap.mapRawButton(1), "b", "NX raw #1 Nintendo B -> GB B")
|
||||
eq(GamepadMap.mapRawButton(2), "a", "NX raw #2 Nintendo A -> GB A")
|
||||
eq(GamepadMap.mapRawButton(3), nil, "NX raw Y (#3) not mapped as confirm")
|
||||
eq(GamepadMap.mapRawButton(4), nil, "NX raw X (#4) not mapped as confirm")
|
||||
GamepadMap._setForceNXForTests(false)
|
||||
|
||||
Input:init()
|
||||
Input:gamepadpressed(gamepadJoy, "a")
|
||||
Input:joystickpressed(gamepadJoy, 1) -- must no-op
|
||||
Input:joystickpressed(gamepadJoy, 2) -- must no-op (would have set b)
|
||||
Input:joystickpressed(gamepadJoy, 2) -- must no-op
|
||||
Input:step()
|
||||
check(Input:wasPressed("a"), "gamepad A edge present")
|
||||
check(Input:wasPressed("a"), "desktop gamepad A edge present")
|
||||
check(not Input:wasPressed("b"), "raw must not add B alongside gamepad A")
|
||||
check(Input:isDown("a"), "A held from pad source only")
|
||||
|
||||
-- NX: physical A arrives as SDL "b" → GB A.
|
||||
GamepadMap._setForceNXForTests(true)
|
||||
Input:init()
|
||||
Input:gamepadpressed(gamepadJoy, "b")
|
||||
Input:step()
|
||||
check(Input:wasPressed("a"), "NX physical A (SDL b) confirms as GB A")
|
||||
check(not Input:wasPressed("b"), "NX physical A must not also erase")
|
||||
GamepadMap._setForceNXForTests(false)
|
||||
|
||||
Input:init()
|
||||
Input:joystickpressed(rawJoy, 1)
|
||||
Input:step()
|
||||
check(Input:wasPressed("a"), "non-gamepad raw #1 still maps to A")
|
||||
check(Input:wasPressed("a"), "non-gamepad raw #1 still maps to A on desktop")
|
||||
|
||||
T.finish()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
-- NX raw fallback indices measured on OLED hardware (SWNX-11).
|
||||
-- NX raw fallback + Nintendo face remap (SWNX-11).
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
if not _G.love then _G.love = require("tests.love_stub") end
|
||||
|
||||
@@ -9,11 +9,12 @@ local GamepadMap = require("src.core.GamepadMap")
|
||||
|
||||
GamepadMap._setForceNXForTests(true)
|
||||
|
||||
-- Phase 0 / naming diagnosis: Y→#3→a, X→#4→b; Nintendo B/A at #1/#2.
|
||||
eq(GamepadMap.mapRawButton(3), "a", "NX raw Y (#3) maps to GB A")
|
||||
eq(GamepadMap.mapRawButton(4), "b", "NX raw X (#4) maps to GB B")
|
||||
eq(GamepadMap.mapRawToGamepadButton(3), "a", "NX raw #3 routes to gamepad a")
|
||||
eq(GamepadMap.mapRawToGamepadButton(4), "b", "NX raw #4 routes to gamepad b")
|
||||
eq(GamepadMap.mapGamepadButton("b"), "a", "NX physical A (SDL b) -> GB A")
|
||||
eq(GamepadMap.mapGamepadButton("a"), "b", "NX physical B (SDL a) -> GB B")
|
||||
eq(GamepadMap.mapRawButton(1), "b", "NX raw #1 Nintendo B -> GB B")
|
||||
eq(GamepadMap.mapRawButton(2), "a", "NX raw #2 Nintendo A -> GB A")
|
||||
eq(GamepadMap.mapRawToGamepadButton(1), "a", "NX raw #1 routes as SDL south name")
|
||||
eq(GamepadMap.mapRawToGamepadButton(2), "b", "NX raw #2 routes as SDL east name")
|
||||
eq(GamepadMap.mapRawButton(9), "select", "NX minus (#9) -> select")
|
||||
eq(GamepadMap.mapRawButton(10), "start", "NX plus (#10) -> start")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user