90 lines
3.1 KiB
Rust
90 lines
3.1 KiB
Rust
//! Named success/violation replays per category (TRAFFIC-06/08 gate).
|
|
//!
|
|
//! Headless and deterministic: tick-indexed normalized input through the
|
|
//! production `TrafficWorld::tick` path. Every production scenario must
|
|
//! build, tick without panic, and keep a stable terminal result. Practice
|
|
//! honesty is pinned: finish-marker completion never claims an assessed
|
|
//! behaviour, and every hazard scenario spawns its actor.
|
|
|
|
use nigig_traffic::traffic::road::spawns_for;
|
|
use nigig_traffic::traffic::scenario::{all, Category};
|
|
use nigig_traffic::traffic::{Phase, TrafficWorld};
|
|
|
|
fn drive_until_settled(index: usize, ticks: u64) -> TrafficWorld {
|
|
let mut w = TrafficWorld::new();
|
|
w.load_scenario(index);
|
|
let input = makepad_game_blocks::DriveInput::default();
|
|
for _ in 0..ticks {
|
|
w.tick(&input);
|
|
}
|
|
w
|
|
}
|
|
|
|
#[test]
|
|
fn every_scenario_builds_ticks_and_settles_deterministically() {
|
|
let scenarios = all();
|
|
assert!(scenarios.len() >= 47, "catalog has {}", scenarios.len());
|
|
for (i, s) in scenarios.iter().enumerate() {
|
|
let a = drive_until_settled(i, 600);
|
|
let b = drive_until_settled(i, 600);
|
|
assert_eq!(a.run.tick, 600, "{}", s.id);
|
|
assert_eq!(a.run.phase, b.run.phase, "{}: nondeterministic", s.id);
|
|
assert_eq!(
|
|
a.verdict_counts(),
|
|
b.verdict_counts(),
|
|
"{}: nondeterministic",
|
|
s.id
|
|
);
|
|
// Idle input never completes or fails: the finish marker is far and
|
|
// no input was given. (A scenario that self-completes with no
|
|
// driving would be finish-circle laundering of nothing.)
|
|
assert!(
|
|
matches!(a.run.phase, Phase::Driving),
|
|
"{}: idle run left Driving ({:?})",
|
|
s.id,
|
|
a.run.phase
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn hazard_scenarios_spawn_their_actor() {
|
|
for s in all().iter().filter(|s| s.category == Category::Hazards) {
|
|
let spawns = spawns_for(s);
|
|
assert_eq!(spawns.len(), 1, "{}: hazard lesson with no hazard", s.id);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn priority_scenarios_field_a_crossing_car() {
|
|
for s in all().iter().filter(|s| s.category == Category::Priority) {
|
|
let spawns = spawns_for(s);
|
|
assert_eq!(spawns.len(), 1, "{}: priority lesson with no traffic", s.id);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn same_input_replay_gives_same_result_twice() {
|
|
// Deterministic attempts (invariant 10): fixed scenario + seeded start
|
|
// + tick-indexed input ⇒ same trace/result.
|
|
fn replay() -> (Phase, [u64; 4], i32, u32) {
|
|
let mut w = TrafficWorld::new();
|
|
w.load_scenario(0);
|
|
for i in 0..600 {
|
|
let input = makepad_game_blocks::DriveInput {
|
|
steer: if i % 120 < 60 { 0.5 } else { -0.5 },
|
|
throttle: if i < 400 { 1.0 } else { 0.0 },
|
|
brake: if i >= 400 { 1.0 } else { 0.0 },
|
|
..makepad_game_blocks::DriveInput::default()
|
|
};
|
|
w.tick(&input);
|
|
}
|
|
(
|
|
w.run.phase,
|
|
w.verdict_counts(),
|
|
w.run.score.total_points,
|
|
w.run.score.mistakes,
|
|
)
|
|
}
|
|
assert_eq!(replay(), replay());
|
|
}
|