Skip to content

Second pad for scripts and the debug channel (pad2, port) — #101 phase 3 - #104

Merged
gascarcella merged 1 commit into
mainfrom
gascarcella/player2-script-debug
Oct 24, 2026
Merged

gascarcella merged 1 commit into
mainfrom
gascarcella/player2-script-debug

Conversation

@gascarcella

Copy link
Copy Markdown
Owner

Phase 3 of #101, the last one (phase 1 #102: the runtime; phase 2 #103: the Controls screen). Closes #101.

What

  • Script (runtime/script.c and tools/replay/run.lua, the same engine on both runners):
    • "pad2": true at the top level puts a digital pad in port 1 from the first frame.
    • A press step with "port": 1 holds its buttons there. Other steps, and a press without port, drive port 0.
    • A press on port 1 without pad2 is refused when the script loads, as is a pad2 that isn't a boolean.
    • run.lua always sets PCSX-Redux's pads[2].Connected from pad2. Redux's default is no pad 2 (checked in the pinned build), and this way the user's Redux configuration never decides it.
    • Both records trace port 1 from its first frame ("port": 1 entries).
    • Port 1's held buttons are part of the script's save state.
  • Debug channel (runtime/debug.c):
    • pad takes port (0 or 1). Port 1 has a pad in it while the channel owns it.
    • Each port is owned on its own; the window keeps the other one. port_debug_pad_owned is now a bitmask.
    • pad_free takes an optional port; without one it frees every port the channel owns.
    • status adds pad2_owner, pad2 and pad2_connected.
  • MCP (tools/mcp): pad_press/pad_hold/pad_release/pad_free take port; game_status adds pad2_buttons; the fake game models both ports.
  • Docs: RUNTIME "The replay runners" (the script's second pad), PORT (the debug ops, "Known limitations"), tools/mcp/README.md.

Gates that ran

  • tests/hello_test.py (new checks):
    • A script with pad2 and a press on port 1 traces port 1 in the log (I 1 port 1 buttons 0x0000, I 4 … 0x4000, I 8 … 0x0000) and the record (port-1 entries at frames 1, 4, 8).
    • hello's picture at vsync 8 differs from the same press on port 0 and from no press, so the game sees the press.
    • Both refusals.
    • All of this also passes on the Windows build under Wine (--exe --wine).
  • tools/mcp/selftest.py: 77/77, with the new checks for port 1 (status, owning port 1 alone, pad_free(port=1) keeping port 0, port=2 refused).
  • The real debug channel, by hand (hello with --debug), same frame in three runs: holding buttons on port 1 changes the picture, holding them on port 0 changes it differently, and nothing held is the baseline. The log traces port 1 and its release on pad_free.
  • A save state taken mid-press on port 1, resumed: the log after the saved frame is the straight run's.
  • PCSX-Redux (the game's pinned build, OpenBIOS, its disc), by hand:
    • Without Connected the game's port-1 buffer reads FF… (no pad); with it, 00 41 FF FF; CROSS on pad 2 gives FF BF.
    • With this branch's run.lua, a script with pad2 presses CROSS on port 1 until the game's buffer shows it (passes).
    • A parity script (pad2, a press on port 1, then START on port 0) gives identical inputs in the emulator's and the port's records, with the checkpoint at the same frame.
  • dw2003recomp: tests/port/run.py with this branch and with main (Controls: Player 2 on the Gamepad tab (input.player2) — #101 phase 2 #103 merged) both pass, and the outputs are byte-identical (only output paths inside replay.script differ).
  • tests/hello_test.py --menu, tasks_test.py (plain, --sanitize, --m32), shim_test.py, replay_test.py, game_gen_test.py, py_compile.

Notes

  • For an empty port, PCSX-Redux leaves bytes 1-3 of the game's buffer at 00, where the shim writes 00 FF FF. Only the status byte (0xFF, no pad) means anything there, and it matches. Left as it is.
  • replay_test.py doesn't exercise run.lua (it fakes the emulator), so the Redux checks above were run by hand.

🤖 Generated with Claude Code

…ad "port"

The last part of #101: port 1 from the tools that drive a run, not only from
the window.

- Script (runtime/script.c, tools/replay/run.lua: the same engine on both
  runners): `"pad2": true` at the top puts a pad in port 1 from the first
  frame, and a press step with `"port": 1` holds its buttons there; the other
  steps drive port 0, and a press on port 1 without pad2 is refused at load.
  run.lua sets PCSX-Redux's pad 2 Connected from pad2 either way (Redux's
  default is none, and the user's configuration never decides it) and records
  port 1's inputs from its first frame, as the port does. The held buttons of
  both ports are part of the script's save state.
- Debug channel (runtime/debug.c): `pad` takes `port` (0 or 1; port 1 is
  connected while the channel owns it), each port owned on its own (the
  window keeps the other: port_debug_pad_owned is a bitmask now); `pad_free`
  takes an optional port (none: every port the channel owns); status adds
  pad2_owner, pad2 and pad2_connected.
- MCP (tools/mcp): pad_press/pad_hold/pad_release/pad_free take `port`;
  game_status gives pad2_buttons; the fake game models both ports.
- tests/hello_test.py: a script with pad2 and a press on port 1 traces port 1
  in its log and record, and hello draws another picture than with the press
  on port 0 or none (the game sees it); the two refusals. The refused scripts'
  crash reports go to the build directory.

Closes #101 (phase 3 of 3).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@gascarcella
gascarcella merged commit 3ef599d into main Oct 24, 2026
5 checks passed
@gascarcella
gascarcella deleted the gascarcella/player2-script-debug branch October 24, 2026 19:08
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.

Input: a second pad (port 1) for two-player games, local or over game streaming

1 participant