Skip to content

tank.hpp

TankKinematics — a 2-wheel differential (skid-steer) drive.

This header declares 1 type (6 members).

Extracted from include/shulib/kinematics/tank.hpp — this page is that header's documentation, reformatted, so it cannot disagree with the code. Prose about how to think about the API lives in the user guide; worked recipes live in the cookbook; this page is the complete, mechanical list of what exists.

Contents

class TankKinematics

class TankKinematics final : public IKinematics

A 2-wheel differential (skid-steer) drive. Canonical wheel order is 0 = LEFT, 1 = RIGHT, in F1's body frame (+X forward, +Y left, ω CCW-positive). Deliberately a hand-written IKinematics rather than a MatrixKinematics preset: tank's strafe column is all-zero, so it is rank-2 and the holonomic engine's full-rank precondition correctly refuses it. Pure geometry — no state, no clock, no HAL; the only thing it knows is the track width.

class, declared at include/shulib/kinematics/tank.hpp:32.

TankKinematics::TankKinematics

explicit TankKinematics(units::Length trackWidth)

trackWidth = lateral distance between the left and right wheel contact lines.

function, declared at include/shulib/kinematics/tank.hpp:35.

TankKinematics::toWheels

[[nodiscard]] WheelSpeeds toWheels(const math::ChassisSpeeds& body) const override

Inverse kinematics: left = vx − ω·halfTrack, right = vx + ω·halfTrack, both in in/s, from a BODY-frame twist. The commanded vy is SILENTLY IGNORED — not clamped, not an error — because a tank cannot strafe and strafeAuthority() == 0 is how the motion layer is meant to have zeroed it already. Nothing here limits speed (§13 #5); that is desaturate()'s job.

function, declared at include/shulib/kinematics/tank.hpp:43.

TankKinematics::forward

[[nodiscard]] math::Twist2d forward(const WheelSpeeds& wheels) const override

Forward kinematics for odometry: vx = (left + right)/2, ω = (right − left)/(2·halfTrack), and vy ALWAYS exactly 0 — this drivetrain cannot observe lateral motion, so a real skid sideways is reported as no motion at all. Exact left-inverse of toWheels() on the achievable (vx, ω) subspace, so forward(toWheels(t)) returns t only when t's vy was already 0. Precondition: exactly 2 wheels, in the canonical left-then-right order.

function, declared at include/shulib/kinematics/tank.hpp:57.

TankKinematics::desaturate

[[nodiscard]] WheelSpeeds desaturate(const WheelSpeeds& wheels, units::Velocity maxWheelSpeed) const override

The uniform scale (desaturateUniform): if either wheel is over maxWheelSpeed, BOTH are scaled by the same factor, which preserves left:right and therefore the arc the command describes — the robot follows the same curve, more slowly. Returned unchanged when already within budget; never scales up. Precondition: maxWheelSpeed > 0.

function, declared at include/shulib/kinematics/tank.hpp:70.

TankKinematics::strafeAuthority

[[nodiscard]] double strafeAuthority() const override

Always 0: no lateral authority whatever, so the motion layer's |body vy| ≤ strafeAuthority()·maxLinearSpeed clamp reduces to "vy must be 0" here.

function, declared at include/shulib/kinematics/tank.hpp:77.

TankKinematics::wheelCount

[[nodiscard]] int wheelCount() const override

Always 2 — the left and right KINEMATIC wheels. How many physical motors sit on each side is the HAL's business; kinematics sees one speed per side.

function, declared at include/shulib/kinematics/tank.hpp:81.

Design commentary, from the header

The header opens with the reasoning behind these shapes. It is reproduced here in full because a reference that only lists signatures teaches nobody why.

The header’s own reasoning — 15 lines
 TankKinematics — a 2-wheel differential (skid-steer) drive. Body frame per F1
 (frame.hpp): +X = FORWARD, +Y = left, ω CCW-positive. The wheels drive along +X
 (forward); a commanded vy (strafe) is physically unachievable and is ignored —
 strafeAuthority() = 0 (= max sustainable |vy|/|vx|, §13 #5).

 This is a DEDICATED impl, NOT a MatrixKinematics preset: tank is rank-2 (its
 strafe column is all-zero), so the holonomic engine's full-rank precondition
 correctly excludes it. The closed form (halfTrack = trackWidth/2):

   left  = vx − ω·halfTrack          right = vx + ω·halfTrack
   vx = (left + right)/2             ω    = (right − left)/(2·halfTrack)

 ω·halfTrack is the sanctioned radian-drop (rad/s·in → in/s); the inverse
 re-attaches it. Canonical wheel order: 0 = left, 1 = right.

Last updated 2026-08-15 — published from the main release branch, commit fa545ff. This site tracks releases, so between them it can lag the repository — that lag is deliberate, and it is what keeps the development log off the public site. For the current state of the code, see the repository.