diff --git a/Cargo.lock b/Cargo.lock index 7f95725..af3e680 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1658,7 +1658,7 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "lesavka_client" -version = "0.26.5" +version = "0.26.6" dependencies = [ "anyhow", "async-stream", @@ -1692,7 +1692,7 @@ dependencies = [ [[package]] name = "lesavka_common" -version = "0.26.5" +version = "0.26.6" dependencies = [ "anyhow", "base64", @@ -1704,7 +1704,7 @@ dependencies = [ [[package]] name = "lesavka_server" -version = "0.26.5" +version = "0.26.6" dependencies = [ "anyhow", "base64", diff --git a/client/Cargo.toml b/client/Cargo.toml index fd4eb70..5d7de79 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -4,7 +4,7 @@ path = "src/main.rs" [package] name = "lesavka_client" -version = "0.26.5" +version = "0.26.6" edition = "2024" [dependencies] diff --git a/common/Cargo.toml b/common/Cargo.toml index cca839f..cd3180e 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lesavka_common" -version = "0.26.5" +version = "0.26.6" edition = "2024" build = "build.rs" diff --git a/server/Cargo.toml b/server/Cargo.toml index c2bc56c..ae21d9d 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -16,7 +16,7 @@ bench = false [package] name = "lesavka_server" -version = "0.26.5" +version = "0.26.6" edition = "2024" autobins = false diff --git a/server/src/video_sinks/hevc_mjpeg_guard/mjpeg_visual_seams.rs b/server/src/video_sinks/hevc_mjpeg_guard/mjpeg_visual_seams.rs index 4526ed8..358b8b7 100644 --- a/server/src/video_sinks/hevc_mjpeg_guard/mjpeg_visual_seams.rs +++ b/server/src/video_sinks/hevc_mjpeg_guard/mjpeg_visual_seams.rs @@ -78,7 +78,7 @@ fn partial_seam_run_pct_threshold() -> usize { /// /// Inputs: optional pixel-guard tuning. Output: luma stddev threshold. Why: /// natural shelf/chair/table edges can look like a partial seam on one row, -/// but damaged codec slabs usually have a low-texture block on one side. +/// but damaged codec slabs usually keep both sides of the tear low-texture. fn partial_seam_block_stddev_threshold() -> f64 { f64::from( env_u32( @@ -177,8 +177,9 @@ fn region_luma_stddev( /// Measure texture around a candidate partial-width seam. /// /// Inputs: sampled frame, seam row, and candidate horizontal run. Output: -/// standard deviation for the lower suspect block. Why: real scene detail can -/// have a sharp edge, but damaged UVC tiles tend to be broad low-texture slabs. +/// standard deviation for the least slab-like side of the suspect block. Why: +/// real wall/furniture edges often have one smooth side, but damaged UVC tiles +/// tend to create broad low-texture slabs on both sides of the tear. fn partial_seam_block_stddev( frame: &SampledFrame, row: usize, @@ -191,9 +192,8 @@ fn partial_seam_block_stddev( let below_end = row.saturating_add(window).min(frame.height); let below = region_luma_stddev(frame, row, below_end, col_start, col_end); match (above, below) { - (Some(above), Some(below)) => Some(above.min(below)), - (Some(value), None) | (None, Some(value)) => Some(value), - (None, None) => None, + (Some(above), Some(below)) => Some(above.max(below)), + (Some(_), None) | (None, Some(_)) | (None, None) => None, } } diff --git a/server/src/video_sinks/hevc_mjpeg_guard/tests/mod.rs b/server/src/video_sinks/hevc_mjpeg_guard/tests/mod.rs index d40843e..b72d021 100644 --- a/server/src/video_sinks/hevc_mjpeg_guard/tests/mod.rs +++ b/server/src/video_sinks/hevc_mjpeg_guard/tests/mod.rs @@ -329,6 +329,66 @@ fn pixel_guard_accepts_textured_partial_scene_edges() { ); } +#[test] +/// Verifies smooth wall plus textured furniture edges are not frozen as tears. +fn pixel_guard_accepts_wall_over_couch_scene_edges() { + temp_env::with_vars( + [ + ("LESAVKA_UVC_MJPEG_PIXEL_GUARD", Some("1")), + ( + "LESAVKA_UVC_MJPEG_PIXEL_GUARD_PARTIAL_SEAM_DELTA", + Some("28"), + ), + ( + "LESAVKA_UVC_MJPEG_PIXEL_GUARD_PARTIAL_SEAM_COVERAGE_PCT", + Some("32"), + ), + ( + "LESAVKA_UVC_MJPEG_PIXEL_GUARD_PARTIAL_SEAM_RUN_PCT", + Some("12"), + ), + ( + "LESAVKA_UVC_MJPEG_PIXEL_GUARD_PARTIAL_SEAM_BLOCK_STDDEV", + Some("14"), + ), + ], + || { + let width = 320; + let height = 180; + let mut frame = vec![0u8; width * height * 3]; + let seam_y = height.saturating_mul(77) / 100; + let start_x = width.saturating_mul(61) / 100; + let end_x = width.saturating_mul(80) / 100; + + for y in 0..height { + for x in 0..width { + let offset = (y * width + x) * 3; + if y < seam_y || !(start_x..end_x).contains(&x) { + // Keep the wall naturally smooth but not slab-flat; the + // regression is specifically about one smooth side plus + // one textured side, not about blank-frame detection. + let shade = 174u8.saturating_add(((x + y) % 28) as u8); + frame[offset] = shade; + frame[offset + 1] = shade.saturating_add(6); + frame[offset + 2] = shade.saturating_sub(5); + } else { + let texture = ((x * 23 + y * 17) % 74) as u8; + let base = 54u8.saturating_add(texture); + frame[offset] = base; + frame[offset + 1] = base.saturating_add((x % 27) as u8); + frame[offset + 2] = base.saturating_sub((y % 21) as u8); + } + } + } + + assert_eq!( + super::mjpeg_visual_guard::sampled_rgb_frame_for_test(width, height, &frame), + None + ); + }, + ); +} + #[test] /// Verifies low-detail gray slabs freeze out before UVC handoff. fn pixel_guard_rejects_flat_lower_block_frames() {