Skip to main content

jugar_probar/pixel_coverage/
wasm_demo.rs

1//! WASM Pixel GUI Demo - GPU-Accelerated Random Fill
2//!
3//! Demonstrates probar's pixel coverage capabilities with a TUI that
4//! randomly fills a 1080p grid using GPU-accelerated computation.
5//!
6//! Implements PROBAR-SPEC-009 with:
7//! - PCG-XSH-RR deterministic RNG
8//! - Wilson score confidence intervals
9//! - FalsifiabilityGate integration
10//! - **Actual GPU acceleration via trueno** (when `gpu` feature enabled)
11//!
12//! ## References
13//!
14//! - Nickolls et al. (2008): GPU parallel computing model
15//! - O'Neill (2014): PCG random number generation
16//! - Wilson (1927): Wilson score interval
17//! - W3C (2021): WebGPU specification
18//! - Mahajan et al. (2021): Pixel-based visual testing
19
20use super::ConfidenceInterval;
21use std::time::Duration;
22
23#[cfg(feature = "gpu")]
24use trueno::backends::gpu::GpuDevice;
25
26/// PCG-XSH-RR constants from O'Neill (2014)
27const PCG_MULTIPLIER: u32 = 747796405;
28const PCG_INCREMENT: u32 = 2891336453;
29const PCG_OUTPUT_MUL: u32 = 277803737;
30
31/// Configuration for the WASM pixel demo
32#[derive(Debug, Clone)]
33pub struct WasmDemoConfig {
34    /// Screen width in pixels (default: 1920)
35    pub width: u32,
36    /// Screen height in pixels (default: 1080)
37    pub height: u32,
38    /// Probability of filling a pixel per frame (default: 0.01)
39    pub fill_probability: f32,
40    /// Target coverage percentage (default: 0.99)
41    pub target_coverage: f32,
42    /// RNG seed for determinism
43    pub seed: u64,
44    /// Color palette for rendering
45    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    /// Create 1080p configuration
63    #[must_use]
64    pub fn hd_1080p() -> Self {
65        Self::default()
66    }
67
68    /// Create 720p configuration
69    #[must_use]
70    pub fn hd_720p() -> Self {
71        Self {
72            width: 1280,
73            height: 720,
74            ..Self::default()
75        }
76    }
77
78    /// Create small test configuration
79    #[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    /// Set custom seed
91    #[must_use]
92    pub fn with_seed(mut self, seed: u64) -> Self {
93        self.seed = seed;
94        self
95    }
96
97    /// Set fill probability
98    #[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    /// Validate configuration
105    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/// Color palette options
123#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
124pub enum DemoPalette {
125    /// Viridis - colorblind-safe (default)
126    #[default]
127    Viridis,
128    /// Magma - dark to bright
129    Magma,
130    /// Heat - traditional heat map
131    Heat,
132    /// Grayscale
133    Grayscale,
134}
135
136/// Severity level for gap regions
137#[derive(Debug, Clone, Copy, PartialEq, Eq)]
138pub enum GapSeverity {
139    /// Informational (small gap, <25 pixels)
140    Info,
141    /// Warning (medium gap, 25-100 pixels)
142    Warning,
143    /// Critical (large gap, >100 pixels)
144    Critical,
145}
146
147/// A gap region in the pixel buffer
148#[derive(Debug, Clone)]
149pub struct DemoGapRegion {
150    /// X position
151    pub x: usize,
152    /// Y position
153    pub y: usize,
154    /// Width
155    pub width: usize,
156    /// Height
157    pub height: usize,
158    /// Total pixel count
159    pub size: usize,
160    /// Severity level
161    pub severity: GapSeverity,
162}
163
164/// Configuration errors
165#[derive(Debug, Clone, PartialEq)]
166pub enum ConfigError {
167    /// Invalid screen dimensions (width and height must be > 0)
168    InvalidDimensions {
169        /// Width value
170        width: u32,
171        /// Height value
172        height: u32,
173    },
174    /// Invalid fill probability
175    InvalidProbability(f32),
176    /// Invalid target coverage
177    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/// PCG-XSH-RR random number generator (O'Neill, 2014)
199///
200/// Provides deterministic, high-quality random numbers with minimal state.
201#[derive(Debug, Clone)]
202pub struct PcgRng {
203    state: u64,
204    increment: u64,
205}
206
207impl PcgRng {
208    /// Create new RNG with seed
209    #[must_use]
210    pub fn new(seed: u64) -> Self {
211        let mut rng = Self {
212            state: 0,
213            increment: (seed << 1) | 1, // Must be odd
214        };
215        // Warm up state
216        let _ = rng.next_u32();
217        rng.state = rng.state.wrapping_add(seed);
218        let _ = rng.next_u32();
219        rng
220    }
221
222    /// Generate next 32-bit random value
223    #[must_use]
224    pub fn next_u32(&mut self) -> u32 {
225        let old_state = self.state;
226
227        // Advance state using LCG
228        self.state = old_state
229            .wrapping_mul(u64::from(PCG_MULTIPLIER))
230            .wrapping_add(self.increment);
231
232        // XSH-RR output function
233        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    /// Generate random float in [0, 1)
242    #[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    /// Generate hash for pixel index (pure function for GPU)
248    #[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    /// Check if pixel should be filled based on probability
260    #[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/// GPU pixel buffer for demo rendering
269///
270/// When `gpu` feature is enabled, this uses actual GPU compute via trueno/wgpu.
271/// Otherwise falls back to CPU implementation.
272#[derive(Debug, Clone)]
273pub struct GpuPixelBuffer {
274    /// Pixel data (0.0 = uncovered, 1.0 = fully covered)
275    pub pixels: Vec<f32>,
276    /// Buffer width
277    pub width: u32,
278    /// Buffer height
279    pub height: u32,
280    /// Current frame number
281    pub frame: u32,
282    /// RNG seed
283    pub seed: u32,
284    /// Whether GPU is being used
285    pub using_gpu: bool,
286}
287
288/// GPU backend for actual hardware acceleration
289#[cfg(feature = "gpu")]
290pub struct GpuAccelerator {
291    device: GpuDevice,
292}
293
294#[cfg(feature = "gpu")]
295impl GpuAccelerator {
296    /// Create new GPU accelerator
297    pub fn new() -> Result<Self, String> {
298        let device = GpuDevice::new()?;
299        Ok(Self { device })
300    }
301
302    /// Check if GPU is available
303    #[allow(dead_code)]
304    pub fn is_available() -> bool {
305        GpuDevice::is_available()
306    }
307
308    /// Execute parallel fill on GPU using wgpu compute
309    ///
310    /// Uses trueno's GPU backend (wgpu/Vulkan) for actual hardware acceleration.
311    /// The RNG is computed on CPU but the comparison and assignment can be
312    /// parallelized via GPU vector operations.
313    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        // Generate random values (this computation is embarrassingly parallel)
325        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        // Generate gradient values for positions
333        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        // Use GPU for vector operations when buffer is large enough
342        // trueno's GPU backend works best for operations on >100K elements
343        if total > 100_000 {
344            // GPU-accelerated threshold comparison using trueno's sigmoid
345            // (sigmoid of large negative = ~0, sigmoid of large positive = ~1)
346            // This demonstrates actual GPU compute dispatch
347            let scaled: Vec<f32> = random_values
348                .iter()
349                .map(|&r| (probability - r) * 100.0) // Scale difference for sigmoid
350                .collect();
351
352            let mut mask = vec![0.0f32; total];
353            // Use GPU sigmoid as a soft threshold function
354            self.device.sigmoid(&scaled, &mut mask)?;
355
356            // Apply mask to update pixels
357            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 smaller buffers, CPU is faster due to transfer overhead
364            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    /// Create new pixel buffer (tries GPU first, falls back to CPU)
377    #[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    /// Check if GPU acceleration is available
393    #[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    /// Get GPU device name if available
406    #[must_use]
407    pub fn gpu_device_name() -> Option<String> {
408        #[cfg(feature = "gpu")]
409        {
410            if GpuDevice::is_available() {
411                // trueno uses wgpu which auto-detects best GPU
412                // Return generic name since adapter_info is internal
413                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    /// Create 1080p buffer
425    #[must_use]
426    pub fn new_1080p() -> Self {
427        Self::new(1920, 1080, 42)
428    }
429
430    /// Create 720p buffer
431    #[must_use]
432    pub fn new_720p() -> Self {
433        Self::new(1280, 720, 42)
434    }
435
436    /// Total number of pixels
437    #[must_use]
438    pub fn total_pixels(&self) -> usize {
439        self.pixels.len()
440    }
441
442    /// Execute random fill pass (GPU-accelerated when available)
443    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        // CPU fallback
468        self.random_fill_pass_cpu(probability);
469    }
470
471    /// CPU-only random fill pass (fallback)
472    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                // Calculate color based on position (viridis-like gradient)
479                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); // Minimum non-zero for "covered"
483            }
484        }
485    }
486
487    /// Run multiple fill passes until target coverage
488    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    /// Calculate coverage statistics
498    #[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    /// Get coverage percentage
514    #[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    /// Find gap regions (contiguous uncovered areas)
521    #[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                    // Found start of gap - flood fill to find extent
531                    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    /// Flood fill to find connected gap region
543    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            // Add neighbors (4-connected)
568            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    /// Downsample to terminal resolution
599    #[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                // Average pixels in this cell
608                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    /// Reset buffer
633    pub fn reset(&mut self) {
634        self.pixels.fill(0.0);
635        self.frame = 0;
636    }
637
638    /// Check if this buffer is using GPU acceleration
639    #[must_use]
640    pub fn is_using_gpu(&self) -> bool {
641        self.using_gpu
642    }
643}
644
645/// Coverage statistics with Wilson CI
646#[derive(Debug, Clone)]
647pub struct CoverageStats {
648    /// Number of covered pixels
649    pub covered: usize,
650    /// Total number of pixels
651    pub total: usize,
652    /// Coverage percentage (0.0 - 1.0)
653    pub percentage: f32,
654    /// Wilson confidence interval
655    pub wilson_ci: ConfidenceInterval,
656    /// Gap regions
657    pub gaps: Vec<DemoGapRegion>,
658}
659
660impl CoverageStats {
661    /// Check if coverage meets threshold
662    #[must_use]
663    pub fn meets_threshold(&self, threshold: f32) -> bool {
664        self.percentage >= threshold
665    }
666
667    /// Get largest gap size
668    #[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/// Calculate Wilson score confidence interval (Wilson, 1927)
675///
676/// Provides better coverage for small samples than normal approximation.
677#[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    // Z-score for confidence level (95% = 1.96)
695    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/// Demo state for TUI rendering
718#[derive(Debug)]
719pub struct WasmPixelDemo {
720    /// GPU pixel buffer
721    pub buffer: GpuPixelBuffer,
722    /// Configuration
723    pub config: WasmDemoConfig,
724    /// Start time for measuring convergence
725    pub start_time: std::time::Instant,
726    /// Whether demo is complete
727    pub complete: bool,
728}
729
730impl WasmPixelDemo {
731    /// Create new demo with configuration
732    #[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    /// Create 1080p demo
743    #[must_use]
744    pub fn hd_1080p() -> Self {
745        Self::new(WasmDemoConfig::hd_1080p())
746    }
747
748    /// Execute one frame
749    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    /// Get current stats
762    #[must_use]
763    pub fn stats(&self) -> CoverageStats {
764        self.buffer.coverage_stats()
765    }
766
767    /// Get elapsed time
768    #[must_use]
769    pub fn elapsed(&self) -> Duration {
770        self.start_time.elapsed()
771    }
772
773    /// Check if demo is complete
774    #[must_use]
775    pub fn is_complete(&self) -> bool {
776        self.complete
777    }
778
779    /// Get frame count
780    #[must_use]
781    pub fn frame_count(&self) -> u32 {
782        self.buffer.frame
783    }
784
785    /// Reset demo
786    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    // =========================================================================
799    // Section 1: Configuration Tests (QA 6-10)
800    // =========================================================================
801
802    #[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    // =========================================================================
853    // Section 2: PCG RNG Tests (QA 21-30)
854    // =========================================================================
855
856    #[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        // At least some values should differ
872        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        // With 0.0 probability, should never fill
902        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        // With 1.0 probability, should always fill
910        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        // PCG should handle zero seed correctly (unlike some simple LCGs)
927        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    // =========================================================================
934    // Section 3: GPU Buffer Tests (QA 11-20)
935    // =========================================================================
936
937    #[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    // =========================================================================
1005    // Section 4: Coverage Statistics Tests (QA 41-50)
1006    // =========================================================================
1007
1008    #[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        // Upper bound should be very close to 1.0 (clamped)
1036        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    // =========================================================================
1062    // Section 5: Gap Detection Tests (QA 51-60)
1063    // =========================================================================
1064
1065    #[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        // Fill all pixels
1077        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    // =========================================================================
1092    // Section 6: Demo Lifecycle Tests (QA 61-70)
1093    // =========================================================================
1094
1095    #[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        // Run until complete or max frames
1121        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    // =========================================================================
1144    // Section 7: Downsampling Tests (QA 31-40)
1145    // =========================================================================
1146
1147    #[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); // Fill all
1158
1159        let downsampled = buffer.downsample(10, 10);
1160        let ds_covered = downsampled.iter().filter(|&&v| v > 0.0).count();
1161
1162        // All cells should be covered
1163        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    // =========================================================================
1174    // Section 8: Palette Tests
1175    // =========================================================================
1176
1177    #[test]
1178    fn h0_palette_01_default_is_viridis() {
1179        let config = WasmDemoConfig::default();
1180        assert_eq!(config.palette, DemoPalette::Viridis);
1181    }
1182
1183    // =========================================================================
1184    // Section 9: Performance Regression Tests (QA 61-70)
1185    // =========================================================================
1186
1187    #[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        // Should create in under 5s (generous for loaded systems)
1194        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        // 100 frames on 10k pixels - generous for loaded systems
1212        assert!(elapsed.as_secs() < 30, "100 fill passes took {:?}", elapsed);
1213    }
1214
1215    // =========================================================================
1216    // Section 10: Error Handling Tests (QA 81-90)
1217    // =========================================================================
1218
1219    #[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    // =========================================================================
1239    // Section 11: Additional Configuration Tests (Coverage Boost)
1240    // =========================================================================
1241
1242    #[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        // Should inherit defaults for other fields
1248        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        // Should preserve other defaults
1266        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    // =========================================================================
1311    // Section 12: ConfigError Display Tests (Coverage Boost)
1312    // =========================================================================
1313
1314    #[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        // Verify it implements std::error::Error
1335        let _ = err.source(); // Should return None (default impl)
1336        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    // =========================================================================
1354    // Section 13: DemoPalette Tests (Coverage Boost)
1355    // =========================================================================
1356
1357    #[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            // Test Debug
1368            let debug = format!("{:?}", palette);
1369            assert!(!debug.is_empty());
1370
1371            // Test Clone
1372            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    // =========================================================================
1385    // Section 14: GapSeverity Tests (Coverage Boost)
1386    // =========================================================================
1387
1388    #[test]
1389    fn h0_gap_04_severity_info() {
1390        // Gaps < 25 pixels should be Info
1391        let mut buffer = GpuPixelBuffer::new(10, 10, 42);
1392        // Fill most pixels, leaving a small gap of ~10 pixels
1393        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        // Gaps 25-100 pixels should be Warning
1409        let mut buffer = GpuPixelBuffer::new(20, 20, 42);
1410        // Fill all but 50 pixels (5x10 region)
1411        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        // Gaps > 100 pixels should be Critical
1430        let buffer = GpuPixelBuffer::new(20, 20, 42);
1431        // All 400 pixels are uncovered
1432        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        // Test all severity variants for Debug, Clone, Eq
1441        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    // =========================================================================
1457    // Section 15: DemoGapRegion Tests (Coverage Boost)
1458    // =========================================================================
1459
1460    #[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        // Test Debug and Clone
1479        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    // =========================================================================
1487    // Section 16: GpuPixelBuffer Additional Tests (Coverage Boost)
1488    // =========================================================================
1489
1490    #[test]
1491    fn h0_buffer_08_gpu_device_name() {
1492        // GPU device name returns None when gpu feature is disabled
1493        let name = GpuPixelBuffer::gpu_device_name();
1494        // Just verify it doesn't panic - result depends on feature flags
1495        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        // Without gpu feature, should be false
1503        #[cfg(not(feature = "gpu"))]
1504        assert!(!using_gpu);
1505        // Just verify it returns a valid bool
1506        let _ = format!("{}", using_gpu);
1507    }
1508
1509    #[test]
1510    fn h0_buffer_10_gpu_available() {
1511        let available = GpuPixelBuffer::gpu_available();
1512        // Without gpu feature, should be false
1513        #[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    // =========================================================================
1529    // Section 17: CoverageStats Additional Tests (Coverage Boost)
1530    // =========================================================================
1531
1532    #[test]
1533    fn h0_stats_07_max_gap_size_no_gaps() {
1534        let mut buffer = GpuPixelBuffer::new(10, 10, 42);
1535        // Fill all pixels
1536        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        // Fill exactly 80% of pixels
1549        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    // =========================================================================
1572    // Section 18: WasmPixelDemo Additional Tests (Coverage Boost)
1573    // =========================================================================
1574
1575    #[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        // After one tick, coverage is tracked (may be 0 or more)
1599        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, // Fill everything
1608            target_coverage: 0.01, // Very low target
1609            ..Default::default()
1610        };
1611        let mut demo = WasmPixelDemo::new(config);
1612
1613        demo.tick(); // This should complete
1614        assert!(demo.is_complete());
1615
1616        let frame_before = demo.frame_count();
1617        demo.tick(); // Should return early
1618        let frame_after = demo.frame_count();
1619
1620        // Frame count should NOT increase when complete
1621        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    // =========================================================================
1632    // Section 19: Wilson CI Additional Tests (Coverage Boost)
1633    // =========================================================================
1634
1635    #[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        // 90% CI should be narrower than 95% CI
1640        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        // 99% CI should be wider than 95% CI
1649        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        // Non-standard confidence level defaults to 1.96 z-score
1656        let ci = wilson_confidence_interval(50, 100, 0.85);
1657        // Should still produce valid bounds
1658        assert!(ci.lower >= 0.0);
1659        assert!(ci.upper <= 1.0);
1660        assert!(ci.lower <= ci.upper);
1661    }
1662
1663    // =========================================================================
1664    // Section 20: Flood Fill Edge Cases (Coverage Boost)
1665    // =========================================================================
1666
1667    #[test]
1668    fn h0_flood_01_single_pixel_gap() {
1669        let mut buffer = GpuPixelBuffer::new(5, 5, 42);
1670        // Fill all but center pixel
1671        for (idx, pixel) in buffer.pixels.iter_mut().enumerate() {
1672            if idx != 12 {
1673                // Center of 5x5
1674                *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        // Fill center, leave corners empty
1688        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        // Should have one connected gap around the border
1699        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        // Fill everything
1706        for pixel in &mut buffer.pixels {
1707            *pixel = 1.0;
1708        }
1709        // Create 4 isolated single-pixel gaps
1710        buffer.pixels[0] = 0.0; // Top-left
1711        buffer.pixels[9] = 0.0; // Top-right
1712        buffer.pixels[90] = 0.0; // Bottom-left
1713        buffer.pixels[99] = 0.0; // Bottom-right
1714
1715        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    // =========================================================================
1724    // Section 21: Downsample Edge Cases (Coverage Boost)
1725    // =========================================================================
1726
1727    #[test]
1728    fn h0_downsample_04_exact_multiple() {
1729        let buffer = GpuPixelBuffer::new(100, 100, 42);
1730        let downsampled = buffer.downsample(10, 10);
1731        // Each cell averages 100 source pixels (10x10)
1732        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        // When terminal is larger than source, scale factors become 0
1745        // which means count stays 0 and result is all zeros
1746        let buffer = GpuPixelBuffer::new(10, 10, 42);
1747        let downsampled = buffer.downsample(100, 100);
1748        assert_eq!(downsampled.len(), 10000);
1749    }
1750
1751    // =========================================================================
1752    // Section 22: RNG Edge Cases (Coverage Boost)
1753    // =========================================================================
1754
1755    #[test]
1756    fn h0_rng_09_hash_pixel_edge_values() {
1757        // Test with extreme values
1758        let hash_max = PcgRng::hash_pixel(u32::MAX, u32::MAX, u32::MAX);
1759        let hash_zero = PcgRng::hash_pixel(0, 0, 0);
1760
1761        // Hashes should be different
1762        assert_ne!(hash_max, hash_zero);
1763
1764        // Should produce valid u32 values (not panic)
1765        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        // Test probability at boundary values
1772        let near_zero = 0.000001;
1773        let near_one = 0.999999;
1774
1775        // At near-zero probability, very few should fill
1776        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        // At near-one probability, almost all should fill
1789        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    // =========================================================================
1803    // Section 23: Fill to Coverage Edge Cases (Coverage Boost)
1804    // =========================================================================
1805
1806    #[test]
1807    fn h0_fill_01_max_frames_reached() {
1808        let mut buffer = GpuPixelBuffer::new(100, 100, 42);
1809        // Very low probability, won't reach 100% in 5 frames
1810        buffer.fill_to_coverage(1.0, 0.001, 5);
1811        // Should have run exactly 5 frames
1812        assert_eq!(buffer.frame, 5);
1813        // Coverage should be less than 100%
1814        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        // High probability, should reach 50% quickly
1821        buffer.fill_to_coverage(0.5, 1.0, 1000);
1822        // Should not have run all 1000 frames
1823        assert!(buffer.frame < 1000);
1824        // Should have at least 50% coverage
1825        assert!(buffer.coverage_percentage() >= 0.5);
1826    }
1827
1828    // =========================================================================
1829    // Section 24: Config Clone and Debug (Coverage Boost)
1830    // =========================================================================
1831
1832    #[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    // =========================================================================
1846    // Section 25: PcgRng Clone and Debug (Coverage Boost)
1847    // =========================================================================
1848
1849    #[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        // Cloned RNG should produce same values
1858        let mut rng1 = rng;
1859        let mut rng2 = cloned;
1860        assert_eq!(rng1.next_u32(), rng2.next_u32());
1861    }
1862
1863    // =========================================================================
1864    // Section 26: GpuPixelBuffer Clone and Debug (Coverage Boost)
1865    // =========================================================================
1866
1867    #[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}