Fix OTF aiming at field walls with velocity clamping#554
Conversation
When strafing against a wall, wheel odometry reports velocity into the wall even though the robot isn't moving that direction. This causes the OTF solver to lead shots based on phantom motion. Fix by zeroing the velocity component pointing off-field when the robot pose is within bumper-width of a wall boundary. Closes #553 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR addresses incorrect on-the-fly (OTF) shot leading when the drivetrain is pressed against a field wall by clamping “phantom” off-field velocity components near field boundaries, and then using those corrected speeds in OTF future-pose and hub angular velocity calculations.
Changes:
- Added
Swerve.getWallCorrectedChassisSpeeds()to zero field-relative velocity components that point out of bounds when near a wall. - Updated
Swerve.getFuturePoseFromTime()to use wall-corrected chassis speeds for OTF pose prediction. - Updated
Cannon.getHubAngularVelocity(...)to use wall-corrected chassis speeds; addedFieldConstants.FIELD_LENGTH/FIELD_WIDTH.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
src/main/java/frc/robot/subsystems/Swerve.java |
Introduces wall-aware chassis-speed clamping and uses it for OTF future pose prediction. |
src/main/java/frc/robot/subsystems/Cannon.java |
Switches hub angular velocity computation to use wall-corrected drivetrain speeds. |
src/main/java/frc/robot/constants/FieldConstants.java |
Adds explicit field X/Y extents for wall boundary checks. |
| // When the robot is pressed against a field wall, wheels spin but the robot | ||
| // doesn't actually move into the wall. This zeros the velocity component | ||
| // pointing off-field so the OTF solver doesn't lead shots based on phantom motion. | ||
| private static final double WALL_MARGIN = 0.20; // meters (~8 in, bumper width + tolerance) | ||
|
|
There was a problem hiding this comment.
WALL_MARGIN is compared against the robot pose translation, which is typically the robot’s center of rotation. With DriveConstants.RobotWidth = 30 in (half-width ~0.38 m), a margin of 0.20 m likely won’t trigger until the robot center is unrealistically close to/through the wall, so the clamping may never activate when actually pressed against a wall. Consider basing the margin on half the robot’s footprint (e.g., RobotWidth/2 plus tolerance) or explicit bumper-to-center X/Y extents.
| double robotTargetAngle = getTargetAngle(pose).in(Radians); | ||
|
|
||
| Translation2d fieldRelativeVelocity = new Translation2d(drivetrain.getCurrentRobotChassisSpeeds().vxMetersPerSecond, drivetrain.getCurrentRobotChassisSpeeds().vyMetersPerSecond).rotateBy(drivetrain.getPose().getRotation()); | ||
| Translation2d fieldRelativeVelocity = new Translation2d(drivetrain.getWallCorrectedChassisSpeeds().vxMetersPerSecond, drivetrain.getWallCorrectedChassisSpeeds().vyMetersPerSecond).rotateBy(drivetrain.getPose().getRotation()); | ||
|
|
||
| double hubRotation = (-fieldRelativeVelocity.getX() * Math.sin(robotTargetAngle) + fieldRelativeVelocity.getY() * Math.cos(robotTargetAngle)) / getTargetDistance().in(Meters); |
There was a problem hiding this comment.
getHubAngularVelocity(Pose2d pose) mixes the passed-in pose (used for getTargetAngle(pose)) with the current distance (getTargetDistance()), which can be inconsistent—especially when this is called with a future-predicted pose during OTF. Use getTargetDistance(pose) so the geometry matches the provided pose. Also, getWallCorrectedChassisSpeeds() is called twice in the same expression; store it in a local variable to avoid potential intra-cycle inconsistencies and extra work.
| public static final double FIELD_MIDDLE_Y = 4.034663; | ||
|
|
||
| public static final double FIELD_LENGTH = 16.540988; // meters (X extent) | ||
| public static final double FIELD_WIDTH = 8.069326; // meters (Y extent) |
There was a problem hiding this comment.
FIELD_LENGTH/FIELD_WIDTH introduce a second source of truth for the same dimensions already hard-coded elsewhere in this file (e.g., FIELD). To avoid future drift, consider defining the field dimensions in one place (have FIELD and other rectangles reference these constants, or derive these constants from FIELD).
Summary
getWallCorrectedChassisSpeeds()toSwervethat zeros the velocity component pointing off-field when the robot is within ~8 inches of a wall boundarygetFuturePoseFromTime()and Cannon's hub angular velocity now use corrected speedsgetCurrentRobotChassisSpeeds()is unchanged — PathPlanner and other consumers are unaffectedProblem
When strafing against a field wall, wheel odometry reports velocity into the wall (wheels spin but robot doesn't move). The OTF solver uses this phantom velocity to predict future pose, causing it to lead shots incorrectly.
Approach
Wall-aware clamping is preferred over pose differentiation because it:
Test plan
Closes #553
🤖 Generated with Claude Code