Skip to content

[compass] Fix: a refused gamepad_set leaves part of its change pending, so the next set or tap sends it - #49

Merged
skulitom merged 5 commits into
mainfrom
compass/anode-fix-2026-10-15-gamepad-set-atomic
Oct 15, 2026
Merged

skulitom merged 5 commits into
mainfrom
compass/anode-fix-2026-10-15-gamepad-set-atomic

Conversation

@skulitom

@skulitom skulitom commented Oct 15, 2026 •

Copy link
Copy Markdown
Owner

Report

ANODE-BUGS.md B-041 (filed 2026-10-15 by compass-anode-fixer from a read-only code hunt in its nightly run): a rejected gamepad_set leaves part of its change pending, so the next set or tap sends it.

Root cause

GamepadManager.Apply (src/Anode/Core/Gamepad/GamepadManager.cs) resolved and set each button, axis and trigger as it read them, then called SubmitReport once at the end. Every pad runs with AutoSubmitReport = false (set in Attach), so a set only changes the pad's pending report. When a later name was unknown, ResolveButton, ResolveAxis or ResolveTrigger threw before SubmitReport, the agent got an error, and the changes made before it stayed pending. The agent's next gamepad_set or gamepad_tap sent them (a gamepad_reset or a detach clears the report first, so those threw them away). Example: gamepad_set {"buttons": {"a": true, "dpad_up": true}} fails on dpad_up, and A goes down with the agent's next call, so the game in the seat sees A held that the agent believes it never pressed. The MCP schema takes any button name, so a guessed name reaches this path.

Two smaller faults in the same names:

  • The unknown-button error listed "lt or rt" among the buttons, so gamepad_set buttons {"lt": true} and anode gamepad hold lt were told "Unknown gamepad button 'lt'. Use …, lt or rt."
  • gamepad_tap recognised lt, rt, lefttrigger and righttrigger as triggers, but not l2 and r2, which ResolveTrigger accepts; gamepad_tap l2 failed with that same error.

Fix

  • Apply resolves every name and value first into a list of changes, then sets them and submits one report under the lock. A refused call changes nothing.
  • One TriggerNamed helper now names the triggers for ResolveTrigger, Tap and the button error. A trigger passed as a button hears "'lt' is a trigger, not a button: tap it, or, over MCP, set it from 0 to 1 under gamepad_set's triggers." (the CLI can tap a trigger but not hold one) The unknown-button list no longer offers lt or rt.
  • No change to the MCP schema, the CLI or the docs: they already list the buttons without the triggers, and gamepad_tap's "lt or rt" stays true.

Tests

  • New quick check gamepad set all or nothing (Cli/GamepadChecks.cs, SetAllOrNothing). The stand-in FakePad now keeps what was set until a report sends it, like ViGEm's pad with AutoSubmitReport off, and keeps each report it sent. Refused sets for an unknown button, an unknown axis after a good button and axis, an unknown trigger after a good axis and trigger, and a trigger named as a button leave nothing for the next report; the unknown-button error no longer offers lt or rt; the next valid set sends only its own change, in one report; a full set sends buttons, axes and triggers together; gamepad_tap l2 pulls the left trigger to 255 and releases it. No controller is plugged in.
  • Mutations (each built in Debug and run through selftest --quick): 7 of 7 caught (buttons, axes or triggers set as they are read, which is the old code for each; the trigger message reverted; lt or rt back in the button list; Tap's old trigger test; a valid set never submitted). Every restore byte-identical.
  • Second review (a fresh Claude subagent, read-only): no P1. Its P2: the 0.11.3 release notes and validation record ([compass] Anode 0.11.3 candidate: ship the merged fixes, and move the registry off 0.11.0 #47) didn't list this fix; f2ef33f adds it there (and [compass] Fix: a refused Reconnect sets a running seat to error, so display reconnects answer "stopped" and the next disconnect drops the seat #48 did the same for itself), with the quick-check count now 110. Its P3s applied in 714100f (the stand-in, the tap check, a vacuous assertion, the CLI-true trigger message, this description's reset/detach sentence); not taken: Tap resolving the trigger once (kept the diff small).
  • Codex GitHub connector: 👍, no findings on 6e0e297.
  • Merged with main after [compass] Fix: a refused Reconnect sets a running seat to error, so display reconnects answer "stopped" and the next disconnect drops the seat #48 (f6e4c8c); the mutations were re-run on the final code: 7 of 7 caught.
  • FOCUS test command (scripts\build.ps1 -QuickTest -OutputDirectory artifacts\pkg-build): passed on the final head with main merged in: Release publish, 110/110 quick checks, docs consistent.
  • Debug selftest --quick: 109/109 before main was merged in.

Manual check in a real seat

None needed for the fix itself: the stand-in models the one ViGEm behaviour it relies on (sets wait for SubmitReport when AutoSubmitReport is off). The full self-test's "virtual gamepad" check (a real ViGEm pad, machine-wide) still runs the same Apply call when someone runs it with a gamepad; agents don't.

CHANGELOG, release notes and the 0.11.3 validation record: one entry each, since 0.11.3 isn't released yet.

🤖 Generated with Claude Code

…ort (B-041)

GamepadManager.Apply set each button, axis and trigger as it read them.
The pads run with AutoSubmitReport off, so when a later name was unknown
the call threw before SubmitReport and the earlier changes stayed in the
pending report, for the agent's next gamepad_set or gamepad_tap to send.
Every name is now resolved first; the changes are set and submitted
together.

A trigger passed as a button got "Unknown gamepad button 'lt'. Use ...
lt or rt."; it is now told it is a trigger. gamepad_tap recognises the
trigger names the triggers take (l2, r2). The stand-in pad in the quick
checks now keeps what was set until a report sends it, and the new check
"gamepad set all or nothing" covers all three.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-15T22:45:44.940495Z 6e0e297 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

skulitom and others added 4 commits October 15, 2026 23:56
…r message

From #49's second review: ViGEm's Equals(object) compares type and index,
so the stand-in keys by control alone; it keeps each sent report so the
check proves a tapped trigger was pulled to 255, and counts reports instead
of an Unsent test that is always false after a submit; the trigger error
says gamepad_set's triggers are an MCP option.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From #49's second review: the 0.11.3 candidate's record (#47) now names
this fix, its stand-in coverage limit and the 110 quick checks after #48
and #49.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… behind

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@skulitom
skulitom merged commit 44fe0d6 into main Oct 15, 2026
2 checks passed
@skulitom
skulitom deleted the compass/anode-fix-2026-10-15-gamepad-set-atomic branch October 15, 2026 23:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant