use jugar_probar::validators::{
CompressionAlgorithm, ScreenshotContent, StreamingMetric, StreamingUxValidator,
TestExecutionStats, VuMeterConfig, VuMeterSample,
};
use std::time::Duration;
fn main() {
println!("╔══════════════════════════════════════════════════════════════╗");
println!("║ Streaming UX Validation Demo (PROBAR-SPEC-011) ║");
println!("╚══════════════════════════════════════════════════════════════╝\n");
demo_streaming_validator();
demo_vu_meter_validation();
demo_test_execution_stats();
demo_screenshot_classification();
}
fn demo_streaming_validator() {
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
println!(" 1. StreamingUxValidator Demo");
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
let mut validator = StreamingUxValidator::for_audio();
println!("Created audio streaming validator:");
println!(" - Max latency: 100ms");
println!(" - Buffer underrun threshold: 3");
println!(" - Initial state: {:?}\n", validator.state());
validator.start();
println!("Started streaming session...");
println!(" State: {:?}", validator.state());
validator.record_metric(StreamingMetric::FirstByteReceived);
println!(" Received first byte");
for i in 0..10 {
validator.record_metric(StreamingMetric::AudioChunk {
samples: 1024,
sample_rate: 16000,
});
validator.record_metric(StreamingMetric::Latency(Duration::from_millis(50 + i * 5)));
}
println!(" Processed 10 audio chunks");
println!(" State: {:?}", validator.state());
for i in 0..30 {
validator.record_metric(StreamingMetric::FrameRendered {
timestamp: i * 33, });
}
println!(" Rendered 30 frames");
println!(" Average FPS: {:.1}", validator.average_fps());
validator.record_metric(StreamingMetric::BufferUnderrun);
println!(" Buffer underrun occurred!");
println!(" State: {:?}", validator.state());
validator.record_metric(StreamingMetric::FrameRendered { timestamp: 1000 });
println!(" Recovered from stall");
println!(" State: {:?}", validator.state());
validator.complete();
println!(" Session completed");
match validator.validate() {
Ok(result) => {
println!("\n Validation PASSED:");
println!(" - Buffer underruns: {}", result.buffer_underruns);
println!(" - Dropped frames: {}", result.dropped_frames);
println!(" - Average FPS: {:.1}", result.average_fps);
println!(" - Max latency: {:?}", result.max_latency_recorded);
println!(" - Total frames: {}", result.total_frames);
}
Err(e) => {
println!("\n Validation FAILED: {e}");
}
}
println!();
}
fn demo_vu_meter_validation() {
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
println!(" 2. VU Meter Validation Demo");
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
let config = VuMeterConfig::default()
.with_min_level(0.1)
.with_max_level(0.9)
.with_update_rate_hz(30.0)
.with_max_stale_ms(100);
println!("VU Meter Configuration:");
println!(" - Min level: {:.1}", config.min_level);
println!(" - Max level: {:.1}", config.max_level);
println!(" - Update rate: {:.0} Hz", config.update_rate_hz);
println!(" - Max stale: {}ms\n", config.max_stale_ms);
let samples = vec![
VuMeterSample {
timestamp_ms: 0.0,
level: 0.2,
},
VuMeterSample {
timestamp_ms: 33.3,
level: 0.5,
},
VuMeterSample {
timestamp_ms: 66.6,
level: 0.7,
},
VuMeterSample {
timestamp_ms: 100.0,
level: 0.4,
},
];
println!("Validating VU meter samples:");
for sample in &samples {
match config.validate_sample(sample.level) {
Ok(()) => println!(
" [{:6.1}ms] Level {:.2} - OK",
sample.timestamp_ms, sample.level
),
Err(e) => println!(
" [{:6.1}ms] Level {:.2} - ERROR: {e}",
sample.timestamp_ms, sample.level
),
}
}
println!("\nEdge case validation:");
println!(
" Level -0.1: {:?}",
config.validate_sample(-0.1).map_err(|e| e.to_string())
);
println!(
" Level 1.5: {:?}",
config.validate_sample(1.5).map_err(|e| e.to_string())
);
println!(
" Level 0.95: {:?}",
config.validate_sample(0.95).map_err(|e| e.to_string())
);
println!();
}
fn demo_test_execution_stats() {
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
println!(" 3. Test Execution Stats Demo (trueno-zram pattern)");
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
let mut stats = TestExecutionStats::new();
stats.start();
println!("Recording game state captures...\n");
let captures = [
(4096, 1024, "Normal game state"),
(4096, 512, "Sparse game state (high compression)"),
(4096, 200, "Same-fill page (>90% compression)"),
(4096, 2048, "Complex game state"),
(4096, 100, "Nearly empty (same-fill)"),
(8192, 4096, "Large state (50% compression)"),
];
for (raw, compressed, description) in captures {
stats.record_state_capture(raw, compressed);
let ratio = raw as f64 / compressed as f64;
println!(" {description}");
println!(" Raw: {raw} bytes, Compressed: {compressed} bytes, Ratio: {ratio:.1}x");
}
stats.stop();
println!("\n Summary Statistics:");
println!(" ─────────────────────────────────────────");
println!(" States captured: {}", stats.states_captured);
println!(" Total raw bytes: {} bytes", stats.bytes_raw);
println!(" Total compressed: {} bytes", stats.bytes_compressed);
println!(" Compression ratio: {:.2}x", stats.compression_ratio());
println!(" Efficiency: {:.1}%", stats.efficiency() * 100.0);
println!(" Storage savings: {:.2} MB", stats.storage_savings_mb());
println!(" Same-fill pages: {}", stats.same_fill_pages);
println!(
" Same-fill ratio: {:.1}%",
stats.same_fill_ratio() * 100.0
);
println!();
}
fn demo_screenshot_classification() {
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
println!(" 4. Screenshot Content Classification Demo");
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
let test_cases: Vec<(&str, Vec<u8>)> = vec![
("Uniform white (blank screen)", vec![255u8; 1000]),
("Uniform black (loading screen)", vec![0u8; 1000]),
("UI-dominated (mostly background with some elements)", {
let mut pixels = vec![240u8; 900];
pixels.extend(vec![50u8; 50]);
pixels.extend(vec![100u8; 50]);
pixels
}),
(
"Game world (varied content)",
(0..1000).map(|i| ((i * 7 + 13) % 128) as u8).collect(),
),
(
"High entropy (random noise)",
(0..1000).map(|i| ((i * 127 + 37) % 256) as u8).collect(),
),
];
for (description, pixels) in test_cases {
let content = ScreenshotContent::classify(&pixels);
let content_type = match &content {
ScreenshotContent::Uniform { fill_value } => format!("Uniform (fill: {fill_value})"),
ScreenshotContent::UiDominated { entropy } => {
format!("UI-Dominated (entropy: {entropy:.2})")
}
ScreenshotContent::GameWorld { entropy } => {
format!("Game World (entropy: {entropy:.2})")
}
ScreenshotContent::HighEntropy { entropy } => {
format!("High Entropy (entropy: {entropy:.2})")
}
};
let algorithm = match content.recommended_algorithm() {
CompressionAlgorithm::Rle => "RLE",
CompressionAlgorithm::Png => "PNG",
CompressionAlgorithm::Zstd => "Zstd",
CompressionAlgorithm::Lz4 => "LZ4",
};
println!(" {description}:");
println!(" Classification: {content_type}");
println!(" Recommended algorithm: {algorithm}");
println!(" Expected ratio: {}", content.expected_ratio_hint());
println!();
}
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
println!(" Demo complete!");
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
}