#!/usr/bin/env python3
"""Qualify concurrent 10 Hz tracking and lower-rate semantic perception."""

from __future__ import annotations

import argparse
import hashlib
import importlib.metadata
import json
import math
import os
import platform
import resource
import shutil
import threading
import time
from collections import Counter
from dataclasses import dataclass
from datetime import UTC, datetime
from pathlib import Path
from typing import Any

import numpy as np
from run_e4_full_session_segmentation import (
    TARGET_CLASS_COUNT,
    _dependency_manifest,
    _load_model,
)
from run_e4_full_session_segmentation import (
    _profile as _read_semantic_profile,
)
from run_e4_full_session_segmentation import (
    _validate_source as _validate_semantic_source,
)
from run_e5_instance_tracking import (
    TwoStageTracker,
    _detections,
    _duplicate_pairs,
    _infer,
    _load_valid_fov,
    _preprocess,
    _read_timeline,
    _track_document,
    _validate_source,
    _verify_model,
)
from run_e8_realtime_tracking import (
    FrameEnvelope,
    LatestWinsQueue,
    _wait_until,
)
from run_e8_realtime_tracking import (
    _read_profile as _read_detector_profile,
)
from run_recorded_perception_epoch import (
    _artifact,
    _canonical_json,
    _GpuTelemetry,
    _percentiles,
    _sha256,
    _valid_sha256,
    _validate_job,
    _write_json,
)

PROFILE_SCHEMA = "missioncore.e9-multirate-perception-profile/v1"
DETECTOR_FRAME_SCHEMA = "missioncore.e9-multirate-detector-frame/v1"
SEMANTIC_FRAME_SCHEMA = "missioncore.e9-multirate-semantic-frame/v1"
MERGED_FRAME_SCHEMA = "missioncore.e9-multirate-merged-frame/v1"
REPORT_SCHEMA = "missioncore.e9-multirate-perception-report/v1"
RESULT_SCHEMA = "missioncore.e9-multirate-perception-result/v1"
IDENTITY_SCHEMA = "missioncore.e9-multirate-perception-identity/v1"
PIPELINE_ID = "concurrent-yolox-tracking-eomt-semantic-latest-wins/v1"


def _arguments() -> argparse.Namespace:
    parser = argparse.ArgumentParser()
    commands = parser.add_subparsers(dest="command", required=True)
    for name in ("preflight", "run"):
        command = commands.add_parser(name)
        command.add_argument("--job", type=Path, required=True)
        command.add_argument("--profile", type=Path, required=True)
        command.add_argument("--detector-profile", type=Path, required=True)
        command.add_argument("--semantic-profile", type=Path, required=True)
        command.add_argument("--valid-fov-root", type=Path, required=True)
        command.add_argument("--model-root", type=Path, required=True)
        command.add_argument("--cache", type=Path, required=True)
        command.add_argument("--environment", type=Path, required=True)
        if name == "run":
            command.add_argument("--frames", type=Path, required=True)
            command.add_argument("--timeline", type=Path, required=True)
            command.add_argument("--output", type=Path, required=True)
            command.add_argument("--triton-url", required=True)
            command.add_argument("--free-bytes-floor", type=int, default=0)
            command.add_argument("--orchestrator-sha256", required=True)
            command.add_argument("--container-image", required=True)
            command.add_argument("--telemetry-interval-seconds", type=float, default=1.0)
    return parser.parse_args()


def _read_object(path: Path) -> dict[str, Any]:
    value = json.loads(path.read_text(encoding="utf-8-sig"))
    if not isinstance(value, dict):
        raise RuntimeError(f"JSON root is not an object: {path}")
    return value


def _read_profile(path: Path) -> tuple[dict[str, Any], str]:
    resolved = path.resolve(strict=True)
    profile = _read_object(resolved)
    replay = profile.get("replay")
    acceptance = profile.get("acceptance")
    source = profile.get("source")
    if (
        profile.get("schema_version") != PROFILE_SCHEMA
        or profile.get("mode") not in {"pilot", "qualification"}
        or not isinstance(replay, dict)
        or not isinstance(acceptance, dict)
        or not isinstance(source, dict)
        or source.get("source_id") != "sensor.camera.right"
        or source.get("resolution") != [800, 600]
    ):
        raise RuntimeError("LAB E9 profile contract changed")
    speed = replay.get("speed")
    detector_capacity = replay.get("detector_queue_capacity")
    semantic_capacity = replay.get("semantic_queue_capacity")
    semantic_stride = replay.get("semantic_sample_every_frames")
    semantic_ttl = replay.get("semantic_ttl_ms")
    if (
        not _positive_number(speed)
        or not 0.1 <= float(speed) <= 10
        or not isinstance(detector_capacity, int)
        or isinstance(detector_capacity, bool)
        or not 1 <= detector_capacity <= 8
        or not isinstance(semantic_capacity, int)
        or isinstance(semantic_capacity, bool)
        or not 1 <= semantic_capacity <= 4
        or not isinstance(semantic_stride, int)
        or isinstance(semantic_stride, bool)
        or not 2 <= semantic_stride <= 30
        or not _positive_number(semantic_ttl)
        or not 100 <= float(semantic_ttl) <= 5000
    ):
        raise RuntimeError("LAB E9 replay configuration is invalid")
    required = (
        "detector_minimum_effective_fps",
        "detector_maximum_drop_fraction",
        "detector_maximum_p95_result_age_ms",
        "semantic_minimum_effective_fps",
        "semantic_maximum_drop_fraction",
        "semantic_maximum_p95_completion_age_ms",
        "minimum_fresh_semantic_coverage",
    )
    if (
        any(not _nonnegative_number(acceptance.get(name)) for name in required)
        or float(acceptance["detector_maximum_drop_fraction"]) > 1
        or float(acceptance["semantic_maximum_drop_fraction"]) > 1
        or float(acceptance["minimum_fresh_semantic_coverage"]) > 1
        or not isinstance(acceptance.get("require_zero_failures"), bool)
    ):
        raise RuntimeError("LAB E9 acceptance configuration is invalid")
    return profile, _sha256(resolved)


def _positive_number(value: object) -> bool:
    return _nonnegative_number(value) and float(value) > 0


def _nonnegative_number(value: object) -> bool:
    return (
        isinstance(value, int | float)
        and not isinstance(value, bool)
        and math.isfinite(float(value))
        and float(value) >= 0
    )


@dataclass(frozen=True, slots=True)
class SemanticResult:
    frame_index: int
    source_frame_index: int
    session_seconds: float
    completed_monotonic: float
    completion_age_ms: float
    mask_sha256: str
    class_pixels: dict[str, int]
    class_fractions: dict[str, float]


class LatestSemantic:
    def __init__(self) -> None:
        self._lock = threading.Lock()
        self._value: SemanticResult | None = None

    def publish(self, value: SemanticResult) -> None:
        with self._lock:
            if self._value is not None and value.frame_index <= self._value.frame_index:
                raise RuntimeError("LAB E9 semantic results are not monotonic")
            self._value = value

    def snapshot(self) -> SemanticResult | None:
        with self._lock:
            return self._value


def _producer(
    *,
    detector_queue: LatestWinsQueue,
    semantic_queue: LatestWinsQueue,
    semantic_stride: int,
    frame_paths: list[Path],
    timeline_rows: list[dict[str, Any]],
    replay_started: float,
    speed: float,
    error: list[BaseException],
) -> None:
    from PIL import Image

    first_session = float(timeline_rows[0]["session_seconds"])
    try:
        for frame_index, (path, row) in enumerate(zip(frame_paths, timeline_rows, strict=True)):
            scheduled = replay_started + (float(row["session_seconds"]) - first_session) / speed
            _wait_until(scheduled)
            decode_started = time.perf_counter()
            with Image.open(path) as opened:
                image = np.asarray(opened.convert("RGB"), dtype=np.uint8)
            decoded = time.perf_counter()
            if image.shape != (600, 800, 3):
                raise RuntimeError("LAB E9 input resolution changed")
            envelope = FrameEnvelope(
                frame_index=frame_index,
                path=path,
                timeline=row,
                scheduled_monotonic=scheduled,
                decoded_monotonic=decoded,
                image=image,
                decode_ms=(decoded - decode_started) * 1000.0,
                source_release_lag_ms=max(0.0, (decode_started - scheduled) * 1000.0),
            )
            detector_queue.publish(envelope)
            if frame_index % semantic_stride == 0:
                semantic_queue.publish(envelope)
    except BaseException as exc:
        error.append(exc)
    finally:
        detector_queue.close()
        semantic_queue.close()


def _semantic_worker(
    *,
    queue: LatestWinsQueue,
    latest: LatestSemantic,
    stream: Any,
    valid_mask: np.ndarray,
    target_lut: np.ndarray,
    target_names: dict[int, str],
    infer: Any,
    latency: dict[str, list[float]],
    failures: list[BaseException],
) -> None:
    try:
        while (envelope := queue.take()) is not None:
            started = time.perf_counter()
            latency["image_decode_ms"].append(envelope.decode_ms)
            latency["queue_wait_ms"].append(
                max(0.0, (started - envelope.decoded_monotonic) * 1000.0)
            )
            fill_started = time.perf_counter()
            model_input = np.where(valid_mask[..., None], envelope.image, 0).astype(np.uint8)
            latency["valid_fov_fill_ms"].append((time.perf_counter() - fill_started) * 1000.0)
            semantic, measured = infer(model_input)
            if semantic.max() >= len(target_lut):
                raise RuntimeError("LAB E9 EoMT emitted an unknown category")
            target = target_lut[semantic]
            target = target.copy()
            target[~valid_mask] = 0
            for name, value in measured.items():
                latency[name].append(float(value))
            completed = time.perf_counter()
            completion_age_ms = max(0.0, (completed - envelope.scheduled_monotonic) * 1000.0)
            latency["completion_age_ms"].append(completion_age_ms)
            latency["processing_ms"].append((completed - started) * 1000.0)
            counts = np.bincount(target[valid_mask], minlength=TARGET_CLASS_COUNT)
            class_pixels = {
                target_names[index]: int(counts[index])
                for index in range(1, TARGET_CLASS_COUNT)
                if int(counts[index]) > 0
            }
            valid_pixels = int(valid_mask.sum())
            result = SemanticResult(
                frame_index=envelope.frame_index,
                source_frame_index=int(envelope.timeline["source_frame_index"]),
                session_seconds=float(envelope.timeline["session_seconds"]),
                completed_monotonic=completed,
                completion_age_ms=completion_age_ms,
                mask_sha256=hashlib.sha256(target.tobytes()).hexdigest(),
                class_pixels=class_pixels,
                class_fractions={
                    name: round(count / valid_pixels, 9) for name, count in class_pixels.items()
                },
            )
            latest.publish(result)
            stream.write(
                json.dumps(
                    {
                        "schema_version": SEMANTIC_FRAME_SCHEMA,
                        "frame_index": result.frame_index,
                        "source_frame_index": result.source_frame_index,
                        "session_seconds": round(result.session_seconds, 9),
                        "completion_age_ms": round(result.completion_age_ms, 6),
                        "mask_sha256": result.mask_sha256,
                        "class_pixels": result.class_pixels,
                        "class_fractions": result.class_fractions,
                    },
                    sort_keys=True,
                    separators=(",", ":"),
                    allow_nan=False,
                )
                + "\n"
            )
    except BaseException as exc:
        failures.append(exc)


def _semantic_binding(
    semantic: SemanticResult | None,
    *,
    frame_session_seconds: float,
    ttl_ms: float,
) -> dict[str, Any]:
    if semantic is None:
        return {
            "status": "unavailable",
            "source_frame_index": None,
            "source_age_ms": None,
            "completion_age_ms": None,
            "mask_sha256": None,
        }
    source_age_ms = max(0.0, (frame_session_seconds - semantic.session_seconds) * 1000.0)
    return {
        "status": "fresh" if source_age_ms <= ttl_ms else "stale",
        "source_frame_index": semantic.source_frame_index,
        "source_age_ms": round(source_age_ms, 6),
        "completion_age_ms": round(semantic.completion_age_ms, 6),
        "mask_sha256": semantic.mask_sha256,
    }


def _semantic_infer_factory(
    processor: Any,
    model: Any,
    device: Any,
) -> Any:
    import torch
    from PIL import Image

    def infer(image: np.ndarray) -> tuple[np.ndarray, dict[str, float]]:
        processor_started = time.perf_counter()
        inputs = processor(images=Image.fromarray(image), return_tensors="pt")
        processor_ms = (time.perf_counter() - processor_started) * 1000.0
        torch.cuda.synchronize()
        transfer_started = time.perf_counter()
        inputs = {
            name: value.to(device) if isinstance(value, torch.Tensor) else value
            for name, value in inputs.items()
        }
        torch.cuda.synchronize()
        transfer_ms = (time.perf_counter() - transfer_started) * 1000.0
        forward_started = time.perf_counter()
        with torch.inference_mode(), torch.amp.autocast("cuda", dtype=torch.float16):
            outputs = model(**inputs)
        torch.cuda.synchronize()
        forward_ms = (time.perf_counter() - forward_started) * 1000.0
        post_started = time.perf_counter()
        semantic = processor.post_process_semantic_segmentation(
            outputs,
            target_sizes=[image.shape[:2]],
        )[0]
        semantic = semantic.detach().cpu().numpy().astype(np.uint8)
        post_ms = (time.perf_counter() - post_started) * 1000.0
        del inputs, outputs
        return semantic, {
            "processor_ms": processor_ms,
            "host_to_device_ms": transfer_ms,
            "forward_ms": forward_ms,
            "model_postprocess_ms": post_ms,
        }

    return infer


def _preflight(args: argparse.Namespace) -> int:
    import torch

    job = _validate_job(args.job.resolve(strict=True))
    profile, profile_sha256 = _read_profile(args.profile)
    detector_profile, detector_profile_sha256 = _read_detector_profile(args.detector_profile)
    semantic_profile, semantic_profile_sha256 = _read_semantic_profile(args.semantic_profile)
    _validate_source(job, detector_profile)
    _validate_semantic_source(job, semantic_profile)
    if profile["source"] != detector_profile["source"]:
        raise RuntimeError("LAB E9 source and detector bindings differ")
    if profile["source"] != {
        key: semantic_profile["source"][key]
        for key in ("source_id", "resolution", "calibration_slot", "calibration_sha256")
    }:
        raise RuntimeError("LAB E9 source and semantic bindings differ")
    valid_mask, valid_fov = _load_valid_fov(args.valid_fov_root, job, detector_profile)
    detector_files = _verify_model(detector_profile, args.model_root)
    dependency = _dependency_manifest(args.environment.resolve(strict=True))
    if dependency["identity"]["profile_sha256"] != semantic_profile_sha256:
        raise RuntimeError("LAB E9 semantic dependencies belong to another profile")
    if not torch.cuda.is_available():
        raise RuntimeError("CUDA is required for LAB E9")
    device = torch.device("cuda:0")
    processor, model, _labels, _lut, semantic_files = _load_model(
        semantic_profile,
        args.cache.resolve(strict=True),
        device,
    )
    del processor, model, _labels, _lut
    torch.cuda.empty_cache()
    print(
        json.dumps(
            {
                "state": "preflight-ready",
                "job_id": job["job_id"],
                "profile_sha256": profile_sha256,
                "detector_profile_sha256": detector_profile_sha256,
                "semantic_profile_sha256": semantic_profile_sha256,
                "detector_files": detector_files,
                "semantic_files": semantic_files,
                "valid_fov": valid_fov,
                "cuda_device": torch.cuda.get_device_name(),
            },
            sort_keys=True,
        ),
        flush=True,
    )
    return 0


def _run(args: argparse.Namespace) -> int:
    import torch
    import transformers
    from PIL import Image
    from scipy.optimize import linear_sum_assignment

    if not _valid_sha256(args.orchestrator_sha256):
        raise RuntimeError("LAB E9 orchestrator SHA-256 is invalid")
    job = _validate_job(args.job.resolve(strict=True))
    profile, profile_sha256 = _read_profile(args.profile)
    detector_profile, detector_profile_sha256 = _read_detector_profile(args.detector_profile)
    semantic_profile, semantic_profile_sha256 = _read_semantic_profile(args.semantic_profile)
    _validate_source(job, detector_profile)
    _validate_semantic_source(job, semantic_profile)
    valid_mask, valid_fov = _load_valid_fov(args.valid_fov_root, job, detector_profile)
    detector_files = _verify_model(detector_profile, args.model_root)
    dependency = _dependency_manifest(args.environment.resolve(strict=True))
    if dependency["identity"]["profile_sha256"] != semantic_profile_sha256:
        raise RuntimeError("LAB E9 semantic dependency identity changed")
    frames_root = args.frames.resolve(strict=True)
    frame_paths = sorted(frames_root.glob("frame-*.png"))
    if not frame_paths or [path.name for path in frame_paths] != [
        f"frame-{index:06d}.png" for index in range(1, len(frame_paths) + 1)
    ]:
        raise RuntimeError("LAB E9 decoded frame sequence changed")
    timeline_path = args.timeline.resolve(strict=True)
    timeline_rows = _read_timeline(timeline_path, len(frame_paths))

    output = args.output.resolve()
    if output.exists():
        raise RuntimeError("LAB E9 output must be absent")
    output.mkdir(mode=0o700, parents=True, exist_ok=False)
    _assert_disk_floor(output, args.free_bytes_floor, 0)

    if not torch.cuda.is_available():
        raise RuntimeError("CUDA is required for LAB E9")
    device = torch.device("cuda:0")
    processor, semantic_model, semantic_labels, target_lut, semantic_files = _load_model(
        semantic_profile,
        args.cache.resolve(strict=True),
        device,
    )
    target_names = {
        int(key): str(value) for key, value in semantic_profile["target_taxonomy"].items()
    }
    if set(target_names) != set(range(TARGET_CLASS_COUNT)):
        raise RuntimeError("LAB E9 target taxonomy changed")
    infer_semantic = _semantic_infer_factory(processor, semantic_model, device)

    tracker = TwoStageTracker(detector_profile["tracking"])
    linear_sum_assignment(np.zeros((1, 1), dtype=np.float64))
    with Image.open(frame_paths[0]) as opened:
        warm_image = np.asarray(opened.convert("RGB"), dtype=np.uint8)
    detector_tensor = _preprocess(warm_image, valid_mask, detector_profile)
    _infer(args.triton_url, detector_profile["model"], detector_tensor)
    semantic_input = np.where(valid_mask[..., None], warm_image, 0).astype(np.uint8)
    warm_semantic, _warm_latency = infer_semantic(semantic_input)
    del detector_tensor, semantic_input, warm_semantic, _warm_latency, warm_image

    replay = profile["replay"]
    detector_queue = LatestWinsQueue(int(replay["detector_queue_capacity"]))
    semantic_queue = LatestWinsQueue(int(replay["semantic_queue_capacity"]))
    latest_semantic = LatestSemantic()
    producer_errors: list[BaseException] = []
    semantic_errors: list[BaseException] = []
    detector_failures = 0
    detector_latency = {
        name: []
        for name in (
            "image_decode_ms",
            "source_release_lag_ms",
            "queue_wait_ms",
            "preprocess_ms",
            "triton_request_ms",
            "postprocess_ms",
            "tracking_ms",
            "processing_ms",
            "result_age_ms",
        )
    }
    semantic_latency = {
        name: []
        for name in (
            "image_decode_ms",
            "queue_wait_ms",
            "valid_fov_fill_ms",
            "processor_ms",
            "host_to_device_ms",
            "forward_ms",
            "model_postprocess_ms",
            "processing_ms",
            "completion_age_ms",
        )
    }
    detector_labels: Counter[str] = Counter()
    track_labels: Counter[str] = Counter()
    semantic_status: Counter[str] = Counter()
    unique_tracks: set[int] = set()
    duplicate_pairs = 0
    detector_path = output / "detector-frames.jsonl"
    semantic_path = output / "semantic-frames.jsonl"
    merged_path = output / "merged-frames.jsonl"
    gpu_path = output / "gpu-telemetry.jsonl"
    disk_before = shutil.disk_usage(output).free

    with (
        detector_path.open("x", encoding="utf-8", newline="\n") as detector_stream,
        semantic_path.open("x", encoding="utf-8", newline="\n") as semantic_stream,
        merged_path.open("x", encoding="utf-8", newline="\n") as merged_stream,
        gpu_path.open("x", encoding="utf-8", newline="\n") as gpu_stream,
        _GpuTelemetry(gpu_stream, args.telemetry_interval_seconds) as gpu_telemetry,
    ):
        semantic_thread = threading.Thread(
            target=_semantic_worker,
            kwargs={
                "queue": semantic_queue,
                "latest": latest_semantic,
                "stream": semantic_stream,
                "valid_mask": valid_mask,
                "target_lut": target_lut,
                "target_names": target_names,
                "infer": infer_semantic,
                "latency": semantic_latency,
                "failures": semantic_errors,
            },
            name="lab-e9-semantic-consumer",
            daemon=True,
        )
        semantic_thread.start()
        replay_started = time.perf_counter() + 0.25
        producer = threading.Thread(
            target=_producer,
            kwargs={
                "detector_queue": detector_queue,
                "semantic_queue": semantic_queue,
                "semantic_stride": int(replay["semantic_sample_every_frames"]),
                "frame_paths": frame_paths,
                "timeline_rows": timeline_rows,
                "replay_started": replay_started,
                "speed": float(replay["speed"]),
                "error": producer_errors,
            },
            name="lab-e9-source-producer",
            daemon=True,
        )
        producer.start()

        while (envelope := detector_queue.take()) is not None:
            started = time.perf_counter()
            detector_latency["image_decode_ms"].append(envelope.decode_ms)
            detector_latency["source_release_lag_ms"].append(envelope.source_release_lag_ms)
            detector_latency["queue_wait_ms"].append(
                max(0.0, (started - envelope.decoded_monotonic) * 1000.0)
            )
            try:
                preprocess_started = time.perf_counter()
                tensor = _preprocess(envelope.image, valid_mask, detector_profile)
                detector_latency["preprocess_ms"].append(
                    (time.perf_counter() - preprocess_started) * 1000.0
                )
                output_tensor, request_ms = _infer(
                    args.triton_url, detector_profile["model"], tensor
                )
                detector_latency["triton_request_ms"].append(request_ms)
                post_started = time.perf_counter()
                detections, _rejected = _detections(output_tensor, detector_profile, valid_mask)
                detector_latency["postprocess_ms"].append(
                    (time.perf_counter() - post_started) * 1000.0
                )
                tracking_started = time.perf_counter()
                tracks = tracker.update(detections, envelope.frame_index)
                detector_latency["tracking_ms"].append(
                    (time.perf_counter() - tracking_started) * 1000.0
                )
            except Exception:
                detector_failures += 1
                raise
            completed = time.perf_counter()
            processing_ms = (completed - started) * 1000.0
            result_age_ms = max(0.0, (completed - envelope.scheduled_monotonic) * 1000.0)
            detector_latency["processing_ms"].append(processing_ms)
            detector_latency["result_age_ms"].append(result_age_ms)
            detector_labels.update(str(item["label"]) for item in detections)
            duplicate_pairs += _duplicate_pairs(tracks)
            for track in tracks:
                unique_tracks.add(track.track_id)
                track_labels[track.label] += 1
            semantic_binding = _semantic_binding(
                latest_semantic.snapshot(),
                frame_session_seconds=float(envelope.timeline["session_seconds"]),
                ttl_ms=float(replay["semantic_ttl_ms"]),
            )
            semantic_status[str(semantic_binding["status"])] += 1
            detector_document = {
                "schema_version": DETECTOR_FRAME_SCHEMA,
                "frame_index": envelope.frame_index,
                "source_frame_index": envelope.timeline["source_frame_index"],
                "session_seconds": round(float(envelope.timeline["session_seconds"]), 9),
                "result_age_ms": round(result_age_ms, 6),
                "detections": detections,
                "tracks": [_track_document(track) for track in tracks],
            }
            detector_stream.write(
                json.dumps(
                    detector_document,
                    sort_keys=True,
                    separators=(",", ":"),
                    allow_nan=False,
                )
                + "\n"
            )
            merged_stream.write(
                json.dumps(
                    {
                        "schema_version": MERGED_FRAME_SCHEMA,
                        "frame_index": envelope.frame_index,
                        "source_frame_index": envelope.timeline["source_frame_index"],
                        "session_seconds": detector_document["session_seconds"],
                        "detector_result_age_ms": round(result_age_ms, 6),
                        "tracks": detector_document["tracks"],
                        "semantic": semantic_binding,
                    },
                    sort_keys=True,
                    separators=(",", ":"),
                    allow_nan=False,
                )
                + "\n"
            )
            consumed = int(detector_queue.snapshot()["consumed"])
            if consumed % 100 == 0:
                detector_stream.flush()
                merged_stream.flush()
                semantic_stream.flush()
                gpu_stream.flush()
                _assert_disk_floor(output, args.free_bytes_floor, consumed)
                print(
                    json.dumps(
                        {
                            "phase": "multirate",
                            "detector_consumed": consumed,
                            "detector_dropped": detector_queue.snapshot()["dropped_overflow"],
                            "semantic_consumed": semantic_queue.snapshot()["consumed"],
                            "semantic_dropped": semantic_queue.snapshot()["dropped_overflow"],
                        },
                        sort_keys=True,
                    ),
                    flush=True,
                )

        producer.join(timeout=5)
        if producer.is_alive() or producer_errors:
            raise RuntimeError("LAB E9 producer failed")
        semantic_thread.join(timeout=30)
        if semantic_thread.is_alive():
            raise RuntimeError("LAB E9 semantic consumer did not terminate")
        if semantic_errors:
            raise RuntimeError("LAB E9 semantic consumer failed") from semantic_errors[0]
        detector_stream.flush()
        semantic_stream.flush()
        merged_stream.flush()
        os.fsync(detector_stream.fileno())
        os.fsync(semantic_stream.fileno())
        os.fsync(merged_stream.fileno())

    replay_finished = time.perf_counter()
    detector_state = detector_queue.snapshot()
    semantic_state = semantic_queue.snapshot()
    source_span = (
        float(timeline_rows[-1]["session_seconds"]) - float(timeline_rows[0]["session_seconds"])
    ) / float(replay["speed"])
    replay_wall = replay_finished - replay_started
    scheduled_semantic = ((len(frame_paths) - 1) // int(replay["semantic_sample_every_frames"])) + 1
    detector_fps = int(detector_state["consumed"]) / max(source_span, replay_wall, 1e-9)
    semantic_fps = int(semantic_state["consumed"]) / max(source_span, replay_wall, 1e-9)
    detector_drop_fraction = int(detector_state["dropped_overflow"]) / len(frame_paths)
    semantic_drop_fraction = int(semantic_state["dropped_overflow"]) / scheduled_semantic
    fresh_coverage = semantic_status["fresh"] / max(1, int(detector_state["consumed"]))
    detector_percentiles = {name: _percentiles(values) for name, values in detector_latency.items()}
    semantic_percentiles = {name: _percentiles(values) for name, values in semantic_latency.items()}
    acceptance = profile["acceptance"]
    checks = {
        "detector_queue_bounded": int(detector_state["maximum_depth"])
        <= int(detector_state["capacity"]),
        "detector_accounting": int(detector_state["consumed"])
        + int(detector_state["dropped_overflow"])
        == len(frame_paths),
        "detector_minimum_effective_fps": detector_fps
        >= float(acceptance["detector_minimum_effective_fps"]),
        "detector_maximum_drop_fraction": detector_drop_fraction
        <= float(acceptance["detector_maximum_drop_fraction"]),
        "detector_maximum_p95_result_age_ms": float(detector_percentiles["result_age_ms"]["p95"])
        <= float(acceptance["detector_maximum_p95_result_age_ms"]),
        "semantic_queue_bounded": int(semantic_state["maximum_depth"])
        <= int(semantic_state["capacity"]),
        "semantic_accounting": int(semantic_state["consumed"])
        + int(semantic_state["dropped_overflow"])
        == scheduled_semantic,
        "semantic_minimum_effective_fps": semantic_fps
        >= float(acceptance["semantic_minimum_effective_fps"]),
        "semantic_maximum_drop_fraction": semantic_drop_fraction
        <= float(acceptance["semantic_maximum_drop_fraction"]),
        "semantic_maximum_p95_completion_age_ms": float(
            semantic_percentiles["completion_age_ms"]["p95"]
        )
        <= float(acceptance["semantic_maximum_p95_completion_age_ms"]),
        "minimum_fresh_semantic_coverage": fresh_coverage
        >= float(acceptance["minimum_fresh_semantic_coverage"]),
        "zero_failures": detector_failures == 0 and not semantic_errors,
    }
    accepted = all(checks.values())

    identity = {
        "schema_version": IDENTITY_SCHEMA,
        "job_id": job["job_id"],
        "input_sha256": job["input_sha256"],
        "session_id": job["input"]["session_id"],
        "source_id": job["input"]["source_id"],
        "selection": {
            "frame_count": len(frame_paths),
            "source_start_frame_index": timeline_rows[0]["source_frame_index"],
            "source_end_frame_index": timeline_rows[-1]["source_frame_index"],
            "timeline_start_seconds": timeline_rows[0]["session_seconds"],
            "timeline_end_seconds": timeline_rows[-1]["session_seconds"],
            "timeline_sha256": _sha256(timeline_path),
        },
        "configuration": {
            "pipeline": PIPELINE_ID,
            "profile": profile,
            "profile_sha256": profile_sha256,
            "detector_profile_sha256": detector_profile_sha256,
            "semantic_profile_sha256": semantic_profile_sha256,
            "runner_sha256": _sha256(Path(__file__).resolve(strict=True)),
            "orchestrator_sha256": args.orchestrator_sha256,
            "container_image": args.container_image,
            "valid_fov": valid_fov,
        },
        "models": {
            "detector": detector_profile["model"],
            "detector_files": detector_files,
            "semantic": semantic_profile["model"],
            "semantic_files": semantic_files,
        },
    }
    identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
    result_id = f"e9-multirate-perception-{identity_sha256}"
    metrics = {
        "source_span_seconds": round(source_span, 6),
        "replay_wall_seconds": round(replay_wall, 6),
        "detector": {
            "frames_expected": len(frame_paths),
            "frames_processed": detector_state["consumed"],
            "frames_dropped": detector_state["dropped_overflow"],
            "drop_fraction": round(detector_drop_fraction, 9),
            "effective_frames_per_second": round(detector_fps, 6),
            "queue": detector_state,
            "latency_ms": detector_percentiles,
            "detections": int(sum(detector_labels.values())),
            "unique_confirmed_tracks": len(unique_tracks),
            "track_observations": int(sum(track_labels.values())),
            "same_class_duplicate_pairs_iou_ge_0_8": duplicate_pairs,
        },
        "semantic": {
            "frames_scheduled": scheduled_semantic,
            "frames_processed": semantic_state["consumed"],
            "frames_dropped": semantic_state["dropped_overflow"],
            "drop_fraction": round(semantic_drop_fraction, 9),
            "effective_frames_per_second": round(semantic_fps, 6),
            "queue": semantic_state,
            "latency_ms": semantic_percentiles,
            "binding_status_counts": dict(sorted(semantic_status.items())),
            "fresh_coverage": round(fresh_coverage, 9),
        },
        "gpu_telemetry": gpu_telemetry.summary(),
        "process_peak_rss_mib": round(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024, 3),
        "cuda_peak_memory_allocated_mib": round(torch.cuda.max_memory_allocated() / 2**20, 3),
        "cuda_peak_memory_reserved_mib": round(torch.cuda.max_memory_reserved() / 2**20, 3),
        "disk": {
            "free_bytes_before_replay": disk_before,
            "free_bytes_after_replay": shutil.disk_usage(output).free,
            "free_bytes_floor": args.free_bytes_floor,
        },
    }
    report = {
        "schema_version": REPORT_SCHEMA,
        "result_id": result_id,
        "created_at_utc": datetime.now(UTC)
        .isoformat(timespec="milliseconds")
        .replace("+00:00", "Z"),
        "state": "accepted" if accepted else "rejected",
        "ground_truth": False,
        "identity": identity,
        "runtime": {
            "hostname": platform.node(),
            "platform": platform.platform(),
            "python": platform.python_version(),
            "numpy": np.__version__,
            "torch": torch.__version__,
            "transformers": transformers.__version__,
            "scipy": importlib.metadata.version("scipy"),
            "gpu": torch.cuda.get_device_name(),
        },
        "metrics": metrics,
        "acceptance": {
            "accepted": accepted,
            "checks": checks,
            "navigation_or_safety_accepted": False,
        },
        "limitations": [
            "Recorded source-paced replay, not live K1 transport.",
            (
                "Semantic masks are measured in memory; this gate stores hashes and "
                "class totals, not mask images."
            ),
            "Generic Cityscapes and COCO models are not forest-domain or safety validated.",
            "LiDAR fusion and 3D world-state publication remain downstream gates.",
        ],
    }
    report_path = output / "run-report.json"
    _write_json(report_path, report)
    artifacts = [
        _artifact(
            detector_path,
            "multirate-detector-frames",
            "application/x-ndjson",
            DETECTOR_FRAME_SCHEMA,
        ),
        _artifact(
            semantic_path,
            "multirate-semantic-frames",
            "application/x-ndjson",
            SEMANTIC_FRAME_SCHEMA,
        ),
        _artifact(
            merged_path, "multirate-merged-frames", "application/x-ndjson", MERGED_FRAME_SCHEMA
        ),
        _artifact(gpu_path, "worker-gpu-telemetry", "application/x-ndjson"),
        _artifact(report_path, "multirate-run-report", "application/json", REPORT_SCHEMA),
    ]
    result = {
        "schema_version": RESULT_SCHEMA,
        "result_id": result_id,
        "identity_sha256": identity_sha256,
        "identity": identity,
        "created_at_utc": report["created_at_utc"],
        "acceptance_state": report["state"],
        "ground_truth": False,
        "publication_scope": "recorded-multirate-qualification-only",
        "frames_processed": detector_state["consumed"],
        "artifacts": artifacts,
    }
    _write_json(output / "result.json", result)
    print(
        json.dumps(
            {
                "result_id": result_id,
                "accepted": accepted,
                "detector_fps": round(detector_fps, 6),
                "detector_dropped": detector_state["dropped_overflow"],
                "detector_result_age_p95_ms": detector_percentiles["result_age_ms"]["p95"],
                "semantic_fps": round(semantic_fps, 6),
                "semantic_dropped": semantic_state["dropped_overflow"],
                "semantic_completion_age_p95_ms": semantic_percentiles["completion_age_ms"]["p95"],
                "fresh_semantic_coverage": round(fresh_coverage, 6),
            },
            sort_keys=True,
        ),
        flush=True,
    )
    # A completed rejected qualification is still immutable diagnostic evidence.
    # The manifest state, not the process exit code, carries acceptance.
    return 0


def _assert_disk_floor(path: Path, floor: int, frame: int) -> None:
    if floor < 0:
        raise RuntimeError("LAB E9 disk floor is invalid")
    free = shutil.disk_usage(path).free
    if floor and free < floor:
        raise RuntimeError(f"LAB E9 crossed the D-backed free-space floor at frame {frame}")


def main() -> int:
    args = _arguments()
    if args.command == "preflight":
        return _preflight(args)
    return _run(args)


if __name__ == "__main__":
    raise SystemExit(main())
