//! Adversarial rule gates (TRAFFIC-06): shortcuts must not pass, and //! completion text must never confirm a behaviour the rules cannot see. //! //! Because every scenario is a labelled practice preview, these tests pin //! the HONEST behaviour: finish-only shortcuts complete as PRACTICE (with //! practice wording), and no verdict string claims sign compliance, //! yielding, correct exits, hazard response, or parking orientation. use makepad_widgets::Vec3f; use nigig_traffic::traffic::geom::CourseFrame; use nigig_traffic::traffic::objective::{evaluate, EvalContext, Verdict}; use nigig_traffic::traffic::road::{BayBox, BayOrientation, LaneBox, RoadMeta, RoundaboutBox}; use nigig_traffic::traffic::scenario::Objective; fn meta() -> RoadMeta { RoadMeta { finish: Vec3f { x: 40.0, y: 0.0, z: 0.0, }, finish_radius: 4.0, frame: CourseFrame::from_start_finish([0.0, 0.0], [40.0, 0.0]), lane_boxes: vec![LaneBox { center: Vec3f { x: 0.0, y: 0.0, z: -1.75, }, half: Vec3f { x: 20.0, y: 0.5, z: 1.75, }, lane_y: -1.75, }], bays: vec![BayBox { center: Vec3f { x: 6.0, y: 0.0, z: 5.25, }, half: Vec3f { x: 1.3, y: 0.1, z: 1.3, }, orientation: BayOrientation::Perpendicular, }], roundabout: Some(RoundaboutBox { center: Vec3f::default(), radius: 12.0, }), speed_limit_kmh: 50.0, } } fn at(meta: &RoadMeta, x: f32, speed: f32) -> EvalContext<'_> { let dx = x - 40.0; EvalContext { meta, player_pos: Vec3f { x, y: 0.0, z: 0.0 }, player_half: Vec3f { x: 0.9, y: 0.5, z: 1.9, }, player_speed: speed, player_forward_x: 1.0, player_yaw: -std::f32::consts::FRAC_PI_2, rel_heading: 0.0, hazard_ids: &[], nearest_hazard_clearance_sq: f32::MAX, dist_to_finish_sq: dx * dx, } } #[test] fn finish_only_shortcut_completes_as_practice_not_proof() { let m = meta(); // Roundabout: teleport to the finish without entering/circulating. let c = at(&m, 40.0, 8.0); let v = evaluate( &c, &Objective::Roundabout { enter: true, exit_at: Some(2), give_way: true, }, ); match v { Verdict::Complete(msg) => { assert!( msg.contains("Practice"), "must be labelled practice, got: {msg}" ); assert!( !msg.contains("signal") && !msg.contains("exit"), "must not confirm exit/signal behaviour, got: {msg}" ); } other => panic!("finish must complete practice, got {other:?}"), } } #[test] fn no_completion_text_claims_assessed_behaviour() { let m = meta(); let finish = at(&m, 40.0, 8.0); let cases = [ Objective::Roundabout { enter: true, exit_at: Some(2), give_way: true, }, Objective::Signs { signs: vec![nigig_traffic::traffic::road::SignType::Stop], }, Objective::Priority { yield_: true, stop: false, }, Objective::Hazards { avoid_collision: true, brake: true, }, Objective::Speed { limit_kmh: 50.0 }, Objective::FreeDrive, ]; // "obeyed", "respected", "avoided", "compliance", "correct exit", // "collision" (as a confirmed event) must not appear in completion text. let banned = [ "obeyed", "respected", "avoided", "compliance", "collision", "correct exit", "lanes held", ]; for o in &cases { if let Verdict::Complete(msg) = evaluate(&finish, o) { for b in banned { assert!(!msg.contains(b), "{o:?}: completion claims '{b}': {msg}"); } } else { panic!("{o:?}: finish must complete"); } } } #[test] fn wrong_bay_parking_cannot_complete() { let m = meta(); // Stopped far from the bay: mistake, never complete. let mut c = at(&m, 6.0, 0.0); c.player_pos = Vec3f { x: 6.0, y: 0.0, z: 30.0, }; assert_eq!( evaluate( &c, &Objective::Parking { bay_index: Some(0), orientation: BayOrientation::Perpendicular } ), Verdict::Mistake("Neat stop — position the car inside the bay first.") ); } #[test] fn forbidden_uturn_attempt_fails_even_as_practice() { let m = meta(); let mut c = at(&m, 10.0, 8.0); c.rel_heading = std::f64::consts::PI; c.player_forward_x = -1.0; assert_eq!( evaluate( &c, &Objective::UTurn { legal: false, open_area: true } ), Verdict::Fail("You attempted a forbidden U-turn.") ); }