#[derive(Debug, Clone, PartialEq)]
pub struct TravelAssessment {
pub distance_km: f64,
pub mode: String,
pub needed_days: f64,
pub claimed_days: f64,
pub plausible: bool,
}
pub fn speed_km_per_day(mode: &str) -> f64 {
match mode.to_ascii_lowercase().as_str() {
"ship" | "sail" | "sea" | "boat" => 120.0,
"horse" | "ride" | "mounted" => 55.0,
"cart" | "wagon" => 25.0,
_ => 30.0, }
}
pub fn mode_recognized(mode: &str) -> bool {
matches!(
mode.to_ascii_lowercase().as_str(),
"ship" | "sail" | "sea" | "boat" | "horse" | "ride" | "mounted" | "cart" | "wagon"
| "foot" | "walk" | "march" | "on foot"
)
}
pub fn assess(distance_km: f64, claimed_days: f64, mode: &str) -> TravelAssessment {
let needed = distance_km / speed_km_per_day(mode).max(1.0);
let plausible = claimed_days >= needed * 0.85;
TravelAssessment {
distance_km,
mode: mode.to_string(),
needed_days: needed,
claimed_days,
plausible,
}
}
pub fn cell_km(radius_earth: f64, grid_width: usize, grid_height: usize) -> (f64, f64) {
const EARTH_RADIUS_KM: f64 = 6371.0;
let r = radius_earth.max(0.01) * EARTH_RADIUS_KM;
let x = 2.0 * std::f64::consts::PI * r / (grid_width.max(1) as f64);
let y = std::f64::consts::PI * r / (grid_height.max(1) as f64);
(x, y)
}
pub fn distance_km(radius_earth: f64, grid_width: usize, grid_height: usize, dx: f64, dy: f64) -> f64 {
let (xk, yk) = cell_km(radius_earth, grid_width, grid_height);
((dx * xk).powi(2) + (dy * yk).powi(2)).sqrt()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_day_on_foot_across_a_hundred_km_is_implausible() {
let a = assess(100.0, 1.0, "foot");
assert!(!a.plausible);
assert!(a.needed_days > 3.0);
assert!(assess(100.0, 4.0, "foot").plausible);
}
#[test]
fn ships_are_faster_than_marching() {
assert!(speed_km_per_day("ship") > speed_km_per_day("foot"));
assert!(assess(300.0, 3.0, "ship").plausible);
assert!(!assess(300.0, 3.0, "foot").plausible);
}
#[test]
fn cells_are_anisotropic_on_the_real_grid() {
let (xk, yk) = cell_km(1.0, 160, 120);
assert!(xk > yk);
assert!((xk / yk - 1.5).abs() < 0.01, "ratio {}", xk / yk);
let (xk2, _) = cell_km(2.0, 160, 120);
assert!(xk2 > xk);
}
#[test]
fn distance_respects_the_axis() {
let ns = distance_km(1.0, 160, 120, 0.0, 4.0);
let ew = distance_km(1.0, 160, 120, 4.0, 0.0);
assert!(ns < ew, "N–S {ns} should be < E–W {ew}");
}
#[test]
fn unknown_mode_is_flagged() {
assert!(mode_recognized("ship") && mode_recognized("boat") && mode_recognized("foot"));
assert!(!mode_recognized("teleport"));
}
}