sim.live and the keyboard window¶
org.horizon36596.simloop.sim.live, and the separate module SimLoop/desktop/
(org.horizon36596.simloop.desktop)
Drive the simulated robot from your laptop keyboard, record the input for each tick, and replay it
headless to reproduce the robot-authored log values. Compare decoded values with RlogDecodedCompare;
raw RLOG bytes and PsiKit's writer metadata need not match.
What you use¶
| Type | What it is |
|---|---|
GamepadInputSource |
Where each loop's gamepads come from: a keyboard, a recorded track, or a test's script. |
KeyboardGamepadSource |
Turns held keys into a gamepad through a fixed keymap. One keyboard drives gamepad 1 or gamepad 2. |
GamepadInputTrack |
A recorded session: one plain-text line per loop. read(path), replay(), and a Writer. |
RecordingGamepadInputSource |
Wraps a source and writes every frame to a track before handing it on. |
GamepadInputGate |
Plugs a source into ScenarioRunner.runUntil: the run lasts as long as the source has input. |
GamepadState |
One gamepad on one loop, as a value. applyTo(gamepad) delivers it the way a Driver Station packet does. |
RealTimePacer |
Slows a run to real time so a person can drive it. The only class in SimLoop that reads a wall clock. |
KeyboardWindow (in desktop/) |
A small AWT window that reports which keys are held while it has focus. |
A session and its replay¶
From src/test/java/org/horizon36596/simloop/sim/live/LiveSessionReplayTest.java. A live session wraps a
keyboard source in a recorder and paces the run; a replay reads the track back and runs as fast as it can.
try (GamepadInputTrack.Writer writer = new GamepadInputTrack.Writer(track,
Collections.singletonMap("opMode", "ToyRobot"), true)) {
GamepadInputSource keyboard = new RecordingGamepadInputSource(
new KeyboardGamepadSource(new ScriptedKeyboard(), 10_000), writer);
liveTicks = runToyRobot(keyboard, liveRlog, pacerOnATestClock(),
Collections.singletonList(countingReceiver));
}
GamepadInputTrack recorded = GamepadInputTrack.read(track);
int replayTicks1 = runToyRobot(recorded.replay(), replay1, ScenarioRunner.NO_PACING,
Collections.<LogDataReceiver>emptyList());
and the run itself:
GamepadInputGate gamepads = new GamepadInputGate(input);
return ScenarioRunner.runUntil("ToyRobot", STUB_CONFIG, timer, DT, rlog, gamepads, pacer, extraReceivers,
deltaTime -> {
gamepads.current().gamepad1.applyTo(gamepad1);
gamepads.current().gamepad2.applyTo(gamepad2);
// ... your robot code reads gamepad1 and gamepad2 ...
});
The test then checks that the live log and both replays decode identically, field for field. In a real
session, use new RealTimePacer() in place of the test's pacer, and a real keyboard in place of
ScriptedKeyboard.
The keymap¶
| Keys | Gamepad |
|---|---|
W S / A D |
left stick y / x (W is stick up, which is negative y, as on a real gamepad) |
I K / J L |
right stick y / x |
Z / C |
left / right trigger |
| arrow keys | d-pad |
1 2 3 4 |
a b x y (cross, circle, square, triangle) |
Q / E |
left / right bumper |
F / H |
left / right stick button |
ENTER / BACKSPACE |
start / back |
SHIFT held |
every axis at half travel |
TAB |
switch the keys between gamepad 1 and gamepad 2 |
ESCAPE |
end the session |
Using the keyboard window¶
KeyboardWindow comes with the starter folder: simloop-starter/simloop.gradle already has the line
that adds it to your tests. It works on Windows, macOS and Linux. If you installed SimLoop by hand, add
the same line yourself, in TeamCode/build.gradle:
It is the same coordinate as the library with a desktop classifier: a second jar, kept apart because
the window uses the desktop JDK's window toolkit (AWT), which Android code compiles without. Then hand it
to KeyboardGamepadSource. Its public API is strings and booleans, so no AWT type reaches your Android
test sources:
KeyboardWindow window = KeyboardWindow.open("SimLoop live drive", helpLines);
KeyboardGamepadSource.Keyboard keys = new KeyboardGamepadSource.Keyboard() {
@Override public Set<String> keysDown() { return window.keysDown(); }
@Override public boolean isOpen() { return window.isOpen(); }
};
GamepadInputSource source = new KeyboardGamepadSource(keys, 15_000); // 5 min at 50 Hz, at most
Run the live session from a test on a machine with a display. KeyboardWindow.open throws
IllegalStateException on a headless machine such as CI; replay a recorded track there instead.
Units¶
- A track line is one loop, not a span of time. A replay must run at the same tick as the session.
The track does not check this, so put the tick in its header metadata (
# tickSeconds: 0.02) and read it back frommetadata(). - Axis values: unitless, sticks in
[-1, 1], triggers in[0, 1], written withFloat.toStringso they read back to the same bits. RealTimePacer.MAX_LAG_BEFORE_RESET_SECONDS: 0.25 s. A run that falls further behind real time than this stops trying to catch up, andlagResets()counts how often that happened.
What this package will not do¶
- It will not read a real game controller. Keyboard only. A controller needs a native input library.
- It will not make a paced run differ from its replay. The pacer only waits; it never changes what a tick computes. That is the point, and a test pins it.
- It will not filter key auto-repeat on Linux (X11). A held key can read as up for the odd loop. The
recording is still what the sim saw.
xset r offwhile driving avoids it. - It will not open a window in CI.