Give a title pad transform the host pad of a digital-presented player

A digital presentation folds the sticks onto the D-pad and centres them,
so a transform on a digital player (the default for many titles) never
saw the stick. The transform decides what the guest sees, so for a
digital stage-1 pad with no controller source its frame is now the host
pad: real sticks and buttons without the stick->D-pad fold, type still
DIGITAL (new PadExtHooks.host_pad, capture_pad_slot_view). The output is
pre-filled with the stock pad and a decline delivers it, so pass-through
stays the stock folded digital bytes (mod_pad_transform_run takes the
stock pad). Analog pads, sources and guarded frames are unchanged.

Tests: pad_external_input_test (digital player: frame has the real lx and
unfolded buttons, decline and pass-through deliver the stock digital
bytes, no host-pad query for analog / guarded / source ports),
mod_pad_transform_test (decline returns the stock pad, output pre-filled
from it).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Shokunin
2026-10-05 20:24:54 -07:00
parent a4d1f31487
commit 82bc90dff2
8 changed files with 168 additions and 41 deletions
+7 -6
View File
@@ -6,14 +6,15 @@ extern "C" {
/* Storage and per-frame validation for psx_mod_set_pad_transform (stage 2b of
* pad_external_input.h). Main (emulation) thread only.
*
* run(): 0 = no transform and no release pending (caller passes the frame
* through). 1 = *out is what to deliver: the transform's validated output,
* the pass-through frame when it declined, a neutral frame of the frame's type
* when its output was invalid, or the one neutral release frame owed after a
* detach/reset. reset() detaches every transform, leaving that release owed.
* run(): 0 = no transform and no release pending (caller delivers the stock
* pad). 1 = *out is what to deliver: the transform's validated output, the
* stock pad when it declined, a neutral frame of the frame's type when its
* output was invalid, or the one neutral release frame owed after a
* detach/reset. `stock` is the pad stages 1-2 resolved, which the output
* arrives pre-filled with; NULL = the frame itself. reset() detaches every transform, leaving that release owed.
* initial_type(): the registered boot/hotplug type, or -1. */
int mod_pad_transform_run(uint32_t player, const PSXModPadFrame *frame,
PSXModPadOutput *out);
const PSXModPadOutput *stock, PSXModPadOutput *out);
void mod_pad_transform_reset(void);
int mod_pad_transform_initial_type(uint32_t player);
#ifdef __cplusplus
+8 -3
View File
@@ -810,13 +810,18 @@ int psx_mod_set_controller_source(uint32_t player, PSXModControllerSource source
* (low-latency resample), so keep it a function of its input plus game state.
*
* The frame is the resolved pad: active-low buttons, sticks (0x80 centred),
* type (PSX_MOD_PAD_*), and host extras of the port: host_flags bit 0 = a
* type (PSX_MOD_PAD_*), and host extras of the port. A player presented as
* digital (and not driven by a controller source) is given as its host pad
* instead: the real sticks, and buttons without the stick->D-pad fold, with
* type still DIGITAL; the transform decides what the guest sees.
* host_flags bit 0 = a
* gamepad is assigned, bit 1 / bit 2 = it has a left / right trigger axis;
* host_lt / host_rt are those triggers, 0 released .. 255 fully pressed
* (0 when absent or while the savestate input guard is armed).
*
* The output arrives pre-filled with the frame (pass-through, pressures 0).
* Return non-zero to apply it, 0 to pass the frame through unchanged. The
* The output arrives pre-filled with the stock pad (what the player gets
* without a transform, so a digital pad keeps its fold and centred sticks;
* pressures 0). Return non-zero to apply it, 0 to deliver the stock pad. The
* type must be one of allowed_types (bit per PSX_MOD_PAD_*); bytes are
* 0..255 and buttons 0..0xFFFF; anything else delivers a neutral frame.
* NeGcon uses lx as twist (0x80 centre) and negcon_i / negcon_ii / negcon_l
+26 -6
View File
@@ -5757,7 +5757,11 @@ static void apply_input_override_to_sio(int override_word) {
* disconnected. `guarded` (savestate input guard) delivers neutral buttons and
* sticks but still resolves presence/type. No mouse or source side effects:
* those are layered on top by pad_ext_resolve(). */
static int capture_pad_slot(int s, PsxNetPad* out, bool guarded) {
/* host_view: the port's host pad before presentation, for a title pad
* transform (PadExtHooks.host_pad): real sticks and buttons without the
* digital stick->D-pad fold, whatever the configured mode; type unchanged. */
static int capture_pad_slot_view(int s, PsxNetPad* out, bool guarded,
bool host_view) {
if (!out) return 0;
out->buttons = 0xFFFFu;
out->lx = out->ly = out->rx = out->ry = 0x80u;
@@ -5785,7 +5789,7 @@ static int capture_pad_slot(int s, PsxNetPad* out, bool guarded) {
const int mode = effective_player_mode_for_sio(p, s);
uint8_t st[4] = { 0x80, 0x80, 0x80, 0x80 };
if (mode == PSXRecompV4::PAD_MODE_ANALOG || p.steering_wheel ||
g_mod_controller_policy[s].callback) {
g_mod_controller_policy[s].callback || host_view) {
pad_sticks_for(p, player, st);
}
const uint16_t policy_buttons =
@@ -5818,7 +5822,8 @@ static int capture_pad_slot(int s, PsxNetPad* out, bool guarded) {
* exactly as on a real DualShock. This is what stops a dual-analog game's
* D-pad control (Ape Escape's camera rotate) from being spun by stick
* movement or centre drift. Digital mode keeps the stick->D-pad fold. */
const bool suppress_stick = (eff_analog != 0) || p.steering_wheel;
const bool suppress_stick = (eff_analog != 0) || p.steering_wheel ||
host_view;
uint16_t btn = src.device ? pad_buttons_for(p, player, suppress_stick)
: (uint16_t)0xFFFF;
/* kind==1 already consumed the binds inside pad_buttons_for — ANDing the
@@ -5836,7 +5841,7 @@ static int capture_pad_slot(int s, PsxNetPad* out, bool guarded) {
* may press a button may also steer. kind==1 already folded its binds
* inside pad_sticks_for; psx_keybinds_sticks only widens a deflection, so
* applying it twice is idempotent. */
if (eff_analog) {
if (eff_analog || host_view) {
if (src.keybinds) {
const Uint8* keys = SDL_GetKeyboardState(NULL);
psx_keybinds_sticks(keys, player, st);
@@ -5844,7 +5849,7 @@ static int capture_pad_slot(int s, PsxNetPad* out, bool guarded) {
if (src.all_pads)
dev_any_controller_sticks(st);
}
if (!eff_analog && !p.steering_wheel) {
if (!eff_analog && !p.steering_wheel && !host_view) {
st[0] = st[1] = st[2] = st[3] = 0x80;
}
@@ -5855,6 +5860,10 @@ static int capture_pad_slot(int s, PsxNetPad* out, bool guarded) {
return 1;
}
static int capture_pad_slot(int s, PsxNetPad* out, bool guarded) {
return capture_pad_slot_view(s, out, guarded, false);
}
/* Netplay-only capture: assigned PlayerInput for this slot only. Never merges
* keyboard-all / all-controllers (dev_any_input). Same pad-mode / stick rules
* as capture_pad_slot otherwise. present_sio_slot is the delay-sync seat this
@@ -6453,9 +6462,19 @@ static void pad_ext_host_extras(void*, int s, uint32_t* flags, uint32_t* lt,
SDL_GameControllerGetAxis(handle, SDL_CONTROLLER_AXIS_TRIGGERRIGHT));
}
}
/* (1) Host pad of port s before presentation, for the transform frame. */
static int pad_ext_host_pad(void*, int s, uint16_t* buttons, uint8_t st[4]) {
PsxNetPad pad;
if (s < 0 || s >= PSX_MAX_PLAYERS) return 0;
if (!capture_pad_slot_view(s, &pad, false, true)) return 0;
*buttons = pad.buttons;
st[0] = pad.lx; st[1] = pad.ly; st[2] = pad.rx; st[3] = pad.ry;
return 1;
}
static int pad_ext_pad_transform(void*, int s, const PSXModPadFrame* frame,
const PSXModPadOutput* stock,
PSXModPadOutput* out) {
return mod_pad_transform_run((uint32_t)s, frame, out);
return mod_pad_transform_run((uint32_t)s, frame, stock, out);
}
static void pad_ext_mouse_fold(void*, int connected, int analog, uint16_t buttons,
uint8_t* rx, uint8_t* ry) {
@@ -6472,6 +6491,7 @@ static PadExtHooks pad_ext_main_hooks(void) {
h.mouse_fold = pad_ext_mouse_fold;
h.host_extras = pad_ext_host_extras;
h.pad_transform = pad_ext_pad_transform;
h.host_pad = pad_ext_host_pad;
return h;
}
+6 -4
View File
@@ -80,7 +80,7 @@ static int output_valid(uint32_t allowed, const PSXModPadOutput *o) {
}
int mod_pad_transform_run(uint32_t player, const PSXModPadFrame *frame,
PSXModPadOutput *out) {
const PSXModPadOutput *stock, PSXModPadOutput *out) {
if (!frame || !out || player >= PSX_MAX_PLAYERS) return 0;
if (s_release[player] && !s_active[player]) {
s_release[player] = 0;
@@ -88,10 +88,12 @@ int mod_pad_transform_run(uint32_t player, const PSXModPadFrame *frame,
return 1;
}
if (!s_active[player]) return 0;
PSXModPadOutput o;
pass_through(frame, &o);
PSXModPadOutput o, base;
if (stock) base = *stock;
else pass_through(frame, &base);
o = base;
if (!s_xf[player].transform(frame, &o)) {
pass_through(frame, out);
*out = base;
return 1;
}
if (!output_valid(s_xf[player].allowed_types, &o)) {
+21 -4
View File
@@ -11,19 +11,36 @@ int pad_ext_live(const PadExtGate *g) {
/* Stage 2b: run the title transform over the resolved pad and pack its
* output back into the PsxNetPad (NeGcon: lx twist, rx I, ry II, ly L). */
static void pad_ext_transform(const PadExtHooks *h, int s, int guarded,
PsxNetPad *out) {
int from_source, PsxNetPad *out) {
PSXModPadFrame f;
PSXModPadOutput o;
PSXModPadOutput stock, o;
if (!h->pad_transform) return;
memset(&stock, 0, sizeof stock);
stock.struct_size = sizeof stock;
stock.buttons = out->buttons;
stock.type = out->analog;
stock.lx = out->lx; stock.ly = out->ly; stock.rx = out->rx; stock.ry = out->ry;
memset(&f, 0, sizeof f);
f.struct_size = sizeof f;
f.player = (uint32_t)s;
f.buttons = out->buttons;
f.lx = out->lx; f.ly = out->ly; f.rx = out->rx; f.ry = out->ry;
f.type = out->analog;
/* A digital presentation folds the sticks onto the D-pad and centres
* them; the transform decides what the guest sees, so its frame is the
* host pad instead. The stock (pass-through) output keeps the fold. */
if (out->analog == PSX_MOD_PAD_DIGITAL && !from_source && !guarded &&
h->host_pad) {
uint16_t hb;
uint8_t st[4];
if (h->host_pad(h->ctx, s, &hb, st)) {
f.buttons = hb;
f.lx = st[0]; f.ly = st[1]; f.rx = st[2]; f.ry = st[3];
}
}
if (h->host_extras) h->host_extras(h->ctx, s, &f.host_flags, &f.host_lt, &f.host_rt);
if (guarded) f.host_lt = f.host_rt = 0;
if (!h->pad_transform(h->ctx, s, &f, &o)) return;
if (!h->pad_transform(h->ctx, s, &f, &stock, &o)) return;
if (guarded) {
o.buttons = 0xffff;
o.lx = o.ly = o.rx = o.ry = 0x80;
@@ -66,7 +83,7 @@ int pad_ext_resolve(const PadExtHooks *h, int s, PsxNetPad *out) {
if (s == 0) h->mouse_reset(h->ctx);
return 0;
}
pad_ext_transform(h, s, guarded, out);
pad_ext_transform(h, s, guarded, has_source, out);
if (s == 0) {
if (guarded || out->analog == PSX_MOD_PAD_NEGCON) h->mouse_reset(h->ctx);
else h->mouse_fold(h->ctx, out->connected, out->analog, out->buttons,
+11 -4
View File
@@ -7,8 +7,10 @@
* buttons = source & physical; sticks/type from the source, through
* the controller presentation policy (hook)
* (2b) title pad transform (psx_mod_set_pad_transform), if any: sees the
* pad of (1)/(2) plus the host extras; may rewrite buttons, sticks
* and the presented type (NeGcon packed as PsxNetPad documents)
* pad of (1)/(2) plus the host extras (a digital pad with no source
* as its host pad: real sticks, no stick->D-pad fold); may rewrite
* buttons, sticks and the presented type (NeGcon packed as PsxNetPad
* documents). Declining delivers the stock pad of (1)/(2).
* (3) local mouse policy (psx_mod_set_local_mouse_policy), P1 only: may
* override ONLY the right analog axes of whatever (1)-(2b) produced;
* a NeGcon result has no right stick, so it resets the mouse.
@@ -57,9 +59,14 @@ typedef struct PadExtHooks {
void (*host_extras)(void *ctx, int s, uint32_t *flags, uint32_t *lt,
uint32_t *rt);
/* (2b) Title transform: mod_pad_transform_run semantics (0 = none,
* 1 = deliver *out). NULL = no transform stage. */
* 1 = deliver *out; `stock` is the pass-through). NULL = no stage. */
int (*pad_transform)(void *ctx, int s, const PSXModPadFrame *frame,
PSXModPadOutput *out);
const PSXModPadOutput *stock, PSXModPadOutput *out);
/* (1) Host pad of port `s` before presentation: buttons without the
* digital stick->D-pad fold and the real sticks, whatever the configured
* mode. Queried only for the transform frame of a digital stage-1 pad
* with no source; 0 = unavailable (the frame keeps the stock pad). */
int (*host_pad)(void *ctx, int s, uint16_t *buttons, uint8_t st[4]);
} PadExtHooks;
/* Resolve one port. Returns 1 = deliver *out, 0 = no device/source here. */
+27 -11
View File
@@ -47,7 +47,7 @@ int main(void) {
t.transform = xf;
/* Nothing registered: no stage, nothing owed. */
CHECK(mod_pad_transform_run(0, &f, &o) == 0);
CHECK(mod_pad_transform_run(0, &f, NULL, &o) == 0);
CHECK(mod_pad_transform_initial_type(0) == -1);
/* Registration validation. */
@@ -57,13 +57,13 @@ int main(void) {
{ PSXModPadTransform b = t; b.allowed_types = 0; CHECK(!psx_mod_set_pad_transform(0, &b)); }
{ PSXModPadTransform b = t; b.allowed_types |= 1u << 4; CHECK(!psx_mod_set_pad_transform(0, &b)); }
{ PSXModPadTransform b = t; b.initial_type = PSX_MOD_PAD_JOGCON; CHECK(!psx_mod_set_pad_transform(0, &b)); }
CHECK(mod_pad_transform_run(0, &f, &o) == 0);
CHECK(mod_pad_transform_run(0, &f, NULL, &o) == 0);
CHECK(psx_mod_set_pad_transform(0, &t));
CHECK(mod_pad_transform_initial_type(0) == PSX_MOD_PAD_DUALSHOCK);
CHECK(mod_pad_transform_initial_type(1) == -1);
/* Applied output; the frame carries pad + host extras. */
CHECK(mod_pad_transform_run(0, &f, &o) == 1);
CHECK(mod_pad_transform_run(0, &f, NULL, &o) == 1);
CHECK(seen.player == 0 && seen.host_rt == 200 && seen.type == PSX_MOD_PAD_DUALSHOCK);
CHECK(o.type == PSX_MOD_PAD_NEGCON && o.buttons == 0xffffu &&
o.negcon_i == 200 && o.negcon_ii == 30 && o.negcon_l == 0);
@@ -71,14 +71,30 @@ int main(void) {
/* Decline -> exact pass-through. */
mode = 1;
CHECK(mod_pad_transform_run(0, &f, &o) == 1);
CHECK(mod_pad_transform_run(0, &f, NULL, &o) == 1);
CHECK(o.type == PSX_MOD_PAD_DUALSHOCK && o.buttons == 0xfbffu &&
o.lx == 0x40 && o.ry == 0x70 && o.negcon_i == 0);
/* Decline with a stock pad -> that stock pad, not the frame (a digital
* player's frame is its host pad; the stock pad keeps the fold). */
{
PSXModPadOutput stock;
memset(&stock, 0, sizeof stock);
stock.struct_size = sizeof stock;
stock.buttons = 0xff7fu; stock.type = PSX_MOD_PAD_DIGITAL;
stock.lx = stock.ly = stock.rx = stock.ry = 0x80;
CHECK(mod_pad_transform_run(0, &f, &stock, &o) == 1);
CHECK(memcmp(&o, &stock, sizeof o) == 0);
mode = 0; /* applied: pre-filled from the stock pad */
CHECK(mod_pad_transform_run(0, &f, &stock, &o) == 1);
CHECK(o.type == PSX_MOD_PAD_NEGCON && o.lx == 0x80 && o.negcon_i == 200);
mode = 1;
}
/* Invalid output (range, disallowed type, struct size) -> neutral of the
* frame's type. */
for (mode = 2; mode <= 4; mode++) {
CHECK(mod_pad_transform_run(0, &f, &o) == 1);
CHECK(mod_pad_transform_run(0, &f, NULL, &o) == 1);
CHECK(o.type == PSX_MOD_PAD_DUALSHOCK && o.buttons == 0xffffu &&
o.lx == 0x80 && o.ry == 0x80 && o.negcon_i == 0);
}
@@ -88,21 +104,21 @@ int main(void) {
mode = 0;
CHECK(psx_mod_set_pad_transform(0, NULL));
calls = 0;
CHECK(mod_pad_transform_run(0, &f, &o) == 1);
CHECK(mod_pad_transform_run(0, &f, NULL, &o) == 1);
CHECK(calls == 0 && o.buttons == 0xffffu && o.type == PSX_MOD_PAD_DUALSHOCK &&
o.lx == 0x80);
CHECK(mod_pad_transform_run(0, &f, &o) == 0);
CHECK(mod_pad_transform_run(0, &f, NULL, &o) == 0);
CHECK(mod_pad_transform_initial_type(0) == -1);
/* Session reset: same release for every registered player. */
CHECK(psx_mod_set_pad_transform(0, &t) && psx_mod_set_pad_transform(1, &t));
mod_pad_transform_reset();
CHECK(mod_pad_transform_run(0, &f, &o) == 1 && o.buttons == 0xffffu);
CHECK(mod_pad_transform_run(1, &f, &o) == 1 && o.buttons == 0xffffu);
CHECK(mod_pad_transform_run(0, &f, &o) == 0 && mod_pad_transform_run(1, &f, &o) == 0);
CHECK(mod_pad_transform_run(0, &f, NULL, &o) == 1 && o.buttons == 0xffffu);
CHECK(mod_pad_transform_run(1, &f, NULL, &o) == 1 && o.buttons == 0xffffu);
CHECK(mod_pad_transform_run(0, &f, NULL, &o) == 0 && mod_pad_transform_run(1, &f, NULL, &o) == 0);
/* A reset with nothing registered owes nothing. */
mod_pad_transform_reset();
CHECK(mod_pad_transform_run(0, &f, &o) == 0);
CHECK(mod_pad_transform_run(0, &f, NULL, &o) == 0);
fprintf(stderr, "mod_pad_transform: passed\n");
return 0;
}
+62 -3
View File
@@ -17,6 +17,8 @@ static struct {
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;
@@ -56,10 +58,16 @@ static void h_mfold(void *c, int con, int an, uint16_t b, uint8_t *rx, uint8_t *
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, PSXModPadOutput *o) {
(void)c; (void)s; L('t'); T.xf_calls++; T.xf_seen = *f;
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_out; return 1;
*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) {
@@ -209,6 +217,57 @@ int main(void) {
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);