Scaffold workspace, shared types and requirements spec

Cargo workspace with core/ble/fit/probe crates. crates/core/src/types.rs
is the fixed contract between the BLE layer, ride engine and UI.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-05 13:11:32 +02:00
co-authored by Claude Opus 5
commit eb216dd9e6
21 changed files with 2910 additions and 0 deletions
+63
View File
@@ -0,0 +1,63 @@
//! Virtual speed from measured power (§5.7 of REQUIREMENTS.md).
//!
//! The app owns the physics rather than trusting the trainer's reported speed
//! (FR-7.1). This makes ride behaviour reproducible in tests, independent of
//! the trainer's internal mass assumptions, and is a prerequisite for virtual
//! gearing later.
//!
//! Per tick:
//! ```text
//! F_propulsive = (P × drivetrain_efficiency) / max(v, v_min)
//! F_gravity = m × g × sin(atan(gradient))
//! F_rolling = m × g × Crr × cos(atan(gradient))
//! F_aero = ½ × ρ × CdA × v²
//! a = (F_propulsive F_gravity F_rolling F_aero) / m
//! v += a × Δt (clamped at ≥ 0)
//! ```
use crate::types::RiderConfig;
pub const GRAVITY: f32 = 9.80665;
/// Speed floor used to keep `P / v` finite at a standstill. Also the speed
/// below which the rider is considered stopped.
pub const MIN_SPEED_MPS: f32 = 0.5;
/// Evolving physical state of the virtual rider.
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct PhysicsState {
/// Virtual speed, metres per second.
pub speed_mps: f32,
/// Virtual distance travelled, metres.
pub distance_m: f64,
/// Cumulative elevation gained, metres.
pub elevation_gain_m: f32,
}
impl PhysicsState {
/// Advance the simulation by `dt` seconds under `power_w` at `gradient_pct`.
///
/// Must model inertia (FR-7.3) — speed accelerates toward equilibrium
/// rather than snapping to it — and must never produce negative speed,
/// NaN, or unbounded values for any finite input.
pub fn step(&mut self, power_w: f32, gradient_pct: f32, cfg: &RiderConfig, dt: f32) {
let _ = (power_w, gradient_pct, cfg, dt);
todo!("implemented in crates/core/src/physics.rs — see AGENT task A")
}
pub fn speed_kph(&self) -> f32 {
self.speed_mps * 3.6
}
pub fn is_moving(&self) -> bool {
self.speed_mps > MIN_SPEED_MPS
}
}
/// Steady-state speed for a given power and gradient — the speed at which
/// propulsive and resistive forces balance. Useful for tests and for sanity
/// checks on the resistance curve later.
pub fn equilibrium_speed_mps(power_w: f32, gradient_pct: f32, cfg: &RiderConfig) -> f32 {
let _ = (power_w, gradient_pct, cfg);
todo!("implemented in crates/core/src/physics.rs — see AGENT task A")
}