//! `quality()` beyond two rating groups. //! //! The historical golden (two equal singletons) is asserted in //! `src/lib.rs::tests::test_quality`. These cover the N-group generalisation, //! which previously panicked with an out-of-bounds index at 3+ groups. use trueskill_tt::{Gaussian, quality}; const BETA: f64 = 25.0 / 3.0 / 2.0; fn rating(mu: f64, sigma: f64) -> Gaussian { Gaussian::from_ms(mu, sigma) } #[test] fn three_equal_groups_is_finite_and_in_range() { let r = rating(25.0, 3.0); let q = quality(&[&[r], &[r], &[r]], BETA); assert!(q.is_finite(), "quality must be finite, got {q}"); assert!((0.0..=1.0).contains(&q), "quality out of range: {q}"); } #[test] fn quality_supports_many_groups() { let r = rating(25.0, 3.0); for n in 2..=8 { let holders: Vec<[Gaussian; 1]> = (0..n).map(|_| [r]).collect(); let groups: Vec<&[Gaussian]> = holders.iter().map(|g| g.as_slice()).collect(); let q = quality(&groups, BETA); assert!(q.is_finite(), "n={n}: quality must be finite, got {q}"); assert!((0.0..=1.0).contains(&q), "n={n}: out of range: {q}"); } } /// Equal-strength groups are the best-matched case: introducing a skill gap /// must lower quality. #[test] fn imbalance_lowers_quality() { let strong = rating(40.0, 3.0); let average = rating(25.0, 3.0); let balanced = quality(&[&[average], &[average], &[average]], BETA); let lopsided = quality(&[&[strong], &[average], &[average]], BETA); assert!( lopsided < balanced, "expected imbalanced quality {lopsided} < balanced {balanced}" ); } /// Quality is a property of the multiset of groups, not their order. #[test] fn quality_is_permutation_invariant() { let a = rating(30.0, 2.0); let b = rating(25.0, 3.0); let c = rating(20.0, 4.0); let forward = quality(&[&[a], &[b], &[c]], BETA); let reversed = quality(&[&[c], &[b], &[a]], BETA); assert!( (forward - reversed).abs() < 1e-9, "permutation changed quality: {forward} vs {reversed}" ); } #[test] fn multi_player_groups_work() { let r = rating(25.0, 3.0); let q = quality(&[&[r, r], &[r, r], &[r, r]], BETA); assert!(q.is_finite()); assert!((0.0..=1.0).contains(&q)); } #[test] fn uneven_group_sizes_work() { let r = rating(25.0, 3.0); let q = quality(&[&[r, r], &[r], &[r, r, r]], BETA); assert!(q.is_finite(), "got {q}"); assert!((0.0..=1.0).contains(&q), "got {q}"); } #[test] #[should_panic(expected = "at least 2 rating groups")] fn single_group_panics_with_clear_message() { let r = rating(25.0, 3.0); let _ = quality(&[&[r]], BETA); } #[test] #[should_panic(expected = "at least 2 rating groups")] fn zero_groups_panics_with_clear_message() { let _ = quality(&[], BETA); } #[test] #[should_panic(expected = "non-empty")] fn empty_group_panics_with_clear_message() { let r = rating(25.0, 3.0); let _ = quality(&[&[r], &[]], BETA); } #[test] fn history_predict_quality_supports_three_teams() { use trueskill_tt::History; let mut h = History::default(); h.record_winner(&"a", &"b", 1).unwrap(); h.record_winner(&"b", &"c", 2).unwrap(); h.converge().unwrap(); let q = h.predict_quality(&[&[&"a"], &[&"b"], &[&"c"]]); assert!( q.is_finite(), "3-team predict_quality must be finite, got {q}" ); assert!((0.0..=1.0).contains(&q), "out of range: {q}"); }