> For the complete documentation index, see [llms.txt](https://yams.yassrobotics.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://yams.yassrobotics.com/changelog/readme.md).

# Changelog

New updates and improvements

## Unreleased

These changes are on `master` and will ship in the next release.

### Added

* `getPose3d()` on every mechanism (C++ `GetPose3d()`): its `Pose3d` relative to the robot, to publish as a component of an AdvantageScope robot model. It is worked out from the motor's position, so it works on the robot as well as in simulation: an arm's or pivot's axis turned to its angle, an elevator's carriage at its height, or a flywheel's axle turned by its position. `DoubleJointedArm.getUpperPose3d()` gives the upper arm's. See [How do I show my mechanisms on a 3D robot model?](https://yams.yassrobotics.com/documentation/how-to-simulation-and-logging/how-do-i-show-my-mechanisms-on-a-3d-robot-model).
* `MechanismPositionConfig.withRelativePosition(Transform3d)`, which also sets how the mechanism is turned where it is mounted, and `withParent(Supplier<Pose3d>)`, which mounts it on another mechanism, such as an arm on `elevator::getPose3d`. `getMountPose()`, `getRotatedPose(Angle)` and `getTranslatedPose(Distance, Angle)` do the math for parts YAMS has no mechanism for.
* **C++:** `ArmConfig`, `ElevatorConfig`, `PivotConfig` and `FlyWheelConfig` take a `MechanismPositionConfig` with `WithMechanismPositionConfig(...)`.
* `ElevatorConfig.withStages(Distance... stageHeights)` (C++ `WithStages(0.6_m, 0.6_m, 0.5_m)`): how far each of the elevator's moving stages travels relative to the stage below it, one height for each stage, the carriage last. `Elevator.getCascadePose3d()` and `getContinuousPose3d(boolean carriageFirst)` (C++ `GetCascadePose3d()` and `GetContinuousPose3d(bool carriageFirst)`) then give each stage's pose, from the stage nearest the fixed stage to the carriage: a cascade elevator's stages all move at once, each by its share of the carriage's height, and a continuous elevator's stages move one at a time, the carriage first or last. Both need 2 or more stages. The stage heights must add up to the elevator's maximum height, or the `Elevator` throws an `ElevatorConfigurationException`, and without hard limits they give the elevator's height, from 0 to their total (`getStageHeightTotal()`). See [Elevator stages](https://yams.yassrobotics.com/documentation/how-to-simulation-and-logging/how-do-i-show-my-mechanisms-on-a-3d-robot-model#elevator-stages).
* `YMotors.getThriftyPulsar(int)`: a `DCMotor` of the Thrifty Pulsar 775 from Thrifty's specs, since WPILib has none. `TalonFXSWrapper` drives it as `NEO_JST`.
* Swerve live tuning can drive the modules at a voltage, without their closed loops, to characterize them: `modules/drive/voltage/enabled` and `modules/drive/voltage/setpoint` while drive tuning is on, and `modules/azimuth/voltage/enabled` and `modules/azimuth/voltage/setpoint` while azimuth tuning is on, under `Tuning/<name>/`. A closed loop that runs on the SystemCore is stopped while its voltage is on. The YAGSL config generator's Live mode uses these for its **Auto** gains. See [Characterize the modules](https://yams.yassrobotics.com/documentation/understanding/live-tuning#characterize-the-modules).
* `SmartMotorController.getClosedLoopControllerPeriod()` (C++ `GetClosedLoopControllerPeriod()`): how often the closed loop runs, 1 ms on the motor controller or the closed loop control period on the SystemCore. Published as `closedloop/period` at `HIGH`, or with `withClosedLoopPeriod()` (`WithClosedLoopPeriod()`).
* `SwerveModule.getCouplingVelocity()` (C++ `GetCouplingVelocity()`) is public, and the C++ `SwerveDrive` has `ApplyTuningValues()`, as Java does.
* SPARK and TalonFX feedback and feedforward gains are checked with the vendor's status code when they change, retrying for up to 15 ms, and raise an alert if they still aren't applied. Only the SPARK's closed loop or feedforward config, or the TalonFX's slot, is sent, to use less of the CAN bus.

### Changed

* **Breaking:** the mechanisms' `getRelativeMechanismPosition()` is replaced by `getPose3d()` (C++: `GetRelativeMechanismPosition()` by `GetPose3d()`). The old method was read from the simulation's `Mechanism2d`, threw a `NullPointerException` on the robot, gave degrees to `Rotation3d` as radians, and turned the wrong way. `SmartMotorController.getRelativeMechanismPosition()`, which reads the relative encoder, is unchanged.
* **Breaking (C++):** `MechanismPositionConfig` is now the class that places a mechanism in 3D, as in Java. It was a named setpoint class that was declared but never defined.
* A `Pivot`'s pose turns in its movement plane: XY, like a turret, unless it is given a `MechanismPositionConfig`, which is XZ unless set otherwise. The C++ `PivotConfig` defaults to XY too, as the Java one does.
* **Behavior change:** stopping a `SmartMotorController`'s closed loop, as a mechanism does before driving it at a duty cycle or voltage, also stops its loosely coupled followers' closed loops and sets them to a duty cycle of 0. Starting it starts theirs (Java and C++).
* `TalonFXSWrapper` drives a NEO Vortex as `VORTEX_JST` rather than `NEO_JST`, as the C++ wrapper and YAMG already did.
* **Behavior change:** swerve live tuning applies only the values that changed, and sends the setpoints again when a tuning mode turns on or off. A tuning mode that is on turns off the ones it outranks (auto-align, then drive tuning, then azimuth tuning). Ending the tuning command turns every mode off and holds the wheels where they are.
* **Breaking (C++):** the swerve tuning command is published at `Mechanisms/<name>/tuning`, as in Java, rather than `Mechanisms/<name>/tuning/driveToPose`.

### Removed

* **Breaking:** `SmartMechanism.getRelativeMechanismPosition()` (Java) and `GetRelativeMechanismPosition()` on the C++ `Arm`, `Elevator`, `Pivot` and `FlyWheel`. Use `getPose3d()`.

### Fixed

* A loosely coupled follower whose closed loop runs on the SystemCore (a Thrifty Nova, a SPARK with an exponential profile, or any motor controller with an LQR) kept driving to its last setpoint after its leader stopped, including after a duty cycle of 0: its closed loop set its output again on the next loop.
* Simulation: a `Pivot` stops at its hard stops. It kept its speed when put back on a stop, and flew past again on the next loop. A pivot that wraps continuously (`withContinuousWrapping`) has no hard stops, and turns on past where it wraps (Java and C++).
* Simulation: a `Pivot` starts at its starting position. Its simulation started at 0, away from its encoder, so it was stopped at hard stops it had not reached (Java and C++).
* Simulation: a TalonFX or TalonFXS keeps an encoder position it is set to. It set the position before moving its simulated rotor, so a Talon at 60 degrees set to 0 read -60 (Java).
* `TalonFXSWrapper` threw `IllegalArgumentException("Unknown motor for TalonFXS...")` for a Thrifty Pulsar.
* C++ `cpptest` example: `projectYear` is `2027_alpha7`, to match its GradleRIO.

## 2026.10.15

See [Updating from v2026.10.09 to v2026.10.15](https://github.com/Yet-Another-Software-Suite/YAMS/blob/master/MIGRATION.md#updating-from-v20261009-to-v20261015) in the migration guide.

### Added

* `SwerveInputStream` aim options, carried over from YAGSL 2026: `withAimLookahead(Time)`, `withAimFeedforward(SimpleMotorFeedforward)`, `withAimHeadingOffset(Rotation2d)`, `isAimLocked(Angle)` and the `aimLock(Angle)` Trigger, `getTargetVector(Pose2d)` and `calculateAngularVelocity(Angle)`. Aiming without them behaves as before.
* `SwerveDrive.setAzimuthAngles(Rotation2d)`: turns every module to one angle without driving it. Azimuth live tuning uses it, so it turns the modules again.
* **C++:** the swerve drive matches the Java one: the 3D gyro and gyro heading axis, anti-tipping, module speed desaturation, the coupling ratio, feedforward forces and the default velocity deadband.
* Commands v2 `SwerveDrive.driveToPose(Pose2d, Distance, Angle)`, which ends once the robot is within the tolerances, and `isNear(Pose2d, Distance, Angle)` and `stop()`. These were only on the Commands v3 `SwerveDrive`; `isNear` and `stop` now live on the shared `SwerveDrive`.
* `SmartMechanism.getMeasurementSetpoint()`: the mechanism's position setpoint as a `Distance`, converted with the mechanism circumference. It is empty when there is no position setpoint or no circumference is configured.
* `EasyCRT.solve(Angle, Angle)` / `EasyCRT::Solve(...)`: solve for travel centered on zero, with a `solve(a, b, minAngle, maxAngle)` overload for other travel.
* `EasyCRT.getUniqueRange()` / `EasyCRT::GetUniqueRange()`: how far the shared gear can turn before the two encoder readings repeat.
* A `Turret` that seeds its motor from `EasyCRT` at startup, with a unit test, in the `BroncBotz_2026` examples for Commands v2 and Commands v3.

### Changed

* **Behavior change:** an `ArmFeedforward`'s kV and kA are per rotation everywhere. The closed loop on the SystemCore (such as the Thrifty Nova's) read them per radian. If you tuned an arm's kV or kA on the SystemCore with 2026.10.9, multiply them by 2π to keep the same behavior.
* **Breaking:** `SwerveModule.lock(Rotation2d)` is now `setAzimuthAngle(Rotation2d)`. It turns the wheel without driving it.
* The external encoder telemetry is only published when there is an external encoder. `externalencoder/position` and `externalencoder/velocity` keep their names.
* The vendordep JSONs require the matching WPILib command library (`command2` or `command3`).
* **Behavior change:** the Commands v2 `SwerveDrive.driveToPose(Pose2d)` now stops the drive when it ends or is interrupted, as the Commands v3 one already did. It used to leave the last commanded speeds in place.
* **Behavior change:** `withTemperatureCutoff` now follows where the closed loop runs, because the cutoff is only checked by the closed loop on the SystemCore (Java and C++). On a SPARK MAX or SPARK Flex it needs an exponential profile or an LQR controller: a trapezoidal profile with a cutoff used to be accepted and silently ignored, and now throws at startup. On a TalonFX or TalonFXS it is now accepted with an LQR controller, where it used to always throw.
* **Breaking:** `EasyCRT` is now built from three tooth counts and solves from two readings (Java and C++). The match tolerance is derived from the tooth counts, and a reading pair that does not agree returns an empty result.
* **Breaking (Java):** `DifferentialMechanism.set(...)` now takes `(tilt, twist)` like every other method, and mixes them with the same signs as `setPosition` (left = tilt + twist, right = tilt - twist). It took `(twist, tilt)` and drove the twist the opposite way. Swap the arguments if you called it as `(twist, tilt)`.
* `EasyCRTConfigGuide.md` is replaced by `EasyCRTGuide.md`, which explains how to choose tooth counts, zero and invert the encoders, handle gear trains, and check your margin yourself.
* `MIGRATION.md` now covers the whole 2026 to 2027 upgrade.

### Removed

* **Breaking:** `EasyCRTConfig`, including its gearing helpers, offset and inversion setters, match tolerance, and gear recommender, and the `EasyCRT` status, error and iteration getters.

### Fixed

* Simulated SPARKs follow their profile and feedforward. REVLib's simulation does not run MAXMotion, and works out kCos from the setpoint instead of the position, so in simulation a SPARK with a trapezoidal profile runs the profile on the SystemCore. On the robot it still uses MAXMotion. See [wpilibsuite/SystemcoreTesting#425](https://github.com/wpilibsuite/SystemcoreTesting/issues/425).
* Simulated SPARKs start at their starting position, and report their real voltage. Both were off by the gear ratio.
* Every motor controller gives the same voltage for the same kP, kI, kD, kS, kV, kA and kG in YAMS units, from every closed loop slot. This fixed an elevator feedforward under velocity control on the SystemCore, which left out kV.
* **C++:** swerve fixes the Java swerve already had, and swerve azimuth live tuning.
* `ExponentialProfilePIDController.createConstraints(maxVolts, maxVelocity, maxAcceleration)` passed kV and kA as state space terms, which gave an unstable profile with a negative maximum velocity. It now builds the same constraints as `SmartMotorControllerConfig.withExponentialProfile(Voltage, AngularVelocity, AngularAcceleration)`.
* Simulation: `Arm`, `Elevator`, `Pivot`, `FlyWheel`, `DoubleJointedArm` and `DifferentialMechanism` no longer replace the shared `BatterySim` voltage with one computed from their own current draw alone. The simulated battery now sags from every mechanism's combined draw, and `enableDischarge(...)` is respected, no matter which mechanism iterates last (Java and C++).
* `simple_robot-2027` examples: the simulated gyro yaw was set in radians instead of degrees, and the elevators were missing the carriage weight and starting position that `Elevator` requires in simulation.
* C++ `cpptest` example: the field-relative `SwerveInputStream` output is converted to robot-relative before `SwerveDrive::Drive`.
* `GearBox` Javadoc: `fromTeeth(...)` is a train of meshing gears, so `fromTeeth(12, 60, 12, 60)` is 5:1, not 25:1.

## 2026.10.9

### Added

* `NovaWrapper` (Java `yams.core.motorcontrollers.local.NovaWrapper`, C++ `yams/motorcontrollers/local/NovaWrapper.hpp`): a `SmartMotorController` for the Thrifty Nova, with simulation, telemetry, and alert support. Requires the ThriftyLib 2027 vendordep.
* `SmartMotorController.getRelativeMechanismPosition()` / `getRelativeMechanismVelocity()` (Java) and `GetRelativeMechanismPosition()` / `GetRelativeMechanismVelocity()` (C++): read only the relative (motor) encoder, even when an external encoder is configured. `getMechanismPosition()` / `getMechanismVelocity()` still prefer the external encoder.
* **C++:** `SmartMotorController`, `SmartMotorControllerConfig`, simulation, telemetry, `SparkWrapper`, `TalonFXWrapper`, and `TalonFXSWrapper` now match the Java API.

### Changed

* **Breaking:** `getExternalEncoderPosition()` / `getExternalEncoderVelocity()` renamed to `getExternalEncoderMechanismPosition()` / `getExternalEncoderMechanismVelocity()` (C++: `GetExternalEncoderMechanismPosition()` / `GetExternalEncoderMechanismVelocity()`).
* Alerts now refer to the SystemCore instead of the RIO.
* Maven artifacts (Java and C++) and the vendordep JSONs are now served from `https://cdn.yassrobotics.com/`, so networks that block `github.io` can still install and update YAMS. Reinstall the vendordep from the new URL:

  | Command framework | Vendordep URL                                      |
  | ----------------- | -------------------------------------------------- |
  | Commands v2       | `https://cdn.yassrobotics.com/yams_commands2.json` |
  | Commands v3       | `https://cdn.yassrobotics.com/yams_commands3.json` |

## 2026.10.8

### Added

* `SwerveInputStream.withTelemetry(String, TelemetryVerbosity)` (Java) / `SwerveInputStream::WithTelemetry(...)` (C++): publishes the stream's drive mode (`LOW`), its configuration (`MID`), and editable copies of the configuration plus a `Live Tuning` command (`HIGH`). The stream publishes every time it is read, so there is nothing to call each loop. Access it with `getTelemetry()` / `GetTelemetry()`.

{% hint style="info" %}
Call `withTelemetry(...)` last, after the rest of the stream configuration, so the dashboard starts with the configured values. A `clone()` of a stream has no telemetry.
{% endhint %}

## 2026.10.5

### Added

* `GyroAxis` enum (`yams.core.mechanisms.config.enums`: `YAW` default, `ROLL`, `PITCH`) and `SwerveDriveConfig.withGyroHeadingAxis(GyroAxis)` (Java): use a gyro mounted on its side as the heading source. The gyro attitude is rotated into the robot frame, so the heading keeps its full range and anti tipping reads the robot's tilt.
* `SwerveModule.lock(Rotation2d)` and `SwerveModuleConfig.getLockedState(...)` (Java).

### Changed

* **Breaking (Java):** `SwerveDriveConfig.getGyroAngle()` and `SwerveDrive.getGyroAngle()` are removed. Use `getGyroRotation3d()`. The heading now wraps at +/-180 degrees instead of counting continuously.
* Vendordeps now declare `requires` and `conflictsWith`, so the Commands v2 and Commands v3 variants of YAMS cannot be installed together.

### Fixed

* **Java:** `SwerveDrive.lockPose()` now turns the wheels into the X pattern. Previously the module velocity deadband replaced the zero speed states, so the wheels held their current angle.
* **Java:** Simulated SPARKs now close their loop at 1 kHz like a real SPARK (and like simulated Talons), instead of once per 20 ms simulation period. Integral and derivative gains are no longer scaled by the loop period in simulation. Real robot gain conversion is unchanged.

## 2026.10.3

YAMS now targets **WPILib 2027** (`2027_alpha7`) and the SystemCore. Robot code built against earlier releases needs to be migrated; see **Upgrading from 2026 to 2027** in the [YAMS documentation](https://yagsl.gitbook.io/yams/documentation).

### Added

* Two vendordeps, one per WPILib command framework. Install exactly one:
  * **Commands v2:** `yams_commands2.json`, with command bindings in `yams.commands2.*`.
  * **Commands v3:** `yams_commands3.json`, with command bindings in `yams.commands3.*` (Java only, since WPILib has no C++ Commands v3).
* An offline installer, `<version>-YAMS-Offline.zip`, attached to each GitHub release. Extract it into your WPILib `2027_alpha7` folder, then install the vendordep offline.
* Example projects for both command frameworks under `examples/commands2` and `examples/commands3`.

### Changed

* **Breaking:** WPILib 2027 packages (`org.wpilib.*` in Java, `wpi::` in C++), Java 25, Phoenix 6 2027, and REVLib 2027.
* **Breaking (Java):** packages split into `yams.core.*` (mechanism and motor controller state, simulation, telemetry, configs, wrappers, gearing, math, units) and `yams.commands2.*` / `yams.commands3.*` (subsystem bound mechanisms, `SmartMotorControllerConfig`, `SwerveDriveConfig`, `SwerveDrive`, `SwerveInputStream`, and their `Command`/`Trigger` factories). Robot code imports mechanisms and those configs from the command framework package.
* **Breaking (Java):** the `isNear(...)` `Trigger` factory is renamed `near(...)`, matching `gte()`, `lte()`, `between()`, `max()`, and `min()`. The boolean `isNear(...)` check is unchanged.
* Live tuning registers itself when telemetry is `TelemetryVerbosity.HIGH`. In other cases, opt in with `SmartMotorControllerConfig.setupLiveTuning()`.
* **Breaking:** `withIdleMode(MotorMode)` is renamed `withZeroPower(MotorMode)` (C++: `WithZeroPower`).

## 2026.9.16

### Changed

* **Breaking:** `SmartMotorControllerConfig.getZeroOffset()` is renamed `getExternalEncoderZeroOffset()`, matching the existing `withExternalEncoderZeroOffset(...)` setter.

### Fixed

* `TalonFXWrapper` no longer applies the zero offset to the rotor as well, which double applied the offset.

## 2026.9.10

### Added

* `SmartMotorController.setVelocity(AngularVelocity, Force)` / `setVelocity(LinearVelocity, Force)` and `SwerveDrive.setRobotRelativeChassisSpeeds(..., Force[])`: pass a feedforward force (for example from a path follower) straight through to the drive motors. `SwerveModule` gained the matching overloads.
* `SwerveDriveConfig.clone()`.
* `SmartMotorControllerConfig.withSimulationPeriod(Time)` (C++: `WithSimulationPeriod`).
* `SimSupplier.getStatorCurrent()` / `getSupplyCurrent()`.
* The gyro angular velocity is derived from the heading when no gyro velocity supplier is set, so angular velocity scaling works without one.

### Fixed

* The simulated swerve pose was integrated twice, so it moved robot relative instead of field relative.

## 2026.9.2

### Added

* Advanced telemetry for `SwerveDrive` and `SwerveModule`: `SwerveDriveConfig.withTelemetry(String, TelemetryVerbosity)` / `withTelemetry(String, SwerveDriveTelemetryConfig)` and `SwerveModuleConfig.withTelemetry(String, SwerveModuleTelemetryConfig)`, with more live tunable fields.
* `SwerveDrive.setTranslationPID(PIDController)`, `setRotationPID(PIDController)`, `driveToPoseSetpoint(Pose2d)`, and `getModules()`.
* `SwerveModule.getRawAbsoluteEncoderAngle()`.

### Changed

* `SwerveDrive.resetAzimuthPID()` is renamed `resetRotationPID()`.
* `SwerveInputStream` takes its maximum velocities from the `SwerveDrive`.

### Removed

* **Breaking:** `SwerveDriveConfig.withDataLogName(...)` and `SwerveModuleConfig.withDataLogName(...)`. Use the `withTelemetry(...)` overloads above.
* **Breaking:** drive to pose in `SwerveInputStream`, including `atTargetPose(double)`. Use `SwerveDrive.driveToPoseSetpoint(Pose2d)`.

### Fixed

* The absolute encoder supplier is zeroed when an absolute encoder is supplied to `SwerveModuleConfig`.

## 2026.8.26

### Added

* `SwerveDrive.getSimPose()` (Java) / `SwerveDrive::GetSimPose()` (C++) — a ground-truth simulated pose that assumes every module reached its last-commanded `SwerveModuleState` perfectly. It's integrated in `simIterate()`/`SimIterate()` from the desired (not measured) module states via a `Twist2d`/`frc::Twist2d`, independent of the noisy, gyro/odometry-fused pose returned by `getPose()`/`GetPose()`. `resetOdometry(...)`/`ResetOdometry(...)` also snaps it to the given pose, keeping it in sync with the fused estimate. Intended as a "known truth" source for simulated vision systems.
* `SwerveDrive.getRobotRelativeChassisSpeedsFromState(SwerveModuleState[])` (Java) / `SwerveDrive::GetRobotRelativeChassisSpeedsFromState(...)` (C++) — converts an arbitrary set of module states back into robot-relative chassis speeds.
* `SwerveDrive.getDesiredModuleStates()` (Java) / `SwerveDrive::GetDesiredModuleStates()` (C++) — returns the last-commanded module states, mirroring the existing `getDesiredChassisSpeeds()`/`GetDesiredChassisSpeeds()`.

### Removed

* **Breaking:** `SwerveDrive.getPoseEstimator()` (Java) / `SwerveDrive::GetPoseEstimator()` (C++) — direct access to the underlying `SwerveDrivePoseEstimator` was too easy to misuse (calling `update(...)`/`resetPosition(...)`/`resetPose(...)` on it directly corrupts the pose estimate or desyncs it from the drive's gyro offset). Use `getPose()`, `resetOdometry(Pose2d)`, and `addVisionMeasurement(...)` instead.

## 2026.8.20

### Added

* `BatterySim` (Java & C++) now models a single, shared robot battery in simulation. Every simulated `SmartMotorController` — via its `SimSupplier` (`ArmSimSupplier`, `ElevatorSimSupplier`, `DCMotorSimSupplier`) or hardware wrapper (`SparkWrapper`, `TalonFXWrapper`, `TalonFXSWrapper`) — reports its own current draw into one combined loaded-voltage calculation, instead of each mechanism computing `RoboRioSim` voltage sag from only its own current draw and overwriting the others.
* `BatterySim.enableDischarge(double, Voltage, Resistance)` (Java) / `BatterySim::EnableDischarge(double, units::volt_t, units::ohm_t)` (C++) — optional realistic battery discharge simulation. Current draw is integrated into amp-hours consumed over time, sagging the open-circuit voltage and raising internal resistance as the battery empties, following a typical FRC sealed lead-acid discharge curve. Also added `disableDischarge()`/`DisableDischarge()`, `resetDischarge()`/`ResetDischarge()`, and `getStateOfCharge()`/`GetStateOfCharge()`.
* `BatterySim.replaceSOCInterpolation(InterpolatingDoubleTreeMap)` (Java) / `BatterySim::ReplaceSOCInterpolation(const std::map<double, double>&)` (C++) — swap in a custom state-of-charge → voltage discharge curve, for modeling a well-used competition battery, a different chemistry, or a curve measured from a real battery on a load tester.

### Changed

* **C++:** `Arm`, `Elevator`, `Pivot`, and `FlyWheel` no longer call `RoboRioSim::SetVInVoltage(...)` directly from their own current draw. Voltage now comes from the shared `BatterySim`, so combined load across multiple mechanisms sags the simulated bus voltage realistically instead of only reflecting whichever mechanism updated last.

{% hint style="info" %}
Discharge simulation is off by default and only affects simulation — on a real robot, `RobotController.getBatteryVoltage()`/`frc::RobotController::GetBatteryVoltage()` always reflects actual hardware.
{% endhint %}

## 2026.8.18

### Added

* `SwerveDriveConfig.getDiscretization()` — returns the discretization time used by `optimizeRobotRelativeChassisSpeeds(...)`, automatically preferring the simulation-specific override from `withSimDiscretizationTime(...)` when running in simulation.

## 2026.8.17

### Added

* `SmartMotorControllerTelemetryConfig.withCustom(DoubleTelemetryField, boolean)` / `withCustom(BooleanTelemetryField, boolean)` (Java, plus array overloads) — an escape hatch to enable or disable a telemetry field directly when there isn't a dedicated `with*()` setter for it yet.

### Changed

* Telemetry now only publishes fields that are actually enabled, instead of publishing every registered field regardless of its enabled state.
* **Java & C++:** Swerve module telemetry now nests each module under `swerve/modules/<name>/...` instead of `swerve/<name>/...`, removing a double `/` that showed up in some NetworkTables/DataLog paths.

### Fixed

* **Java & C++:** Cosine compensation in `SwerveModuleConfig` now reads the azimuth angle once per control cycle and reuses that same value for both state optimization and the cosine scalar, instead of re-querying the absolute encoder separately for each step. Previously those two reads could disagree, which showed up as skewed drive speed or the wheel losing its commanded direction right around a 180° heading error.

## 2026.8.16

### Added

* `MechanismTelemetry` is now the primary coordinator of all mechanism telemetry, and gained explicit DataLog support alongside NetworkTables.
* `SwerveDriveConfig.withDataLogName(String)`/`getDataLogName()` — logs the drive's pose, gyro, and chassis speeds/module states to a WPILib DataLog under the given name, in addition to NetworkTables.
* `SwerveModuleConfig.withDataLogName(String)`/`getDataLogName()` — logs that module's absolute encoder angle to a WPILib DataLog under the given name, in addition to NetworkTables.

### Changed

* `SwerveModule` now shares a single `MechanismTelemetry` instance (and loop timer) across its drive and azimuth motors instead of each motor re-running its own telemetry setup.

{% hint style="info" %}
Neither `SwerveDriveConfig.withDataLogName(...)` nor `SwerveModuleConfig.withDataLogName(...)` cascades down to a module's drive/azimuth motors. Set a DataLog name on each motor's own `SmartMotorControllerConfig`/`SmartMotorControllerTelemetryConfig` for per-motor logging.
{% endhint %}

## 2026.7.10

### Added

* **C++:** `SmartMotorControllerConfig::WithFeedforward(...)` — a single overloaded setter that takes a WPILib feedforward object directly (`frc::ArmFeedforward`, `frc::ElevatorFeedforward`, or `frc::SimpleMotorFeedforward<units::turns>`), replacing the setters removed below.

### Removed

* **Breaking (C++):** `SmartMotorControllerConfig` removed `WithArmFeedforward`, `WithElevatorFeedforward`, `WithSimpleFeedforward`, and the standalone `WithKp`/`WithKi`/`WithKd`/`WithKs`/`WithKv`/`WithKa`/`WithKg` setters. See the migration note above.

## 2026.7.09

### Added

* `SwerveModule` now publishes the azimuth absolute encoder angle to telemetry.
* Added `SwerveModuleConfig.getAbsoluteEncoderSupplier()` and `getRawAbsoluteEncoderAngle()` (Java) to expose the absolute encoder reading directly, including a variant with the configured zero offset stripped out. Swerve telemetry now reports this raw, offset-free angle.

### Changed

* `SwerveModule.setSwerveModuleState(SwerveModuleState)` now returns the optimized `SwerveModuleState` instead of `void`.

### Fixed

* `SwerveDrive`'s desired module states now reflect the actual post-optimization `SwerveModuleState` returned by `setSwerveModuleState(...)` above, instead of the pre-optimization input — fixes desired-state telemetry/logging showing the wrong angle after a flip.

## 2026.7.06

### Fixed

* Fixed `SwerveModuleConfig`'s state optimizer entering an infinite re-optimization loop right at a flip boundary. The flip decision now debounces against the last *commanded* angle instead of being re-derived from the live absolute encoder every loop.

## 2026.6.25.1

### Added

* `SmartMotorControllerCommandRegistry` now throws `IllegalStateException` when two different `Subsystem` instances register commands under the same name, and adds `removeCommands(Subsystem)` to clear a subsystem's registered commands (e.g. between tests).

## 2026.6.25

### Added

* Added a full C++ port of YAMS: mechanisms (`Arm`, `Elevator`, `Pivot`, `FlyWheel`, `DoubleFlyWheel`, `DifferentialDrive`), motor controller wrappers (Spark, TalonFX, TalonFXS), telemetry, units, and unit tests, mirroring the existing Java API.
* Added `SwerveDrive` and `SwerveInputStream` (new in both Java and C++).
* Added Doxygen documentation generation for the C++ API.

### Removed

* **Breaking:** Removed ThriftyNova motor controller support (`NovaWrapper`) from the Java API; there is no direct replacement.

### Changed

* Adopted `wpiformat` for consistent Java/C++ formatting across the codebase.

## 2026.1.17

* Empirical Method for CRT by [@mjjbycicle](https://github.com/mjjbycicle) in [#68](https://github.com/Yet-Another-Software-Suite/YAMS/pull/68)
* SmartMotorController Control Request Fix by [@thenetworkgrinch](https://github.com/thenetworkgrinch) in [#69](https://github.com/Yet-Another-Software-Suite/YAMS/pull/69)
* Fixed the DCMotorSim to work with the correct units by [@thenetworkgrinch](https://github.com/thenetworkgrinch) in [#72](https://github.com/Yet-Another-Software-Suite/YAMS/pull/72)
* Swerve drive sysId by [@thenetworkgrinch](https://github.com/thenetworkgrinch) in [#73](https://github.com/Yet-Another-Software-Suite/YAMS/pull/73)
* Inversions shouldn't apply in Simulation by [@thenetworkgrinch](https://github.com/thenetworkgrinch) in [#74](https://github.com/Yet-Another-Software-Suite/YAMS/pull/74)
* MaybeCRT by [@alexalex02](https://github.com/alexalex02) in [#70](https://github.com/Yet-Another-Software-Suite/YAMS/pull/70)
* Kitbot added by [@nobodyhere4](https://github.com/nobodyhere4) in [#62](https://github.com/Yet-Another-Software-Suite/YAMS/pull/62)

## 2026.1.16

* Fixed non-profiled Closed Loop Control in TalonFXS, TalonFX, and SparkMax.
* Added `Conversion` for ThriftyNova encoder readings to account for encoder ticks.
* Fixed `SwerveDrive` template.
* Added Shoot on the move example.
* [Added Hood + Turret example ](https://github.com/Yet-Another-Software-Suite/YAMS/pull/63)(by 9738)

## 2026.1.4

* Added Imperial `YUnits.PoundSquareInches` `YUnits.PoundsSquareFeet`
* Deprecated `withMOI(double)` in favor of `withMOI(MomentOfInertia)`.
* Added `withMOI(MomentOfInertia)`

## 2026.1.2

* Added `SmartMotorControllerConfig().clone()` to allow for clonable configs.
* Added `MechanismConfig.withSmartMotorController()` for all mechanism and null param constructors
* Added `SensorData.set()` overloads
* 'Trapazoidal' Spelling Error Correction by [@akhaled247](https://github.com/akhaled247) in [#56](https://github.com/Yet-Another-Software-Suite/YAMS/pull/56)
* REV 2026 with Feedforwards and PIDs by [@thenetworkgrinch](https://github.com/thenetworkgrinch) in [#55](https://github.com/Yet-Another-Software-Suite/YAMS/pull/55)
* Added the option to loosely coupled followers. by [@thenetworkgrinch](https://github.com/thenetworkgrinch) in [#58](https://github.com/Yet-Another-Software-Suite/YAMS/pull/58)
* Allow for independently created config classes for constants files. by [@thenetworkgrinch](https://github.com/thenetworkgrinch) in [#59](https://github.com/Yet-Another-Software-Suite/YAMS/pull/59)
* Changed `SwerveInputStream.copy()` to `SwerveInputStream.clone()`
* Modified `SmartMotorControllerConfig` to allow for creation without a `Subsystem` but still require a subsystem eventually.
* Modified `SmartMotorController` telemetry to only create Live Tuning and other useful commands if tunable setpoints are not enabled.
* Added `MechanismGearing(double...)` overload
* Added DifferentialDrive example.

## 2025.12.22

* Added the ability to use multiple SMC's with Live Tuning in one subsystem
* Added AdvantageKit example
* Modified `SwerveDrive` to support AdvantageKit better.
* Updated Telemetry Setup for standalone SMC's to be automatic.
* Added Units to Telemetry Fields.
* Added DoubleFlyWheel example.
* Fixed ElevatorConfig comments saying "length" instead of "angle" [#54](https://github.com/Yet-Another-Software-Suite/YAMS/pull/54)
* Added support for user moment of inertia in ExponentialProfilePIDController [#50](https://github.com/Yet-Another-Software-Suite/YAMS/pull/50)
* Improved Exponential Elevator Examples [#46](https://github.com/Yet-Another-Software-Suite/YAMS/pull/46)
* Added ExponentialProfile.
* Add MapleSim Integration to SwerveDrive by [@konnorreynolds](https://github.com/konnorreynolds) in [#49](https://github.com/Yet-Another-Software-Suite/YAMS/pull/49)

## 2025.11.05

* Renamed `.withExternalGearing` to `.withExternalEncoderGearing`.
* Added several new easy commands, like "Up", "Down", and "ZeroEncoder".
* Moved commands to "SmartDashboard/Mechanisms/MECHNAME/MOTORNAME/Commands/".
* Fixed immutable SensorData fields.
* Fixed `SparkWrapper.setEncoderPosition` to set the sim position correctly.

## 2025.11.02

* Renamed `SmartMotorControllerConfig.withZeroOffset` to `SmartMotorControllerConfig.withExternalEncoderZeroOffset`

## 2025.10.31

* Updated `SwerveInputStream` to use `.with*` for shared styling.
* Added `swerve_drive` example.

## 2025.10.29

* Added setpoint visualization to Arm Mechanism2d
* Fixed ArmFeedforward calculation on Spark's, and Nova's. Changed velocity calculation from Rotations to Radians. Found by 8114 (The Rocketeers)

## 2025.10.27

* Renamed `Shooter` to `FlyWheel`
* Added Loop Time to each mechanism.

## 2025.10.14

* Added `YUnits` which allows you to include more exotic units in your code.
* Added `Sensor`s which allow you to seamlessly fetch automatically modified sensor data during sim and real data when running IRL.
