use crate::arrival_log::{ArrivalLog, CurrentTick, DEFAULT_ARRIVAL_WINDOW_TICKS};
use crate::sim::Simulation;
use crate::stop::StopId;
use crate::world::World;
use super::helpers::{default_config, scan};
#[test]
fn arrival_log_counts_events_within_window() {
let mut world = World::new();
let stop_a = world.spawn();
let stop_b = world.spawn();
let mut log = ArrivalLog::default();
log.record(100, stop_a);
log.record(150, stop_a);
log.record(200, stop_b);
log.record(300, stop_a);
assert_eq!(log.arrivals_in_window(stop_a, 300, 200), 3);
assert_eq!(log.arrivals_in_window(stop_b, 300, 200), 1);
assert_eq!(log.arrivals_in_window(stop_a, 300, 150), 2);
assert_eq!(log.arrivals_in_window(stop_a, 300, 0), 0);
}
#[test]
fn arrival_log_prunes_old_events() {
let mut world = World::new();
let stop = world.spawn();
let mut log = ArrivalLog::default();
for tick in 0..1_000u64 {
log.record(tick, stop);
}
log.prune_before(900);
assert_eq!(log.arrivals_in_window(stop, 999, 1_000), 100);
assert_eq!(log.arrivals_in_window(stop, 999, 200), 100);
}
#[test]
fn simulation_records_spawns_in_arrival_log() {
let config = default_config();
let mut sim = Simulation::new(&config, scan()).unwrap();
let origin = sim.stop_entity(StopId(0)).unwrap();
let _ = sim.spawn_rider(StopId(0), StopId(2), 70.0).unwrap();
let _ = sim.spawn_rider(StopId(0), StopId(2), 70.0).unwrap();
let count = sim
.world()
.resource::<ArrivalLog>()
.expect("ArrivalLog resource must be registered at construction")
.arrivals_in_window(origin, sim.current_tick(), 60);
assert_eq!(count, 2);
}
#[test]
fn arrival_log_survives_snapshot_round_trip() {
let config = default_config();
let mut sim = Simulation::new(&config, scan()).unwrap();
let custom_retention = 72_000;
sim.set_arrival_log_retention_ticks(custom_retention);
sim.spawn_rider(StopId(0), StopId(2), 70.0).unwrap();
sim.spawn_rider(StopId(0), StopId(2), 70.0).unwrap();
sim.step();
let origin = sim.stop_entity(StopId(0)).unwrap();
let before = sim
.world()
.resource::<ArrivalLog>()
.unwrap()
.arrivals_in_window(origin, sim.current_tick(), 120);
assert!(before >= 2, "precondition: snapshot source has arrivals");
let snap = sim.snapshot();
let restored = snap.restore(None).unwrap();
let origin_after = restored.stop_entity(StopId(0)).unwrap();
let log_after = restored
.world()
.resource::<ArrivalLog>()
.expect("restore must reinstall the ArrivalLog resource");
assert_eq!(
log_after.arrivals_in_window(origin_after, restored.current_tick(), 120),
before,
"arrival counts must survive snapshot → restore"
);
let ct = restored
.world()
.resource::<CurrentTick>()
.expect("restore must reinstall the CurrentTick resource");
assert_eq!(ct.0, restored.current_tick());
let retention = restored
.world()
.resource::<crate::arrival_log::ArrivalLogRetention>()
.expect("restore must reinstall the ArrivalLogRetention resource");
assert_eq!(retention.0, custom_retention);
}
#[test]
fn arrival_log_is_pruned_by_step() {
let config = default_config();
let mut sim = Simulation::new(&config, scan()).unwrap();
sim.spawn_rider(StopId(0), StopId(2), 70.0).unwrap();
let target = DEFAULT_ARRIVAL_WINDOW_TICKS + 100;
while sim.current_tick() < target {
sim.step();
}
let log = sim.world().resource::<ArrivalLog>().unwrap();
assert_eq!(
log.len(),
0,
"entries older than the rolling window must be pruned, not merely filtered"
);
}
#[test]
fn dispatch_manifest_exposes_recent_arrivals() {
let config = default_config();
let mut sim = Simulation::new(&config, scan()).unwrap();
let origin = sim.stop_entity(StopId(0)).unwrap();
sim.spawn_rider(StopId(0), StopId(2), 70.0).unwrap();
sim.spawn_rider(StopId(0), StopId(2), 70.0).unwrap();
for _ in 0..5 {
sim.step();
}
let manifest = sim.peek_dispatch_manifest();
assert_eq!(
manifest.arrivals_at(origin),
2,
"manifest must surface recent-arrival counts for strategies"
);
let other = sim.stop_entity(StopId(2)).unwrap();
assert_eq!(manifest.arrivals_at(other), 0);
}