Add per-backend input debug visualization + round-trip view
Makes it possible to isolate an input->tool problem from a tool->game one in the
Controllers panel (under Debug details):
- which backend fed each slot is already shown via the per-pad source tag
("XInput #0" / "Steam Input") in the Incoming section;
- a new "Round-trip" table shows, per slot, the state we forwarded (the active
backend's pad) next to what the game actually read back, highlighting matches.
For the round-trip, the XInput hook now echoes the state it returns to the game into
the status (protocol v9->v10: per-slot read_state in HookStatus).
Verified: hook_selftest (x64 + x86) asserts the hook records the game-read state;
full build x64 + x86 clean; ctest x64 9/9, x86 3/3.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -182,6 +182,37 @@ void ControllersPanel::draw(const InputSource& input, const HookStatusView& stat
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
// Round-trip view: what we forwarded (the active backend's pad, tagged per slot in
|
||||
// the Incoming section above) vs what the game actually read back through the hook.
|
||||
// A mismatch isolates a tool->game forwarding problem from an input->tool one.
|
||||
if (debug_details)
|
||||
{
|
||||
ImGui::SeparatorText("Round-trip (forwarded vs game read)");
|
||||
if (ImGui::BeginTable("roundtrip", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingStretchProp))
|
||||
{
|
||||
ImGui::TableSetupColumn("Slot");
|
||||
ImGui::TableSetupColumn("Forwarded btn / LX,LY");
|
||||
ImGui::TableSetupColumn("Game read btn / LX,LY");
|
||||
ImGui::TableHeadersRow();
|
||||
const auto& fwd = input.pads();
|
||||
for (int i = 0; i < static_cast<int>(kMaxPads); ++i)
|
||||
{
|
||||
const CoopPadState& f = fwd[i].state;
|
||||
const CoopPadState& r = status.read_state[i];
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%d", i);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("0x%04X %d,%d", f.buttons, f.thumb_lx, f.thumb_ly);
|
||||
ImGui::TableNextColumn();
|
||||
const bool match = f.buttons == r.buttons && f.thumb_lx == r.thumb_lx && f.thumb_ly == r.thumb_ly;
|
||||
ImGui::TextColored(match ? kGreen : kGrey, "0x%04X %d,%d", r.buttons, r.thumb_lx, r.thumb_ly);
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
ImGui::TextDisabled("Slot tags in 'Incoming' above show which backend (XInput / Steam) fed each slot.");
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
|
||||
@@ -89,6 +89,7 @@ HookStatusView IpcServer::hook_status() const
|
||||
{
|
||||
view.rumble_left[i] = s.rumble_left[i];
|
||||
view.rumble_right[i] = s.rumble_right[i];
|
||||
view.read_state[i] = s.read_state[i];
|
||||
}
|
||||
return view;
|
||||
}
|
||||
|
||||
@@ -40,6 +40,9 @@ struct HookStatusView
|
||||
// Per-slot rumble the game requested (host forwards it to the guest's pad).
|
||||
std::uint16_t rumble_left[kMaxPads] = {};
|
||||
std::uint16_t rumble_right[kMaxPads] = {};
|
||||
|
||||
// The last pad state the hook returned to the game per slot (round-trip view).
|
||||
CoopPadState read_state[kMaxPads] = {};
|
||||
};
|
||||
|
||||
// Plain snapshot of the Present-hook video channel for the Video mirror panel.
|
||||
|
||||
Reference in New Issue
Block a user