use tanmatra::bridge::TimeContext;
#[must_use]
#[inline]
pub fn simulation_seconds(ctx: &TimeContext) -> f64 {
ctx.effective_elapsed_s()
}
#[must_use]
#[inline]
pub fn simulation_hours(ctx: &TimeContext) -> f64 {
ctx.effective_elapsed_s() / 3600.0
}
#[must_use]
#[inline]
pub fn time_multiplier(ctx: &TimeContext) -> f64 {
ctx.multiplier()
}
#[must_use]
#[inline]
pub fn is_paused(ctx: &TimeContext) -> bool {
ctx.is_paused()
}
#[must_use]
#[inline]
pub fn growth_rate_scale(ctx: &TimeContext) -> f64 {
ctx.multiplier()
}
#[must_use]
#[inline]
pub fn decay_rate_scale(ctx: &TimeContext) -> f64 {
ctx.multiplier()
}
#[must_use]
#[inline]
pub fn circadian_hour_of_day(ctx: &TimeContext) -> f64 {
let elapsed_s = ctx.effective_elapsed_s();
let hours = elapsed_s / 3600.0;
hours.rem_euclid(24.0)
}
#[must_use]
#[inline]
pub fn circadian_day_fraction(ctx: &TimeContext) -> f64 {
circadian_hour_of_day(ctx) / 24.0
}
#[must_use]
#[inline]
pub fn simulation_days(ctx: &TimeContext) -> f64 {
ctx.effective_elapsed_s() / 86400.0
}
#[must_use]
#[inline]
pub fn delta_seconds(prev: &TimeContext, now: &TimeContext) -> f64 {
if prev.is_paused() || now.is_paused() {
return 0.0;
}
let dt = now.effective_elapsed_s() - prev.effective_elapsed_s();
dt.max(0.0)
}
#[must_use]
#[inline]
pub fn delta_hours(prev: &TimeContext, now: &TimeContext) -> f64 {
delta_seconds(prev, now) / 3600.0
}
#[cfg(test)]
mod tests {
use super::*;
use tanmatra::bridge::SimulationClock;
#[test]
fn real_time_basics() {
let ctx = TimeContext::real_time(7200.0); assert!((simulation_seconds(&ctx) - 7200.0).abs() < 0.001);
assert!((simulation_hours(&ctx) - 2.0).abs() < 0.001);
assert!((time_multiplier(&ctx) - 1.0).abs() < 0.001);
assert!(!is_paused(&ctx));
}
#[test]
fn simulated_time_scaling() {
let clock = SimulationClock::new(0.0).set_multiplier(0.0, 10.0);
let ctx = TimeContext::from_simulation_clock(&clock, 100.0);
assert!((simulation_seconds(&ctx) - 1000.0).abs() < 0.001);
assert!((time_multiplier(&ctx) - 10.0).abs() < 0.001);
}
#[test]
fn paused_freezes() {
let clock = SimulationClock::new(0.0).pause(50.0);
let ctx = TimeContext::from_simulation_clock(&clock, 200.0);
assert!(is_paused(&ctx));
assert!((growth_rate_scale(&ctx)).abs() < 0.001);
assert!((decay_rate_scale(&ctx)).abs() < 0.001);
}
#[test]
fn circadian_wraps_24h() {
let ctx = TimeContext::real_time(25.0 * 3600.0);
let hour = circadian_hour_of_day(&ctx);
assert!((hour - 1.0).abs() < 0.01);
}
#[test]
fn circadian_noon() {
let ctx = TimeContext::real_time(12.0 * 3600.0);
assert!((circadian_hour_of_day(&ctx) - 12.0).abs() < 0.01);
assert!((circadian_day_fraction(&ctx) - 0.5).abs() < 0.01);
}
#[test]
fn circadian_accelerated() {
let clock = SimulationClock::new(0.0).set_multiplier(0.0, 10.0);
let ctx = TimeContext::from_simulation_clock(&clock, 3600.0);
assert!((circadian_hour_of_day(&ctx) - 10.0).abs() < 0.01);
}
#[test]
fn simulation_days_count() {
let ctx = TimeContext::real_time(3.5 * 86400.0);
assert!((simulation_days(&ctx) - 3.5).abs() < 0.001);
}
#[test]
fn delta_seconds_basic() {
let prev = TimeContext::real_time(1000.0);
let now = TimeContext::real_time(1005.0);
assert!((delta_seconds(&prev, &now) - 5.0).abs() < 0.001);
}
#[test]
fn delta_seconds_paused_is_zero() {
let prev = TimeContext::real_time(1000.0);
let clock = SimulationClock::new(0.0).pause(0.0);
let now = TimeContext::from_simulation_clock(&clock, 2000.0);
assert!((delta_seconds(&prev, &now)).abs() < 0.001);
}
#[test]
fn delta_seconds_accelerated() {
let clock = SimulationClock::new(0.0).set_multiplier(0.0, 5.0);
let prev = TimeContext::from_simulation_clock(&clock, 100.0);
let now = TimeContext::from_simulation_clock(&clock, 110.0);
assert!((delta_seconds(&prev, &now) - 50.0).abs() < 0.001);
}
#[test]
fn delta_hours_basic() {
let prev = TimeContext::real_time(0.0);
let now = TimeContext::real_time(7200.0);
assert!((delta_hours(&prev, &now) - 2.0).abs() < 0.001);
}
#[test]
fn growth_and_decay_match_multiplier() {
let clock = SimulationClock::new(0.0).set_multiplier(0.0, 3.0);
let ctx = TimeContext::from_simulation_clock(&clock, 10.0);
assert!((growth_rate_scale(&ctx) - 3.0).abs() < 0.001);
assert!((decay_rate_scale(&ctx) - 3.0).abs() < 0.001);
}
#[test]
fn serde_roundtrip_time_context() {
let ctx = TimeContext::real_time(12345.0);
let json = serde_json::to_string(&ctx).unwrap();
let deser: TimeContext = serde_json::from_str(&json).unwrap();
assert!((ctx.tai_seconds() - deser.tai_seconds()).abs() < f64::EPSILON);
}
#[test]
fn serde_roundtrip_simulation_clock() {
let clock = SimulationClock::new(100.0).set_multiplier(100.0, 5.0);
let json = serde_json::to_string(&clock).unwrap();
let deser: SimulationClock = serde_json::from_str(&json).unwrap();
assert!((clock.simulation_time(200.0) - deser.simulation_time(200.0)).abs() < f64::EPSILON);
}
#[test]
fn negative_elapsed_clamped() {
let prev = TimeContext::real_time(2000.0);
let now = TimeContext::real_time(1000.0);
assert!((delta_seconds(&prev, &now)).abs() < 0.001);
}
}