diff --git a/src/core/GamepadMap.lua b/src/core/GamepadMap.lua index 68fe4ff9..0f713845 100644 --- a/src/core/GamepadMap.lua +++ b/src/core/GamepadMap.lua @@ -67,6 +67,24 @@ function GamepadMap.mapGamepadButton(button) return GamepadMap.gamepadBindings()[button] end +-- Select+face display chords (docs / Nintendo UX): +-- Select+A → "2" (COLORS), Select+B → "3" (TILT), +-- Select+Y → "5", Select+X → "6", Select+L (leftshoulder) → "7". +-- For a/b: resolve through mapGamepadButton then GB a→"2", b→"3" so NX +-- Nintendo physical A/B match the docs despite SDL face-label swap. +-- Caller (Game:gamepadpressed) must require Select held; this is map-only. +function GamepadMap.displayChordDigit(gamepadButton) + if gamepadButton == "y" then return "5" end + if gamepadButton == "x" then return "6" end + if gamepadButton == "leftshoulder" then return "7" end + if gamepadButton == "a" or gamepadButton == "b" then + local gb = GamepadMap.mapGamepadButton(gamepadButton) + if gb == "a" then return "2" end + if gb == "b" then return "3" end + end + return nil +end + -- love-nx / SDL: when isGamepad(), face+menu already arrive via gamepad*. -- Applying joystickpressed raw on top double-fires GB A/B in one frame. function GamepadMap.ignoreRawForJoystick(joystick) diff --git a/tests/engine/input_display_chord_test.lua b/tests/engine/input_display_chord_test.lua new file mode 100644 index 00000000..9acddcec --- /dev/null +++ b/tests/engine/input_display_chord_test.lua @@ -0,0 +1,43 @@ +-- Select+face display chord digit map (NXMOD-06..09). +-- Spec: Nintendo UX A/B → keys 2/3; Y/X/L → 5/6/7. Map-only (Select held is Game's job). +package.path = "./?.lua;./?/init.lua;" .. package.path +if not _G.love then _G.love = require("tests.love_stub") end + +local T = require("tests.harness") +local check = T.check +local eq = T.eq + +local GamepadMap = require("src.core.GamepadMap") + +-- Desktop / default: SDL face labels map identity for A/B chords. +GamepadMap._setForceNXForTests(false) +eq(GamepadMap.displayChordDigit("a"), "2", "desktop SDL a (GB A) -> key 2") +eq(GamepadMap.displayChordDigit("b"), "3", "desktop SDL b (GB B) -> key 3") +eq(GamepadMap.displayChordDigit("y"), "5", "Y -> key 5") +eq(GamepadMap.displayChordDigit("x"), "6", "X -> key 6") +eq(GamepadMap.displayChordDigit("leftshoulder"), "7", "leftshoulder (L) -> key 7") + +-- Unmapped buttons yield nil (no accidental digit). +eq(GamepadMap.displayChordDigit("start"), nil, "start is not a display chord") +eq(GamepadMap.displayChordDigit("back"), nil, "back/Select alone is not a digit") +eq(GamepadMap.displayChordDigit("dpup"), nil, "d-pad is not a display chord") +eq(GamepadMap.displayChordDigit("rightshoulder"), nil, "R is not a display chord") +eq(GamepadMap.displayChordDigit(nil), nil, "nil button -> nil") + +-- NX Nintendo UX: GamepadMap swaps SDL a/b so physical A/B match docs. +-- Physical Nintendo A arrives as SDL "b" → GB a → key "2". +-- Physical Nintendo B arrives as SDL "a" → GB b → key "3". +GamepadMap._setForceNXForTests(true) +eq(GamepadMap.mapGamepadButton("b"), "a", "precondition: NX SDL east -> GB A") +eq(GamepadMap.mapGamepadButton("a"), "b", "precondition: NX SDL south -> GB B") +eq(GamepadMap.displayChordDigit("b"), "2", + "NX physical A (SDL b / GB a) -> key 2") +eq(GamepadMap.displayChordDigit("a"), "3", + "NX physical B (SDL a / GB b) -> key 3") +-- Y/X/L unchanged under NX face swap. +eq(GamepadMap.displayChordDigit("y"), "5", "NX Y -> key 5") +eq(GamepadMap.displayChordDigit("x"), "6", "NX X -> key 6") +eq(GamepadMap.displayChordDigit("leftshoulder"), "7", "NX L -> key 7") +GamepadMap._setForceNXForTests(false) + +T.finish()