media: add probe-driven calibration tooling

This commit is contained in:
Brad Stein 2026-05-02 13:44:32 -03:00
parent 2b26fded66
commit 59725fcbfb
9 changed files with 419 additions and 12 deletions

View File

@ -298,3 +298,20 @@ Context: 0.17.11 installed cleanly on both ends (`092c03a`) and fixed the consta
- [x] Update contract tests to encode the new audio continuity policy.
- [x] Run focused client queue/probe contracts and package checks.
- [x] Push clean semver `0.17.12` for installed client/server testing.
## 0.17.13 Probe-Driven Calibration Loop Checklist
Context: 0.17.12 installed cleanly on both ends (`2b26fde`) and moved the paired-pulse median into the near-sync zone (`median=+71.6ms`, first/last `+59.8ms/-60.1ms`), but the run still failed with only 4 usable coded pairs and `p95=206.8ms`. Static guesses have reached diminishing returns. 0.17.13 adds a measured calibration loop so the mirrored probe can become the authority for site-specific browser-visible output compensation when, and only when, the analyzer has enough stable evidence.
- [x] Keep 0.17.13 scoped to probe-driven sync calibration tooling; do not change freshness ceilings, queue policy, UAC smoothness, or startup healing behavior.
- [x] Expose relay CLI calibration state and safe calibration actions for scripts.
- [x] Make the mirrored probe locate the analyzer `report.json` after a run and print the calibration decision.
- [x] Add opt-in probe calibration apply mode gated by `LESAVKA_SYNC_APPLY_CALIBRATION=1`.
- [x] Apply only when analyzer `calibration.ready=true`; otherwise refuse and print the analyzer reason.
- [x] Default probe-driven correction to video offset adjustment, because the measured residual is browser-visible UAC egress delay relative to UVC video.
- [x] Keep saving the measured offset as the site default opt-in via `LESAVKA_SYNC_SAVE_CALIBRATION=1`.
- [x] Update contract tests for relay CLI and manual probe script behavior.
- [x] Run focused relay/manual-script tests and package checks.
- [ ] Push clean semver `0.17.13` for installed client/server testing.
Follow-up candidate: after 0.17.13 proves safe measured apply/refuse behavior, add a segmented live-calibration probe. The current browser probe uploads one WebM after recording ends, so it can only do measure/apply/rerun. A true same-session loop should run a longer stimulus, capture/analyze separate Tethys browser windows, apply calibration only between windows, and use the next window as the confirmation segment so before/after evidence is not mixed.

6
Cargo.lock generated
View File

@ -1652,7 +1652,7 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]]
name = "lesavka_client"
version = "0.17.12"
version = "0.17.13"
dependencies = [
"anyhow",
"async-stream",
@ -1686,7 +1686,7 @@ dependencies = [
[[package]]
name = "lesavka_common"
version = "0.17.12"
version = "0.17.13"
dependencies = [
"anyhow",
"base64",
@ -1698,7 +1698,7 @@ dependencies = [
[[package]]
name = "lesavka_server"
version = "0.17.12"
version = "0.17.13"
dependencies = [
"anyhow",
"base64",

View File

@ -4,7 +4,7 @@ path = "src/main.rs"
[package]
name = "lesavka_client"
version = "0.17.12"
version = "0.17.13"
edition = "2024"
[dependencies]

View File

@ -1,6 +1,7 @@
use anyhow::{Context, Result, bail};
use lesavka_common::lesavka::{
CapturePowerCommand, HandshakeSet, SetCapturePowerRequest, relay_client::RelayClient,
CalibrationAction, CalibrationRequest, CalibrationState, CapturePowerCommand, HandshakeSet,
SetCapturePowerRequest, relay_client::RelayClient,
};
#[cfg(not(coverage))]
use lesavka_common::lesavka::{Empty, handshake_client::HandshakeClient};
@ -14,6 +15,11 @@ use lesavka_client::relay_transport;
enum CommandKind {
Status,
Version,
CalibrationStatus,
CalibrationAdjust,
CalibrationRestoreDefault,
CalibrationRestoreFactory,
CalibrationSaveDefault,
Auto,
On,
Off,
@ -30,6 +36,15 @@ impl CommandKind {
match value {
"status" | "get" => Some(Self::Status),
"version" | "versions" => Some(Self::Version),
"calibration" | "calibration-status" => Some(Self::CalibrationStatus),
"calibrate" | "calibration-adjust" => Some(Self::CalibrationAdjust),
"calibration-restore-default" | "restore-calibration" => {
Some(Self::CalibrationRestoreDefault)
}
"calibration-restore-factory" | "factory-calibration" => {
Some(Self::CalibrationRestoreFactory)
}
"calibration-save-default" | "save-calibration" => Some(Self::CalibrationSaveDefault),
"auto" => Some(Self::Auto),
"on" | "force-on" => Some(Self::On),
"off" | "force-off" => Some(Self::Off),
@ -47,6 +62,9 @@ impl CommandKind {
struct Config {
server: String,
command: CommandKind,
audio_delta_us: i64,
video_delta_us: i64,
note: String,
}
#[derive(Debug, Eq, PartialEq)]
@ -56,7 +74,7 @@ enum ParseOutcome {
}
fn usage() -> &'static str {
"Usage: lesavka-relayctl [--server http://HOST:50051] <status|version|upstream-sync|auto|on|off|recover-usb|recover-uac|recover-uvc|reset-usb>"
"Usage: lesavka-relayctl [--server http://HOST:50051] <status|version|upstream-sync|calibration|calibrate <audio_delta_us> <video_delta_us> [note]|calibration-save-default|calibration-restore-default|calibration-restore-factory|auto|on|off|recover-usb|recover-uac|recover-uvc|reset-usb>"
}
fn parse_args_outcome_from<I, S>(args: I) -> Result<ParseOutcome>
@ -67,6 +85,7 @@ where
let mut args = args.into_iter().map(Into::into);
let mut server = "http://127.0.0.1:50051".to_string();
let mut command = None;
let mut command_args = Vec::new();
while let Some(arg) = args.next() {
match arg.as_str() {
@ -86,16 +105,46 @@ where
bail!("unknown command `{arg}`\n{}", usage());
}
}
_ => bail!("unexpected argument `{arg}`\n{}", usage()),
_ => command_args.push(arg),
}
}
let command = command.unwrap_or(CommandKind::Status);
let (audio_delta_us, video_delta_us, note) = parse_command_args(command, command_args)?;
Ok(ParseOutcome::Run(Config {
server,
command: command.unwrap_or(CommandKind::Status),
command,
audio_delta_us,
video_delta_us,
note,
}))
}
fn parse_command_args(command: CommandKind, args: Vec<String>) -> Result<(i64, i64, String)> {
if command != CommandKind::CalibrationAdjust {
if let Some(arg) = args.first() {
bail!("unexpected argument `{arg}`\n{}", usage());
}
return Ok((0, 0, String::new()));
}
if args.len() < 2 {
bail!(
"calibrate requires audio_delta_us and video_delta_us\n{}",
usage()
);
}
let audio_delta_us = args[0]
.parse::<i64>()
.with_context(|| format!("parsing audio_delta_us `{}`", args[0]))?;
let video_delta_us = args[1]
.parse::<i64>()
.with_context(|| format!("parsing video_delta_us `{}`", args[1]))?;
let note = args[2..].join(" ");
Ok((audio_delta_us, video_delta_us, note))
}
#[cfg(test)]
fn parse_args_from<I, S>(args: I) -> Result<Config>
where
@ -116,6 +165,11 @@ fn capture_power_request(command: CommandKind) -> Option<SetCapturePowerRequest>
let (enabled, command) = match command {
CommandKind::Status
| CommandKind::Version
| CommandKind::CalibrationStatus
| CommandKind::CalibrationAdjust
| CommandKind::CalibrationRestoreDefault
| CommandKind::CalibrationRestoreFactory
| CommandKind::CalibrationSaveDefault
| CommandKind::RecoverUsb
| CommandKind::RecoverUac
| CommandKind::RecoverUvc
@ -221,6 +275,66 @@ fn print_upstream_sync(state: lesavka_common::lesavka::UpstreamSyncState) {
println!("planner_detail={}", state.last_reason);
}
fn print_calibration_state(state: CalibrationState) {
println!("calibration_profile={}", state.profile);
println!(
"calibration_factory_audio_offset_us={}",
state.factory_audio_offset_us
);
println!(
"calibration_factory_video_offset_us={}",
state.factory_video_offset_us
);
println!(
"calibration_default_audio_offset_us={}",
state.default_audio_offset_us
);
println!(
"calibration_default_video_offset_us={}",
state.default_video_offset_us
);
println!(
"calibration_active_audio_offset_us={}",
state.active_audio_offset_us
);
println!(
"calibration_active_video_offset_us={}",
state.active_video_offset_us
);
println!("calibration_source={}", state.source);
println!("calibration_confidence={}", state.confidence);
println!("calibration_updated_at={}", state.updated_at);
println!("calibration_detail={}", state.detail);
}
fn calibration_request_for(config: &Config) -> Option<CalibrationRequest> {
let action = match config.command {
CommandKind::CalibrationAdjust => CalibrationAction::AdjustActive,
CommandKind::CalibrationRestoreDefault => CalibrationAction::RestoreDefault,
CommandKind::CalibrationRestoreFactory => CalibrationAction::RestoreFactory,
CommandKind::CalibrationSaveDefault => CalibrationAction::SaveActiveAsDefault,
CommandKind::Status
| CommandKind::Version
| CommandKind::CalibrationStatus
| CommandKind::Auto
| CommandKind::On
| CommandKind::Off
| CommandKind::RecoverUsb
| CommandKind::RecoverUac
| CommandKind::RecoverUvc
| CommandKind::ResetUsb
| CommandKind::UpstreamSync => return None,
};
Some(CalibrationRequest {
action: action as i32,
audio_delta_us: config.audio_delta_us,
video_delta_us: config.video_delta_us,
observed_delivery_skew_ms: 0.0,
observed_enqueue_skew_ms: 0.0,
note: config.note.clone(),
})
}
#[cfg(not(coverage))]
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> {
@ -247,6 +361,11 @@ async fn main() -> Result<()> {
CommandKind::Off => "forcing capture power off",
CommandKind::Status
| CommandKind::Version
| CommandKind::CalibrationStatus
| CommandKind::CalibrationAdjust
| CommandKind::CalibrationRestoreDefault
| CommandKind::CalibrationRestoreFactory
| CommandKind::CalibrationSaveDefault
| CommandKind::RecoverUsb
| CommandKind::RecoverUac
| CommandKind::RecoverUvc
@ -262,6 +381,16 @@ async fn main() -> Result<()> {
return Ok(());
}
if let Some(request) = calibration_request_for(&config) {
let reply = client
.calibrate(Request::new(request))
.await
.context("applying upstream A/V calibration")?
.into_inner();
print_calibration_state(reply);
return Ok(());
}
let reply = match config.command {
CommandKind::Status => client
.get_capture_power(Request::new(Empty {}))
@ -313,9 +442,22 @@ async fn main() -> Result<()> {
print_upstream_sync(reply);
return Ok(());
}
CommandKind::CalibrationStatus => {
let reply = client
.get_calibration(Request::new(Empty {}))
.await
.context("querying upstream A/V calibration")?
.into_inner();
print_calibration_state(reply);
return Ok(());
}
CommandKind::Version | CommandKind::Auto | CommandKind::On | CommandKind::Off => {
unreachable!()
}
CommandKind::CalibrationAdjust
| CommandKind::CalibrationRestoreDefault
| CommandKind::CalibrationRestoreFactory
| CommandKind::CalibrationSaveDefault => unreachable!(),
};
print_state(reply);
@ -330,8 +472,8 @@ fn main() {
#[cfg(test)]
mod tests {
use super::{
CapturePowerCommand, CommandKind, ParseOutcome, capture_power_request, parse_args_from,
parse_args_outcome_from,
CalibrationAction, CapturePowerCommand, CommandKind, Config, ParseOutcome,
calibration_request_for, capture_power_request, parse_args_from, parse_args_outcome_from,
};
use lesavka_common::lesavka::CapturePowerState;
@ -342,6 +484,26 @@ mod tests {
assert_eq!(CommandKind::parse("get"), Some(CommandKind::Status));
assert_eq!(CommandKind::parse("version"), Some(CommandKind::Version));
assert_eq!(CommandKind::parse("versions"), Some(CommandKind::Version));
assert_eq!(
CommandKind::parse("calibration"),
Some(CommandKind::CalibrationStatus)
);
assert_eq!(
CommandKind::parse("calibrate"),
Some(CommandKind::CalibrationAdjust)
);
assert_eq!(
CommandKind::parse("calibration-restore-default"),
Some(CommandKind::CalibrationRestoreDefault)
);
assert_eq!(
CommandKind::parse("calibration-restore-factory"),
Some(CommandKind::CalibrationRestoreFactory)
);
assert_eq!(
CommandKind::parse("calibration-save-default"),
Some(CommandKind::CalibrationSaveDefault)
);
assert_eq!(
CommandKind::parse("upstream-sync"),
Some(CommandKind::UpstreamSync)
@ -373,6 +535,9 @@ mod tests {
let config = parse_args_from(std::iter::empty::<&str>()).expect("default config");
assert_eq!(config.server, "http://127.0.0.1:50051");
assert_eq!(config.command, CommandKind::Status);
assert_eq!(config.audio_delta_us, 0);
assert_eq!(config.video_delta_us, 0);
assert!(config.note.is_empty());
}
#[test]
@ -383,11 +548,31 @@ mod tests {
assert_eq!(config.command, CommandKind::UpstreamSync);
}
#[test]
fn parse_args_accepts_calibration_adjustment() {
let config = parse_args_from([
"--server",
"http://lab:50051",
"calibrate",
"0",
"71600",
"probe",
"median",
])
.expect("calibration config");
assert_eq!(config.command, CommandKind::CalibrationAdjust);
assert_eq!(config.audio_delta_us, 0);
assert_eq!(config.video_delta_us, 71_600);
assert_eq!(config.note, "probe median");
}
#[test]
fn parse_args_rejects_bad_inputs() {
assert!(parse_args_from(["--server"]).is_err());
assert!(parse_args_from(["nope"]).is_err());
assert!(parse_args_from(["status", "extra"]).is_err());
assert!(parse_args_from(["calibrate"]).is_err());
assert!(parse_args_from(["calibrate", "0", "not-int"]).is_err());
}
#[test]
@ -437,6 +622,11 @@ mod tests {
assert!(capture_power_request(CommandKind::Status).is_none());
assert!(capture_power_request(CommandKind::Version).is_none());
assert!(capture_power_request(CommandKind::CalibrationStatus).is_none());
assert!(capture_power_request(CommandKind::CalibrationAdjust).is_none());
assert!(capture_power_request(CommandKind::CalibrationRestoreDefault).is_none());
assert!(capture_power_request(CommandKind::CalibrationRestoreFactory).is_none());
assert!(capture_power_request(CommandKind::CalibrationSaveDefault).is_none());
assert!(capture_power_request(CommandKind::RecoverUsb).is_none());
assert!(capture_power_request(CommandKind::RecoverUac).is_none());
assert!(capture_power_request(CommandKind::RecoverUvc).is_none());
@ -456,4 +646,43 @@ mod tests {
detail: "ready".to_string(),
});
}
#[test]
fn print_calibration_accepts_full_payload() {
super::print_calibration_state(lesavka_common::lesavka::CalibrationState {
profile: "mjpeg".to_string(),
factory_audio_offset_us: 0,
factory_video_offset_us: 1_090_000,
default_audio_offset_us: 0,
default_video_offset_us: 1_090_000,
active_audio_offset_us: 0,
active_video_offset_us: 1_161_600,
source: "manual".to_string(),
confidence: "manual".to_string(),
updated_at: "2026-05-02T00:00:00Z".to_string(),
detail: "probe nudge".to_string(),
});
}
#[test]
fn calibration_requests_are_only_built_for_calibration_mutations() {
let config = Config {
server: "http://127.0.0.1:50051".to_string(),
command: CommandKind::CalibrationAdjust,
audio_delta_us: 0,
video_delta_us: 71_600,
note: "probe".to_string(),
};
let request = calibration_request_for(&config).expect("request");
assert_eq!(request.action, CalibrationAction::AdjustActive as i32);
assert_eq!(request.audio_delta_us, 0);
assert_eq!(request.video_delta_us, 71_600);
assert_eq!(request.note, "probe");
let status = Config {
command: CommandKind::CalibrationStatus,
..config
};
assert!(calibration_request_for(&status).is_none());
}
}

View File

@ -1,6 +1,6 @@
[package]
name = "lesavka_common"
version = "0.17.12"
version = "0.17.13"
edition = "2024"
build = "build.rs"

View File

@ -23,6 +23,9 @@ PROBE_PULSE_PERIOD_MS=${PROBE_PULSE_PERIOD_MS:-1000}
PROBE_PULSE_WIDTH_MS=${PROBE_PULSE_WIDTH_MS:-120}
PROBE_MARKER_TICK_PERIOD=${PROBE_MARKER_TICK_PERIOD:-5}
PROBE_EVENT_WIDTH_CODES=${PROBE_EVENT_WIDTH_CODES:-1,2,1,3,2,4,1,1,3,1,4,2,1,2,3,4,1,3,2,2,4,1,2,4,3,1,1,4,2,3,1,2}
LESAVKA_SYNC_APPLY_CALIBRATION=${LESAVKA_SYNC_APPLY_CALIBRATION:-0}
LESAVKA_SYNC_SAVE_CALIBRATION=${LESAVKA_SYNC_SAVE_CALIBRATION:-0}
LESAVKA_SYNC_CALIBRATION_TARGET=${LESAVKA_SYNC_CALIBRATION_TARGET:-video}
STIMULUS_PORT=${STIMULUS_PORT:-18444}
STIMULUS_SETTLE_SECONDS=${STIMULUS_SETTLE_SECONDS:-10}
LOCAL_OUTPUT_DIR=${LOCAL_OUTPUT_DIR:-"${REPO_ROOT}/tmp"}
@ -221,6 +224,134 @@ print_upstream_sync_state() {
done <<<"${sync_output}"
}
print_upstream_calibration_state() {
local label="$1"
echo "==> upstream calibration state (${label})"
if [[ ! -x "${REPO_ROOT}/target/debug/lesavka-relayctl" ]]; then
(cd "${REPO_ROOT}" && cargo build -p lesavka_client --bin lesavka-relayctl >/dev/null)
fi
local calibration_output
if ! calibration_output="$(
LESAVKA_TLS_DOMAIN="${LESAVKA_TLS_DOMAIN}" \
"${REPO_ROOT}/target/debug/lesavka-relayctl" \
--server "${RESOLVED_LESAVKA_SERVER_ADDR}" \
calibration 2>&1
)"; then
echo " ↪ calibration query failed: ${calibration_output}"
return 0
fi
while IFS= read -r line; do
[[ -n "${line}" ]] && echo "${line}"
done <<<"${calibration_output}"
}
latest_report_json() {
find "${ARTIFACT_DIR}" -mindepth 2 -maxdepth 2 -type f -name report.json -printf '%T@ %p\n' 2>/dev/null \
| sort -n \
| tail -n 1 \
| cut -d' ' -f2-
}
maybe_apply_probe_calibration() {
local report_json
report_json="$(latest_report_json)"
echo "==> probe calibration decision"
if [[ -z "${report_json}" || ! -f "${report_json}" ]]; then
echo " ↪ report_json=missing"
echo " ↪ calibration apply skipped: analyzer report was not produced"
return 0
fi
local summary
if ! summary="$(python3 - "${report_json}" "${LESAVKA_SYNC_CALIBRATION_TARGET}" <<'PY'
import json
import shlex
import sys
report_path = sys.argv[1]
target = sys.argv[2].strip().lower()
with open(report_path, "r", encoding="utf-8") as handle:
report = json.load(handle)
cal = report.get("calibration", {})
verdict = report.get("verdict", {})
if target not in {"audio", "video"}:
target = "video"
audio_recommendation = int(cal.get("recommended_audio_offset_adjust_us") or 0)
video_recommendation = int(cal.get("recommended_video_offset_adjust_us") or 0)
audio_delta = audio_recommendation if target == "audio" else 0
video_delta = video_recommendation if target == "video" else 0
fields = {
"report_json": report_path,
"calibration_ready": str(bool(cal.get("ready"))).lower(),
"calibration_target": target,
"calibration_audio_recommendation_us": audio_recommendation,
"calibration_video_recommendation_us": video_recommendation,
"calibration_apply_audio_delta_us": audio_delta,
"calibration_apply_video_delta_us": video_delta,
"calibration_note": cal.get("note", ""),
"verdict_status": verdict.get("status", ""),
"paired_pulses": report.get("paired_event_count", 0),
"median_skew_ms": f"{float(report.get('median_skew_ms', 0.0)):+.1f}",
"p95_abs_skew_ms": f"{float(verdict.get('p95_abs_skew_ms', 0.0)):.1f}",
"drift_ms": f"{float(report.get('drift_ms', 0.0)):+.1f}",
}
for key, value in fields.items():
print(f"{key}={shlex.quote(str(value))}")
PY
)"; then
echo " ↪ failed to parse ${report_json}; calibration apply skipped" >&2
return 0
fi
eval "${summary}"
echo " ↪ report_json=${report_json}"
echo " ↪ verdict_status=${verdict_status}"
echo " ↪ paired_pulses=${paired_pulses}"
echo " ↪ median_skew_ms=${median_skew_ms}"
echo " ↪ p95_abs_skew_ms=${p95_abs_skew_ms}"
echo " ↪ drift_ms=${drift_ms}"
echo " ↪ calibration_ready=${calibration_ready}"
echo " ↪ calibration_target=${calibration_target}"
echo " ↪ recommended_audio_offset_adjust_us=${calibration_audio_recommendation_us}"
echo " ↪ recommended_video_offset_adjust_us=${calibration_video_recommendation_us}"
echo " ↪ calibration_note=${calibration_note}"
if [[ "${LESAVKA_SYNC_APPLY_CALIBRATION}" != "1" ]]; then
echo " ↪ calibration apply disabled; set LESAVKA_SYNC_APPLY_CALIBRATION=1 to apply a ready recommendation"
return 0
fi
if [[ "${calibration_ready}" != "true" ]]; then
echo " ↪ calibration apply refused: analyzer did not mark this report calibration-ready"
return 0
fi
if [[ "${calibration_apply_audio_delta_us}" == "0" && "${calibration_apply_video_delta_us}" == "0" ]]; then
echo " ↪ calibration apply skipped: recommended delta is already zero"
return 0
fi
local note="mirrored probe ${STAMP}: target=${calibration_target}, median=${median_skew_ms}ms, p95=${p95_abs_skew_ms}ms, pairs=${paired_pulses}"
echo " ↪ applying calibration: audio_delta_us=${calibration_apply_audio_delta_us}, video_delta_us=${calibration_apply_video_delta_us}"
LESAVKA_TLS_DOMAIN="${LESAVKA_TLS_DOMAIN}" \
"${REPO_ROOT}/target/debug/lesavka-relayctl" \
--server "${RESOLVED_LESAVKA_SERVER_ADDR}" \
calibrate "${calibration_apply_audio_delta_us}" "${calibration_apply_video_delta_us}" "${note}" \
| sed 's/^/ ↪ /'
if [[ "${LESAVKA_SYNC_SAVE_CALIBRATION}" == "1" ]]; then
echo " ↪ saving active calibration as site default"
LESAVKA_TLS_DOMAIN="${LESAVKA_TLS_DOMAIN}" \
"${REPO_ROOT}/target/debug/lesavka-relayctl" \
--server "${RESOLVED_LESAVKA_SERVER_ADDR}" \
calibration-save-default \
| sed 's/^/ ↪ /'
else
echo " ↪ active calibration not saved; set LESAVKA_SYNC_SAVE_CALIBRATION=1 after a confirming rerun"
fi
}
start_local_stimulus() {
echo "==> starting local A/V stimulus server"
python3 "${REPO_ROOT}/scripts/manual/local_av_stimulus.py" \
@ -303,12 +434,15 @@ echo "==> prebuilding real client and analyzer"
start_server_tunnel_if_needed
print_lesavka_versions
print_upstream_calibration_state "before mirrored run"
print_upstream_sync_state "before mirrored run"
start_local_stimulus
start_real_lesavka_client
run_status=0
run_browser_capture_with_real_driver || run_status=$?
maybe_apply_probe_calibration
print_upstream_sync_state "after mirrored run"
print_upstream_calibration_state "after mirrored run"
if ((run_status != 0)); then
echo "==> mirrored probe failed"

View File

@ -10,7 +10,7 @@ bench = false
[package]
name = "lesavka_server"
version = "0.17.12"
version = "0.17.13"
edition = "2024"
autobins = false

View File

@ -111,7 +111,17 @@ fn mirrored_sync_script_uses_real_client_capture_path() {
"combined version+revision",
"run_status=0",
"run_browser_capture_with_real_driver || run_status=$?",
"LESAVKA_SYNC_APPLY_CALIBRATION=${LESAVKA_SYNC_APPLY_CALIBRATION:-0}",
"LESAVKA_SYNC_SAVE_CALIBRATION=${LESAVKA_SYNC_SAVE_CALIBRATION:-0}",
"LESAVKA_SYNC_CALIBRATION_TARGET=${LESAVKA_SYNC_CALIBRATION_TARGET:-video}",
"print_upstream_calibration_state \"before mirrored run\"",
"maybe_apply_probe_calibration",
"calibration_ready=${calibration_ready}",
"calibration apply refused: analyzer did not mark this report calibration-ready",
"calibrate \"${calibration_apply_audio_delta_us}\" \"${calibration_apply_video_delta_us}\"",
"calibration-save-default",
"print_upstream_sync_state \"after mirrored run\"",
"print_upstream_calibration_state \"after mirrored run\"",
"==> mirrored probe failed",
] {
assert!(

View File

@ -14,6 +14,22 @@ mod relayctl_binary {
fn command_aliases_and_usage_are_stable() {
assert_eq!(CommandKind::parse("status"), Some(CommandKind::Status));
assert_eq!(CommandKind::parse("get"), Some(CommandKind::Status));
assert_eq!(
CommandKind::parse("calibration"),
Some(CommandKind::CalibrationStatus)
);
assert_eq!(
CommandKind::parse("calibration-status"),
Some(CommandKind::CalibrationStatus)
);
assert_eq!(
CommandKind::parse("calibrate"),
Some(CommandKind::CalibrationAdjust)
);
assert_eq!(
CommandKind::parse("calibration-save-default"),
Some(CommandKind::CalibrationSaveDefault)
);
assert_eq!(CommandKind::parse("auto"), Some(CommandKind::Auto));
assert_eq!(CommandKind::parse("on"), Some(CommandKind::On));
assert_eq!(CommandKind::parse("force-on"), Some(CommandKind::On));
@ -40,6 +56,7 @@ mod relayctl_binary {
assert!(usage().contains("lesavka-relayctl"));
assert!(usage().contains("recover-uac"));
assert!(usage().contains("recover-uvc"));
assert!(usage().contains("calibrate <audio_delta_us> <video_delta_us>"));
}
#[tokio::test(flavor = "current_thread")]