Skip to content

test(virtual): add depth tests for vJoy, vXBox, axis output, FFB passthrough#53

Open
EffortlessSteven wants to merge 3 commits intomainfrom
feat/virtual-device-depth
Open

test(virtual): add depth tests for vJoy, vXBox, axis output, FFB passthrough#53
EffortlessSteven wants to merge 3 commits intomainfrom
feat/virtual-device-depth

Conversation

@EffortlessSteven
Copy link
Member

@EffortlessSteven EffortlessSteven commented Mar 1, 2026

Summary

Add 51 depth tests for the virtual device subsystem in \crates/flight-virtual/tests/depth_tests.rs.

Test categories

Category Tests What's covered
Device creation 8 Virtual joystick, Xbox controller emulation, device properties (axes/buttons/hats), multiple simultaneous devices, multiple backend instances, cleanup on drop (vJoy + uinput), disconnect/reconnect
Axis output 10 Full range [-1.0, 1.0], out-of-range clamping (positive/negative, mock + vJoy), 16-bit resolution (vJoy + uinput), multiple axes, rapid updates (10K writes), deadzone+scale mapping, all merge strategies
Button output 7 Press/release, multiple simultaneous, state tracking via VirtualDevice, toggle mode, direct/momentary mode, pulse mode, button remapping
Hat switch output 6 All 8-way POV positions, center/neutral, position transitions, HID encoding round-trip, mapper passthrough, vJoy all directions
FFB passthrough 5 Receive effects via output reports, emulator queue lifecycle (create/update/destroy), capability reporting, disconnect rejection
Error handling 10 Driver not available, slot occupied + fallback, vJoy double-acquire, permission denied guidance, not-acquired guards, invalid IDs, double release, manager duplicate/unknown device, unknown report type, empty report
Output pipeline 4 Controller-to-frame round trip, smoothing dampening, rate limiting, emulated device axis parsing

Verification

  • \cargo test -p flight-virtual --test depth_tests\ — 51 passed, 0 failed
  • \cargo clippy -p flight-virtual -- -D warnings\ — clean

- VirtualController with axis/button/hat management and snapshots
- VirtualOutput with smoothing and rate-limited frame generation
- EmulatedDevice for HID device emulation in testing
- 35+ tests covering all virtual device functionality

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 1, 2026 02:18
@gemini-code-assist
Copy link

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@chatgpt-codex-connector
Copy link

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@coderabbitai
Copy link

coderabbitai bot commented Mar 1, 2026

Warning

Rate limit exceeded

@EffortlessSteven has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 27 minutes and 35 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between cd2e54d and 16ff848.

📒 Files selected for processing (5)
  • crates/flight-virtual/src/device_emulator.rs
  • crates/flight-virtual/src/lib.rs
  • crates/flight-virtual/src/virtual_controller.rs
  • crates/flight-virtual/src/virtual_output.rs
  • crates/flight-virtual/tests/depth_tests.rs
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/virtual-device-depth

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@qodo-free-for-open-source-projects

Review Summary by Qodo

Add virtual controller, output, and device emulator infrastructure

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Adds virtual controller with configurable axes, buttons, and hat switches
• Implements virtual output frame generation with rate limiting and smoothing
• Provides HID device emulator for testing without physical hardware
• Includes 35+ comprehensive tests covering all virtual device functionality
Diagram
flowchart LR
  VC["VirtualController<br/>axes/buttons/hats"] -- "snapshot()" --> CS["ControllerSnapshot"]
  CS -- "compute_frame()" --> VO["VirtualOutput<br/>rate limiting/smoothing"]
  VO -- "OutputFrame" --> ED["EmulatedDevice<br/>HID emulation"]
  ED -- "inject_input()" --> ED
  ED -- "enqueue_output()" --> ED
Loading

Grey Divider

File Changes

1. crates/flight-virtual/src/virtual_controller.rs ✨ Enhancement +280/-0

Virtual input controller with state management

• Introduces VirtualController struct with configurable axes, buttons, and hat switches
• Provides methods to set/get individual input elements with bounds checking
• Implements snapshot() for immutable state capture at a point in time
• Includes 16 tests covering initialization, clamping, state management, and edge cases

crates/flight-virtual/src/virtual_controller.rs


2. crates/flight-virtual/src/virtual_output.rs ✨ Enhancement +288/-0

Virtual output with rate limiting and smoothing

• Implements VirtualOutput for frame generation with rate limiting
• Adds optional low-pass smoothing filter for axis values with configurable alpha
• Produces OutputFrame with smoothed axes, buttons, hats, and timestamps
• Includes 10 tests covering rate limiting, smoothing convergence, and state reset

crates/flight-virtual/src/virtual_output.rs


3. crates/flight-virtual/src/device_emulator.rs ✨ Enhancement +300/-0

HID device emulator for testing

• Creates EmulatedDevice for HID device emulation with configurable VID/PID
• Parses raw input reports into normalized axis values in range [-1.0, 1.0]
• Manages output report queue for force-feedback and LED commands
• Includes 11 tests covering device identification, axis parsing, and report handling

crates/flight-virtual/src/device_emulator.rs


View more (1)
4. crates/flight-virtual/src/lib.rs ✨ Enhancement +7/-0

Module exports for virtual device infrastructure

• Exports three new modules: device_emulator, virtual_controller, virtual_output
• Re-exports public types: EmulatedDevice, EmulatedDeviceConfig, VirtualController,
 VirtualControllerConfig, VirtualOutput, VirtualOutputConfig, ControllerSnapshot,
 OutputFrame
• Makes virtual device infrastructure available to library consumers

crates/flight-virtual/src/lib.rs


Grey Divider

Qodo Logo

@qodo-free-for-open-source-projects
Copy link

qodo-free-for-open-source-projects bot commented Mar 1, 2026

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Axis shrink panics/stales🐞 Bug ✓ Correctness
Description
VirtualOutput never shrinks smoothed_axes; if a later snapshot has fewer axes than a previous
one, the non-smoothing path can panic (copy_from_slice length mismatch) and the smoothing path can
emit extra stale axes beyond the snapshot length.
Code

crates/flight-virtual/src/virtual_output.rs[R98-113]

+        // Grow smoothed state to match snapshot.
+        if self.smoothed_axes.len() < snapshot.axes.len() {
+            self.smoothed_axes.resize(snapshot.axes.len(), 0.0);
+        }
+
+        let axes = if self.config.smoothing_enabled {
+            let alpha = self.config.smoothing_alpha.clamp(0.0, 1.0);
+            for (smoothed, &raw) in self.smoothed_axes.iter_mut().zip(snapshot.axes.iter()) {
+                *smoothed += alpha * (raw - *smoothed);
+            }
+            self.smoothed_axes.clone()
+        } else {
+            // Copy raw values into smoothed state so a later enable is seamless.
+            self.smoothed_axes.copy_from_slice(&snapshot.axes);
+            snapshot.axes.clone()
+        };
Evidence
smoothed_axes is only resized when it is shorter than the snapshot; it is never truncated when the
snapshot is shorter. In the non-smoothing branch, copy_from_slice requires equal-length slices and
will panic if smoothed_axes.len() > snapshot.axes.len(). In the smoothing branch, iterating with
zip updates only the first snapshot.axes.len() elements, but returning
self.smoothed_axes.clone() returns the full (possibly longer) vector, leaking stale trailing axis
values.

crates/flight-virtual/src/virtual_output.rs[85-113]
crates/flight-virtual/src/virtual_controller.rs[35-44]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`VirtualOutput::compute_frame` maintains `smoothed_axes` that only grows. If `snapshot.axes.len()` decreases across calls, the non-smoothing path can panic (`copy_from_slice` requires equal lengths), and the smoothing path can return extra stale trailing axes.
### Issue Context
This is an API-facing function taking `&amp;amp;amp;ControllerSnapshot` (a public struct with `Vec` fields), so callers can legally provide snapshots with varying axis counts.
### Fix Focus Areas
- crates/flight-virtual/src/virtual_output.rs[85-113]
- crates/flight-virtual/src/virtual_output.rs[131-288]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Backlog emits same timestamp 🐞 Bug ✓ Correctness
Description
Rate limiting subtracts min_interval only once; after a large dt_s, accumulated_time may still
be >= min_interval. If the caller then calls again with dt_s=0, additional frames can be emitted
with the same timestamp because timestamp is driven only by total_time += dt_s.
Code

crates/flight-virtual/src/virtual_output.rs[R90-120]

+        self.accumulated_time += dt_s;
+        self.total_time += dt_s;
+
+        if self.accumulated_time < self.min_interval {
+            return None;
+        }
+        self.accumulated_time -= self.min_interval;
+
+        // Grow smoothed state to match snapshot.
+        if self.smoothed_axes.len() < snapshot.axes.len() {
+            self.smoothed_axes.resize(snapshot.axes.len(), 0.0);
+        }
+
+        let axes = if self.config.smoothing_enabled {
+            let alpha = self.config.smoothing_alpha.clamp(0.0, 1.0);
+            for (smoothed, &raw) in self.smoothed_axes.iter_mut().zip(snapshot.axes.iter()) {
+                *smoothed += alpha * (raw - *smoothed);
+            }
+            self.smoothed_axes.clone()
+        } else {
+            // Copy raw values into smoothed state so a later enable is seamless.
+            self.smoothed_axes.copy_from_slice(&snapshot.axes);
+            snapshot.axes.clone()
+        };
+
+        Some(OutputFrame {
+            axes,
+            buttons: snapshot.buttons.clone(),
+            hats: snapshot.hats.clone(),
+            timestamp: self.total_time,
+        })
Evidence
total_time advances only by dt_s, while accumulated_time is reduced by only one interval per
emitted frame. This allows multiple emissions across subsequent calls without time progressing,
producing duplicate timestamps (and potentially bursty emission) when callers try to drain
accumulated time with dt_s=0.

crates/flight-virtual/src/virtual_output.rs[90-97]
crates/flight-virtual/src/virtual_output.rs[115-120]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The rate limiter can leave `accumulated_time` &amp;amp;gt;= `min_interval` after emitting a frame. Subsequent calls with `dt_s=0` may emit more frames without advancing `total_time`, yielding duplicate timestamps.
### Issue Context
`compute_frame` returns only a single `OutputFrame` per call (`Option&amp;amp;lt;OutputFrame&amp;amp;gt;`), so it cannot naturally emit multiple frames to catch up in a single invocation.
### Fix Focus Areas
- crates/flight-virtual/src/virtual_output.rs[90-120]
- crates/flight-virtual/src/virtual_output.rs[131-288]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment on lines +98 to +113
// Grow smoothed state to match snapshot.
if self.smoothed_axes.len() < snapshot.axes.len() {
self.smoothed_axes.resize(snapshot.axes.len(), 0.0);
}

let axes = if self.config.smoothing_enabled {
let alpha = self.config.smoothing_alpha.clamp(0.0, 1.0);
for (smoothed, &raw) in self.smoothed_axes.iter_mut().zip(snapshot.axes.iter()) {
*smoothed += alpha * (raw - *smoothed);
}
self.smoothed_axes.clone()
} else {
// Copy raw values into smoothed state so a later enable is seamless.
self.smoothed_axes.copy_from_slice(&snapshot.axes);
snapshot.axes.clone()
};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Axis shrink panics/stales 🐞 Bug ✓ Correctness

VirtualOutput never shrinks smoothed_axes; if a later snapshot has fewer axes than a previous
one, the non-smoothing path can panic (copy_from_slice length mismatch) and the smoothing path can
emit extra stale axes beyond the snapshot length.
Agent Prompt
### Issue description
`VirtualOutput::compute_frame` maintains `smoothed_axes` that only grows. If `snapshot.axes.len()` decreases across calls, the non-smoothing path can panic (`copy_from_slice` requires equal lengths), and the smoothing path can return extra stale trailing axes.

### Issue Context
This is an API-facing function taking `&ControllerSnapshot` (a public struct with `Vec` fields), so callers can legally provide snapshots with varying axis counts.

### Fix Focus Areas
- crates/flight-virtual/src/virtual_output.rs[85-113]
- crates/flight-virtual/src/virtual_output.rs[131-288]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

- Resize smoothed_axes to match snapshot axis count each frame
- Truncate on shrink, extend with 0.0 on grow
- Add test for varying axis counts across frames

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

…through

Add 51 depth tests covering device creation, axis output, button modes, hat switches, FFB passthrough, error handling, and output pipeline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@EffortlessSteven EffortlessSteven changed the title feat(virtual): add virtual controller, output, and device emulator test(virtual): add depth tests for vJoy, vXBox, axis output, FFB passthrough Mar 1, 2026
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.

2 participants