Gearing as development, physics-derived load model, 105kg rider

Gears are metres per crank revolution rather than gradient offsets, and
resistive_force_n exposes what the road is doing at a given speed so load
can be computed directly instead of servoed.

The load model is tested but not yet commanded: FTMS sim mode has the
trainer compute rolling and aero itself, so sending a gradient that already
contains them would double-count. Needs Crr/Cw zeroed and a ride to verify.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-05 15:46:07 +02:00
co-authored by Claude Opus 5
parent 57eb5e809b
commit f2c4cb2120
5 changed files with 428 additions and 119 deletions
+21
View File
@@ -250,6 +250,27 @@ pub fn equilibrium_speed_mps(power_w: f32, gradient_pct: f32, cfg: &RiderConfig)
0.5 * (lo + hi)
}
/// Total resistive force at a given speed and gradient, newtons.
///
/// This is what the road is doing to the rider: gravity down the slope, rolling
/// resistance, and aerodynamic drag rising with the square of speed. It is the
/// force a trainer must reproduce at the wheel for the ride to feel real, and
/// therefore the basis for virtual gearing (see `crate::gearing`).
pub fn resistive_force_n(speed_mps: f32, gradient_pct: f32, cfg: &RiderConfig) -> f32 {
let forces = Forces::new(0.0, gradient_pct, cfg);
let v = if speed_mps.is_finite() {
speed_mps.clamp(0.0, MAX_SPEED_MPS)
} else {
0.0
};
let f = forces.resistive_n + forces.drag_k * v * v;
if f.is_finite() {
f
} else {
0.0
}
}
#[cfg(test)]
mod tests {
use super::*;