Skip to content

Input: a second pad on port 1 from the window (input.player2) — #101 phase 1 - #102

Merged
gascarcella merged 1 commit into
mainfrom
gascarcella/input-a-second-pad-port-1-for-two-player-games-l
Oct 24, 2026
Merged

gascarcella merged 1 commit into
mainfrom
gascarcella/input-a-second-pad-port-1-for-two-player-games-l

Conversation

@gascarcella

Copy link
Copy Markdown
Owner

Phase 1 of #101: the runtime half of a second pad. Phases 2 (the launcher's and in-game menu's Controls screen) and 3 (port 1 for the script, debug channel, MCP and run.lua) come in separate PRs.

What

  • input.player2 ("off" default, "first", "second"): that gamepad, by connection order (SDL instance ids), is player 2. It drives pad 2 (port 1) with the shared input.gamepad map. Port 1 is connected only while that gamepad is there. The keyboard and every other gamepad stay player 1's. A bad value exits 64 like other settings. The launcher keeps the key when it writes the file (it keeps unknown input members), so it can be set by hand before phase 2.
  • Hotkeys are player 1's only. Player 2's Back+Start reaches the game as SELECT+START and doesn't open the menu.
  • Both ports set every vsync by whoever owns the pad (window; a script sets port 1 empty). The shim's "connected" is part of a save state, so a state saved with a second pad doesn't carry it into a run without one. When the debug channel owns port 0, the window keeps port 1.
  • Traces:
    • Log line for port 0 unchanged. Port 1: I <frame> port 1 buttons 0x.... / I <frame> port 1 none.
    • Record inputs: port-1 entries get "port": 1 (and "connected": false when unplugged).
    • port_framelog_input(port, connected, buttons). The game doesn't call it.
    • Crash report: a pad 2: line when one is connected.
  • --input-test, the players' part:
    • Attaches a second virtual gamepad, presses an input on each pad and on both, presses player 2's gamepad-only hotkey triggers, then unplugs each pad.
    • After every poll, both ports must match the setting (with "off": everything on port 0, port 1 empty).
    • With "first", the second virtual gamepad is attached first, so the main steps keep a player-1 gamepad.
  • hello tints its gradient from pad 2 when one is connected. Its headless picture hash is unchanged.
  • Multitap later: players are PORT_PADS arrays and the setting is player<n> (DECISIONS: "Player 2: a gamepad by connection order, off by default, never a hotkey").
  • Docs: RUNTIME "Input", the log table, "The record"; PORT LIBPAD row, "Input", "Known limitations"; LAUNCHER "Input bindings".

Gates that ran

  • tests/hello_test.py (picture hash unchanged) and tests/hello_test.py --menu: the input self-test with the defaults, then again with player2 "second" and "first"; each reports 10 of 10 players' checks.
  • The players' part shown to fail: with player 2 never given a gamepad (7/10), and with player 2's inputs reaching hotkeys (9/10, exit 6).
  • --input-test --script with "second": nothing sent, and the script's log is byte-identical to a run without the test.
  • tests/tasks_test.py (plain, --sanitize, --m32), tests/shim_test.py (plain, --sanitize), game_gen_test, replay_test, sha256_test, psyq/check.sh --compile, py_compile.
  • Windows: hello cross-built (-DPSXSTACK_SDL=ON), tests/hello_test.py --exe --wine passes. By hand under Wine, --input-test with "second" and "first" passes the players' part too (CI's Wine step doesn't run the input test).
  • dw2003recomp (pins v0.3.14 = this branch's base): tests/port/run.py at the pin and with -DPSXSTACK_DIR= this branch both pass. All logs, records, SPU traces and checkpoint dumps are byte-identical; the only differences are output paths inside replay.script.
  • Not run: a real two-player session (two physical pads, or Moonlight). That needs a person and a game that reads port 1.

Notes

  • The in-game menu's navigation still takes events from any gamepad, player 2's included. It only opens from player 1's hotkey.

🤖 Generated with Claude Code

A setting, input.player2 ("off" by default, "first" or "second": that gamepad
by connection order), makes one gamepad player 2: it drives the PS1's port 1
with the gamepad map, port 1 is connected only while that gamepad is there,
and its inputs never reach a hotkey. The keyboard and every other gamepad stay
player 1's, so a one-pad setup, every log and every record are unchanged.
Whoever owns the pad sets both ports every vsync (a script leaves port 1
empty), since the shim's "connected" is part of a save state.

The frame log and the record trace port 1 when it is used ("I <frame> port 1
..." lines, inputs entries with "port": 1); the crash report names pad 2 when
one is connected. --input-test gains a players' part: a second virtual
gamepad, inputs on each pad and on both, player 2's gamepad hotkey chords
(reach pad 2, fire nothing), and the unplugs. hello tints its gradient from
pad 2; tests/hello_test.py --menu runs the self-test with "second" and
"first". The runtime's players are PORT_PADS arrays and the setting is
player<n>, so a multitap can add players later.

Part of #101 (phase 1 of 3: the runtime; the launcher's Controls screen and
the script/debug channel's port 1 follow).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@gascarcella
gascarcella merged commit 8931c73 into main Oct 24, 2026
5 checks passed
@gascarcella
gascarcella deleted the gascarcella/input-a-second-pad-port-1-for-two-player-games-l branch October 24, 2026 18:35
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.

1 participant