Skip to content

Support the NSO GameCube controller - #30

Merged
JoeGeC merged 17 commits into
JoeGeC:mainfrom
thisishuey:add-gamecube-controller-support
Oct 14, 2026
Merged

JoeGeC merged 17 commits into
JoeGeC:mainfrom
thisishuey:add-gamecube-controller-support

Conversation

@thisishuey

@thisishuey thisishuey commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

The NSO GameCube controller advertises product ID 0x2073, which the scanner didn't know. It connected as an unknown side, and a lone unknown controller is treated as a sideways Joy-Con. So its stick and D-pad were turned 90 degrees, its C-stick was never decoded, L + R didn't assign it, and it played as whichever Joy-Con the first button pressed suggested.

The scanner now names a ControllerModel from the product ID. The GameCube controller reports in the Pro Controller's layout, so it's parsed and assigned as one: one body, both sticks.

It shares nothing else with the Pro. For button mapping it's a body of its own, beside a pair and a lone Joy-Con, with its own layouts and saved bindings. Its GameCube default puts Z on its Z button (reported as ZR) and the triggers on L/R. The mapping editor offers only the buttons it has, with ZR shown as Z. It has no stick clicks, so no layout or saved binding can use them. In the Joy-Con Wii layout, which puts − on − and Recenter on a stick click, it gets − on X and Recenter on Y instead. Dolphin setup configures a GameCube player and still leaves a Pro player's section alone. Joy-Con pairs and Pro Controllers keep their layouts exactly as they were.

Analog L/R reach Dolphin. The controller reports trigger travel at 0x3C/0x3D. TriggerCalibrator maps it from the factory zero (SPI 0x013140) to the first stop, where analog travel ends, and drives the virtual gamepad's trigger bytes with it. Dolphin setup binds L-Analog and R-Analog to those axes, and the click at the second stop stays its own button. DSU carries no trigger travel: its packet fills the analog bytes from buttons. Every other controller keeps ZL/ZR on the trigger bytes.

Its other shoulder buttons move so each lands where Android expects it: ZL and Z on BUTTON_L1/BUTTON_R1, and the trigger clicks on BUTTON_THUMBL/BUTTON_THUMBR, which it has no stick clicks for. Nothing goes on BUTTON_L2/R2: on a Retroid Pocket Nova those keys followed the trigger axes, so with Z there, pulling R pressed Z and Z itself did nothing. Dolphin and Eden bind whatever each button sends; this decides what apps the app doesn't configure see.

At connect it also reads its factory stick calibration (0x0130A8 for the main stick, 0x0130E8 for the C-stick, decoded as the Linux and SDL Switch 2 drivers do), so its sticks start centred with their full range. Learning still applies on top, and takes over when the flash is unset or implausible. Only the GameCube controller's values have been checked against its sticks, so Joy-Con 2 and Pro Controllers aren't read and keep learning theirs.

A controller whose advertisement names no type, or the wrong one, can be corrected from its card: tapping its type opens a menu with Detect automatically first, then Left or Right Joy-Con, Pro Controller or GameCube controller. The choice is a ControllerModel stored per BLE address, which captures show is a fixed public address, so it applies again on reconnect. Scanning waits for the saved choices to load, so none is missed. On a connected controller it takes effect at once: the parser reads the new side's sticks, stick calibration starts over, and switching to GameCube reads its factory calibration. A controller assigned to a player is unassigned first, since its slot was checked against the old type. A manual choice is marked on the card, and only an unrecognised controller gets a warning.

A controller with analog triggers never falls back to the console protocol. Its report has no trigger bytes, and the GameCube controller answers on the common channel anyway.

Tested in Dolphin on a Retroid Pocket Nova (Android 13), with three GameCube controllers connected at once as players 1–3. Recognition, L + R assign, both sticks, the D-pad, every button, analog L/R with a separate click, the bumper layout, and the type menu all behave, including switching a controller to Pro and back to GameCube. Packet captures from the three controllers back the offsets and calibration values in protocol.md. Eden is untested on hardware: its config is covered by unit tests only. Four controllers and Android 14+ are untested, and scanning waiting for saved types has no unit test because Joycon2Manager builds its own scanner.

Rumble is left out: the app has no rumble path yet.

Closes #27

@JoeGeC JoeGeC left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this, it's a really thorough PR. Fixing it at the product ID and having one ControllerModel instead of guessing the side in several places is a big improvement. So is the shared SpiReadCommand/SpiReadReply, and the trigger calibration that learns only from a held first stop is a nice way round the hardware. The docs are exactly how I'd want them too: measurements dated, with the hardware named. A few things before I merge:

1. Rebase onto main. It conflicts with the clone-support and GATT-queue commits in JoyconConnection.kt, PacketParser.kt, ConnectionPool.kt and JoyconInput.kt. JoyconConnection needs the most care, since both changes touch the init writes. Please also check how the 0x3C/0x3D trigger bytes behave on the console-protocol path the clone commit added.

2. Eden: ZL also clicks the left stick (I think). ZL and Z move onto the stick clicks, but the GameCube body reuses the Pro layout, which still binds LStickClick → LS and RStickClick → RS. So in Eden, button_zl and button_lstick both end up on 106, and pressing ZL would fire ZL plus L3 (Z the same with R3). The mapping editor already leaves LS/RS out of physicalButtons(GAMECUBE), so the default layout disagrees with the editor. Returning null from emittedFor(GAMECUBE) for LS/RS, or dropping them from the GameCube entries in SwitchProMapping, would fix it. A test along the lines of EdenGamepadConfigTest asserting no button_lstick line would pin it down.

3. The controller type menu. I like having an override, but I'd rather it didn't add a full-width picker to every unassigned card, since Auto is right almost every time. Could it move to the type label in the card header instead?

  • Make the header's type label ("GameCube", "Pro", "Left"…) tappable, with a small ▾, and open the menu from there using PanelDropdownMenu. When detection works, the card looks as it does today.
  • When the model is UNKNOWN, show a WarningBox on the card ("Type not recognised — tap the name to set it"), so it only asks when it needs to.
  • Put Detect automatically first in the menu instead of "Auto: X", and mark a manually set type (e.g. "Pro Controller · set manually") so it's easy to spot and undo.
  • Add GameCube controller to the choices. That means storing the override as a ControllerModel rather than a Side. As it is, a misdetected GameCube controller can only be forced to Pro, and then loses analog triggers, its layouts and its Dolphin port.

Smaller things, take or leave:

  • The saved overrides start out empty (stateIn(..., emptyMap())), so a controller found before DataStore emits connects on Auto. Waiting for the first emission before connecting would avoid that.
  • JoyconConnection.factory is written from BLE callbacks and read in overrideType on the main thread. It should probably be @Volatile.
  • EdenGamepadConfig.sideFor and EdenDsuConfig.sideFor duplicate PlayerState.joyconSide(). Since you're adding a line to each, it'd be nice to have both call joyconSide().
  • DSU still sends L2/R2 as on or off and never sees the analog travel. That's fine for now, but worth a line in virtual-gamepad.md.

The NSO GameCube controller advertises product ID 0x2073, which the
scanner didn't know. It connected as an unknown side, and a lone unknown
controller is treated as a sideways Joy-Con. So its stick and D-pad were
turned 90 degrees, its C-stick was never decoded, L + R didn't assign
it, and it played as whichever Joy-Con the first button pressed
suggested.

The scanner now names a ControllerModel from the product ID. The
GameCube controller reports in the Pro Controller's layout, so it's
parsed and assigned as one: one body, both sticks.

It shares nothing else with the Pro. For button mapping it's a body of
its own, beside a pair and a lone Joy-Con, with its own layouts and
saved bindings. Its default puts Z on its Z button (reported as ZR) and
the triggers on L/R. The mapping editor offers only the buttons it has,
with ZR shown as Z. Dolphin setup configures a GameCube player and still
skips Pro players, as before. Joy-Con pairs and Pro Controllers keep
their layouts exactly as they were.

Each controller card now has a Controller type menu: Auto, Pro
Controller, Left or Right Joy-Con. This covers any controller whose
advertisement names no type or the wrong one. The choice is stored per
BLE address, which captures show is a fixed public address, so it
applies again on reconnect. On a connected controller it takes effect at
once: the parser reads the new side's sticks, and stick calibration
starts over. A controller assigned to a player is unassigned first,
since its slot was checked against the old type.

Analog L/R now reach Dolphin. The controller reports trigger travel at
0x3C/0x3D. TriggerCalibrator maps it from the factory zero (SPI
0x013140) to the first stop, where analog travel ends, and drives the
virtual gamepad's trigger bytes with it. Dolphin setup binds L-Analog
and R-Analog to those axes and keeps the click at the second stop on
L/R. Every other controller keeps ZL/ZR on the trigger bytes.

On this controller ZL and Z also move off BUTTON_L2/R2 and onto the
stick clicks, which it doesn't have. On a Retroid Pocket Nova those keys
followed the trigger axes, so pulling R pressed Z and Z itself did
nothing. With the move, both work.

At connect it also reads its factory stick calibration (0x0130A8 for the
main stick, 0x0130E8 for the C-stick, decoded as the Linux and SDL
Switch 2 drivers do), so its sticks start centred with their full range.
Learning still applies on top, and takes over when the flash is unset or
implausible. Only the GameCube controller's values have been checked
against its sticks, so Joy-Con 2 and Pro Controllers aren't read and
keep learning theirs.

Tested on a Retroid Pocket Nova (Android 13), with three GameCube
controllers connected at once as players 1–3 in Dolphin. Recognition,
L + R assign, both sticks, the D-pad, every button, analog L/R with a
separate click, and the type menu all behave. Packet captures from the
three controllers back the offsets and calibration values in
protocol.md. Four controllers and Android 14+ are untested.

Rumble is left out: the app has no rumble path yet.

Closes JoeGeC#27

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@thisishuey
thisishuey force-pushed the add-gamecube-controller-support branch from dd6e9b8 to 8ca60ab Compare October 13, 2026 01:05
thisishuey and others added 12 commits October 13, 2026 04:16
A GameCube controller's ZL and Z travel on the stick-click bits, which it
has no buttons for. Its layouts still bound the stick clicks there, so
each press fired twice: in Eden, ZL also pressed L3 and Z pressed R3, on
both the virtual gamepad and DSU. In the Joy-Con Wii layout, Z pressed
Two and Recenter at once.

emittedFor now resolves LS and RS to nothing for a GameCube controller,
so no config writer can bind them, whatever the mapping says. Its Switch
Pro layout drops the stick-click targets, and its Joy-Con Wii layout
leaves Recenter unbound: there is no stick click to put it on, and the
Wii layout's default, R, is already One there.

The Joy-Con layout's no-double-fire test now checks the buttons each
body emits rather than the ones it names. That catches this case and
caught the R clash.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The analog triggers already drive the trigger axes, so the bumper keys
are where Android expects ZL and Z: Z is the GameCube's bumper. They move
from the stick clicks to BUTTON_L1/BUTTON_R1, and the trigger clicks at
the bottom of L/R take the free stick clicks instead. Nothing goes on
BUTTON_L2/R2, which can follow the trigger axes.

Dolphin and Eden bind whatever each button sends, so they play as
before. This changes what apps the app doesn't configure see by default:
a full trigger pull no longer presses a bumper, and Z no longer presses
the right stick in.

Tested on a Retroid Pocket Nova (Android 13) with an NSO GameCube
controller.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Joy-Con layout puts − on the controller's own − and Recenter on a
stick click. A GameCube controller has neither, so − pressed nothing and
Recenter was left unbound. − now takes X, as it already does on a lone
right Joy-Con, and Recenter takes Y, which was free.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The override was stored as a Side, so a GameCube controller the scanner
didn't recognise could only be forced to Pro Controller. That gave it a
Pro's layouts, with no analog triggers and no C-stick calibration.

The override is now a ControllerModel: Left or Right Joy-Con, Pro
Controller or GameCube controller. The model in effect is the override
or, without one, what the advertisement named, and the side follows from
it. ConnectedJoycon carries both, so the menu's Auto entry still names
what was detected.

Switching a connected controller to GameCube also reads its factory
calibration, as connecting as one does.

Overrides saved as a side no longer parse and are dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The type picker was a full-width dropdown on every unassigned card,
recognised or not. The type label at the top of the card now opens it
instead, so a recognised controller costs no extra row.

The menu lists Detect automatically first, sub-labelled with what was
detected, then the four types. A manual choice shows SET MANUALLY beside
the label. Only an unrecognised controller gets a warning, and tapping
it opens the same menu.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The saved type overrides started as an empty map until DataStore
answered, so a controller found in that window connected as its
advertised type and kept it. A scan now starts once they have loaded:
at once in the usual case, when they already have.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It is written on the binder thread a reply arrives on and read on the
main thread when the type changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
EdenGamepadConfig, EdenDsuConfig and DolphinGcpadConfig each kept their
own copy of PlayerState.joyconSide(), so the GameCube case had to be added
three times. They now call it. Dolphin's GameCube setup still leaves a Pro
Controller to the player, now under one name in both places it checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The console fallback takes over after 1.5 s of silence on the common
channel, or on an SMP security request. The console report has no
trigger bytes and its parser knows only the Joy-Con's buttons, so a
GameCube controller moved there would lose its triggers. It answers on
the common channel, so this hasn't been seen, but nothing stopped it.
Now a controller with analog triggers never starts a console session,
and on a security request it goes on to the common channel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both rules lived inside Android classes no unit test can build. They now
sit in ConsoleFallback and StoredControllerModel, with tests: a GameCube
controller never falls back to the console protocol, and a side saved by
an earlier build reads as no override.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…names

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@thisishuey
thisishuey force-pushed the add-gamecube-controller-support branch from bc4d180 to 3a999f1 Compare October 13, 2026 04:16
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@thisishuey

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review! Everything's addressed, and I've updated the description to match. Rebased onto main, then one commit per item:

Commit What
8ca60ab The original change, rebased. Calibration reads now go through CommonSession and ControllerCommands.spiRead (our SPI-read builder is gone). model/typeOverride moved after deviceName in ConnectedJoycon so the positional test callers still compile.
7d79662 Stick-click bindings no longer fire with ZL/Z
925545c ZL/Z on L1/R1, trigger clicks on L3/R3
e0104af Joy-Con Wii layout: − on X, Recenter on Y for a GameCube controller
b204969 Type override stored as a ControllerModel, so GameCube is a choice
f7bc9d4 Type menu moved into the card's type label
b4701a9 Scanning waits for saved overrides to load
581d212 @Volatile on factory
65a1106 Config writers call PlayerState.joyconSide()
b83bc3f DSU trigger note in virtual-gamepad.md
bb3237b Controllers with analog triggers never fall back to the console protocol
3d808d8 Tests for the console fallback guard and for reading saved controller types
3a999f1 GameCube controller added to CONTRIBUTING.md's bug-report list
543a4dc virtual-gamepad.md says the bumper layout was confirmed in Dolphin only

Eden double-binding: good catch, and it was wider than Eden. ZL and Z travelled on the stick-click bits, so any layout binding a stick click fired twice: Eden over both the pad and DSU, and in the Joy-Con Wii layout, Z pressed both Two and Recenter. emittedFor now resolves LS/RS to nothing for a GameCube controller, so no config writer can bind them whatever the mapping says, and the Switch Pro layout drops those targets for it. Tests check that neither Eden output writes a button_lstick/button_rstick line. The Joy-Con layout's no-double-fire test now compares what each controller actually sends, which is what caught the Wii case.

Bumpers (not something you asked for): ZL and Z are now on BUTTON_L1/BUTTON_R1, and the clicks at the bottom of L/R on BUTTON_THUMBL/BUTTON_THUMBR. The analog travel stays on the trigger axes. Dolphin and Eden bind whatever each button sends, so they should play the same: confirmed in Dolphin, while Eden is covered by the config tests only. It changes what apps we don't configure see: Z is the GameCube's bumper and now reads as one, and a full trigger pull no longer presses a bumper. Nothing uses L2/R2, for the reason in virtual-gamepad.md#analog-triggers.

Type menu: the override is a ControllerModel now, so a misdetected GameCube controller can be set to GameCube and gets its analog triggers and layouts. Switching a connected controller to GameCube also reads its factory calibration. The card's type label opens the menu: Detect automatically first, sub-labelled with what was detected, then the four types. A manual choice shows SET MANUALLY, and only an unrecognised controller gets a warning, which opens the same menu.

Console protocol and trigger bytes: the GameCube controller answers on the common channel, so in practice it never reached the console path. To rule it out, bb3237b keeps controllers with analog triggers off it entirely. The console report has no trigger bytes and ConsolePacketParser only knows Joy-Con buttons, so it couldn't work there. On a security request it now goes on to the common channel.

DSU: its L2/R2 analog bytes stay 0 for this controller, because the encoder fills them from buttons (0 or 255). Sending real travel there would be a small follow-up if you want it.

Tested in Dolphin on a Retroid Pocket Nova (Android 13) with NSO GameCube controllers up to f7bc9d4: the bumper layout, the type menu, and switching a controller to GameCube and back. I haven't tested Eden on hardware, so the Eden double-binding fix is covered by the config tests only. The last eight commits are small and covered by unit tests (build and :konsist:test pass locally), except the wait-for-overrides change in b4701a9: Joycon2Manager builds its own BLE scanner, so it has no unit test.

@thisishuey
thisishuey requested a review from JoeGeC October 13, 2026 04:30

@JoeGeC JoeGeC left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this is looking really good. I installed it on an AYN Thor and the type menu works nicely. Two changes before merge, both about the GameCube controller showing up as a Pro Controller:

1. Label it as a GameCube controller everywhere, not just on the assignment card

The assignment card says GameCube, but once the controller is assigned it reads "Pro" again:

  • PlayerMappingCard.kt, ControllerChips: the mapping screen chip uses R.string.controller_pro for any hasPro player.
  • CompactPlayerRow.kt:62: same in the compact view.

Please give these a GameCube label when hasGameCube. A shared helper for "which label does this player's controller get" would stop the three places drifting apart again.

2. Show only the GameCube controller's own buttons in the detailed view

ControllerLayout.kt sends any hasPro player to ProControllerLayout, which draws inputs the GameCube controller doesn't have: GL/GR, −, and the LS/RS click on the stick cards. Its Z also shows as "ZR". Keep the overall layout the same, but for a GameCube controller:

  • leave out GL, GR, − and the stick clicks
  • label ZR as "Z", as the mapping editor already does

Driving this from one list of the buttons each model has would be the cleanest way. MappingOptions.physicalButtons(GAMECUBE) already has that list, so it's worth sharing rather than writing it out a second time.

thisishuey and others added 3 commits October 13, 2026 22:43
… listed

The assignment card said GameCube, but once assigned, the compact player
row and the mapping card said Pro for any Pro-shaped body. All three now
name a controller by its model through the same exhaustive when, so a
new model fails to build until each has a word for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It used the Pro Controller's layout as is: GL, GR, − and the stick
clicks, with Z labelled ZR. GameCubeButtons in core:model now holds what
it has and its Z marking; the detailed view leaves out the rest and
marks Z, and the mapping editor offers from the same list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
L and R are its triggers, so they head the shoulder columns, the place
ZL and ZR take on a Pro Controller. The two columns now share one
ShoulderColumn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@thisishuey

Copy link
Copy Markdown
Contributor Author

Both done:

Commit What
8220eec The compact player row and the mapping card name a GameCube controller as one, like the assignment card
a09f182 The detailed view draws only the GameCube controller's own buttons, with ZR marked Z
ee2ea80 The detailed view puts its triggers, L and R, above ZL and Z

Labels: the three call sites live in feature:assignment:presentation, feature:connection:presentation and core:buttonmapping:presentation, which share only core:model and core:designsystem. A composable helper can't go in the design system (it stays app-agnostic per architecture.md), and core:model can't hold strings. So each module has the same ConnectedJoycon.modelLabel(), an exhaustive when over ControllerModel. They name by model rather than by hasPro, and a new model fails to build in all three until it has a word. Happy to move them into a shared presentation module instead if you'd prefer that.

Detailed view: GameCubeButtons in core:model is now the one list of what the controller has, plus its Z marking. ProControllerLayout leaves out GL, GR, − and the stick-click highlight for a GameCube controller and marks Z, with the battery kept centred. L and R head the shoulder columns, so the on-screen layout matches the controller the way the other layouts match theirs: they're its triggers, where a Pro Controller has ZL and ZR. MappingOptions.physicalButtons(GAMECUBE) and the editor's Z label read the same list. It has a test, and DESIGN.md notes it.

Tested on a Retroid Pocket Nova (Android 13): an NSO GameCube controller is labelled GameCube on the compact row and the mapping card, and its detailed view shows only its own buttons, with L/R above ZL/Z and each lighting on press. A Pro Controller and left and right Joy-Cons still show and behave as before.

@thisishuey
thisishuey requested a review from JoeGeC October 13, 2026 23:51
@JoeGeC
JoeGeC merged commit 690b8cb into JoeGeC:main Oct 14, 2026
1 check passed
thisishuey added a commit to thisishuey/joycon2android that referenced this pull request Oct 14, 2026
Dolphin passes an uncalibrated stick straight through, so a full tilt
reaches GameCube ±127, where a real pad's gate stops near ±101 on the
main stick and ±92 on the C-stick. Every push lands about a quarter
further than on hardware, so walking slowly or aiming finely is hard: a
light push already runs, and a scope overshoots. Dolphin for Android has
no Calibrate button, and nothing else sets it.

Dolphin setup now writes `Calibration = 100.00`, a full circle, for each
stick it binds. Dolphin then maps the stick's rim onto its default Gate
Size octagon: 79.37% for the main stick and 72.21% for the C-stick,
values Dolphin averaged from genuine controllers. A full push still
reaches the real gate. Sources are Dolphin's GCPadEmu.h and
StickGate.cpp, and Dolphin PR #8963.

Setup still leaves a Pro Controller player's section to the player. It
only adds the two calibration keys there, keeping the bindings they
made, and only to a section that already exists.

A new Dolphin section in Settings has a Calibrate sticks switch. It's on
by default, for anyone who prefers the old full-range feel, and takes
effect at the next Set up. The README covers the setting and adds a
troubleshooting row.

Tested on a Retroid Pocket Nova (Android 13) with NSO GameCube
controllers, using the GameCube controller support from JoeGeC#30: walking and
scope aiming feel like an original controller. Joy-Con 2 and Pro
Controllers are untested.

The hand-mapped step skips a GameCube controller player, since setup
writes their whole section.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
JoeGeC pushed a commit that referenced this pull request Oct 15, 2026
* Calibrate Dolphin's sticks so they stop at the GameCube gate

Dolphin passes an uncalibrated stick straight through, so a full tilt
reaches GameCube ±127, where a real pad's gate stops near ±101 on the
main stick and ±92 on the C-stick. Every push lands about a quarter
further than on hardware, so walking slowly or aiming finely is hard: a
light push already runs, and a scope overshoots. Dolphin for Android has
no Calibrate button, and nothing else sets it.

Dolphin setup now writes `Calibration = 100.00`, a full circle, for each
stick it binds. Dolphin then maps the stick's rim onto its default Gate
Size octagon: 79.37% for the main stick and 72.21% for the C-stick,
values Dolphin averaged from genuine controllers. A full push still
reaches the real gate. Sources are Dolphin's GCPadEmu.h and
StickGate.cpp, and Dolphin PR #8963.

Setup still leaves a Pro Controller player's section to the player. It
only adds the two calibration keys there, keeping the bindings they
made, and only to a section that already exists.

A new Dolphin section in Settings has a Calibrate sticks switch. It's on
by default, for anyone who prefers the old full-range feel, and takes
effect at the next Set up. The README covers the setting and adds a
troubleshooting row.

Tested on a Retroid Pocket Nova (Android 13) with NSO GameCube
controllers, using the GameCube controller support from #30: walking and
scope aiming feel like an original controller. Joy-Con 2 and Pro
Controllers are untested.

The hand-mapped step skips a GameCube controller player, since setup
writes their whole section.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Always calibrate Dolphin's sticks, and keep a calibration already set

Review feedback: the Calibrate sticks switch only served someone who
prefers the twitchier full-range feel, and turning it off never undid the
keys setup had added to a Pro Controller's section. Setup now always
calibrates, and the switch, its setting and its use case are gone.

Setup keeps a Calibration the section already has instead of overwriting
it, in a Joy-Con section it writes as well as a Pro Controller's. A
player who calibrated by hand keeps it across Set up.

The full-circle constant moves next to STANDARD_CONTROLLER, out from
under the hat-switch comment. A duplicate Pro Controller test goes, and
two tests cover a kept calibration. The README drops the settings entry
and keeps the troubleshooting row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Ability to manually assign NSO Gamecube Controller as Pro Controller via touch menu

2 participants