use crate::components::{Patience, Preferences, RiderPhase};
use crate::dispatch::scan::ScanDispatch;
use crate::events::Event;
use crate::sim::Simulation;
use crate::stop::StopId;
use crate::tests::helpers::default_config;
#[test]
fn patience_zero_abandons_immediately() {
let config = default_config();
let mut sim = Simulation::new(&config, ScanDispatch::new()).unwrap();
let rider = sim.spawn_rider(StopId(0), StopId(2), 70.0).unwrap();
sim.world_mut().set_patience(
rider,
Patience {
max_wait_ticks: 0,
waited_ticks: 0,
},
);
sim.step();
let phase = sim.world().rider(rider).map(|r| r.phase);
assert_eq!(
phase,
Some(RiderPhase::Abandoned),
"rider with max_wait_ticks=0 should abandon immediately"
);
}
#[test]
fn patience_one_abandons_after_one_tick() {
let config = default_config();
let mut sim = Simulation::new(&config, ScanDispatch::new()).unwrap();
let rider = sim.spawn_rider(StopId(0), StopId(2), 70.0).unwrap();
sim.world_mut().set_patience(
rider,
Patience {
max_wait_ticks: 1,
waited_ticks: 0,
},
);
sim.step();
let phase = sim.world().rider(rider).map(|r| r.phase);
assert_eq!(
phase,
Some(RiderPhase::Abandoned),
"rider with max_wait_ticks=1 should abandon after 1 tick"
);
}
#[test]
fn patience_max_never_overflows() {
let config = default_config();
let mut sim = Simulation::new(&config, ScanDispatch::new()).unwrap();
let rider = sim.spawn_rider(StopId(0), StopId(2), 70.0).unwrap();
sim.world_mut().set_patience(
rider,
Patience {
max_wait_ticks: u64::MAX,
waited_ticks: u64::MAX - 1,
},
);
sim.step();
let phase = sim.world().rider(rider).map(|r| r.phase);
assert_eq!(phase, Some(RiderPhase::Abandoned));
}
#[test]
fn preferences_zero_crowding_rejects_any_load() {
let config = default_config();
let mut sim = Simulation::new(&config, ScanDispatch::new()).unwrap();
let r1 = sim.spawn_rider(StopId(0), StopId(2), 70.0).unwrap();
let r2 = sim.spawn_rider(StopId(0), StopId(2), 70.0).unwrap();
sim.world_mut().set_preferences(
r2,
Preferences {
skip_full_elevator: true,
max_crowding_factor: 0.0,
balk_threshold_ticks: None,
abandon_on_full: false,
},
);
for _ in 0..500 {
sim.step();
if let Some(r) = sim.world().rider(r1)
&& matches!(r.phase, RiderPhase::Riding(_) | RiderPhase::Arrived)
{
break;
}
}
let events = sim.drain_events();
let preference_rejections = events.iter().any(|e| {
matches!(
e,
Event::RiderRejected {
rider,
reason,
..
} if *rider == r2 && *reason == crate::error::RejectionReason::PreferenceBased
)
});
if sim
.world()
.rider(r1)
.is_some_and(|r| matches!(r.phase, RiderPhase::Riding(_) | RiderPhase::Arrived))
{
assert!(
preference_rejections,
"rider with max_crowding_factor=0.0 should be rejected when elevator has any load"
);
}
}
#[test]
fn weight_exactly_at_capacity_boards() {
let config = default_config();
let mut sim = Simulation::new(&config, ScanDispatch::new()).unwrap();
let rider = sim.spawn_rider(StopId(0), StopId(2), 800.0).unwrap();
let mut boarded = false;
for _ in 0..500 {
sim.step();
if let Some(r) = sim.world().rider(rider)
&& matches!(
r.phase,
RiderPhase::Boarding(_) | RiderPhase::Riding(_) | RiderPhase::Arrived
)
{
boarded = true;
break;
}
}
assert!(
boarded,
"rider weighing exactly capacity should be able to board"
);
}