1use super::{CriterionStatus, EddDemo, FalsificationStatus};
29use crate::engine::rng::SimRng;
30use serde::{Deserialize, Serialize};
31use std::collections::VecDeque;
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct KingmanHockeyDemo {
36 pub time: f64,
38 pub utilization: f64,
40 pub mean_service_time: f64,
42 pub cv_arrivals: f64,
44 pub cv_service: f64,
46 pub queue_length: usize,
48 pub total_wait_time: f64,
50 pub customers_served: u64,
52 #[serde(skip)]
54 arrival_times: VecDeque<f64>,
55 next_arrival: f64,
57 next_departure: Option<f64>,
59 pub history: Vec<(f64, f64, f64)>,
61 #[serde(skip)]
63 rng: Option<SimRng>,
64 pub seed: u64,
66}
67
68impl Default for KingmanHockeyDemo {
69 fn default() -> Self {
70 Self::new(42)
71 }
72}
73
74impl KingmanHockeyDemo {
75 #[must_use]
77 pub fn new(seed: u64) -> Self {
78 let mut demo = Self {
79 time: 0.0,
80 utilization: 0.5,
81 mean_service_time: 1.0,
82 cv_arrivals: 1.0, cv_service: 1.0, queue_length: 0,
85 total_wait_time: 0.0,
86 customers_served: 0,
87 arrival_times: VecDeque::new(),
88 next_arrival: 0.0,
89 next_departure: None,
90 history: Vec::new(),
91 rng: Some(SimRng::new(seed)),
92 seed,
93 };
94 demo.schedule_arrival();
95 demo
96 }
97
98 pub fn set_utilization(&mut self, rho: f64) {
100 self.utilization = rho.clamp(0.01, 0.99);
101 }
102
103 pub fn set_cv(&mut self, cv_arrivals: f64, cv_service: f64) {
105 self.cv_arrivals = cv_arrivals.max(0.1);
106 self.cv_service = cv_service.max(0.1);
107 }
108
109 #[must_use]
111 pub fn arrival_rate(&self) -> f64 {
112 self.utilization / self.mean_service_time
113 }
114
115 #[must_use]
117 pub fn service_rate(&self) -> f64 {
118 1.0 / self.mean_service_time
119 }
120
121 #[must_use]
123 pub fn kingman_prediction(&self) -> f64 {
124 let rho = self.utilization;
125 let ca2 = self.cv_arrivals * self.cv_arrivals;
126 let cs2 = self.cv_service * self.cv_service;
127 let ts = self.mean_service_time;
128
129 (rho / (1.0 - rho)) * ((ca2 + cs2) / 2.0) * ts
131 }
132
133 #[must_use]
135 pub fn linear_prediction(&self, base_util: f64, base_wait: f64) -> f64 {
136 let slope = base_wait / base_util;
139 slope * self.utilization
140 }
141
142 #[must_use]
144 pub fn average_wait_time(&self) -> f64 {
145 if self.customers_served > 0 {
146 self.total_wait_time / self.customers_served as f64
147 } else {
148 0.0
149 }
150 }
151
152 #[allow(clippy::option_if_let_else)]
154 fn generate_variate(&mut self, mean: f64, cv: f64) -> f64 {
155 if let Some(ref mut rng) = self.rng {
156 if (cv - 1.0).abs() < 0.01 {
157 let u: f64 = rng.gen_range_f64(0.0001, 1.0);
159 -mean * u.ln()
160 } else {
161 let variance = (cv * mean).powi(2);
165 let shape = mean * mean / variance;
166
167 let k = shape.round().max(1.0) as usize;
169 let lambda = k as f64 / mean;
170 let mut sum = 0.0;
171 for _ in 0..k {
172 let u: f64 = rng.gen_range_f64(0.0001, 1.0);
173 sum += -u.ln() / lambda;
174 }
175 sum
176 }
177 } else {
178 mean
179 }
180 }
181
182 fn schedule_arrival(&mut self) {
184 let mean_interarrival = 1.0 / self.arrival_rate();
185 let interarrival = self.generate_variate(mean_interarrival, self.cv_arrivals);
186 self.next_arrival = self.time + interarrival;
187 }
188
189 fn schedule_departure(&mut self) {
191 let service_time = self.generate_variate(self.mean_service_time, self.cv_service);
192 self.next_departure = Some(self.time + service_time);
193 }
194
195 fn process_arrival(&mut self) {
197 let server_idle = self.next_departure.is_none();
199
200 if server_idle {
201 self.customers_served += 1;
203 self.total_wait_time += 0.0;
205 self.schedule_departure();
206 } else {
207 self.arrival_times.push_back(self.time);
209 self.queue_length += 1;
210 }
211
212 self.schedule_arrival();
213 }
214
215 fn process_departure(&mut self) {
217 if self.queue_length > 0 {
221 self.queue_length -= 1;
222 self.customers_served += 1;
223
224 if let Some(arrival_time) = self.arrival_times.pop_front() {
226 let queue_wait_time = self.time - arrival_time;
227 self.total_wait_time += queue_wait_time;
228 }
229
230 self.schedule_departure();
232 } else {
233 self.next_departure = None;
235 }
236 }
237
238 #[allow(clippy::while_float)]
240 pub fn run_until(&mut self, end_time: f64) {
241 while self.time < end_time {
242 self.step(0.0);
243 }
244 }
245
246 pub fn build_hockey_stick(&mut self, utilization_levels: &[f64], sim_time: f64) {
248 let mut results = Vec::with_capacity(utilization_levels.len());
249
250 for &rho in utilization_levels {
251 self.reset();
253 self.set_utilization(rho);
254
255 self.run_until(sim_time);
257
258 results.push((rho, self.average_wait_time(), self.kingman_prediction()));
260 }
261
262 self.history = results;
264 }
265
266 #[must_use]
268 pub fn calculate_r_squared(&self) -> f64 {
269 if self.history.len() < 3 {
270 return 0.0;
271 }
272
273 let n = self.history.len() as f64;
275 let mean_sim: f64 = self.history.iter().map(|(_, sim, _)| sim).sum::<f64>() / n;
276
277 let ss_tot: f64 = self
278 .history
279 .iter()
280 .map(|(_, sim, _)| (sim - mean_sim).powi(2))
281 .sum();
282
283 let ss_res: f64 = self
284 .history
285 .iter()
286 .map(|(_, sim, pred)| (sim - pred).powi(2))
287 .sum();
288
289 if ss_tot > f64::EPSILON {
290 1.0 - (ss_res / ss_tot)
291 } else {
292 0.0
293 }
294 }
295
296 #[must_use]
298 pub fn hockey_stick_ratio(&self) -> f64 {
299 let wait_50 = self
300 .history
301 .iter()
302 .find(|(rho, _, _)| (*rho - 0.5).abs() < 0.05)
303 .map(|(_, w, _)| *w);
304
305 let wait_95 = self
306 .history
307 .iter()
308 .find(|(rho, _, _)| (*rho - 0.95).abs() < 0.05)
309 .map(|(_, w, _)| *w);
310
311 match (wait_50, wait_95) {
312 (Some(w50), Some(w95)) if w50 > f64::EPSILON => w95 / w50,
313 _ => 0.0,
314 }
315 }
316}
317
318impl EddDemo for KingmanHockeyDemo {
319 fn name(&self) -> &'static str {
320 "Kingman's Hockey Stick"
321 }
322
323 fn emc_ref(&self) -> &'static str {
324 "operations/kingmans_formula"
325 }
326
327 fn step(&mut self, _dt: f64) {
328 let next_arrival = self.next_arrival;
330 let next_departure = self.next_departure;
331
332 let is_arrival = next_departure.is_none_or(|dep| next_arrival <= dep);
334
335 if is_arrival {
336 self.time = next_arrival;
337 self.process_arrival();
338 } else if let Some(dep) = next_departure {
339 self.time = dep;
340 self.process_departure();
341 }
342 }
343
344 fn verify_equation(&self) -> bool {
345 let ratio = self.hockey_stick_ratio();
346 let r_squared = self.calculate_r_squared();
347
348 ratio > 10.0 && r_squared > 0.90
349 }
350
351 fn get_falsification_status(&self) -> FalsificationStatus {
352 let ratio = self.hockey_stick_ratio();
353 let r_squared = self.calculate_r_squared();
354
355 let hockey_passed = ratio > 10.0;
356 let fit_passed = r_squared > 0.90;
357
358 FalsificationStatus {
359 verified: hockey_passed && fit_passed,
360 criteria: vec![
361 CriterionStatus {
362 id: "KF-HOCKEY".to_string(),
363 name: "Hockey stick shape".to_string(),
364 passed: hockey_passed,
365 value: ratio,
366 threshold: 10.0,
367 },
368 CriterionStatus {
369 id: "KF-FIT".to_string(),
370 name: "Kingman fit R²".to_string(),
371 passed: fit_passed,
372 value: r_squared,
373 threshold: 0.90,
374 },
375 ],
376 message: if hockey_passed && fit_passed {
377 format!("Kingman verified: wait_95/wait_50={ratio:.1}×, R²={r_squared:.4}")
378 } else if !hockey_passed {
379 format!("Hockey stick not pronounced: ratio={ratio:.1}× (expected >10×)")
380 } else {
381 format!("Kingman fit poor: R²={r_squared:.4} (expected >0.90)")
382 },
383 }
384 }
385
386 fn reset(&mut self) {
387 let seed = self.seed;
388 let cv_a = self.cv_arrivals;
389 let cv_s = self.cv_service;
390
391 *self = Self::new(seed);
392 self.cv_arrivals = cv_a;
393 self.cv_service = cv_s;
394 }
395}
396
397#[cfg(feature = "wasm")]
402mod wasm {
403 use super::{EddDemo, KingmanHockeyDemo};
404 use wasm_bindgen::prelude::*;
405
406 #[wasm_bindgen]
407 pub struct WasmKingmanHockey {
408 inner: KingmanHockeyDemo,
409 }
410
411 #[wasm_bindgen]
412 impl WasmKingmanHockey {
413 #[wasm_bindgen(constructor)]
414 pub fn new(seed: u64) -> Self {
415 Self {
416 inner: KingmanHockeyDemo::new(seed),
417 }
418 }
419
420 pub fn step(&mut self) {
421 self.inner.step(0.0);
422 }
423
424 pub fn get_utilization(&self) -> f64 {
425 self.inner.utilization
426 }
427
428 pub fn get_queue_length(&self) -> usize {
429 self.inner.queue_length
430 }
431
432 pub fn get_average_wait(&self) -> f64 {
433 self.inner.average_wait_time()
434 }
435
436 pub fn get_kingman_prediction(&self) -> f64 {
437 self.inner.kingman_prediction()
438 }
439
440 pub fn set_utilization(&mut self, rho: f64) {
441 self.inner.set_utilization(rho);
442 }
443
444 pub fn set_cv(&mut self, cv_arrivals: f64, cv_service: f64) {
445 self.inner.set_cv(cv_arrivals, cv_service);
446 }
447
448 pub fn verify_equation(&self) -> bool {
449 self.inner.verify_equation()
450 }
451
452 pub fn reset(&mut self) {
453 self.inner.reset();
454 }
455
456 pub fn get_status_json(&self) -> String {
457 serde_json::to_string(&self.inner.get_falsification_status()).unwrap_or_default()
458 }
459 }
460}
461
462#[cfg(test)]
467mod tests {
468 use super::*;
469
470 #[test]
475 fn test_equation_kingman_formula() {
476 let demo = KingmanHockeyDemo::new(42);
477
478 let rho = demo.utilization;
480 let ca2 = demo.cv_arrivals * demo.cv_arrivals;
481 let cs2 = demo.cv_service * demo.cv_service;
482 let ts = demo.mean_service_time;
483
484 let expected = (rho / (1.0 - rho)) * ((ca2 + cs2) / 2.0) * ts;
485 let prediction = demo.kingman_prediction();
486
487 assert!((expected - prediction).abs() < 1e-10);
488 }
489
490 #[test]
491 fn test_equation_hyperbolic_growth() {
492 let values = [0.5, 0.7, 0.9, 0.95, 0.99];
494 let factors: Vec<f64> = values.iter().map(|&rho| rho / (1.0 - rho)).collect();
495
496 assert!((factors[0] - 1.0).abs() < 0.01);
500 assert!((factors[2] - 9.0).abs() < 0.01);
501 assert!((factors[4] - 99.0).abs() < 1.0);
502 }
503
504 #[test]
509 fn test_failing_linear_underestimates() {
510 let mut demo = KingmanHockeyDemo::new(42);
511
512 demo.set_utilization(0.5);
514 demo.run_until(1000.0);
515 let wait_50 = demo.average_wait_time();
516
517 let linear_95 = demo.linear_prediction(0.5, wait_50);
519 demo.set_utilization(0.95);
520
521 let kingman_95 = demo.kingman_prediction();
523
524 assert!(
526 kingman_95 > linear_95 * 5.0,
527 "Kingman {} should be >>5x linear {}",
528 kingman_95,
529 linear_95
530 );
531 }
532
533 #[test]
538 fn test_verification_kingman_accuracy() {
539 let mut demo = KingmanHockeyDemo::new(42);
540
541 let utils = [0.5, 0.7, 0.85];
543
544 for &rho in &utils {
545 demo.reset();
546 demo.set_utilization(rho);
547 demo.run_until(5000.0);
548
549 let simulated = demo.average_wait_time();
550 let predicted = demo.kingman_prediction();
551
552 let error = (simulated - predicted).abs() / predicted;
554 assert!(
555 error < 0.5,
556 "At ρ={rho}: simulated={simulated:.2}, predicted={predicted:.2}, error={:.0}%",
557 error * 100.0
558 );
559 }
560 }
561
562 #[test]
567 fn test_verification_hockey_stick() {
568 let mut demo = KingmanHockeyDemo::new(42);
569
570 let utilizations = vec![0.3, 0.5, 0.7, 0.8, 0.85, 0.9, 0.95];
571 demo.build_hockey_stick(&utilizations, 2000.0);
572
573 let ratio = demo.hockey_stick_ratio();
574 assert!(
575 ratio > 5.0,
576 "Hockey stick ratio should be >5, got {ratio:.1}"
577 );
578 }
579
580 #[test]
581 fn test_verification_exponential_growth() {
582 let mut demo = KingmanHockeyDemo::new(42);
583
584 let utils = [0.5, 0.7, 0.85, 0.9, 0.95];
586 let mut waits = Vec::new();
587
588 for &rho in &utils {
589 demo.reset();
590 demo.set_utilization(rho);
591 demo.run_until(3000.0);
592 waits.push(demo.average_wait_time());
593 }
594
595 for i in 1..waits.len() - 1 {
597 let delta_prev = waits[i] - waits[i - 1];
598 let delta_curr = waits[i + 1] - waits[i];
599 println!(
601 "Util {:.0}%→{:.0}%: Δ={:.2}, {:.0}%→{:.0}%: Δ={:.2}",
602 utils[i - 1] * 100.0,
603 utils[i] * 100.0,
604 delta_prev,
605 utils[i] * 100.0,
606 utils[i + 1] * 100.0,
607 delta_curr
608 );
609 }
610 }
611
612 #[test]
617 fn test_falsification_high_cv() {
618 let mut low_cv = KingmanHockeyDemo::new(42);
620 low_cv.set_cv(0.5, 0.5);
621 low_cv.set_utilization(0.8);
622 low_cv.run_until(3000.0);
623 let wait_low = low_cv.average_wait_time();
624
625 let mut high_cv = KingmanHockeyDemo::new(42);
626 high_cv.set_cv(1.5, 1.5);
627 high_cv.set_utilization(0.8);
628 high_cv.run_until(3000.0);
629 let wait_high = high_cv.average_wait_time();
630
631 assert!(
633 wait_high > wait_low,
634 "High CV wait {wait_high:.2} should > low CV wait {wait_low:.2}"
635 );
636 }
637
638 #[test]
639 fn test_falsification_status_structure() {
640 let demo = KingmanHockeyDemo::new(42);
641 let status = demo.get_falsification_status();
642
643 assert_eq!(status.criteria.len(), 2);
644 assert_eq!(status.criteria[0].id, "KF-HOCKEY");
645 assert_eq!(status.criteria[1].id, "KF-FIT");
646 }
647
648 #[test]
653 fn test_demo_trait_implementation() {
654 let mut demo = KingmanHockeyDemo::new(42);
655
656 assert_eq!(demo.name(), "Kingman's Hockey Stick");
657 assert_eq!(demo.emc_ref(), "operations/kingmans_formula");
658
659 demo.step(0.0);
660 assert!(demo.time > 0.0);
661
662 demo.reset();
663 assert_eq!(demo.time, 0.0);
664 }
665
666 #[test]
667 fn test_reproducibility() {
668 let mut demo1 = KingmanHockeyDemo::new(42);
669 let mut demo2 = KingmanHockeyDemo::new(42);
670
671 demo1.run_until(100.0);
672 demo2.run_until(100.0);
673
674 assert_eq!(demo1.customers_served, demo2.customers_served);
675 }
676
677 #[test]
682 fn test_default() {
683 let demo = KingmanHockeyDemo::default();
684 assert_eq!(demo.seed, 42);
685 assert!((demo.utilization - 0.5).abs() < 1e-10);
686 }
687
688 #[test]
689 fn test_clone() {
690 let demo = KingmanHockeyDemo::new(42);
691 let cloned = demo.clone();
692 assert_eq!(demo.seed, cloned.seed);
693 assert!((demo.utilization - cloned.utilization).abs() < 1e-10);
694 }
695
696 #[test]
697 fn test_debug() {
698 let demo = KingmanHockeyDemo::new(42);
699 let debug_str = format!("{demo:?}");
700 assert!(debug_str.contains("KingmanHockeyDemo"));
701 }
702
703 #[test]
704 fn test_serialization() {
705 let demo = KingmanHockeyDemo::new(42);
706 let json = serde_json::to_string(&demo).expect("serialize");
707 assert!(json.contains("utilization"));
708
709 let restored: KingmanHockeyDemo = serde_json::from_str(&json).expect("deserialize");
710 assert!((restored.utilization - demo.utilization).abs() < 1e-10);
711 }
712
713 #[test]
714 fn test_set_utilization_clamping() {
715 let mut demo = KingmanHockeyDemo::new(42);
716
717 demo.set_utilization(0.001);
719 assert!((demo.utilization - 0.01).abs() < 1e-10);
720
721 demo.set_utilization(1.5);
723 assert!((demo.utilization - 0.99).abs() < 1e-10);
724
725 demo.set_utilization(0.75);
727 assert!((demo.utilization - 0.75).abs() < 1e-10);
728 }
729
730 #[test]
731 fn test_set_cv_clamping() {
732 let mut demo = KingmanHockeyDemo::new(42);
733
734 demo.set_cv(0.01, 0.01);
736 assert!((demo.cv_arrivals - 0.1).abs() < 1e-10);
737 assert!((demo.cv_service - 0.1).abs() < 1e-10);
738
739 demo.set_cv(2.0, 1.5);
741 assert!((demo.cv_arrivals - 2.0).abs() < 1e-10);
742 assert!((demo.cv_service - 1.5).abs() < 1e-10);
743 }
744
745 #[test]
746 fn test_arrival_rate() {
747 let mut demo = KingmanHockeyDemo::new(42);
748 demo.set_utilization(0.8);
749 demo.mean_service_time = 2.0;
750
751 let rate = demo.arrival_rate();
753 assert!((rate - 0.4).abs() < 1e-10);
754 }
755
756 #[test]
757 fn test_service_rate() {
758 let mut demo = KingmanHockeyDemo::new(42);
759 demo.mean_service_time = 2.0;
760
761 let rate = demo.service_rate();
763 assert!((rate - 0.5).abs() < 1e-10);
764 }
765
766 #[test]
767 fn test_average_wait_time_zero_customers() {
768 let demo = KingmanHockeyDemo::new(42);
769 assert!((demo.average_wait_time() - 0.0).abs() < 1e-10);
771 }
772
773 #[test]
774 fn test_calculate_r_squared_insufficient_data() {
775 let mut demo = KingmanHockeyDemo::new(42);
776 demo.history = vec![(0.5, 1.0, 1.1)];
778 assert!((demo.calculate_r_squared() - 0.0).abs() < 1e-10);
779
780 demo.history = vec![(0.5, 1.0, 1.1), (0.7, 2.0, 2.1)];
781 assert!((demo.calculate_r_squared() - 0.0).abs() < 1e-10);
782 }
783
784 #[test]
785 fn test_calculate_r_squared_perfect_fit() {
786 let mut demo = KingmanHockeyDemo::new(42);
787 demo.history = vec![(0.5, 1.0, 1.0), (0.7, 2.0, 2.0), (0.9, 9.0, 9.0)];
789 let r2 = demo.calculate_r_squared();
790 assert!((r2 - 1.0).abs() < 1e-10, "R² should be 1.0, got {r2}");
791 }
792
793 #[test]
794 fn test_calculate_r_squared_zero_variance() {
795 let mut demo = KingmanHockeyDemo::new(42);
796 demo.history = vec![(0.5, 1.0, 1.1), (0.7, 1.0, 1.2), (0.9, 1.0, 1.3)];
798 let r2 = demo.calculate_r_squared();
799 assert!((r2 - 0.0).abs() < 1e-10);
800 }
801
802 #[test]
803 fn test_hockey_stick_ratio_no_data() {
804 let mut demo = KingmanHockeyDemo::new(42);
805 demo.history = vec![];
806 assert!((demo.hockey_stick_ratio() - 0.0).abs() < 1e-10);
807 }
808
809 #[test]
810 fn test_hockey_stick_ratio_missing_50() {
811 let mut demo = KingmanHockeyDemo::new(42);
812 demo.history = vec![(0.9, 5.0, 5.0), (0.95, 10.0, 10.0)];
813 assert!((demo.hockey_stick_ratio() - 0.0).abs() < 1e-10);
814 }
815
816 #[test]
817 fn test_hockey_stick_ratio_missing_95() {
818 let mut demo = KingmanHockeyDemo::new(42);
819 demo.history = vec![(0.5, 1.0, 1.0), (0.7, 2.0, 2.0)];
820 assert!((demo.hockey_stick_ratio() - 0.0).abs() < 1e-10);
821 }
822
823 #[test]
824 fn test_hockey_stick_ratio_zero_wait_50() {
825 let mut demo = KingmanHockeyDemo::new(42);
826 demo.history = vec![(0.5, 0.0, 0.1), (0.95, 10.0, 10.0)];
827 assert!((demo.hockey_stick_ratio() - 0.0).abs() < 1e-10);
828 }
829
830 #[test]
831 fn test_linear_prediction() {
832 let mut demo = KingmanHockeyDemo::new(42);
833 demo.set_utilization(0.9);
834
835 let linear = demo.linear_prediction(0.5, 1.0);
837 assert!((linear - 1.8).abs() < 1e-10);
839 }
840
841 #[test]
842 fn test_process_departure_empty_queue() {
843 let mut demo = KingmanHockeyDemo::new(42);
844 demo.run_until(10.0);
846 }
848
849 #[test]
850 fn test_high_cv_gamma_distribution() {
851 let mut demo = KingmanHockeyDemo::new(42);
852 demo.set_cv(0.3, 0.3); demo.run_until(100.0);
854 assert!(demo.customers_served > 0);
855 }
856
857 #[test]
858 fn test_reset_preserves_cv() {
859 let mut demo = KingmanHockeyDemo::new(42);
860 demo.set_cv(2.0, 1.5);
861 demo.run_until(100.0);
862 demo.reset();
863
864 assert!((demo.cv_arrivals - 2.0).abs() < 1e-10);
865 assert!((demo.cv_service - 1.5).abs() < 1e-10);
866 assert_eq!(demo.time, 0.0);
867 }
868
869 #[test]
870 fn test_run_until_boundary() {
871 let mut demo = KingmanHockeyDemo::new(42);
872 demo.run_until(0.0);
873 assert!(demo.time >= 0.0);
875 }
876
877 #[test]
878 fn test_step_multiple() {
879 let mut demo = KingmanHockeyDemo::new(42);
880 for _ in 0..100 {
881 demo.step(0.0);
882 }
883 assert!(demo.time > 0.0);
884 assert!(demo.customers_served > 0);
885 }
886
887 #[test]
888 fn test_falsification_status_failed_hockey() {
889 let mut demo = KingmanHockeyDemo::new(42);
890 demo.history = vec![
892 (0.5, 1.0, 1.0),
893 (0.95, 5.0, 19.0), ];
895 let status = demo.get_falsification_status();
896 assert!(!status.verified);
897 assert!(status.message.contains("Hockey stick not pronounced"));
898 }
899
900 #[test]
901 fn test_falsification_status_failed_fit() {
902 let mut demo = KingmanHockeyDemo::new(42);
903 demo.history = vec![
905 (0.5, 1.0, 5.0), (0.7, 2.0, 10.0), (0.95, 19.0, 50.0), ];
909 let status = demo.get_falsification_status();
910 assert!(status.message.contains("R²") || !status.verified);
912 }
913
914 #[test]
915 fn test_queue_dynamics() {
916 let mut demo = KingmanHockeyDemo::new(42);
917 demo.set_utilization(0.95); demo.run_until(500.0);
919
920 assert!(demo.customers_served > 10);
922 }
923}