diff --git a/Cargo.lock b/Cargo.lock index 8aa1cab..4de1d2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1652,7 +1652,7 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "lesavka_client" -version = "0.19.25" +version = "0.19.26" dependencies = [ "anyhow", "async-stream", @@ -1686,7 +1686,7 @@ dependencies = [ [[package]] name = "lesavka_common" -version = "0.19.25" +version = "0.19.26" dependencies = [ "anyhow", "base64", @@ -1698,7 +1698,7 @@ dependencies = [ [[package]] name = "lesavka_server" -version = "0.19.25" +version = "0.19.26" dependencies = [ "anyhow", "base64", diff --git a/client/Cargo.toml b/client/Cargo.toml index 7a4aaf7..422bce1 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -4,7 +4,7 @@ path = "src/main.rs" [package] name = "lesavka_client" -version = "0.19.25" +version = "0.19.26" edition = "2024" [dependencies] diff --git a/common/Cargo.toml b/common/Cargo.toml index f157cff..a2186ff 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lesavka_common" -version = "0.19.25" +version = "0.19.26" edition = "2024" build = "build.rs" diff --git a/scripts/manual/run_server_to_rc_mode_matrix.sh b/scripts/manual/run_server_to_rc_mode_matrix.sh index 52cbead..77daf95 100755 --- a/scripts/manual/run_server_to_rc_mode_matrix.sh +++ b/scripts/manual/run_server_to_rc_mode_matrix.sh @@ -78,8 +78,10 @@ LESAVKA_SERVER_RC_REQUIRE_ALL_CODED_PAIRS=${LESAVKA_SERVER_RC_REQUIRE_ALL_CODED_ LESAVKA_SERVER_RC_REQUIRE_SMOOTHNESS_PASS=${LESAVKA_SERVER_RC_REQUIRE_SMOOTHNESS_PASS:-0} LESAVKA_SERVER_RC_SIGNAL_READY=${LESAVKA_SERVER_RC_SIGNAL_READY:-1} LESAVKA_SERVER_RC_SIGNAL_READY_MIN_PAIRS=${LESAVKA_SERVER_RC_SIGNAL_READY_MIN_PAIRS:-3} -LESAVKA_SERVER_RC_SIGNAL_READY_DURATION_SECONDS=${LESAVKA_SERVER_RC_SIGNAL_READY_DURATION_SECONDS:-8} +LESAVKA_SERVER_RC_SIGNAL_READY_DURATION_SECONDS=${LESAVKA_SERVER_RC_SIGNAL_READY_DURATION_SECONDS:-12} LESAVKA_SERVER_RC_SIGNAL_READY_WARMUP_SECONDS=${LESAVKA_SERVER_RC_SIGNAL_READY_WARMUP_SECONDS:-1} +LESAVKA_SERVER_RC_SIGNAL_READY_ATTEMPTS=${LESAVKA_SERVER_RC_SIGNAL_READY_ATTEMPTS:-4} +LESAVKA_SERVER_RC_SIGNAL_READY_RETRY_DELAY_SECONDS=${LESAVKA_SERVER_RC_SIGNAL_READY_RETRY_DELAY_SECONDS:-5} LESAVKA_SERVER_RC_MAX_VIDEO_HICCUPS=${LESAVKA_SERVER_RC_MAX_VIDEO_HICCUPS:-0} LESAVKA_SERVER_RC_MAX_AUDIO_HICCUPS=${LESAVKA_SERVER_RC_MAX_AUDIO_HICCUPS:-0} @@ -364,6 +366,229 @@ raise SystemExit(0 if passed else 1) PY } +write_signal_readiness_attempt_result() { + local attempt=$1 + local artifact_dir=$2 + local run_status=$3 + local run_log=$4 + local min_pairs=$5 + local output_json=$6 + python3 - <<'PY' \ + "${attempt}" \ + "${artifact_dir}" \ + "${run_status}" \ + "${run_log}" \ + "${min_pairs}" \ + "${output_json}" +import json +import pathlib +import re +import sys + +( + attempt_raw, + artifact_dir_raw, + run_status_raw, + run_log_raw, + min_pairs_raw, + output_json_raw, +) = sys.argv[1:] + +def as_int(value, default=0): + try: + return int(str(value).strip()) + except Exception: + return default + +def as_float(value, default=0.0): + try: + return float(str(value).strip()) + except Exception: + return default + +def load_json(path): + try: + return json.loads(path.read_text()) + except Exception: + return {} + +def read_text(path): + try: + return path.read_text(errors="replace") + except Exception: + return "" + +attempt = as_int(attempt_raw) +run_status = as_int(run_status_raw) +min_pairs = as_int(min_pairs_raw, 1) +run_log = pathlib.Path(run_log_raw) +artifact_dir = pathlib.Path(artifact_dir_raw) if artifact_dir_raw else None +if artifact_dir is None or not artifact_dir.exists(): + match = re.findall(r"^artifact_dir:\s*(.+)$", read_text(run_log), flags=re.MULTILINE) + if match: + artifact_dir = pathlib.Path(match[-1].strip()) +if artifact_dir is None: + artifact_dir = pathlib.Path("__missing_signal_readiness_artifact__") + +report = load_json(artifact_dir / "report.json") +timeline = load_json(artifact_dir / "server-output-timeline.json") +error_log = artifact_dir / "analysis-error.log" +error_text = read_text(error_log) +if not error_text: + error_text = read_text(run_log) + +events = timeline.get("events") or [] +server_event_count = len(events) +server_video_handoff_count = sum( + 1 + for event in events + if event.get("video_feed_unix_ns") is not None + or event.get("video_feed_monotonic_us") is not None +) +server_audio_handoff_count = sum( + 1 + for event in events + if event.get("audio_push_unix_ns") is not None + or event.get("audio_push_monotonic_us") is not None +) + +verdict = report.get("verdict") or {} +signature_coverage = report.get("signature_coverage") or {} +video_events = as_int(report.get("video_event_count")) +audio_events = as_int(report.get("audio_event_count")) +paired_events = as_int(report.get("paired_event_count")) +coded_pairs = as_int(signature_coverage.get("paired_event_count"), paired_events) +expected_pairs = as_int(signature_coverage.get("expected_event_count")) +raw_activity_delta_ms = as_float(report.get("activity_start_delta_ms")) +analyzer_failure = "" + +failure_match = re.search( + r"coded pulse common window removed one stream entirely;[^\n]*" + r"\(video=(?P