Gameport joystick support and host USB controller integration through SDL, plus UI mapper, and MCP tools#2162
Gameport joystick support and host USB controller integration through SDL, plus UI mapper, and MCP tools#2162Copilot wants to merge 13 commits into
Conversation
|
@copilot proceed with next slice |
Slice 9 (scripted joystick input replay) shipped in 5773eba: Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
--JoystickProfilesDirectory CLI + wire MIDI/Rumble routers into Gameport
maximilien-noal
left a comment
There was a problem hiding this comment.
@copilot ensure all slices are implemented
Slice 10 (hardware-free I want to be transparent about the rest. Four large work items realistically need their own sessions because each pulls in dependencies outside of
Each of those would benefit from its own focused PR with the right reviewers and a real device / SDL build environment. I can pick whichever you want to tackle next. Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
maximilien-noal
left a comment
There was a problem hiding this comment.
@copilot ensure all slices are delivered in this PR.
Dosbox staging is your reference https://github.com/dosbox-staging/dosbox-staging
Three more slices delivered in 3f6015e (full suite 1883 passed):
Remaining DOSBox-Staging parity items (real SDL3 adapter, Avalonia mapper UI, end-to-end smoke against a real game) need a UI/native build environment and are best handled in a follow-up PR; everything that fits inside Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
|
@copilot use the same Nuget package for the SDL as master. Ensure you deliver the SDL integration and parity slice in this PR. |
Slice 14 (SDL integration) shipped in f146261. Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
maximilien-noal
left a comment
There was a problem hiding this comment.
@copilot vastly simplify the PR description.
Simplified the PR description down to a checklist of the 6 slices. |
|
@copilot don't mention slices, use the PR description template, rewrite a global overview in the PR description |
Added a joystick mapper UI window in fd2dabd. New "Joystick > Mapper..." menu opens a window with toolbar (New / Load / Save / Save As / Add Profile / Remove Profile), profile list, per-profile fields (name, GUID, device name, type, deadzone, circular deadzone, swap stick B), DataGrids for axes and buttons, and panels for hat / rumble / MIDI-on-gameport. Save/load go through Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
There was a problem hiding this comment.
Pull request overview
This PR introduces a new joystick/gameport input stack (IBM PC port 0x201) with supporting JSON profile persistence, replay tooling, keyboard fallback, MCP controls, and an Avalonia joystick-mapper UI, integrating joystick events into the existing InputEventHub pipeline and updating DI/wiring accordingly.
Changes:
- Added joystick event types/interfaces and extended
InputEventHubto queue joystick connect/axis/button/hat events alongside keyboard/mouse. - Implemented joystick profile persistence (mapping + replay JSON stores), routers (rumble + MIDI-on-gameport), replay player/host, and keyboard-as-joystick fallback with extensive new test coverage.
- Added Avalonia joystick mapper window + menu entry and wired SDL joystick polling into
MainWindowViewModel, plus DI updates (gameport device + profile auto-loading/activation).
Reviewed changes
Copilot reviewed 79 out of 79 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Spice86.Tests/ViewModels/JoystickMapperViewModelTests.cs | Adds tests for mapper VM state, add/remove profile, save/load via store + picker mocks. |
| tests/Spice86.Tests/Native/SdlJoystickInputTests.cs | Smoke tests that SDL adapter init/poll/rescan/dispose behave in CI without devices. |
| tests/Spice86.Tests/Emulator/Devices/Input/Joystick/Replay/JoystickReplayJsonStoreTests.cs | Validates replay script JSON round-trip, schema handling, lenient parsing, and formatting. |
| tests/Spice86.Tests/Emulator/Devices/Input/Joystick/Replay/JoystickReplayHostTests.cs | Tests real-time replay host ticking against a deterministic time provider. |
| tests/Spice86.Tests/Emulator/Devices/Input/Joystick/Mapping/RumbleRouterTests.cs | Covers rumble routing defaults, scaling/clamping, disable semantics, and logging transitions. |
| tests/Spice86.Tests/Emulator/Devices/Input/Joystick/Mapping/MidiOnGameportRouterTests.cs | Covers MIDI-on-gameport routing enable/disable, base-port handling, and transitions. |
| tests/Spice86.Tests/Emulator/Devices/Input/Joystick/Mapping/JoystickProfileAutoLoaderTests.cs | Tests deterministic profile loading/merging + GUID/name/default/fallback resolution. |
| tests/Spice86.Tests/Emulator/Devices/Input/Joystick/Mapping/JoystickProfileActivatorTests.cs | Tests connect/disconnect applying profile settings to routers and disposal behavior. |
| tests/Spice86.Tests/Emulator/Devices/Input/Joystick/Mapping/JoystickMappingJsonStoreTests.cs | Validates mapping JSON store round-trips, schema rejection, enum string serialization. |
| tests/Spice86.Tests/Emulator/Devices/Input/Joystick/Keyboard/KeyboardJoystickMapperTests.cs | End-to-end tests for keyboard fallback posting joystick events through the hub. |
| tests/Spice86.Tests/Emulator/Devices/Input/Joystick/JoystickTestDoubles.cs | Adds deterministic fake time provider + fake joystick GUI event source for tests. |
| tests/Spice86.Tests/Emulator/Devices/Input/Joystick/InputEventHubJoystickFlowTests.cs | Ensures joystick events queue on UI side and apply on emulator pump into gameport. |
| tests/Spice86.Tests/Emulator/Devices/Input/Joystick/GameportTimerTests.cs | Validates timer arming/expiry matches DOSBox-staging style formulas. |
| tests/Spice86.Tests/Emulator/Devices/Input/Joystick/GameportTests.cs | Covers port 0x201 read/write behavior, buttons, arming window, disconnect reset, etc. |
| tests/Spice86.Tests/Emulator/Devices/Input/Joystick/GameportRouterWiringTests.cs | Verifies gameport write forwarding to MIDI router and exposure of optional routers. |
| src/Spice86/Views/MainWindow.axaml | Adds “Joystick → Mapper…” menu entry. |
| src/Spice86/Views/JoystickMapperWindow.axaml.cs | New code-behind for the joystick mapper window. |
| src/Spice86/Views/JoystickMapperWindow.axaml | New mapper UI layout: toolbar, profile list, bindings grids, hat/rumble/MIDI panels. |
| src/Spice86/Views/Behaviors/ShowAdditionnalWindowBehavior.cs | Adds behavior hook to open the joystick mapper window. |
| src/Spice86/ViewModels/ViewModelWithErrorDialog.cs | Adds ShowJoystickMapperCommand relay command. |
| src/Spice86/ViewModels/Services/HostStorageProvider.cs | Extends storage provider abstraction with open-file picker API. |
| src/Spice86/ViewModels/Services/HeadlessGui.cs | Implements IGuiJoystickEvents and adds joystick simulation helpers for headless mode. |
| src/Spice86/ViewModels/MainWindowViewModel.cs | Implements IGuiJoystickEvents; wires SDL joystick init + polling/rescan timers + event forwarding. |
| src/Spice86/ViewModels/JoystickProfileEditorViewModel.cs | New editor VM for a single profile including axes/buttons/hat/rumble/MIDI fields. |
| src/Spice86/ViewModels/JoystickMapperViewModel.cs | New mapper VM with New/Load/Save/SaveAs, profile add/remove, and mapping materialization. |
| src/Spice86/ViewModels/JoystickButtonMappingEditorViewModel.cs | New editor VM for a single button binding row. |
| src/Spice86/ViewModels/JoystickAxisMappingEditorViewModel.cs | New editor VM for a single axis binding row. |
| src/Spice86/Spice86DependencyInjection.cs | Wires gameport device + routers + profile auto-loader/activator; registers mapper VM in resources; updates InputEventHub ctor usage. |
| src/Spice86.Shared/Interfaces/IMpu401DataSink.cs | Adds narrow MPU-401 data sink abstraction for routing gameport writes. |
| src/Spice86.Shared/Interfaces/IGuiJoystickEvents.cs | Adds UI→core joystick event interface parallel to keyboard/mouse. |
| src/Spice86.Shared/Emulator/Input/Joystick/VirtualStickState.cs | Adds immutable per-stick snapshot state (axes/buttons/hat/connected). |
| src/Spice86.Shared/Emulator/Input/Joystick/VirtualJoystickState.cs | Adds immutable two-stick snapshot state. |
| src/Spice86.Shared/Emulator/Input/Joystick/RumbleEffect.cs | Defines rumble effect data model + Stop sentinel. |
| src/Spice86.Shared/Emulator/Input/Joystick/Replay/JoystickReplayStepType.cs | Defines replay step kinds (Axis/Button/Hat/Connect/Disconnect). |
| src/Spice86.Shared/Emulator/Input/Joystick/Replay/JoystickReplayStep.cs | Defines replay step payload fields and delay semantics. |
| src/Spice86.Shared/Emulator/Input/Joystick/Replay/JoystickReplayScript.cs | Defines replay script document root and schema versioning. |
| src/Spice86.Shared/Emulator/Input/Joystick/Replay/IJoystickReplayStore.cs | Adds replay store interface for script persistence. |
| src/Spice86.Shared/Emulator/Input/Joystick/Mapping/VirtualButton.cs | Defines logical gameport button identifiers. |
| src/Spice86.Shared/Emulator/Input/Joystick/Mapping/VirtualAxis.cs | Defines logical gameport axis identifiers. |
| src/Spice86.Shared/Emulator/Input/Joystick/Mapping/RumbleMapping.cs | Adds per-profile rumble enable + amplitude scale settings. |
| src/Spice86.Shared/Emulator/Input/Joystick/Mapping/MidiOnGameportSettings.cs | Adds per-profile MIDI-on-gameport enable + base-port settings. |
| src/Spice86.Shared/Emulator/Input/Joystick/Mapping/JoystickProfile.cs | Adds mapping profile POCO (identity + bindings + hat/rumble/MIDI settings). |
| src/Spice86.Shared/Emulator/Input/Joystick/Mapping/JoystickMapping.cs | Adds mapping document root POCO (schema + default name + profiles). |
| src/Spice86.Shared/Emulator/Input/Joystick/Mapping/IJoystickMappingStore.cs | Adds persistence interface for joystick mapping documents. |
| src/Spice86.Shared/Emulator/Input/Joystick/Mapping/HatMapping.cs | Adds hat mapping POCO. |
| src/Spice86.Shared/Emulator/Input/Joystick/Mapping/ButtonMapping.cs | Adds button mapping POCO (raw index → virtual button + autofire flag). |
| src/Spice86.Shared/Emulator/Input/Joystick/Mapping/AxisMapping.cs | Adds axis mapping POCO (raw index → virtual axis + invert/scale/deadzone). |
| src/Spice86.Shared/Emulator/Input/Joystick/JoystickType.cs | Adds joystick “personality” enum aligned with DOSBox-staging types. |
| src/Spice86.Shared/Emulator/Input/Joystick/JoystickHatEventArgs.cs | Adds hat event args type. |
| src/Spice86.Shared/Emulator/Input/Joystick/JoystickHatDirection.cs | Adds hat direction enum aligned with SDL hat bitmask values. |
| src/Spice86.Shared/Emulator/Input/Joystick/JoystickConnectionEventArgs.cs | Adds connect/disconnect event args with optional device GUID. |
| src/Spice86.Shared/Emulator/Input/Joystick/JoystickButtonEventArgs.cs | Adds button event args type. |
| src/Spice86.Shared/Emulator/Input/Joystick/JoystickAxisEventArgs.cs | Adds axis event args type. |
| src/Spice86.Shared/Emulator/Input/Joystick/JoystickAxis.cs | Adds logical axis enum (X/Y/Z/R). |
| src/Spice86.Shared/Emulator/Input/Joystick/IRumbleSink.cs | Adds rumble sink abstraction for UI-layer haptics implementation. |
| src/Spice86.Shared/Emulator/Input/Joystick/IGameportPortReader.cs | Adds read-only port 0x201 peek interface for UI/MCP diagnostics. |
| src/Spice86.Shared/Emulator/Input/Joystick/GameportConstants.cs | Adds gameport emulation constants and bit masks. |
| src/Spice86.Core/Emulator/VM/Machine.cs | Renames exposed joystick device from Joystick to Gameport. |
| src/Spice86.Core/Emulator/VM/InputEventQueue.cs | Extends InputEventHub to handle joystick events and expose PostJoystick* APIs. |
| src/Spice86.Core/Emulator/Mcp/EmulatorMcpTools.cs | Adds MCP tools for joystick axis/button/hat/connect/disconnect, keyboard fallback enable, and replay advancement. |
| src/Spice86.Core/Emulator/Mcp/EmulatorMcpServices.cs | Adds service slots for keyboard joystick mapper and replay player used by MCP tools. |
| src/Spice86.Core/Emulator/Devices/Input/Joystick/Replay/JoystickReplayPlayer.cs | Adds deterministic pull-based replay player that posts events to InputEventHub. |
| src/Spice86.Core/Emulator/Devices/Input/Joystick/Replay/JoystickReplayJsonStore.cs | Adds JSON store for replay scripts (string enums, comments/commas, schema gating). |
| src/Spice86.Core/Emulator/Devices/Input/Joystick/Replay/JoystickReplayHost.cs | Adds real-time host that drives replay player off an ITimeProvider. |
| src/Spice86.Core/Emulator/Devices/Input/Joystick/Mapping/RumbleRouter.cs | Adds rumble routing with enable/scale, clamping, and transition-only logging. |
| src/Spice86.Core/Emulator/Devices/Input/Joystick/Mapping/MidiOnGameportRouter.cs | Adds MIDI-on-gameport routing for port 0x201 writes to MPU-401 sink. |
| src/Spice86.Core/Emulator/Devices/Input/Joystick/Mapping/LoadedProfiles.cs | Adds value type for merged profiles + default profile name result. |
| src/Spice86.Core/Emulator/Devices/Input/Joystick/Mapping/JoystickProfileAutoLoader.cs | Adds directory scanner and resolver for joystick profiles with embedded fallback. |
| src/Spice86.Core/Emulator/Devices/Input/Joystick/Mapping/JoystickProfileActivator.cs | Adds connect/disconnect listener to apply profile MIDI/rumble settings immediately. |
| src/Spice86.Core/Emulator/Devices/Input/Joystick/Mapping/JoystickMappingJsonStore.cs | Adds JSON store for joystick mappings (string enums, schema gating, lenient parsing). |
| src/Spice86.Core/Emulator/Devices/Input/Joystick/Keyboard/KeyboardJoystickMapper.cs | Adds keyboard-as-joystick mapper (enabled toggle, bindings, axis recentre). |
| src/Spice86.Core/Emulator/Devices/Input/Joystick/Keyboard/KeyboardJoystickBindingKind.cs | Adds binding-kind enum for keyboard joystick mapper. |
| src/Spice86.Core/Emulator/Devices/Input/Joystick/Keyboard/KeyboardJoystickBinding.cs | Adds binding model from physical key to axis-half or button. |
| src/Spice86.Core/Emulator/Devices/Input/Joystick/Joystick.cs | Removes old stub joystick I/O handler. |
| src/Spice86.Core/Emulator/Devices/Input/Joystick/GameportTimer.cs | Adds RC-decay timer used by gameport timed read/write path. |
| src/Spice86.Core/CLI/Configuration.cs | Adds --JoystickProfilesDirectory CLI option for profile scanning. |
fd2dabd to
cbdb115
Compare
…ces, DOSBox-staging gameport constants, mapping schema Agent-Logs-Url: https://github.com/OpenRakis/Spice86/sessions/77a2e1ed-2b4f-4828-a9db-c59bcdb240f7 Co-authored-by: maximilien-noal <1087524+maximilien-noal@users.noreply.github.com>
…ouse flow) Agent-Logs-Url: https://github.com/OpenRakis/Spice86/sessions/e90f7ac2-ca21-4c0d-9219-5a9f967cde16 Co-authored-by: maximilien-noal <1087524+maximilien-noal@users.noreply.github.com>
Agent-Logs-Url: https://github.com/OpenRakis/Spice86/sessions/e90f7ac2-ca21-4c0d-9219-5a9f967cde16 Co-authored-by: maximilien-noal <1087524+maximilien-noal@users.noreply.github.com>
…tore impl + 11 tests) Agent-Logs-Url: https://github.com/OpenRakis/Spice86/sessions/38e77ba2-3744-4b5d-9f71-e90fced835e6 Co-authored-by: maximilien-noal <1087524+maximilien-noal@users.noreply.github.com>
…llback + 13 tests) Agent-Logs-Url: https://github.com/OpenRakis/Spice86/sessions/0926ff87-c4cf-4fc7-8e2e-11cbc5dcc3e4 Co-authored-by: maximilien-noal <1087524+maximilien-noal@users.noreply.github.com>
…rites to MPU-401, +10 tests) Agent-Logs-Url: https://github.com/OpenRakis/Spice86/sessions/f8f2b647-fddb-481b-9d7e-f83c96575a2e Co-authored-by: maximilien-noal <1087524+maximilien-noal@users.noreply.github.com>
…nk, +16 tests) Agent-Logs-Url: https://github.com/OpenRakis/Spice86/sessions/e64a1b01-57b3-4b6e-adda-10654c4f0b27 Co-authored-by: maximilien-noal <1087524+maximilien-noal@users.noreply.github.com>
…leRouter into Gameport + JoystickProfileActivator (+8 tests) Agent-Logs-Url: https://github.com/OpenRakis/Spice86/sessions/3cb50ba6-8b69-4097-977b-9eab9668535a Co-authored-by: maximilien-noal <1087524+maximilien-noal@users.noreply.github.com>
…layer, +21 tests) Agent-Logs-Url: https://github.com/OpenRakis/Spice86/sessions/8ddef10b-4916-4d96-ab2d-7be92046de57 Co-authored-by: maximilien-noal <1087524+maximilien-noal@users.noreply.github.com>
Agent-Logs-Url: https://github.com/OpenRakis/Spice86/sessions/2371bd7d-b9e7-435b-baf9-ead3ae528ffa Co-authored-by: maximilien-noal <1087524+maximilien-noal@users.noreply.github.com>
…MCP tools Agent-Logs-Url: https://github.com/OpenRakis/Spice86/sessions/f58c9805-b460-43c1-9001-8a7be1dafd6c Co-authored-by: maximilien-noal <1087524+maximilien-noal@users.noreply.github.com>
…l wiring Agent-Logs-Url: https://github.com/OpenRakis/Spice86/sessions/4ce620b1-f9c3-4225-bf1f-4e3091952b1e Co-authored-by: maximilien-noal <1087524+maximilien-noal@users.noreply.github.com>
Agent-Logs-Url: https://github.com/OpenRakis/Spice86/sessions/2964e933-14a1-42d3-9713-423b42686183 Co-authored-by: maximilien-noal <1087524+maximilien-noal@users.noreply.github.com>
cbdb115 to
44b1ef0
Compare
Description of Changes
Core features
--JoystickProfilesDirectory, embedded Xbox-360 default fallback).Joystick > Mapper...menu) with save/load: toolbar (New / Load / Save / Save As / Add Profile / Remove Profile), profile list, per-profile fields (name, GUID, device name, type, deadzone, circular deadzone, swap stick B), DataGrids for axes and buttons, panels for hat / rumble / MIDI-on-gameport, file picking viaIHostStorageProvider.OpenFilePickerAsync/SaveFilePickerAsync, persistence throughJoystickMappingJsonStore.Emulation
read_p201_timed/write_p201_timedparity): two virtual sticks, four buttons, RC-timed axis decay on read, write-strobe arms the timers.Silk.NET.SDLNuGet, ships native lib for Linux/macOS/Windows).Reverse engineering
InputEventHub, so any reverse-engineered game reads a single deterministic event stream regardless of how it was produced.Joystick / protocol variants supported
SdlJoystickInput.Rationale behind Changes
The earlier mapping/routing work delivered the configuration layer but left the actual input surfaces unfilled. Centralising every input path on
InputEventHubkeeps the gameport, profile activator, MIDI-on-gameport router, rumble router and the new mapper UI aligned on one schema and one dispatch point — mirroring how DOSBox-Staging's SDL2 events feed the same mapper layer that its keyboard fallback uses. Reusing theSilk.NET.SDLNuGet the project already ships avoids a second SDL binding and keeps published artifact size unchanged. The mapper UI gives users a graphical alternative to hand-editing the JSON profile files while persisting through the exact sameIJoystickMappingStoreso on-disk format stays identical.Suggested Testing Steps
JoystickReplayJsonStore, drive it viaJoystickReplayPlayer.AdvanceTo(or the real-timeJoystickReplayHost), and confirm each event reaches the gameport.EmulatorMcpServiceswithInputEventHub+KeyboardJoystickMapper+JoystickReplayPlayer, invoke each new MCP tool and confirm the corresponding event fires.DeviceName; raise a connection event and confirm GUID precedence then name-substring fallback.Joystick > Mapper..., click Add Profile, edit fields / axes / buttons / hat / rumble / MIDI-on-gameport, click Save As to write a JSON file, then click New + Load to reload and confirm the round-trip is faithful.