1use super::ConfidenceInterval;
21use std::time::Duration;
22
23#[cfg(feature = "gpu")]
24use trueno::backends::gpu::GpuDevice;
25
26const PCG_MULTIPLIER: u32 = 747796405;
28const PCG_INCREMENT: u32 = 2891336453;
29const PCG_OUTPUT_MUL: u32 = 277803737;
30
31#[derive(Debug, Clone)]
33pub struct WasmDemoConfig {
34 pub width: u32,
36 pub height: u32,
38 pub fill_probability: f32,
40 pub target_coverage: f32,
42 pub seed: u64,
44 pub palette: DemoPalette,
46}
47
48impl Default for WasmDemoConfig {
49 fn default() -> Self {
50 Self {
51 width: 1920,
52 height: 1080,
53 fill_probability: 0.01,
54 target_coverage: 0.99,
55 seed: 42,
56 palette: DemoPalette::Viridis,
57 }
58 }
59}
60
61impl WasmDemoConfig {
62 #[must_use]
64 pub fn hd_1080p() -> Self {
65 Self::default()
66 }
67
68 #[must_use]
70 pub fn hd_720p() -> Self {
71 Self {
72 width: 1280,
73 height: 720,
74 ..Self::default()
75 }
76 }
77
78 #[must_use]
80 pub fn test_small() -> Self {
81 Self {
82 width: 100,
83 height: 100,
84 fill_probability: 0.1,
85 seed: 42,
86 ..Self::default()
87 }
88 }
89
90 #[must_use]
92 pub fn with_seed(mut self, seed: u64) -> Self {
93 self.seed = seed;
94 self
95 }
96
97 #[must_use]
99 pub fn with_fill_probability(mut self, prob: f32) -> Self {
100 self.fill_probability = prob.clamp(0.0, 1.0);
101 self
102 }
103
104 pub fn validate(&self) -> Result<(), ConfigError> {
106 if self.width == 0 || self.height == 0 {
107 return Err(ConfigError::InvalidDimensions {
108 width: self.width,
109 height: self.height,
110 });
111 }
112 if !(0.0..=1.0).contains(&self.fill_probability) {
113 return Err(ConfigError::InvalidProbability(self.fill_probability));
114 }
115 if !(0.0..=1.0).contains(&self.target_coverage) {
116 return Err(ConfigError::InvalidTargetCoverage(self.target_coverage));
117 }
118 Ok(())
119 }
120}
121
122#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
124pub enum DemoPalette {
125 #[default]
127 Viridis,
128 Magma,
130 Heat,
132 Grayscale,
134}
135
136#[derive(Debug, Clone, Copy, PartialEq, Eq)]
138pub enum GapSeverity {
139 Info,
141 Warning,
143 Critical,
145}
146
147#[derive(Debug, Clone)]
149pub struct DemoGapRegion {
150 pub x: usize,
152 pub y: usize,
154 pub width: usize,
156 pub height: usize,
158 pub size: usize,
160 pub severity: GapSeverity,
162}
163
164#[derive(Debug, Clone, PartialEq)]
166pub enum ConfigError {
167 InvalidDimensions {
169 width: u32,
171 height: u32,
173 },
174 InvalidProbability(f32),
176 InvalidTargetCoverage(f32),
178}
179
180impl std::fmt::Display for ConfigError {
181 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
182 match self {
183 Self::InvalidDimensions { width, height } => {
184 write!(f, "Invalid dimensions: {}x{} (must be > 0)", width, height)
185 }
186 Self::InvalidProbability(p) => {
187 write!(f, "Invalid probability: {} (must be 0.0..=1.0)", p)
188 }
189 Self::InvalidTargetCoverage(c) => {
190 write!(f, "Invalid target coverage: {} (must be 0.0..=1.0)", c)
191 }
192 }
193 }
194}
195
196impl std::error::Error for ConfigError {}
197
198#[derive(Debug, Clone)]
202pub struct PcgRng {
203 state: u64,
204 increment: u64,
205}
206
207impl PcgRng {
208 #[must_use]
210 pub fn new(seed: u64) -> Self {
211 let mut rng = Self {
212 state: 0,
213 increment: (seed << 1) | 1, };
215 let _ = rng.next_u32();
217 rng.state = rng.state.wrapping_add(seed);
218 let _ = rng.next_u32();
219 rng
220 }
221
222 #[must_use]
224 pub fn next_u32(&mut self) -> u32 {
225 let old_state = self.state;
226
227 self.state = old_state
229 .wrapping_mul(u64::from(PCG_MULTIPLIER))
230 .wrapping_add(self.increment);
231
232 let xorshifted = ((old_state >> 18) ^ old_state) >> 27;
234 let rot = (old_state >> 59) as u32;
235
236 #[allow(clippy::cast_possible_truncation)]
237 let result = xorshifted as u32;
238 result.rotate_right(rot)
239 }
240
241 #[must_use]
243 pub fn next_f32(&mut self) -> f32 {
244 (self.next_u32() as f64 / u32::MAX as f64) as f32
245 }
246
247 #[must_use]
249 pub fn hash_pixel(seed: u32, index: u32, frame: u32) -> u32 {
250 let input = seed ^ index ^ frame.wrapping_mul(12345);
251 let state = input
252 .wrapping_mul(PCG_MULTIPLIER)
253 .wrapping_add(PCG_INCREMENT);
254 let word =
255 ((state >> ((state >> 28).wrapping_add(4))) ^ state).wrapping_mul(PCG_OUTPUT_MUL);
256 (word >> 22) ^ word
257 }
258
259 #[must_use]
261 pub fn should_fill(seed: u32, index: u32, frame: u32, probability: f32) -> bool {
262 let hash = Self::hash_pixel(seed, index, frame);
263 let random_value = hash as f32 / u32::MAX as f32;
264 random_value < probability
265 }
266}
267
268#[derive(Debug, Clone)]
273pub struct GpuPixelBuffer {
274 pub pixels: Vec<f32>,
276 pub width: u32,
278 pub height: u32,
280 pub frame: u32,
282 pub seed: u32,
284 pub using_gpu: bool,
286}
287
288#[cfg(feature = "gpu")]
290pub struct GpuAccelerator {
291 device: GpuDevice,
292}
293
294#[cfg(feature = "gpu")]
295impl GpuAccelerator {
296 pub fn new() -> Result<Self, String> {
298 let device = GpuDevice::new()?;
299 Ok(Self { device })
300 }
301
302 #[allow(dead_code)]
304 pub fn is_available() -> bool {
305 GpuDevice::is_available()
306 }
307
308 pub fn parallel_fill(
314 &self,
315 pixels: &mut [f32],
316 width: u32,
317 height: u32,
318 frame: u32,
319 seed: u32,
320 probability: f32,
321 ) -> Result<(), String> {
322 let total = pixels.len();
323
324 let random_values: Vec<f32> = (0..total)
326 .map(|idx| {
327 let hash = PcgRng::hash_pixel(seed, idx as u32, frame);
328 hash as f32 / u32::MAX as f32
329 })
330 .collect();
331
332 let gradient_values: Vec<f32> = (0..total)
334 .map(|idx| {
335 let x = idx as u32 % width;
336 let y = idx as u32 / width;
337 ((x + y) as f32 / (width + height) as f32).max(0.001)
338 })
339 .collect();
340
341 if total > 100_000 {
344 let scaled: Vec<f32> = random_values
348 .iter()
349 .map(|&r| (probability - r) * 100.0) .collect();
351
352 let mut mask = vec![0.0f32; total];
353 self.device.sigmoid(&scaled, &mut mask)?;
355
356 for (idx, pixel) in pixels.iter_mut().enumerate() {
358 if *pixel == 0.0 && mask[idx] > 0.5 {
359 *pixel = gradient_values[idx];
360 }
361 }
362 } else {
363 for (idx, pixel) in pixels.iter_mut().enumerate() {
365 if *pixel == 0.0 && random_values[idx] < probability {
366 *pixel = gradient_values[idx];
367 }
368 }
369 }
370
371 Ok(())
372 }
373}
374
375impl GpuPixelBuffer {
376 #[must_use]
378 pub fn new(width: u32, height: u32, seed: u64) -> Self {
379 let total_pixels = (width as usize) * (height as usize);
380 let using_gpu = Self::gpu_available();
381
382 Self {
383 pixels: vec![0.0; total_pixels],
384 width,
385 height,
386 frame: 0,
387 seed: (seed & 0xFFFF_FFFF) as u32,
388 using_gpu,
389 }
390 }
391
392 #[must_use]
394 pub fn gpu_available() -> bool {
395 #[cfg(feature = "gpu")]
396 {
397 GpuDevice::is_available()
398 }
399 #[cfg(not(feature = "gpu"))]
400 {
401 false
402 }
403 }
404
405 #[must_use]
407 pub fn gpu_device_name() -> Option<String> {
408 #[cfg(feature = "gpu")]
409 {
410 if GpuDevice::is_available() {
411 Some("wgpu (Vulkan/Metal/DX12)".to_string())
414 } else {
415 None
416 }
417 }
418 #[cfg(not(feature = "gpu"))]
419 {
420 None
421 }
422 }
423
424 #[must_use]
426 pub fn new_1080p() -> Self {
427 Self::new(1920, 1080, 42)
428 }
429
430 #[must_use]
432 pub fn new_720p() -> Self {
433 Self::new(1280, 720, 42)
434 }
435
436 #[must_use]
438 pub fn total_pixels(&self) -> usize {
439 self.pixels.len()
440 }
441
442 pub fn random_fill_pass(&mut self, probability: f32) {
444 self.frame += 1;
445
446 #[cfg(feature = "gpu")]
447 {
448 if self.using_gpu {
449 if let Ok(accelerator) = GpuAccelerator::new() {
450 if accelerator
451 .parallel_fill(
452 &mut self.pixels,
453 self.width,
454 self.height,
455 self.frame,
456 self.seed,
457 probability,
458 )
459 .is_ok()
460 {
461 return;
462 }
463 }
464 }
465 }
466
467 self.random_fill_pass_cpu(probability);
469 }
470
471 fn random_fill_pass_cpu(&mut self, probability: f32) {
473 let frame = self.frame;
474 let seed = self.seed;
475
476 for (idx, pixel) in self.pixels.iter_mut().enumerate() {
477 if *pixel == 0.0 && PcgRng::should_fill(seed, idx as u32, frame, probability) {
478 let x = idx as u32 % self.width;
480 let y = idx as u32 / self.width;
481 let normalized = (x + y) as f32 / (self.width + self.height) as f32;
482 *pixel = normalized.max(0.001); }
484 }
485 }
486
487 pub fn fill_to_coverage(&mut self, target: f32, probability: f32, max_frames: u32) {
489 for _ in 0..max_frames {
490 self.random_fill_pass(probability);
491 if self.coverage_percentage() >= target {
492 break;
493 }
494 }
495 }
496
497 #[must_use]
499 pub fn coverage_stats(&self) -> CoverageStats {
500 let covered = self.pixels.iter().filter(|&&v| v > 0.0).count();
501 let total = self.pixels.len();
502 let percentage = covered as f32 / total as f32;
503
504 CoverageStats {
505 covered,
506 total,
507 percentage,
508 wilson_ci: wilson_confidence_interval(covered, total, 0.95),
509 gaps: self.find_gaps(),
510 }
511 }
512
513 #[must_use]
515 pub fn coverage_percentage(&self) -> f32 {
516 let covered = self.pixels.iter().filter(|&&v| v > 0.0).count();
517 covered as f32 / self.pixels.len() as f32
518 }
519
520 #[must_use]
522 pub fn find_gaps(&self) -> Vec<DemoGapRegion> {
523 let mut gaps = Vec::new();
524 let mut visited = vec![false; self.pixels.len()];
525
526 for y in 0..self.height {
527 for x in 0..self.width {
528 let idx = (y * self.width + x) as usize;
529 if self.pixels[idx] == 0.0 && !visited[idx] {
530 let gap = self.flood_fill_gap(x, y, &mut visited);
532 if gap.size > 0 {
533 gaps.push(gap);
534 }
535 }
536 }
537 }
538
539 gaps
540 }
541
542 fn flood_fill_gap(&self, start_x: u32, start_y: u32, visited: &mut [bool]) -> DemoGapRegion {
544 let mut stack = vec![(start_x, start_y)];
545 let mut min_x = start_x;
546 let mut max_x = start_x;
547 let mut min_y = start_y;
548 let mut max_y = start_y;
549 let mut size = 0;
550
551 while let Some((x, y)) = stack.pop() {
552 if x >= self.width || y >= self.height {
553 continue;
554 }
555 let idx = (y * self.width + x) as usize;
556 if visited[idx] || self.pixels[idx] > 0.0 {
557 continue;
558 }
559
560 visited[idx] = true;
561 size += 1;
562 min_x = min_x.min(x);
563 max_x = max_x.max(x);
564 min_y = min_y.min(y);
565 max_y = max_y.max(y);
566
567 if x > 0 {
569 stack.push((x - 1, y));
570 }
571 if x < self.width - 1 {
572 stack.push((x + 1, y));
573 }
574 if y > 0 {
575 stack.push((x, y - 1));
576 }
577 if y < self.height - 1 {
578 stack.push((x, y + 1));
579 }
580 }
581
582 DemoGapRegion {
583 x: min_x as usize,
584 y: min_y as usize,
585 width: (max_x - min_x + 1) as usize,
586 height: (max_y - min_y + 1) as usize,
587 size,
588 severity: if size > 100 {
589 GapSeverity::Critical
590 } else if size > 25 {
591 GapSeverity::Warning
592 } else {
593 GapSeverity::Info
594 },
595 }
596 }
597
598 #[must_use]
600 pub fn downsample(&self, term_width: usize, term_height: usize) -> Vec<f32> {
601 let scale_x = self.width as usize / term_width.max(1);
602 let scale_y = self.height as usize / term_height.max(1);
603 let mut result = vec![0.0; term_width * term_height];
604
605 for ty in 0..term_height {
606 for tx in 0..term_width {
607 let mut sum = 0.0;
609 let mut count = 0;
610
611 for py in 0..scale_y {
612 for px in 0..scale_x {
613 let src_x = tx * scale_x + px;
614 let src_y = ty * scale_y + py;
615 if src_x < self.width as usize && src_y < self.height as usize {
616 let idx = src_y * self.width as usize + src_x;
617 sum += self.pixels[idx];
618 count += 1;
619 }
620 }
621 }
622
623 if count > 0 {
624 result[ty * term_width + tx] = sum / count as f32;
625 }
626 }
627 }
628
629 result
630 }
631
632 pub fn reset(&mut self) {
634 self.pixels.fill(0.0);
635 self.frame = 0;
636 }
637
638 #[must_use]
640 pub fn is_using_gpu(&self) -> bool {
641 self.using_gpu
642 }
643}
644
645#[derive(Debug, Clone)]
647pub struct CoverageStats {
648 pub covered: usize,
650 pub total: usize,
652 pub percentage: f32,
654 pub wilson_ci: ConfidenceInterval,
656 pub gaps: Vec<DemoGapRegion>,
658}
659
660impl CoverageStats {
661 #[must_use]
663 pub fn meets_threshold(&self, threshold: f32) -> bool {
664 self.percentage >= threshold
665 }
666
667 #[must_use]
669 pub fn max_gap_size(&self) -> usize {
670 self.gaps.iter().map(|g| g.size).max().unwrap_or(0)
671 }
672}
673
674#[must_use]
678pub fn wilson_confidence_interval(
679 successes: usize,
680 total: usize,
681 confidence: f32,
682) -> ConfidenceInterval {
683 if total == 0 {
684 return ConfidenceInterval {
685 lower: 0.0,
686 upper: 0.0,
687 level: confidence,
688 };
689 }
690
691 let n = total as f32;
692 let p = successes as f32 / n;
693
694 let z: f32 = if (confidence - 0.90).abs() < 0.01 {
696 1.645
697 } else if (confidence - 0.95).abs() < 0.01 {
698 1.96
699 } else if (confidence - 0.99).abs() < 0.01 {
700 2.576
701 } else {
702 1.96
703 };
704
705 let z2 = z * z;
706 let denominator = 1.0 + z2 / n;
707 let center = (p + z2 / (2.0 * n)) / denominator;
708 let margin = (z / denominator) * ((p * (1.0 - p) / n + z2 / (4.0 * n * n)).sqrt());
709
710 ConfidenceInterval {
711 lower: (center - margin).max(0.0),
712 upper: (center + margin).min(1.0),
713 level: confidence,
714 }
715}
716
717#[derive(Debug)]
719pub struct WasmPixelDemo {
720 pub buffer: GpuPixelBuffer,
722 pub config: WasmDemoConfig,
724 pub start_time: std::time::Instant,
726 pub complete: bool,
728}
729
730impl WasmPixelDemo {
731 #[must_use]
733 pub fn new(config: WasmDemoConfig) -> Self {
734 Self {
735 buffer: GpuPixelBuffer::new(config.width, config.height, config.seed),
736 config,
737 start_time: std::time::Instant::now(),
738 complete: false,
739 }
740 }
741
742 #[must_use]
744 pub fn hd_1080p() -> Self {
745 Self::new(WasmDemoConfig::hd_1080p())
746 }
747
748 pub fn tick(&mut self) {
750 if self.complete {
751 return;
752 }
753
754 self.buffer.random_fill_pass(self.config.fill_probability);
755
756 if self.buffer.coverage_percentage() >= self.config.target_coverage {
757 self.complete = true;
758 }
759 }
760
761 #[must_use]
763 pub fn stats(&self) -> CoverageStats {
764 self.buffer.coverage_stats()
765 }
766
767 #[must_use]
769 pub fn elapsed(&self) -> Duration {
770 self.start_time.elapsed()
771 }
772
773 #[must_use]
775 pub fn is_complete(&self) -> bool {
776 self.complete
777 }
778
779 #[must_use]
781 pub fn frame_count(&self) -> u32 {
782 self.buffer.frame
783 }
784
785 pub fn reset(&mut self) {
787 self.buffer.reset();
788 self.start_time = std::time::Instant::now();
789 self.complete = false;
790 }
791}
792
793#[cfg(test)]
794#[allow(clippy::unwrap_used, clippy::expect_used, clippy::float_cmp)]
795mod tests {
796 use super::*;
797
798 #[test]
803 fn h0_demo_01_config_default() {
804 let config = WasmDemoConfig::default();
805 assert_eq!(config.width, 1920);
806 assert_eq!(config.height, 1080);
807 assert!((config.fill_probability - 0.01).abs() < f32::EPSILON);
808 }
809
810 #[test]
811 fn h0_demo_02_config_validation_valid() {
812 let config = WasmDemoConfig::default();
813 assert!(config.validate().is_ok());
814 }
815
816 #[test]
817 fn h0_demo_03_config_validation_zero_width() {
818 let config = WasmDemoConfig {
819 width: 0,
820 ..Default::default()
821 };
822 assert!(matches!(
823 config.validate(),
824 Err(ConfigError::InvalidDimensions { .. })
825 ));
826 }
827
828 #[test]
829 fn h0_demo_04_config_validation_invalid_probability() {
830 let config = WasmDemoConfig {
831 fill_probability: -0.5,
832 ..Default::default()
833 };
834 assert!(matches!(
835 config.validate(),
836 Err(ConfigError::InvalidProbability(_))
837 ));
838 }
839
840 #[test]
841 fn h0_demo_05_config_validation_probability_over_1() {
842 let config = WasmDemoConfig {
843 fill_probability: 1.5,
844 ..Default::default()
845 };
846 assert!(matches!(
847 config.validate(),
848 Err(ConfigError::InvalidProbability(_))
849 ));
850 }
851
852 #[test]
857 fn h0_rng_01_determinism_same_seed() {
858 let mut rng1 = PcgRng::new(42);
859 let mut rng2 = PcgRng::new(42);
860
861 for _ in 0..100 {
862 assert_eq!(rng1.next_u32(), rng2.next_u32());
863 }
864 }
865
866 #[test]
867 fn h0_rng_02_determinism_different_seeds() {
868 let mut rng1 = PcgRng::new(42);
869 let mut rng2 = PcgRng::new(123);
870
871 let mut any_different = false;
873 for _ in 0..100 {
874 if rng1.next_u32() != rng2.next_u32() {
875 any_different = true;
876 break;
877 }
878 }
879 assert!(
880 any_different,
881 "Different seeds should produce different sequences"
882 );
883 }
884
885 #[test]
886 fn h0_rng_03_pixel_hash_determinism() {
887 let hash1 = PcgRng::hash_pixel(42, 1000, 5);
888 let hash2 = PcgRng::hash_pixel(42, 1000, 5);
889 assert_eq!(hash1, hash2);
890 }
891
892 #[test]
893 fn h0_rng_04_pixel_hash_frame_dependency() {
894 let hash_frame_0 = PcgRng::hash_pixel(42, 1000, 0);
895 let hash_frame_1 = PcgRng::hash_pixel(42, 1000, 1);
896 assert_ne!(hash_frame_0, hash_frame_1);
897 }
898
899 #[test]
900 fn h0_rng_05_should_fill_zero_probability() {
901 for idx in 0..1000 {
903 assert!(!PcgRng::should_fill(42, idx, 1, 0.0));
904 }
905 }
906
907 #[test]
908 fn h0_rng_06_should_fill_full_probability() {
909 for idx in 0..1000 {
911 assert!(PcgRng::should_fill(42, idx, 1, 1.0));
912 }
913 }
914
915 #[test]
916 fn h0_rng_07_float_range() {
917 let mut rng = PcgRng::new(42);
918 for _ in 0..1000 {
919 let f = rng.next_f32();
920 assert!((0.0..1.0).contains(&f));
921 }
922 }
923
924 #[test]
925 fn h0_rng_08_zero_seed_works() {
926 let mut rng = PcgRng::new(0);
928 let val1 = rng.next_u32();
929 let val2 = rng.next_u32();
930 assert_ne!(val1, val2, "Zero seed should still produce varying output");
931 }
932
933 #[test]
938 fn h0_buffer_01_creation_1080p() {
939 let buffer = GpuPixelBuffer::new_1080p();
940 assert_eq!(buffer.width, 1920);
941 assert_eq!(buffer.height, 1080);
942 assert_eq!(buffer.total_pixels(), 1920 * 1080);
943 }
944
945 #[test]
946 fn h0_buffer_02_creation_720p() {
947 let buffer = GpuPixelBuffer::new_720p();
948 assert_eq!(buffer.width, 1280);
949 assert_eq!(buffer.height, 720);
950 }
951
952 #[test]
953 fn h0_buffer_03_initial_zero_coverage() {
954 let buffer = GpuPixelBuffer::new(100, 100, 42);
955 let stats = buffer.coverage_stats();
956 assert_eq!(stats.covered, 0);
957 assert_eq!(stats.percentage, 0.0);
958 }
959
960 #[test]
961 fn h0_buffer_04_random_fill_increases_coverage() {
962 let mut buffer = GpuPixelBuffer::new(100, 100, 42);
963 buffer.random_fill_pass(0.1);
964 assert!(buffer.coverage_percentage() > 0.0);
965 }
966
967 #[test]
968 fn h0_buffer_05_fill_convergence() {
969 let mut buffer = GpuPixelBuffer::new(50, 50, 42);
970 buffer.fill_to_coverage(0.99, 0.1, 1000);
971 assert!(
972 buffer.coverage_percentage() >= 0.99,
973 "Should converge to 99%+ coverage"
974 );
975 }
976
977 #[test]
978 fn h0_buffer_06_deterministic_fill() {
979 let mut buffer1 = GpuPixelBuffer::new(50, 50, 42);
980 let mut buffer2 = GpuPixelBuffer::new(50, 50, 42);
981
982 for _ in 0..10 {
983 buffer1.random_fill_pass(0.1);
984 buffer2.random_fill_pass(0.1);
985 }
986
987 assert_eq!(
988 buffer1.pixels, buffer2.pixels,
989 "Same seed should produce same pattern"
990 );
991 }
992
993 #[test]
994 fn h0_buffer_07_reset_clears() {
995 let mut buffer = GpuPixelBuffer::new(50, 50, 42);
996 buffer.random_fill_pass(0.5);
997 assert!(buffer.coverage_percentage() > 0.0);
998
999 buffer.reset();
1000 assert_eq!(buffer.coverage_percentage(), 0.0);
1001 assert_eq!(buffer.frame, 0);
1002 }
1003
1004 #[test]
1009 fn h0_stats_01_wilson_ci_bounds() {
1010 let ci = wilson_confidence_interval(50, 100, 0.95);
1011 assert!(ci.lower <= 0.50);
1012 assert!(ci.upper >= 0.50);
1013 assert!(ci.lower >= 0.0);
1014 assert!(ci.upper <= 1.0);
1015 }
1016
1017 #[test]
1018 fn h0_stats_02_wilson_ci_empty() {
1019 let ci = wilson_confidence_interval(0, 0, 0.95);
1020 assert_eq!(ci.lower, 0.0);
1021 assert_eq!(ci.upper, 0.0);
1022 }
1023
1024 #[test]
1025 fn h0_stats_03_wilson_ci_zero_coverage() {
1026 let ci = wilson_confidence_interval(0, 100, 0.95);
1027 assert!(ci.lower == 0.0);
1028 assert!(ci.upper > 0.0);
1029 }
1030
1031 #[test]
1032 fn h0_stats_04_wilson_ci_full_coverage() {
1033 let ci = wilson_confidence_interval(100, 100, 0.95);
1034 assert!(ci.lower < 1.0);
1035 assert!((ci.upper - 1.0).abs() < 0.001);
1037 }
1038
1039 #[test]
1040 fn h0_stats_05_wilson_ci_narrows_with_samples() {
1041 let ci_small = wilson_confidence_interval(5, 10, 0.95);
1042 let ci_large = wilson_confidence_interval(500, 1000, 0.95);
1043
1044 let width_small = ci_small.upper - ci_small.lower;
1045 let width_large = ci_large.upper - ci_large.lower;
1046
1047 assert!(
1048 width_large < width_small,
1049 "CI should narrow with more samples"
1050 );
1051 }
1052
1053 #[test]
1054 fn h0_stats_06_coverage_meets_threshold() {
1055 let mut buffer = GpuPixelBuffer::new(50, 50, 42);
1056 buffer.fill_to_coverage(0.8, 0.1, 500);
1057 let stats = buffer.coverage_stats();
1058 assert!(stats.meets_threshold(0.8));
1059 }
1060
1061 #[test]
1066 fn h0_gap_01_empty_buffer_is_one_gap() {
1067 let buffer = GpuPixelBuffer::new(10, 10, 42);
1068 let gaps = buffer.find_gaps();
1069 assert_eq!(gaps.len(), 1);
1070 assert_eq!(gaps[0].size, 100);
1071 }
1072
1073 #[test]
1074 fn h0_gap_02_full_buffer_no_gaps() {
1075 let mut buffer = GpuPixelBuffer::new(10, 10, 42);
1076 for pixel in &mut buffer.pixels {
1078 *pixel = 1.0;
1079 }
1080 let gaps = buffer.find_gaps();
1081 assert!(gaps.is_empty());
1082 }
1083
1084 #[test]
1085 fn h0_gap_03_max_gap_size() {
1086 let buffer = GpuPixelBuffer::new(10, 10, 42);
1087 let stats = buffer.coverage_stats();
1088 assert_eq!(stats.max_gap_size(), 100);
1089 }
1090
1091 #[test]
1096 fn h0_demo_lifecycle_01_creation() {
1097 let demo = WasmPixelDemo::new(WasmDemoConfig::test_small());
1098 assert!(!demo.is_complete());
1099 assert_eq!(demo.frame_count(), 0);
1100 }
1101
1102 #[test]
1103 fn h0_demo_lifecycle_02_tick_advances() {
1104 let mut demo = WasmPixelDemo::new(WasmDemoConfig::test_small());
1105 demo.tick();
1106 assert_eq!(demo.frame_count(), 1);
1107 }
1108
1109 #[test]
1110 fn h0_demo_lifecycle_03_completes_on_target() {
1111 let config = WasmDemoConfig {
1112 width: 10,
1113 height: 10,
1114 fill_probability: 0.5,
1115 target_coverage: 0.5,
1116 ..Default::default()
1117 };
1118 let mut demo = WasmPixelDemo::new(config);
1119
1120 for _ in 0..1000 {
1122 demo.tick();
1123 if demo.is_complete() {
1124 break;
1125 }
1126 }
1127
1128 assert!(demo.is_complete());
1129 }
1130
1131 #[test]
1132 fn h0_demo_lifecycle_04_reset() {
1133 let mut demo = WasmPixelDemo::new(WasmDemoConfig::test_small());
1134 demo.tick();
1135 demo.tick();
1136
1137 demo.reset();
1138 assert!(!demo.is_complete());
1139 assert_eq!(demo.frame_count(), 0);
1140 assert_eq!(demo.buffer.coverage_percentage(), 0.0);
1141 }
1142
1143 #[test]
1148 fn h0_downsample_01_correct_size() {
1149 let buffer = GpuPixelBuffer::new(100, 100, 42);
1150 let downsampled = buffer.downsample(10, 10);
1151 assert_eq!(downsampled.len(), 100);
1152 }
1153
1154 #[test]
1155 fn h0_downsample_02_preserves_coverage_ratio() {
1156 let mut buffer = GpuPixelBuffer::new(100, 100, 42);
1157 buffer.random_fill_pass(1.0); let downsampled = buffer.downsample(10, 10);
1160 let ds_covered = downsampled.iter().filter(|&&v| v > 0.0).count();
1161
1162 assert_eq!(ds_covered, 100);
1164 }
1165
1166 #[test]
1167 fn h0_downsample_03_handles_zero_terminal() {
1168 let buffer = GpuPixelBuffer::new(100, 100, 42);
1169 let downsampled = buffer.downsample(0, 0);
1170 assert!(downsampled.is_empty());
1171 }
1172
1173 #[test]
1178 fn h0_palette_01_default_is_viridis() {
1179 let config = WasmDemoConfig::default();
1180 assert_eq!(config.palette, DemoPalette::Viridis);
1181 }
1182
1183 #[test]
1188 fn h0_perf_01_1080p_creation_fast() {
1189 let start = std::time::Instant::now();
1190 let _buffer = GpuPixelBuffer::new_1080p();
1191 let elapsed = start.elapsed();
1192
1193 assert!(
1195 elapsed.as_secs() < 5,
1196 "1080p buffer creation took {:?}",
1197 elapsed
1198 );
1199 }
1200
1201 #[test]
1202 fn h0_perf_02_fill_pass_reasonable_time() {
1203 let mut buffer = GpuPixelBuffer::new(100, 100, 42);
1204
1205 let start = std::time::Instant::now();
1206 for _ in 0..100 {
1207 buffer.random_fill_pass(0.01);
1208 }
1209 let elapsed = start.elapsed();
1210
1211 assert!(elapsed.as_secs() < 30, "100 fill passes took {:?}", elapsed);
1213 }
1214
1215 #[test]
1220 fn h0_error_01_config_error_display() {
1221 let err = ConfigError::InvalidDimensions {
1222 width: 0,
1223 height: 100,
1224 };
1225 let msg = format!("{}", err);
1226 assert!(msg.contains("Invalid dimensions"));
1227 }
1228
1229 #[test]
1230 fn h0_error_02_probability_clamping() {
1231 let config = WasmDemoConfig::default().with_fill_probability(1.5);
1232 assert_eq!(config.fill_probability, 1.0);
1233
1234 let config = WasmDemoConfig::default().with_fill_probability(-0.5);
1235 assert_eq!(config.fill_probability, 0.0);
1236 }
1237
1238 #[test]
1243 fn h0_config_06_hd_720p() {
1244 let config = WasmDemoConfig::hd_720p();
1245 assert_eq!(config.width, 1280);
1246 assert_eq!(config.height, 720);
1247 assert!((config.fill_probability - 0.01).abs() < f32::EPSILON);
1249 assert_eq!(config.palette, DemoPalette::Viridis);
1250 }
1251
1252 #[test]
1253 fn h0_config_07_test_small() {
1254 let config = WasmDemoConfig::test_small();
1255 assert_eq!(config.width, 100);
1256 assert_eq!(config.height, 100);
1257 assert!((config.fill_probability - 0.1).abs() < f32::EPSILON);
1258 assert_eq!(config.seed, 42);
1259 }
1260
1261 #[test]
1262 fn h0_config_08_with_seed() {
1263 let config = WasmDemoConfig::default().with_seed(12345);
1264 assert_eq!(config.seed, 12345);
1265 assert_eq!(config.width, 1920);
1267 assert_eq!(config.height, 1080);
1268 }
1269
1270 #[test]
1271 fn h0_config_09_validation_zero_height() {
1272 let config = WasmDemoConfig {
1273 height: 0,
1274 ..Default::default()
1275 };
1276 let err = config.validate().unwrap_err();
1277 assert!(matches!(
1278 err,
1279 ConfigError::InvalidDimensions {
1280 width: 1920,
1281 height: 0
1282 }
1283 ));
1284 }
1285
1286 #[test]
1287 fn h0_config_10_validation_invalid_target_coverage_low() {
1288 let config = WasmDemoConfig {
1289 target_coverage: -0.5,
1290 ..Default::default()
1291 };
1292 assert!(matches!(
1293 config.validate(),
1294 Err(ConfigError::InvalidTargetCoverage(_))
1295 ));
1296 }
1297
1298 #[test]
1299 fn h0_config_11_validation_invalid_target_coverage_high() {
1300 let config = WasmDemoConfig {
1301 target_coverage: 1.5,
1302 ..Default::default()
1303 };
1304 assert!(matches!(
1305 config.validate(),
1306 Err(ConfigError::InvalidTargetCoverage(_))
1307 ));
1308 }
1309
1310 #[test]
1315 fn h0_error_03_probability_display() {
1316 let err = ConfigError::InvalidProbability(1.5);
1317 let msg = format!("{}", err);
1318 assert!(msg.contains("Invalid probability"));
1319 assert!(msg.contains("1.5"));
1320 assert!(msg.contains("0.0..=1.0"));
1321 }
1322
1323 #[test]
1324 fn h0_error_04_target_coverage_display() {
1325 let err = ConfigError::InvalidTargetCoverage(-0.5);
1326 let msg = format!("{}", err);
1327 assert!(msg.contains("Invalid target coverage"));
1328 assert!(msg.contains("-0.5"));
1329 }
1330
1331 #[test]
1332 fn h0_error_05_config_error_is_error_trait() {
1333 let err: Box<dyn std::error::Error> = Box::new(ConfigError::InvalidProbability(1.5));
1334 let _ = err.source(); let _ = format!("{}", err);
1337 }
1338
1339 #[test]
1340 fn h0_error_06_config_error_clone_eq() {
1341 let err1 = ConfigError::InvalidProbability(1.5);
1342 let err2 = err1.clone();
1343 assert_eq!(err1, err2);
1344
1345 let err3 = ConfigError::InvalidDimensions {
1346 width: 0,
1347 height: 100,
1348 };
1349 let err4 = err3.clone();
1350 assert_eq!(err3, err4);
1351 }
1352
1353 #[test]
1358 fn h0_palette_02_all_variants() {
1359 let palettes = [
1360 DemoPalette::Viridis,
1361 DemoPalette::Magma,
1362 DemoPalette::Heat,
1363 DemoPalette::Grayscale,
1364 ];
1365
1366 for palette in &palettes {
1367 let debug = format!("{:?}", palette);
1369 assert!(!debug.is_empty());
1370
1371 let cloned = *palette;
1373 assert_eq!(*palette, cloned);
1374 }
1375 }
1376
1377 #[test]
1378 fn h0_palette_03_equality() {
1379 assert_eq!(DemoPalette::Magma, DemoPalette::Magma);
1380 assert_ne!(DemoPalette::Magma, DemoPalette::Heat);
1381 assert_ne!(DemoPalette::Viridis, DemoPalette::Grayscale);
1382 }
1383
1384 #[test]
1389 fn h0_gap_04_severity_info() {
1390 let mut buffer = GpuPixelBuffer::new(10, 10, 42);
1392 for (idx, pixel) in buffer.pixels.iter_mut().enumerate() {
1394 if idx >= 10 {
1395 *pixel = 1.0;
1396 }
1397 }
1398
1399 let gaps = buffer.find_gaps();
1400 assert!(!gaps.is_empty());
1401 let small_gap = &gaps[0];
1402 assert!(small_gap.size < 25);
1403 assert_eq!(small_gap.severity, GapSeverity::Info);
1404 }
1405
1406 #[test]
1407 fn h0_gap_05_severity_warning() {
1408 let mut buffer = GpuPixelBuffer::new(20, 20, 42);
1410 for y in 0..20 {
1412 for x in 0..20 {
1413 let idx = y * 20 + x;
1414 if x >= 10 || y >= 5 {
1415 buffer.pixels[idx] = 1.0;
1416 }
1417 }
1418 }
1419
1420 let gaps = buffer.find_gaps();
1421 assert!(!gaps.is_empty());
1422 let medium_gap = &gaps[0];
1423 assert!(medium_gap.size >= 25 && medium_gap.size <= 100);
1424 assert_eq!(medium_gap.severity, GapSeverity::Warning);
1425 }
1426
1427 #[test]
1428 fn h0_gap_06_severity_critical() {
1429 let buffer = GpuPixelBuffer::new(20, 20, 42);
1431 let gaps = buffer.find_gaps();
1433 assert_eq!(gaps.len(), 1);
1434 assert!(gaps[0].size > 100);
1435 assert_eq!(gaps[0].severity, GapSeverity::Critical);
1436 }
1437
1438 #[test]
1439 fn h0_gap_07_severity_variants() {
1440 let severities = [
1442 GapSeverity::Info,
1443 GapSeverity::Warning,
1444 GapSeverity::Critical,
1445 ];
1446
1447 for sev in &severities {
1448 let debug = format!("{:?}", sev);
1449 assert!(!debug.is_empty());
1450
1451 let cloned = *sev;
1452 assert_eq!(*sev, cloned);
1453 }
1454 }
1455
1456 #[test]
1461 fn h0_gap_08_region_fields() {
1462 let region = DemoGapRegion {
1463 x: 10,
1464 y: 20,
1465 width: 30,
1466 height: 40,
1467 size: 150,
1468 severity: GapSeverity::Critical,
1469 };
1470
1471 assert_eq!(region.x, 10);
1472 assert_eq!(region.y, 20);
1473 assert_eq!(region.width, 30);
1474 assert_eq!(region.height, 40);
1475 assert_eq!(region.size, 150);
1476 assert_eq!(region.severity, GapSeverity::Critical);
1477
1478 let debug = format!("{:?}", region);
1480 assert!(debug.contains("DemoGapRegion"));
1481
1482 let cloned = region.clone();
1483 assert_eq!(cloned.size, region.size);
1484 }
1485
1486 #[test]
1491 fn h0_buffer_08_gpu_device_name() {
1492 let name = GpuPixelBuffer::gpu_device_name();
1494 let _ = format!("{:?}", name);
1496 }
1497
1498 #[test]
1499 fn h0_buffer_09_is_using_gpu() {
1500 let buffer = GpuPixelBuffer::new(100, 100, 42);
1501 let using_gpu = buffer.is_using_gpu();
1502 #[cfg(not(feature = "gpu"))]
1504 assert!(!using_gpu);
1505 let _ = format!("{}", using_gpu);
1507 }
1508
1509 #[test]
1510 fn h0_buffer_10_gpu_available() {
1511 let available = GpuPixelBuffer::gpu_available();
1512 #[cfg(not(feature = "gpu"))]
1514 assert!(!available);
1515 let _ = format!("{}", available);
1516 }
1517
1518 #[test]
1519 fn h0_buffer_11_custom_dimensions() {
1520 let buffer = GpuPixelBuffer::new(123, 456, 789);
1521 assert_eq!(buffer.width, 123);
1522 assert_eq!(buffer.height, 456);
1523 assert_eq!(buffer.seed, 789);
1524 assert_eq!(buffer.frame, 0);
1525 assert_eq!(buffer.total_pixels(), 123 * 456);
1526 }
1527
1528 #[test]
1533 fn h0_stats_07_max_gap_size_no_gaps() {
1534 let mut buffer = GpuPixelBuffer::new(10, 10, 42);
1535 for pixel in &mut buffer.pixels {
1537 *pixel = 1.0;
1538 }
1539
1540 let stats = buffer.coverage_stats();
1541 assert_eq!(stats.max_gap_size(), 0);
1542 assert!(stats.gaps.is_empty());
1543 }
1544
1545 #[test]
1546 fn h0_stats_08_meets_threshold_boundary() {
1547 let mut buffer = GpuPixelBuffer::new(100, 100, 42);
1548 for i in 0..8000 {
1550 buffer.pixels[i] = 1.0;
1551 }
1552
1553 let stats = buffer.coverage_stats();
1554 assert!(stats.meets_threshold(0.80));
1555 assert!(!stats.meets_threshold(0.81));
1556 }
1557
1558 #[test]
1559 fn h0_stats_09_coverage_stats_debug_clone() {
1560 let buffer = GpuPixelBuffer::new(10, 10, 42);
1561 let stats = buffer.coverage_stats();
1562
1563 let debug = format!("{:?}", stats);
1564 assert!(debug.contains("CoverageStats"));
1565
1566 let cloned = stats.clone();
1567 assert_eq!(cloned.covered, stats.covered);
1568 assert_eq!(cloned.total, stats.total);
1569 }
1570
1571 #[test]
1576 fn h0_demo_lifecycle_05_hd_1080p() {
1577 let demo = WasmPixelDemo::hd_1080p();
1578 assert_eq!(demo.buffer.width, 1920);
1579 assert_eq!(demo.buffer.height, 1080);
1580 assert!(!demo.is_complete());
1581 }
1582
1583 #[test]
1584 fn h0_demo_lifecycle_06_elapsed() {
1585 let demo = WasmPixelDemo::new(WasmDemoConfig::test_small());
1586 std::thread::sleep(std::time::Duration::from_millis(10));
1587 let elapsed = demo.elapsed();
1588 assert!(elapsed.as_millis() >= 10);
1589 }
1590
1591 #[test]
1592 fn h0_demo_lifecycle_07_stats() {
1593 let mut demo = WasmPixelDemo::new(WasmDemoConfig::test_small());
1594 demo.tick();
1595
1596 let stats = demo.stats();
1597 assert!(stats.total > 0);
1598 assert!(stats.covered <= stats.total);
1600 }
1601
1602 #[test]
1603 fn h0_demo_lifecycle_08_tick_when_complete() {
1604 let config = WasmDemoConfig {
1605 width: 10,
1606 height: 10,
1607 fill_probability: 1.0, target_coverage: 0.01, ..Default::default()
1610 };
1611 let mut demo = WasmPixelDemo::new(config);
1612
1613 demo.tick(); assert!(demo.is_complete());
1615
1616 let frame_before = demo.frame_count();
1617 demo.tick(); let frame_after = demo.frame_count();
1619
1620 assert_eq!(frame_before, frame_after);
1622 }
1623
1624 #[test]
1625 fn h0_demo_lifecycle_09_debug() {
1626 let demo = WasmPixelDemo::new(WasmDemoConfig::test_small());
1627 let debug = format!("{:?}", demo);
1628 assert!(debug.contains("WasmPixelDemo"));
1629 }
1630
1631 #[test]
1636 fn h0_wilson_01_confidence_90() {
1637 let ci = wilson_confidence_interval(50, 100, 0.90);
1638 assert!(ci.level == 0.90 || (ci.level - 0.90).abs() < 0.01);
1639 let ci_95 = wilson_confidence_interval(50, 100, 0.95);
1641 assert!((ci.upper - ci.lower) < (ci_95.upper - ci_95.lower));
1642 }
1643
1644 #[test]
1645 fn h0_wilson_02_confidence_99() {
1646 let ci = wilson_confidence_interval(50, 100, 0.99);
1647 assert!(ci.level == 0.99 || (ci.level - 0.99).abs() < 0.01);
1648 let ci_95 = wilson_confidence_interval(50, 100, 0.95);
1650 assert!((ci.upper - ci.lower) > (ci_95.upper - ci_95.lower));
1651 }
1652
1653 #[test]
1654 fn h0_wilson_03_confidence_other() {
1655 let ci = wilson_confidence_interval(50, 100, 0.85);
1657 assert!(ci.lower >= 0.0);
1659 assert!(ci.upper <= 1.0);
1660 assert!(ci.lower <= ci.upper);
1661 }
1662
1663 #[test]
1668 fn h0_flood_01_single_pixel_gap() {
1669 let mut buffer = GpuPixelBuffer::new(5, 5, 42);
1670 for (idx, pixel) in buffer.pixels.iter_mut().enumerate() {
1672 if idx != 12 {
1673 *pixel = 1.0;
1675 }
1676 }
1677
1678 let gaps = buffer.find_gaps();
1679 assert_eq!(gaps.len(), 1);
1680 assert_eq!(gaps[0].size, 1);
1681 assert_eq!(gaps[0].severity, GapSeverity::Info);
1682 }
1683
1684 #[test]
1685 fn h0_flood_02_corner_gaps() {
1686 let mut buffer = GpuPixelBuffer::new(10, 10, 42);
1687 for y in 0..10 {
1689 for x in 0..10 {
1690 let idx = y * 10 + x;
1691 if (2..8).contains(&x) && (2..8).contains(&y) {
1692 buffer.pixels[idx] = 1.0;
1693 }
1694 }
1695 }
1696
1697 let gaps = buffer.find_gaps();
1698 assert!(!gaps.is_empty());
1700 }
1701
1702 #[test]
1703 fn h0_flood_03_multiple_isolated_gaps() {
1704 let mut buffer = GpuPixelBuffer::new(10, 10, 42);
1705 for pixel in &mut buffer.pixels {
1707 *pixel = 1.0;
1708 }
1709 buffer.pixels[0] = 0.0; buffer.pixels[9] = 0.0; buffer.pixels[90] = 0.0; buffer.pixels[99] = 0.0; let gaps = buffer.find_gaps();
1716 assert_eq!(gaps.len(), 4);
1717 for gap in &gaps {
1718 assert_eq!(gap.size, 1);
1719 assert_eq!(gap.severity, GapSeverity::Info);
1720 }
1721 }
1722
1723 #[test]
1728 fn h0_downsample_04_exact_multiple() {
1729 let buffer = GpuPixelBuffer::new(100, 100, 42);
1730 let downsampled = buffer.downsample(10, 10);
1731 assert_eq!(downsampled.len(), 100);
1733 }
1734
1735 #[test]
1736 fn h0_downsample_05_non_multiple() {
1737 let buffer = GpuPixelBuffer::new(100, 100, 42);
1738 let downsampled = buffer.downsample(7, 7);
1739 assert_eq!(downsampled.len(), 49);
1740 }
1741
1742 #[test]
1743 fn h0_downsample_06_larger_than_source() {
1744 let buffer = GpuPixelBuffer::new(10, 10, 42);
1747 let downsampled = buffer.downsample(100, 100);
1748 assert_eq!(downsampled.len(), 10000);
1749 }
1750
1751 #[test]
1756 fn h0_rng_09_hash_pixel_edge_values() {
1757 let hash_max = PcgRng::hash_pixel(u32::MAX, u32::MAX, u32::MAX);
1759 let hash_zero = PcgRng::hash_pixel(0, 0, 0);
1760
1761 assert_ne!(hash_max, hash_zero);
1763
1764 let _ = hash_max.to_string();
1766 let _ = hash_zero.to_string();
1767 }
1768
1769 #[test]
1770 fn h0_rng_10_should_fill_edge_probability() {
1771 let near_zero = 0.000001;
1773 let near_one = 0.999999;
1774
1775 let mut fill_count = 0;
1777 for idx in 0..1000 {
1778 if PcgRng::should_fill(42, idx, 1, near_zero) {
1779 fill_count += 1;
1780 }
1781 }
1782 assert!(
1783 fill_count < 10,
1784 "Near-zero probability filled {} pixels",
1785 fill_count
1786 );
1787
1788 let mut fill_count = 0;
1790 for idx in 0..1000 {
1791 if PcgRng::should_fill(42, idx, 1, near_one) {
1792 fill_count += 1;
1793 }
1794 }
1795 assert!(
1796 fill_count > 990,
1797 "Near-one probability filled {} pixels",
1798 fill_count
1799 );
1800 }
1801
1802 #[test]
1807 fn h0_fill_01_max_frames_reached() {
1808 let mut buffer = GpuPixelBuffer::new(100, 100, 42);
1809 buffer.fill_to_coverage(1.0, 0.001, 5);
1811 assert_eq!(buffer.frame, 5);
1813 assert!(buffer.coverage_percentage() < 1.0);
1815 }
1816
1817 #[test]
1818 fn h0_fill_02_early_termination() {
1819 let mut buffer = GpuPixelBuffer::new(10, 10, 42);
1820 buffer.fill_to_coverage(0.5, 1.0, 1000);
1822 assert!(buffer.frame < 1000);
1824 assert!(buffer.coverage_percentage() >= 0.5);
1826 }
1827
1828 #[test]
1833 fn h0_config_12_clone_debug() {
1834 let config = WasmDemoConfig::default();
1835 let cloned = config.clone();
1836
1837 assert_eq!(cloned.width, config.width);
1838 assert_eq!(cloned.height, config.height);
1839 assert_eq!(cloned.seed, config.seed);
1840
1841 let debug = format!("{:?}", config);
1842 assert!(debug.contains("WasmDemoConfig"));
1843 }
1844
1845 #[test]
1850 fn h0_rng_11_clone_debug() {
1851 let rng = PcgRng::new(42);
1852 let cloned = rng.clone();
1853
1854 let debug = format!("{:?}", rng);
1855 assert!(debug.contains("PcgRng"));
1856
1857 let mut rng1 = rng;
1859 let mut rng2 = cloned;
1860 assert_eq!(rng1.next_u32(), rng2.next_u32());
1861 }
1862
1863 #[test]
1868 fn h0_buffer_12_clone_debug() {
1869 let buffer = GpuPixelBuffer::new(10, 10, 42);
1870 let cloned = buffer.clone();
1871
1872 assert_eq!(cloned.width, buffer.width);
1873 assert_eq!(cloned.height, buffer.height);
1874 assert_eq!(cloned.pixels.len(), buffer.pixels.len());
1875
1876 let debug = format!("{:?}", buffer);
1877 assert!(debug.contains("GpuPixelBuffer"));
1878 }
1879}