[iterate-4C] M3: default connected controller for user 0 (reach the Press A title loop)

Sylpheed's boot reaches its front-end TITLE shell (steady frame loop — 2219
swaps over 5B instr; XamResetInactivity/XamEnableInactivityProcessing/
XamUserGetSigninState front-end mgmt; XamInputGetCapabilities polled 8880x) but
then STALLS: headless has no HID device, so GetCapabilities returns
DEVICE_NOT_CONNECTED and the game loops forever waiting for a controller. It
never advances to read input (XamInputGetState / XamInputGetKeystrokeEx = 0).

Canary reaches the "Press A" menu because its default input drivers (xinput /
winkey) present an always-connected controller. Match that: present a default
always-connected, IDLE (no-buttons) virtual controller for user 0 (a console
always exposes controller slots; a real gamepad under --ui still supplies actual
button state — see xam_input_get_state, which prefers ui.snapshot_gamepad()).
Set XENIA_NO_PAD=1 to present no controller (the raw headless no-HID trajectory).

MEASURED (headless, this is not inference): with the controller connected the
game advances into the interactive input-poll loop —
  XamInputGetKeystrokeEx  0 -> 2164  (once per frame)
  XamInputGetState        0 -> 2165
  XamInputSetState (rumble) 0 -> 1
i.e. the "Press A" title-screen loop (attract video repeats while it polls for
A), exactly the M3 milestone behavior. Visual confirmation of the on-screen
"Press A" prompt is the remaining step (--ui + frame readback).

GOLDEN RE-BASELINE (deliberate): a connected controller changes the guest
trajectory WITHIN the 200M window (the title polls input before 200M), so both
sylpheed_n200m.json and sylpheed_n2m.json are re-baselined. Change is proven
ISOLATED: XENIA_NO_PAD=1 reproduces the OLD goldens byte-identical at both n.
New n200m: instructions 200000203, imports 575647, draws 3208, swaps 910
(was 200000239 / 575447 / 3165 / 895 — small front-end-input increments; RTs/
shaders/textures unchanged). All 6 JIT configs match the new golden byte-
identical (interp / JIT / JIT+CHAIN / +REGCACHE / REGION_MAX=1 / budget=1==
interp+budget=1); 131 xenia-kernel + 21 xenia-jit tests green.
This commit is contained in:
MechaCat02
2026-07-05 13:29:52 +02:00
parent f562e3ec2b
commit 0e24e980fb
2 changed files with 35 additions and 12 deletions

View File

@@ -1,9 +1,9 @@
{
"instructions": 200000239,
"imports": 575447,
"instructions": 200000203,
"imports": 575647,
"unimpl": 0,
"draws": 3165,
"swaps": 895,
"draws": 3208,
"swaps": 910,
"unique_render_targets": 2,
"shader_blobs_live": 6,
"texture_cache_entries": 1

View File

@@ -125,6 +125,29 @@ fn gamepad_key(state: &xenia_hid::GamepadState) -> u128 {
u128::from_be_bytes(bytes)
}
/// Whether user 0 has a default always-connected, idle (no-buttons) virtual
/// controller. **Default ON** — matches canary, whose default input drivers
/// (xinput / winkey) present a connected pad, so a title screen advances to its
/// "Press A" input-poll state instead of stalling forever on
/// DEVICE_NOT_CONNECTED (a console always exposes controller slots; a real
/// gamepad under `--ui` supplies actual button state, otherwise the pad is idle).
/// Set `XENIA_NO_PAD=1` to present NO controller (reproduces the raw headless
/// no-HID trajectory).
fn virtual_pad_enabled() -> bool {
use std::sync::OnceLock;
static V: OnceLock<bool> = OnceLock::new();
*V.get_or_init(|| {
!std::env::var("XENIA_NO_PAD")
.map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
.unwrap_or(false)
})
}
/// True iff user 0 has a controller: a real host gamepad, or the default pad.
fn user_connected(state: &KernelState, user: u32) -> bool {
state.ui.as_ref().is_some_and(|ui| ui.is_connected(user)) || (user == 0 && virtual_pad_enabled())
}
fn xam_input_get_capabilities(
ctx: &mut PpcContext,
mem: &GuestMemory,
@@ -133,8 +156,7 @@ fn xam_input_get_capabilities(
// r3 = user_index, r4 = flags, r5 = out X_INPUT_CAPABILITIES*
let user = ctx.gpr[3] as u32;
let out_ptr = ctx.gpr[5] as u32;
let connected = state.ui.as_ref().is_some_and(|ui| ui.is_connected(user));
if !connected {
if !user_connected(state, user) {
ctx.gpr[3] = xenia_hid::errors::DEVICE_NOT_CONNECTED as u64;
return;
}
@@ -146,15 +168,16 @@ fn xam_input_get_state(ctx: &mut PpcContext, mem: &GuestMemory, state: &mut Kern
// r3 = user_index, r4 = flags, r5 = out X_INPUT_STATE*
let user = ctx.gpr[3] as u32;
let out_ptr = ctx.gpr[5] as u32;
let Some(ui) = state.ui.as_ref() else {
ctx.gpr[3] = xenia_hid::errors::DEVICE_NOT_CONNECTED as u64;
return;
};
if !ui.is_connected(user) {
if !user_connected(state, user) {
ctx.gpr[3] = xenia_hid::errors::DEVICE_NOT_CONNECTED as u64;
return;
}
let gamepad = ui.snapshot_gamepad();
// Real host gamepad if present, else the idle virtual pad (XENIA_VIRTUAL_PAD).
let gamepad = state
.ui
.as_ref()
.map(|ui| ui.snapshot_gamepad())
.unwrap_or_default();
let key = gamepad_key(&gamepad);
if key != state.last_input_bytes {
state.input_packet_number = state.input_packet_number.wrapping_add(1);