From dbaebaf37a0490e53d85877b10f2f7ae2784b9b4 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 14 Jul 2026 10:00:51 -0700 Subject: [PATCH] Expose analog input through debug controls Co-authored-by: douglasjv --- .../douglas-pr15-debug-analog-input.md | 9 +++++++ runtime/include/sio.h | 1 + runtime/src/debug_server.c | 24 +++++++++++++++---- runtime/src/sio.c | 10 ++++++++ tools/debug_client.py | 22 +++++++++++++++-- 5 files changed, 60 insertions(+), 6 deletions(-) create mode 100644 docs/internal/upstream/douglas-pr15-debug-analog-input.md diff --git a/docs/internal/upstream/douglas-pr15-debug-analog-input.md b/docs/internal/upstream/douglas-pr15-debug-analog-input.md new file mode 100644 index 00000000..d43fd677 --- /dev/null +++ b/docs/internal/upstream/douglas-pr15-debug-analog-input.md @@ -0,0 +1,9 @@ +# Analog debug input and observability + +Source: [PSXRecomp PR #15](https://github.com/mstan/psxrecomp/pull/15), +commits [`df7d1faa`](https://github.com/douglasjv/psxrecomp-tweaks/commit/df7d1faa5f27be0ba357463a763c77efc43e1f91) +and [`31cdfb4f`](https://github.com/douglasjv/psxrecomp-tweaks/commit/31cdfb4f4dac305e98661dfdbf8e685533fd7ca4). + +This branch lets a debug `press` carry analog axes and reports live and +overridden stick values through `pad_status`. Widescreen, overlay discovery, +and unrelated debug commands are excluded. diff --git a/runtime/include/sio.h b/runtime/include/sio.h index e6c1da9a..7363a018 100644 --- a/runtime/include/sio.h +++ b/runtime/include/sio.h @@ -109,6 +109,7 @@ uint8_t sio_peek_rx_data(void); /* Debug accessors: is a pad connected on the slot, and is it in analog mode. */ int sio_get_pad_connected(int slot); int sio_get_pad_analog(int slot); +void sio_get_pad_sticks(int slot, uint8_t out[4]); /* ---- SIO byte-level trace ring buffer ---- * 1M entries × ~28 B ≈ 32 MB. At ~600 byte/sec that's ~30 min of history. */ diff --git a/runtime/src/debug_server.c b/runtime/src/debug_server.c index 30da53a8..ff8a231d 100644 --- a/runtime/src/debug_server.c +++ b/runtime/src/debug_server.c @@ -6739,6 +6739,13 @@ static void handle_press(int id, const char *json) if (buttons < 0) { send_err(id, "missing buttons"); return; } s_input_override = buttons; s_input_frames = frames; + int ax[4] = { json_get_int(json, "lx", -1), json_get_int(json, "ly", -1), + json_get_int(json, "rx", -1), json_get_int(json, "ry", -1) }; + s_axis_override = (ax[0] >= 0 || ax[1] >= 0 || ax[2] >= 0 || ax[3] >= 0); + for (int i = 0; i < 4; i++) { + int v = ax[i] < 0 ? 0x80 : (ax[i] > 255 ? 255 : ax[i]); + s_axis_st[i] = (uint8_t)v; + } send_ok(id); } @@ -6748,19 +6755,28 @@ extern uint16_t sio_get_pad_buttons(void); extern uint16_t sio_get_pad_buttons_slot(int slot); extern int sio_get_pad_connected(int slot); extern int sio_get_pad_analog(int slot); +extern void sio_get_pad_sticks(int slot, uint8_t out[4]); static void handle_pad_status(int id, const char *json) { (void)json; uint16_t pad0 = sio_get_pad_buttons_slot(0); uint16_t pad1 = sio_get_pad_buttons_slot(1); + uint8_t sticks0[4], sticks1[4]; + sio_get_pad_sticks(0, sticks0); + sio_get_pad_sticks(1, sticks1); send_fmt("{\"id\":%d,\"ok\":true,\"pad\":\"0x%04X\"," - "\"slot0\":{\"buttons\":\"0x%04X\",\"connected\":%s,\"analog\":%s}," - "\"slot1\":{\"buttons\":\"0x%04X\",\"connected\":%s,\"analog\":%s}," - "\"override\":%d,\"override_frames\":%d}\n", + "\"slot0\":{\"buttons\":\"0x%04X\",\"connected\":%s,\"analog\":%s,\"sticks\":[%u,%u,%u,%u]}," + "\"slot1\":{\"buttons\":\"0x%04X\",\"connected\":%s,\"analog\":%s,\"sticks\":[%u,%u,%u,%u]}," + "\"override\":%d,\"override_frames\":%d," + "\"override_axes\":[%u,%u,%u,%u],\"override_axes_valid\":%s}\n", id, pad0, pad0, sio_get_pad_connected(0) ? "true" : "false", sio_get_pad_analog(0) ? "true" : "false", + sticks0[0], sticks0[1], sticks0[2], sticks0[3], pad1, sio_get_pad_connected(1) ? "true" : "false", sio_get_pad_analog(1) ? "true" : "false", - s_input_override, s_input_frames); + sticks1[0], sticks1[1], sticks1[2], sticks1[3], + s_input_override, s_input_frames, + s_axis_st[0], s_axis_st[1], s_axis_st[2], s_axis_st[3], + s_axis_override ? "true" : "false"); } static void handle_clear_input(int id, const char *json) diff --git a/runtime/src/sio.c b/runtime/src/sio.c index 2df0f109..0b5058c0 100644 --- a/runtime/src/sio.c +++ b/runtime/src/sio.c @@ -675,6 +675,16 @@ int sio_get_pad_analog(int slot) { return (slot >= 0 && slot <= 1) ? pad_analog[slot] : 0; } +void sio_get_pad_sticks(int slot, uint8_t out[4]) { + if (!out) return; + if (slot < 0 || slot > 1) { + out[0] = out[1] = out[2] = out[3] = 0x80; + return; + } + out[0] = pad_stick[slot][0]; out[1] = pad_stick[slot][1]; + out[2] = pad_stick[slot][2]; out[3] = pad_stick[slot][3]; +} + /* ── LEGACY pad-config compatibility (Tomba "Hybrid" controller) ───────────── * * Why this exists, and why it is explicitly LEGACY: diff --git a/tools/debug_client.py b/tools/debug_client.py index 8db30fc1..b8da4249 100644 --- a/tools/debug_client.py +++ b/tools/debug_client.py @@ -261,8 +261,26 @@ def build_cmd(args): return {"cmd": "get_snapshots"}, pretty_json elif cmd == "input": if len(args) < 2: - return None, lambda _: "Usage: input " - return {"cmd": "set_input", "buttons": args[1]}, pretty_json + return None, lambda _: "Usage: input [lx=N ly=N rx=N ry=N]" + d = {"cmd": "set_input", "buttons": args[1]} + for item in args[2:]: + key, sep, value = item.partition("=") + if not sep or key not in ("lx", "ly", "rx", "ry"): + return None, lambda _, item=item: f"Invalid analog axis '{item}'" + d[key] = int(value, 0) + return d, pretty_json + elif cmd == "press": + if len(args) < 2: + return None, lambda _: "Usage: press [frames] [lx=N ly=N rx=N ry=N]" + has_frames = len(args) > 2 and "=" not in args[2] + d = {"cmd": "press", "buttons": int(args[1], 0), + "frames": int(args[2]) if has_frames else 1} + for item in args[3 if has_frames else 2:]: + key, sep, value = item.partition("=") + if not sep or key not in ("lx", "ly", "rx", "ry"): + return None, lambda _, item=item: f"Invalid analog axis '{item}'" + d[key] = int(value, 0) + return d, pretty_json elif cmd == "clear_input": return {"cmd": "clear_input"}, pretty_json elif cmd == "ws_margin":