mirror of
https://github.com/libretro/virtualjaguar-libretro.git
synced 2026-11-07 12:47:58 -10:00
test(input): validate Team Tap and 6D controller against AtariJaguarPadtest
Adds test/tools/padtest_probe.c, a test/harness-based tool that drives darthcloud's AtariJaguarPadtest ROM (github.com/darthcloud/AtariJaguarPadtest, v1.0, not committed -- private/unredistributable) through the real update_input() delivery path, closing the blind spot PR #547 recorded: register-level tests (sixd_decode_test) call InputDevFeed6D() directly and can never see a frontend-shaped defect like Z/TY reaching the wire as analog-button-only values. Team Tap (#513): fully corroborated end-to-end. padtest's own bus decode and a direct joypad0Buttons[] memory dump agree exactly with what was fed to all four sockets. Also reproduces and dismisses padtest's own documented "small visual glitches" trap with hard evidence (a single glyph rendering miss with the underlying bits proven correct via memory dump). 6D controller (#538): could not be corroborated, but not because of a core defect. padtest's detect_ctrl() only calls its bank-switching decoder on socket 1, and only when a Team Tap is already present; without a tap it hardcodes socket 0 -- the only socket a bank-switching device can ever occupy -- to STDPAD unconditionally. This is proven via source (the padtest.c quote), ground truth (InputDevGetType()==INPUTDEV_6D and InputDevAnyAttached()==1 throughout), and screen evidence. The TR V10 X-sign and bank-1 button-order ambiguities from docs/teamtap-procontroller-spike.md and the 6D user-guide section remain open; findings and the two false- positive traps are documented in docs/input-devices-user-guide.md. make TEST_EXPORTS=1 test: 1266 passed, 0 failed, exit 0. Refs #513, #538. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
1 parent
1ced42b14d
commit
5d6fe4cc08
3 files changed
+675
-2
No files matched your search
@@ -137,6 +137,7 @@ test/vendor/
|
||||
/test/tools/hires_box_check
|
||||
/test/tools/hires_state_digest
|
||||
/test/tools/hires_shot
|
||||
/test/tools/padtest_probe
|
||||
test/tools/__pycache__/
|
||||
.build-config
|
||||
.link-mode
|
||||
|
||||
@@ -470,6 +470,51 @@ Reports that the adapter is **inert** on a title that should support it are
|
||||
just as valuable as crash reports — that is the failure mode the tester ROM
|
||||
cannot catch.
|
||||
|
||||
### Validated end-to-end against a second test ROM (2026-08-20)
|
||||
|
||||
The Joypad-TeamTap Tester above proves detection only. A second, independent
|
||||
program closes that gap: **AtariJaguarPadtest**
|
||||
(<https://github.com/darthcloud/AtariJaguarPadtest>, Apache 2.0, release v1.0,
|
||||
`padtest.rom`, md5 `2e1663f2c8bc557f2f94229d8d73e7dc`), by darthcloud — the
|
||||
BlueRetro author and the same source as the *Technical Reference V10* used to
|
||||
implement #513/#538. It reads `$F14000` directly, does its own bank and
|
||||
socket-3 detection, and prints a live per-socket, per-button decode — a
|
||||
program driving the register set independently of anything in this repo.
|
||||
|
||||
Crucially, it was run through the **real delivery path**
|
||||
(`update_input()` → `$F14000`/`$F14002`), not the register-level decode
|
||||
functions directly — a shared `test/harness`-based tool
|
||||
(`test/tools/padtest_probe.c`, not wired into `make test` because the ROM is
|
||||
private and unredistributable) stands in for the frontend, feeding known
|
||||
RetroPad state through `input_state_cb` exactly as RetroArch would.
|
||||
|
||||
With *Team Tap* selected on Jaguar port 1 and one distinct direction held per
|
||||
player (socket 0 = Up, socket 1 = Down, socket 2 = Left, socket 3 = Right),
|
||||
padtest reported all four sockets as `STDPAD` and highlighted exactly the
|
||||
button fed to each — `^` on socket 0, `v` on socket 1, `<` on socket 2, `>`
|
||||
on socket 3, nothing else. Cross-checked against the core's own
|
||||
`joypad0Buttons[]` array (dumped via `harness_dlsym`) after the run: each
|
||||
socket's slot held exactly the fed direction and nothing else, and
|
||||
`JoystickGetTeamTap(0)` read true. **Two independent readers — padtest's own
|
||||
bus decode and a direct memory dump — agree exactly with what was fed.**
|
||||
This is the first full per-socket, per-button confirmation this feature has
|
||||
had; the PD tester above only ever exercised the detection bit.
|
||||
|
||||
Two things noticed along the way are padtest's own documented known issues,
|
||||
not bugs in this core, and are recorded here so nobody re-files them:
|
||||
|
||||
1. **Frame rate is terrible.** padtest re-detects every controller on every
|
||||
single frame, by design — a real game does it once at boot.
|
||||
2. **Occasional single-glyph rendering misses**, reproduced here: holding Up
|
||||
*and* A together on the plain-joypad path left `^` unhighlighted on
|
||||
screen while `A` rendered correctly, on two separate runs. A direct
|
||||
`joypad0Buttons[]` dump proved both bits were correctly set
|
||||
(`U=255 A=255`) the whole time — the delivery was right and the glitch is
|
||||
in padtest/JagStudio's own rendering (JagStudio does its own controller
|
||||
polling that conflicts with padtest's main poll; the padtest author could
|
||||
not disable it). Any single miss-highlighted glyph should be checked
|
||||
against a memory dump before being read as a real defect.
|
||||
|
||||
## Pro Controller (#514)
|
||||
|
||||
Set *Port 1/2 > Controller Type* to **Pro Controller (6-button)**. Default is
|
||||
@@ -568,7 +613,12 @@ ever been checked against. What ships here is a best attempt built from the
|
||||
*Jaguar Technical Reference V10* alone — pages 15, 16, 21, 22, 23 and 27–28.
|
||||
It is conformant to the manual (`test/tools/sixd_decode_test` asserts that,
|
||||
cell by cell) and **unvalidated against any real program**. Please do not
|
||||
read "supported" as "verified".
|
||||
read "supported" as "verified". One published test ROM claims 6D support and
|
||||
was tried against this core's real delivery path in 2026-08; it turned out
|
||||
to be structurally unable to reach its own 6D decoder for a directly-attached
|
||||
device, so this remains unvalidated — see "Attempted validation against
|
||||
AtariJaguarPadtest" below for the full account and why it isn't the
|
||||
corroboration it looks like at first.
|
||||
|
||||
### What the device is
|
||||
|
||||
@@ -657,6 +707,94 @@ the buttons run **A, B, C, D** up the rows, but in bank 1 they run
|
||||
we implement it as printed — it is the most likely place for the manual (or
|
||||
our reading of it) to be wrong.
|
||||
|
||||
### Attempted validation against AtariJaguarPadtest, and why it could not settle anything (2026-08-20)
|
||||
|
||||
The same second test ROM used to validate Team Tap above —
|
||||
**AtariJaguarPadtest** (<https://github.com/darthcloud/AtariJaguarPadtest>,
|
||||
darthcloud, v1.0, `padtest.rom`, md5 `2e1663f2c8bc557f2f94229d8d73e7dc`) —
|
||||
also claims 6D support (its enum has a `SIXDPAD` case and a
|
||||
`print_6dpad_btns()` that prints `X`/`Y`/`Z`/`TX`/`TY`/`TZ` and the `A`–`G`
|
||||
buttons). It was run the same way, through `update_input()` via
|
||||
`test/tools/padtest_probe.c`, feeding a distinct nonzero value per axis
|
||||
(left stick, right stick, both shoulder pairs) and holding every 6D button.
|
||||
|
||||
**It could not corroborate anything about this device, and the reason is a
|
||||
limitation in padtest itself, not in this core.** padtest's own
|
||||
`detect_ctrl()` only calls its bank-switching decoder
|
||||
(`get_basic_type()`/`get_banked_type()`) on **socket 1**, and only when a
|
||||
Team Tap is already detected on that port. Without a tap it hardcodes socket
|
||||
0 — the *only* socket a bank-switching device can ever occupy, on this core
|
||||
and on real hardware (TR10 itself: behind a tap, "software control of
|
||||
advanced features like … analogue/digital mode will not be possible") — to
|
||||
`STDPAD`, unconditionally:
|
||||
|
||||
```c
|
||||
/* padtest.c, detect_ctrl(), the no-adapter branch */
|
||||
dev_type[i][0] = STDPAD;
|
||||
...
|
||||
dev_type[i][1] = get_basic_type(i, 1); /* the ONLY socket ever checked */
|
||||
```
|
||||
|
||||
Socket 1 is provably dead in this branch too: its row codes
|
||||
(`sockets_row_codes[1]`, low nibbles `$0`-`$3`) all decode to Team-Tap socket
|
||||
1 in this core's own tables, which read idle with no tap attached — matching
|
||||
the `S1: NONE` padtest prints in every screenshot below. So **padtest cannot
|
||||
reach its own SIXDPAD decode path for a directly-attached device under any
|
||||
core option this core offers**, because the one configuration where a
|
||||
bank-switching device can appear (no tap, socket 0) is exactly the one
|
||||
branch that never calls the decoder.
|
||||
|
||||
This was confirmed three ways, not just inferred from source:
|
||||
|
||||
1. **Ground truth, independent of padtest's rendering:** `InputDevGetType(0)`
|
||||
read `9` (`INPUTDEV_6D`) and `InputDevAnyAttached()` read `1` throughout
|
||||
— the core correctly selected and engaged the device every time.
|
||||
2. **Source**: the `detect_ctrl()` quote above.
|
||||
3. **Screen**: every 6D run printed `P0: S0: STDPAD`, never `SIXDPAD`, with a
|
||||
handful of standard-pad glyphs (`O C B A` and some digits) lit up more or
|
||||
less at random — padtest reading our bank-switching bit stream through
|
||||
the *wrong* (plain-joypad) lookup table. Rerunning with the fed X axis
|
||||
flipped (`+20500` → `-20500`) and separately with the two shoulder pairs
|
||||
fed as pure digital presses instead of real analog pressure (see below)
|
||||
produced **pixel-identical** screenshots outside the frame counter in
|
||||
both cases (verified with a pixel diff, not by eye) — further evidence
|
||||
that whatever padtest is showing here is not a meaningful decode of this
|
||||
device, not evidence about the axes themselves.
|
||||
|
||||
One narrower thing this run *does* show, independent of padtest's broken
|
||||
label: the two runs that fed Z (`R2 − L2`) and TY (`R − L`) as real analog
|
||||
pressure versus as **digital-only** presses (analog-button read forced to 0,
|
||||
only the `RETRO_DEVICE_ID_JOYPAD_MASK` bit set — the exact "frontend with no
|
||||
analog-button support" shape of the original #547 defect) produced
|
||||
byte-identical output at the bus. That is consistent with `update_input()`'s
|
||||
fallback (`if (sh[i]==0 && (ret&bit)) sh[i]=32767;`) carrying full-scale Z/TY
|
||||
across correctly even when only digital state is available — the specific
|
||||
failure class this whole validation effort exists to catch — but it is *not*
|
||||
the same as padtest confirming the values are numerically right, which it
|
||||
cannot do at all for this device. Treat it as "the fallback still fires and
|
||||
reaches the wire," not as "Z and TY read correctly."
|
||||
|
||||
**Net effect on the two open ambiguities below: still open.** The X-sign
|
||||
question and the bank-1 button order are exactly as unresolved as before
|
||||
this session — padtest cannot see either one for a directly-attached device.
|
||||
The liveness gate (STDPAD until an axis deflects) still could not be
|
||||
isolated as a separate behaviour in this run, since `6D controller selected,
|
||||
nothing deflected` and `padtest can't decode this device at all` both print
|
||||
`STDPAD` and are indistinguishable on screen; don't read a `STDPAD` result
|
||||
here as proof the liveness gate fired, only as consistent with it. The
|
||||
`Rezero` button is additionally unverifiable through padtest by construction
|
||||
— its own button table (`btns_6d[]`) has only seven glyphs, `A`-`G`; Rezero
|
||||
has no glyph in the ROM's own display, tap present or not. Nothing here
|
||||
displays a torque *sign* either (`TX`/`TY`/`TZ` print as plain signed/unsigned
|
||||
magnitudes), so the torque-sign question is exactly as unresolvable as
|
||||
[`docs/teamtap-procontroller-spike.md`](teamtap-procontroller-spike.md) and
|
||||
the section below already say.
|
||||
|
||||
The tool (`test/tools/padtest_probe.c`) is still useful for the *next*
|
||||
attempt at this — a future BlueRetro-side or homebrew program that reads a
|
||||
6D controller correctly on socket 0 without a tap could be pointed at it and
|
||||
would get a real answer.
|
||||
|
||||
### Please test this, and tell us what you find
|
||||
|
||||
There is no game to try, so what would help is:
|
||||
@@ -664,7 +802,10 @@ There is no game to try, so what would help is:
|
||||
- **Homebrew.** Write something that scans the three banks the way TR10
|
||||
describes, and tell us whether the values arrive where you expect. If you
|
||||
are testing controller *detection*, remember to deflect an axis first —
|
||||
otherwise the port honestly reports a standard joypad.
|
||||
otherwise the port honestly reports a standard joypad. **AtariJaguarPadtest
|
||||
is not this** — see the subsection above for why its own detection logic
|
||||
can never reach a directly-attached bank-switching device; a new homebrew
|
||||
program (or a fix to padtest upstream) is still needed.
|
||||
- **Real hardware.** If a 6D prototype, or any device that implements this
|
||||
protocol, actually exists somewhere, a capture of a working scan would
|
||||
settle the X sign, the torque signs and the bank-1 button order in one go.
|
||||
|
||||
@@ -0,0 +1,531 @@
|
||||
/*
|
||||
* padtest_probe.c -- drives darthcloud's AtariJaguarPadtest ROM through the
|
||||
* REAL delivery path (update_input(), not InputDevFeed*() directly) to
|
||||
* validate Team Tap (#513) and the 6D controller (#538) against a program
|
||||
* that reads $F14000/$F14002 the way an actual game would.
|
||||
*
|
||||
* Why this exists: PR #547 (6D controller) shipped with a register-level
|
||||
* test (sixd_decode_test) that calls InputDevFeed6D() directly and never
|
||||
* enters update_input(). That blind spot let Z and TY reach the wire as
|
||||
* ANALOG-BUTTON-only values; a frontend that does not implement analog
|
||||
* button reads answered 0 for a held shoulder, leaving two of six axes
|
||||
* permanently dead even though every register-level assertion passed.
|
||||
* This tool exercises the full RetroPad -> update_input() -> $F14000
|
||||
* path with a harness input_callback standing in for the frontend, so it
|
||||
* CAN see that class of defect. See docs/input-devices-user-guide.md,
|
||||
* the "Validated end-to-end against a second test ROM" (Team Tap) and
|
||||
* "Attempted validation against AtariJaguarPadtest" (6D controller)
|
||||
* subsections.
|
||||
*
|
||||
* TWO FALSE-POSITIVE TRAPS (both padtest's own documented known issues,
|
||||
* neither an emulation bug -- see the app's README):
|
||||
* 1. Terrible frame rate: padtest re-detects every controller on every
|
||||
* single frame by design (a real game does it once at boot). Do not
|
||||
* chase this as a performance regression, and this tool deliberately
|
||||
* does not measure or report frame timing (see CLAUDE.md rule 6: no
|
||||
* wall-clock numbers with a concurrent agent fleet on the host).
|
||||
* 2. Small visual glitches: JagStudio (the dev kit padtest links against)
|
||||
* does its own controller polling that conflicts with padtest's main
|
||||
* poll loop. The author could not disable it.
|
||||
* A third trap belongs on this list, not in padtest's docs but in ours:
|
||||
* the 6D controller (like the analog/driving controller before it) stays
|
||||
* a plain RetroPad -- reported as STDPAD, not SIXDPAD -- until an axis
|
||||
* deflects past ANALOG_THRESHOLD. A "rest" scenario that shows STDPAD is
|
||||
* the liveness gate working, not a delivery failure.
|
||||
*
|
||||
* Build:
|
||||
* cc -O2 -Wall -std=c99 -I. -I./libretro-common/include \
|
||||
* -o test/tools/padtest_probe test/tools/padtest_probe.c \
|
||||
* test/harness/harness.c -ldl -lm
|
||||
*
|
||||
* Run (one scenario per invocation; see SCENARIOS below):
|
||||
* VJ_EXPECT_BUILD=$(./scripts/build-id.sh) ./test/tools/padtest_probe \
|
||||
* ./virtualjaguar_libretro.dylib test/roms/private/padtest/padtest.rom \
|
||||
* --scenario 6d_live --option virtualjaguar_p1_device=6d \
|
||||
* --out /tmp/padtest_6d_live.ppm
|
||||
*
|
||||
* The tool always prints, to stdout, the exact digital/analog values it
|
||||
* fed the core for the run -- read that alongside the screenshot rather
|
||||
* than trusting recollection of what a scenario "should" have sent.
|
||||
*/
|
||||
|
||||
#include "../harness/harness.h"
|
||||
#include "../../libretro-common/include/libretro.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define PT_MAX_BTN 17
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
/* Port 0 (Jaguar port 1) digital buttons held, RETRO_DEVICE_ID_JOYPAD_*
|
||||
* ids, terminated by -1. */
|
||||
int p0_buttons[PT_MAX_BTN];
|
||||
/* Team Tap: three extra sockets on frontend ports 2, 3, 4 (teamtap_user[0]
|
||||
* in libretro.c), each its own digital button list. */
|
||||
int use_teamtap;
|
||||
int tap_buttons[3][PT_MAX_BTN];
|
||||
/* 6D controller: four analog axes plus four analog-button shoulder
|
||||
* reads (fed as real pressure unless digital_fallback is set, in which
|
||||
* case the analog-button read is always 0 and the fallback in
|
||||
* update_input() -- "if (sh[i]==0 && (ret&bit)) sh[i]=32767" -- has to
|
||||
* carry Z/TY on its own, exactly the frontend class that produced the
|
||||
* original defect). */
|
||||
int use_6d;
|
||||
int digital_fallback;
|
||||
int32_t lx, ly, rx, ry; /* left stick X/Y, right stick X/Y */
|
||||
int32_t sh_l2, sh_r2, sh_l, sh_r; /* shoulder pressure (or digital press w/ 0 analog) */
|
||||
} pt_scenario;
|
||||
|
||||
static pt_scenario g_sc;
|
||||
|
||||
static const int PT_NONE[PT_MAX_BTN] = {
|
||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------
|
||||
* Scenario table. One entry per padtest_probe --scenario NAME.
|
||||
* ------------------------------------------------------------------- */
|
||||
|
||||
static void pt_scenario_standard(pt_scenario *sc)
|
||||
{
|
||||
memset(sc, 0, sizeof(*sc));
|
||||
sc->name = "standard";
|
||||
/* Baseline: plain joypad on port 1, hold Up + A so padtest's
|
||||
* print_stdpad_btns highlights two distinct characters we can read
|
||||
* off the screenshot ('^' and 'A'). */
|
||||
sc->p0_buttons[0] = RETRO_DEVICE_ID_JOYPAD_UP;
|
||||
sc->p0_buttons[1] = RETRO_DEVICE_ID_JOYPAD_A;
|
||||
sc->p0_buttons[2] = -1;
|
||||
}
|
||||
|
||||
/* Diagnostic only: press every digital button padtest's stdpad table
|
||||
* covers (U/D/L/R + P/O/C/B/A), to see whether direction glyphs ever
|
||||
* render "pressed" the way letter glyphs visibly do. */
|
||||
static void pt_scenario_standard_all(pt_scenario *sc)
|
||||
{
|
||||
memset(sc, 0, sizeof(*sc));
|
||||
sc->name = "standard_all";
|
||||
sc->p0_buttons[0] = RETRO_DEVICE_ID_JOYPAD_UP;
|
||||
sc->p0_buttons[1] = RETRO_DEVICE_ID_JOYPAD_DOWN;
|
||||
sc->p0_buttons[2] = RETRO_DEVICE_ID_JOYPAD_LEFT;
|
||||
sc->p0_buttons[3] = RETRO_DEVICE_ID_JOYPAD_RIGHT;
|
||||
sc->p0_buttons[4] = RETRO_DEVICE_ID_JOYPAD_SELECT; /* Pause -> 'P' */
|
||||
sc->p0_buttons[5] = RETRO_DEVICE_ID_JOYPAD_START; /* Option -> 'O' */
|
||||
sc->p0_buttons[6] = RETRO_DEVICE_ID_JOYPAD_Y; /* C */
|
||||
sc->p0_buttons[7] = RETRO_DEVICE_ID_JOYPAD_B; /* B */
|
||||
sc->p0_buttons[8] = RETRO_DEVICE_ID_JOYPAD_A; /* A */
|
||||
sc->p0_buttons[9] = -1;
|
||||
}
|
||||
|
||||
static void pt_scenario_dirs_only(pt_scenario *sc)
|
||||
{
|
||||
memset(sc, 0, sizeof(*sc));
|
||||
sc->name = "dirs_only";
|
||||
sc->p0_buttons[0] = RETRO_DEVICE_ID_JOYPAD_UP;
|
||||
sc->p0_buttons[1] = RETRO_DEVICE_ID_JOYPAD_DOWN;
|
||||
sc->p0_buttons[2] = RETRO_DEVICE_ID_JOYPAD_LEFT;
|
||||
sc->p0_buttons[3] = RETRO_DEVICE_ID_JOYPAD_RIGHT;
|
||||
sc->p0_buttons[4] = -1;
|
||||
}
|
||||
|
||||
static void pt_scenario_up_only(pt_scenario *sc)
|
||||
{
|
||||
memset(sc, 0, sizeof(*sc));
|
||||
sc->name = "up_only";
|
||||
sc->p0_buttons[0] = RETRO_DEVICE_ID_JOYPAD_UP;
|
||||
sc->p0_buttons[1] = -1;
|
||||
}
|
||||
|
||||
static void pt_scenario_letters_only(pt_scenario *sc)
|
||||
{
|
||||
memset(sc, 0, sizeof(*sc));
|
||||
sc->name = "letters_only";
|
||||
sc->p0_buttons[0] = RETRO_DEVICE_ID_JOYPAD_A;
|
||||
sc->p0_buttons[1] = RETRO_DEVICE_ID_JOYPAD_B;
|
||||
sc->p0_buttons[2] = -1;
|
||||
}
|
||||
|
||||
static void pt_scenario_up_and_pause(pt_scenario *sc)
|
||||
{
|
||||
memset(sc, 0, sizeof(*sc));
|
||||
sc->name = "up_and_pause";
|
||||
sc->p0_buttons[0] = RETRO_DEVICE_ID_JOYPAD_UP;
|
||||
sc->p0_buttons[1] = RETRO_DEVICE_ID_JOYPAD_SELECT; /* Pause -> 'P', row0 B0 */
|
||||
sc->p0_buttons[2] = -1;
|
||||
}
|
||||
|
||||
static void pt_scenario_down_and_a(pt_scenario *sc)
|
||||
{
|
||||
memset(sc, 0, sizeof(*sc));
|
||||
sc->name = "down_and_a";
|
||||
sc->p0_buttons[0] = RETRO_DEVICE_ID_JOYPAD_DOWN;
|
||||
sc->p0_buttons[1] = RETRO_DEVICE_ID_JOYPAD_A;
|
||||
sc->p0_buttons[2] = -1;
|
||||
}
|
||||
|
||||
static void pt_scenario_teamtap(pt_scenario *sc)
|
||||
{
|
||||
memset(sc, 0, sizeof(*sc));
|
||||
sc->name = "teamtap";
|
||||
sc->use_teamtap = 1;
|
||||
/* Socket 0 (the main port-1 pad): Up */
|
||||
sc->p0_buttons[0] = RETRO_DEVICE_ID_JOYPAD_UP;
|
||||
sc->p0_buttons[1] = -1;
|
||||
/* Socket 1 (frontend port 2): Down */
|
||||
sc->tap_buttons[0][0] = RETRO_DEVICE_ID_JOYPAD_DOWN;
|
||||
sc->tap_buttons[0][1] = -1;
|
||||
/* Socket 2 (frontend port 3): Left */
|
||||
sc->tap_buttons[1][0] = RETRO_DEVICE_ID_JOYPAD_LEFT;
|
||||
sc->tap_buttons[1][1] = -1;
|
||||
/* Socket 3 (frontend port 4): Right */
|
||||
sc->tap_buttons[2][0] = RETRO_DEVICE_ID_JOYPAD_RIGHT;
|
||||
sc->tap_buttons[2][1] = -1;
|
||||
}
|
||||
|
||||
static void pt_scenario_6d_rest(pt_scenario *sc)
|
||||
{
|
||||
memset(sc, 0, sizeof(*sc));
|
||||
sc->name = "6d_rest";
|
||||
sc->use_6d = 1;
|
||||
sc->p0_buttons[0] = -1;
|
||||
/* Every axis centred: liveness must NOT trip. Documents false-positive
|
||||
* trap #3 (see file header) -- padtest should show STDPAD, not
|
||||
* SIXDPAD, and that is correct, not a bug. */
|
||||
}
|
||||
|
||||
/* Distinct nonzero value per axis so a single screenshot identifies which
|
||||
* displayed field corresponds to which fed axis, and so the X sign
|
||||
* (TR V10 p.23's prose/figure contradiction) can be read directly: LEFT
|
||||
* stick X is pushed RIGHT (positive host value). All buttons pressed too,
|
||||
* to check bank-0 (A-D) and bank-1 (E/F/G, descending) against
|
||||
* INPUTDEV_SW_* -- Rezero is fed but padtest's own btns_6d table has no
|
||||
* glyph for it, so it will NOT appear on screen; that is padtest's
|
||||
* limitation, not ours (see docs). */
|
||||
static void pt_scenario_6d_live(pt_scenario *sc)
|
||||
{
|
||||
memset(sc, 0, sizeof(*sc));
|
||||
sc->name = "6d_live";
|
||||
sc->use_6d = 1;
|
||||
sc->lx = 20500; /* host RIGHT on left stick -> X (sign under test) */
|
||||
sc->ly = -10000; /* host UP on left stick -> Y */
|
||||
sc->rx = 8000; /* host RIGHT on right stick -> TX */
|
||||
sc->ry = -12000; /* host UP on right stick -> TZ */
|
||||
sc->sh_r2 = 20000; sc->sh_l2 = 0; /* Z = R2 - L2, real pressure */
|
||||
sc->sh_r = 15000; sc->sh_l = 0; /* TY = R - L, real pressure */
|
||||
sc->p0_buttons[0] = RETRO_DEVICE_ID_JOYPAD_A; /* -> SW_A */
|
||||
sc->p0_buttons[1] = RETRO_DEVICE_ID_JOYPAD_B; /* -> SW_B */
|
||||
sc->p0_buttons[2] = RETRO_DEVICE_ID_JOYPAD_Y; /* -> SW_C */
|
||||
sc->p0_buttons[3] = RETRO_DEVICE_ID_JOYPAD_X; /* -> SW_D */
|
||||
sc->p0_buttons[4] = RETRO_DEVICE_ID_JOYPAD_L3; /* -> SW_E */
|
||||
sc->p0_buttons[5] = RETRO_DEVICE_ID_JOYPAD_R3; /* -> SW_F */
|
||||
sc->p0_buttons[6] = RETRO_DEVICE_ID_JOYPAD_START; /* -> SW_G */
|
||||
sc->p0_buttons[7] = RETRO_DEVICE_ID_JOYPAD_SELECT; /* -> SW_REZERO (not shown by padtest) */
|
||||
sc->p0_buttons[8] = RETRO_DEVICE_ID_JOYPAD_L2; /* digital companion to sh_r2/l2 */
|
||||
sc->p0_buttons[9] = RETRO_DEVICE_ID_JOYPAD_R2;
|
||||
sc->p0_buttons[10] = RETRO_DEVICE_ID_JOYPAD_L;
|
||||
sc->p0_buttons[11] = RETRO_DEVICE_ID_JOYPAD_R;
|
||||
sc->p0_buttons[12] = -1;
|
||||
}
|
||||
|
||||
static void pt_scenario_6d_x_negative(pt_scenario *sc)
|
||||
{
|
||||
pt_scenario_6d_live(sc);
|
||||
sc->name = "6d_x_negative";
|
||||
sc->lx = -20500; /* host LEFT on left stick: the other sign */
|
||||
}
|
||||
|
||||
/* Same axes as 6d_live, but the analog-button reads for L2/R2/L/R are
|
||||
* forced to 0 -- the exact "frontend with no analog-button support"
|
||||
* scenario that left Z and TY dead in the merged PR. Only the digital
|
||||
* JOYPAD_MASK bit says the shoulder is held; update_input()'s fallback
|
||||
* ("if (sh[i]==0 && (ret&bit)) sh[i]=32767") is the only thing that can
|
||||
* carry Z/TY across in this run. If Z or TY reads 0 here while 6d_live
|
||||
* shows them nonzero, the fallback is broken -- a real, reportable
|
||||
* defect, not a false positive. */
|
||||
static void pt_scenario_6d_digital_fallback(pt_scenario *sc)
|
||||
{
|
||||
pt_scenario_6d_live(sc);
|
||||
sc->name = "6d_digital_fallback";
|
||||
sc->digital_fallback = 1;
|
||||
}
|
||||
|
||||
typedef void (*pt_scenario_fn)(pt_scenario *);
|
||||
|
||||
static const struct { const char *name; pt_scenario_fn fn; } SCENARIOS[] = {
|
||||
{ "standard", pt_scenario_standard },
|
||||
{ "standard_all", pt_scenario_standard_all },
|
||||
{ "dirs_only", pt_scenario_dirs_only },
|
||||
{ "up_only", pt_scenario_up_only },
|
||||
{ "letters_only", pt_scenario_letters_only },
|
||||
{ "up_and_pause", pt_scenario_up_and_pause },
|
||||
{ "down_and_a", pt_scenario_down_and_a },
|
||||
{ "teamtap", pt_scenario_teamtap },
|
||||
{ "6d_rest", pt_scenario_6d_rest },
|
||||
{ "6d_live", pt_scenario_6d_live },
|
||||
{ "6d_x_negative", pt_scenario_6d_x_negative },
|
||||
{ "6d_digital_fallback", pt_scenario_6d_digital_fallback },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static int pt_has(const int *list, int id)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < PT_MAX_BTN && list[i] >= 0; i++)
|
||||
if (list[i] == id) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int16_t pt_input_cb(void *ud, unsigned port, unsigned device,
|
||||
unsigned index, unsigned id)
|
||||
{
|
||||
const pt_scenario *sc = (const pt_scenario *)ud;
|
||||
const int *list = NULL;
|
||||
|
||||
if (device == RETRO_DEVICE_JOYPAD)
|
||||
{
|
||||
if (port == 0)
|
||||
list = sc->p0_buttons;
|
||||
else if (sc->use_teamtap && port == 2)
|
||||
list = sc->tap_buttons[0];
|
||||
else if (sc->use_teamtap && port == 3)
|
||||
list = sc->tap_buttons[1];
|
||||
else if (sc->use_teamtap && port == 4)
|
||||
list = sc->tap_buttons[2];
|
||||
else
|
||||
list = PT_NONE;
|
||||
|
||||
if (id == RETRO_DEVICE_ID_JOYPAD_MASK)
|
||||
{
|
||||
int16_t mask = 0;
|
||||
int i;
|
||||
for (i = 0; i < PT_MAX_BTN && list[i] >= 0; i++)
|
||||
mask = (int16_t)(mask | (1 << list[i]));
|
||||
return mask;
|
||||
}
|
||||
return (int16_t)(pt_has(list, (int)id) ? 1 : 0);
|
||||
}
|
||||
|
||||
if (device == RETRO_DEVICE_ANALOG && port == 0 && sc->use_6d)
|
||||
{
|
||||
if (index == RETRO_DEVICE_INDEX_ANALOG_LEFT)
|
||||
{
|
||||
if (id == RETRO_DEVICE_ID_ANALOG_X) return (int16_t)sc->lx;
|
||||
if (id == RETRO_DEVICE_ID_ANALOG_Y) return (int16_t)sc->ly;
|
||||
}
|
||||
else if (index == RETRO_DEVICE_INDEX_ANALOG_RIGHT)
|
||||
{
|
||||
if (id == RETRO_DEVICE_ID_ANALOG_X) return (int16_t)sc->rx;
|
||||
if (id == RETRO_DEVICE_ID_ANALOG_Y) return (int16_t)sc->ry;
|
||||
}
|
||||
else if (index == RETRO_DEVICE_INDEX_ANALOG_BUTTON)
|
||||
{
|
||||
if (sc->digital_fallback)
|
||||
return 0;
|
||||
if (id == RETRO_DEVICE_ID_JOYPAD_L2) return (int16_t)sc->sh_l2;
|
||||
if (id == RETRO_DEVICE_ID_JOYPAD_R2) return (int16_t)sc->sh_r2;
|
||||
if (id == RETRO_DEVICE_ID_JOYPAD_L) return (int16_t)sc->sh_l;
|
||||
if (id == RETRO_DEVICE_ID_JOYPAD_R) return (int16_t)sc->sh_r;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------
|
||||
* Screenshot: PPM, same convert-with-sips convention as
|
||||
* test/tools/cd_visual_verify.c.
|
||||
* ------------------------------------------------------------------- */
|
||||
|
||||
typedef struct {
|
||||
uint32_t *px;
|
||||
unsigned w, h;
|
||||
unsigned cur_frame;
|
||||
} pt_video;
|
||||
|
||||
static void pt_video_cb(void *ud, const void *data, unsigned w, unsigned h,
|
||||
size_t pitch)
|
||||
{
|
||||
pt_video *v = (pt_video *)ud;
|
||||
unsigned y;
|
||||
const uint8_t *src = (const uint8_t *)data;
|
||||
|
||||
if (!data || !w || !h) return;
|
||||
free(v->px);
|
||||
v->px = (uint32_t *)malloc((size_t)w * h * 4);
|
||||
if (!v->px) return;
|
||||
for (y = 0; y < h; y++)
|
||||
memcpy(&v->px[y * w], src + y * pitch, (size_t)w * 4);
|
||||
v->w = w; v->h = h;
|
||||
}
|
||||
|
||||
static bool pt_frame_cb(void *ud, unsigned frame)
|
||||
{
|
||||
pt_video *v = (pt_video *)ud;
|
||||
v->cur_frame = frame;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void pt_write_ppm(const pt_video *v, const char *path)
|
||||
{
|
||||
FILE *f;
|
||||
unsigned x, y;
|
||||
if (!v->px || !v->w || !v->h || !path) return;
|
||||
f = fopen(path, "wb");
|
||||
if (!f) { fprintf(stderr, "padtest_probe: cannot write %s\n", path); return; }
|
||||
fprintf(f, "P6\n%u %u\n255\n", v->w, v->h);
|
||||
for (y = 0; y < v->h; y++)
|
||||
for (x = 0; x < v->w; x++)
|
||||
{
|
||||
uint32_t p = v->px[y * v->w + x];
|
||||
unsigned char rgb[3];
|
||||
rgb[0] = (unsigned char)((p >> 16) & 0xFF);
|
||||
rgb[1] = (unsigned char)((p >> 8) & 0xFF);
|
||||
rgb[2] = (unsigned char)(p & 0xFF);
|
||||
fwrite(rgb, 1, 3, f);
|
||||
}
|
||||
fclose(f);
|
||||
fprintf(stderr, "padtest_probe: wrote %s (%ux%u)\n", path, v->w, v->h);
|
||||
}
|
||||
|
||||
static void pt_dump_scenario(const pt_scenario *sc)
|
||||
{
|
||||
int i;
|
||||
printf("padtest_probe: scenario=%s\n", sc->name);
|
||||
printf(" p0_buttons:");
|
||||
for (i = 0; i < PT_MAX_BTN && sc->p0_buttons[i] >= 0; i++)
|
||||
printf(" %d", sc->p0_buttons[i]);
|
||||
printf("\n");
|
||||
if (sc->use_teamtap)
|
||||
{
|
||||
int s;
|
||||
for (s = 0; s < 3; s++)
|
||||
{
|
||||
printf(" tap_socket[%d]_buttons:", s + 1);
|
||||
for (i = 0; i < PT_MAX_BTN && sc->tap_buttons[s][i] >= 0; i++)
|
||||
printf(" %d", sc->tap_buttons[s][i]);
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
if (sc->use_6d)
|
||||
{
|
||||
printf(" lx=%d ly=%d rx=%d ry=%d\n", sc->lx, sc->ly, sc->rx, sc->ry);
|
||||
printf(" sh_l2=%d sh_r2=%d sh_l=%d sh_r=%d digital_fallback=%d\n",
|
||||
sc->sh_l2, sc->sh_r2, sc->sh_l, sc->sh_r, sc->digital_fallback);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
harness_config cfg = HARNESS_CONFIG_DEFAULT;
|
||||
pt_video video;
|
||||
const char *scenario_name = NULL;
|
||||
const char *out_path = "/tmp/padtest_probe.ppm";
|
||||
int i;
|
||||
|
||||
memset(&video, 0, sizeof(video));
|
||||
|
||||
for (i = 1; i < argc; i++)
|
||||
{
|
||||
if (strcmp(argv[i], "--scenario") == 0 && i + 1 < argc)
|
||||
scenario_name = argv[++i];
|
||||
else if (strcmp(argv[i], "--out") == 0 && i + 1 < argc)
|
||||
out_path = argv[++i];
|
||||
}
|
||||
|
||||
if (!scenario_name)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"usage: padtest_probe <core> <padtest.rom> --scenario NAME "
|
||||
"[--out FILE.ppm] [--option K=V ...] [--frames N]\n"
|
||||
"scenarios:");
|
||||
for (i = 0; SCENARIOS[i].name; i++)
|
||||
fprintf(stderr, " %s", SCENARIOS[i].name);
|
||||
fprintf(stderr, "\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (i = 0; SCENARIOS[i].name; i++)
|
||||
if (strcmp(SCENARIOS[i].name, scenario_name) == 0) break;
|
||||
if (!SCENARIOS[i].name)
|
||||
{
|
||||
fprintf(stderr, "padtest_probe: unknown scenario '%s'\n", scenario_name);
|
||||
return 1;
|
||||
}
|
||||
SCENARIOS[i].fn(&g_sc);
|
||||
pt_dump_scenario(&g_sc);
|
||||
|
||||
cfg.frames = 120;
|
||||
cfg.video_callback = pt_video_cb;
|
||||
cfg.video_callback_data = &video;
|
||||
cfg.frame_callback = pt_frame_cb;
|
||||
cfg.frame_callback_data = &video;
|
||||
cfg.input_callback = pt_input_cb;
|
||||
cfg.input_callback_data = &g_sc;
|
||||
|
||||
if (!harness_init_from_args(&cfg, argc, argv)) return 1;
|
||||
if (!cfg.rom_path)
|
||||
{
|
||||
fprintf(stderr, "padtest_probe: missing ROM path\n");
|
||||
return 1;
|
||||
}
|
||||
if (!harness_load_rom(&cfg)) return 1;
|
||||
|
||||
harness_run(&cfg);
|
||||
|
||||
/* Ground-truth check, independent of padtest's own rendering: read the
|
||||
* core's OWN joypad0Buttons[] array directly. BUTTON_U=0, D=1, L=2,
|
||||
* R=3, A=16, B=17, C=18, PAUSE=19, OPTION=20 (src/jerry/joystick.h). */
|
||||
{
|
||||
uint8_t *jb = (uint8_t *)harness_dlsym(&cfg, "joypad0Buttons");
|
||||
if (jb)
|
||||
printf("padtest_probe: joypad0Buttons[U,D,L,R,A,B,C,PAUSE,OPTION] "
|
||||
"= %u %u %u %u %u %u %u %u %u\n",
|
||||
jb[0], jb[1], jb[2], jb[3], jb[16], jb[17], jb[18], jb[19], jb[20]);
|
||||
else
|
||||
fprintf(stderr, "padtest_probe: joypad0Buttons not exported -- "
|
||||
"build the core with TEST_EXPORTS=1\n");
|
||||
}
|
||||
|
||||
if (g_sc.use_6d)
|
||||
{
|
||||
typedef int (*get_type_fn)(int);
|
||||
typedef int (*any_attached_fn)(void);
|
||||
get_type_fn get_type = (get_type_fn)harness_dlsym(&cfg, "InputDevGetType");
|
||||
any_attached_fn any_attached =
|
||||
(any_attached_fn)harness_dlsym(&cfg, "InputDevAnyAttached");
|
||||
if (get_type)
|
||||
printf("padtest_probe: InputDevGetType(0)=%d "
|
||||
"(INPUTDEV_6D is the last enum value; see inputdev.h)\n",
|
||||
get_type(0));
|
||||
if (any_attached)
|
||||
printf("padtest_probe: InputDevAnyAttached()=%d\n", any_attached());
|
||||
}
|
||||
|
||||
if (g_sc.use_teamtap)
|
||||
{
|
||||
typedef bool (*get_tap_fn)(int);
|
||||
get_tap_fn get_tap = (get_tap_fn)harness_dlsym(&cfg, "JoystickGetTeamTap");
|
||||
uint8_t *jb = (uint8_t *)harness_dlsym(&cfg, "joypad0Buttons");
|
||||
if (get_tap)
|
||||
printf("padtest_probe: after run, JoystickGetTeamTap(0)=%d "
|
||||
"JoystickGetTeamTap(1)=%d\n", get_tap(0), get_tap(1));
|
||||
if (jb)
|
||||
{
|
||||
int s;
|
||||
printf("padtest_probe: joypad0Buttons ground truth "
|
||||
"(U,D,L,R,...,A,B,C @ socket*21):\n");
|
||||
for (s = 0; s < 4; s++)
|
||||
printf(" socket%d: U=%u D=%u L=%u R=%u A=%u B=%u C=%u\n",
|
||||
s, jb[s*21+0], jb[s*21+1], jb[s*21+2], jb[s*21+3],
|
||||
jb[s*21+16], jb[s*21+17], jb[s*21+18]);
|
||||
}
|
||||
}
|
||||
|
||||
pt_write_ppm(&video, out_path);
|
||||
free(video.px);
|
||||
harness_shutdown(&cfg);
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user