From cdaf79bc49b29abf0bdab6ab1d13754a565148e9 Mon Sep 17 00:00:00 2001 From: Brad Stein Date: Fri, 1 May 2026 19:49:23 -0300 Subject: [PATCH] media: migrate early zero video offset --- AGENTS.md | 2 +- server/src/calibration.rs | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index ad48d32..20052ca 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -221,4 +221,4 @@ Context: 0.16.x proved that queue tweaks and static calibration cannot guarantee - 2026-05-01: Added `GetUpstreamSync` RPC, `lesavka-relayctl upstream-sync`, launcher diagnostics text, and mirrored-probe before/after planner snapshots so 0.17 probe runs report the exact planner state under test. - 2026-05-01: Validation green: `cargo test -p lesavka_server --lib --bins`, `cargo test -p lesavka_testing`, `cargo test -p lesavka_client --bins --lib`, and targeted installer/RPC/layout contracts. - 2026-05-01: First installed 0.17.0 mirrored browser probe on client/server commit `3920e0a` failed honestly: planner reported fresh live state (`live_lag_ms=10`, `skew_ms=+20.7`) but browser-observed paired pulses showed audio late by median `+349.1ms`, p95 `429.1ms`, with 6 video freezes/skew drops. Replayed artifact after analyzer hardening now reports `gross_failure` instead of false raw-start `catastrophic_failure`. -- 2026-05-01: Patch follow-up models the observed MJPEG/UVC browser egress delta by defaulting video playout offset to `+350ms` and preserving the 1s freshness ceiling. Raw activity-start evidence is now ignored for verdict/calibration when it disagrees with paired pulses that are already failing directly. +- 2026-05-01: Patch follow-up models the observed MJPEG/UVC browser egress delta by defaulting video playout offset to `+350ms` and preserving the 1s freshness ceiling. Raw activity-start evidence is now ignored for verdict/calibration when it disagrees with paired pulses that are already failing directly. Existing early-0.17 `audio=0/video=0` factory/env calibration files migrate to the new `video=+350ms` default on load. diff --git a/server/src/calibration.rs b/server/src/calibration.rs index b866dea..12e774d 100644 --- a/server/src/calibration.rs +++ b/server/src/calibration.rs @@ -245,7 +245,8 @@ fn migrate_legacy_snapshot(mut state: CalibrationSnapshot) -> CalibrationSnapsho .contains("loaded upstream A/V calibration defaults"); let untouched_legacy_audio = (matches!( state.default_audio_offset_us, - LEGACY_FACTORY_MJPEG_AUDIO_OFFSET_US + FACTORY_MJPEG_AUDIO_OFFSET_US + | LEGACY_FACTORY_MJPEG_AUDIO_OFFSET_US | PREVIOUS_FACTORY_MJPEG_AUDIO_OFFSET_US | PREVIOUS_TUNED_MJPEG_AUDIO_OFFSET_US ) || clamped_previous_baseline) @@ -486,6 +487,37 @@ mod tests { }); } + #[test] + fn load_migrates_early_zero_video_factory_mjpeg_baseline() { + let file = NamedTempFile::new().expect("temp calibration file"); + std::fs::write( + file.path(), + r#" + profile="mjpeg" + default_audio_offset_us=0 + default_video_offset_us=0 + active_audio_offset_us=0 + active_video_offset_us=0 + source="factory" + confidence="factory" + detail="loaded upstream A/V calibration defaults" + "#, + ) + .expect("early zero calibration seed"); + let path = file.path().to_string_lossy().to_string(); + temp_env::with_var("LESAVKA_CALIBRATION_PATH", Some(path.as_str()), || { + let runtime = Arc::new(UpstreamMediaRuntime::new()); + let store = CalibrationStore::load(runtime.clone()); + let state = store.current(); + assert_eq!(state.active_audio_offset_us, 0); + assert_eq!(state.default_audio_offset_us, 0); + assert_eq!(state.active_video_offset_us, 350_000); + assert_eq!(state.default_video_offset_us, 350_000); + assert_eq!(state.source, "factory"); + assert_eq!(runtime.playout_offsets(), (350_000, 0)); + }); + } + #[test] fn load_keeps_manual_legacy_sized_calibration() { let file = NamedTempFile::new().expect("temp calibration file");