From 8089a92937a351335b127a6889392a6189aaa382 Mon Sep 17 00:00:00 2001 From: saltyeg <31107399+saltyeg@users.noreply.github.com> Date: Wed, 28 Oct 2026 22:22:09 -0700 Subject: [PATCH] Deliver buttons a script presses by their PlayStation names Gamepad.copy() carries only the Xbox button names and sets the PlayStation ones from them, so a script that set triangle, cross, share and so on was silently ignored. Each PlayStation name is now ORed into its Xbox twin on a staging copy before delivery, so the OpMode sees the button under both names, as on the robot; the script's own gamepad is not changed, so releasing the button releases it. --- .../simloop/opmode/GamepadScript.java | 4 +- .../simloop/opmode/OpModeContext.java | 27 +++++++++++- .../opmode/IterativeOpModeRunnerTest.java | 42 +++++++++++++++++++ 3 files changed, 70 insertions(+), 3 deletions(-) diff --git a/SimLoop/src/main/java/org/horizon36596/simloop/opmode/GamepadScript.java b/SimLoop/src/main/java/org/horizon36596/simloop/opmode/GamepadScript.java index 0cb8a1d..b13b34f 100644 --- a/SimLoop/src/main/java/org/horizon36596/simloop/opmode/GamepadScript.java +++ b/SimLoop/src/main/java/org/horizon36596/simloop/opmode/GamepadScript.java @@ -9,7 +9,9 @@ * *
The two gamepads passed in are the same objects on every tick, so a button stays held until the script * releases it, exactly like a real one. Write only fields ({@code gamepad1.left_stick_y = -1f}, - * {@code gamepad1.a = true}); do not keep references to them outside the call. + * {@code gamepad1.a = true}); do not keep references to them outside the call. A button can be pressed by either + * of its names ({@code a} or {@code cross}, {@code y} or {@code triangle}, {@code back} or {@code share}, ...): the + * OpMode sees both names pressed, as it would on the robot. * *
A script must be a pure function of its arguments (and of state it owns) for a run to be deterministic.
*/
diff --git a/SimLoop/src/main/java/org/horizon36596/simloop/opmode/OpModeContext.java b/SimLoop/src/main/java/org/horizon36596/simloop/opmode/OpModeContext.java
index 32352be..1c6582f 100644
--- a/SimLoop/src/main/java/org/horizon36596/simloop/opmode/OpModeContext.java
+++ b/SimLoop/src/main/java/org/horizon36596/simloop/opmode/OpModeContext.java
@@ -32,6 +32,9 @@ final class OpModeContext {
/** The operator's gamepad as the Driver Station holds it. */
final Gamepad driverStationGamepad2 = new Gamepad();
+ /** Where a script's gamepad is staged on its way to the OpMode, with its PlayStation names folded in. */
+ private final Gamepad staged = new Gamepad();
+
/** Sim time, in seconds, at which {@code getRuntime()} reads zero. */
private double runtimeOriginSeconds;
@@ -73,6 +76,26 @@ TelemetryRewiring inject(HardwareMap hardwareMap, RecordingTelemetry telemetry,
return rewiring;
}
+ /**
+ * Copies a script's gamepad into the OpMode's the way the SDK delivers Driver Station data, with the buttons
+ * the script pressed by their PlayStation names counted too. {@code Gamepad.copy} carries only the Xbox names
+ * ({@code toByteArray}) and sets the PlayStation ones from them ({@code updateButtonAliases}), so on its own it
+ * drops {@code triangle = true}: a real Driver Station has one button there, sent under its Xbox name. Each
+ * PlayStation name is ORed into its Xbox twin on a staging copy, never on the script's gamepad, so a button
+ * the script releases is released. The pairs are the SDK's ({@code updateButtonAliases}, RobotCore 12.0.0).
+ */
+ private void deliver(Gamepad script, Gamepad toOpMode) {
+ staged.copy(script);
+ staged.a |= script.cross;
+ staged.b |= script.circle;
+ staged.x |= script.square;
+ staged.y |= script.triangle;
+ staged.back |= script.share;
+ staged.start |= script.options;
+ staged.guide |= script.ps;
+ toOpMode.copy(staged);
+ }
+
/** Makes {@code getRuntime()} and {@code time} count from the sim clock's current reading: INIT. */
void startRuntimeHere() {
runtimeOriginSeconds = timer.time();
@@ -96,8 +119,8 @@ void markStopRequested() {
* SDK's edge detection), and {@code time} and {@code getRuntime()} are set to the sim runtime.
*/
void beforeUserCode() {
- opMode.gamepad1.copy(driverStationGamepad1);
- opMode.gamepad2.copy(driverStationGamepad2);
+ deliver(driverStationGamepad1, opMode.gamepad1);
+ deliver(driverStationGamepad2, opMode.gamepad2);
double runtimeSeconds = timer.time() - runtimeOriginSeconds;
opMode.time = runtimeSeconds;
opMode.resetRuntime();
diff --git a/SimLoop/src/test/java/org/horizon36596/simloop/opmode/IterativeOpModeRunnerTest.java b/SimLoop/src/test/java/org/horizon36596/simloop/opmode/IterativeOpModeRunnerTest.java
index af01e4a..858f99b 100644
--- a/SimLoop/src/test/java/org/horizon36596/simloop/opmode/IterativeOpModeRunnerTest.java
+++ b/SimLoop/src/test/java/org/horizon36596/simloop/opmode/IterativeOpModeRunnerTest.java
@@ -192,6 +192,48 @@ void theScriptDrivesTheGamepadsTickByTickAndEdgeDetectionSeesOnePressPerPress()
assertEquals(2, opMode.presses, "aWasPressed() fires once per press, not once per tick held");
}
+ /** Records each tick what the OpMode sees of one button, under both its names. */
+ static class TriangleReader extends OpMode {
+ final List