switch-pico/tests/test_controller_profile_native.py
Joey Yakimowich-Payne d15b0ec711 feat(profiles): add solo Joy-Con layouts and digital stick mappings
Add per-profile native solo routing, stick/click swapping, SL/SR output mappings, and digital left-stick directions with analog priority. Extend both codecs to schema 11 while preserving legacy profiles, expose the controls and preview in Studio, and carry rail outputs through Pro emulation. Verified with 541 tests, firmware builds, editor checks, and saved-profile preservation during native 0.89 deployment.
2026-09-15 17:33:31 -06:00

113 lines
4.5 KiB
Python

import shutil
import subprocess
from pathlib import Path
from dataclasses import replace
from switch_pico_bridge import config_manager
def test_controller_profile_native(tmp_path: Path) -> None:
root = Path(__file__).resolve().parents[1]
compiler = shutil.which("c++") or shutil.which("g++")
assert compiler is not None, "a host C++ compiler is required"
executable = tmp_path / "controller_profile_test"
subprocess.run(
[
compiler,
"-std=c++17",
"-Wall",
"-Wextra",
"-Werror",
"-pedantic",
f"-I{root / 'src' / 'firmware'}",
str(root / "tests" / "controller_profile_test.cpp"),
str(root / "src" / "firmware" / "core" / "controller_identity.cpp"),
str(root / "src" / "firmware" / "profile" / "controller_profile.cpp"),
"-o",
str(executable),
],
check=True,
cwd=root,
)
subprocess.run([str(executable)], check=True, cwd=root)
legacy = subprocess.run(
[str(executable), "--legacy9"], check=True, cwd=root, capture_output=True
).stdout
migrated = config_manager.ControllerProfile.from_bytes(legacy)
assert migrated.to_bytes()[2:] == legacy[2:]
assert migrated.native_joycon_layout == 0
assert migrated.swap_sticks is False
assert migrated.swing.macro == 0
assert migrated.nunchuk_swing.button == 15
assert migrated.combined_swing.macro == 3
upgraded = subprocess.run(
[str(executable), "--codec"],
input=legacy, check=True, cwd=root, capture_output=True,
).stdout
assert upgraded == migrated.to_bytes()
legacy10 = subprocess.run(
[str(executable), "--legacy10"], check=True, cwd=root, capture_output=True
).stdout
migrated10 = config_manager.ControllerProfile.from_bytes(legacy10)
assert legacy10[:4] == bytes((10, 0, 128, 1))
assert migrated10.native_joycon_layout == 2
assert migrated10.swap_sticks is True
assert migrated10.button_map[0] == 18
assert migrated10.extra_button_map[0] == 19
assert migrated10.shift.button_map[0] == 20
assert migrated10.shift.extra_button_map[0] == 21
assert migrated10.left_trigger.output == migrated10.right_trigger.output == 21
assert migrated10.to_bytes()[2:] == legacy10[2:]
upgraded10 = subprocess.run(
[str(executable), "--codec"],
input=legacy10, check=True, cwd=root, capture_output=True,
).stdout
assert upgraded10 == migrated10.to_bytes()
directions = replace(
migrated10,
button_map=(*migrated10.button_map[:12], 22, 23, 24, 25),
extra_button_map=(24, *migrated10.extra_button_map[1:]),
shift=replace(
migrated10.shift,
button_map=(23, *migrated10.shift.button_map[1:]),
extra_button_map=(25, *migrated10.shift.extra_button_map[1:]),
),
left_trigger=replace(migrated10.left_trigger, output=22),
right_trigger=replace(migrated10.right_trigger, output=22),
)
direction_wire = directions.to_bytes()
native_directions = subprocess.run(
[str(executable), "--codec"],
input=direction_wire, check=True, cwd=root, capture_output=True,
).stdout
assert native_directions == direction_wire
assert config_manager.ControllerProfile.from_bytes(native_directions) == directions
assert config_manager.ControllerProfile.from_json(directions.to_json()) == directions
for layout in range(len(config_manager.NATIVE_JOYCON_LAYOUTS)):
for swap in (False, True):
profile = replace(
migrated,
native_joycon_layout=layout,
swap_sticks=swap,
button_map=(18, *migrated.button_map[1:]),
extra_button_map=(19, *migrated.extra_button_map[1:]),
shift=replace(
migrated.shift,
button_map=(20, *migrated.shift.button_map[1:]),
extra_button_map=(21, *migrated.shift.extra_button_map[1:]),
),
left_trigger=replace(migrated.left_trigger, output=21),
right_trigger=replace(migrated.right_trigger, output=21),
)
encoded = profile.to_bytes()
native = subprocess.run(
[str(executable), "--codec"],
input=encoded, check=True, cwd=root, capture_output=True,
).stdout
assert native == encoded
assert config_manager.ControllerProfile.from_bytes(native) == profile