190 lines
5.2 KiB
Rust
190 lines
5.2 KiB
Rust
//! 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.")
|
|
);
|
|
}
|