Files
psxrecomp/runtime/tests/test_pad_external_input.c
T

291 lines
15 KiB
C

/* Resolution order of external offline input (physical -> source -> mouse). */
#include "pad_external_input.h"
#include <stdio.h>
#include <string.h>
#define CHECK(expr) do { if (!(expr)) { \
fprintf(stderr, "FAIL line %d: %s\n", __LINE__, #expr); return 1; } } while (0)
static struct {
int guard, have_device, have_source, released;
PsxNetPad local; PSXModControllerState src;
int mouse_override; uint8_t mouse_rx, mouse_ry;
char log[64]; int n;
int capture_calls, source_calls, resolve_calls, release_calls;
int mouse_resets, mouse_folds, last_guarded_cap, last_guarded_res;
int fold_connected, fold_analog; uint16_t fold_buttons; uint8_t fold_rx, fold_ry;
int fold_slot_calls[2];
int src_analog_out;
int xf_ret; PSXModPadOutput xf_out; PSXModPadFrame xf_seen; int xf_calls;
PSXModPadOutput xf_stock; int xf_echo_stock;
int host_pad_ok, host_pad_calls; uint16_t host_buttons; uint8_t host_st[4];
uint32_t ex_flags, ex_lt, ex_rt;
} T;
static void L(char c) { T.log[T.n++] = c; T.log[T.n] = 0; }
static int h_guard(void *c) { (void)c; L('g'); return T.guard; }
static int h_cap(void *c, int s, PsxNetPad *o, int g) {
(void)c; (void)s; L('c'); T.capture_calls++; T.last_guarded_cap = g;
if (!T.have_device) { memset(o, 0, sizeof *o); return 0; }
*o = T.local; if (g) { o->buttons = 0xffff; o->rx = o->ry = 0x80; }
return 1;
}
static int h_src(void *c, int s, PSXModControllerState *st, int *rel) {
(void)c; (void)s; L('s'); T.source_calls++;
if (!T.have_source) return 0;
*st = T.src; *rel = T.released; return 1;
}
static void h_rel(void *c, int s, PsxNetPad *o) {
(void)c; (void)s; L('R'); T.release_calls++;
o->buttons = 0xffff; o->lx = o->ly = o->rx = o->ry = 128; o->analog = 1; o->connected = 1;
}
static void h_res(void *c, int s, const PSXModControllerState *st, int g, PsxNetPad *o) {
(void)c; (void)s; L('r'); T.resolve_calls++; T.last_guarded_res = g;
o->lx = (uint8_t)st->lx; o->ly = (uint8_t)st->ly;
o->rx = (uint8_t)st->rx; o->ry = (uint8_t)st->ry;
o->analog = (uint8_t)T.src_analog_out; o->connected = 1;
if (!o->analog) o->lx = o->ly = o->rx = o->ry = 128;
if (g) { o->buttons = 0xffff; o->lx = o->ly = o->rx = o->ry = 128; }
}
static void h_mreset(void *c) { (void)c; L('x'); T.mouse_resets++; }
static void h_mfold(void *c, int con, int an, uint16_t b, uint8_t *rx, uint8_t *ry) {
(void)c; L('m'); T.mouse_folds++;
T.fold_connected = con; T.fold_analog = an; T.fold_buttons = b;
T.fold_rx = *rx; T.fold_ry = *ry;
if (T.mouse_override) { *rx = T.mouse_rx; *ry = T.mouse_ry; }
}
static void h_extras(void *c, int s, uint32_t *f, uint32_t *lt, uint32_t *rt) {
(void)c; (void)s; L('e'); *f = T.ex_flags; *lt = T.ex_lt; *rt = T.ex_rt;
}
static int h_xf(void *c, int s, const PSXModPadFrame *f,
const PSXModPadOutput *stock, PSXModPadOutput *o) {
(void)c; (void)s; L('t'); T.xf_calls++; T.xf_seen = *f; T.xf_stock = *stock;
if (!T.xf_ret) return 0;
*o = T.xf_echo_stock ? *stock : T.xf_out; return 1;
}
static int h_host_pad(void *c, int s, uint16_t *b, uint8_t st[4]) {
(void)c; (void)s; L('h'); T.host_pad_calls++;
if (!T.host_pad_ok) return 0;
*b = T.host_buttons; memcpy(st, T.host_st, 4); return 1;
}
static PadExtHooks hooks(void) {
PadExtHooks h; memset(&h, 0, sizeof h);
h.guard_active = h_guard; h.capture_local = h_cap; h.source_sample = h_src;
h.source_release = h_rel; h.source_resolve = h_res;
h.mouse_reset = h_mreset; h.mouse_fold = h_mfold;
return h;
}
static void reset(void) {
memset(&T, 0, sizeof T);
T.have_device = 1; T.src_analog_out = 1;
T.local.buttons = 0xfffe; /* SELECT held */
T.local.lx = 10; T.local.ly = 20; T.local.rx = 30; T.local.ry = 40;
T.local.analog = 1; T.local.connected = 1;
T.src.struct_size = sizeof T.src; T.src.buttons = 0xfeff;
T.src.lx = 200; T.src.ly = 5; T.src.rx = 128; T.src.ry = 128; T.src.analog = 1;
}
int main(void) {
PadExtHooks h = hooks();
PsxNetPad o;
/* No source, no mouse effect: physical pad passes through bit-identical. */
reset();
CHECK(pad_ext_resolve(&h, 0, &o) == 1);
CHECK(o.buttons == 0xfffe && o.lx == 10 && o.ly == 20 && o.rx == 30 &&
o.ry == 40 && o.analog == 1 && o.connected == 1);
CHECK(T.capture_calls == 1 && T.resolve_calls == 0 && T.release_calls == 0);
CHECK(T.mouse_folds == 1 && T.fold_connected == 1 && T.fold_analog == 1 &&
T.fold_buttons == 0xfffe && T.fold_rx == 30 && T.fold_ry == 40);
CHECK(strcmp(T.log, "gscm") == 0);
/* Other ports never reach the mouse; no device: 0 and P1 resets it. */
reset();
CHECK(pad_ext_resolve(&h, 1, &o) == 1 && T.mouse_folds == 0 && T.mouse_resets == 0);
reset(); T.have_device = 0;
CHECK(pad_ext_resolve(&h, 1, &o) == 0 && T.mouse_resets == 0);
reset(); T.have_device = 0;
CHECK(pad_ext_resolve(&h, 0, &o) == 0 && T.mouse_resets == 1 && T.mouse_folds == 0);
/* Mouse only: overrides the right stick, left stick/buttons untouched. */
reset(); T.local.rx = T.local.ry = 128; T.mouse_override = 1; T.mouse_rx = 250; T.mouse_ry = 7;
CHECK(pad_ext_resolve(&h, 0, &o) == 1);
CHECK(o.rx == 250 && o.ry == 7 && o.lx == 10 && o.ly == 20 && o.buttons == 0xfffe);
/* Source only: buttons ANDed with physical, sticks/type from the source,
* physical capture sampled exactly once, mouse offered the RESOLVED pad. */
reset();
T.have_source = 1; T.src.rx = 90; T.src.ry = 100;
CHECK(pad_ext_resolve(&h, 0, &o) == 1);
CHECK(o.buttons == (0xfeff & 0xfffe) && o.lx == 200 && o.ly == 5 &&
o.rx == 90 && o.ry == 100 && o.analog == 1 && o.connected == 1);
CHECK(T.capture_calls == 1 && T.resolve_calls == 1);
CHECK(T.fold_buttons == (0xfeff & 0xfffe) && T.fold_analog == 1 &&
T.fold_rx == 90 && T.fold_ry == 100); /* not the physical 30/40 */
CHECK(strcmp(T.log, "gscrm") == 0);
/* Source + mouse together: mouse overrides ONLY the right axes. */
reset();
T.have_source = 1; T.mouse_override = 1; T.mouse_rx = 33; T.mouse_ry = 44;
CHECK(pad_ext_resolve(&h, 0, &o) == 1);
CHECK(o.rx == 33 && o.ry == 44 && o.lx == 200 && o.ly == 5 &&
o.buttons == (0xfeff & 0xfffe) && T.mouse_resets == 0);
/* Source on a port with no physical device: source buttons as-is. */
reset(); T.have_source = 1; T.have_device = 0;
CHECK(pad_ext_resolve(&h, 0, &o) == 1 && o.buttons == 0xfeff && o.connected == 1);
CHECK(T.mouse_folds == 1 && T.mouse_resets == 0);
/* Digital source: sticks neutral, mouse sees analog=0. */
reset(); T.have_source = 1; T.src_analog_out = 0;
CHECK(pad_ext_resolve(&h, 0, &o) == 1 && o.analog == 0 && o.lx == 128 &&
T.fold_analog == 0);
/* Source release frame: neutral pad, mouse released, no physical capture. */
reset(); T.have_source = 1; T.released = 1;
CHECK(pad_ext_resolve(&h, 0, &o) == 1 && o.buttons == 0xffff && o.lx == 128);
CHECK(T.release_calls == 1 && T.capture_calls == 0 && T.resolve_calls == 0);
CHECK(T.mouse_resets == 1 && T.mouse_folds == 0);
reset(); T.have_source = 1; T.released = 1;
CHECK(pad_ext_resolve(&h, 1, &o) == 1 && T.mouse_resets == 0);
/* Input guard: external input is neutral and the mouse is released; the
* guard is forwarded to capture (physical) and resolve (source). */
reset(); T.guard = 1;
CHECK(pad_ext_resolve(&h, 0, &o) == 1 && T.last_guarded_cap == 1);
CHECK(o.buttons == 0xffff && o.rx == 128 && T.mouse_resets == 1 && T.mouse_folds == 0);
reset(); T.guard = 1; T.have_source = 1;
CHECK(pad_ext_resolve(&h, 0, &o) == 1 && T.last_guarded_res == 1);
CHECK(o.buttons == 0xffff && o.lx == 128 && T.mouse_resets == 1 && T.mouse_folds == 0);
/* Stage 2b, title transform. Not registered (hook returns 0): the pad of
* stages 1-2 passes through bit-identical. */
{
PadExtHooks hx = hooks();
hx.host_extras = h_extras; hx.pad_transform = h_xf;
reset();
CHECK(pad_ext_resolve(&hx, 0, &o) == 1);
CHECK(o.buttons == 0xfffe && o.lx == 10 && o.ly == 20 && o.rx == 30 &&
o.ry == 40 && o.analog == 1);
CHECK(strcmp(T.log, "gscetm") == 0);
/* It sees the resolved pad (after the source) plus host extras, and a
* NeGcon output is packed lx twist, rx I, ry II, ly L; the mouse is
* reset rather than folded into the pressures. */
reset();
T.have_source = 1;
T.ex_flags = 7; T.ex_lt = 60; T.ex_rt = 250;
T.xf_ret = 1;
T.xf_out.struct_size = sizeof T.xf_out; T.xf_out.buttons = 0xbfff;
T.xf_out.type = PSX_MOD_PAD_NEGCON; T.xf_out.lx = 0x30;
T.xf_out.ly = 0x99; T.xf_out.rx = 0x98; T.xf_out.ry = 0x97;
T.xf_out.negcon_i = 250; T.xf_out.negcon_ii = 60; T.xf_out.negcon_l = 5;
T.mouse_override = 1; T.mouse_rx = 1; T.mouse_ry = 2;
CHECK(pad_ext_resolve(&hx, 0, &o) == 1);
CHECK(T.xf_seen.struct_size == sizeof T.xf_seen && T.xf_seen.player == 0);
CHECK(T.xf_seen.buttons == (0xfeff & 0xfffe) && T.xf_seen.lx == 200 &&
T.xf_seen.type == 1 && T.xf_seen.host_flags == (7 | PSX_MOD_PAD_HOST_SOURCE) &&
T.xf_seen.host_lt == 60 && T.xf_seen.host_rt == 250);
CHECK(o.analog == PSX_MOD_PAD_NEGCON && o.buttons == 0xbfff &&
o.lx == 0x30 && o.rx == 250 && o.ry == 60 && o.ly == 5);
CHECK(T.mouse_folds == 0 && T.mouse_resets == 1);
CHECK(strcmp(T.log, "gscretx") == 0);
/* DualShock output: sticks from the output, mouse folds as usual. */
reset();
T.xf_ret = 1; T.xf_out.struct_size = sizeof T.xf_out;
T.xf_out.buttons = 0xfff0; T.xf_out.type = PSX_MOD_PAD_DUALSHOCK;
T.xf_out.lx = 1; T.xf_out.ly = 2; T.xf_out.rx = 3; T.xf_out.ry = 4;
CHECK(pad_ext_resolve(&hx, 0, &o) == 1);
CHECK(o.analog == 1 && o.lx == 1 && o.ly == 2 && o.rx == 3 && o.ry == 4 &&
T.mouse_folds == 1 && T.fold_rx == 3 && T.fold_buttons == 0xfff0);
/* Digital output centres the sticks. */
T.xf_out.type = PSX_MOD_PAD_DIGITAL;
CHECK(pad_ext_resolve(&hx, 1, &o) == 1 && o.analog == 0 && o.lx == 0x80);
/* Input guard: the transform still runs, sees no trigger values, and
* its output is neutralized while keeping the presented type. */
reset(); T.guard = 1; T.ex_flags = 7; T.ex_lt = 99; T.ex_rt = 99;
T.xf_ret = 1; T.xf_out.struct_size = sizeof T.xf_out;
T.xf_out.buttons = 0x0000; T.xf_out.type = PSX_MOD_PAD_NEGCON;
T.xf_out.negcon_i = 200;
CHECK(pad_ext_resolve(&hx, 0, &o) == 1);
CHECK(T.xf_seen.host_flags == 7 && T.xf_seen.host_lt == 0 && T.xf_seen.host_rt == 0);
CHECK(o.analog == PSX_MOD_PAD_NEGCON && o.buttons == 0xffff &&
o.lx == 0x80 && o.rx == 0 && o.ry == 0 && o.ly == 0);
/* Digital-presented player: the frame is the host pad (real sticks,
* buttons without the stick->D-pad fold, type still digital); the
* stock output, delivered on decline, is the folded digital pad. */
hx.host_pad = h_host_pad;
{
const uint16_t folded = 0xff7e; /* SELECT + LEFT from the stick */
reset();
T.local.analog = 0; T.local.buttons = folded;
T.local.lx = T.local.ly = T.local.rx = T.local.ry = 0x80;
T.host_pad_ok = 1; T.host_buttons = 0xfffe;
T.host_st[0] = 0x10; T.host_st[1] = 0x80; T.host_st[2] = 0x81; T.host_st[3] = 0x7f;
CHECK(pad_ext_resolve(&hx, 0, &o) == 1);
CHECK(T.host_pad_calls == 1 && T.xf_seen.type == PSX_MOD_PAD_DIGITAL &&
T.xf_seen.lx == 0x10 && T.xf_seen.rx == 0x81 &&
T.xf_seen.buttons == 0xfffe);
CHECK(T.xf_stock.buttons == folded && T.xf_stock.lx == 0x80 &&
T.xf_stock.type == PSX_MOD_PAD_DIGITAL);
CHECK(o.buttons == folded && o.analog == 0 && o.lx == 0x80 &&
o.ly == 0x80 && o.rx == 0x80 && o.ry == 0x80 && o.connected == 1);
/* Declined through mod_pad_transform_run (stock echoed back):
* still the stock digital bytes. */
reset();
T.local.analog = 0; T.local.buttons = folded;
T.local.lx = T.local.ly = T.local.rx = T.local.ry = 0x80;
T.host_pad_ok = 1; T.host_buttons = 0xfffe; T.host_st[0] = 0x10;
T.xf_ret = 1; T.xf_echo_stock = 1;
CHECK(pad_ext_resolve(&hx, 0, &o) == 1);
CHECK(o.buttons == folded && o.analog == 0 && o.lx == 0x80 &&
o.ly == 0x80 && o.rx == 0x80 && o.ry == 0x80);
/* A NeGcon from the real stick. */
T.xf_echo_stock = 0; T.xf_out.struct_size = sizeof T.xf_out;
T.xf_out.type = PSX_MOD_PAD_NEGCON; T.xf_out.buttons = 0xffff;
T.xf_out.lx = 0x10;
CHECK(pad_ext_resolve(&hx, 0, &o) == 1 && o.analog == PSX_MOD_PAD_NEGCON &&
o.lx == 0x10);
/* Analog pads, guarded frames and source-driven ports keep the
* resolved pad as the frame; no host-pad query. */
reset(); T.host_pad_ok = 1; T.host_st[0] = 0x10;
CHECK(pad_ext_resolve(&hx, 0, &o) == 1 && T.host_pad_calls == 0 &&
T.xf_seen.lx == 10);
reset(); T.local.analog = 0; T.guard = 1; T.host_pad_ok = 1;
CHECK(pad_ext_resolve(&hx, 0, &o) == 1 && T.host_pad_calls == 0);
reset(); T.have_source = 1; T.src_analog_out = 0; T.host_pad_ok = 1;
CHECK(pad_ext_resolve(&hx, 0, &o) == 1 && T.host_pad_calls == 0);
/* Unavailable host pad: the frame stays the stock pad. */
reset(); T.local.analog = 0; T.local.buttons = folded;
T.local.lx = 0x80;
CHECK(pad_ext_resolve(&hx, 0, &o) == 1 && T.host_pad_calls == 1 &&
T.xf_seen.buttons == folded && T.xf_seen.lx == 0x80);
}
/* No device and no source: the transform is not consulted. */
reset(); T.have_device = 0; T.xf_ret = 1;
CHECK(pad_ext_resolve(&hx, 0, &o) == 0 && T.xf_calls == 0);
}
/* Gating predicate: live only when every blocking condition is clear. */
{
PadExtGate g; memset(&g, 0, sizeof g);
CHECK(pad_ext_live(&g) == 1 && pad_ext_live(NULL) == 0);
int *f[] = { &g.injected_input, &g.headless, &g.netplay_active,
&g.netplay_resim, &g.selfcheck_locked, &g.selfcheck_resim,
&g.render_pass, &g.savestate_menu_open, &g.rewind_open,
&g.input_guard, &g.ui_capture };
for (unsigned i = 0; i < sizeof f / sizeof f[0]; i++) {
*f[i] = 1; CHECK(pad_ext_live(&g) == 0); *f[i] = 0;
}
CHECK(pad_ext_live(&g) == 1);
}
return 0;
}